Peter Lubell-Doughtie -

Squashing Commits with an Interactive Git Rebase

history

There are plenty of reasons to get familiar with and start using git’s interactive rebase. You might want to edit a commit message, delete commits, reorder commits, or edit commits.

Here we will talk about using it to “squash” (as in combine, merge, or meld) multiple commits into a single commit.

In our specific use case, we have a bunch of single commits involving small changes to code formatting that we made to satisfy the formatting and static analysis checks our CI tool runs. There’s no good reason for these changes to each be recorded in a separate commit, they all represent part of the same improvement: to pass the formatting and static analysis checks. They would therefore be better represented (and easier to understand) if we record them in a single commit.

This is not something to do without a bit of reflection. When squashing (or fixing up) commits you will lose history, so you should be sure the history you are overwriting is not meaningful to the story of your code. For example, if one commit fixes the indentation of line 166 and another commit fixes the indentation of line 167, it is very hard to think of why these commits should be separate.

Given that git rebase --interactive is editing the history, you will need to know how much of the history you want to edit. That is, how many commits back from the HEAD do you need to go? It’s fine (but unnecessary) to go father back than you need, but if you don’t go far enough back you will not be able to modify the commit. In our case we need to go around 13 commits back from the HEAD, which translates to the command:

git rebase --interactive HEAD~13

This brings up a document in your terminal editor that you will modify to control what the rebase does. For us, the unmodified document looked like:

rebase original

The earliest commit we want to keep is 5c04e31, second from the top. This shows us we would have been fine going back 12 commits. The text at the bottom explains what you can do to the commits, as well as how to bail out and leave everything untouched (delete all the lines in the file, save, and quit). In our case, we want to squash or s a bunch of commits, so we change the file to this:

rebase squash

All of those ses on the left hand side mean (as it says in the comments below) “meld into previous commit”. Our edits are telling git to combine commits 2efcabb and those before it into commit 5c04e31, the most previous commit that has not been squashed.

To have these changes take effect we save and quit our text editor (in VIM we press esc + :x (or :wq). git now brings up another document so we can write our commit message:

rebase message

The orignal shows all of the messages from each commit that we are squashing. We edit the document so that it contains a single message representing all of the commits:

rebase message edit

We save and quit with another esc + :x, and we’re done! We can check our new history with:

git log --oneline

history

The history now shows our new commit (we’ve highlighted it red in the image). The final step is to make our change remote. If we’re confident we got everything correct we can push to the branch we edited. Because we rewrote the history, it must be a force push.

git push --force origin HEAD

And that’s it! Remote logs will now show the new history with our single commit in place of the others. If you are unsure that you got everything right, you can push your new history to a new branch and have someone check it over:

git checkout -b old-branch-with-new-history
git push origin HEAD

Now you know all the steps to using an interactive git rebase to squash multiple commits into a single commit. Remember that git rebase is a very powerful tool, and with great power comes the need to make backups and double check your work for correctness. For more details on git rebase and the other ways it can be used checkout the git docs, this Atlassian tutorial with lots of pretty pictures, and whatever else a web search brings up.