Merge Strategies Explained
When completing a Pull Request on GitHub, you have three options for integrating changes. Understanding these strategies helps you maintain a clean and useful Git history.
Quick Comparison
| Strategy | History | Best For | Commits |
|---|---|---|---|
| Create a Merge Commit | Preserves all commits + merge commit | Team collaboration, audit trails | All original commits + 1 merge |
| Squash and Merge | Combines into single commit | Feature branches, clean history | 1 commit |
| Rebase and Merge | Linear history, no merge commit | Small changes, linear workflow | All original commits (rebased) |
Visual Comparison
BEFORE MERGE (Feature branch with 3 commits)
============================================
main A───────B───────C
\
feature D───E───F
│ │ │
│ │ └── "Fix typo"
│ └────── "Add validation"
└────────── "Initial feature"
AFTER MERGE - Three Different Results:
======================================
┌─────────────────────────────────────────────────────────────────────────────┐
│ OPTION 1: CREATE A MERGE COMMIT │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ main A───────B───────C───────────M (merge commit) │
│ \ / │
│ feature D─────E─────F─────────── │
│ │
│ Result: 4 new items in main (D, E, F + merge commit M) │
│ History: Shows exactly when and how branch was merged │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ OPTION 2: SQUASH AND MERGE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ main A───────B───────C───────S (single squashed commit) │
│ │ │
│ └── Contains all changes from D+E+F │
│ │
│ Result: 1 new commit in main (S combines D, E, F) │
│ History: Clean, single commit per feature │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ OPTION 3: REBASE AND MERGE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ main A───────B───────C───────D'──────E'──────F' │
│ │ │ │ │
│ │ │ └── "Fix typo" │
│ │ └────────── "Add validation" │
│ └────────────────── "Initial feature" │
│ │
│ Result: 3 new commits in main (D', E', F' - rebased versions) │
│ History: Linear, as if feature was developed on latest main │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Option 1: Create a Merge Commit
How It Works
Before: After:
main ──A──B──C main ──A──B──C─────────M
\ \ /
feature D──E──F feature D──E──F─────
GitHub creates a new "merge commit" (M) that has two parents: the latest main commit (C) and the last feature commit (F).
What You See in History
git log --oneline
a1b2c3d Merge pull request #42 from feature/invoice-validation
f9e8d7c Fix typo in validation message
e7d6c5b Add invoice amount validation
d5c4b3a Initial invoice validation feature
c3b2a1f Previous main commit
When to Use
✅ Team collaboration - Shows who worked on what and when ✅ Audit requirements - Complete history for compliance ✅ Complex features - Preserves the development story ✅ Multiple contributors - Credits everyone's commits
Example Scenario
Developer A and Developer B both contributed to feature/customer-portal:
Developer A: 3 commits (authentication)
Developer B: 2 commits (dashboard)
Using "Create Merge Commit" preserves both developers' contributions
with their original commit messages and timestamps.
Option 2: Squash and Merge
How It Works
Before: After:
main ──A──B──C main ──A──B──C──S
\ │
feature D──E──F (S = D + E + F combined)
│ │ │
│ │ └── "fix typo"
│ └───── "add validation"
└──────── "wip: initial"
All commits (D, E, F) are combined into a single new commit (S) on main.
What You See in History
git log --oneline
s1q2u3a Add invoice validation feature (#42)
c3b2a1f Previous main commit
b2a1c0e Another previous commit
The squash commit message typically includes:
- PR title as commit title
- List of original commits in the body
- PR number for reference
When to Use
✅ Messy commit history - "WIP", "fix typo", "oops" commits ✅ Feature branches - One commit per feature in main ✅ Solo work - Your intermediate commits don't matter to others ✅ Clean main branch - Easy to read and understand
Example Scenario
Your feature branch has these commits:
1. "WIP: starting invoice validation"
2. "more work on validation"
3. "fix bug"
4. "actually fix the bug"
5. "Add invoice validation - DONE"
After Squash and Merge:
1. "Add invoice validation feature (#42)"
Much cleaner! 🎉
Squash Commit Message
GitHub lets you edit the final squash commit message:
Add invoice validation feature (#42)
* WIP: starting invoice validation
* more work on validation
* fix bug
* actually fix the bug
* Add invoice validation - DONE
Co-authored-by: developer@company.com
Option 3: Rebase and Merge
How It Works
Before: After:
main ──A──B──C main ──A──B──C──D'──E'──F'
\
feature D──E──F (D', E', F' are "rebased" copies)
Each commit is replayed on top of main, creating new commits (D', E', F') with:
- Same changes as original
- New commit hashes
- New parent commits
What You See in History
git log --oneline
f1e2d3c Fix typo in validation message
e2d3c4b Add invoice amount validation
d3c4b5a Initial invoice validation feature
c3b2a1f Previous main commit
b2a1c0e Another previous commit
Notice: No merge commit, linear history!
When to Use
✅ Linear history - No merge commits cluttering the log ✅ Clean commits - Each commit is meaningful and complete ✅ Small changes - Few commits that are already clean ✅ Bisect-friendly - Easier to find bugs with git bisect
When NOT to Use
❌ Shared branches - Never rebase commits others are using ❌ Messy commits - Use squash instead ❌ Complex merges - Can create conflicts at each commit
Example Scenario
Your feature branch has 2 clean, atomic commits:
1. "Add CustomerValidator class"
2. "Integrate CustomerValidator with invoice form"
Using Rebase and Merge keeps both commits in a clean,
linear history without a merge commit.
Result in main:
──●──●──●──[Add CustomerValidator class]──[Integrate CustomerValidator...]
Side-by-Side Comparison
Scenario: Feature with 3 Commits
Feature Branch: feature/add-customer-validation
Commits:
1. "Add validation helper functions"
2. "Create CustomerValidator class"
3. "Add unit tests for validation"
Result After Each Strategy
┌─────────────────────────────────────────────────────────────────────────────┐
│ GIT LOG COMPARISON │
└─────────────────────────────────────────────────────────────────────────────┘
CREATE MERGE COMMIT:
────────────────────
* a1b2c3d Merge pull request #15 from feature/add-customer-validation
|\
| * f4e5d6c Add unit tests for validation
| * e3d4c5b Create CustomerValidator class
| * d2c3b4a Add validation helper functions
|/
* c1b2a3f Previous commit on main
SQUASH AND MERGE:
─────────────────
* s9q8u7a Add customer validation feature (#15)
* c1b2a3f Previous commit on main
REBASE AND MERGE:
─────────────────
* f4e5d6c Add unit tests for validation
* e3d4c5b Create CustomerValidator class
* d2c3b4a Add validation helper functions
* c1b2a3f Previous commit on main
Team Recommendations
For NetSuite SDF Projects
| Scenario | Recommended Strategy | Reason |
|---|---|---|
| Feature development | Squash and Merge | Clean history, one commit per feature |
| Bugfix with single commit | Rebase and Merge | Already clean, keep it linear |
| Hotfix to production | Create Merge Commit | Audit trail for production changes |
| Multi-developer PR | Create Merge Commit | Preserve everyone's contributions |
| WIP/messy commits | Squash and Merge | Clean up before merging |
Suggested Team Policy
┌─────────────────────────────────────────────────────────────────────────────┐
│ RECOMMENDED MERGE POLICY │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ feature/* → develop Use: SQUASH AND MERGE │
│ Reason: Clean develop history │
│ │
│ bugfix/* → develop Use: SQUASH AND MERGE │
│ Reason: Single commit per fix │
│ │
│ develop → main Use: CREATE MERGE COMMIT │
│ Reason: Mark release points clearly │
│ │
│ hotfix/* → main Use: CREATE MERGE COMMIT │
│ Reason: Audit trail for production │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
GitHub Settings
Configure Default Merge Strategy
Repository admins can set the default and allowed merge strategies:
- Go to Repository Settings
- Click General (left sidebar)
- Scroll to Pull Requests section
- Configure options:
☑ Allow merge commits
☑ Allow squash merging ← Set as default
☐ Allow rebase merging
☑ Automatically delete head branches
Enforce Specific Strategy
To require a specific merge strategy:
- Go to Settings → Branches
- Add/edit branch protection rule for
main - Under "Require a pull request before merging":
- Select allowed merge methods
Quick Decision Guide
┌─────────────────────────────────────────────────────────────────────────────┐
│ WHICH MERGE STRATEGY SHOULD I USE? │
└─────────────────────────────────────────────────────────────────────────────┘
START
│
▼
Is your commit history messy? ──YES──▶ SQUASH AND MERGE
(WIP, fix typo, oops, etc.) "Clean it up!"
│
NO
│
▼
Do you need audit trail? ──YES──▶ CREATE MERGE COMMIT
(Production, compliance) "Preserve everything!"
│
NO
│
▼
Do you want linear history? ──YES──▶ REBASE AND MERGE
(Clean, atomic commits) "Keep it linear!"
│
NO
│
▼
Default: SQUASH AND MERGE
"When in doubt, squash it out!"
After Merge: Delete Branch vs Rebase and Continue
After your PR is merged to main, you have two options for handling your feature branch:
Option A: Delete the Feature Branch (Recommended)
BEFORE MERGE:
─────────────
main ──A──B──C
\
feature D──E──F
AFTER MERGE + DELETE:
─────────────────────
main ──A──B──C──M (merge commit with D,E,F)
feature [DELETED] ✓
Next feature? Create NEW branch from updated main:
main ──A──B──C──M
\
feature/new G──H
What happens:
- PR merged to main
- Feature branch deleted (local + remote)
- For new work: pull latest main, create fresh branch
Commands:
# After PR is merged on GitHub
git checkout main
git pull origin main
# Delete local feature branch
git branch -d feature/old-feature
# Delete remote branch (if not auto-deleted)
git push origin --delete feature/old-feature
# Start new work
git checkout -b feature/new-feature
Option B: Rebase and Continue Working
BEFORE MERGE:
─────────────
main ──A──B──C
\
feature D──E──F
AFTER MERGE (main updated):
───────────────────────────
main ──A──B──C──M (contains D,E,F)
\
feature D──E──F (still points here - OUTDATED!)
AFTER REBASE onto main:
───────────────────────
main ──A──B──C──M
\
feature (empty - commits already in main)
OR if you had new commits:
main ──A──B──C──M
\
feature G'──H' (new work rebased on top)
What happens:
- PR merged to main
- You continue working on same branch
- Rebase to get latest main changes
- Your branch now builds on top of merged main
Commands:
# Fetch latest changes
git fetch origin
# Rebase your branch onto updated main
git checkout feature/my-feature
git rebase origin/main
# If your commits are already in main, branch becomes "empty"
# If you have new commits, they're replayed on top of main
Visual Comparison
┌─────────────────────────────────────────────────────────────────────────────┐
│ SCENARIO: Feature merged, now you want to add more functionality │
└─────────────────────────────────────────────────────────────────────────────┘
OPTION A: DELETE + NEW BRANCH (Recommended)
───────────────────────────────────────────
1. feature/add-validation merged to main
2. Delete feature/add-validation
3. Create feature/add-error-messages from main
main ──●──●──●──M────────────────
\
feature/add-error-msgs G──H──I
Result: Clean separation between features
Clear history of what each branch did
OPTION B: REBASE + CONTINUE (Not Recommended)
─────────────────────────────────────────────
1. feature/add-validation merged to main
2. Keep branch, add more commits
3. Rebase onto main
main ──●──●──●──M
\ \
feature D─E─F G'─H'─I' (rebased new work)
└─┬─┘
└── These are now in main (duplicated context)
Result: Confusing - same branch for different features
Hard to track what's new vs already merged
When to Use Each
| Scenario | Approach | Reason |
|---|---|---|
| Feature complete | Delete branch | Clean slate for next work |
| One-time bugfix | Delete branch | Fix is done, branch served its purpose |
| Ongoing work in phases | Delete + new branch | Each phase gets its own PR |
| Experiment/spike | Delete branch | Throw away or start fresh |
| Long-running release branch | Rebase | Continuously sync with main |
Team Recommendation
┌─────────────────────────────────────────────────────────────────────────────┐
│ BEST PRACTICE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ✓ ALWAYS delete feature branches after merge │
│ │
│ ✓ Create NEW branches for new features │
│ │
│ ✓ Enable "Automatically delete head branches" in GitHub settings │
│ │
│ ✗ DON'T reuse branches for multiple unrelated features │
│ │
│ ✗ DON'T keep stale branches around "just in case" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
GitHub Auto-Delete Setting
Enable automatic branch deletion after merge:
- Go to Repository Settings
- Scroll to Pull Requests
- Check ☑ Automatically delete head branches
After PR merge:
┌─────────────────────────────────────┐
│ Pull request successfully merged │
│ and closed │
│ │
│ 🗑️ Branch feature/xyz deleted │
│ │
│ [Restore branch] │
└─────────────────────────────────────┘
Summary
| Strategy | Commits in Main | Merge Commit? | Best For |
|---|---|---|---|
| Create Merge Commit | All + merge | Yes | Audit, collaboration |
| Squash and Merge | 1 combined | No | Clean history |
| Rebase and Merge | All (rebased) | No | Linear history |
| After Merge | When to Use | Result |
|---|---|---|
| Delete branch | Always (recommended) | Clean repo, clear history |
| Rebase + continue | Rarely (long-running branches) | Synced branch, can cause confusion |
Discuss with your team and pick a consistent strategy. Document it in your repository's CONTRIBUTING.md file so everyone follows the same approach.