Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/scripts/check-ci-per-commit-label.sh
Original file line number Diff line number Diff line change
@@ -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 <event_name> <repository> <sha> [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 <event_name> <repository> <sha> [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}"
41 changes: 41 additions & 0 deletions .github/scripts/run-ci-per-commit.sh
Original file line number Diff line number Diff line change
@@ -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 <base_sha> <head_sha>
#
# Arguments:
# base_sha - the base commit (exclusive)
# head_sha - the head commit (inclusive)
set -euo pipefail

base="${1:?Usage: run-ci-per-commit.sh <base_sha> <head_sha>}"
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."
17 changes: 10 additions & 7 deletions .github/workflows/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

75 changes: 37 additions & 38 deletions .github/workflows/ci-per-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
push:
branches:
- main
pull_request:
types: [labeled]
branches:
- main

jobs:
check-label:
Expand All @@ -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 }}"
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -165,6 +167,7 @@ and this project adheres to

<!-- Commit links -->

[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
Expand Down Expand Up @@ -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
Loading