Clean up historyAlways fixable

Squash several commits into one

How do I combine multiple commits into a single commit?

Short answer

git rebase -i HEAD~n opens an editor where you mark commits as squash or fixup to fold them into the one above. For a quick all-in-one collapse, git reset --soft HEAD~n followed by a fresh commit achieves the same result with no editor at all.

quick fix
git reset --soft HEAD~3
git commit -m "One clean commit"

Collapses the last 3 commits into a single new one

Does this match your situation?

  • Your branch has a trail of 'wip', 'fix typo' and 'try again' commits.
  • A reviewer asked you to clean up the history before merging.
  • You want one tidy commit per logical change.

Step-by-step fix

  1. The quick way: soft reset and recommit

    Moving the branch back n commits with --soft leaves every change staged. Committing once turns them into a single commit. This is the fastest approach when you want all of them collapsed into one.

    step 1
    git reset --soft HEAD~3
    git commit -m "Add inventory system with save support"
    
  2. The precise way: interactive rebase

    Use this when you want to keep some commits separate. Git lists them oldest-first; change pick to squash to merge a commit into the one above it while combining messages, or fixup to merge it and discard its message.

    step 2
    git rebase -i HEAD~5
    
    # In the editor:
    #   pick   a1b2c3d Add inventory system
    #   squash f4e5d6c wip
    #   squash 9a8b7c6 fix typo
    #   pick   1122334 Add save support
    

    The first line must stay pick — there's nothing above it to squash into.

  3. Write one good message

    With squash, Git opens an editor containing all the original messages so you can compose a single coherent one. Delete the noise and describe the change as a whole.

    step 3
    Add inventory system with save support
    
    - Grid-based inventory with stacking
    - Persists to the save file on scene change
    - Covered by unit tests
    
  4. Push the rewritten branch

    Squashing replaces commits with new hashes, so an ordinary push is rejected. Force with lease so you can't accidentally clobber someone else's push.

    step 4
    git push --force-with-lease
    

    Only on your own branch. If others are working on it, use the platform's Squash and merge button instead.

Why this works

Squashing works because Git doesn't care how many commits produced a given tree — it only records snapshots and parents. A soft reset moves the branch pointer back while leaving the index at your latest state, so the difference between the new base and the index is exactly the combined change of all the commits you skipped. Committing that difference produces one commit with the same final content. Interactive rebase does the same thing per-group, replaying commits and merging the ones you marked.

How to stop it happening again

  • Commit in logical units as you go, rather than by the clock.
  • Use git commit --fixup <hash> plus git rebase -i --autosquash to mark fixups automatically.
  • Or just enable Squash and merge on your pull requests and stop worrying about local history.

Commands used in this guide

git rebase -i

Reorder, squash, split, reword or drop your recent commits.

git reset

Move the current branch pointer, optionally rewriting the index and your files.

git commit

Record everything currently staged as a permanent snapshot.

git push

Upload your commits to the remote repository.

Still stuck?

Search the full command reference and every other rescue guide — there are 56 of them, covering everything from detached HEAD to force-push disasters.

Browse all guides →

Frequently asked questions

How do I squash the last 3 commits into one?

git reset --soft HEAD~3 followed by git commit -m "message" is the fastest way — the soft reset leaves all changes staged and the new commit records them as one. Alternatively use git rebase -i HEAD~3 and mark the lower two as squash.

What is the difference between squash and fixup?

Both merge a commit into the one above it. squash opens an editor so you can combine the commit messages, while fixup silently discards the squashed commit's message — ideal for 'fix typo' commits that add nothing to the story.

Can I squash commits that are already pushed?

Yes, but it rewrites history, so you'll need git push --force-with-lease and anyone else on the branch must reset to the new history. On a shared branch, prefer your platform's Squash and merge option, which does it server-side at merge time.

Related rescue guides

← All Git Rescue guides