Demystify git rebase with animations

January 18, 2019

There are lots of commands in git and we can often achieve our goal in several different ways. I usually manage to get my stuff done with just few commands like rebase, reset, cherry-pick. But sometimes I felt I was doing it ineffectively. Like for example squashing all commits into a single one so that it will be easier to rebase afterward. Even though I heard that rebasing with the onto option could be useful, I never took the time to man it… until recently. I guess now is a good time to write something about it.

When to use it?

Here are some use-cases:

  • we want to cherry-pick multiples commits
  • we have created a branch from the wrong one and we want to bring all the commits to the right one
  • we want to delete a range of commits

Basically it all comes down to this: apply a range of commits somewhere else

Rebase

Let’s begin with a little reminder about how rebase works in its simplest form. From the man (unix) we get:

git rebase upstream [branch]

I am gonna give some examples and intentionally avoid any short-hand way at first.

When we run the command git rebase master topic then:

  1. Git performs a checkout topic
  2. The index and working tree are reset to master. This is exactly like running git reset --hard master except that commits X, Y, Z are put aside for later use. Basically all commits that are in topic but not in master (master..topic)
  3. Those commits are applied one by one on master. This is like cherry-picking them.

Step 2 may fail applying the commits. Don’t worry, be happy, we can either resolve the conflict by running git rebase --continue or get back to the state we left before the rebase with git rebase --abort

Note If we already are on the topic branch (HEADtopic) then we can omit the branch arg and just type git rebase master (step 0 is skipped) This is a common short-hand.

What about the --onto option?

The man tells us:

git rebase --onto newbase upstream [branch]

The only difference is in step 1: git hard resets to newbase instead of upstream. Step 2 remains the same, commits upstream..branch are applied.

Lets take an example

We have created a branch topicB from topicA but it was a “mistake”. The work on topicB is totally unrelated to topicA and thus doesn’t depend on it.

So naturally, we need to fix this unfortunate mistake to make topicB fork from master. To do so we can run:

git rebase --onto master topicA topicB

As seen previously, step 1 is hard resetting to master, step 2 is applying commits topicA..topicB .

Another example

I told you in the intro that we could also remove a range of commits by using this option. Well lets say we have a branch like the one below and want to get rid of commits F and G.

We can run:

git rebase --onto E G I

Step 1 is a hard reset to E and step 2 is picking G..I (as a reminder G is excluded: this is how .. works in git) F and G are removed.

Let’s play with the past?

The -i option lets us interact with any commits. We can move, squash, delete, rename, edit… It’s on of the most powerful git command that allows us to do pretty much anything with the git history.