build: assemble the changelog from per-PR fragments in changelog.d - #306
Merged
Merged
Conversation
Each pull request adds changelog.d/<issue>.<section>.md instead of editing ## [Unreleased], so parallel pull requests no longer conflict. The release script checks every fragment before writing anything, merges them under their Keep a Changelog headings after any hand-written entry, appends the issue link from the file name, and deletes them; the release job commits the deletions. Closes #213
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #213
Pull requests stop editing
## [Unreleased]. Each one adds a fragment tochangelog.d/, and the release merges the fragments intoCHANGELOG.mdand deletes them, so parallel PRs no longer conflict over the changelog.How a contributor writes a fragment
changelog.d/<issue>.<section>.md, where section isadded,changed,deprecated,removed,fixedorsecurity. The file holds one entry: bold one-line summary first, without the leading-and without the issue link. The release adds both, taking the link from the file name.changelog.d/65.added.md:is released under
### AddedasA second entry for the same issue and section goes in
65.added.2.md, then.3.md, and so on.Design decisions
-and no link. Leaving out the bullet means a contributor cannot get the list marker or the continuation indent wrong; the script adds-and indents continuation lines by two. Leaving out the link keeps it consistent with the file name. Both mistakes are caught: a leading-or a link to the fragment's own issue fails with a message saying what to do. Links to other issues/PRs in the text are fine..<n>.md(n >= 2). That is simpler to validate than several entries in one file: a top-level-/*line or a markdown heading after the first line is rejected, pointing at<issue>.<section>.2.md..1.mdis rejected so there is exactly one name for the first entry. Nested lists are fine when indented; if the fragment ends in one, the link goes in its own paragraph instead of onto the last nested item.n. Any other hand-written###section (e.g.Documentation) is kept after those; loose entries above the first heading stay on top. With no fragments the changelog is left byte-for-byte as before.[Unreleased]compare link, the same source the compare links use, not hard-coded.main()reads and checks every fragment, assembles, promotes and builds the release notes before writing anything; only then does it writeCHANGELOG.md/the notes and delete the fragments. All malformed fragments are listed in one error.--dry-rundeletes nothing.changelog.dnext to--changelog(not the cwd), so the existing tests on temporary changelogs can never touch the repository's fragments.--fragmentsoverrides it.README.mdand dotfiles are ignored; anything else that is not a well-formed fragment (including a subdirectory) is an error.test_the_repository_changelog_can_be_releasednow merges the real pending fragments before promoting (and only adds its placeholder when there are neither fragments nor hand-written entries), and asserts every fragment's issue link reaches the release notes. A malformed fragment therefore fails CI in the PR that adds it, not at release time.release.yml (kept minimal; #302's SHA pins should rebase cleanly)
Two lines of behaviour:
releasejob, "Commit the release": an artifact cannot carry a deletion, so the job deletes the fragments itself beforegit addand stageschangelog.d. It is the same commitbuildchecked out, andbuildfails on anything inchangelog.d/other than fragments,README.mdand dotfiles, sofind changelog.d -maxdepth 1 -type f -name '*.md' ! -name README.md ! -name '.*' -deleteremoves exactly the fragments the script consumed.git diff --statalso listschangelog.d, so a rehearsal shows the fragment deletions the release commit would carry.Nothing else changed: the dry-run path still runs the script without
--dry-runinbuild, writes nothing permanent, and therelease/publishjobs are still skipped.Docs
changelog.d/README.md(kept in git; describes the convention).docs/DEVELOPMENT.md: new "Changelog fragments" section; Releasing steps updated.docs/CONTRIBUTING.mdandi18n/zh-TW/docs/CONTRIBUTING.md: PR step 2 now asks for a fragment. There is no zh-TW DEVELOPMENT.md and no PR template in the repo.CHANGELOG.mdheader paragraph: entries come from fragments,## [Unreleased]is usually empty between releases.CLAUDE.md: one line on the convention.No changelog fragment for this PR: it is dev tooling with no user-facing change.
Rehearsal (throwaway clone of this branch)
Three fake merged PRs:
296.fixed.md,296.fixed.2.md,297.added.md(ending in a nested list), plus one entry hand-written under## [Unreleased] / ### Fixed. Thenuv version --bump patchand the exact workflow invocationuv run python scripts/changelog_release.py --version "$VERSION" --release-notes release-notes.md(exit 0).Dry-run diff stat:
CHANGELOG.md:release-notes.md:releasejob simulated on a fresh clone of the same commit with the artifact files copied in, running the new "Commit the release" lines verbatim: the commitchore: release v0.3.9carries the same six-file stat, andgit ls-files changelog.dafterwards is justchangelog.d/README.md.Malformed fragments, same invocation (exit 1, nothing written, fragments kept, no
release-notes.md):No workflow was dispatched.
Checks
uv run pytest: 891 passed, 191 skipped (live Redis/Memcached, no servers started); coverage 94% total,scripts/changelog_release.py100%uv run ruff check fastapi_cachex tests scripts,ruff format --check: cleanuv run mypy fastapi_cachex --strict,mypy tests,mypy scripts: cleanuv run pre-commit run --all-files: all passedzensical build --strict(en and zh-TW): no issuesFollow-up (out of scope)
An optional CI check that a PR either adds a
changelog.d/fragment or carries ano-changeloglabel (the label does not exist yet).