Using Git's 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 afterwards wonder how you got by without knowing it.
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 afterwards 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.
Update (June 2019): This article was originally written in 2013. You know how it feels to come back to something you did a long time ago and all you can do is see everything wrong with it? Well I just did that and, long story short, spent a while editing it and updating it so it doesn't make me cringe quite as much. Cheers!
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.
Let's say you’ve been working a bit on a new feature, and if you're anything like me if you run git log
you'll find you've ended up with history that looks like this:
master
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
origin/master
(Other commits already pushed to the upstream repo)
There's five local commits here, but only two of them are actual commits. The rest are fixing errors or bugs in code that no one else has seen. Leaving them in the history would just clutter it up. It's best for each commit to represent a complete bit of work so it's easier to understand by reviewers now, and by other developers later if they have to go back into the history.
This is where "interactive rebase" comes in. It's a tool in Git to let you edit the recent history in almost any way you could want. The best way to understand it is to actually use it. Since we’ve added five commits here, we start by running a rebase with the -i
("interactive") flag:
git rebase -i HEAD~5
This will open up your editor – the same one you'd use to write a commit message – and at the top you'll see 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. Below that, there's some instructions about what's going on including a list of commands you can use:
pick
— leave the commit as-isreword
— change the commit summary only (this is a recent addition)edit
— change the commit contents, much like doing agit 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, like with squash but throw away the commit message instead of appending them.
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
All I've done here is change the command “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 rewrite the commit history so it looks like this:
master
Implement the remainder of feature XYZ- Implement part of feature XYZ
origin/master
(Other commits already pushed to the upstream repo)
It's important to understand here that all the changes from the commits are still there! They've just been incorporated into the previous commit. So, for example the changes in the "Oops, forgot to add this file" commit, presumably to add the missing file, is now part of the previous "Implement part of feature XYZ" commit where it belongs.
As I said, I use this almost daily to prepare a patch set for review. After I rebase, I prepare patch emails with git format-patch -2 --attach
, then send them with git send-email 000[12]* --to=mailinglist@somewhere.com
.
Update (June 2019): I'm leaving that last paragraph in for posterity. I can't believe that used to be my workflow! If I were writing this today I would have talked about posting a pull request with the changes.
I hope that helps you out. Interactive Rebase is a very useful feature of Git that I haven’t seen in other source control systems. It can be a bit confusing at first but once you get the hang of it, it's a very handy tool making the history clean and easy to understand for reviewers and for anyone who may look at the history later.
Thanks for reading
If you need an expert at building complex systems and leading development projects, I want to hear from you! I'm available on a limited consulting basis. More information on the services page.