Merge Strategies
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
Fast-Forward Merge
Happens when main has no new commits since the branch was created. Git simply moves the pointer forward.
Before
After git merge feature
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
After git merge feature
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-Forward | With --no-ff |
|---|---|
| Linear history, clean but flat | Clear "feature bubbles" in graph |
| Can't tell where a feature started/ended | Easy to see and revert entire features |
| Default behavior | Many 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
| Strategy | Merge Commit? | Branch History | Use When |
|---|---|---|---|
| Fast-forward | No | Lost | Simple, linear changes; solo work |
| Three-way | Yes | Preserved | Collaborative development; parallel work |
| No-FF | Yes (forced) | Preserved | Team policy; want to see feature boundaries |
| Squash | No (single new) | Condensed | Messy 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
| Problem | Cause | Fix |
|---|---|---|
CONFLICT messages | Both branches modified same lines | See Conflict Resolution |
Already up to date | Nothing new to merge | Verify you're on the right branch |
| Unwanted merge commit | Used merge when you wanted rebase | Use git rebase instead (next page) |
| Merge created wrong history | Used fast-forward when --no-ff was better | Can'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-ffonmain— 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
- Rebase Explained — An alternative to merging for cleaner history
- Conflict Resolution — Handle merge conflicts confidently