chore: replace changelog merge=union with changelog.d fragments - #501
Draft
alex-clickhouse wants to merge 3 commits into
Draft
chore: replace changelog merge=union with changelog.d fragments#501alex-clickhouse wants to merge 3 commits into
alex-clickhouse wants to merge 3 commits into
Conversation
GitHub ignores .gitattributes merge drivers when merging pull requests (github/community discussion #9288), so the CHANGELOG.md / RELEASENOTES.md merge=union added in #489 never took effect where the conflicts actually happen. Union merge is not the right answer locally either: it resolves by interleaving both sides' lines, which lands entries under the wrong heading. Replace it with the fragment pattern used by towncrier and changesets. Each change drops its own file in changelog.d/, so two pull requests add two distinct paths and git has nothing to reconcile. scripts/changelog.cs folds them into CHANGELOG.md at release time. CHANGELOG.md is now the single source of truth. RELEASENOTES.md is generated from it by dropping every section below v1.0.0 and the empty Unreleased stub, which removes the duplicated prose both files carried (they were byte-identical over their overlap apart from three drifted lines) and the need to write every entry twice. The 23 pending Unreleased entries are migrated to fragments named after the pull request that introduced them. Their bodies were extracted by line number rather than retyped: --release reproduces the section with no prose line changed, only reordered by PR number within each section. scripts/changelog.cs is a .NET 10 file-based app with no dependencies, and lives outside every project directory so the solution build ignores it. Co-Authored-By: Claude <noreply@anthropic.com>
alex-clickhouse
force-pushed
the
chore/changelog-fragments
branch
from
August 3, 2026 12:42
7fac529 to
198560c
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR replaces the previously attempted merge=union approach for changelog conflict avoidance with a fragment-based workflow (changelog.d/), backed by a new scripts/changelog.cs tool and updated docs/review guidance. This fits the repo by making changelog updates conflict-free across concurrent PRs and by generating RELEASENOTES.md from CHANGELOG.md as a single source of truth.
Changes:
- Add
scripts/changelog.csto scaffold, validate, render, and releasechangelog.d/fragments, and to generate/syncRELEASENOTES.md. - Move existing “Unreleased” prose out of
CHANGELOG.md/RELEASENOTES.mdinto per-change fragment files underchangelog.d/. - Update contributor/agent docs and review-skill checklists to require fragments and treat
RELEASENOTES.mdas generated.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/changelog.cs | New tool to manage changelog fragments and generate release notes. |
| CHANGELOG.md | Removes “Unreleased” body so entries come only from fragments. |
| RELEASENOTES.md | Drops “Unreleased” stub; keeps release notes content generated from changelog. |
| changelog.d/README.md | Documents the fragment workflow and naming/categories. |
| changelog.d/496-getschema-columns-where-clause.fixes.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/495-getschema-columns-dispose-command.fixes.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/482-typed-array-reads.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/481-small-tuple-construction.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/480-presize-map-read-dictionary.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/477-midstream-exception-detection.fixes.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/474-insertoptions-copy-drops-options.fixes.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/457-request-uri-allocations.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/453-dynamic-type-singletons.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/451-pooled-response-read-buffer.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/448-fixed-size-read-allocations.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/446-schema-table-numeric-scale.fixes.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/444-insert-serializer-buffer.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/442-stream-insert-payload.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/442-memorystreammanager-obsolete.deprecations.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/441-empty-http-error-body.fixes.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/437-pooled-compressor-write-buffer.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/434-poco-insert-boxing.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/432-decimal-write-allocations.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/431-builtin-lz4-codec.features.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/427-pluggable-insert-compression.features.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/420-variant-write-type-lookup.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| changelog.d/392-useformdataparameters-connection-string.improvements.md | Changelog fragment extracted from prior Unreleased entry. |
| CONTRIBUTING.md | Adds contributor guidance for fragments and release workflow usage. |
| AGENTS.md | Updates agent guidance to use fragments and the new CI gate. |
| .gitattributes | Removes reliance on changelog merge drivers; documents fragment approach. |
| .github/workflows/changelog.yml | Adds CI validation/rendering for fragments and generated release notes. |
| .claude/skills/review/SKILL.md | Updates review checklist to require fragments and treat notes as generated. |
| .agents/skills/review/SKILL.md | Same review-checklist update (mirrored skill). |
Both outputs of the release workflow are permanent: a NuGet package can only be delisted, never unpublished, and RELEASENOTES.md is baked into the package via PackageReleaseNotes. So dispatching a release before the changelog.d/ fragments have been folded in ships a package whose notes describe the previous version, silently, with nothing to roll back. Add `--verify-release <version>`, which asserts no fragments are still pending, that CHANGELOG.md's newest released section is the version being released, and that RELEASENOTES.md is in sync. Run it as the first job in release.yml, gating build (and therefore sign, push and the GitHub release). It compares base versions rather than section titles, because release candidates are cut against the section for the version they lead up to -- 1.3.0-rc1 shipped against the v1.3.0 section, so an exact title match would have blocked it. Release inputs are also bare (1.3.0) while changelog sections carry a v prefix, which the existing version normalization already bridges. The version reaches the script through the environment rather than being interpolated into the run script, so the workflow_dispatch input cannot inject shell. Co-Authored-By: Claude <noreply@anthropic.com>
Addresses the Copilot review on #501. The fragment-body validation accepts both '* ' and '- ' as the leading bullet, but the failure message named only '* ', understating what passes. The stray double space before "Note:" was fixed in CHANGELOG.md rather than in RELEASENOTES.md, since the latter is now generated and would be overwritten by --sync-notes; the notes are regenerated here instead. Co-Authored-By: Claude <noreply@anthropic.com>
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.
Problem
The
merge=unionattributes onCHANGELOG.md/RELEASENOTES.mdadded in #489 don't work: GitHub ignores.gitattributesmerge drivers when merging pull requests (github/community#9288), which is exactly where the conflicts happen.It isn't the right answer locally either — union merge resolves by interleaving both sides' lines, so entries land under the wrong heading and can duplicate. It removed conflicts without producing correct output.
Approach
The fragment pattern, as used by towncrier (Python) and changesets (JS). Each change drops its own file in
changelog.d/, so two PRs add two distinct paths and git has nothing to reconcile. No merge driver required.scripts/changelog.csis a .NET 10 file-based app — no dependencies, ~280 lines, 0.33s warm. It lives outside every project directory, so the solution build ignores it.--new <category> <name>--check--renderUnreleasedsection--release <version>--sync-notesRELEASENOTES.mdRELEASENOTES.md is now generated
The two files were byte-identical over their overlap apart from three drifted lines —
RELEASENOTES.mdwas justCHANGELOG.mdtruncated atv1.0.0. So every PR was writing the same prose twice, doubling the conflict surface for no extra information.CHANGELOG.mdis now the single source of truth;RELEASENOTES.mdis generated from it (it ships in the package viaPackageReleaseNotes). CI fails if it drifts.The two behave oppositely at release time: CHANGELOG is prepended to and never rewritten; RELEASENOTES is fully replaced, so sections fall off the bottom when
ReleaseNotesFlooris bumped at a major.Verification
Fragment bodies were extracted by line number, not retyped. The round-trip is proven:
--releasereproduces main'sUnreleasedsection with zero prose lines changed — a sorted-line diff shows only the two added header lines.That mattered for one non-obvious reason: the 158-line
v1.0.0section uses bare---lines as horizontal rules, which a naive "line followed by---" parser reads as phantom sections. The header rule also requires a version-shaped title; those rules survive--releaseintact.Also covered: all four fragment-validation failures, unknown category, the double-apply guard, and the version floor (0 pre-1.0 sections in RELEASENOTES, 3 retained in CHANGELOG).
Reviewer notes
Unreleasedstub is dropped from RELEASENOTES. Since--checkkeeps that section empty, the generated notes would otherwise ship a stub header inside the.nupkg. It now opens on the newest released version.RELEASENOTES.mdregains(see ClickHouse.Driver.Common/Vendor/K4os)on the LZ4 entry — the single place the two files intentionally diverged. Flagging it explicitly since it's a repo-internal path; say the word and I'll reword the CHANGELOG entry instead.Docs updated in
CONTRIBUTING.md,AGENTS.md,changelog.d/README.md, and both review-skill mirrors.🤖 Generated with Claude Code