This post was originally published on https://belev.dev/some-of-the-most-used-git-interactive-rebase-options
In the previous blog post Git merge vs rebase to keep feature branch up to date we learn about the differences between
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
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
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-authorcan 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
- 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.
git rebase re-applies one-by-one the commits from your current branch onto another. The
-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
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
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 <email@example.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
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 <firstname.lastname@example.org> 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
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 <email@example.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
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.
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
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
B, I would strongly suggest reconsidering reordering. It is very likely to not need reordering but something else instead.
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. I hope you enjoyed it and learned something new. If so, please follow me on Twitter where I will share other tips, new articles, and things I learn. If you would like to learn more, have a chat about software development or give me some feedback, don't be shy and drop me a DM.
Top comments (0)