Read more

Git: rebase dependent feature branch after squash

Daniel Straßner
June 08, 2017Software engineer at makandra GmbH

This card will show you how to use git rebase --onto without confusion.

Use case:

Illustration web development

Do you need DevOps-experts?

Your development team has a full backlog? No time for infrastructure architecture? Our DevOps team is ready to support you!

  • We build reliable cloud solutions with Infrastructure as code
  • We are experts in security, Linux and databases
  • We support your dev team to perform
Read more Show archive.org snapshot

You've got two feature branches (one and two), where two depends on one. Now commits of branch one have changed after you branched two from it (i.e. after code review the commits of branch one are squashed). Thus the commit history of branch one has changed. Branch two's history however didn't change.

Solution:

To make the branches share the same commit history again you will have to rebase and replay (attach) the additional commits of branch two onto branch one.

The easiest way for me to correctly use git rebase --onto is to count the number of additional commits of branch two and use the following command:

git rebase --onto one HEAD~<commit_count>

Make sure you are on branch two while executing this command.

Example:

Say one looks like:

%%{init: { 'gitGraph': { 'mainBranchName': 'one'}} }%%
gitGraph:
commit id: "O"
commit id: "A"
commit id: "B"
commit id: "C"

two has a few extra commits on top of that:

%%{init: { 'gitGraph': { 'mainBranchName': 'one'}} }%%
gitGraph:
commit id: "O"
commit id: "A" type: HIGHLIGHT
commit id: "B" type: HIGHLIGHT
commit id: "C" type: HIGHLIGHT
branch two
commit id: "D"
commit id: "E"
commit id: "F"

You squash feature branch one down, giving you:

%%{init: { 'gitGraph': { 'mainBranchName': 'one'}} }%%
gitGraph:
commit id: "O"
commit id: "S" type: HIGHLIGHT

Now, when you try to rebase the dependent branch two, git is going to try to figure out the common ancestor between those branches. While it originally would have been C, if you had not squashed the commits down, git instead finds O as the common ancestor. As a result, git is trying to replay A, B, and C which are already contained in S, and you're going to get a bunch of conflicts.

For this reason, you can't really rely on a typical rebase command, and you have to be more explicit about it by supplying the --onto parameter:

git rebase --onto one HEAD~3  # instruct git to replay only the last
                              # 3 commits, D E and F, onto one.

Modify the HEAD~3 parameter as necessary for your branches, and you shouldn't have to deal with any redundant conflict resolution. The resulting git history should look like this:

%%{init: { 'gitGraph': { 'mainBranchName': 'one'}} }%%
gitGraph:
commit id: "O"
commit id: "S"
branch two
commit id: "D"
commit id: "E"
commit id: "F"

Some alternate syntax, if you don't like specifying ranges and you still have a commit pointing to the unsquashed branch one (one_unsquashed):

git rebase --onto one one_unsquashed two # replay all commits, starting at one_unsquashed
                                         # exclusive, through two inclusive 
                                         # onto one (squashed)

You might want to consider creating a branch like one_unsquashed anytime before squashing it down if there are dependent branches and you want to use this syntax.

See also

Daniel Straßner
June 08, 2017Software engineer at makandra GmbH
Posted by Daniel Straßner to makandra dev (2017-06-08 11:20)