Skip to main content

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

StrategyHistoryBest ForCommits
Create a Merge CommitPreserves all commits + merge commitTeam collaboration, audit trailsAll original commits + 1 merge
Squash and MergeCombines into single commitFeature branches, clean history1 commit
Rebase and MergeLinear history, no merge commitSmall changes, linear workflowAll 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

ScenarioRecommended StrategyReason
Feature developmentSquash and MergeClean history, one commit per feature
Bugfix with single commitRebase and MergeAlready clean, keep it linear
Hotfix to productionCreate Merge CommitAudit trail for production changes
Multi-developer PRCreate Merge CommitPreserve everyone's contributions
WIP/messy commitsSquash and MergeClean 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:

  1. Go to Repository Settings
  2. Click General (left sidebar)
  3. Scroll to Pull Requests section
  4. 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:

  1. Go to Settings → Branches
  2. Add/edit branch protection rule for main
  3. 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:

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:

  1. PR merged to main
  2. Feature branch deleted (local + remote)
  3. 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:

  1. PR merged to main
  2. You continue working on same branch
  3. Rebase to get latest main changes
  4. 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

ScenarioApproachReason
Feature completeDelete branchClean slate for next work
One-time bugfixDelete branchFix is done, branch served its purpose
Ongoing work in phasesDelete + new branchEach phase gets its own PR
Experiment/spikeDelete branchThrow away or start fresh
Long-running release branchRebaseContinuously 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:

  1. Go to Repository Settings
  2. Scroll to Pull Requests
  3. Check ☑ Automatically delete head branches
After PR merge:
┌─────────────────────────────────────┐
│ Pull request successfully merged │
│ and closed │
│ │
│ 🗑️ Branch feature/xyz deleted │
│ │
│ [Restore branch] │
└─────────────────────────────────────┘

Summary

StrategyCommits in MainMerge Commit?Best For
Create Merge CommitAll + mergeYesAudit, collaboration
Squash and Merge1 combinedNoClean history
Rebase and MergeAll (rebased)NoLinear history
After MergeWhen to UseResult
Delete branchAlways (recommended)Clean repo, clear history
Rebase + continueRarely (long-running branches)Synced branch, can cause confusion
Team Tip

Discuss with your team and pick a consistent strategy. Document it in your repository's CONTRIBUTING.md file so everyone follows the same approach.