— Git — 7 min read
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:
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.
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.txtgit add hello-world.txtgit 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?
--reset-author
can be used to change the author.-m "Message"
option.Let's fix the typo and execute git commit --amend
.
echo "Hello world." > ./hello-world.txtgit add hello-world.txtgit 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 --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.txtpick 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
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 stateA master \ B--C--D feature* 367eb93 (HEAD -> master) Update text* 30c1d6a Add hello world* 70228b2 update master.txt// Wanted stateA 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.txtpick 30c1d6a Add hello worldpick 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.txts 30c1d6a Add hello world // short versionsquash 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
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.txtsquash 30c1d6a Add hello worldfixup 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
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.txtdrop 30c1d6a Add hello worldd 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.
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 DA--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 worldpick 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 worldpick 367eb93 Update text
By saving and existing Git editor:
$ git rebase -i HEAD~2Stopped at 30c1d6a... Add hello worldYou can amend the commit now, with git commit --amendOnce 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.
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:
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.txtpick 30c1d6a Add hello worldpick 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 worldpick 367eb93 Update textpick 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.
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:
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 didn't like the article or you have an idea for improvement, please reach out to me on Twitter and drop me a DM with feedback so I can improve and provide better content in the future 💪.