From d329fba82aafcb03035a4d70cb48a52e66b711c2 Mon Sep 17 00:00:00 2001 From: Fabio Akita Date: Sat, 29 Aug 2026 00:41:58 -0300 Subject: [PATCH] fix(ci): stop the frozen-CHANGELOG check firing on a merged branch (#525) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ci): stop the frozen-CHANGELOG check firing on a merged branch The check diffed line ranges since the merge base, so any branch that merged `main` after a release saw the new `## [X.Y.Z]` section as lines it had touched — even though the section arrived through the merge. It fired on #508 within a day of being added. It now compares the released half of the file against the base branch directly, which is the question it was always asking: does this branch's copy of an already-released section differ from the base's? A section the base has and the branch does not is a stale branch, not a rewrite, and is allowed. Two pipelines also exited 141 under `pipefail`: `head -1` and `grep -q` both close the pipe on completion, and the resulting SIGPIPE counted as failure. The second made the check announce "HEAD predates " on a branch that contained it. Both replaced with parameter expansion. Controlled both ways rather than assumed: run against 4844020, where #517's entry genuinely sat inside the released [1.33.0], it still fails and names the lines; run against a branch that merged main post-release, it passes. * fix(changelog): move this branch's entry out of the released 1.35.0 section The branch predates v1.35.0. Merging main brought the release heading down over an entry that was written under [Unreleased], so the bullet ended up inside a tagged section. Relocated, not rewritten: [1.35.0] keeps exactly the two entries that shipped in it. Co-Authored-By: Claude Opus 5 --------- Co-authored-by: Claude Opus 5 --- CHANGELOG.md | 9 +++++ scripts/check-changelog-frozen.sh | 56 +++++++++++++++++++------------ 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a6091e3..f645ff28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- The CHANGELOG frozen-section check no longer fires on a branch that merged + `main` after a release. It compared line ranges since the merge base, so a + newly released section arriving through the merge read as lines the branch + had touched. It now compares the released half of the file directly against + the base branch, which is the question it was always asking. Two pipelines + also exited 141 under `pipefail` when `head -1` and `grep -q` closed a pipe + early, so the check reported "HEAD predates" on a branch that did not. + ## [1.35.0] - 2026-08-29 ### Added diff --git a/scripts/check-changelog-frozen.sh b/scripts/check-changelog-frozen.sh index 7af0ecb3..8574a5fd 100755 --- a/scripts/check-changelog-frozen.sh +++ b/scripts/check-changelog-frozen.sh @@ -32,40 +32,54 @@ git rev-parse --verify "$BASE_REF" >/dev/null 2>&1 || { exit 0 } -MERGE_BASE="$(git merge-base "$BASE_REF" HEAD)" -if [[ "$MERGE_BASE" == "$(git rev-parse HEAD)" ]]; then - echo "check-changelog-frozen: HEAD is an ancestor of $BASE_REF; nothing to check." +# Compare the released half of the file directly against the base branch, +# rather than diffing line ranges since the merge base. +# +# The line-range form fired falsely on any branch that merged the base after a +# release: from the old merge base, the whole new `## [X.Y.Z]` section reads as +# lines this branch touched, even though it arrived through the merge. The +# question that matters is simpler — does this branch's copy of an +# already-released section differ from the base branch's copy? +# +# A section the base has and this branch does not is fine (a stale branch, not +# a rewrite), so only sections present in both are compared. + +released_half() { + git show "$1:CHANGELOG.md" 2>/dev/null | awk '/^## \[[0-9]/{f=1} f' +} + +BASE_RELEASED="$(released_half "$BASE_REF")" +HEAD_RELEASED="$(released_half HEAD)" + +if [[ -z "$BASE_RELEASED" ]]; then + echo "check-changelog-frozen: base has no released section yet." exit 0 fi -# First line number of the first released section in the *new* file. -FROZEN_FROM="$(awk '/^## \[[0-9]/{print NR; exit}' CHANGELOG.md)" -if [[ -z "$FROZEN_FROM" ]]; then - echo "check-changelog-frozen: no released section yet." +# The newest released heading on the base. Anything at or below it in HEAD must +# match the base byte for byte. +# `head -1` under `pipefail` exits 141 on SIGPIPE, so read the first line +# without a pipe. +NEWEST="${BASE_RELEASED%%$'\n'*}" +# Substring tests, not pipes: `grep -q` closes the pipe on its first match and +# `pipefail` reports that SIGPIPE as failure, which made this branch look like +# it predated the release. +if [[ "$HEAD_RELEASED" != *"$NEWEST"* ]]; then + echo "check-changelog-frozen: HEAD predates $NEWEST; nothing to compare." exit 0 fi -# Every new-file line number this diff touches, via the unified hunk headers. -TOUCHED="$(git diff --unified=0 "$MERGE_BASE" HEAD -- CHANGELOG.md \ - | awk '/^@@/{ split($3, a, ","); start = a[1] + 0; if (start < 0) start = -start; - count = (a[2] == "" ? 1 : a[2] + 0); - for (i = 0; i < count; i++) print start + i }')" +HEAD_FROM_NEWEST="${HEAD_RELEASED#*"$NEWEST"}" +HEAD_FROM_NEWEST="${NEWEST}${HEAD_FROM_NEWEST}" -BAD="" -for ln in $TOUCHED; do - if (( ln >= FROZEN_FROM )); then - BAD+=" line $ln: $(sed -n "${ln}p" CHANGELOG.md)"$'\n' - fi -done - -if [[ -z "$BAD" ]]; then +if [[ "$HEAD_FROM_NEWEST" == "$BASE_RELEASED" ]]; then echo "CHANGELOG: no released section was modified." exit 0 fi echo "error: this change modifies an already-released CHANGELOG section." >&2 echo >&2 -printf '%s' "$BAD" >&2 +diff <(printf '%s\n' "$BASE_RELEASED") <(printf '%s\n' "$HEAD_FROM_NEWEST") | head -40 >&2 cat >&2 <<'EOF' Released sections are frozen. An entry for unreleased work belongs under