Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: November 6, 2023
We can often hear the word “squash” when we talk about Git workflows.
In this tutorial, we’ll briefly 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 do that.
When we say “squash” in Git, it means to combine multiple continuous commits into one.
Let’s take a look at the following four commits – A, B, C, and D – as an example:
┌───┐ ┌───┐ ┌───┐ ┌───┐
... │ A │◄─────┤ B │◄────┤ C │◄─────┤ D │
└───┘ └───┘ └───┘ └───┘
If we squash commits B, C, and D, then the commit log would look like this instead:
┌───┐ ┌───┐
... │ A │◄─────┤ E │
└───┘ └───┘
At this point, E contains all the changes from B, C, and D.
Here, we can see that commits B, C, and D represent individual commits on top of commit A. After squashing these commits, we consolidate their changes into a single commit, E, while preserving the history of commit A. Commit E now includes all the changes that were previously in B, C, and D.
Next, let’s discuss why we should squash commits.
Squashing commits helps keep our Git history clean and easy to understand. During development, we might make many small commits to fix bugs or tweak features. While these are important at the moment, they can clutter the history. Squashing helps group related changes into one meaningful commit, giving the project a more organized and readable timeline.
Additionally, squashing makes the review process smoother. It’s easier for teammates to review a single commit that represents a complete feature or fix rather than multiple smaller ones. This keeps the focus on the actual changes instead of individual, less significant updates.
Moreover, squashing makes it much easier to roll back changes. If we ever need to undo a feature or a fix, it’s much easier to revert a single squashed commit than to manage multiple smaller ones. This reduces the risk of inconsistencies when rolling back.
Finally, squashing commits simplifies merging. It reduces the chance of conflicts and makes the main branch’s history more concise. Let’s discuss when we should squash commits in the next section.
Squashing is particularly useful when we’ve accumulated a series of related commits while working on a feature. Often, these are just small adjustments or fixes. Before merging the feature branch into the main branch, it’s best to combine these into a single, cohesive commit. This way, the project’s history remains clean and easier to navigate.
Another ideal time to squash commits is before submitting a pull request. By consolidating multiple small updates into one, we present a more polished and straightforward commit for the review process.
Teams often squash commits during merges to simplify the process and reduce the likelihood of conflicts. This is particularly helpful when dealing with long-running branches or when the commit history becomes cluttered with minor changes. Squashing helps keep the project history clear and easy to work with.
It’s important to note that while squashing is a common Git operation, git squash is not a Git command. Instead, Git provides various commands and methods to achieve the squashing effect.
Let’s see the various ways to squash commits, covering the following approaches:
Next, let’s see these approaches in action.
Before we start, let’s create a Git alias slog (which 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’ll be using the Baeldung/ops-tutorials repository on GitHub as our sample repository. Let’s clone the repository:
$ git clone https://github.com/Baeldung/ops-tutorials.git
$ cd ops-tutorials
Next, let’s check out the gh-actions-demo branch:
$ git checkout gh-actions-demo
Now, let’s use the slog alias we created earlier to see the commit history of this branch:
$ git slog
We should see a similar output to this:
* bd3c2d8 2024-10-15 13:22:04 +0100 Implement Slack notifications (HEAD -> gh-actions-demo, origin/gh-actions-demo) (davydocsurg)
* 31908ef 2024-10-15 13:20:10 +0100 Add environment-specific deployments (davydocsurg)
* f87e360 2024-10-15 13:17:54 +0100 Implement artifact uploading (davydocsurg)
* 1171930 2024-10-15 13:16:59 +0100 Add test step (davydocsurg)
* 6983864 2024-10-15 13:15:43 +0100 Add build step (davydocsurg)
* c497509 2024-10-15 13:14:32 +0100 Implement caching for npm dependencies (davydocsurg)
* 1358e94 2024-10-15 13:12:48 +0100 Add Node.js setup step (davydocsurg)
* 850e5e2 2024-10-15 13:00:09 +0100 Initialize basic GitHub Actions workflow (davydocsurg)
In this example, we will 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, these are the last four commits:
* bd3c2d8 2024-10-15 13:22:04 +0100 Implement Slack notifications (HEAD -> gh-actions-demo, origin/gh-actions-demo) (davydocsurg)
* 31908ef 2024-10-15 13:20:10 +0100 Add environment-specific deployments (davydocsurg)
* f87e360 2024-10-15 13:17:54 +0100 Implement artifact uploading (davydocsurg)
* 1171930 2024-10-15 13:16:59 +0100 Add test step (davydocsurg)
Moreover, if we’ve squashed already pushed commits, and we would like to publish the squashed result, we have to do a force push.
It’s worth mentioning that force push to a public repository could be a dangerous operation, as it may overwrite others’ commits.
So, when a force push is needed, we should make sure only to force push the required branch. For example, we can set the push.default property to current so that only the current branch will be pushed/force-pushed to the remote repository.
Alternatively, we can force push to only one branch by adding a “+” in front of the refspec to push. For example, git push origin +feature will force a push to the feature branch.
To squash the last four commits using interactive rebase, let’s run:
git rebase -i HEAD~[X]
So, this is what 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:
pick 1171930 Add test step
pick f87e360 Implement artifact uploading
pick 31908ef Add environment-specific deployments
pick bd3c2d8 Implement Slack notifications
# Rebase 6983864..bd3c2d8 onto 6983864 (4 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 [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# 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
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# create a merge commit using the original merge commit's
# message (or the oneline, if no original merge commit was
# specified); use -c <commit> to reword the commit message
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
# to this position in the new commits. The <ref> is
# updated at the end of the rebase
#
# 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.
#
As we can see in the output 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:
pick 1171930 Add test step
squash f87e360 Implement artifact uploading
squash 31908ef Add environment-specific deployments
squash bd3c2d8 Implement Slack notifications
# Rebase 6983864..bd3c2d8 onto 6983864 (4 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
...
If we save the change and exit the editor, Git will do the rebase, following our instructions:
$ git rebase -i HEAD~4
[detached HEAD 6169064] Add test step
Date: Tue Oct 15 13:16:59 2024 +0100
1 file changed, 54 insertions(+), 1 deletion(-)
Successfully rebased and updated refs/heads/gh-actions-demo.
Now here’s what we’ll see if we check the Git commit log once again:
$ git slog
* 6169064 2024-10-15 13:16:59 +0100 Add test step (HEAD -> gh-actions-demo) (davydocsurg)
| * bd3c2d8 2024-10-15 13:22:04 +0100 Implement Slack notifications (origin/gh-actions-demo) (davydocsurg)
| * 31908ef 2024-10-15 13:20:10 +0100 Add environment-specific deployments (davydocsurg)
| * f87e360 2024-10-15 13:17:54 +0100 Implement artifact uploading (davydocsurg)
| * 1171930 2024-10-15 13:16:59 +0100 Add test step (davydocsurg)
|/
* 6983864 2024-10-15 13:15:43 +0100 Add build step (davydocsurg)
* c497509 2024-10-15 13:14:32 +0100 Implement caching for npm dependencies (davydocsurg)
* 1358e94 2024-10-15 13:12:48 +0100 Add Node.js setup step (davydocsurg)
* 850e5e2 2024-10-15 13:00:09 +0100 Initialize basic GitHub Actions workflow (davydocsurg)
As the slog output shows, we’ve squashed the last four commits into one new commit, 6169064.
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 -1commit 6169064bda9dc77edbc5a4e834dc76da42a96f0b (HEAD -> gh-actions-demo) Author: davydocsurg Date: Tue Oct 15 13:16:59 2024 +0100 Add test step Implement artifact uploading Add environment-specific deployments Implement Slack notifications
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 run the command git rebase -i hash_onto.
Let’s see how it works:
$ git slog
* 6169064 2024-10-15 13:16:59 +0100 Add test step (HEAD -> gh-actions-demo) (davydocsurg)
| * bd3c2d8 2024-10-15 13:22:04 +0100 Implement Slack notifications (origin/gh-actions-demo) (davydocsurg)
| * 31908ef 2024-10-15 13:20:10 +0100 Add environment-specific deployments (davydocsurg)
| * f87e360 2024-10-15 13:17:54 +0100 Implement artifact uploading (davydocsurg)
| * 1171930 2024-10-15 13:16:59 +0100 Add test step (davydocsurg)
|/
* 6983864 2024-10-15 13:15:43 +0100 Add build step (davydocsurg)
* c497509 2024-10-15 13:14:32 +0100 Implement caching for npm dependencies (davydocsurg)
* 1358e94 2024-10-15 13:12:48 +0100 Add Node.js setup step (davydocsurg)
* 850e5e2 2024-10-15 13:00:09 +0100 Initialize basic GitHub Actions workflow (davydocsurg)
Now let’s say we would like to squash all commits and rebase onto the commit c497509.
So, we don’t have to count how many commits we need to squash. Instead, we can just execute the command git rebase -i c497509.
We’ve learned that we need to change the pick commands into squash in the editor, and Git will do the squashing as we expected:
$ git rebase -i c497509
[detached HEAD 85f4f6b] Add build step
Date: Tue Oct 15 13:15:43 2024 +0100
1 file changed, 56 insertions(+), 1 deletion(-)
Successfully rebased and updated refs/heads/gh-actions-demo.
We’ve seen how to use Git interactive rebase to squash commits. This can effectively clean the commit graph in a branch.
However, we sometimes make many commits in our feature branch while working on it. After we’ve developed the feature, we usually want to merge the feature branch to the main branch.
We want to keep the main 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 squash all the commits on feature into a single main commit.
Let’s understand it through an example:
$ git slog
* 85f4f6b 2024-10-15 13:15:43 +0100 Add build step (HEAD -> gh-actions-demo) (davydocsurg)
| * bd3c2d8 2024-10-15 13:22:04 +0100 Implement Slack notifications (origin/gh-actions-demo) (davydocsurg)
| * 31908ef 2024-10-15 13:20:10 +0100 Add environment-specific deployments (davydocsurg)
| * f87e360 2024-10-15 13:17:54 +0100 Implement artifact uploading (davydocsurg)
| * 1171930 2024-10-15 13:16:59 +0100 Add test step (davydocsurg)
| * 6983864 2024-10-15 13:15:43 +0100 Add build step (davydocsurg)
|/
* c497509 2024-10-15 13:14:32 +0100 Implement caching for npm dependencies (davydocsurg)
* 1358e94 2024-10-15 13:12:48 +0100 Add Node.js setup step (davydocsurg)
* 850e5e2 2024-10-15 13:00:09 +0100 Initialize basic GitHub Actions workflow (davydocsurg)
As the output above shows, we’ve made several commits in the gh-actions-demo feature branch.
Now we want to merge the result back to the main branch with one single commit to keep the main branch clean:
$ git checkout main
Switched to branch 'main'
$ git merge --squash gh-actions-demo
Updating b980b0d..85f4f6b
Fast-forward
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 (the gh-actions-demo branch in this scenario) into local changes in the working copy:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
We can now commit the changes to complete the merge:
$ git commit -am 'Squashed and merged the gh-actions-demo branch'
[main fa938f6] Squashed and merged the gh-actions-demo branch
5 files changed, 88 insertions(+), 1 deletion(-)
Now let’s check the branch graph:
$ git slog
* fa938f6 2024-10-15 17:43:56 +0100 Squashed and merged the gh-actions-demo branch (HEAD -> main) (davydocsurg)
| * 85f4f6b 2024-10-15 13:15:43 +0100 Add build step (gh-actions-demo) (davydocsurg)
| | * bd3c2d8 2024-10-15 13:22:04 +0100 Implement Slack notifications (origin/gh-actions-demo) (davydocsurg)
| | * 31908ef 2024-10-15 13:20:10 +0100 Add environment-specific deployments (davydocsurg)
| | * f87e360 2024-10-15 13:17:54 +0100 Implement artifact uploading (davydocsurg)
| | * 1171930 2024-10-15 13:16:59 +0100 Add test step (davydocsurg)
| | * 6983864 2024-10-15 13:15:43 +0100 Add build step (davydocsurg)
| |/
| * c497509 2024-10-15 13:14:32 +0100 Implement caching for npm dependencies (davydocsurg)
| * 1358e94 2024-10-15 13:12:48 +0100 Add Node.js setup step (davydocsurg)
| * 850e5e2 2024-10-15 13:00:09 +0100 Initialize basic GitHub Actions workflow (davydocsurg)
We can see that we’ve merged all the changes in the gh-actions-demo branch into the main branch, and we have one single commit, fa938f6, in the main branch. Meanwhile, the individual commits are still present in the feature branch.
When working with platforms like GitHub, GitLab, or Bitbucket, we can squash commits when merging a pull request. This option automatically combines all commits from the feature branch into a single commit on the target branch.
Here’s how we can do this on GitHub:


This method simplifies the commit history when collaborating with others, without requiring any local Git commands.
We can also squash commits using the git reset command. This method involves resetting to an earlier commit and creating a new, single commit with all changes.
We first run the git reset command to move the HEAD back to the desired commit:
$ git reset --soft HEAD~N
Replace N with the number of commits we want to squash.
Next, we stage all the changes:
$ git add .
Then, we create a new squashed commit:
$ git commit -m "New squashed commit message"
Finally, we push the changes with force:
$ git push --force
This method is ideal for squashing commits locally.
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’ve focused on squashing with Git commands, enabling us to better understand and control the process.
In this article, we talked about what Git squashing is and when we should consider using it.
We also learned how to squash commits in Git.