Generic Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

We may often hear the word “squash” when we talk about Git workflows.

In this tutorial, we'll shortly introduce what Git squashing is. Then, we'll talk about when we need to squash commits.

Finally, we'll take a closer look at how to squash commits.

2.What's Git Squashing?

When we say “squash” in Git, it means to combine multiple continuous commits into one. An example can explain it quickly:


          ┌───┐      ┌───┐     ┌───┐      ┌───┐
    ...   │ A │◄─────┤ B │◄────┤ C │◄─────┤ D │
          └───┘      └───┘     └───┘      └───┘

 After Squashing commits B, C, and D:

          ┌───┐      ┌───┐
    ...   │ A │◄─────┤ E │
          └───┘      └───┘

          ( The commit E includes the changes in B, C, and D.)

In this example, we've squashed the commits B, C, and D into E.

After understanding what the Git squashing operation is, we may want to know when we should squash commits. So next, let's talk about it.

3. When to Squash Commits?

Simply put, we use squashing to keep the branch graph clean.

Let's imagine how we implement a new feature. Usually, we'll commit multiple times before we reach a satisfactory result, such as some fixes and tests.

However, when we've implemented the feature, those intermediate commits look redundant. So, in this case, we may want to squash our commits into one.

Another common scenario in which we want to squash commits is when we merge branches.

Very likely, when we start working on a new feature, we'll start a feature branch. Let's say we've done our work with 20 commits in our feature branch.

So, when we merge the feature branch to the master branch, we want to do a squashing to combine the 20 commits into one. In this way, we keep the master branch clean.

4. How to Squash Commits?

Today, some modern IDEs, such as IntelliJ and Eclipse, have integrated support for common Git operations. This allows us to squash commits from a GUI.

For example, in IntelliJ, we can select the commits we want to squash and choose “Squash Commits” in the right-click context menu:

However, in this tutorial, we'll focus on squashing with Git commands.

We should note that squash is not a Git command, even if it's a common Git operation. That is, “git squash … ” is an invalid Git command.

We'll address two different approaches to squashing commits:

Next, let's see them in action.

4. Squashing by Interactive Rebase

Before we start, let's created a Git alias slog (stands for short log) to show Git commit logs in a compact view:

git config --global alias.slog = log --graph --all --topo-order --pretty='format:%h %ai %s%d (%an)'

We've prepared a Git repository as an example:

