There are plenty of reasons to get familiar with and start using
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.
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
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:
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
s a bunch of
commits, so we change the file to this:
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
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
git now brings up another document so we can
write our commit 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:
We save and quit with another
:x, and we’re done! We can check our
new history with:
git log --oneline
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
this Atlassian tutorial
with lots of pretty pictures, and whatever else a web search brings up.