How to Squash Commits in Git

Azhar Bashir Khan Feb 02, 2024
  1. Using Interactive git rebase Tool to Squash Git Commits
  2. Using git merge -squash to Squash Git Commits
How to Squash Commits in Git

We will learn Git squashing in this tutorial. The basic idea is to take multiple continuous commits and squash them into one.

The main intention is to condense many commits to a few relevant commits. Thus, doing this makes the git history look concise and clear.

Another way of looking at it is that we do multiple commits related to some task. After a while, when we reach a satisfactory state, the many commit messages clutter up the git history.

At this point, we may want to combine the different commits into one so that the git history looks clear and best reflects the task done.

Another use case is to do squashing while doing branch merging. Usually, we create a feature branch from the main branch for some feature development.

After feature completion, we merge the feature branch into the main branch. Here too, we may want to squash the various commit messages done in the feature branch into one when merging into the main branch.

Please note that there is no git squash command.

There are two ways to achieve Git squashing:

  • git rebase -i as an interactive tool used to squash commits
  • git merge -squash using the -squash option while merging

Using Interactive git rebase Tool to Squash Git Commits

Consider the following git log excerpt, which shows the last four commits from HEAD that we are interested in squashing.

25c38c4 remove .class files
da66e6a Delete version.ini
f4e3f09 Delete .log
b0e6655 Delete .lock
da66e6a github git notes

We can see in the log the first four commit messages signifying the operations of deleting different irrelevant files. Now, we will squash these four commits into one.

Following is the syntax of the command to squash the last X commits using the interactive rebase tool.

git rebase -i HEAD~[X]

Thus, to squash the four commits, we would do as below.

$ git rebase -i HEAD~4

After issuing this command, Git will invoke the default editor with details of commits to squash, as shown below.

pick b0e6655 Delete .lock
pick f4e3f09 Delete .log 
pick da66e6a Delete version.ini
pick 25c38c4 remove .class files

# Rebase 652d2fe..25c38c4 onto 652d2fe (4 command(s))
#
# 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 editor shows the various commits with the pick command. It also shows information about the available commands. We will be using the squash (or s) command.

As shown below, we will keep the first commit with the pick command and change from pick to s (for squash) command for the remaining three commits.

pick b0e6655 Delete .lock
s f4e3f09 Delete .log 
s da66e6a Delete version.ini
s 25c38c4 remove .class files

# Rebase 652d2fe..25c38c4 onto 652d2fe (4 command(s))
#
...

The commits marked with squash ( or s) will be merged to the main commit viz. the one marked with pick.

Now, we will save the changes in the editor and exit. After this, the rebase -i tool will open another editor to enter the commit message, as below:

# This is a combination of 4 commits. The first commit's message is:

Delete .lock

# This is the 2nd commit message:

Delete .log 

# This is the 3rd commit message:

Delete version.ini

# This is the 4th commit message:

remove .class files

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sun Jan 3 16:39:23 2021 +0530
#
# interactive rebase in progress; onto 652d2fe
# Last commands done (4 commands done):
#    pick b0e6655 Delete .lock
#    s f4e3f09 Delete .log 
#    s da66e6a Delete version.ini
#    s 25c38c4 remove .class files
# No commands remaining.
# You are currently editing a commit while rebasing branch 'master' on '652d2fe'.
#
# Changes to be committed:
#       new file:   github-git-notes.txt
#

Now, we will add the new commit message at the top of the first commit message.

Deleted irrelevant files

# This is a combination of 4 commits. The first commit's message is:

Delete .lock

# This is the 2nd commit message:

Delete .log
...

After saving and exiting the editor, the rebase -i tool will print the following message.

HEAD~2
Rebasing (2/2)


[detached HEAD caab6e8] Deleted irrelevant files
 Date: Sun Jan 3 16:39:23 2021 +0530
 1 file changed, 54 insertions(+)
 create mode 100644 github-git-notes.txt
Successfully rebased and updated refs/heads/master.

Now, we will check the git log and see the squashed commit (i.e.) single commit message instead of the four commit messages.

$ git log --oneline
25c38c4 Deleted irrelevant files
da66e6a github git notes
...

Using git merge -squash to Squash Git Commits

Following is the command’s syntax to merge a branch with the current branch (usually main) and squash the commits of the source branch.

git merge --squash <source_branch_name_to_squash>

We will now merge the feature branch viz. feature1 with squashing with the main branch.

First, we will checkout to the main branch.

$ git checkout main
Switched to branch 'main'

Then, we will do a git merge with the squash option as follows.

$ git merge --squash feature1
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested

When we do a merge with --squash option, Git will not create a merge commit in the destination branch, as it does in a normal merge. Instead, Git takes all the changes in the source branch viz. feature1 and puts it as local changes in the working copy of the destination branch viz. main.

Please see below.

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   config.ini

Here, the file config.ini has the changes done in the feature1 branch.

Now, all that remains is to commit the changes to the main branch as below.

$ git commit -am 'Merged and squashed the feature1 branch changes'
[main 573b923] Squashed and merged the feature1 branch
 1 file changed, 4 insertions(+)

Thus, we have now merged the changes in the feature1 branch into the main branch, along with squashing the commit messages of the feature1 branch. We now only have a single commit message in the main branch.

Related Article - Git Rebase

Related Article - Git Merge