Version control with Git has become a default in every modern developer’s tool belt. Commands like
pull have made it into our fingers’ muscle memory. But relatively few developers know about the “more advanced” features in Git — and how incredibly valuable they can be! In this article, we’re going to explore “interactive rebase”, one of the most powerful tools in Git.
In a nutshell, and without exaggeration, interactive rebase can help you become a better developer, by allowing you to create a clean and well-structured commit history in your projects.
Why is a well-structured commit history important? Just imagine the opposite: a hard-to-read commit history, where you have no idea what your colleagues actually did with their recent changes. More and more “dark corners” start to emerge in such as project, and you only understand the small parts that you worked on yourself.
Contrast this with a clean and well-structured commit history: it helps make a project’s codebase more readable and easier to understand. This is an essential ingredient for a healthy, long-lasting project!
What Interactive Rebase Can Do for You
Interactive Rebase helps you optimize and clean up your commit history. It covers many different use cases, some of which allow you to to the following:
- edit an old commit message
- delete a commit
- squash/combine multiple commits
- reorder commits
- fix old commits
- split/reopen old commits for editing
When to Use Interactive Rebase (and When Not To!)
Like a couple of other Git tools, interactive rebase “rewrites history”. This means that, when you manipulate a series of commits using interactive rebase, this part of your commit history will be rewritten: the commit’s SHA-1 hashes will have changed. They’re completely new commit objects, so to speak.
This fact calls for a simple but important rule to live by: don’t use interactive rebase (or other tools that rewrite history) on commits that you’ve already shared with your colleagues on a remote repository. Instead, use it to clean up your own, local commits — such as in one of your own feature branches — before merging them into a team branch.
The Basic Mechanism of an Interactive Rebase Operation
Although there are many different things that interactive rebase can be used for, the basic workflow is always the same. Once you’ve firmly understood this basic mechanism, interactive rebase will lose its air of “complex mystery” and become a valuable, accessible item in your tool belt.
Step 1: Where should you start the session?
The first question you need to answer is: “What part of my commit history do I want to manipulate?” This tells you where you should start your interactive rebase session. Let’s make a practical example and say we’d like to edit an old commit message (which is what we’ll actually do in practice in a moment).
Our starting situation is pictured below, where we’re editing an old commit message via interactive rebase.
To be able to change the commit message in
C2, we have to start our interactive rebase session at its parent commit (or even before that, if you want to). In this example case, we would use
C1 as the starting point for our interactive rebase session.
Step 2: starting the actual session!
Starting the actual session is pretty simple:
$ git rebase -i HEAD~3
We’re using the
git rebase command with the
-i flag (to indicate we indeed want it to be “interactive”) and provide the base commit (that we came up with in our first step above). In this example, I’ve used
HEAD~3 to specify the commit that’s “3 behind the HEAD commit”. Alternatively, I also could have provided a specific SHA-1 hash.
Step 3: telling Git what you want to do
After starting the interactive rebase session, you’ll be presented with an editor window where Git lists a series of commits — from the latest commit, all the way to (but not including) the one you picked as a base commit in Step 1.
There are two important things to keep in mind in this step:
- Commits are listed in reverse order! The newest commit, which we would expect to appear at the top, will appear at the bottom of the list. Don’t worry: your Git repository is fit as a fiddle! 🥳 Remember that we’re in the process of performing an interactive rebase operation, and this requires Git to reapply the commits from oldest to newest at the end of the operation.
- Don’t make your actual changes in this editor window! Although you might be itching to simply go ahead and change the commit message in this editor window (after all, that’s what we actually want to do …), you have to show some patience. Here, we are only going to tell Git what we want to do — but not make the actual change. I’ll demonstrate this point in practice shortly!
With this theoretical overview out of the way, let’s dive into some practical cases together!
Editing an Old Commit Message
One of the very popular use cases of interactive rebase is that you can edit an old commit message after the fact. You might be aware that
git commit --amend also allows you to change a commit’s message — but only if it’s the very latest commit. For any commit older than that, we have to use interactive rebase!
Let’s take a look at a concrete scenario. Below is an image of a bad commit message that needs to be corrected.
Note: For a better overview and clearer visualization, I’m using the Tower Git desktop client in some of my screenshots. You don’t need Tower to follow along in this tutorial.
For our example, let’s say that we’d like to edit the message of the commit currently titled “Optimize markup structure in index…”
Our first step is to determine the base commit for starting this interactive rebase session. Since we have to (at least) go back to the parent of our “bad apple” commit, we’re starting our session at HEAD~3 (three commits behind the HEAD commit, which is the one that’s titled “Change headlines …”):
$ git rebase -i HEAD~3
Right after executing this command, your favorite editor will open up and present the list of commits you just selected (by providing a base commit).
As a reminder: although you might be tempted to do so, we do not change the commit message here. We only mark up the respective line with an “action keyword”. In our case, we want to
reword the commit (which means we’d like to change the commit message, but leave the rest of the commit as is).
Quite practically, all the available action keywords are documented at the bottom of this window — so there’s no need to remember anything by heart!
Once you’ve replaced the standard
pick keyword (which means “take the commit as is”) with your preferred action keyword, you can simply save and close the window.
After doing so, a new editor window will open that contains the current commit message. Finally, we’re allowed to do what we set out to do in the first place: edit this old commit’s message!
After making our change and then saving and closing the editor window, the interactive rebase session is complete — and our commit message is updated! 🎉
Deleting an Unwanted Commit
Interactive rebase also allows you to delete an old commit from your history that you don’t need (or want) anymore. Just imagine you have accidentally included a personal password in a recent commit: sensitive information like this should not, in most cases, be included in a codebase.
Also remember that simply deleting the information and committing again doesn’t really solve your problem: this would mean the password is still saved in the repository, in the form of your old commit. What you really want is to cleanly and completely delete this piece of data from the repository altogether!
Let’s start by determining the base commit for our interactive rebase session. Since we need to start at least at the bad commit’s parent, we’re using the “Optimize markup structure…” commit as our basis:
$ git rebase -i 6bcf266b
Notice that, this time, I’ve used a concrete SHA-1 hash in the
git rebase -i command. Instead of the commit hash, of course, I could have used
HEAD~2 to address that commit.
After executing this command, we’re again presented with a list of commits.
This time, we’re using the
drop action keyword to get rid of the unwanted commit. Alternatively, in this special case, we could also simply delete the whole line from the editor. If a line (representing a commit) is not present anymore when saving and closing the window, Git will delete the respective commit.
However you choose to do it, after you’ve saved and closed the editor window, the commit will be deleted from your repository’s history!
Combining Multiple Commits into One
Another use case for interactive rebase is when you want to combine multiple separate commits into a single one. Before we dive into how this works, let’s spend a few moments talking about when or why this could be valuable.
Generally speaking, making commits “bigger” (by combining multiple into one) is not a good strategy in most cases. The general rule of thumb is to keep commits as small as possible, because “small” means “easier to read and understand”. But there are situations where this can make sense nonetheless. Here are two examples:
- Imagine that you noticed a problem with an older commit. You might then go ahead and produce a new commit that fixes the problem. In such a situation, being able to combine these commits into a single one makes a lot of sense: the newer commit, after all, was just a “Band-Aid” to fix a problem that shouldn’t have been there in the first place. By combining these commits, it looks like there never was a problem in the first place!
- Another example is when you notice that you made things a bit too granular. Making small commits is all good and well, but littering your commit history with lots of unnecessarily small commits would mean overshooting the mark.
The rationale is the same in both examples: by combining two (or multiple) commits that should have been a single one in the first place, you’re producing a cleaner and more readable commit history!
Let’s walk through a practical example together and take the situation pictured below as our starting situation.
Let’s say that, semantically, it makes more sense for these two commits to be a single one. Using the
squash tool of interactive rebase, we can indeed combine them:
$ git rebase -i HEAD~3
By now, you’re already used to what happens next: an editor window opens up with a list of commits.
I already mentioned that we’re going to use the
squash action keyword in this case. There’s an important thing to know about how
squash works: the line you mark up with the keyword will be combined with the line directly above! This explains why I marked line 2 with the
squash keyword in our example.
After saving and closing this window, a new one will open up. This is because, by combining multiple commits, we are of course creating a new one. And this one needs a commit message, like any other commit, too!
What you see in the above screenshot is what Git prepared for us: it combined the commit messages of the respective original commits along with some comments. You’re free to delete the old messages and start fresh — or keep them and add some more information.
After saving and closing this editor window, we can proudly state: what used to be two separate commits is now a single one!
Harnessing the Power of Interactive Rebase
I hope you agree that Git’s interactive rebase tools can be very valuable! As developers, it’s important for us to strive for a clean and clear commit history. It’s a crucial ingredient in keeping a codebase healthy and easy to understand (both for your teammates, and yourself, after some time has passed).
If you want to learn more, I highly recommend the “First Aid Kit for Git”. It’s a (free) collection of short videos that show you how to clean up and undo mistakes in Git.