Rewrite the past in fewer steps with rebase

January 18, 2019

There are lots of commands in git and you can often achieve your goal in several different ways. I usually manage to get my stuff done with just few cmds 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 knew that rebasing with the --onto option would be useful, I never took the time to man it… until recently. I guess now is a good time to write something about it.

🌳 I assume in this post that you have basic knowledge about git, and that you are familiar with its index, working tree as well as commands like reset and cherry-pick

When can the --onto option be useful?

Here are some use-cases:

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

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


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 you 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, you can either resolve the conflict by running git rebase --continue or get back to the state you left before the rebase with git rebase --abort

Note If you already are on the topic branch (HEADtopic) then you 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

You 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, you need to rectify this unfortunate mistake to make topicB fork from master. To do so you 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 you have a branch like the one below and want to get rid of commits F and G.

You 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.

And the -i option?

The -i option lets you visualize commits that are concerned by the rebase and interact with them. You can squash, delete, rename, edit… basically playing with the past!!