How do I squash my last N commits together?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
To squash the last N commits into one, use git reset --soft to move the branch pointer back N commits while keeping all changes staged, then create a new commit:
This is the simplest and most reliable method. An interactive rebase (git rebase -i HEAD~N) also works and gives you more control over which commits to keep, squash, or reword, but the soft reset approach is faster when you just want everything collapsed into a single commit.
Method 1: Soft Reset (Simplest)
The soft reset moves the branch pointer backward while leaving the working tree and staging area untouched. All changes from the last N commits remain staged, ready for a new commit.
Example: Squash the Last 3 Commits
After this, the three separate commits are replaced by a single commit containing all their changes.
Why Soft Reset Works
The three reset modes clarify why --soft is the right choice:
| Flag | Moves HEAD | Resets Staging Area | Resets Working Tree |
--soft | Yes | No | No |
--mixed (default) | Yes | Yes | No |
--hard | Yes | Yes | Yes |
With --soft, the changes from the "removed" commits stay staged. With --mixed, they become unstaged modifications. With --hard, they are discarded entirely. For squashing, --soft is what you want because you intend to recommit those exact changes.
Method 2: Interactive Rebase (More Control)
Interactive rebase gives you fine-grained control over each commit: keep, squash, reword, edit, reorder, or drop.
This opens your editor with a list like:
To squash the second and third commits into the first, change pick to squash (or s):
Save and close the editor. Git opens a second editor window where you can write the combined commit message. All three original messages are shown as a starting point.
Rebase Commands Reference
| Command | Short | Effect |
pick | p | Keep the commit as-is |
squash | s | Merge into the previous commit, combine messages |
fixup | f | Merge into the previous commit, discard this message |
reword | r | Keep the commit but edit its message |
edit | e | Pause rebase to amend the commit |
drop | d | Remove the commit entirely |
Using fixup Instead of squash
If you do not want to edit the combined message and just want to keep the first commit's message:
This skips the message editing step entirely. The resulting commit has the message from the first (pick) commit.
Method 3: Merge with --squash (For Branches)
When squashing a feature branch into main, git merge --squash combines all branch commits into a single staged change:
This does not create a merge commit. It takes all the changes from the feature branch, stages them, and lets you commit them as a single commit on main. The feature branch's individual commits are not preserved in main's history.
Choosing the Right Method
| Scenario | Best Method |
| Collapse last N commits into one, no fuss | git reset --soft HEAD~N |
| Selectively squash some commits, keep others | git rebase -i HEAD~N |
| Squash a feature branch into main | git merge --squash feature-branch |
| Rewrite commit messages during squash | git rebase -i HEAD~N with reword |
| Drop specific commits while squashing others | git rebase -i HEAD~N with drop |
Pushing After a Squash
Squashing rewrites history. The squashed commit has a different hash than the original commits. If you have already pushed the original commits to a remote, you need a force push:
Use --force-with-lease instead of --force. It refuses to push if someone else has pushed new commits to the remote branch since your last fetch, preventing you from accidentally overwriting their work.
Never force push to shared branches like main or develop without coordinating with your team. Squash before pushing, or squash on merge via the pull request UI.
Squash Workflows in Pull Requests
Most Git hosting platforms (GitHub, GitLab, Bitbucket) offer a "Squash and merge" button that squashes all PR commits into one when merging. This is the safest approach for teams because:
- No one needs to rewrite history locally
- No force pushes are needed
- The PR retains its full commit history for review context
- The target branch gets a clean, single commit
On GitHub, configure this in repository settings under "Merge button" to allow or require squash merging.
Recovering From a Bad Squash
If you squash and immediately realize you made a mistake, git reflog shows the previous state:
The reflog keeps entries for about 90 days by default, so you have a safety net even after aggressive history rewriting.
Squashing Commits That Include Merge Commits
If the range of commits you want to squash includes merge commits, git rebase -i can produce conflicts or confusing results because rebase replays commits linearly.
For this situation, the soft reset approach is more reliable:
The soft reset does not care about the topology of the commits being collapsed. It just moves the pointer and leaves all accumulated changes staged.
Common Pitfalls
Using HEAD~N with the wrong value of N is the most common mistake. Count carefully, or use git log --oneline first to visually identify which commit you want as the new base. If N includes commits from before your feature branch started, you will inadvertently squash commits that belong to other work.
Force pushing to a shared branch after squashing overwrites other people's commits. Always use --force-with-lease, and only force push to branches you own (feature branches). For shared branches, squash on merge via the hosting platform UI.
Forgetting to write a meaningful combined commit message wastes the opportunity that squashing creates. The whole point of squashing "fix typo," "WIP," and "addressing review comments" is to replace them with a clear, descriptive message.
Running git rebase -i when there are uncommitted changes in the working tree causes the rebase to fail or produces unexpected results. Commit or stash your changes before starting an interactive rebase.
Confusing squash and fixup in interactive rebase leads to losing commit messages you wanted to keep. squash combines messages and lets you edit. fixup silently discards the message. Choose based on whether you need the commit's message in the final result.
Squashing commits that have already been cherry-picked to another branch creates duplicate changes. The squashed commit has a different hash, so Git does not recognize it as the same change, leading to conflicts when the branches merge.
Summary
- Use
git reset --soft HEAD~Nfollowed bygit commitfor the simplest squash. All changes stay staged. - Use
git rebase -i HEAD~Nwhen you need to selectively squash, reword, or drop individual commits. - Use
git merge --squashto squash a feature branch into another branch. - Always use
--force-with-leaseinstead of--forcewhen pushing rewritten history. - Prefer "Squash and merge" in pull request UIs to avoid force pushes entirely.
- Check
git reflogto recover from a bad squash.

