Squashing All Commits in a Git Topic Branch

git guide

When all work in a topic branch is complete and we are ready to bring the changes back to the master branch (or open a pull request), it may be a good idea to clean up the commit history first. This is especially true if your commits are full of messages like “WIP”, “fixing”, “progress”, etc. Even more frequently, the individual topic commits are nothing but noise and you simply want to squash all of them.

Let’s assume that this is our commit log:

commit c9e1e685ff81b1a6ec75848423219caa752838c4 (HEAD -> topic)
Author: Alex Yatskov <alex@foosoft.net>
Date:   Sun Jul 17 10:37:05 2022 -0700


commit 4ad391d17d92fe499247ac848556a3fd72842621
Author: Alex Yatskov <alex@foosoft.net>
Date:   Sun Jul 17 10:36:49 2022 -0700


commit bef920b9789d988507b1972de697273ea2df3b24
Author: Alex Yatskov <alex@foosoft.net>
Date:   Sun Jul 17 10:36:39 2022 -0700


commit 9e2d24395008af39c6192df128bcbce0deaa2732 (master)
Author: Alex Yatskov <alex@foosoft.net>
Date:   Sun Jul 17 10:36:09 2022 -0700

    Mainline feature

Typically, to squash the topic commits, you might execute the following:

git rebase -i 9e2d24395008af39c6192df128bcbce0deaa2732

This command will open a text editor, prompting you to specify which commits to pick and which to squash:

pick bef920b WIP
pick 4ad391d Fixing
pick c9e1e68 Progress

# Rebase 9e2d243..c9e1e68 onto 9e2d243 (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.

You would then pick the first commit and squash the reset:

pick bef920b WIP
squash 4ad391d Fixing
squash c9e1e68 Progress

There is nothing wrong with this approach, but it get cumbersome if you have dozens of commits. A quicker way would be to reset the branch to the start of the topic changes, and re-commit as follows:

git reset --soft 9e2d24395008af39c6192df128bcbce0deaa2732
git commit -m "Everything required for my new feature"

This operation has the advantage of being non-interactive, and produces the same result (at the cost of flexibility in specifying what to squash). If you are like me and prefer to squash all topic commits anyway, this may be an improvement to your git workflow.