docs(RELEASING): promote with a merge, not a rebase — the rebase is what mints twin trees - #370
Conversation
…hat mints twin trees Step 6 prescribed `gh pr merge --rebase`, which rebases develop's commits onto main and mints new shas. develop keeps the originals, so main accumulated TWINS and the branches stopped sharing recent ancestry — 37 commits apart over three releases, with every promotion PR coming back CONFLICTING and needing a hand-built reconcile commit (cfd92fa at v1.15.0, 60aa883 at v1.15.1). The rebase also defeated the reason the doc gave for choosing it: 'so the tag sits on the same commit as develop's release commit'. A rebase mints a twin, so the tag landed on a different sha that merely shared the tree. de4e781 healed the divergence (`merge -s ours`, develop's tree kept byte-for-byte), so main is an ancestor of develop again and `--merge` now fast-forwards cleanly. This records the corrected step plus the invariant to check before promoting, and the back-merge to run if a hotfix ever lands on main directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…button Validation catch on my own change: I had written `gh pr merge --merge` with the comment 'fast-forwards main to develop'. It does not. GitHub's --merge ALWAYS writes a merge commit — verified against this repo's own history, where every past 'promote via merge' on main (23fcd27, 22dd8f2, 9b04a37) has two parents. That matters twice over: the tag would sit on the merge commit rather than on develop's release commit (the goal the step exists to serve), and main would gain a commit develop lacks, breaking the 'main is an ancestor of develop' invariant the same paragraph tells releasers to check — one commit per release. Since de4e781 made main an ancestor again, a real fast-forward is available and is what the step now prescribes: `git push origin develop:main`, guarded by the is-ancestor check. Verified a ff-only onto develop lands on develop's exact sha. GitHub closes the PR as merged once its commits are reachable from main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Validation catch on my own change — pushed The first version of this PR said
So Corrected to a real fast-forward push, which is available now that git merge-base --is-ancestor origin/main origin/develop || { echo 'NOT a fast-forward'; exit 1; }
git push origin develop:mainVerified by simulation on a throwaway ref: Also re-checked the rest of the doc's claims rather than assuming: the |
…otion push Second validation pass, after being told to double-check. Two things I asserted were not supported by the evidence I cited: 1. I claimed --merge's always-writes-a-merge-commit behaviour was 'verified against this repo's history' via 23fcd27 / 22dd8f2 / 9b04a37. Those three promotions all happened when main and develop had ALREADY diverged, so a merge commit was the only possible outcome — they prove nothing about what GitHub does when a fast-forward IS available. Dropped the citation; the behaviour is stated as GitHub's semantics. 2. I implied no tag had ever sat on develop's release commit. v1.13.0 and v1.14.0 did. The regression starts at v1.15.0, where the rebase promotion came in. Corrected. Also records what the ruleset actually is, which the previous version hand-waved as '--admin-style bypass': Main Branch carries pull_request + non_fast_forward + deletion, with OrganizationAdmin bypass at bypass_mode 'always'. The fast-forward push satisfies non_fast_forward and bypasses pull_request. That push shape is NOT yet tested against the ruleset, so it is marked unverified for the next releaser to confirm rather than presented as known-good. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Retracting the evidence table in my previous comment — pushed I cited The conclusion still holds (GitHub's Second correction: I implied no tag had ever sat on develop's release commit. v1.13.0 and v1.14.0 did — the regression begins at v1.15.0, when the rebase promotion came in. (My check for this was also methodologically unsound after Third, and the one that actually mattered operationally: the previous version hand-waved the push as "--admin-style bypass applies". The real config, verified:
So the push satisfies Net: the direction is unchanged and the healing ( |
…d a false bypass claim Told to keep checking. Four more errors found in my own text: 1. 'three releases' while diverged -> FIVE (v1.13.0, v1.13.1, v1.14.0, v1.15.0, v1.15.1). Verified by listing every tag dated after the merge-base. 2. Wrong cause. I blamed --rebase for starting it. The split began one release earlier, at v1.12.0's MERGE promotion: 23fcd27 has two parents and its second parent 3220f57 IS the merge-base, so main took a merge commit develop never got back. --rebase then compounded it into twins. Both modes break the same invariant, which is why the invariant — not the merge mode — now leads. 3. I had 'corrected' my earlier claim to say v1.13.0/v1.14.0 landed their tags on develop's release commit. They did not. That check was run against POST-heal develop, which now contains main's history through the merge parent, so it compared a commit to itself. Against pre-heal develop (7ea7e78) no tag from v1.12.0 to v1.15.1 is reachable — my original claim was right and the 'correction' introduced the error. 4. 'the same bypass the documented direct pushes to develop already lean on' is false: the Main Branch ruleset targets refs/heads/main ONLY and develop carries no rules at all, so pushes there bypass nothing. That makes the fast-forward push the FIRST thing to rely on that bypass — no precedent — so the untested marker now says so and carries a concrete fallback. Also removed a duplicated sentence and an internal contradiction (the step opened with 'not a direct push' and then prescribed one), and fixed the stale perf-baseline paragraph in the same file, which still promised miner-0 gates 'every time' — v1.15.0 was gated on miner-2, v1.15.1 on miner-3, and miner-0 cannot pass at all while Secure Boot is on for its Windows dual-boot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Third pass — four more errors in my own text, pushed
Because of #2, the doc now leads with the invariant ( Also fixed: a duplicated sentence, and an internal contradiction where the step opened with "not a direct push" and then prescribed one. One extra fix in the same file, outside the original scope: the perf-baseline paragraph still promised the recording "means miner-0 every time, since the release gate itself always runs there". That is false and would send the next releaser to a box that cannot pass — v1.15.0 was gated on miner-2, v1.15.1 on miner-3, and miner-0 can't pass at all while Secure Boot is on for its Windows dual-boot (lockdown Audit of the pushed file: all five cited shas resolve to what the text says they are, all eight version strings are real tags, markdownlint clean, no test asserts on this file, all relative links resolve. |
…0 correction Cosmetic follow-up to 7b61994: editing mid-paragraph left a 147-char prose line and then a ragged 32-char one, against the file's ~100-105 wrap. MD013 is disabled so lint never flagged it and the rendered output is identical, but the source was inconsistent with the rest of the document. No wording changes — verified by a word-level diff against develop, which shows only the step 6 rewrite and the miner-0 correction, no phantom edits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up to the v1.15.1 cut, which cost a hand-built reconcile commit for the third release running.
The bug in the process
Step 6 prescribed
gh pr merge --rebase --admin. That rebases develop's commits ontomain, minting new shas — somaincarries twins of commitsdevelopstill holds under their original shas, and the branches stop sharing recent ancestry.Measured before the fix:
3220f57, 2026-07-19 — three releases agoCONFLICTING, hand-built reconcile commit (cfd92fav1.15.0,60aa883v1.15.1)The rebase also defeated the reason the doc gave for choosing it — "so the tag sits on the same commit as develop's release commit". A rebase mints a twin, so the tag landed on a different sha that merely shared the tree. The process failed its own stated goal.
The fix
de4e781on develop healed the divergence withgit merge -s ours origin/main: develop's tree kept byte-for-byte,mainrecorded as a parent. The-s oursassertion that main's changes were already incorporated was verified true first —git diff origin/main origin/developshowed only the v1.15.1 perf baseline, i.e. main carried nothing develop lacked. No history rewritten, no force-push, no content change.Divergence went 37 → 0, and
mainis an ancestor ofdevelopagain.This PR corrects step 6 to
--merge(which now fast-forwards cleanly onto develop's release commit, actually delivering what the doc wanted), and records:mainis always an ancestor ofdevelop— with thegit merge-base --is-ancestorcheck to run before promoting;maindirectly;--rebasemust not come back, so the next releaser doesn't reintroduce it.Docs only;
make lint-mdclean.