When merge strategy matters
Every pull request on GitHub eventually needs to land on your main branch, but how it gets there shapes your entire commit history. Whether you choose to squash, merge commit, or rebase determines not only how clean your main branch looks, but also how easy it will be for teammates to understand what changed, when, and why months from now. This guide walks through squash, merge or rebase on GitHub: a step-by-step guide, showing you when each strategy makes sense and how to implement it across your team.
Squash: one clean commit per PR
Squashing combines all commits from a feature branch into a single commit on main. This is the strategy that keeps your main branch the cleanest and most readable, especially when you have dozens of PRs landing each week.
When to use squash: Use this for feature work where the individual commits on the branch don't tell a meaningful story. If you have ten commits with messages like "fix typo," "try again," "works now," squashing prevents that noise from polluting main.
Real-world scenario: Your team ships a new checkout flow. A developer makes 12 commits while building it: three attempts at validation, two bug fixes, four refactors. When it's ready, squashing those 12 into one commit with a clear message like "feat: new checkout flow with saved payment methods" makes the history much easier to navigate later.
How to configure on GitHub: Go to your repository Settings → Pull Requests. Under "Allow squash merging," check the box. You can also allow developers to choose other methods if needed. When a PR is ready, the person merging clicks the dropdown next to "Merge pull request" and selects "Squash and merge."
| Before squash | After squash |
|---|---|
abc123 fix typodef456 try againghi789 works nowjkl012 refactor utils |
mno345 feat: new checkout flow |
The trade-off: you lose the granular commit history from the branch. If a bug is introduced in one of those hidden commits, debugging becomes harder since you can't bisect through individual steps. For features where the journey doesn't matter, this is fine. For critical infrastructure changes, you might want more detail.
Merge commit: preserves branch history
A merge commit creates a new commit that joins two branches together. It preserves every commit from the feature branch, showing exactly what work happened there before it was integrated.
When to use merge commit: Use this when your commits tell a meaningful story—refactors broken into logical chunks, migrations done in phases, or when you need full traceability for compliance or debugging.
Real-world scenario: You're migrating from PostgreSQL connection pooling via PgBouncer to using connection pooling at the application level. Your branch has five commits: one for the config setup, one for the connection pool initialization, one for metrics, one for graceful shutdown, and one for documentation. Each commit is self-contained and reviewed. Keeping them in history lets future developers understand the migration strategy step by step.
How to configure on GitHub: In Settings → Pull Requests, check "Allow merge commits." When merging, select "Create a merge commit" from the dropdown. GitHub will create a merge commit with the default message "Merge pull request #123 from branch-name" which you can customize.
git log --oneline main
abc123 Merge pull request #45 from feature/pooling-migration
def456 docs: pooling migration guide
ghi789 feat: graceful shutdown for pool
jkl012 feat: add pool metrics
mno345 feat: initialize connection pool
pqr678 feat: pooling config setup
stu901 previous work on main
The advantage: full history is preserved and branching structure is clear. The disadvantage: your main branch history can become messy with interleaving commits from multiple branches, especially in high-velocity teams.
Rebase: linear history with all commits
Rebasing replays all commits from your feature branch on top of main, as if they were authored after the latest main commit. You get a clean linear history with every individual commit visible.
When to use rebase: Use this when your commits are logical and self-contained, and you want a linear, easy-to-read history without merge commits cluttering the graph.
Real-world scenario: Your backend team is shipping a new API endpoint for customer analytics. You have four commits: one for the database schema, one for the repository layer, one for the service logic, and one for the HTTP handler. Each commit compiles and passes tests independently. Rebasing stacks them linearly on main, making git bisect and code review archaeology trivial.
How to configure on GitHub: In Settings → Pull Requests, check "Allow rebase merging." When merging, click the dropdown and select "Rebase and merge." GitHub rewrites the branch commits on top of main and fast-forwards.
git log --oneline main
abc123 feat: analytics endpoint HTTP handler
def456 feat: analytics service logic
ghi789 feat: analytics repository layer
jkl012 feat: analytics database schema
mno345 previous work on main
The advantage: clean linear history, every commit is present for debugging. The disadvantage: if the branch existed publicly or others pulled from it, rewriting history can cause problems. Also, it requires discipline—developers must ensure each commit is actually independent and working.
Choosing a strategy for your repository
There's no universal right answer. Your choice depends on team size, code review culture, and how much commit history you actually need to preserve.
Go with squash if: Your team is large and shipping many small features. You value a clean main branch history over granular commit archaeology. You accept that individual branch commits might not be meaningful.
Go with merge commit if: You have long-running feature branches with meaningful commit breakdowns. You need clear visualization of branch integration points. You're working in domains like finance, healthcare, or critical infrastructure where audit trails matter.
Go with rebase if: Your team is smaller and disciplined about atomic commits. You want a linear history but still need individual commits visible. Your commits genuinely represent logical work units that can stand alone.
Best practice: Pick one per repository and enforce it. GitHub allows you to disable the other merge methods, preventing accidental inconsistency. Add this to your team's contribution guide so new developers know what to expect.
// In Settings → Pull Requests
✓ Allow squash merging (only this one)
☐ Allow merge commits
☐ Allow rebase merging
Common mistakes
Switching strategies mid-project: Don't use squash for one PR and rebase for another on the same repository. Your history becomes unpredictable and harder to navigate. Decide once and stick with it.
Allowing all three methods: This creates choice paralysis and inconsistency. Developers will pick different strategies based on mood or convenience, resulting in a chaotic main history.
Using squash for complex refactors: If you're refactoring a critical system with seven carefully thought-out commits, squashing them into one "refactor: payment system" loses too much context. A future bug fix will be harder to understand.
Rebasing large branches: If your branch is weeks old and heavily diverged from main, rebasing can cause merge conflicts on every commit. Consider merge commit instead to resolve conflicts once.
Not communicating the strategy: If you enforce squash but don't document it, developers will ask why their commits disappeared. Make it explicit in your onboarding and contributing guidelines.
Key takeaways
- Squash gives you the cleanest main branch by combining branch commits into one, ideal for high-velocity teams with many small features.
- Merge commit preserves branch structure and full commit history, best for long-running work where each commit tells part of the story.
- Rebase creates a linear history with all individual commits visible, suited to disciplined teams with atomic, self-contained commits.
- Choose one strategy per repository and disable the others in settings to enforce consistency across your team.
- Document your choice in your contributing guide so developers understand the rationale and don't waste time asking questions.
- Your choice affects debugging, code archaeology, and team onboarding—pick based on your team size and project complexity, not personal preference.






Comments
Loading comments…