Squash Commits with Rebase
Use interactive rebase to squash commits and clean up your Git history. Learn step-by-step with hands-on practice, troubleshooting tips, and what to study next.
Focus: use interactive rebase to squash commits
Your commit history reads like a messy notebook: 14 commits for what should be one feature, half of them titled "fix typo" and "oops". Before you merge that branch, you want a clean, reviewable history that tells a story — not a blow-by-blow of every mistake. Interactive rebase is your cleanup tool, letting you squash multiple commits into one, so your main branch stays readable and your future self (and teammates) can actually understand what changed and why.
The problem this lesson solves
Messy commit history is a real problem in collaborative development. When you open a pull request with 20 commits that say "wip", "fix", and "try again", reviewers can’t see the forest for the trees. Worse, when someone later uses git bisect to find a regression, a pile of half-broken commits makes the hunt painful. You need a way to combine multiple commits into one logical unit — and that’s exactly what git rebase --interactive gives you.
Interactive rebase lets you rewrite history on a branch before you share it. Whether you’re squashing a dozen small commits into one feature commit or just cleaning up a local mess, this command is the professional’s choice. It’s powerful, but also dangerous if misused — so this lesson will teach you the safe, focused workflow.
Core concept / mental model
Think of your Git history as a list of recorded steps on a hiking trail. Each commit is a marker saying "I was here, and here’s what I did." Some markers are useful — "reached the summit", "crossed the river". Others are just noise — "tied my shoelace", "turned around to check the view". Interactive rebase lets you delete or merge those noisy markers before putting up a final map for your team.
More technically, each commit is a snapshot with a parent pointer. Squashing takes several commits and collapses them into a single new commit. Git replays your branch’s commits onto a new base, merging the changes and producing a cleaner linear history.
Pro tip: Only squash commits that you haven’t pushed to a shared branch yet. Once history is public, rewriting it can cause chaos for anyone who has pulled your branch.
How it works step by step
Interactive rebase works by opening an editor where you re-enact your history, commit by commit, with instructions on what to do with each one. Here’s the conceptual flow:
- Identify the range: You choose a commit to base the rebase on — usually the commit just before your feature branch diverged (e.g.,
main). All commits after that point are up for rewriting. - Open the interactive editor:
git rebase -i <base>opens a text editor listing the commits, oldest first, with a command word in front of each (likepick,squash,reword). - Choose your actions: For squashing, you mark all but the first commit with
squash(ors). Git will then combine each squash commit into the one above it. - Write a new commit message: For a group of squashed commits, Git asks for a combined commit message. You can keep the existing ones or write a fresh, descriptive message.
- Resolve conflicts if any: If changes overlap between commits, Git pauses and asks you to fix conflicts manually.
- Complete the rebase: After conflicts are resolved (or if none), Git finishes rewriting your history. Your branch now has fewer, cleaner commits.
This process is non-destructive in the sense that your working tree is safe — but the old commits are still recoverable via git reflog if you make a mistake.
Hands-on walkthrough
Let’s see it in action. First, create a sample repository with several commits that you’ll squash:
mkdir squash-demo && cd squash-demo
git init
echo "line 1" > app.txt
git add app.txt && git commit -m "Initial commit"
echo "line 2" >> app.txt && git commit -am "Add feature A"
echo "line 3" >> app.txt && git commit -am "fix typo"
echo "line 4" >> app.txt && git commit -am "add tests"
Now your history looks like this:
$ git log --oneline
4b2f1e1 add tests
c9d3a2f fix typo
7a8b9c0 Add feature A
1a2b3c4 Initial commit
You want to squash the last three commits into one clean feature commit. Run:
git rebase -i 1a2b3c4
Your editor opens with the following (order is oldest first):
pick 7a8b9c0 Add feature A
pick c9d3a2f fix typo
pick 4b2f1e1 add tests
Change the words to:
pick 7a8b9c0 Add feature A
squash c9d3a2f fix typo
squash 4b2f1e1 add tests
Save and exit. Git will open a commit-message editor combining the original messages. Replace them with something like:
Add feature A with tests
Save and quit. Check the log again:
$ git log --oneline
f3a9c1e Add feature A with tests
1a2b3c4 Initial commit
Your history is now clean: one feature commit on top of the initial commit. You can safely push this branch with git push --force-with-lease if you had already pushed the messy version.
Another common use case: squashing all commits on a branch into a single one before merging to main. Use git rebase -i with the base commit of your branch (often the merge-base with main).
Compare options / when to choose what
Interactive rebase is not the only way to squashing. Here’s a quick comparison:
| Method | Command | Use case | Risk level |
|---|---|---|---|
| Interactive rebase | git rebase -i <base> |
Squash/rewrite a range of commits with full control | Medium — requires careful conflict resolution |
git merge --squash |
git merge --squash <branch> |
Collapse an entire branch into a single commit on the current branch | Low — no history rewrite, but discards individual commit messages |
git reset --soft + git commit |
git reset --soft <base> && git commit |
Combine all commits after a point into one with a new message | Low — simple, but you lose the original commits and their separation |
When to choose what:
- Use interactive rebase when you want to keep some commits as they are and only squash a few, or when you need to reorder/reword commits.
- Use
git merge --squashwhen you want to merge a feature branch intomainas a single unit and don’t care about preserving the branch’s internal history. - Use
git reset --softfor a quick local cleanup when you just want to bundle everything since<base>into one commit.
Troubleshooting & edge cases
Conflict during rebase: If commits touch the same lines, Git stops and tells you which files conflict. Resolve the conflicts as you would in a merge, then git add <files> and git rebase --continue. To abort, run git rebase --abort — this returns you to the pre-rebase state.
Accidentally squashed too many commits: Don’t panic. Use git reflog to find the commit that was your pre-rebase HEAD, then git reset --hard <that-commit> to restore. The old commits are still there until garbage collection.
Rebase onto wrong base: Double-check your base commit hash before running. If you include a commit that belongs to someone else on a shared branch, you could rewrite history others rely on. Use git merge-base to find the true branching point.
Editor opens with wrong file: If Git opens a strange editor or you’re stuck, remember you can set GIT_EDITOR to a known one: GIT_EDITOR=nano git rebase -i .... Always save and exit to proceed.
Squashing a pushed branch without force push: The remote will reject your push because history has changed. Use git push --force-with-lease (safer than --force) — but only if no one else is working on that branch.
What you learned & what's next
You now understand how to use interactive rebase to squash commits, when to apply it, and how to recover from mistakes. You practiced combining multiple commits into one logical unit, compared it with merge --squash and reset --soft, and learned to troubleshoot conflicts and aborts. Most importantly, you know why clean history matters for large-scale collaboration and debugging.
Next in the Git Tutorial track, we’ll explore splitting a commit — the reverse of squashing — where you break a big, messy commit into smaller, focused ones. That skill rounds out your history-rewriting toolkit, letting you both compress and expand commits as your workflow demands.
Practice recap
Create a new branch with three small commits, then use git rebase -i to squash them into one. After that, run git reflog to see the original commits, and practice resetting back to them to reinforce recovery. Finally, experiment with git merge --squash on a test branch to compare the results.
Common mistakes
- Rebasing commits that have already been pushed to a shared branch without force-pushing with
--force-with-lease— this can corrupt your teammates' branches. - Squashing all commits on a long-running feature branch into one, losing the ability to see incremental development steps during a code review.
- Using
git rebase -iwithout knowing the exact base commit, accidentally including commits from the main branch in the rewrite. - Panicking after seeing 'CONFLICT' and running
git rebase --abortinstead of resolving the conflict calmly — you lose all your rebase progress and may leave the branch in a confusing state.
Variations
- Use
git rebase -i --autosquashwithfixup!commits you create along the way; Git automatically marks them as squash/fixup, reducing manual editing. - Instead of interactive rebase, you can use
git merge --squashto collapse an entire feature branch into one commit on your current branch. - For a quick single-commit collapse, use
git reset --softfollowed by a new commit message, which preserves all your changes in the staging area.
Real-world use cases
- Cleaning up a feature branch with 15 WIP commits before opening a pull request so reviewers see a single coherent change.
- Squashing commits locally that fix typos and style issues before pushing to a shared repository, keeping the history professional.
- Combining several hotfix commits into one release commit before tagging a new version for production deployment.
Key takeaways
- Interactive rebase (
git rebase -i <base>) lets you rewrite commit history by changing action words likepicktosquash. - Squashing merges multiple commits into one, creating a clean, logical unit of work.
- Only squash commits that haven't been pushed to a shared branch to avoid disrupting teammates.
- Conflicts during rebase are normal; resolve them with
git addandgit rebase --continueor abort withgit rebase --abort. - Your pre-rebase commits are recoverable via
git reflog, so mistakes aren't fatal. - Alternatives like
git merge --squashandgit reset --softoffer simpler but less flexible ways to combine commits.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.