Skip to main content

Squashing Commits

Git Interactive Rebase (Squash vs Fixup)
1
Start Interactive Rebase
git rebase -i HEAD~3 opens git-rebase-todo editor.
2
Configure Commands (pick / squash / fixup)
Mark commit 1 as pick, commit 2 as squash (combines & edits msg), commit 3 as fixup (combines & discards msg).
3
Replay & Combine Commits
Git rewrites history into a single clean combined commit SHA-1.

What is Squashing?

Squashing combines multiple commits into a single commit. This is used to clean up a messy feature branch before merging — turning a series of wip, fix typo, and temp commits into one or a few clean, meaningful commits that tell a story.

Squashing is performed via git rebase -i (interactive rebase). It is a history rewrite, so only squash commits that have not been pushed to a shared branch, or use --force-with-lease if you need to push afterward.


When to Squash

Good use caseNot ideal
Collapsing WIP / "fix typo" / "temp" commits before a PRSquashing logically separate concerns into one giant commit
Creating a clean single commit per ticket/storySquashing after teammates have based work on your branch
Reducing noise in a long-running feature branchWhen each commit is already clean and meaningful

Squashing with Interactive Rebase

Example: Clean up the last 5 commits

git rebase -i HEAD~5

Git opens your editor:

pick a3f9bc2 feat(transactions): add domain model
pick 7d1e4f0 wip: controller in progress
pick 2c8a1b3 fix typo in service
pick 9f3e2d1 add missing null check
pick 1a4b5c6 feat(transactions): add controller endpoint

Change pick to squash (or s) for commits you want to fold upward:

pick a3f9bc2 feat(transactions): add domain model
squash 7d1e4f0 wip: controller in progress
squash 2c8a1b3 fix typo in service
squash 9f3e2d1 add missing null check
squash 1a4b5c6 feat(transactions): add controller endpoint

Git will pause and open the message editor, combining all messages:

# This is a combination of 5 commits.
# The first commit's message is:
feat(transactions): add domain model

# This is the commit message #2:
wip: controller in progress

# This is the commit message #3:
fix typo in service
...

Edit it down to one clean message:

feat(transactions): add transaction listing with date range filter

- Domain model and repository for transaction lookup
- Service layer with date range validation
- REST controller with pagination support

Closes JIRA-123

Save and close — Git produces a single clean commit.


Squashing into Multiple Logical Commits

You do not have to squash everything into one. Group related commits logically:

pick a3f9bc2 feat(transactions): add repository and domain model
squash 7d1e4f0 wip: missing index annotations
squash 2c8a1b3 add missing validation in repository
pick 9f3e2d1 feat(transactions): add service and controller
squash 1a4b5c6 fix: missing error handling in controller
squash b3e9f1a test: add unit tests for service

This produces two clean commits — one for the data layer, one for the service/API layer.


Squash Merge (via git merge --squash)

When merging a feature branch, --squash collapses all feature commits into staged changes and lets you write one clean commit:

git switch main
git merge --squash feature/JIRA-123
git commit -m "feat(transactions): add transaction listing with date range filter (JIRA-123)"

The feature branch's individual commits do not appear in main's history. The feature branch itself is not deleted and is not "merged" in Git's eyes — you must delete it manually.

This is the approach used by GitHub's "Squash and merge" button in pull requests.


After Squashing — Force Push

After squashing commits that were already pushed, force-push to update the remote branch:

git push --force-with-lease origin feature/JIRA-123

Squash vs Fixup

squashfixup
Keeps this commit's message?✅ Yes — adds to combined message❌ No — discards message
When to useMeaningful message worth keepingTrivial fix, message is noise
pick a3f9bc2 feat(transactions): add service logic
squash 7d1e4f0 add missing validation ← message included
fixup 2c8a1b3 fix typo ← message discarded

See Fixup for the --fixup / --autosquash workflow.


Squash at PR Review Time

A good habit: keep whatever commits you need locally while working (safety nets), then squash before requesting review. Reviewers see clean history, and git log on main stays meaningful.

Interview Questions

  1. How do you decide whether to squash all commits vs preserve a few logical commits before merge?
  2. What are the organizational risks of aggressive squashing in regulated environments?
  3. How would you recover safely if a squash/rebase introduced subtle regressions?
  4. When is merge --squash preferable to interactive rebase for team workflows?

Short answer guide:

  • Keep commit granularity aligned with reviewability and rollback needs.
  • Preserve traceability requirements when audit trails matter.
  • Recreate checkpoints from reflog and compare diff equivalence.
  • Use merge squash when teams want simple main history with minimal local rewrite complexity.
📖
Track Page Progress0 / 635 Read
Knowledge Base Completion0%