I don’t know about you, but in many projects I contributed to I found histories when a commit would aim to handle a topic, only to be followed by another one that fixed it, with a message like “Fixing the previous commit” or “Oops”. We saw earlier such things shouldn’t happen if collaborators used git commit --amend properly. But when they don’t, how can we squash our N latest commits into one? As you likely expect, the answer is again git reset.
Our diagram here shows again our c1, c2 and c3 commits. They all tackle the same topic really, their author just had a hard time hammering down the details and fixing bugs for it, it seems. So we want to turn them into a single commit, to make it easier to cherry-pick and reduce history noise. We thus bring HEAD back to c1 using the --soft mode, then create the commit. The --reedit-message (or its shorthand -c) lets us preload the c2 commit message in our editor to re-use or add onto.
I can already hear people telling me this can be done many other ways. So yes, it can be done another way, for instance with a git commit --amend or a rebase. It doesn’t really matter what approach you chose, although some are better suited depending on the particulars of your need: what matters is the end result. Now, as far as I'm concerned, using a git rebase for such a simple need seems overkill and a bit less performant, but there’s no accounting for taste, right?
Back to the terminal for a live demo.
(git lg) So here we have a history where the 3 latest commits are updating files in our project. These are “Update file-1”, “Update file-2” and “Update sub-directory files”. It turns out all these updates are part of a single task and need each other, and we really want to lump them together in a single commit.
So we’ll do a git reset --soft HEAD~3 to bring HEAD back to the “Add sub-directory” commit, forgetting about our three original commits (git lg), but still retaining all of their work in the stage, as we can verify with git status. We can now create a single commit with all this: git commit -m 'Update files'. There, we squashed three commits into one!
Another approach for a 2-commit squash would be to go just one step back in --soft mode still, then to amend commit c2 with the extra contents from c3 in the stage. This gives the same result, except we automatically kept most of the metadata from c2, including its author name, author date and message.