From d30efd236104f207ce9dfa7e75181ce66539bc74 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Sat, 7 Feb 2026 21:26:04 -0300 Subject: [PATCH 1/2] CI: enforce CHANGELOG.md hygiene in pull requests Adds check-changelog-commit.sh that verifies: 1. CHANGELOG.md changes are in dedicated commits 2. Referenced commit hashes exist in the repository 3. Commit link URLs match their reference keys Also adds shellcheck CI job and lint-shell Makefile target. Closes #35 Closes #36 --- .github/scripts/check-changelog-commit.sh | 75 +++++++++++++++++++++++ .github/workflows/changelog.yaml | 22 +++++++ Makefile | 4 ++ 3 files changed, 101 insertions(+) create mode 100755 .github/scripts/check-changelog-commit.sh diff --git a/.github/scripts/check-changelog-commit.sh b/.github/scripts/check-changelog-commit.sh new file mode 100755 index 0000000..396d90a --- /dev/null +++ b/.github/scripts/check-changelog-commit.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Verify CHANGELOG.md hygiene in pull requests: +# 1. CHANGELOG.md changes must be in their own dedicated commit +# 2. Commit hashes referenced in new changelog entries must exist +# in the branch +# 3. Commit link URLs must match their reference keys +# +# Usage: check-changelog-commit.sh +set -euo pipefail + +base="${1:?Usage: check-changelog-commit.sh }" +errors=0 + +# --- Check 1: CHANGELOG.md must be in dedicated commits --- + +for commit in $(git log --format=%H "${base}..HEAD"); do + files=$(git diff-tree --no-commit-id --name-only -r "$commit") + if echo "$files" | grep -q "^CHANGELOG.md$"; then + file_count=$(echo "$files" | wc -l | tr -d ' ') + if [ "$file_count" -gt 1 ]; then + echo "::error::Commit $commit modifies CHANGELOG.md alongside other files." + echo "CHANGELOG.md changes must be in their own dedicated commit." + errors=$((errors + 1)) + fi + fi +done + +# --- Check 2: Referenced commit hashes must exist in the branch --- + +# Get new changelog lines added in this PR +changelog_diff=$(git diff "${base}..HEAD" -- CHANGELOG.md \ + | grep "^+" | grep -v "^+++" || true) + +# Extract short commit hashes from inline references like ([abc1234], ...) +inline_hashes=$(echo "$changelog_diff" \ + | grep -oE '\(\[([0-9a-f]{7,})\]' \ + | grep -oE '[0-9a-f]{7,}' | sort -u || true) + +# Get all commits reachable from HEAD (not just this branch) +for hash in $inline_hashes; do + if ! git cat-file -t "$hash" >/dev/null 2>&1; then + echo "::error::Commit $hash referenced in CHANGELOG.md does not exist." + errors=$((errors + 1)) + fi +done + +# --- Check 3: Link definition URLs must match their keys --- +# Matches lines like: [abc1234]: https://.../commit/xyz7890 +# and verifies abc1234 == xyz7890 + +link_lines=$(echo "$changelog_diff" \ + | grep -E '^\+\[[0-9a-f]{7,}\]: https://.*commit/' || true) + +while IFS= read -r line; do + [ -z "$line" ] && continue + key=$(echo "$line" \ + | grep -oE '\[([0-9a-f]{7,})\]' | head -1 \ + | tr -d '[]') + url_hash=$(echo "$line" \ + | grep -oE 'commit/[0-9a-f]{7,}' \ + | sed 's|commit/||') + if [ -n "$key" ] && [ -n "$url_hash" ] && [ "$key" != "$url_hash" ]; then + echo "::error::Link [$key] points to commit/$url_hash but should point to commit/$key" + errors=$((errors + 1)) + fi +done <<< "$link_lines" + +# --- Summary --- + +if [ "$errors" -gt 0 ]; then + echo "Found $errors changelog error(s)." + exit 1 +fi + +echo "All changelog checks passed." diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index 8bcc123..2f014a6 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -15,3 +15,25 @@ jobs: - uses: tarides/changelog-check-action@v3 with: changelog: CHANGELOG.md + + check-changelog-commit: + name: Check changelog is in dedicated commit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Verify CHANGELOG.md changes are in own commit + run: > + .github/scripts/check-changelog-commit.sh + "${{ github.event.pull_request.base.sha }}" + + shellcheck: + name: Shellcheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install shellcheck + run: sudo apt-get install -y shellcheck + - name: Run shellcheck + run: make lint-shell diff --git a/Makefile b/Makefile index 82a23ca..ffee387 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,10 @@ lint: ## Lint code using ruff lint-fix: ## Lint and fix code using ruff poetry run ruff check --fix . +.PHONY: lint-shell +lint-shell: ## Lint shell scripts using shellcheck + shellcheck .github/scripts/*.sh + .PHONY: typecheck typecheck: ## Run mypy type checker poetry run mypy l9format From 34c970d1190be4116bcc9bf8368ed2cef8c5bdda Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Sat, 7 Feb 2026 21:26:25 -0300 Subject: [PATCH 2/2] CHANGELOG: add entry for changelog hygiene CI (#35, #36) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea92566..71bc5fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ and this project adheres to - Tests now import from `l9format` package directly instead of `l9format.l9format` ([e8aef2e], [#21]) +### Infrastructure + +- CI: enforce CHANGELOG.md changes are in dedicated commits + ([d30efd2], [#35]) + ## [1.4.0] - 2026-02-07 ### Added @@ -132,6 +137,7 @@ and this project adheres to +[d30efd2]: https://github.com/LeakIX/l9format-python/commit/d30efd2 [1dcfbef]: https://github.com/LeakIX/l9format-python/commit/1dcfbef [e8aef2e]: https://github.com/LeakIX/l9format-python/commit/e8aef2e [8f45e82]: https://github.com/LeakIX/l9format-python/commit/8f45e82 @@ -193,3 +199,4 @@ and this project adheres to [#16]: https://github.com/LeakIX/l9format-python/pull/16 [#18]: https://github.com/LeakIX/l9format-python/pull/18 [#21]: https://github.com/LeakIX/l9format-python/issues/21 +[#35]: https://github.com/LeakIX/l9format-python/issues/35