The gospel of git: Interactive Rebase

Today I saw this in my scrollback buffer:

<believer1> so, I changed the patch to not break dbus proto, tested it ... problem is,
            that it is followed by the sftp bit ... what is the best practice? 
            cherry-pick to the different branch apply and amend? or reset apply amend?
<believer1> any special tricks?
<believer2> checkout the commit you wanna edit into a different branch, change it, then
            rebase the original branch on top of it
<believer2> i think that's what i always do
<believer1> sounds reasonable

And it was good. These disciples are trying to follow the holy commandments of git, including the important:

Thou shalt keep a clean version history

However, while they are good of heart, doing the best they can, they are not properly enlightened on the book of rebase in the gospel of git. Fortunately such enlightenment is easily come by with some reading and training.

The first rule when it comes to rebases is:

Don’t fear the rebase

Rebasing is history rewriting operation, and most fear of the rebase come from the fear of losing ones history. This is only natural, after all those who lose their history are doomed to rewrite it again. However, once a change is comitted git never loses it (unless you manually gc which I recommend never doing). If you rewrite the history of a branch and the branch now points to this new history, the old commits and history are still stored in your .git directory. All you need to know is the sha1 id of the last commit before your rewrite and you can always access it. For instance, if you’re on a branch that you completely messed up you can get everything back using:

git reset --hard <old-sha1>

This will reset the current branch so that it points to the old history.

If you’re uncertain of how rewriting works and fear something will go wrong, just store away the previous commit which you can easily get using e.g. gitk (or using “git rev-parse HEAD” if you want to be hardcore). And, if you forgot to do this, things are not lost, you can use “git reflog” to find the old history.

So, having overcome our fear of the rebase, how does one actually use interactive rebasing in this case? The background is that you have a bunch of local commits on you branch, each implementing a small independent change (so called microcommits), but before this is applied or merged into a public repo you discover a bug in one of the changes in the middle of the series. It would be nice if such a bug was never visible in the final version history. What to do?

Typically what I do is commit the fix like usual, with a short commit message like “Fixed up the foobar change”. Then you start the interactive rebase using “git rebase -i origin“. This will bring up your chosen editor with a bunch of lines in them, looking something like this:

pick fa1afe1 Implement the foobar function
pick 124efd3 Use the foobar function
pick cafe123 Fixed up the foobar change

Each line corresponds to one commit on your branch that will be applied during the rebase. You can change the order of the lines to change the history order, or you can delete lines to drop certain commits. What we want to do here is move the fixup to just after the right commit and then change “pick” to “squash” (or just “s” for short) which will merge the two changes into one. So change it to this, save and exit:

pick fa1afe1 Implement the foobar function
s cafe123 Fixed up the foobar change
pick 124efd3 Use the foobar function

This will bring up an editor with the commit messages for the first two commits which you edit (often just remove the second one), save and exit. Then the rest of the rebase is done and we end up with a clean history.

The true disciple of git always uses a local branch for each feature he works on, and then when it is done, it is merged into master and then pushed. I must confess that I sometimes sin here, doing minor feature development directly on master, rebasing to clean up and then just pushing that. Sometimes when I do this it turns out that the feature was more complicated than I expected and I didn’t finish it before I had to work on something else. If you end up in this situation its very nice to know that you can then create a branch afterwards by just doing:

git branch <branchname>
git reset --hard origin/master

This (if run on master branch with “origin” being the upstream repo) will create a new branch with the given name that contains your local changes, then it will change the master branch so that it points to what upstream points to, allowing you do do other work and return to the branch later.

4 thoughts on “The gospel of git: Interactive Rebase”

  1. Little addition: Instead of

    git reset –hard

    and remembering the old hash, you can use the reflog and do

    get reset –hard @{1}

Leave a Reply to David Cancel reply

Your email address will not be published. Required fields are marked *