Git Rebase - how to squash multiple commits into a single commit

Making frequent small commits during development is one of my habits. These commits by itself do not make sense from the feature building or bug fixing perspective.

Once I made a pull request to an open source project which had these smaller commits. The maintainer insisted to squash those commits into a single meaningful one to keep the commit history cleaner. After some googling, I was able to do it.

I’m going to note down how the squash is done using git interactive rebase for my future self and hopefully, others might find these helpful too.

Setup

To demonstrate, I’ve created a new repository in the Github named git-squash. I’m going to make three commits for adding .gitignore, README, and a text file. Then, I’m going to squash them all into a single commit. You may follow along or apply the squashing into your context. Let’s start!

Let’s create a new repo and add license file.

Create a folder in your preferred location named git-squash and go to that directory. Create a file named .gitignore and make an initial commit. Then add a README.md file and commit it. Do the same for test.txt and test2.txt file.

Connect the local repo with the remote.

git remote add origin [your remote repo]

To view the last 3 commits, type git log -3. The output will be like the following.

commit 9dd4ddd5e5dd275ffa1d49f258d7a1361ca79e2e (HEAD -> master)
Author: ksharifbd <ksharifbd@gmail.com>
Date:   Sun Jun 28 16:58:50 2020 +0600

    adds test2 file

commit 55c996fce2b113871052fc2c8d09f15fddcd8f88
Author: ksharifbd <ksharifbd@gmail.com>
Date:   Sun Jun 28 16:20:21 2020 +0600

    adds txt file

commit 3056bc596550c701d332cbf500d7a49e89213f9e
Author: ksharifbd <ksharifbd@gmail.com>
Date:   Sun Jun 28 16:19:37 2020 +0600

    adds readme

The most recent commits are at the top. We want to squash these 3 commits into a single commit since all these are the setup for the repo.

How to squash

We are going to use git interactive rebase. Type the following command in the CLI.

git rebase -i HEAD~3

We’re telling git to start an interactive rebase for the last 3 commits. Running the command will open a text editor with the following contents.

pick 3056bc5 adds readme
pick 55c996f adds txt file
pick 9dd4ddd adds test2 file

# Rebase 493e41b..9dd4ddd onto 493e41b (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

The contents consist of the last 3 commits and some comments that are instructions and info. Notice that there are several commands given in the comments. We are going to focus on pick and squash.

Also, observe the commit order. The latest one comes at the bottom. Here we’ve to pick a commit and squash others into that. We’ll pick the top commit (first commit we made) and squash the newer ones.

pick 3056bc5 adds readme
squash 55c996f adds txt file
squash 9dd4ddd adds test2 file

# Rebase 493e41b..9dd4ddd onto 493e41b (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Save that and you’ll get another prompt in the text editor to give a commit message.

# This is a combination of 3 commits.
# This is the 1st commit message:

adds readme

# This is the commit message #2:

adds txt file

# This is the commit message #3:

adds test2 file

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sun Jun 28 16:19:37 2020 +0600
#
# interactive rebase in progress; onto 493e41b
# Last commands done (3 commands done):
#    squash 55c996f adds txt file
#    squash 9dd4ddd adds test2 file
# No commands remaining.
# You are currently rebasing branch 'master' on '493e41b'.
#
# Changes to be committed:
#       new file:   README.md
#       new file:   test.txt
#       new file:   test2.txt
#

Here we’re going to write the commit message that best describes our intention and comment out others.

# This is a combination of 3 commits.
# This is the 1st commit message:

project setup

# This is the commit message #2:

# adds txt file

# This is the commit message #3:

# adds test2 file

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sun Jun 28 16:19:37 2020 +0600
#
# interactive rebase in progress; onto 493e41b
# Last commands done (3 commands done):
#    squash 55c996f adds txt file
#    squash 9dd4ddd adds test2 file
# No commands remaining.
# You are currently rebasing branch 'master' on '493e41b'.
#
# Changes to be committed:
#       new file:   README.md
#       new file:   test.txt
#       new file:   test2.txt
#

Save it. You’ll see the following outputs in the shell.

[detached HEAD f324efa] Project setup
 Date: Sun Jun 28 16:19:37 2020 +0600
 3 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README.md
 create mode 100644 test.txt
 create mode 100644 test2.txt
Successfully rebased and updated refs/heads/master.

We’re done! To verify again run the git log -3. But this time you’ll see only 2 commits are shown.

commit f324efa79b0b7b1194d22f4c797405f4f0ab1b41 (HEAD -> master)
Author: ksharifbd <ksharifbd@gmail.com>
Date:   Sun Jun 28 16:19:37 2020 +0600

    Project setup

commit 493e41ba764b5bfc521950b89cf61c1f8078237d
Author: ksharifbd <ksharifbd@gmail.com>
Date:   Sun Jun 28 16:18:51 2020 +0600

    adds git ignore

Now you can push the commits to the remote repo by running git push.

One issues that may arise is that git is not let you push to the remote because some of your squashed commits is already in the remote. In this case run git push --force.

Happy commit squashing :)

Share on

Get the latest post in your inbox!

Kamal Sharif
Hi, I'm Md. Kamal Sharif. I'm a senior frontend developer based in Dhaka, Bangladesh.

Follow Blog

Posts you might like

Testing React custom hook - how to mock useContext value with JestWhy I decided to become a software engineer at the age of 26Advance usage of Array.prototype.reduce() in JavaScript