Git Squash and Merge: Streamlining Your Pull Request History
Git squash and merge helps you maintain a clean commit history by collapsing multiple PR commits into one. Learn how to simplify your pull request workflow.
During a recent sprint, I spent nearly two hours trying to revert a feature that had been broken by a single commit buried in a mess of "fix typo" and "wip" commits. It was a classic case of a cluttered project history making a simple rollback nearly impossible. That’s when I realized the team needed a stricter approach to how we merged code.
If you’re tired of seeing a messy git log filled with incremental, low-value commits, it’s time to implement a git squash strategy for your pull requests. By consolidating your work into a single, cohesive unit, you ensure your main branch remains readable and revertible.
Understanding the Git Squash and Merge Workflow
The core idea behind a git merge --squash is simple: it takes all the commits from your feature branch and bundles them into one single commit on the target branch. Instead of cluttering the history with every iteration you made while building a feature, the project history reflects the "what" rather than the "how."
We’ve found that this is particularly effective for teams using a standard Git Rebase vs. Merge: A Guide to Clean Commit History approach, as it forces developers to write meaningful commit messages that actually explain the business value of a change.
How to execute a squash merge
If you're working in the terminal, you don't necessarily need a fancy UI to do this. Once your feature branch is ready and you're checked out on main, run the following:
Bashgit checkout main git merge --squash feature-branch-name git commit -m "feat: add user authentication module"
The --squash flag tells Git to take all the changes from the feature branch, stage them, but stop before creating a commit. This gives you one final chance to craft a perfect, descriptive commit message.
Why This Leads to a Clean Commit History
A clean commit history isn't just about aesthetics; it’s a functional requirement for large-scale engineering. When every PR is represented by exactly one commit, using git bisect to find a regression becomes trivial. You aren't hunting through 15 "fixed bug" commits; you're looking at one atomic change.
We once had a project where we didn't enforce this. We ended up with roughly 1,400 commits in six months, and about 40% of them were just "oops" or "fixed linting." Cleaning that up took about three days of tedious rebase work. Now, we use the squash strategy, and our git log is readable at a glance.
Comparison of Merge Strategies
| Strategy | History Type | Revertability | Use Case |
|---|---|---|---|
| Standard Merge | Non-linear | Complex | Keeping full commit context |
| Rebase | Linear | Moderate | Maintaining clean, sequential history |
| Squash & Merge | Atomic | Excellent | Feature-based PRs |
Common Pitfalls and Trade-offs
I’ll be honest: squash and merge isn't a silver bullet. If you're working on a feature that spans three weeks and requires constant collaboration, squashing everything can hide the "story" of the development.
We first tried squashing everything indiscriminately, but it broke our ability to track complex dependency changes in our ML pipelines—something we discussed in our guide on Project Milestone: Deployment Readiness for ML Pipelines. If you need to preserve granular history for compliance or audit reasons, squashing might not be the right move.
When to avoid it:
- Long-running experimental branches: Sometimes you need the history to debug why a specific approach was taken.
- Shared feature branches: If multiple people are pushing to the same branch, squashing it can cause massive headaches for your teammates.
FAQ: Streamlining Your Workflow
Q: Does git squash and merge delete my feature branch?
A: No. After you run the command, your feature branch remains intact. You’ll need to delete it manually with git branch -d feature-branch-name.
Q: Can I still see the original commits if I need to? A: Yes, but only if you don't delete the feature branch immediately. Once the branch is deleted, the individual commit history is gone from the main branch. This is the trade-off for a cleaner history.
Q: Is this the same as an interactive rebase?
A: Not quite. An interactive rebase (git rebase -i) allows you to squash specific commits while keeping others. git merge --squash is a destructive, all-in-one-go operation that happens at the moment of integration.
Wrapping Up
Squash and merge isn't just a preference—it’s a discipline. It forces you to think about what you’re shipping and how it fits into the broader project. While we still rely on tools like Git Rerere: Automating Your Git Merge Conflict Resolution to handle the messy conflicts that happen before the merge, the final integration should always be clean and intentional.
Next time you’re about to click "Merge" on a PR, take a second to consider if your current commit history is actually helpful to the person who has to debug it six months from now. If it’s not, squash it.
