Git: Rebase stacked branches with --update-refs

My mama always said, Git commits are like a stack of pancakes (generated by Stable Diffusion)

Update (2023-10-16): This post’s content appears in my new book Boost Your Git DX.

When working on a feature, you might split it into several stacked branches, so you can merge each one separately. But updating such branches can be annoying since you have to manage them independently. Git 2.38 (2022-10-15) made this management easier with the new --update-refs option, which can rebase a stack of branches at once. Let’s look at a couple of examples.

Rebase stacked branches

Imagine you have this situation:

$ git log --oneline --graph @ main
* 866327a (main) Phat bass
| * d1b1bcd (HEAD -> fx_plus) Bleeps and bloops
| * d8f9be0 (fx) Background effects
|/
* fe77864 Hi-hat vibes
...

Two stacked branches, fx_plus and fx, are based on an old commit from main. Say you’d like to rebase them on top of the latest main.

Without --update-refs, you’d need to first rebase fx, then rebase fx_plus on top of the new fx. This is long-winded and prone to error.

--update-refs lets you perform the “double rebase” in one command. Let’s walk through the steps.

First, switch to the final branch:

$ git switch fx_plus

Second, start a rebase onto the main branch with --update-refs:

$ git rebase -i --update-refs main
hint: Waiting for your editor to close the file...

The git-rebase-todo has an u (update-ref) action added for the fx branch:

p d8f9be0 Background effects
u refs/heads/fx

p d1b1bcd Bleeps and bloops

# Rebase 866327a..d1b1bcd onto 866327a (3 commands)
...

If you want to keep all commits, no need to make any changes to the todo file. Indeed, there’s no need to use interactive mode at all, but it’s a good opportunity to see what will happen. Close it, and the rebase will continue.

Third, see the rebase complete:

$ git rebase -i --update-refs main
Successfully rebased and updated refs/heads/fx_plus.
Updated the following refs with --update-refs:
  refs/heads/fx

The output includes the standard message about fx_plus being successfully rebased, plus an extra one about --update-refs updating fx.

Check the Git log and you’ll see both branches are now based on the latest commit on main:

$ git log --oneline --graph @ main
* 4355b19 (HEAD -> fx_plus) Bleeps and bloops
* 0b4e722 (fx) Background effects
* 866327a (main) Phat bass
* fe77864 Hi-hat vibes
...

You can use --update-refs to rebase any number of stacked branches at once. The git-rebase-todo will will contain one u (update-ref) action per branch.

Add changes to stacked branches

Imagine you had that same initial situation:

$ git log --oneline --graph @ main
* 866327a (main) Phat bass
| * d1b1bcd (HEAD -> fx_plus) Bleeps and bloops
| * d8f9be0 (fx) Background effects
|/
* fe77864 Hi-hat vibes

After review of both fx and fx_plus, you want to make some changes to both. With --update-refs you can make those changes once and rebase to integrate them into the right branches.

First, check out the last of the stacked branches:

$ git switch fx_plus

Second, make all changes applicable to any of the stacked branches:

$ git commit --allow-empty -m "Background tweaks"
[fx_plus 542f192] Background tweaks

$ git commit --allow-empty -m "Bleep tweaks"
[fx_plus 7ad43fc] Bleep tweaks

Third, start an interactive rebase of the stack:

$ git rebase -i --update-refs main
hint: Waiting for your editor to close the file...

Fourth, reorder the commits in the git-rebase-todo file to place commits on the appropriate branches. When the file opens, you’ll see it starts like:

p 3ade93c Background effects
u refs/heads/fx

p a9c2dea Bleeps and bloops
p 542f192 Background tweaks
p 7ad43fc Bleep tweaks

# Rebase 866327a..7ad43fc onto 866327a (5 commands)
...

In this case, the “Background tweaks“ commit should be on the fx branch. Move its line above the u (update-ref) line:

p 3ade93c Background effects
p 542f192 Background tweaks
u refs/heads/fx

p a9c2dea Bleeps and bloops
p 7ad43fc Bleep tweaks

# Rebase 866327a..7ad43fc onto 866327a (5 commands)
...

You can also adjust the p (pick) actions to others, notably f (fixup) to squash commits.

Fourth, save and close the file, and Git will perform the instructed rebase:

$ git rebase -i --update-refs main
Successfully rebased and updated refs/heads/fx_plus.
Updated the following refs with --update-refs:
  refs/heads/fx

Now the two branches are on top of the latest main, with their respective commits:

$ git log --oneline --graph @ main
* d56883f (HEAD -> fx_plus) Bleeps and bloops
* 73999ce (fx) Background effects
* 866327a (main) Phat bass
...

Now you can continue working with each branch, for example by pushing them for further review:

$ git push origin fx fx_plus

Enable --update-refs by default

You can make it easier to work with stacked branches by enabling rebase.updateRefs, which makes git rebase use --update-refs by default:

$ git config --global rebase.updateRefs true

Then, every time you rebase a branch, any earlier branches in its stack will also get updated.

Having this option enabled can require a little care, as on occasion --update-refs may pick up branches you didn’t intend to rebase. For example, if you rebase a branch onto an older merged commit, your main branch may appear for updating:

p 866327a Phat bass
u refs/heads/main

p 73999ce Background effects

# Rebase fe77864..73999ce onto fe77864 (3 commands)
...

If this happens, drop all p (pick) and u (update-ref) actions for the unintended branch:

-p 866327a Phat bass
-u refs/heads/main
-
 p 73999ce Background effects

 # Rebase fe77864..73999ce onto fe77864 (3 commands)
 ...

The rebase will then only affect the targeted branch.

Fin

May your commit stacks never topple,

—Adam


😸😸😸 Check out my new book on using GitHub effectively, Boost Your GitHub DX! 😸😸😸


Subscribe via RSS, Twitter, Mastodon, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: