Skip to main content

Merge Strategies

Quick Summary

Merging combines changes from one branch into another. Git provides three main merge approaches: fast-forward (linear), three-way merge (preserves branch history), and squash merge (condensed). Choosing the right strategy shapes your project's commit history.


Overview

flowchart TD
A["git merge feature"] --> B{Can fast-forward?}
B -->|Yes| C["Fast-Forward Merge<br/>(no merge commit)"]
B -->|No| D{Strategy?}
D -->|Default| E["Three-Way Merge<br/>(creates merge commit)"]
D -->|"--squash"| F["Squash Merge<br/>(single commit)"]
D -->|"--no-ff"| G["No-FF Merge<br/>(forced merge commit)"]

Fast-Forward Merge

Happens when main has no new commits since the branch was created. Git simply moves the pointer forward.

Before

gitGraph
commit id: "C1"
commit id: "C2"
branch feature
commit id: "C3"
commit id: "C4"

After git merge feature

gitGraph
commit id: "C1"
commit id: "C2"
commit id: "C3"
commit id: "C4"
git checkout main
git merge feature
Updating a1b2c3d..d4e5f6a
Fast-forward
src/feature.js | 25 ++++++++++
1 file changed, 25 insertions(+)

Result: Linear history, no merge commit. Clean but loses branch context.


Three-Way Merge

Happens when both branches have new commits. Git creates a merge commit with two parents.

Before

gitGraph
commit id: "C1"
commit id: "C2"
branch feature
commit id: "C3"
commit id: "C4"
checkout main
commit id: "C5"

After git merge feature

gitGraph
commit id: "C1"
commit id: "C2"
branch feature
commit id: "C3"
commit id: "C4"
checkout main
commit id: "C5"
merge feature id: "M1 (merge)"
git checkout main
git merge feature
Merge made by the 'ort' strategy.
src/feature.js | 25 ++++++++++
1 file changed, 25 insertions(+)

Result: Preserves full branch history. Merge commit shows where branches joined.


No-Fast-Forward Merge (--no-ff)

Forces a merge commit even when fast-forward is possible. Useful for preserving branch structure.

git checkout main
git merge --no-ff feature -m "Merge feature/auth into main"

Why Use --no-ff?

With Fast-ForwardWith --no-ff
Linear history, clean but flatClear "feature bubbles" in graph
Can't tell where a feature started/endedEasy to see and revert entire features
Default behaviorMany teams enforce this as policy

Squash Merge

Condenses all feature branch commits into a single commit on the target branch.

git checkout main
git merge --squash feature
git commit -m "feat: add user authentication (squashed)"

Before

feature:  C3 → C4 → C5 → C6  (4 messy commits)
main: C1 → C2

After Squash Merge

main:     C1 → C2 → S1  (one clean commit containing all feature changes)

Result: Cleanest history on main, but loses individual commit history from the branch.


Comparison

StrategyMerge Commit?Branch HistoryUse When
Fast-forwardNoLostSimple, linear changes; solo work
Three-wayYesPreservedCollaborative development; parallel work
No-FFYes (forced)PreservedTeam policy; want to see feature boundaries
SquashNo (single new)CondensedMessy branch commits; clean main history

How to Merge

Step-by-Step

# 1. Switch to the target branch
git checkout main

# 2. Ensure you're up to date
git pull origin main

# 3. Merge the feature branch
git merge feature/login

# 4. Push the merged result
git push origin main

# 5. Delete the merged branch (cleanup)
git branch -d feature/login
git push origin --delete feature/login

Abort a Merge

# If a merge goes wrong — cancel and go back
git merge --abort

Troubleshooting

ProblemCauseFix
CONFLICT messagesBoth branches modified same linesSee Conflict Resolution
Already up to dateNothing new to mergeVerify you're on the right branch
Unwanted merge commitUsed merge when you wanted rebaseUse git rebase instead (next page)
Merge created wrong historyUsed fast-forward when --no-ff was betterCan't easily undo — set as default next time

Set --no-ff as Default

# Always create merge commits (recommended for teams)
git config --global merge.ff false

Best Practices

  • Use --no-ff on main — keeps feature boundaries visible in the graph
  • Use squash for messy branches — "WIP", "fix typo", "oops" don't belong in main history
  • Always pull before merging — ensures you merge into the latest code
  • Delete branches after merging — reduces clutter
  • Test before merging to main — ensure the feature works in the merged state

What's Next

  1. Rebase Explained — An alternative to merging for cleaner history
  2. Conflict Resolution — Handle merge conflicts confidently