Fix a Commit History With Git Interactive Rebase
Git interactive rebase is a powerful tool that can help you fix up your commit history to make it more meaningful, professional, and maintainable. In this post, I step through what it is, how it works, and when you should — and shouldn't — use it.
Let's set the scene. You've been working away on a feature branch for about a week and, when you submit the PR for review, the first thing that the reviewer comments on is that the commit history is a bit of a mess. Yes, sadly, something like this happened to me recently. 😪
You look at the commit history of your feature branch and see that:
- There are several commits, one after another, that could logically have been created as one commit.
- You've created a commit, including most of the files, and later added one or several other files, which were overlooked for the earlier commit; and, to top it off
- Some of the commit messages don't really convey the most significant amount of context, and you're starting to get worried that in 3+ months from now, no one — least of all you — is going to know what they all meant.
Okay, this is a little extreme. Well...maybe. But, one or more (or all) of these could reasonably happen, if you're not the most diligent when it comes to creating commits.
And, let's be fair; we all have off days, get sick, feel stressed, or get distracted for several other, legitimate reasons. During these times, it's reasonable to expect that we won't make the most sound decisions.
For these times, it's heartening to know that git — unlike other version control tools — allows you to fix up poor past decisions by using interactive rebase.
If only it were that simple in real life!
What is Git Interactive Rebase
So, what exactly is git interactive rebase?
If you search for
--interactive in git's rebase man page (or on git-scm.com), you'll find the following definition.
-i, --interactive Make a list of the commits which are about to be rebased. Let the user edit that list before rebasing. This mode can also be used to split commits (see SPLITTING COMMITS below). The commit list format can be changed by setting the configuration option rebase.instructionFormat. A customized instruction format will automatically have the long commit hash prepended to the format.
I don't feel that that definition is quite as clear as it could be. So here's my take on it. Git interactive rebase, lets you change a set of one or more contiguous commits, regardless of if they're from the most recent backwards, or from an earlier point in a branch's commit history.
When Would You Not Use it?
I've covered several use cases for when you would use interactive rebase. But there are also times when you wouldn't use it.
As interactive rebase changes a commit history, it's functionality that you have to use with care and wise judgement. If you're working on a branch with even one other person (and a lot of the time we are), you run the risk of changing your coworkers' history, not just your own.
If they've been working on some changes, but you push your changed history first, then they won’t be able to push their changes. What’s more, before they can they'll have to update their local version.
This may result in conflicts, which they'll then have to correct, before they can push their changes. You can only imagine just how happy they're going to be with you if you do this to them.
So, here are my recommendations. Do perform an interactive rebase if:
- You are the only person working on a branch; or
- If you're sure coworkers aren't working on changes at the moment; and
- You communicate to coworkers that you're going to do an interactive rebase session
I'm sure there are other things to consider, but this is the core of my list.
BTW, what are your suggestions, if you're a regular interactive rebase user?
How To Use Git Interactive Rebase
To start an interactive rebase session, you have to pass two things to the
git rebase command:
- A commit range
Here's an example that I might follow pretty commonly:
git rebase --interactive HEAD~3
This will allow me to interactive rebase the last three commits. After running that command, I'll see the following text in VIM, my default shell editor:
pick 611ffc0 Changed the publish date and reduced the lead image size pick 35dd30c Link to the second burp suite article on creating rules pick 8927b05 Add article on ctrl+x+e as a productivity hack # Rebase a85455d..8927b05 onto a85455d (3 commands) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup <commit> = like "squash", but discard this commit's log message # x, exec <command> = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop <commit> = remove commit # l, label <label> = label current HEAD with a name # t, reset <label> = reset HEAD to a label # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] # . create a merge commit using the original merge commit's # . message (or the oneline, if no original merge commit was # . specified). Use -c <commit> to reword the commit message. # # These lines can be re-ordered; they are executed from top to bottom.
You can see that it's quite similar to a standard command-line-based commit message, with some key differences. Of these options, I typically use: reword, edit, squash, and drop, and they're the ones that I find most sync with the topic of this post. So those are the ones I'm going to talk about today. However, I'll touch on the others in a bit of depth shortly.
Picking a Commit
Picking a commit uses the commit at that point in the commit order. It doesn't change it, reword it, etc. You can see that this is the default choice.
One thing that might not be obvious, is that you can reorder commits. To do that, you just move a commit line from one position in the commit history to another. For example, let's say that we want to swap the first and last commits (for whatever reason). To do that, we'd change the earlier example, as in the following example:
pick 8927b05 Add article on ctrl+x+e as a productivity hack pick 35dd30c Link to the second burp suite article on creating rules pick 611ffc0 Changed the publish date and reduced the lead image size
After the file is saved and closed, rebase will reorder the commits and continue — if it can.
Please bear in mind that git may not be able to do that.
If so, you'll have to abort the rebase, using
git rebase --abort or you'll have to fix the rebase conflicts.
Rewording a Commit
If you want to reword a commit message, or commit description, this is the option to choose.
To reword a commit, change pick on the line of the commit that you want to reword to either
reword or only
Here's how we would instruct git rebase to reword the final commit:
pick 8927b05 Add article on ctrl+x+e as a productivity hack pick 35dd30c Link to the second burp suite article on creating rules reword 611ffc0 Changed the publish date and reduced the lead image size
Then, when the rebase runs, you'll see the commit message in your git editor, as you usually would, and you can change it to make it more meaningful, then save and close your editor as usual.
Editing a Commit
This is more involved than most of the other options, as you can change the commit itself, not just the commit's message.
This functionality lets you split a single commit into several, which is quite handy if some of the changes don't make sense being part of the commit.
To do this, change
e, as in the following example.
pick 8927b05 Add article on ctrl+x+e as a productivity hack pick 35dd30c Link to the second burp suite article on creating rules edit 611ffc0 Changed the publish date and reduced the lead image size
When the rebase runs, your history will be checked out at that commit.
You can then reset the commit, change it as you need, and create one or more commits, as the need requires.
After you're finished, use
git rebase --continue, to continue with the rebase session.
Squashing a Commit
Squashing a commit is when you need to merge multiple commits into one (or fewer) commits. You can squash a long contiguous set of commits, or you can squash multiple contiguous commits, whatever the need demands.
In the example below, two contiguous blocks of commits will be squashed into two commits:
pick 35dd30c Link to the second burp suite article on creating rules squash 8927b05 Add article on ctrl+x+e as a productivity hack squash 35dd30c Link to the second burp suite article on creating rules squash 2672275 Rework content into a better site structure s da8399f Complete SEO audit pick 6b64d39 Add content plan for 2020 pick fee7dea Add draft content of git articles squash 37c5593 Add article detailing how to get started with Antora s 121db2e Add article showing how to create redirects in Antora s 9b09df9 Add another article on Burp Suite s 8c0469d Fix up links in original Burp Suite article squash a3ed7ea Optimise image content
After you save the interactive rebase instructions, rebase will then open up your git editor, once for each set of commits that you want to squash, and create a commit message which is a combination of the commit messages of each of the respective commits.
Each commit message will be separated, so that you know where one finishes and the next begins. From there, you have to create a new commit message, save the file, and exit your editor.
You can create a message by amalgamating parts from each commit message. You could also use some parts and completely throw away others. Better yet, you could completely start over. The choice is yours.
Dropping a Commit
To drop a commit, change
That will remove the commit from the commit history.
In the case above, we would drop the final commit as follows:
pick 35dd30c Link to the second burp suite article on creating rules drop 8927b05 Add article on ctrl+x+e as a productivity hack
So that's how to change your commit history using git interactive rebase. It's a very powerful tool, one that I recommend using, discerningly, as and when the need arises.
While there are downsides to using it, if we bear in mind that we may be working with other developers and how our changes may impact them, I'm sure we'll do just fine.
Have you used interactive rebasing yet? If not, are you keen to give it a try? If you don't I'd love to get your opinion in the comments.
- Git Tools - Rewriting History
- 4 Git Command-Line Tips for Greater Productivity
- How to Get GitHub-like Diff Support in Git on the Command-Line
P.S. If you need assistance on any of your PHP projects, I am available for hire for freelance work.