Skip to content

Commit 24529f1

Browse files
asimclaudeCopilot
authored
ci(release): roll CHANGELOG [Unreleased] into the version on tag (#4904)
* ci(release): roll CHANGELOG [Unreleased] into the version on tag The changelog once drifted five releases behind the tags, with everything piled under [Unreleased] (reconstructed in #4898), because releases are cut automatically but the rollup was manual. Mechanize it: when the release workflow cuts vX.Y.Z and [Unreleased] has entries, it rewrites the header into an empty [Unreleased] plus a dated [X.Y.Z] section carrying those entries, commits, pushes to master with the release PAT, and tags the rolled commit. If the push is rejected (branch protection), it warns and tags the current HEAD — a release is never blocked on the docs commit. With no entries there is nothing to roll and the step is skipped. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent bd782e2 commit 24529f1

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

.github/workflows/loop-release.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,63 @@ jobs:
9595
9696
git config user.name "loop release bot"
9797
git config user.email "noreply@users.noreply.github.com"
98+
99+
# Roll [Unreleased] into the new version's section BEFORE tagging, so
100+
# the changelog can never drift behind the tags again (it once fell
101+
# five releases behind, with everything piled under [Unreleased]).
102+
# The rolled commit is what gets tagged. If master rejects the push
103+
# (e.g. branch protection), fall back to tagging the current HEAD and
104+
# say so loudly — a release must not be blocked on the docs commit.
105+
if printf '%s\n' "$UNRELEASED" | grep -qE '^### '; then
106+
DATE=$(date -u +%Y-%m-%d)
107+
if DATE="$DATE" NEXT="$NEXT" python3 - <<'PY'
108+
import os
109+
import sys
110+
111+
path = "CHANGELOG.md"
112+
next_ver = os.environ["NEXT"].lstrip("v")
113+
date = os.environ["DATE"]
114+
115+
with open(path, "r", encoding="utf-8") as f:
116+
txt = f.read()
117+
118+
needle = "## [Unreleased]\n"
119+
placeholder = "\n\n_Nothing yet — rolled into a version section on each release._\n\n---\n\n"
120+
121+
pos = txt.find(needle)
122+
if pos == -1:
123+
sys.exit(1)
124+
125+
after = txt[pos + len(needle):]
126+
# If the placeholder block is still present under [Unreleased], strip it so it
127+
# does not get carried into the versioned section.
128+
if after.startswith(placeholder):
129+
after = after[len(placeholder):]
130+
# Normalize spacing so the version header is followed by exactly one blank line.
131+
after = after.lstrip("\n")
132+
133+
out = txt[:pos + len(needle)] + placeholder + f"## [{next_ver}] - {date}\n\n" + after
134+
135+
with open(path, "w", encoding="utf-8") as f:
136+
f.write(out)
137+
PY
138+
then
139+
git add CHANGELOG.md
140+
git commit -m "docs(changelog): roll [Unreleased] into ${NEXT#v}"
141+
if git push "https://x-access-token:${RELEASE_TOKEN}@github.com/${REPO}.git" HEAD:master; then
142+
echo "Rolled CHANGELOG [Unreleased] into ${NEXT#v}."
143+
else
144+
echo "::warning::CHANGELOG roll push to master was rejected — tagging without it. Roll [Unreleased] into ${NEXT#v} manually."
145+
git reset --hard HEAD^
146+
fi
147+
else
148+
echo "::warning::CHANGELOG [Unreleased] header not found in the expected shape — tagging without the roll."
149+
git checkout -- CHANGELOG.md
150+
fi
151+
else
152+
echo "CHANGELOG [Unreleased] has no entries — nothing to roll."
153+
fi
154+
98155
git tag -a "$NEXT" -m "Release $NEXT — automated $KIND ($COUNT commits since $LATEST)"
99156
git push "https://x-access-token:${RELEASE_TOKEN}@github.com/${REPO}.git" "$NEXT"
100157
echo "Pushed $NEXT."

0 commit comments

Comments
 (0)