Git Squash Commits: How to Combine Commits Into One
Table of Contents
Every dev team will have different standards and opinions on how to use Git. Some teams require all pull requests to contain a single commit, while others prefer to see a series of small, focused commits. If you join a team that prefers a single commit, you will need to know how to "squash" your commits together. To be fair, even if you don't need a single commit, squashing is useful to keep your commit history clean.
All the content from our Boot.dev courses are available for free here on the blog. This one is the "Squash" chapter of Learn Git 2. If you want to try the far more immersive version of the course, do check it out!
What Does It Mean to Squash Commits?
It's exactly what it sounds like. We take all the changes from a series of commits and squash them into a single commit.

Same changes, one commit.
How to Squash Your Last N Commits
Perhaps confusingly, squashing is done with the git rebase command! Here are the steps to squash the last n commits:
- Start an interactive rebase with the command
git rebase -i HEAD~n, wherenis the number of commits you want to squash. - Git will open your default editor with a list of commits. Change the word
picktosquashfor all but the first commit. - Save and close the editor.
- Git will open the editor again so you can write the commit message for the new combined commit.
For example, to squash your last 3 commits:
git rebase -i HEAD~3
Your editor will pop open with something like this:
pick a1b2c3d Add credit card scanner
pick d4e5f6a Add social security scanner
pick f7a8b9c Add phone number scanner
Change it to:
pick a1b2c3d Add credit card scanner
squash d4e5f6a Add social security scanner
squash f7a8b9c Add phone number scanner
Alternatively, you can use s for short:
pick a1b2c3d Add credit card scanner
s d4e5f6a Add social security scanner
s f7a8b9c Add phone number scanner
The -i flag stands for "interactive," and it allows us to edit the commit history before Git applies the changes. HEAD~n is how we reference the last n commits. HEAD points to the current commit (as long as we're in a clean state) and ~n means "n commits before HEAD."
When you're done, verify the result with git log: you should see one shiny new commit where the three used to be.
Why Does Rebase Do the Squashing?
Remember, rebase is all about replaying changes. When we rebase onto a specific commit (HEAD~n), we're telling Git to replay all the changes from the current branch on top of that commit. Then we use the interactive flag to "squash" those changes so that rebase will apply them all in a single commit.
And because rebase replays commits, the same rules apply: if two of your commits touched the same lines in incompatible ways, you might have to work through rebase conflicts along the way.
Can You Squash Commits After Pushing?
Yes, but now you're rewriting history that already exists on the remote, so Git won't let you push normally. Your local branch and the remote branch have diverged: the remote has commits that you just removed.
The git push command has a --force flag that allows us to overwrite the remote branch with our local branch. It's a very dangerous (but useful) flag that overrides the safeguards and just says "make that branch the same as this branch."
git push origin main --force
Because squashing is a destructive operation, it's worth doing the rewrite on a temporary branch first. That way main stays untouched until you've verified the squash worked:
git switch -c temp_main
git rebase -i HEAD~3
git log --oneline
git branch -D main
git branch -m main
git push origin main --force
Squash on temp_main, check the result with git log, delete the old main, rename temp_main to main with git branch -m, and force push. If anything looks wrong before the push, your original main is still sitting there unharmed.
A slightly safer alternative to --force is --force-with-lease, which refuses to overwrite the remote if someone else pushed new commits while you weren't looking.
Squashing Is Scary
When you squash commits, for example:
A - B - C - D
into a single commit:
ABCD
you're removing history from the project. Sure, all the changes are still there, but the individual markers of each change are gone. You can't go back to individual checkpoints anymore, so if commit B introduced a bug, you can no longer inspect or revert just B.
That's why the golden rule is the same as with any history rewrite: only squash commits on your own branches that no one else is using. If you do fat-finger a rebase, all is not lost: the reflog keeps a record of where your branches used to point.
How Do You Squash Commits in a Pull Request?
Squashing main is the weird thing. Squashing a feature branch for a pull request is the common thing. If your team prefers single-commit pull requests, this will likely be your workflow:
- Create a new branch off of
main. - Go about your work on the feature branch making commits as you go.
- When you're ready to get your code into
main, squash all your commits into a single commit. - Push your branch to the remote repository.
- Open a pull request from the feature branch into
main. - Merge the pull request once it's approved.
The squash in step 3 is the same git rebase -i HEAD~n dance from before. We are rewriting the history of our own branch to make it look as if we did all the work in a single commit. This is "normal" and "fine" because it's just our own branch.
Once the pull request is merged (which creates a merge commit), switch back to main, delete the feature branch (say it's called add_scanner), and pull the latest changes:
git switch main
git branch -d add_scanner
git pull origin main
Your local main now has your squashed commit, the merge commit, and all the changes.
Worth knowing: GitHub also has a built-in "Squash and merge" option on pull requests that squashes for you at merge time. Some teams rely on that instead of squashing locally, but you should still know how to do it by hand, the button can't help you once the commits are already tangled.
If you want to practice this whole workflow on a real repo (with a slightly panicked manager breathing down your neck), that's exactly what you'll do in Learn Git 2. And if interactive rebase still feels like wizardry, Learn Git covers the fundamentals first.
Frequently Asked Questions
What does it mean to squash commits in Git?
Squashing takes all the changes from a series of commits and combines them into a single commit, usually to keep history clean before merging.
How do I squash my last 3 commits?
Run git rebase -i HEAD~3, change pick to squash for all but the first commit, save and close the editor, then write the new combined commit message.
Can I squash commits after pushing?
Yes, but your local history will no longer match the remote, so you'll need git push --force. Only do this on branches no one else is using.
Is squashing commits bad?
Squashing your own feature branch is normal and fine. Squashing shared history is risky because it erases the individual commits other people may depend on.
What is the difference between squash and merge?
Merge combines two branches and preserves every commit. Squash collapses multiple commits into one, and squash-merging a pull request does both at once.
