Skip to content

Commit 91b311f

Browse files
committed
docs: say why a rebase drops a merged parent's commits, and what that ref is
Two corrections to the stacked-PR guidance, both learned from landing the generator-payload-alignment stack. FIRST: `git rebase -S origin/main` after a parent merges was already the documented step, with nothing explaining why it works. GitHub reports the child as CONFLICTING the moment its parent merges — the parent's commits are on main under new SHAs while the child still carries the originals, so a three-way merge sees two versions of the same work. The rebase does not, because it drops commits whose patch-id already appears upstream. Measured: a child 5 commits above main, two of them stale copies, rebased to 3 cleanly. Without that sentence, the badge reads as damage and invites a hand repair of something that is already fine. It also now names the opposite case, which needs the opposite tool: a parent rewritten but NOT merged has no upstream patch-id to match, so a plain rebase replays superseded commits on top of their replacements, and the fix is `--onto` from where the child forked. SECOND: the recovery section said `refs/pull/<n>/head` "points at the pre-merge tip". True only when GitHub did not rebase the branch before merging it. Measured on #214: that ref was `e040449`, whose parents include a PR merged after #214 was opened, while the tip actually pushed was `4851e8d` with the same content under a different SHA. The distinction matters because the two uses differ. Restoring a deleted branch only needs the content, so either SHA does. Rebasing a child needs the specific commits that child contains, and the rebased copy is not those — it would replay the stale commits instead of dropping them.
1 parent fc01950 commit 91b311f

1 file changed

Lines changed: 30 additions & 1 deletion

File tree

‎CLAUDE.md‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,26 @@ git rebase -S origin/main # in the next branch's worktree
193193
git push origin --force-with-lease=<branch>:$(git rev-parse origin/<branch>) <branch>
194194
```
195195

196+
**That plain `git rebase origin/main` is correct, and it is worth knowing why,
197+
because the PR will look broken first.** GitHub reports the child as
198+
`CONFLICTING` the moment its parent merges: the parent's commits are on `main`
199+
under new SHAs while the child still carries the originals, so a three-way merge
200+
sees two versions of the same work. The rebase does not, because it drops
201+
commits whose patch-id already appears upstream. Measured on the
202+
`generator-payload-alignment` stack: a child sitting 5 commits above `main`,
203+
two of them stale copies of the merged parent, rebased to 3 with no conflict.
204+
So a `CONFLICTING` badge after a parent merges is the expected state, not a
205+
signal that something needs repairing by hand.
206+
207+
**A rewritten-but-unmerged parent is the opposite case, and needs the opposite
208+
tool.** When you amend or rebase a parent that is still open, its commits are
209+
NOT upstream, so there is no patch-id match to drop them, and a plain rebase
210+
replays the superseded versions on top of their replacements. That surfaces as
211+
a conflict in files the child never touched, where "take mine" silently
212+
discards the parent's fix. There, replay from where the child forked:
213+
`git rebase --onto <parent> <parent's pre-rebase tip> <child>`, or let
214+
`propagate_stack.py` do it.
215+
196216
Three things that will bite:
197217

198218
- **Branch protection is `strict_up_to_date: true`,** so the next PR reports `mergeable_state: "behind"` until you rebase it. That is not a conflict; it is the protection asking for the rebase you owe it.
@@ -213,13 +233,22 @@ Nothing is wrong and nothing needs fixing on `main`. Know it so that `%G?` on a
213233

214234
This happens when the **parent** PR is merged with `--delete-branch`: deleting the parent's head branch (which is the child's base) closes the **child** PR. Two PRs are involved, the merged parent (`<parent>`) and the closed child (`<child>`); `<branch>` is the deleted base, i.e. the parent's head branch.
215235

216-
1. Restore the deleted base branch from **GitHub's own copy of the parent's head**, `refs/pull/<parent>/head`. GitHub keeps that ref after the branch is deleted and after the PR is merged, and it points at the pre-merge tip:
236+
1. Restore the deleted base branch from **GitHub's own copy of the parent's head**, `refs/pull/<parent>/head`. GitHub keeps that ref after the branch is deleted and after the PR is merged, so it is there when the branch itself is gone:
217237

218238
```bash
219239
SHA=$(git ls-remote origin "refs/pull/<parent>/head" | cut -f1)
220240
gh api --method POST repos/<owner>/<repo>/git/refs -f ref=refs/heads/<branch> -f sha="$SHA"
221241
```
222242

243+
**It is the head GitHub last saw, which is not always what you pushed.**
244+
If GitHub rebased the branch before merging it, that ref moves to the
245+
rebased copy. Measured on #214: `refs/pull/214/head` was `e040449`, whose
246+
parents include a PR that merged _after_ #214 was opened, while the tip
247+
actually pushed to the branch was `4851e8d` with the same content under a
248+
different SHA. Fine for restoring a branch, which only needs the content.
249+
Wrong as a rebase upstream for a child, which needs the specific commits
250+
that child contains.
251+
223252
**Do not reach for the merge commit's second parent.** The older form here was:
224253

225254
```bash

0 commit comments

Comments
 (0)