Git & GitHub
December 20, 2021

In Git, how to squash commits?

First, let's find out how many commits need to be squashed into one. This command will show you what commits you have added compared to the master branch:

git cherry -v master

And this one will show you how much of them:

git cherry -v master | wc -l

For example,

> $ git cherry -v master
+ edb880bb972736eebdc86c55a158e659c787b769 check url
+ 16817774ffc4ece00d9a7c75cf26f9a93cd90eda improved readability
+ 8db9d67018394a5a1e1215943ddfe002a25ccc08 tests

> $ git cherry -v master | wc -l
       3

Great! I have 3 commits ahead master. Now I want to rewrite history since HEAD~3, i.e. from what was 3 commits ago. To do this, I do:

git rebase -i HEAD~3

The -i flag means interactively. I have a file like this:

hint: Waiting for your editor to close the file... 
pick edb880bb check url
pick 16817774 improved readability
pick 8db9d670 tests

# Rebase 10def359..8db9d670 onto 10def359 (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 [-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
#
# 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.
#

Here's a list of my commits and a big comment on what I can do. From the list of commands, you can see that we can use squash or fixup in order to squash commits. The first is useful when you want to change the commit message, and the second when you want to use the commit message of the first. So, to squash all the commits, I do like this:

hint: Waiting for your editor to close the file... 
pick edb880bb check url
squash 16817774 improved readability
squash 8db9d670 tests

# Rebase 10def359..8db9d670 onto 10def359 (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 [-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
#
# 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.
#

Then git glues the commits and prompts me to enter the commit message (shows the commit messages of all the merged commits). You can leave it as is or change to whatever you want.

Finally, save the file and quit the editor. You can now type git log and see your single commit that contains 3 commits we were squashing.

Don't forget to force-push like you do after any rebase:

git push -f

That's it. Enjoy!