While you're building a feature, you probably make lots of small
in-progress commits: "WIP", "actually try this", "fix that typo
from earlier", and so on. Useful during the work, embarrassing to
share. Before merging back to main, most people collapse those
throwaway commits into one clean commit that describes the whole
feature.
That collapse is called squashing, and there are two ways to do it. This lesson shows the simpler one.
The soft reset technique
The recipe:
git reset --soft HEAD~3
git commit -m "Implement feature"
Two commands, one clean commit. Here's what each does:
git reset --soft HEAD~3 moves the branch pointer back 3
commits, but keeps all the file changes staged. Nothing on disk
changes. The three commits' file changes are now all sitting in
the staging area, waiting for a new commit.
git commit -m "Implement feature" creates a single new
commit containing everything that was staged. The three WIP
commits are gone from your branch history; one clean commit takes
their place.
The number after HEAD~ is how many commits to reset back over.
If you had 5 WIP commits, you'd use HEAD~5. Count carefully:
reset back to the commit BEFORE your first WIP, not to the WIP
itself.
Why --soft
git reset has three modes, and the mode determines what happens
to your changes:
--softmoves HEAD, keeps everything staged. What we want here.--mixed(the default) moves HEAD, keeps changes but unstages them.--hardmoves HEAD and throws away every change.
--soft is the right choice for squashing because it preserves
the file content as staged changes ready for one big commit. If
you use --mixed you'd need to re-stage manually. If you use
--hard you'd lose the work.
The interactive rebase alternative
The other way to squash uses git rebase -i:
git rebase -i HEAD~3
Git opens an editor with your three commits listed:
pick c2 WIP 1
pick c3 WIP 2
pick c4 WIP 3
Change pick to squash (or s) on the second and third:
pick c2 WIP 1
squash c3 WIP 2
squash c4 WIP 3
Save, close the editor, and Git combines them, opening the editor again for the final message. More steps, more editor invocations, but you can reorder or drop commits at the same time.
For "just squash these three into one", --soft reset is
cleaner. For anything more complicated (reorder, edit an earlier
commit, drop one commit while keeping others), reach for
rebase -i.
Squash before you push
Both techniques rewrite history, so both are subject to the "don't rewrite shared commits" rule. In practice this is easy: squash your feature branch just before you open a pull request. Once the PR is up and others are looking at the branch, the commits are effectively public and you shouldn't reshape them.
Some hosting platforms (GitHub, GitLab) also offer a "squash and
merge" option that squashes the whole PR into one commit when it
lands on main. If your team uses that setting, you don't need
to squash locally at all: the platform does it for you. Ask what
your team's convention is; different projects handle this
differently.