$ git slog
* ac7dd5f 2021-08-23 23:29:15 +0200 Commit D (HEAD -> master) (Kai Yuan)
* 5de0b6f 2021-08-23 23:29:08 +0200 Commit C (Kai Yuan)
* 54a204d 2021-08-23 23:29:02 +0200 Commit B (Kai Yuan)
* c407062 2021-08-23 23:28:56 +0200 Commit A (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

Git's interactive rebase will list all relevant commits in the default editor. In this case, those are the commits we want to squash.

Then, we can control each commit and commit message as we want and save the change in the editor.

Next, let's squash the last four commits.

It's worth mentioning that when we say “the last X commits”, we're talking about the last X commits from the HEAD. So, in this case, the last four commits are:

* ac7dd5f ... Commit D (HEAD -> master)
* 5de0b6f ... Commit C 
* 54a204d ... Commit B 
* c407062 ... Commit A

Moreover, if we've squashed already pushed commits, and we would like to publish the squashed result, we have to do a force push (git push -f).

4.1. Squash the Last X Commits

The syntax to squash the last X commits using interactive rebase is:

git rebase -i HEAD~[X]

So, in this example, we should run:

git rebase -i HEAD~4

After we execute the command, Git will start the system default editor (the Vim editor in this example) with the commits we want to squash and the interactive rebase help information:

As we can see in the screenshot above, all four commits we want to squash are listed in the editor with the “pick” command.

There's a detailed guideline on how to control each commit and commit message in the commented lines that follow.

For example, we can change the “pick” command of commits into “s” or “squash” to squash them:

If we save the change and exit the editor, Git will do the rebase following our instructions:

$ git rebase -i HEAD~4
[detached HEAD f9a9cd5] Commit A
 Date: Mon Aug 23 23:28:56 2021 +0200
 1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

Now, if we check the Git commit log once again:

$ git slog
* f9a9cd5 2021-08-23 23:28:56 +0200 Commit A (HEAD -> master) (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

As the slog output shows, we've squashed the last four commits into one new commit, “f9a9cd5​“.

Now, if we have a look at the complete log of the commit, we can see that the messages of all squashed commits are combined:

$ git log -1
commit f9a9cd50a0d11b6312ba4e6308698bea46e10cf1 (HEAD -> master)
Author: Kai Yuan
Date:   2021-08-23 23:28:56 +0200

    Commit A
    
    Commit B
    
    Commit C
    
    Commit D

4.2. When X is Relatively Large

We've learned that the command git rebase -i HEAD~X is pretty straightforward to squash the last X commits.

However, counting a larger X number can be a pain when we have quite a lot of commits in our branch. Moreover, it's error-prone.

When the X is not easy to count, we can find the commit hash we want to rebase “onto” and execute the command git rebase -i hash_onto.

Let's see how it works by an example:

$ git slog
e7cb693 2021-08-24 15:00:56 +0200 Commit F (HEAD -> master) (Kai Yuan)
2c1aa63 2021-08-24 15:00:45 +0200 Commit E (Kai Yuan)
ac7dd5f 2021-08-23 23:29:15 +0200 Commit D (Kai Yuan)
5de0b6f 2021-08-23 23:29:08 +0200 Commit C (Kai Yuan)
54a204d 2021-08-23 23:29:02 +0200 Commit B (Kai Yuan)
c407062 2021-08-23 23:28:56 +0200 Commit A (Kai Yuan)
29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

As the git slog shows, in this branch, we have some commits.

Now, let's say we would like to squash all commits and rebase onto the commit 29976c5 with the message: “BugFix #1“.

So, we don't have to count how many commits we need to squash. Instead, we can just execute the command git rebase -i 29976c5.

We've learned that we need to change the “pick” commands into “squash” in the editor, and then Git will do the squashing as we expected:

$ git rebase -i 29976c5
[detached HEAD aabf37e] Commit A
 Date: Mon Aug 23 23:28:56 2021 +0200
 1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

$ git slog
* aabf37e 2021-08-23 23:28:56 +0200 Commit A (HEAD -> master) (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

5. Squashing by Merging With the –squash Option

We've seen how to use Git interactive rebase to squash commits. This can effectively clean the commit-graph in a branch.

However, sometimes, we've made many commits in our feature branch when we were working on it. After we've developed the feature, usually, we would like to merge the feature branch to the main branch, say “master”.

We want to keep the master branch graph clean, for example, one feature, one commit. But we don't care about how many commits are in our feature branch.

In this case, we can use the commit git merge –squash command to achieve that. As usual, let's understand it through an example:

$ git slog
* 0ff435a 2021-08-24 15:28:07 +0200 finally, it works. phew! (HEAD -> feature) (Kai Yuan)
* cb5fc72 2021-08-24 15:27:47 +0200 fix a typo (Kai Yuan)
* 251f01c 2021-08-24 15:27:38 +0200 fix a bug (Kai Yuan)
* e8e53d7 2021-08-24 15:27:13 +0200 implement Feature2 (Kai Yuan)
| * 204b03f 2021-08-24 15:30:29 +0200 Urgent HotFix2 (master) (Kai Yuan)
| * 8a58dd4 2021-08-24 15:30:15 +0200 Urgent HotFix1 (Kai Yuan)
|/  
* 172d2ed 2021-08-23 23:28:56 +0200 BugFix #2 (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

As the output above shows, in this Git repository, we've implemented “Feature2” in the feature branch.

In our feature branch, we've made four commits.

Now, we would like to merge the result back to the master branch with one single commit to keep the master branch clean:

$ git checkout master
Switched to branch 'master'

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

Unlike a regular merge, when we execute the command git merge with the –squash option, Git won't automatically create a merge commit. Instead, it turns all changes from the source branch, which is the feature branch in this scenario, into local changes in the working copy:

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   readme.md

In this example, all changes of the “Feature2” are about the readme.md file.

We need to commit the changes to complete the merge:

$ git commit -am'Squashed and merged the Feature2 branch'
[master 565b254] Squashed and merged the Feature2 branch
 1 file changed, 4 insertions(+)

Now, if we check the branch graph:

$ git slog
* 565b254 2021-08-24 15:53:05 +0200 Squashed and merged the Feature2 branch (HEAD -> master) (Kai Yuan)
* 204b03f 2021-08-24 15:30:29 +0200 Urgent HotFix2 (Kai Yuan)
* 8a58dd4 2021-08-24 15:30:15 +0200 Urgent HotFix1 (Kai Yuan)
| * 0ff435a 2021-08-24 15:28:07 +0200 finally, it works. phew! (feature) (Kai Yuan)
| * cb5fc72 2021-08-24 15:27:47 +0200 fix a typo (Kai Yuan)
| * 251f01c 2021-08-24 15:27:38 +0200 fix a bug (Kai Yuan)
| * e8e53d7 2021-08-24 15:27:13 +0200 implement Feature2 (Kai Yuan)
|/  
* 172d2ed 2021-08-23 23:28:56 +0200 BugFix #2 (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

We can see that we've merged all the changes in the feature branch into the master branch, and we have one single commit, 565b254, in the master branch.

On the other hand, in the feature branch, we still have four commits.

6. Conclusion

In this tutorial, we've talked about what Git squashing is and when we should consider using it.

Also, we've learned how to squash commits in Git.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
guest
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments