Interactive rebase is one of my most used Git commands. It’ll let me slice and dice commits in various useful ways. Here’s a few tips for you to unleash the true potential of rebase :)
- Basics: editing, combining, reordering and removing commits
- Breaking a commit into two
- Taking a part of a commit and bringing it to HEAD
- Bonus! How to recover commits if you screw up
Basics
Before we go on, you should understand the basic commands of the interactive rebase tool – If you are familiar with rebase, feel free to skip this part.
First, to enter interactive rebase, you use the following command:
git rebase -i <commit>
commit can be any valid commit – typically it’s a commit before HEAD, and we can use the usual markup to go to a specific commit. For example, to rebase with four older commits:
git rebase -i HEAD~4
This will result in your default editor opening, with text similar to this input:
pick 41cf624 Added connection availability check when flushing queued messages pick a14e959 Added timeouting to clients pick 1934b6e Added disconnect event handler to CometdServer pick 0e8a050 Changed connections so they no longer throw exceptions if they've b een suddenly closed # Rebase c4ca5a7..0e8a050 onto c4ca5a7 # # Commands: # p, pick = use commit # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. #
To perform rebase commands, simply edit this message, save and exit.
The message should be relatively straightforward. Each of the commits you chose are listed in order, prefixed with a rebase command. Pick means nothing will be done to the commit, edit means rebase will allow you to edit the selected commit(s) and squash means the commit will be combined into the commit before it.
To reorder commits, simply change the order the commits appear in. To delete a commit, remove it from the list.
To abort the rebase completely without doing anything, you can either leave the message as it is, or delete everything.
If you feel something went wrong during editing or you get a conflict, you can always use git rebase --abort to abort the rebase. It will return everything as it was before you began rebasing. This works even if there were other rebase commands performed before the edit.
Now, to the more interesting stuff!
Breaking a commit into two
Breaking commits into two (or more) is occasionally useful. For example, I sometimes want to restructure my commits into a more sensible structure – for example one bugfix per commit.
To split a commit with rebase, first find the commit you want to change. Then, use the edit rebase command on it.
When at the commit, reset the index backwards one commit:
git reset HEAD^
Resetting the index like this will rewind the index back one commit, meaning you get to see the changes that were in the commit you are editing. Now you can simply work as you normally would when committing: Choose the files you want to commit, and commit away!
You can commit as many times as you feel is necessary. You could also do a git stash pop, and add stashed code into the history.
When ready, simply do git rebase --continue
Take a part of a commit and bring it to HEAD
Sometimes you may notice that you’ve committed something you didn’t mean to. And you did a few commits on top of that. Oops.
Not to worry, we can easily grab a part of a commit in history and bring it back to the future!
As with the previous tip, find the commit you want to change and run the edit rebase command on it.
You can again do a reset, but if your commit is large, you may find it easier to use git gui to selectively remove things from the previous commit. This is often a time-saving way to just choose the parts you want to change instead of having to do all the commits again.
To change parts of the commit with git gui, run it and choose “Amend last commit”. This will make the changes in the commit show up in the Staged Changes box. Now you can simply choose the changes you want, and remove them from the commit. When done, choose “Commit”. Then exit git gui.
Now you should have the changes you want to move to HEAD. Now, just stash them using git stash, continue the rebase with git rebase --continue and then pop your changes from the stash with git stash pop. You now have the changes from the commit editable in HEAD.
How to recover lost commits
While rebase is a powerful tool, it’s also a very powerful way to totally screw up your repository. Kind of like attempting to use a sledgehammer to squash a bug…
Luckily git actually keeps track of all the changes to your branch tips. This means that even if you delete a commit, you can still get it back with minimal fuss!
To revert a rebase, you can use git reflog. You first need to know the reference you need to return to – usually it’s the previous for the branch, but to be sure check with the following:
git reflog --all
This will output something like this:
1c98e78 refs/heads/master@{0}: rebase -i (finish): refs/heads/master onto 86d0f9 317e759 refs/heads/master@{1}: rebase -i (finish): refs/heads/master onto 86d0f9 b197487 refs/heads/master@{2}: commit: added bar.txt 083838a refs/heads/master@{3}: commit: more foos and bars
In this case, we can see I had done two rebases – master@{0} and master@{1} – and two commits. Since I had not done anything after the botched rebase, which in this case is master@{0}, I can do a reset to the previous:
git reset --hard master@{1}
This would return the history and everything back like it was before the rebase. Note that master is the name of the branch I was working on, so it may be different.
In closing
Knowing how to do these with rebase should help you do about everything else you might need to. Everything else works with similar principles, you just may have to apply the approaches together.
Finally some miscelanous advice: If your rebase conflicts, do not attempt to amend the previous commit. These words of wisdom come from Ilari on the #git IRC channel. Also, be wary of rebasing any commits that have already been pushed, pulled, or in the case of git svn, dcommitted. This will most often just end up screwing things up for everyone.
Did I miss anything? Do you know any other good ways to use git rebase -i? I want to know, so eave a comment!
More git tips:
Git tips for SVN users