git - how to rebase over a rebased branch, stacked branches
It is often useful to split a large change into several smaller branches and create smaller PRs which are easier to understand and review. This approach is also called “stacked branches” or “stacked pull requests”.
The problem with this approach is that a modification of the eariler branch will require rebasing all branches created from it.
For example, we start with this state:
o---o---o---o master
\
o---o---o A
\
o - o B
And we do something like git rebase -i HEAD~3
on branch A
to change some commits:
o---o---o------------o master
\
\--o---o---o new_A
\
o---o---o old_A
\
o - o B
Similar situation, we rebase branch A on top of master
:
o---o---o--------------o master
\ \
o---o---o old_A o---o---o new_A
\
o - o B
In both cases, we edited the branch A
and it became new_A
. Now we need to “move” branch B
to have a root in new_A
instead of old_A
.
Method 1: Rebase using --update-refs
mode
This is a simpler, modern way to solve the problem. The --update-refs
flag is available in Git 2.38 (October 2022) or later versions.
It can be used like this:
# Checkout the "top" branch in the stack
git checkout B
# Rebase on master: both branch A and B will be updated
git rebase -i master --update-refs
In this case, we started with this:
o---o---o---o master
\
o---o---o A
\
o - o B
And after the rebase we get this:
o---o---o---o master
\
o---o---o new A
\
o - o new B
Similarly, we can perform interactive rebase to edit the commit history:
# Checkout the "top" branch in the stack
git checkout B
# Interactive rebase: in this case the "HEAD~5" can cross branch points, affected branches will be updated automatically
git rebase -i HEAD~5 --update-refs
In both cases, git
will update all affected branches and show the update result like this:
Successfully rebased and updated refs/heads/B.
Updated the following refs with --update-refs:
refs/heads/A
(if there are more branches, they will be listed here)
Now we can push changes to the remote repository like this:
git push --force-with-lease B A # we can list all updated branches here
Method 2: Rebase branches one-by-one
This is older solution, before --update-refs
was available.
We use git rebase --onto
to rebase previous branches. It works like this:
# Checkout first branch
git checkout A
# Do something, edit commits
...
# Now we need to rebase later branches
git checkout B
git log # check how many commits we need to rebase
# Rebase (in this case we need to rebase 2 commits on branch B on top of new branch A)
git rebase --onto A B~2 B
# Or, the same, shorter
git rebase HEAD~2 --onto A
This method is also useful if we want to move an existing branch to another root.
For example, we decide that branch B
does not depend on A
and we want it to “grow” from master.
Start state:
o---o---o---o master
\
o---o---o A
\
o - o B
Desired state:
o---o---o---------------o master
\ \
o---o---o A o - o B
This is how to do it:
git checkout B
git log # check how many commits we need to rebase
# Rebase (in this case we need to rebase 2 commits on branch B on top of master)
git rebase --onto master B~2 B
# Or, the same, shorter
git rebase HEAD~2 --onto master
References
git - How to rebase a branch off a rebased branch? - Stack Overflow
Working with stacked branches in Git is easier with –update-refs