Squashing All Commits in a Git Topic Branch
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
Progress
commit 4ad391d17d92fe499247ac848556a3fd72842621
Author: Alex Yatskov <alex@foosoft.net>
Date: Sun Jul 17 10:36:49 2022 -0700
Fixing
commit bef920b9789d988507b1972de697273ea2df3b24
Author: Alex Yatskov <alex@foosoft.net>
Date: Sun Jul 17 10:36:39 2022 -0700
WIP
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.