
September 11, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
You opened a pull request with fourteen commits titled "fix typo", "wip", and "actually fix it". Reviewers skim past the noise. CI logs tie failures to the wrong SHA. That is the moment you need to squash commits the right way—not as a habit on every branch, but as a deliberate cleanup step before merge. On teams I work with through web development in Nepal and remote client repos, squashing is a workflow choice with real trade-offs. This guide shows the commands, the safety rules, and the cases where you should leave history alone.
What does it mean to squash commits the right way?
Squashing merges multiple commits into one. The diff stays the same. The history becomes shorter. "The right way" means three things together: one commit equals one logical change, the message explains why—not only what—and you do not destroy audit trails on branches other people use.
Git offers several squash paths. They are not interchangeable. Pick based on who shares the branch and whether you need per-commit SHAs later for git bisect or signed attestation.
A squashed commit still points to a parent. Git creates a new SHA. Anyone who based work on the old SHAs must reconcile. That is why squashing belongs on feature branches you own—not on main after others have pulled intermediate commits.
The three squash mechanisms teams actually use
- Interactive rebase (
git rebase -i): Rewrites local commits before push. Best control over message and ordering. - Squash merge on a pull request: Platform combines all PR commits at merge time. No force push on the feature branch required.
- Soft reset + recommit:
git reset --softto a base, then one new commit. Fast on small branches; easy to mess up on large ones.
On production Laravel apps I maintain with Deployer 7 and GitLab CI, we squash feature work at merge. We keep granular commits on the feature branch while iterating. Reviewers can still see the progression in the PR timeline even when main gets one commit.
When should you squash commits instead of keeping them separate?
Squash when commits are implementation noise—not when each commit is a independently revertible unit. The decision is contextual. A law-firm portal fix and a trek booking platform deploy both benefit from clean main, but for different reasons.
| Scenario | Squash? | Why |
|---|---|---|
| Feature branch with WIP commits | Yes | Reviewers read one diff; changelog stays readable |
| Each commit passes CI and maps to a ticket | Often no | Granular revert and bisect matter more than brevity |
| Open-source contribution | Usually yes | Maintainers prefer one commit per PR on main |
| Shared long-lived branch others pull | No | Rewriting forces painful rebases for teammates |
| Release branch with tagged versions | No | Tags and SHAs are contractual for deployments |
| Pairing with Conventional Commits | Squash to one type | One feat: or fix: per merged PR |
Squashing also pairs well with signed commits. One squashed commit means one signature to verify. That aligns with supply-chain practices covered in signing Git commits with GPG or SSH. If every WIP commit was signed, squashing produces a new commit that needs a fresh signature after rebase.
Do not squash just to hide mistakes. A bad merge or leaked secret needs revert or reflog recovery—not burial under a squash. Security fixes deserve their own visible commit message on main.
How do you squash commits locally with interactive rebase?
Interactive rebase is the precision tool. You edit a todo list. Git replays commits. You mark which ones to squash. Follow this sequence on a branch only you have pushed—or before first push.
- Ensure your working tree is clean:
git status. - Find the base commit—the point before your feature work:
git log --oneline main..HEAD. - Start interactive rebase against that base:
git rebase -i main. - In the editor, leave the first commit as
pick. Change later commits tosquashors. - Save and close. Git opens a second editor for the combined message.
- Write a message that matches your team standard. Push with care if the branch was already remote.
Example rebase todo list
pick a1b2c3d feat(cart): add zone validation schema
squash d4e5f6a fix typo in zone label
squash 7g8h9i0 wip tests
squash j1k2l3m address review comments After save, Git combines all four into one commit. The resulting message should not paste four subject lines blindly. Write something a release note could quote:
feat(cart): validate delivery zones before checkout
Add server-side zone rules matching NPR delivery bands.
Reject out-of-area addresses with a clear user message.
Includes PHPUnit coverage for edge postcodes. Squash the last N commits without counting back to main
For quick cleanup before your first push:
git reset --soft HEAD~4
git commit -m "feat(cart): validate delivery zones before checkout" --soft keeps staged changes. You lose individual commit messages unless you copied them. Prefer rebase -i when messages contain ticket IDs you need to preserve.
If you already pushed the messy commits, update the remote carefully:
git push --force-with-lease origin feature/delivery-zones --force-with-lease refuses to push if someone else added commits you have not seen. Plain --force overwrites blindly. On shared feature branches, coordinate in chat before any force push. I have seen CI pipelines and cron jobs break because a teammate force-pushed while another was rebasing.
How do you squash commits on a pull request without rewriting shared history?
Pull request squash merge is the lowest-friction option for teams that forbid force push on protected branches. GitHub, GitLab, and Bitbucket all expose it. The platform creates one merge commit on the target branch containing the full PR diff. Individual commits remain visible in the PR UI for archaeology—but not on main.
GitHub squash merge
Enable "Allow squash merging" in repository settings. At merge time, choose "Squash and merge". Edit the default title. GitHub often concatenates commit subjects—delete the noise. Prefer a single line matching Conventional Commits if your changelog tooling expects it.
GitLab squash at merge
Set Squash commits when merge request is accepted in project settings or per MR. GitLab can enforce squash for all merges into main. That matches how several sister sites on my shared EC2 deploy pipeline behave—one commit per MR keeps Deployer release notes predictable.
When PR squash beats local rebase
- Branch protection blocks force push.
- Multiple authors contributed; rewriting reassigns blame awkwardly.
- Reviewers already approved specific SHAs—you should not rewrite after approval without re-review.
- You want zero risk to the source branch; delete it after merge anyway.
PR squash does not replace local hygiene. Smaller commits during development still help reviewers comment on incremental diffs. Squash at the boundary—when work enters shared history.
Reference: GitHub documents merge options at docs.github.com pull request merges. Git's own rebase manual lives at git-scm.com/docs/git-rebase.
What are the common mistakes when squashing Git commits?
Most squash accidents are social, not syntactic. The commands work. Teams skip the rules about shared state. These failures show up repeatedly on client projects and on support and maintenance engagements when a new developer joins mid-sprint.
Force pushing main or a release tag
Never rewrite main, production, or tagged releases others deploy. Deployer symlink swaps and GitLab CI jobs reference SHAs in logs. A force-pushed main breaks clone scripts, open PR bases, and automated changelogs. If you already did—stop pushing and read recover lost commits with git reflog before anyone runs git pull.
Squashing unrelated changes together
One commit should revert cleanly. Do not squash a refactor with a bug fix. If production needs the fix without the refactor, you want two merges or two commits. Use git rebase -i to reorder first—group related picks, squash within groups only.
Losing authorship and co-author trailers
Squash rewrites the committer metadata. Add co-authored-by trailers when pairing:
feat(api): add rate limit headers
Co-authored-by: Name <email@example.com> GitHub and GitLab attribute co-authors from the trailer line. Without it, squash merge can hide contributor credit in the blame view on main.
Breaking signed commit chains
GPG or SSH signing applies to the new squashed commit only after you re-sign. Configure commit signing before squash, or sign again post-rebase. Teams enforcing verified commits via branch rules will block unsigned squashed pushes.
Automating without hooks
Pre-push hooks catch accidental force push to protected refs. Combine with CI lint on commit messages. See Git hooks to automate checks for a practical setup. A regex tester helps validate Conventional Commit patterns in hook scripts before you enforce them repo-wide.
Deploy and CI gotchas after squash
On Laravel 12 or 13 projects, a squashed commit still triggers the same pipeline. Verify these after your first squash merge:
- CI caches keyed by commit SHA invalidate—first build may run slower.
- Deployer release names derived from short SHA change; note the new ID in deploy logs.
- Changelog generators reading conventional commits on
mainsee one entry—ensure the squash message carries the ticket reference. - Open dependabot or renovate PRs rebased onto old SHAs may need a fresh rebase onto new
main.
For infrastructure-heavy repos, Linux system administration teams sometimes tag server config separately from app code. Do not squash across unrelated concerns—keep infra commits out of application feature squashes.
Key Takeaways
- Squash WIP commits into one logical unit with a message that explains why the change exists—not a pile of subject lines.
- Use
git rebase -ion private feature branches; use PR squash merge when branch protection blocks force push. - Always push with
--force-with-lease, never plain--force, and never rewritemainor release tags. - Keep fixes and refactors in separate squashes so production can revert one without the other.
- Re-sign commits and add co-author trailers after squash so blame and verified-commit rules stay intact.
- Pair squash policy with Conventional Commits and pre-push hooks so CI and changelogs stay predictable.
People Also Ask
Is squashing commits the same as rebasing?
No. Rebasing replays commits onto a new base and can reorder or edit each one. Squashing combines multiple commits into one. You often squash during an interactive rebase, but rebase alone does not merge commits unless you mark them squash in the todo list.
Does squash merge delete commit history permanently?
On the target branch, yes—the individual commits become one. On GitHub and GitLab, the pull request page still lists the original commits for review. Cloning main after squash merge shows only the squashed commit. Clone the merged PR URL if you need the granular SHAs later.
When should you never squash commits?
Never squash on branches others have pulled when you plan to force push. Never squash tagged releases. Never combine unrelated fixes that might need independent revert. Open-source maintainers sometimes reject squashing when each commit in a PR is a logically separate patch.
What is the difference between squash and fixup in interactive rebase?
squash merges commits and opens an editor to combine messages. fixup merges commits but discards the secondary commit message entirely. Use fixup for "address review" commits where the first message already describes the final intent.
Build a Git workflow your team can trust
Squash commits the right way is a team policy—not a personal preference. Pick PR squash merge for protected main, interactive rebase for solo cleanup, and document when granular history beats brevity. Your reviewers, CI logs, and future self during a 2 a.m. incident will thank you. If you want help standardising Git workflow, CI, and zero-downtime deploys on a production app, see the services overview or Notary Nepal portfolio case for how clean merge history supports maintainable legal-tech portals. Ready to tighten your pipeline? Contact us to talk through branch rules, hooks, and deploy automation—or browse more guides on the blog and the home page.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

