Rebase After a Squash

February 11, 2019
development git

Depending on your development workflow, rebasing may be a common component of your workflow; ie:

  1. You branch off master (we’ll call this branch A)
  2. You add a bunch of commits, for instance, developing a new feature
  3. You branch off again for a small fix (we’ll call this branch B)
                B1---B2  branch-b
              /
   A1---A2---A3          branch-a
 /
M                        master

Now, A gets merged into master. Typically (if keeping a clean git history), A was squashed into a single commit before merging. This leaves B with the unsquashed history of A, which does not exist in master.

Our history now looks like:

   A1---A2---A3---B1---B2     branch-b
 /
M---A                         master

If you attempt to rebase B off master (git rebase master), you’ll end up with an epic mess (every commit originally in A will now conflict with the squashed commit now in master). Attempting to correct all of the conflicts will likely ruin your week.

Fixing this is actually fairly straight forward, and there are two ways.

Cherry-picking Everything

The first approach is more complex, but a bit safer, and involves cherry-picking all commits onto a new branch. You’ll need to check out a new branch off master (let’s call this branch C), identify all of the commits which occurred after your initial branch off of A, and use git cherry-pick to pull the set of commits over. At the end, you can delete the original B branch, rename C to B, and force push your B branch.

git checkout master               # Checkout master
git checkout -b branch-b-rebased  # Create a new branch for our rebase
git cherry-pick B1..B2            # Pull over the `branch-b` commits
git branch -D branch-b            # Delete the original `branch-b`
git branch -m branch-b            # Rename the new branch to `branch-b`
# You can now force-push your newly rebased branch

Note that you could also git cherry-pick individual commits, but be sure to do them in order.

Interactive Rebase

Using an interactive rebase (--interactive, or -i) is my preferred method, which is similar to the naive git rebase master, however allows us to select which commits we want to apply back on top of master. This option is also commonly used for other instances of modifying commit history, such as reordering commits, or selectively squashing small commits into larger ones.

When used, you’ll be presented with a list of commits which will be reapplied, along with basis instructions for modifying the actions that will be taken. For instance, git rebase -i master in our example repo from branch-b in our example above gives:

pick 72b3e0c Feature A commit 1
pick d1c0b20 Feature A commit 2
pick c4c21df Feature A commit 3
pick a834f6a Feature B commit 1
pick ce91f24 Feature B commit 2

# Rebase 2bb4c6f..ce91f24 onto 2bb4c6f (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Since we know that the first three commits (Feature A) were squashed into master when branch-a was merged, we can drop those commits from the rebase by removing the line.

If we modify this as follows, we can rebase only the “Feature B” commits on top of master for branch-b:

d 72b3e0c Feature A commit 1
d d1c0b20 Feature A commit 2
d c4c21df Feature A commit 3
pick a834f6a Feature B commit 1
pick ce91f24 Feature B commit 2

Conclusion

Using either approach we can achieve the branch structure that we want, and allow branch-b to easily compare to, and be merged back into, master.

      B1---B2     branch-b
     /
M---A              master

As a final note, when modifying the commit history, it is important to remember two things. First, having an understanding of how the git reflog will make it easy to recover from many mistakes. Second, remember to take care when force-pushing shared branches.


Hopefully this works out for you, or was at least helpful. If you have any questions, don’t hesitate to shoot me an email, or follow me on twitter @nrmitchi.