Git's interactive rebase feature
Software development is often a messy process. During development, we frequently have commented or unused chunks of code, spelling errors, or even logic errors in our code. Luckily, with version control systems like Git, we can decide ourselves when we are happy enough with our code to share with our team or the rest of the world. Every time we are satisfied with the state of a logical chunk of code, we create a commit containing our changes.
Nevertheless, these commits are often not as clean and perfect as we wish they would be. When looking at them after a while we often realize that we have a spelling error in our commit message, it took us several commits to correctly implement a certain feature or that we mixed changes from a feature and a bug fix in the same commit.
This is where Git’s interactive rebase feature comes to the rescue.
Maybe you have heard of Git rebase before and you’re wondering what the difference to an interactive rebase is.
A normal rebase let’s you reapply the commits of your branch on a different base commit. You may want to do this if you branched off your branch from master a while ago and now want to base your branch on the current state of the master branch to discover possible conflicts and issues before the actual merge back into the master branch.
An interactive rebase, however, let’s you clean up your commit history from flaws and silly oversights, which you often only spot in hindsight.
In this tutorial article, I will show how to use interactive rebase to:
- Change your commit message
- Split one commit into two or more commits
- Change the order of your commits
- Squash two or more commits into a single one
- Edit your commit to add or remove changes to/from it
Having a clean Git commit history sounds great, but what is the benefit of investing the extra effort? Well, besides the aesthetics you get a semantically correct and meaningful commit history, which will be easier to understand. Anyone, including yourself, who will have to look through the old commits at some point in the future will be grateful for that.
Using interactive rebase on your commits does not edit them, but creates new commits instead. Therefore, it is highly recommended to only use the interactive rebase function on your local commit history, before you push it to the remote.
Interactive rebase on the command line
We will see how to use the interactive rebase feature on the command line. But fear not, it’s fairly intuitive and I will explain the process step by step. Nevertheless, if you think Git’s interactive rebase is an interesting feature, but you don’t want to use the command line, you can also use your Git GUI client. If you’re using Sourcetree, you can try this tutorial:
All the commands are executed in the Terminal app on a Mac, commands on another platform might be slightly different.
Clone the project
If you want to follow the exact steps described in this tutorial, you can clone the demo Git repository as described below.
Open the terminal application and navigate to a location, where you would like to work on the project. If you would like to work on it in your home directory navigate to it first by typing:
Now clone the project and navigate inside:
git clone https://bitbucket.org/karl_mattes/git_interactive_rebase/src/master/ cd git_interactive_rebase
Please note that the intent of the demo repository is to illustrate interactive rebasing and not to actually provide meaningful content.
Check the Git commit history
Before we can actually start improving our commits using the interactive rebase, we first need to take a look at the commit history of the Git repository. To get a brief overview type the following:
git log --oneline
This command will show all the commits on the current branch, with each of them summarized on a single line. For each commit, you can see its commit id, a hash containing 7+ characters, and the actual commit message. If you take a closer look at the commit messages you can see that some of the commits need improvement.
Correct a spelling error
The most obvious issue is probably the bug fix commit “fix: spelling eroor on home page and add logo”, which talks about fixing a spelling error in the code but contains a spelling error in its own commit message. Luckily that’s a rather easy fix. Let’s start our first interactive rebase session by typing the following:
git rebase -i 2d4163c
The “-i” tells Git that we want to do an interactive rebase, while the “2d4163c” tells Git that we want to do the interactive rebase based on the commit with this commit id.
Note: You might need more than 7 characters to identify your commit, depending on the size of your project. Luckily Git will automatically show more characters of the hash, if necessary.
After you typed the command Terminal will send you to an editor: Vim. Vim is a very powerful editor, which takes some practice to master. Here we will only use a few commands to make the process as easy as possible.
As you can see Git already listed all your commits, which are based on the commit you picked in the command, in reverse chronological order. It also added a lot of text, which tells you which commands you can use during the interactive rebase.
Let’s fix our spelling error now. Start by tapping the “i” key on your keyboard. This tells Vim that you want to change into the edit mode to actually edit the text. Vim will indicate this by showing “-- INSERT --” in the bottom line of the Terminal. Navigate to the beginning of the line of the commit, with the spelling error, remove the word “pick” and instead write “reword”. This will tell Git, that we want to reword exactly this commit. To actually edit the commit message we have to quit and save the current Vim session. To do so first tap the Escape key, to leave the insert mode, then type “:wq” and hit Enter.
Git sends you right into the next Vim session. This time it only contains your commit message and some additional information. Again, press the “i” key on your keyboard to get into insert mode and fix the spelling error. Finish the process by pressing the Escape key, typing “:wq” and hit the Enter key.
And that’s it, you managed to remove the spelling error from your commit message. You can confirm the change by typing:
git log --online
Note: Besides the first commit id your commit ids will be different from mine from now on. Make sure to adapt the commit ids to your own whenever needed.
Of course, you can not only use this feature to fix spelling errors but also to completely reword your commit to your liking. Sometimes it takes a while until we figure out how to correctly describe what we did in our commit.
Split a commit into two for a better separation of concerns
You might have noticed that the commit we just fixed actually also has another issue. It mixes a fix and a feature commit. In this step, we will split up the commit into two, to have a better separation of concerns. We will start with the same command as before:
git rebase -i 2d4163c
In Vim tap the “i” key to enter insert mode, erase the “pick” keyword of the respective commit, and type “edit” instead. Tap the Escape key, enter “:wq” and hit Enter to save and leave Vim.
This time Git does not send you right into the next Vim session but sends you back into the Terminal. However, the split of the commit into two commits isn’t done yet. Back in the Terminal, we have to reset the original commit first to stage and commit each of the changes separately.
To reset type:
git reset HEAD^
Afterward, type the following to confirm you have uncommitted changes:
You should be able to see that you have files that are either not staged or currently untracked. Let’s create the first of our two new commits by staging and then committing the logo inside of the images folder. Type the following:
git add images/
Besides the logo itself, we also need to add the code that embeds the logo into the Html of our home page. To do so we type the following:
git add -p index.html
Git then presents a rather confusing interface with a lot of choices. Type “s” and hit Enter. This will allow us to decide about each of the hunks separately. The first hunk will be the correction of the spelling error, so for now we type “n” and hit Enter. The second hunk will be the addition of the image, so type “y” and hit Enter.
Now, as all the relevant changes are staged you just need to type the following to commit your changes:
git commit -m "feat: add logo asset and include it in homepage"
The second commit will be the fix of the spelling error in our code. As this is currently the only change left in the code we can just type the following to stage and commit it:
git add . git commit -m "fix: spelling error on home page"
We finished splitting our commit into two commits. However, we still need to inform Git that we’re finished with our interactive rebase. To do so we can just enter the command:
git rebase --continue
When you’re finished your history should look like the following:
Reorder your commits
Now that we split up the adding the logo asset and fixing the spelling error you might realize, that you didn’t want the spelling error in the first place. There is a way to get rid of the spelling error commit, but still fixing it. To do so we will first change the order of the commits, by swapping the fix of the spelling error (67535ab) with the logo feature commit (07b7ec0) and then squashing fix into our initial commit.
Let’s first start another interactive rebase session to reorder the commits:
git rebase -i 2d4163c
The reordering process is comparatively easy, but we will use some different Vim commands to ease the process. Changing the order of the two commits inside Vim using insert mode seems to be a bit time-consuming and error-prone, so this time we will use cut and paste instead. To cut a whole line type “dd” and to paste it type “p”. You should note that the line will be pasted underneath the line you are currently in with your cursor. Navigate your cursor to the first line, type “dd” and then just type “p” to paste the line.
After you changed the order of your commits in Vim tap the Escape key, type “:wq” and hit Enter to save and exit Vim. Git should tell you that the rebase was successful.
Squash commits that belong together into one commit
Now we can easily squash the two commits into one. To do so first start another interactive rebase session:
git rebase -i 2d4163c
Enter the insert mode by typing “i”, remove the word pick from the first line and type “squash” (or if you’re lazy like me just type “s”).
Now just save and quit by hitting the Escape key, typing “:wq” and tapping Enter.
And we’re done, that was easy.
But wait, the Terminal app is showing us an error message: “Cannot squash without a previous commit.”
So our squash did not work… How can we squash our fix commit into our first commit, if we don’t have any older commit id than the one of our first commit? To do so we will need to slightly change our interactive rebase command.
But first, we will need to abort our previous rebase before we can start a new one:
git rebase --abort
And now, we can change our rebase command like this:
git rebase -i --root
Back in Vim enter the insert mode and change your commits to look like this:
Save and leave Vim using the usual procedure. Now Git will send you right into the next Vim window, to decide the commit message. It will contain both commit messages, the feature commit, and the fix commit. As the fix is integrated right into our code, we don’t need its commit message anymore. To remove everything after the feature commit message, we can just move our cursor in the line underneath it and type “dG”, to remove all the following lines.
Now we just need to save and exit this Vim window using the usual “:wq”.
We finally managed to squash our two commits into one, if you want to you can check the log and confirm:
git log --oneline
Note: The squash command can also be used to squash three or more commits into one. To do so just change the command for consecutive commits in the interactive rebase session from “pick” to “squash”.
Edit a commit to remove a (sensitive) file from the commit
Take a closer look at your commits by typing:
git log --name-status
If you check the log for the commit which introduced the about page, you realize that it doesn’t only contain your change, but also a file named "password.txt". How did this one get in here? Well, no matter how it got here, we definitely do not want this file in our commit history. Let’s edit our commit and remove the file from our repository.
Start the interactive rebase session by typing:
git rebase -i 4b57b67
In Vim enter the insert mode, change the keyword pick to “edit” or “e”, and save and exit the session. Back in the Terminal type the following, to reset the changes of the commit:
git reset HEAD^
Now that all of the changes of the commit have been reset, we can delete (or move) our passwords document. To delete it enter:
Now we can create our commit the way we originally intended to create it. Let’s first stage all of our changes and then commit it:
git add . git commit -m "feat: add about page"
Great, the commit was successful. As the final step we will need to tell Git that we finished our interactive rebase session by typing:
git rebase --continue
Git’s interactive rebase gives us the opportunity to change the history of our commits at a later point in time to either fix mistakes or adapt our work to new circumstances. This can give us a semantically correct and meaningful commit history, which will be way easier to navigate through in the future.
However, interactive rebasing has its limitations and we should only use it while our work hasn’t been pushed to a remote repository, yet.
The repository has been transferred to: