Git tip: Using interactive rebase

Interactive rebase is a one-stop shop for revising history: combining, removing and reordering commits. It’s one of those things where you learn it, and afterward wonder how you got by without knowing it. I use it almost every day when preparing a topic branch for review. If I have to make changes during review, I commit them as incremental patches, then squash them with an interactive rebase before I commit.

In this post, I’ll explain a bit about how it works. Let’s forget about topic branches right now, and just assume you’re working directly on the master.

Say you’ve been working a bit, and you have a recent history that looks like this (most recent to oldest):

  • Fix a bug when you foo the bar
  • Fix typo
  • Implement the remainder of feature XYZ
  • Oops, forgot to add this file
  • Implement part of feature XYZ
  • (Ancient history that’s in the upstream repo)

If your history doesn’t look like this you either don’t commit often enough or you’re super-human. But we all want to appear super-human, so let’s clean that up a bit before we show it to anyone.

We’ve added five commits here, so we want to run the following command:

git rebase -i HEAD~5

(If we were using a topic branch, we could do git rebase -i master — this also has the effect of rebasing the topic branch if the master has been updated)

This will open up your editor, with something that looks like this:

pick 39a2bef Implement part of feature XYZ
pick 4bc987e Oops, forgot to add this file
pick f9282bc Implement the remainder of feature XYZ
pick 2c5dd33 Fix typo
pick a524b5c Fix a bug when you foo the bar

On each line, you have the command (“pick” here), the commit ID, and the description. At the bottom of the file, you’ll see a list of commands you can use:

  • pick — leave the commit as-is
  • reword — change the commit summary only (this is a recent addition)
  • edit — change the commit contents, much like doing a git reset --mixed
  • squash — keep the changes in this commit, but merge it with the commit on the previous line, and append the commit messages and allow the message to be edited.
  • fixup — keep the changes, as with squash but throw away the log message.

You can also delete a line to discard a commit entirely, or reorder them. The idea is that you edit this file so it shows the history you wish you created, then write the file and close your editor. Let’s change it so it looks like this:

pick 39a2bef Implement part of feature XYZ
fixup 4bc987e Oops, forgot to add this file
pick f9282bc Implement the remainder of feature XYZ
fixup 2c5dd33 Fix typo
fixup a524b5c Fix a bug when you foo the bar

I’ve just changed “pick” to “fixup” for all the mistake commits I made. This will roll them in with the commits above them so that, when we close the editor, git will re-write your history so it looks like this:

  • Implement the remainder of feature XYZ
  • Implement part of feature XYZ
  • (Ancient history)

Mistakes? What mistakes? :)

As I said, I use this almost daily to prepare a patch set for review. After I rebase, I’d prepare patch emails with git format-patch -2 --attach, then send them with git send-email 000[12]* --to=mailinglist@somewhere.com.

Hope that helps you out. Rewriting history like this is a key feature of git that I haven’t seen in other source control systems, and it goes a long way towards making your project history look professional.



comments powered by Disqus