Skip to content

Every PR with a changelog entry inserts at the same anchor, so any two conflict for a reason unrelated to their changes #996

Description

@jdatcmd

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

  1. CHANGELOG.md merge=union in .gitattributes — one line, measured above, with the append-only caveat documented beside it.
  2. 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.
  3. Fragment files (changelog.d/<pr>.md) assembled at release. Eliminates the class; costs a build step and a convention nobody here uses yet.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions