Skip to content

Martin Belev

Some of the most used Git interactive rebase options

Git7 min read

Don't be afraid to use git rebase

In the previous blog post Git merge vs rebase to keep feature branch up to date we learn about the differences between merge and rebase. If you aren't very familiar with the basics of those commands and haven't read it yet, it is a good idea to do so before diving into this one.

The idea of this blog post is to show with examples some of the most used options of git rebase command. In my experience, I observed that this is one of the most misunderstood commands, especially for less experienced Git users. Hopefully, this article will provide useful information and things are going to be clearer after you read it. We will go through the following things:

  • amend the last commit
  • interactive rebase
  • squash several commits into a single commit
  • fix older commit
  • reorder commits

Mastering interactive git rebase is a good thing because it enables us to keep a clean and linear history of our changes. This is a great benefit especially when a regression has to be discovered. It is making the work of other people more pleasant as well by quicker and easier orientation.

Before we begin, we should know that all of the examples which we are going to explore from now on are changing Git history. So, please, if you are working on a public branch be extremely careful with it.

In general, we shouldn't change history for public branches like master/develop.

Let's get started now.


Amend the last commit

The provided examples will be with simple text files but they are completely valid in real-life scenarios. Let's start by creating and committing a file with a typo in it.

echo "Helo world." > ./hello-world.txt
git add hello-world.txt
git commit -m "Add hello world"

git commit --amend is the command which we are going to use. It is a convenient way to change the latest commit. But what is it doing really?

  • It creates a new commit which means it changes the history as noted above.
  • It has the same parents and author. --reset-author can be used to change the author.
  • It is using the current commit as a base and the commit message is reused if one is not passed through the -m "Message" option.
  • All staged changes are applied to the commit.
  • Provide a way to change the commit message.

Let's fix the typo and execute git commit --amend.

echo "Hello world." > ./hello-world.txt
git add hello-world.txt
git commit --amend

This is going to open your Git editor - mine is Vim and the examples will be with it. We will see something similar to the snippet below:

Add hello world
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Wed Jun 10 19:48:04 2020 +0300
#
# On branch master
# Changes to be committed:
# new file: hello-world.txt

We didn't provide -m "Message" but Git allows us to change the message if we want. By click i to enter insert mode, we can change the commit message. If we are happy with the changes, click Esc and enter :wq + <Enter> to save and exit. With this, the amend is completed.

Interactive rebase

git rebase re-applies one-by-one the commits from your current branch onto another. The --interactive (-i for short) option provides additional commands for altering individual commits - edit, re-word, squash, .etc. The syntax is the following git rebase -i <after-which-commit> where <after-which-commit> is exclusive. Below we can see a part of how it will look and the options from which we can choose to alter certain commit.

pick 70228b2 update master.txt
pick f1a75f4 Add hello world
# Rebase 6995ded..f1a75f4 onto 6995ded (2 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

Squash several commits into a single commit

I was asked to squash my commits into one in my first PR ever. It was 2015 and I had just started my first job. I didn't know much about Git back then. The only commands I have used were git commit and git push. When I received that comment I didn't know how to do it so my only option was to just search and learn something new.

Consider contributing to open source projects as soon as possible. You will only benefit from it. Don't think that fixing a typo or contributing to documentation is a small thing.

While working we can make a lot of small commits, WIP commits, have commits for fixing PR comments or whatever we decide basically. However, when it comes to merging our branch into master we couldn't allow everything to get into it. Most of the time, those commits are not bringing much value and there is no need to have them in our master branch after merging. Especially if you are using Conventional Commits and generate a Changelog from the commits. Squashing commits into one is there for us to achieve this.

Let's see an example of what we want to achieve in the following diagram:

// Current state
A master
\
B--C--D feature
* 367eb93 (HEAD -> master) Update text
* 30c1d6a Add hello world
* 70228b2 update master.txt
// Wanted state
A master
\
B'
* 30c1d6a (HEAD -> master) update master.txt

Count how many commits you would like to squash and execute git rebase -i HEAD~<commit-count>. In our example it will be 3 commits, so we are going to execute git rebase -i HEAD~3 which will open our Git editor (most of the options will be removed for the sake of brevity):

Note: If you are using Vim after every prompt of the Git editor enter :wq + <Enter> to save and exit which will complete the Git command.

pick 70228b2 update master.txt
pick 30c1d6a Add hello world
pick 367eb93 Update text
# Commands:
# p, pick <commit> = use commit
# s, squash <commit> = use commit, but meld into previous commit

An important thing to keep in mind is that the commits shown in the list are in reverse order. This means the oldest commit will be first on the list. In our example, we will see them as D-C-B. We will pick the oldest commit and squash the other two into it. The squash command has a short form that can be used by changing pick to s. This is saving us from writing a couple of characters.

pick 70228b2 update master.txt
s 30c1d6a Add hello world // short version
squash 367eb93 Update text // full version

After this we are going to see a pretty explanatory confirmation screen:

# This is a combination of 3 commits.
# This is the 1st commit message:
update master.txt
# This is the commit message #2:
Add hello world
# This is the commit message #3:
Update text

By using squash we are keeping all of our previous commit messages. They will be added in the description of the commit in which they were squashed. By using git log we can see it:

commit 1f4f780510181de87dd4358a1df1bb51f99e8c44 (HEAD -> master)
Author: Martin Belev <martinbelev93@gmail.com>
Date: Wed Jun 10 19:48:04 2020 +0300
update master.txt
Add hello world
Update text

Discard a commit message

If you don't want the message of a commit consider using fixup (f for short) instead. It will discard the commit message but keep the changes from the commit.

pick 70228b2 update master.txt
squash 30c1d6a Add hello world
fixup 367eb93 Update text

This will result in:

commit 1f4f780510181de87dd4358a1df1bb51f99e8c44 (HEAD -> master)
Author: Martin Belev <martinbelev93@gmail.com>
Date: Wed Jun 10 19:48:04 2020 +0300
update master.txt
Add hello world

Drop a commit

If you don't want some commits' changes, then you can consider using drop (d for short) which will remove the commit.

pick 70228b2 update master.txt
drop 30c1d6a Add hello world
d 367eb93 Update text

This will result in removing the added hello-world.txt file and those two commits:

commit 1f4f780510181de87dd4358a1df1bb51f99e8c44 (HEAD -> master)
Author: Martin Belev <martinbelev93@gmail.com>
Date: Wed Jun 10 19:48:04 2020 +0300
update master.txt

With this, we have completed the squashing several commits into one example.

Fix older commit

We will look at an example of how to fix older commit. Let's say we have made a couple of commits which we want to keep but we found out that in one of the older commits we missed something. In this case, we want to go back in history and make additional changes to some older commit.

// Mistake in commit D
A--B--C--D--E feature

The commit where we made a mistake is 2nd one (counting from the last one inclusive). We will execute the already familiar command git rebase -i HEAD~2 from our previous example. This will lead to:

pick 30c1d6a Add hello world
pick 367eb93 Update text
# Commands:
# p, pick <commit> = use commit
# e, edit <commit> = use commit, but stop for amending

As mentioned previously, we are seeing the commits in reverse order. This means that the 2nd commit which we want to edit is displayed as first in the list. We will use the edit (e for short) command to fix our mistake.

e 30c1d6a Add hello world
pick 367eb93 Update text

By saving and existing Git editor:

$ git rebase -i HEAD~2
Stopped at 30c1d6a... Add hello world
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue

We can see that we stopped at the commit which we want to edit. Git is providing us some helpful tips on what we can do.

Doesn't You can amend the commit now, with git commit --amend look familiar? This was our first example where we learn how to make a change to the last commit. We stopped on a specific commit which is making it last for the time being. Everything we should do now is making our changes and use git commit --amend like in the first example.

The only thing left to do, as Git suggested to us, is executing git rebase --continue with which we successfully fix a mistake in an older commit.

We went back only to the commit that we need to fix. This is not required specifically. We can rebase more commits and use the edit command which will stop us on the specified commit.

Reorder commits

With git rebase -i we can reorder commits as well. If the commits which we want to reorder don't depend on each other and don't have changes in the same files, it is as simple as cutting the commit we want to move and pasting it before or after some other commit.

The use case I have when I needed reordering is to squash a commit into a specific one in history. An example would be having A--B--C and the need to squash A into C. Which can occur when we have:

  • fix for commit 1
  • useful commit 2
  • useful commit 1

In the end, we want to keep useful commit 1 and useful commit 2 but it doesn't make sense to have fix for commit 1 squashed into useful commit 2.

Now we can have a look at an example. Let's try and move 70228b2 update master.txt before 367eb93 (HEAD -> master) Update text.

* 367eb93 (HEAD -> master) Update text
* 30c1d6a Add hello world
* 70228b2 update master.txt

We want to change the history for 3 commits, so we are going to execute git rebase -i HEAD~3.

pick 70228b2 update master.txt
pick 30c1d6a Add hello world
pick 367eb93 Update text
# These lines can be re-ordered; they are executed from top to bottom.

We are going to cut pick 70228b2 update master.txt and paste it after pick 367eb93 Update text which is going to make it our latest commit.

pick 30c1d6a Add hello world
pick 367eb93 Update text
pick 70228b2 update master.txt

Save and exit the editor with which we have completed the commit reordering. By executing git log --oneline we can make sure that everything is as expected and update master.txt is the last commit in our history.

* c8502f8 (HEAD -> master) update master.txt
* ba137dd Update text
* 08497a9 Add hello world

If the commits which you want to reorder depend on each other - for example, we use the changes from commit A in commit B but we want to reorder A and B, I would strongly suggest reconsidering reordering. It is very likely to not need reordering but something else instead.

Conclusion

My initial idea for this blog post was to be an in-depth, step-by-step tutorial of git rebase. However, writing it and thinking about the time when I started using Git I realized there is a bunch of stuff going on here and it can be difficult to grasp all at once. At least it was for me, a lot of going and practicing this command until I learned it. So I decided to split it and continue the other part in a separate one which will be about properly rebasing our branches.

Let's recap what we go through and why/how it can be useful in our day-to-day work:

  • Making changes to the latest commit - I am using it mostly for adding additional changes that I missed. I have seen some people who are using it when fixing PR comments instead of making separate commits. The drawback of this is that it is making the subsequent PR review harder because there is no way to see only the changes that were made.
  • What is interactive rebase and some of the options we can use with it
  • Squashing several commits into a single commit - I see this as one of the most important once in this article. We went through squashing by keeping and discarding commit messages, as well as deleting whole commits. This is extremely useful for keeping a cleaner history.
  • Fix older commit - to be honest, this one is rarely used IMO. However, it is good to know because it is used from time to time.
  • Reorder commits - I mostly used this when I needed to squash a commit into certain commit (not the previous one). Again, not very widely used but good to know.

We should already see how many things we can achieve by using git rebase -i. All of the seen options can be combined and used together which makes git rebase -i extremely powerful.

Thank you for reading this to the end 🙌 . If you enjoyed it and learned something new, support me by clicking the share button below to reach more people and/or give me a follow on Twitter where we can catch up. I am sharing some other tips, articles, and things I learn there.
If you don't like the article, please leave a comment below or write me a DM with feedback what you don't like so I can improve and provide better content in the future 💪.

© 2020 by Martin Belev. All rights reserved.