From fa582dc8f5bf6d1ef8dbd1c14df1d232662e72c1 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Mon, 16 Mar 2026 17:18:09 +0100 Subject: [PATCH 1/2] CI: use Makefile targets in workflows and add sequential per-commit testing Install gsed on macOS runners. Use Makefile targets instead of raw commands in both action.yml and the per-commit workflow. Add sequential per-commit testing triggered by the ci:per-commit label. --- .github/scripts/check-ci-per-commit-label.sh | 46 ++++++++++++ .github/scripts/run-ci-per-commit.sh | 41 +++++++++++ .github/workflows/action.yml | 17 +++-- .github/workflows/ci-per-commit.yaml | 75 ++++++++++---------- 4 files changed, 134 insertions(+), 45 deletions(-) create mode 100755 .github/scripts/check-ci-per-commit-label.sh create mode 100755 .github/scripts/run-ci-per-commit.sh diff --git a/.github/scripts/check-ci-per-commit-label.sh b/.github/scripts/check-ci-per-commit-label.sh new file mode 100755 index 0000000..8521d34 --- /dev/null +++ b/.github/scripts/check-ci-per-commit-label.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Check if the ci:per-commit label is present. +# +# For pull_request events, checks labels from the event payload. +# For push events, looks up the originating PR via the GitHub API. +# +# Usage: +# check-ci-per-commit-label.sh [labels_json] +# +# Arguments: +# event_name - "pull_request" or "push" +# repository - e.g. "owner/repo" +# sha - commit SHA +# labels_json - JSON array of label names (required for pull_request) +# +# Output: +# Prints has_label=true or has_label=false (for GITHUB_OUTPUT) +set -euo pipefail + +event_name="${1:?Usage: check-ci-per-commit-label.sh [labels_json]}" +repository="${2:?Missing repository}" +sha="${3:?Missing sha}" +labels_json="${4:-}" + +HAS_LABEL="false" + +if [ "$event_name" = "pull_request" ]; then + if echo "$labels_json" | grep -q "ci:per-commit"; then + HAS_LABEL="true" + fi +else + PRS=$(gh api \ + "repos/${repository}/commits/${sha}/pulls" \ + --jq '.[].number') + for pr in $PRS; do + LABELS=$(gh api \ + "repos/${repository}/pulls/${pr}" \ + --jq '.labels[].name') + if echo "$LABELS" | grep -q "^ci:per-commit$"; then + HAS_LABEL="true" + break + fi + done +fi + +echo "has_label=${HAS_LABEL}" diff --git a/.github/scripts/run-ci-per-commit.sh b/.github/scripts/run-ci-per-commit.sh new file mode 100755 index 0000000..3b6da44 --- /dev/null +++ b/.github/scripts/run-ci-per-commit.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Run the full CI checks on each commit in the given range, sequentially. +# Stops at the first commit that fails. +# +# Usage: +# run-ci-per-commit.sh +# +# Arguments: +# base_sha - the base commit (exclusive) +# head_sha - the head commit (inclusive) +set -euo pipefail + +base="${1:?Usage: run-ci-per-commit.sh }" +head="${2:?Missing head_sha}" + +commits=$(git rev-list --reverse "${base}..${head}") +total=$(echo "$commits" | wc -l | tr -d ' ') +current=0 + +for commit in $commits; do + current=$((current + 1)) + short=$(git rev-parse --short "$commit") + subject=$(git log -1 --format=%s "$commit") + echo "" + echo "=== [$current/$total] Testing ${short}: ${subject} ===" + echo "" + + git checkout --quiet "$commit" + make install + make lint + make check-format + make check-sort + make typecheck + make test + + echo "" + echo "=== [$current/$total] PASSED: ${short} ===" +done + +echo "" +echo "All ${total} commit(s) passed CI checks." diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 3110b2a..ff8122e 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -15,18 +15,21 @@ jobs: uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python-version }} + - name: Install gsed (macOS) + if: runner.os == 'macOS' + run: brew install gnu-sed - name: Install dependencies - run: uv sync + run: make install - name: lint - run: uv run ruff check . + run: make lint - name: format check - run: uv run black --check . + run: make check-format - name: sort-check - run: uv run isort --check-only . + run: make check-sort - name: typecheck - run: uv run mypy l9format + run: make typecheck - name: test - run: uv run pytest + run: make test - name: security-audit - run: uv run pip-audit + run: make security-check diff --git a/.github/workflows/ci-per-commit.yaml b/.github/workflows/ci-per-commit.yaml index 43fc540..ab408fd 100644 --- a/.github/workflows/ci-per-commit.yaml +++ b/.github/workflows/ci-per-commit.yaml @@ -4,6 +4,10 @@ on: push: branches: - main + pull_request: + types: [labeled] + branches: + - main jobs: check-label: @@ -12,53 +16,48 @@ jobs: outputs: has_label: ${{ steps.check.outputs.has_label }} steps: - - name: Find associated PR and check label + - uses: actions/checkout@v6 + - name: Check for label id: check env: GH_TOKEN: ${{ github.token }} run: | - PRS=$(gh api \ - "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \ - --jq '.[].number') - HAS_LABEL="false" - for pr in $PRS; do - LABELS=$(gh api \ - "repos/${{ github.repository }}/pulls/${pr}" \ - --jq '.labels[].name') - if echo "$LABELS" | grep -q "^ci:per-commit$"; then - HAS_LABEL="true" - break - fi - done - echo "has_label=${HAS_LABEL}" >> "$GITHUB_OUTPUT" + .github/scripts/check-ci-per-commit-label.sh \ + "${{ github.event_name }}" \ + "${{ github.repository }}" \ + "${{ github.sha }}" \ + '${{ toJSON(github.event.pull_request.labels.*.name) }}' \ + >> "$GITHUB_OUTPUT" - ci: - name: CI + ci-per-commit: + name: CI per commit needs: check-label if: needs.check-label.outputs.has_label == 'true' - strategy: - fail-fast: false - matrix: - python-version: ["3.11", "3.12", "3.13"] - os: ["ubuntu-latest", "macos-latest", "windows-latest"] - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@v6 with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: uv sync - - name: lint - run: uv run ruff check . - - name: format check - run: uv run black --check . - - name: sort-check - run: uv run isort --check-only . - - name: typecheck - run: uv run mypy l9format - - name: test - run: uv run pytest - - name: security-audit - run: uv run pip-audit + python-version: "3.13" + - name: Determine commit range + id: range + env: + GH_TOKEN: ${{ github.token }} + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + BASE="${{ github.event.pull_request.base.sha }}" + HEAD="${{ github.event.pull_request.head.sha }}" + else + BASE="${{ github.event.before }}" + HEAD="${{ github.sha }}" + fi + echo "base=${BASE}" >> "$GITHUB_OUTPUT" + echo "head=${HEAD}" >> "$GITHUB_OUTPUT" + - name: Run CI on each commit + run: | + .github/scripts/run-ci-per-commit.sh \ + "${{ steps.range.outputs.base }}" \ + "${{ steps.range.outputs.head }}" From 265fec6cc018ca0ddd8ec20ef25c73d4721e4ebc Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Mon, 16 Mar 2026 17:18:25 +0100 Subject: [PATCH 2/2] CHANGELOG: use Makefile targets in CI and sequential per-commit testing --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3934b17..86ded19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to and switch CI to `astral-sh/setup-uv` ([5f4fc51], [#56]) - CI: add per-commit workflow triggered by `ci:per-commit` label for easier bisect and revert ([bc4872d], [#59]) +- CI: use Makefile targets in workflows and add sequential per-commit testing + ([fa582dc], [#62]) ## [1.4.0] - 2026-02-09 @@ -165,6 +167,7 @@ and this project adheres to +[fa582dc]: https://github.com/LeakIX/l9format-python/commit/fa582dc [bc4872d]: https://github.com/LeakIX/l9format-python/commit/bc4872d [6c9eecd]: https://github.com/LeakIX/l9format-python/commit/6c9eecd [5f4fc51]: https://github.com/LeakIX/l9format-python/commit/5f4fc51 @@ -258,4 +261,5 @@ and this project adheres to [#51]: https://github.com/LeakIX/l9format-python/pull/51 [#56]: https://github.com/LeakIX/l9format-python/pull/56 [#59]: https://github.com/LeakIX/l9format-python/issues/59 +[#62]: https://github.com/LeakIX/l9format-python/pull/62 [#43]: https://github.com/LeakIX/l9format-python/issues/43