Merge Strategies & Conflict Resolution
Module 02 75 min
Section Objectives
- Understand the different merge strategies (fast-forward, 3-way, squash)
- Perform a merge without conflicts
- Resolve merge conflicts with confidence
- Choose the right merge strategy for each situation
Types of Merges
1. Fast-Forward Merge
A fast-forward merge occurs when the target branch has no new commits since the branch diverged. Git simply moves the pointer forward — no merge commit is created.
After git merge feature/login (fast-forward):
# Perform a fast-forward merge
git switch main
git merge feature/login
# Force a merge commit even if fast-forward is possible
git merge --no-ff feature/login
2. Three-Way Merge (Recursive)
When both branches have diverged (each has new commits), Git creates a merge commit with two parents.
git switch main
git merge feature/auth
# Opens editor for merge commit message
3. Squash Merge
Condenses all commits from a branch into one single commit on the target branch:
git switch main
git merge --squash feature/messy-history
git commit -m "feat: complete payment feature"
# All changes from feature/messy-history → single clean commit
Comparison of Merge Strategies
| Strategy | Creates merge commit? | Preserves history? | When to use |
|---|---|---|---|
| Fast-forward | No | Yes (linear) | Short-lived feature branches |
| 3-way merge | Yes | Yes (all commits) | Long-lived branches |
--no-ff | Always yes | Yes | When you want to mark the merge |
| Squash | No (1 commit) | No (squashed) | Messy history, clean main |
Performing a Merge
# Standard workflow
git switch main # Switch to target branch
git pull # Ensure it's up to date
git merge feature/login # Merge the feature branch
# View the result
git log --oneline --graph
# After merge, delete the feature branch (optional)
git branch -d feature/login
Merge Conflicts
A conflict occurs when the same lines in the same file were modified differently on both branches.
Simulating a Conflict
# Initial state
echo "Hello World" > greeting.txt
git add greeting.txt
git commit -m "initial greeting"
# Branch 1: modify the greeting
git switch -c branch-alice
echo "Hello Alice!" > greeting.txt
git commit -am "feat: Alice's greeting"
# Branch 2: different modification
git switch main
echo "Hello Bob!" > greeting.txt
git commit -am "feat: Bob's greeting"
# Merge: CONFLICT!
git merge branch-alice
# CONFLICT (content): Merge conflict in greeting.txt
Conflict Markers
Git marks conflicts with special markers in the file:
<<<<<<< HEAD
Hello Bob!
=======
Hello Alice!
>>>>>>> branch-alice
| Section | Meaning |
|---|---|
<<<<<<< HEAD | Start of YOUR version (current branch) |
======= | Separator |
>>>>>>> branch-alice | End of INCOMING version (branch being merged) |
Resolving Conflicts
Method 1: Manual Resolution
# 1. Open the conflicted file
# 2. Edit it to remove the markers and keep what you want
# 3. Final result (your choice):
echo "Hello Alice and Bob!" > greeting.txt
# 4. Mark as resolved
git add greeting.txt
# 5. Complete the merge
git commit # Opens editor with pre-filled merge commit message
# or
git commit -m "merge: resolve greeting conflict - include both names"
Method 2: git checkout --ours/--theirs
# Keep only YOUR version (current branch)
git checkout --ours greeting.txt
git add greeting.txt
# Keep only the INCOMING version (branch being merged)
git checkout --theirs greeting.txt
git add greeting.txt
Method 3: VS Code Merge Editor
VS Code has a built-in merge editor that makes it visual:
- Open the conflicted file in VS Code
- You'll see "Accept Current Change" / "Accept Incoming Change" / "Accept Both" buttons
- Click the appropriate option
- Save the file
git addandgit commit
Method 4: git mergetool
# Open a graphical merge tool
git mergetool
# Configure VS Code as merge tool
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
Aborting a Merge
# Abort a merge in progress (restore pre-merge state)
git merge --abort
# Check that the state is clean
git status
Strategies for Preventing Conflicts
| Strategy | Description |
|---|---|
| Frequent small merges | Merge often to avoid large divergences |
| Clear responsibilities | Each developer owns different files |
| Feature flags | Merge code without activating it |
| Communication | Tell your team when modifying shared files |
Regular git pull | Stay up to date with main |
| Good code structure | Single Responsibility Principle → smaller files |
Common Conflict Scenarios
Two developers modified the same function
<<<<<<< HEAD
def calculate_price(item, tax_rate=0.20):
return item['price'] * (1 + tax_rate)
=======
def calculate_price(item, discount=0):
return item['price'] * (1 - discount)
>>>>>>> feature/discounts
Resolution: Combine both features:
def calculate_price(item, tax_rate=0.20, discount=0):
return item['price'] * (1 - discount) * (1 + tax_rate)
JSON configuration file conflict
<<<<<<< HEAD
{
"version": "2.0",
"port": 3000
=======
{
"version": "2.0",
"port": 8080,
"debug": true
>>>>>>> feature/debug-mode
Resolution: Combine the two:
{
"version": "2.0",
"port": 3000,
"debug": true
}
Summary of Key Commands
| Command | Description |
|---|---|
git merge <branch> | Merge a branch |
git merge --no-ff <branch> | Force merge commit |
git merge --squash <branch> | Squash and merge |
git merge --abort | Cancel merge in progress |
git checkout --ours <file> | Keep our version |
git checkout --theirs <file> | Keep their version |
git mergetool | Open merge tool |
Next Steps
- git rebase — Linear History
- Hands-on Lab TP3 — Practice branches and conflicts