changelog: Prevent merge conflicts on NEXT_CHANGELOG by fragmenting it into files#5534
Conversation
Integration test reportCommit: 47ad6f0
8 interesting tests: 4 RECOVERED, 4 SKIP
Top 10 slowest tests (at least 2 minutes):
|
Contributors keep adding entries to the single, append-only NEXT_CHANGELOG.md, so concurrent PRs constantly conflict and force a no-op rebase plus full CI rerun. Introduce .nextchanges/<section>/<name>.md fragments instead: each PR adds its own file, so two PRs never touch the same path and never conflict. The filename is arbitrary (a feature name or PR number) and an entry is just a sentence — creatable straight from the GitHub UI; the leading bullet and a (#NNNN) reference are both optional. tools/collate_changelog.py folds the fragments into the matching NEXT_CHANGELOG.md sections at release time, leaving the release tooling (internal/genkit/tagging.py) to consume NEXT_CHANGELOG.md unchanged. The directory name matches databricks-sdk-py's .nextchanges/ for cross-repo consistency. 'task changelog-check' validates fragment placement and runs as part of 'task checks'. The bare 'cli' gitignore entry is anchored to '/cli' so it no longer ignores .nextchanges/cli/. Co-authored-by: Isaac
A manually dispatched workflow that runs tools/collate_changelog.py, expands any (#NNNN) references to links, and opens a single 'Collate changelog fragments' PR via peter-evans/create-pull-request (matching the bump-vuln-deps pattern of opening a PR rather than pushing to main). Run it and merge the PR before dispatching the 'tagging' workflow. With no fragments present the collator is a no-op, so no PR is opened. Co-authored-by: Isaac
Update the contributor-facing guidance to add a .nextchanges/<section>/<name>.md fragment instead of editing NEXT_CHANGELOG.md: each PR adds its own file so entries never conflict, the filename is arbitrary, the leading bullet and the PR link are optional, and it can be created from the GitHub UI. These are the only two docs that described the old workflow. Co-authored-by: Isaac
6d8d52d to
7707dd1
Compare
Temporary commit for review (paired with the collation commit that follows, both to be reverted). Adds 8 .nextchanges/ fragments exercising every permutation: all five sections (empty and populated), * / - / no bullet marker, no ref / (#N) / bare #N / pre-expanded link, single / multi-bullet / multi-line continuation, and PR-number vs feature-name filenames. Co-authored-by: Isaac
… after review) Temporary commit for review (revert this and the preceding 'add test changelog fragments' commit). Ran 'task changelog-collate', which folds every .nextchanges/ fragment into the matching NEXT_CHANGELOG.md section, expands (#NNNN) references to links, and removes the consumed fragment files. Co-authored-by: Isaac
Addresses review feedback: invoke 'task changelog-collate' (via go tool) instead of duplicating the collate + update_github_links steps inline, so the sequence stays defined in one place (Taskfile.yml). Swaps the uv setup for Go accordingly. Co-authored-by: Isaac
Addresses review feedback: drop the 'create from the GitHub UI' note (this skill is for agents, which don't use the UI) and clarify that NNNN in (#NNNN) is the PR number. Co-authored-by: Isaac
At release time the changelog is built from the per-PR .nextchanges/ fragments and written straight into CHANGELOG.md. The release script is synced verbatim from universe (openapi/tagging/tagging.py) and shared across the SDK repos, so it must stay pristine — editing it in place would be clobbered by the next `genkit update-sdk` and would diverge a shared file. Keep it pristine by renaming the synced copy to internal/genkit/tagging_upstream.py and adding a repo-owned internal/genkit/tagging.py wrapper (despite the name, it's hand-maintained). The wrapper imports tagging_upstream, rebinds two module-level seams — get_next_tag_info (render the body from .nextchanges/<section>/*.md) and clean_next_changelog (delete the consumed fragments and bump .nextchanges/version) — adds a GitHubRepo.delete_file helper, then delegates to the untouched process() for all commit/tag/race/recovery logic. The release version is read from .nextchanges/version (bumped to the next minor after each release; edit it to cut a patch/major) — the role NEXT_CHANGELOG.md's "## Release vX.Y.Z" header played upstream. Because the wrapper is named tagging.py, tagging.yml runs it with the same `uv run tagging.py` as upstream — the only divergence from the synced workflow is the internal/genkit/ path. generate-genkit relocates the synced file to tagging_upstream.py and keeps the wrapper + repo-owned tagging.yml. ruff.toml excludes tagging_upstream.py (synced) and lints the wrapper. Co-authored-by: Isaac
With the tagging.py wrapper rendering .nextchanges/ straight into CHANGELOG.md, the release-time collate step is no longer needed. Removes the changelog-collate workflow and the collate mode of the tools script — which becomes tools/validate_nextchanges.py, a PR-time validator (run by `task changelog-check`) that checks fragment placement and the .nextchanges/version file. update_github_links.py no longer processes NEXT_CHANGELOG.md. NEXT_CHANGELOG.md itself is left in the tree for now (unused); a follow-up cutover PR deletes it. Co-authored-by: Isaac
The .nextchanges/ README, the pr-checklist skill, and the changelog-guard message now describe fragments being rendered straight into CHANGELOG.md at release, and the README documents the .nextchanges/version file. Co-authored-by: Isaac
…og-merge-conflicts # Conflicts: # Taskfile.yml # ruff.toml
9f0097f to
74f3ddd
Compare
Temporary commit for review (revert this and the preceding "add test changelog fragments" commit). Simulates the release locally: renders the .nextchanges/ fragments into a dated CHANGELOG.md section (blank line after each heading, all markers normalized to ` * `), deletes the consumed fragments, and bumps .nextchanges/version to the next minor — what release_tagging.py does at release time via the GitHub API. Co-authored-by: Isaac
This reverts commit a339ab0.
This reverts commit e925f8d.
Point the rule at the .nextchanges/ fragment workflow instead of NEXT_CHANGELOG.md, and extend its globs to trigger on .nextchanges/ edits too. Co-authored-by: Isaac
validate_nextchanges.py now walks every file under .nextchanges/ (not just *.md) and flags anything that isn't a section fragment or known scaffolding (version, README.md, .gitkeep): stray root files, non-.md files in a section, wrong-depth paths, and unknown section directories. This catches misplaced files up front rather than having them silently ignored by the release render. Co-authored-by: Isaac
The `links` task now processes .nextchanges/ fragments in addition to CHANGELOG.md (update_github_links.py's default file set includes both), so raw PR references in fragments are expanded at PR time and enforced by CI's `git diff --exit-code`. release_tagging.py no longer carries its own _expand_pr_links reimplementation — it renders the already-expanded fragments verbatim. One canonical link-expander, no drift. Co-authored-by: Isaac
Make explicit that raw `(#5464)` refs must be expanded at PR time (`task fmt` / `task links`, enforced by CI) — the release renders fragments verbatim and does not expand links, so a raw ref left in a fragment fails CI rather than being handled later. Co-authored-by: Isaac
| uv run --locked internal/genkit/tagging.py --package "$PACKAGES" | ||
| uv run --locked internal/genkit/release_tagging.py --package "$PACKAGES" | ||
| else | ||
| uv run --locked internal/genkit/tagging.py | ||
| uv run --locked internal/genkit/release_tagging.py |
There was a problem hiding this comment.
expected and in line with updated tagging.yml rewrite in Taskfile
| tagging.validate_git_root() | ||
| tagging.init_github() | ||
| tagging.process() |
There was a problem hiding this comment.
consider an upstream change to have a single main()
|
|
||
| def install_nextchanges() -> None: | ||
| """Rebind the tagging seams to the CLI's .nextchanges behavior.""" | ||
| tagging.GitHubRepo.delete_file = _delete_file |
There was a problem hiding this comment.
consider enhancing the upstream tagging.py implementation of GitHubRepo with this method to avoid monkey-patching here
| name = "certifi" | ||
| version = "2026.2.25" | ||
| source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } | ||
| source = { registry = "https://pypi.org/simple" } |
There was a problem hiding this comment.
local githook overwrote this - will revert during next genkit run until #5830
| It injects that behavior by rebinding two module-level seams in the upstream | ||
| module (``get_next_tag_info`` and ``clean_next_changelog``, both called by name | ||
| from ``process_package``/``preview_tag_infos``) and then delegates to the | ||
| untouched ``process()`` for all commit/tag/race/recovery logic. |
There was a problem hiding this comment.
How do we make sure this doesn't regress?
| fragment and is normalized; continuation lines are left as authored. | ||
|
|
||
| Raw PR references (``(#1234)``/``#1234``) in fragments are converted to | ||
| markdown links before release by ``tools/update_github_links.py`` (the |
There was a problem hiding this comment.
Can we remove this script once all of this lands?
There was a problem hiding this comment.
tools/update_github_links.py? No, that's still used in task links / task checks.
Maybe misleading part of the docstring here that can just be omitted entirely - render_nextchanges just takes the fragments verbatim
There was a problem hiding this comment.
I think its purpose was only to check NEXT_CHANGELOG.md.
| fragment and is normalized; continuation lines are left as authored. | ||
|
|
||
| Raw PR references (``(#1234)``/``#1234``) in fragments are converted to | ||
| markdown links before release by ``tools/update_github_links.py`` (the |
There was a problem hiding this comment.
tools/update_github_links.py? No, that's still used in task links / task checks.
Maybe misleading part of the docstring here that can just be omitted entirely - render_nextchanges just takes the fragments verbatim
Co-authored-by: Jan N Rose <janniklas.rose@gmail.com>
Integration test reportCommit: b14ffa3
22 interesting tests: 11 FAIL, 5 flaky, 3 RECOVERED, 2 SKIP, 1 KNOWN
Top 50 slowest tests (at least 2 minutes):
|
Changes
.nextchanges/scaffolding (one file per entry, grouped by section)CHANGELOG.mdvia a repo-ownedinternal/genkit/release_tagging.pywrapper around the syncedtagging.py.nextchanges/version(auto-bumped each release)check-changelogTaskfile target;validate_nextchanges.pyvalidates fragment placement + the version fileWhy
Prevent common merge conflicts on the file during busy/productive times, requiring manual action to rebase.
Why not
merge=unionin .gitattributes? Easy quality of life for local, but unfortunately not supported by GitHub.Tests
.nextchanges/(mixed*/-/ no marker)CHANGELOG.md(blank line after each heading, markers normalized to*), delete fragments, bumpversionvalidate_nextchanges.pyrejects bad entries (each exits 1):not in a known section directoryempty fragment.nextchanges/version→missing; expected the next release versionnot a valid semver versionRollout (follow-up)
This PR already renders
.nextchanges/directly intoCHANGELOG.mdat release.NEXT_CHANGELOG.mdis intentionally left in the tree, now unused, to keep this PR focused.Follow-up cutover PR: #5831
git rm NEXT_CHANGELOG.mdonce nothing references it. It's split out so this PR is easy to review and revert; the deletion is mechanical.This pull request was written by Isaac, an AI coding agent.