Every pull request that adds a changelog entry inserts at the same anchor, so any two of them conflict — and the conflict has nothing to do with the changes.
Measured on a0b916dc rather than recalled.
What happened tonight
#990 merged. Every open PR carrying a changelog entry went DIRTY at that moment:
#988 linuxhikerpm CHANGELOG.md the ONLY overlap; pgc_vacuity.py and test_layer.py collide with nothing
#989 OffgridwithJD rebased
#992 OffgridwithJD rebased
#993 OffgridwithJD rebased
Four PRs, four rebases, and not one of them touched a file another had touched apart from CHANGELOG.md.
It is not new, and the history says so in its own commit messages
87b238c Merge main into #958: CHANGELOG union after #957 landed
5c6d032 Merge main into #958: CHANGELOG union, nothing derived
02674bb Merge main into #957: CHANGELOG union, nothing derived
Three merge commits from earlier the same day whose entire subject is resolving this. Of 27 merges today, 9 touched CHANGELOG.md — so roughly a third of all work is funnelled through one insertion point.
Why it happens
Everything inserts immediately under one heading:
## [Unreleased]
### Added
- <newest entry>
Two branches both add a first child of the same heading. Git sees two different edits to the same location, which is exactly what it is — the anchor is shared even though the content is unrelated.
A fix that works, measured on the real pair
.gitattributes has no entry for CHANGELOG.md. Adding a union merge driver resolves it:
merge-base a0b916dc base 5220 lines, pr989 5259, pr992 5257
default 3-way merge rc=1, 1 conflict marker <- reproduces the conflict
union merge rc=0, 0 markers, 5296 lines <- both entries intact
5296 = 5220 + 39 + 37, and both entries are present and unmangled.
And union's hazard, tested rather than assumed
Union keeps both sides of any divergent hunk with no marker. Where two people edit the same existing line, that silently duplicates it:
THE SHARED LINE, edited by A
THE SHARED LINE, edited by B
That is a real failure mode and it is the reason union is not a default. It does not arise for the case at hand, because changelog entries are pure appends:
pr989 removals from CHANGELOG.md: 0
pr992 removals from CHANGELOG.md: 0
Both sides add and neither modifies. That is union's safe case exactly — and it is worth stating that a PR which edits an existing entry (a correction, a reworded heading) leaves the safe case, and union would then duplicate rather than conflict.
What this is not
Not the same as the ledger budget line. check_ledger_budget.txt also serialises work, and that is by design — the budget is committed precisely so a change to it is a diff a reviewer sees, and the contention is the mechanism working. A census derived against a stale tree is a wrong number, so the rebase there buys correctness.
The changelog anchor buys nothing. Two entries about unrelated subsystems do not need to be serialised, and the rebase they force produces no information.
Options, cheapest first
CHANGELOG.md merge=union in .gitattributes — one line, measured above, with the append-only caveat documented beside it.
- Insert at the bottom of the section rather than the top, so branches append at different offsets. Reduces collisions without eliminating them, and inverts the reading order the file currently has.
- Fragment files (
changelog.d/<pr>.md) assembled at release. Eliminates the class; costs a build step and a convention nobody here uses yet.
- Accept it, and treat the rebase as the price of a hand-ordered changelog.
I have no strong view between 1 and 4 and would not want it decided by whoever is most annoyed at the time — which tonight is me. What is measured is the cost: four rebases from one merge, and nine of twenty-seven merges funnelled through one line.
Acceptance for any fix
Two branches that both add an entry, merged in either order, produce a file containing both entries, each exactly once, with no conflict markers — and a branch that edits an existing entry still conflicts rather than duplicating it. The second half is the one that decides whether option 1 is safe enough.
Every pull request that adds a changelog entry inserts at the same anchor, so any two of them conflict — and the conflict has nothing to do with the changes.
Measured on
a0b916dcrather than recalled.What happened tonight
#990merged. Every open PR carrying a changelog entry wentDIRTYat that moment:Four PRs, four rebases, and not one of them touched a file another had touched apart from
CHANGELOG.md.It is not new, and the history says so in its own commit messages
Three merge commits from earlier the same day whose entire subject is resolving this. Of 27 merges today, 9 touched
CHANGELOG.md— so roughly a third of all work is funnelled through one insertion point.Why it happens
Everything inserts immediately under one heading:
Two branches both add a first child of the same heading. Git sees two different edits to the same location, which is exactly what it is — the anchor is shared even though the content is unrelated.
A fix that works, measured on the real pair
.gitattributeshas no entry forCHANGELOG.md. Adding a union merge driver resolves it:5296 = 5220 + 39 + 37, and both entries are present and unmangled.And union's hazard, tested rather than assumed
Union keeps both sides of any divergent hunk with no marker. Where two people edit the same existing line, that silently duplicates it:
That is a real failure mode and it is the reason union is not a default. It does not arise for the case at hand, because changelog entries are pure appends:
Both sides add and neither modifies. That is union's safe case exactly — and it is worth stating that a PR which edits an existing entry (a correction, a reworded heading) leaves the safe case, and union would then duplicate rather than conflict.
What this is not
Not the same as the ledger budget line.
check_ledger_budget.txtalso serialises work, and that is by design — the budget is committed precisely so a change to it is a diff a reviewer sees, and the contention is the mechanism working. A census derived against a stale tree is a wrong number, so the rebase there buys correctness.The changelog anchor buys nothing. Two entries about unrelated subsystems do not need to be serialised, and the rebase they force produces no information.
Options, cheapest first
CHANGELOG.md merge=unionin.gitattributes— one line, measured above, with the append-only caveat documented beside it.changelog.d/<pr>.md) assembled at release. Eliminates the class; costs a build step and a convention nobody here uses yet.I have no strong view between 1 and 4 and would not want it decided by whoever is most annoyed at the time — which tonight is me. What is measured is the cost: four rebases from one merge, and nine of twenty-seven merges funnelled through one line.
Acceptance for any fix
Two branches that both add an entry, merged in either order, produce a file containing both entries, each exactly once, with no conflict markers — and a branch that edits an existing entry still conflicts rather than duplicating it. The second half is the one that decides whether option 1 is safe enough.