How to rebase stacked Git branches

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

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 each one. Git 2.38 (2022-10-15) makes such updates easier, with the ability to rebase a stack of branches at once, with the new --update-refs. Let’s look at a couple of examples.

Rebase stacked branches

Imagine you have this situation, most recent commits first:

* e67fe90 Add deployment (main)
|
| * 73145a7 Add Poll views (HEAD -> poll_views)
| |
| * 3345a24 Add Poll database models (poll_models)
|/
|
* 86e3722 Set up Django

The main branch has two commits on it. On the side, there are two stacked branches for a new “poll” feature, poll_views and poll_models.

Imagine you’d like to rebase the poll_* branches on top of the latest main. Without --update-refs, you’d need to first rebase poll_models, then rebase poll_views on top of the new poll_models. But with the flag you can do this in one command.

First, switch to the final feature branch:

$ git switch poll_views

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

$ git rebase --update-refs main
Successfully rebased and updated refs/heads/poll_views.
Updated the following refs with --update-refs:
  refs/heads/poll_models

Git tells about poll_views being rebased, and that it update poll_models in the process.

Now both branches live on top of the latest commit on main:

* c6ac1a3 Add Poll views (HEAD -> poll_views)
|
* 9f9622b Add Poll database models (poll_models)
|
* e67fe90 Add deployment (main)
|
* 86e3722 Set up Django

Ta-daa!

How to add changes to stacked branches

Imagine you had that same initial situation:

* bc63397 Add deployment (HEAD -> main)
|
| * 94d92fd Add Poll views (poll_views)
| |
| * 229c030 Add Poll database models (poll_models)
|/
|
* 86e3722 Set up Django

After submitting poll_models and poll_views for code review, you have some changes to make to both. How can you make those changes, and ensure they end up in the right branches?

First, check out the last of the stacked branches:

$ git switch poll_views

Second, make changes applicable to both branches:

...
$ git commit -m "Add database constraint to Poll model"
...
$ git commit -m "Add rate-limiting to view"
...

Third, start an interactive rebase of the stack:

$ git rebase -i --update-refs main

-i is short for --interactive, which enables rebase’s interactive mode. This will open your text editor with a file you can use to control rebase operations.

Fourth, change the rebase file to move the commits to the appropriate branches. When the file opens, you’ll see it starts like:

pick 229c030 Add Poll database models
update-ref refs/heads/poll_models

pick 94d92fd Add Poll views
pick 31d5fc9 Add database constraint to Poll model
pick af05cde Add rate-limiting to view

# Rebase bc63397..af05cde onto bc63397 (5 commands)
#
# Commands:
...

The pick lines list commits, in the order they’ll be rebased. The update-ref line controls the commit that the poll_models branch will point to after the rebase. These are also explained in the Commands comment from Git:

# p, pick <commit> = use commit
...
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
#                       to this position in the new commits. The <ref> is
#                       updated at the end of the rebase
...

In this case, the “Add database constraint to Poll model” commits should be part of poll_models. Move its line above the update-ref line:

pick 229c030 Add Poll database models
pick 31d5fc9 Add database constraint to Poll model
update-ref refs/heads/poll_models

pick 94d92fd Add Poll views
pick af05cde Add rate-limiting to view

# Rebase bc63397..af05cde onto bc63397 (5 commands)
...

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/poll_views.
Updated the following refs with --update-refs:
  refs/heads/poll_models

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

* 9182779 Add rate-limiting to view (HEAD -> poll_views)
|
* df9ae26 Add Poll views
|
* d0cfd78 Add database constraint to Poll model (poll_models)
|
* e7ee0b7 Add Poll database models
|
* bc63397 Add deployment (main)
|
* 86e3722 Set up Django

Huzzah!

Now you can send each branch for another round of code review, probably with git push.

Enable --update-refs by default

You can make it easier to use stacked branches by enabling --update-refs by default. Then, every time you rebase a branch, any earlier branches in the stack will also get updated.

To enable the flag by default, for all repos, add the rebase.updateRefs boolean option to your global config:

$ git config --global --add --bool rebase.updateRefs true

This will add the option in your ~/.gitconfig file like so:

[rebase]
    updateRefs = true

Nice one.

Fin

May your commit stacks never topple,

—Adam


Learn how to make your tests run quickly in my book Speed Up Your Django Tests.


Subscribe via RSS, Twitter, Mastodon, or email:

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

Related posts:

Tags: