Javaexercise.com

How To Squash Commits In Git?

Squashing is the process of merging or combining multiple commits in a single commit.

A benefit of squashing is clean and easy-to-understand version history. We can Squash several minor changes in a single commit. Squashing retains all the changes but reduces the number of commits present.

Squashing also helps developers to collaborate. If a developer is working on a feature branch, then he/she can squash all the commits of the feature branch into a single one before merging it with the master branch. It will make it easy for other developers to understand what is happening, and it will not crowd the history of the project.

Let's take an example to understand the process of squashing. Consider a repository with a master branch and five commits.

A -- B -- C -- D -- E <- master <- HEAD

We want to squash the last three commits into a single commit. After squashing the commits, the changes of commits C, D, and E will be present in commit S.

A -- B -- S <- master <- HEAD

Let's learn how to squash commits in Git.

Squash Using Git Reset

The Git Reset command is used for rewriting the history of our project. We can use this command to squash multiple commits into a single one.

We will use the --soft option with Git Reset. Use the HEAD~N notation to mention the number of commits you want to squash. For example, if we wish to squash the last three commits, then use HEAD~3.

git reset --soft HEAD~N
git commit -m "Squashed Commit Message"

The above command will first revert the last N commits. The --soft option ensures that the changes are not lost and are still present in the staging area. Finally, the Git Commit command creates a new squashed commit that contains all these changes.

Example

Consider a repository with a master branch and four commits(A, B, C, and D). We have created one new file in each commit.

A -- B -- C -- D <- master <- HEAD

Let's squash the last three commits(B, C, and D) into a single commit. Let's first go back to three commits. We will use the Git Reset command with the --soft option.

git reset --soft HEAD~3

The repository state is shown below.

A <- master <- HEAD

Note that the changes of the three commits are not lost. These changes are present in the staging area. We can verify this by running the Git Status command.

git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   b.txt
        new file:   c.txt
        new file:   d.txt

Finally, let's create a single squashed commit with these changes.

git commit -m "Squashed Commit for Commit B, C and D"
A -- S <- master <- HEAD

Squash Using Git Merge

The Git Merge command is used to merge branches. This command provides the option to squash commits before merging branches. We need to use the --squash option to do this. The git merge --squash command is mainly used when we don't want all the commits of a branch to appear in the history of another branch after a merge.

git merge --squash <target-branch>

The above command will combine all changes of the target branch in a single commit. It will add all the combined changes to the staging area of our current branch. We need to use the Git Commit command to add this squashed commit to our current branch.

git commit

Example

Consider a repository with two branches - master and feature.

A -- B -- C <- master <- HEAD
	 \
	  \
	   D -- E <- feature

If we perform a normal merge, then the complete history of the feature branch will be visible on the master branch. A new merge commit(Commit M) will be created.

A -- B -- C -- M <- master <- HEAD
	 \		  /
	  \		/
	   D -- E <- feature

We want to merge the feature branch into master. But, we don't want individual commits of the feature branch to appear on the master branch. We will combine(or squash) all the commits of the feature branch into a single commit. And then add a single squashed commit to the master branch.

Let's first squash the changes of the feature branch and add them to the staging area. Note that we are running the command from the master branch.

git merge --squash feature
Output

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

Finally, let's commit the squashed changes. We can provide a message for the squashed commit. Git also uses a default message. Let's use the default message.

git commit

Commit S is the new squashed commit. It contains the squashed changes for commits D and E.

A -- B -- C -- S <- master <- HEAD
	 \
	  \
	   D -- E <- feature

As we can see, the feature branch is not affected in any way. Only the changes are taken and included in a single commit, which is finally added to the master branch.

Squash Using Git Rebase

The Git Rebase command is used to modify the history of our project. We can use interactive Git Rebase to squash commits.

Let's say that we need to squash the last N commits into a single commit. We first need to run the Git Rebase --interactive(-i, in short) command and pass the reference. We can either use the HEAD~N notation to alter the last N commits. Or we can use the hash of the parent of the commit. Using the parent hash is preferred when we have multiple commits to squash and it is difficult to count them.

git rebase --interactive HEAD~N
git rebase --interactive <parent-commit-hash>

The above command will open a text editor with an entry for each included commit. Next, we need to replace pick with squash. Finally, we need to enter the message for the squashed commit.

Example

Consider a repository with five commits(A, B, C, D, and E). We want to merge the last three commits(C, D, and E) into a single one.

A -- B -- C -- D -- E <- master <- HEAD

Let's first run the Git Rebase command. We can either use HEAD~3 to include the last three commits. Or we can pass the hash of the parent of commit C, that is, the hash of commit B.

git rebase --interactive HEAD~N
git rebase --interactive <commit-B-hash>

The above command will open up our configured text editor with the following content.

pick c10937a C	# commit C
pick 0d4e9cf D	# commit D
pick 9ccebad E 	# commit E

# Rebase 4dcbaa7..9ccebad onto 4dcbaa7 (3 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
# 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.
#
# 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.

Let's replace pick with squash for commit D(hash) and commit E. This will squash commit D and E into the previous commit, commit C.

pick c10937a C		# commit C
squash 0d4e9cf D	# commit D
squash 9ccebad E 	# commit E

After we save and close this editor, a new editor will open up. We can enter the message for this squashed commit.

The final state of our repository is shown below. Commit S is the squashed commit and contains the changes for commit C, D, and E.

A -- B -- S <- master <- HEAD

Conclusion

Squashing is the process of combining multiple continuous commits in a single commit. It helps us to maintain a clean version history. However, one may argue that more commits help us to understand the workflow of a project. It all boils down to personal choice and preference and how the organization wants to maintain the workflow.

There is no dedicated command for squashing. We can use the Git Reset, the Git Rebase, or the Git Merge command. The Git Reset and Git Rebase commands are used to squash the commits of our current branch. The Git Merge --squash command is used to combine commits before a merge. Overall, the Git Rebase command is the recommended method.