skip to content

What problem does git rebase --update-refs solve when you stack dependent branches?

level: seniorimportance: should knowfreq 25%

answer

  1. Several branches sit on one chain of commits
  2. Replay makes new commits; refs stay behind
  3. The manual fix is one --onto per branch
  4. There is a config key to make it always on
  5. Only refs inside the replayed range move

basics

~20 s

When branches point at intermediate commits in a stack, rebasing the top branch normally leaves those refs on the old, pre-rebase copies. The --update-refs option moves them to the corresponding replayed commits so the whole stack stays consistent.

solid answer

~40 s

Stacked branches share one chain of commits: `feature-c` builds on `feature-b`, which builds on `feature-a`, so `feature-a` and `feature-b` point at commits partway along `feature-c`'s history. Rebasing `feature-c` replays that whole chain onto a new base, creating new commits — but `feature-a` and `feature-b` still point at the **old** copies. The stack silently comes apart, and you are left restacking each branch by hand with `git rebase --onto`. `git rebase --update-refs` fixes this: while replaying, it notices refs pointing at commits in the replayed range and repoints them at the new equivalents when the rebase completes. Setting `rebase.updateRefs` to true in config makes it the default for every rebase. It is available in recent Git; on older versions the manual `--onto` restack is the only option.

go deeper

for a junior

Not expected at this level. Knowing that rebasing one branch can leave others pointing at stale commits is already useful.

for a middle

Explain why the refs are left behind — replay creates new commits and refs are independent pointers — and what the option does about it.

for a senior

Show that you have run the manual restack, know that the moved branches still need forced updates, and can say where the automation stops helping.

for a principal

Own the question behind it: whether stacked branches are the right way for the team to split large work, given the restacking and force-push overhead they impose.

## What a stack is In a review-driven workflow, large work is often split into a chain of dependent branches: `feature-a` off `main`, `feature-b` off `feature-a`, `feature-c` off `feature-b`. Physically there is one chain of commits, and the three branch refs point at three positions along it — two of them partway up, one at the tip. Each branch is reviewed on its own, and each depends on the one below it. ## Why the stack breaks on rebase When `main` moves and you rebase the tip branch, Git replays every commit in the range onto the new base, producing **new commits with new hashes**. The branch you rebased is moved to the new tip. But `feature-a` and `feature-b` are just refs, and Git — historically — had no reason to think they had anything to do with this operation. They keep pointing at the old commits. The result is a mess that is worse than it looks: `feature-a` and `feature-b` now name commits that are no longer ancestors of `feature-c`, so the branches appear to have diverged from each other, and the review for each middle branch shows a diff against an obsolete base. Repairing it by hand means a `git rebase --onto` per middle branch, each needing the *old* tip of the branch below it as the exclusion boundary — hashes you have to dig out of the reflog if you did not write them down. ## What --update-refs does Passing `--update-refs` to `git rebase` tells Git to treat the refs pointing into the replayed range as part of the operation. As it replays, it tracks which new commit corresponds to each commit that had a ref on it, and when the rebase finishes it repoints those refs accordingly. One command rebases the entire stack and leaves every branch in it pointing at the right new commit. Setting the config key `rebase.updateRefs` to `true` makes this behaviour the default for all rebases, which is what people who work in stacks generally want. ## Things worth knowing about it - It updates only refs that point at commits **inside the replayed range**. A branch below your rebase point, or on an unrelated line of work, is untouched. - The updated branches are still rewritten branches: if you had pushed any of them, they now need force-pushing exactly as the top branch does, since their new tips are not descendants of what the remote holds. - Every ref it moves is recorded in that ref's reflog, so a wrong outcome is recoverable per branch. - It changes bookkeeping, not mechanics. Conflicts, new hashes, `--continue`/`--skip`/`--abort` all behave exactly as in any rebase. ## When it does not help If the middle branches have diverged — someone committed to `feature-b` directly after `feature-c` was branched — then the chain is no longer a single line and the refs are not simply positions along the replayed range. That case still needs deliberate restacking. `--update-refs` automates the common case, not every case. ## Version note and fallback This is a relatively recent addition to Git; older versions do not have the flag or the config key. If you cannot rely on it, the manual procedure is: note each branch's current tip *before* rebasing (or recover them from the reflog afterwards), then for each middle branch run `git rebase --onto <new-parent-tip> <old-parent-tip> <branch>`, working from the bottom of the stack upward. Knowing that fallback is part of a complete answer, because it shows you understand what the flag automates rather than treating it as magic. ## Why it comes up in interviews It is a strong signal for how someone actually works. Candidates who have maintained stacked branches against a moving trunk have felt this pain and usually have an opinion; candidates who have only ever had one branch at a time have not. Asked well, it also opens the more interesting conversation: whether the stack is worth its maintenance cost at all, or whether the work should be sliced so it lands sooner.

  • How would you restack manually on a Git without this option?
    Note each branch's tip before rebasing, or recover it from that branch's reflog afterwards. Then, from the bottom of the stack upward, run `git rebase --onto <new-parent-tip> <old-parent-tip> <branch>` for each dependent branch. The old parent tip is the exclusion boundary; the new one is the destination.
  • After a rebase moves the middle branches, can you push them normally?
    No. Those branches now point at replayed copies whose hashes differ from what the remote holds, so their updates are not fast-forwards and are rejected like any rewritten branch. Each one needs an explicit forced update, and anyone who had the old commits has to reconcile.
  • Does it move every branch in the repository that could be affected?
    Only refs pointing at commits inside the replayed range. Branches below the rebase point or on unrelated lines of history are left alone. If a middle branch has commits of its own that are not on the chain being replayed, the stack is no longer a single line and needs deliberate restacking.

saying these in an interview costs you the question

  • Thinks a plain rebase already moves stacked branch refs
  • Believes updated branches can be pushed without forcing
  • Assumes it moves unrelated branches too
  • Cannot describe the manual --onto restack it replaces
  • Treats it as changing how conflicts are replayed

context