Skip to content

docs: say why rebasing works where merging does not (#1116) - #1122

Merged
jdatcmd merged 1 commit into
commandprompt:mainfrom
OffgridwithJD:docs/1116-merge-vs-rebase
Sep 18, 2026
Merged

jdatcmd merged 1 commit into
commandprompt:mainfrom
OffgridwithJD:docs/1116-merge-vs-rebase

Conversation

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Follow-up to #1116, prompted by reviewing #1107.

CONTEXT.md already tells a contributor to rebase rather than click Update branch, and gives the commands. It does not say why, and the why is what makes the advice transferable rather than a rule to memorise.

The asymmetry, measured

Git reads .gitattributes from the tree it is merging into. #1107's head predates #1108, so:

merge main INTO the branch   conflicts: CHANGELOG.md  <derived files>
rebase the branch ONTO main  conflicts:               <derived files>
grep -c 'CHANGELOG.md.*merge=union'   0 on that head, 1 on main

Merging brings main's commits into a tree whose attributes have no driver. Rebasing replays the branch onto main, where the driver is already in force.

So for any branch opened before the driver landed, Update branch cannot work even in principle — and that is most branches open more than a day. Without this paragraph the page reads as a preference, and a contributor who tries the button once and gets a conflict has no way to tell whether they did it wrong.

The arm was red on a correct document first

Worth recording, because it is the failure mode that looks most like success.

My first spelling grepped for reads \.gitattributes` from the tree it is merging`. The prose wraps:

505: The advice above is not a preference. Git reads `.gitattributes` from the tree it is
506: merging **into**, so a branch that predates the driver cannot use it:

A line-based pattern finds 0 on the unmutated tree. Then the mutation reddened it too — so the removal proof "passed" while the arm was broken. Two failures agreeing look exactly like one working check.

Re-anchored on merging \*\*into\*\*, which fits one line:

control    PASS  and says WHY rebasing works where merging does not
mutation   FAIL  ... got [0] want [1]

That is the same class as a drift-guard that does not join line continuations: a grep over prose has to know where the prose breaks.

Verification

docs_style.sh 47 checks, rc=0, and the mutation restores byte-exact. It holds no ledger rows, so nothing moves in the ledger or the census.

🤖 Generated with Claude Code

https://claude.ai/code/session_012RSw4qMHS7ByE7PY8Ns4cs

CONTEXT.md told a contributor to rebase rather than click "Update branch" and gave
the commands, but not the reason -- and the reason is what makes the advice
transferable: git reads .gitattributes from the tree it is merging INTO, so a branch
opened before the driver landed cannot use it.

Measured on commandprompt#1107, whose head predates commandprompt#1108:

    merge main INTO the branch   conflicts: CHANGELOG.md  <derived files>
    rebase the branch ONTO main  conflicts:               <derived files>

grep -c 'CHANGELOG.md.*merge=union' gives 0 on that head and 1 on main. Merging
brings main's commits into a tree whose attributes have no driver; rebasing replays
the branch onto main, where the driver is already in force. For any branch older
than the driver, "Update branch" cannot work even in principle.

THE ARM WAS RED ON THE UNMUTATED TREE FIRST. Its grep spanned the prose's line break
-- "the tree it is / merging **into**" -- so a line-based pattern found 0 on a
correct document, and the mutation reddened it too. That looks like a working
removal proof and is two failures agreeing. Re-anchored on a phrase that fits one
line: control green, mutation red.

docs_style 47 checks rc=0. It holds no ledger rows, so no rows move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012RSw4qMHS7ByE7PY8Ns4cs

@jdatcmd jdatcmd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving 9cb8a44. I reproduced the asymmetry rather than take it, on your own cited case.

Both directions, on #1107's head

#1107 head .gitattributes  grep -c 'CHANGELOG.md.*merge=union'  ->  0
origin/main                                                     ->  1

merge main INTO the branch   rc=1  conflicts: CHANGELOG.md  check_ledger_budget.txt  TESTS.md
rebase the branch ONTO main  rc=1  conflicts:               check_ledger_budget.txt  TESTS.md

CHANGELOG.md is in one list and not the other, with the derived files identical in both. That is exactly the claim, and it is the kind that reads as plausible-but-unverified until someone runs it both ways.

The explanation is right too: a merge applies the attributes of the tree being merged into, which on an old branch has no driver; a rebase replays the branch onto main, where the driver is already in force. So Update branch cannot work even in principle for a branch opened before #1108 — which, as you say, is most branches older than a day.

Why this belongs in CONTEXT.md and not in #1107

Agreed, and for the reason you gave: it is a property of the driver, not of their branch. The next person to hit it will be reading the page, not a comment thread on somebody else's PR. #1120 told them what to do and this says why the other thing cannot work, which is what stops the advice reading as superstition.

On the arm that was red on a correct document

its grep spanned the prose's line break, and the mutation reddened it too, so a broken arm and a working removal proof were indistinguishable until I looked at the control rather than the pair.

That is the sharpest thing in your last few messages and it generalises past this arm. A mutation proof compares two runs — mutant red, control green — and a broken arm produces the same pair. The pattern only distinguishes them if the control is checked on its own terms, which is the step that is easy to skip precisely because the pair already looks like success.

It is the same shape as my vacuous arm on #1121 being invisible until the mutation, in reverse: mine passed when it should have failed, yours failed when it should have passed, and in both cases the pair-of-runs was not enough on its own.

On #1123

Filing it while saying you have no trustworthy population count is the right call, and the classifier failing by extracting the outer quote rather than the grep's own pattern is worth keeping in the issue — it is a parsing error that produces a plausible number, which is the kind that gets published.

Your third lever is the one I had not thought of and is cheaper than the other two: choose a population that cannot contain prose about itself. Sweeping the registered suites instead of test/*.sh dropped the runner out of scope with no pattern work at all, because the runner is not a suite. That is a better answer than any regex.

@jdatcmd
jdatcmd merged commit 673895b into commandprompt:main Sep 18, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants