Conflict Resolution
Quick Summary
A merge conflict occurs when Git can't automatically combine changes because two branches modified the same lines of the same file. Conflicts aren't errors — they're Git asking you to make a decision. Understanding the process makes them routine instead of scary.
Why Conflicts Happen
flowchart TD
A["main: line 10 = 'Hello World'"] --> B["Branch A changes line 10 to 'Hello Git'"]
A --> C["Branch B changes line 10 to 'Hello GitHub'"]
B --> D["Merge: CONFLICT!<br/>Which version should line 10 use?"]
C --> D
| Scenario | Conflict? |
|---|---|
| Two branches edit different files | ❌ No — auto-merged |
| Two branches edit different lines of the same file | ❌ No — auto-merged |
| Two branches edit the same line of the same file | ✅ Yes — conflict |
| One branch deletes a file, the other modifies it | ✅ Yes — conflict |
| Both branches add a file with the same name | ✅ Yes — conflict |
Reading Conflict Markers
When a conflict occurs, Git marks the affected sections in the file:
<<<<<<< HEAD
const greeting = "Hello Git";
=======
const greeting = "Hello GitHub";
>>>>>>> feature/github
| Marker | Meaning |
|---|---|
<<<<<<< HEAD | Start of your changes (current branch) |
======= | Divider between the two versions |
>>>>>>> feature/github | End of their changes (incoming branch) |
Three-Way Conflict (with diff3)
Enable the diff3 style for even clearer conflict markers:
git config --global merge.conflictstyle diff3
<<<<<<< HEAD
const greeting = "Hello Git";
||||||| common ancestor
const greeting = "Hello World";
=======
const greeting = "Hello GitHub";
>>>>>>> feature/github
Now you see the original version too, making the intent of each change much clearer.
Step-by-Step Resolution
1. Identify Conflicted Files
git status
On branch main
You have unmerged paths.
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/app.js
both modified: src/config.js
2. Open and Resolve Each File
- Manual (text editor)
- Keep ours
- Keep theirs
- VS Code
Open the file, find the conflict markers, and edit to the desired result:
// Before: conflict markers
<<<<<<< HEAD
const greeting = "Hello Git";
=======
const greeting = "Hello GitHub";
>>>>>>> feature/github
// After: your resolution (remove ALL markers)
const greeting = "Hello Git and GitHub";
# Keep the current branch's version entirely
git checkout --ours src/app.js
# Keep the incoming branch's version entirely
git checkout --theirs src/app.js
VS Code shows an interactive UI with buttons:
| Button | Action |
|---|---|
| Accept Current Change | Keep HEAD version |
| Accept Incoming Change | Keep the branch version |
| Accept Both Changes | Concatenate both |
| Compare Changes | Side-by-side diff |
3. Mark as Resolved
git add src/app.js
git add src/config.js
4. Complete the Merge
git commit
# Git auto-generates a merge commit message
Or if you want a custom message:
git commit -m "Merge feature/github: resolve greeting conflict"
Abort the Merge
# Cancel the entire merge and go back to before
git merge --abort
Conflict Resolution Strategies
| Strategy | When to Use |
|---|---|
Keep ours (--ours) | Your version is correct, theirs is outdated |
Keep theirs (--theirs) | Their version is correct, yours is outdated |
| Manual edit | Both changes are partially correct — combine them |
| Accept both | Both changes are independent additions |
| Rewrite entirely | Neither version is optimal — write something new |
Common Conflict Scenarios
Scenario 1: Both Edit the Same Function
<<<<<<< HEAD
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
=======
function calculateTotal(items) {
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
return subtotal * 1.1; // Add 10% tax
}
>>>>>>> feature/tax
Resolution: Combine both changes:
function calculateTotal(items) {
const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0);
return subtotal * 1.1; // Add 10% tax
}
Scenario 2: Delete vs Modify Conflict
CONFLICT (modify/delete): src/legacy.js deleted in feature/cleanup
and modified in HEAD. Version HEAD of src/legacy.js left in tree.
Resolution: Decide whether the file should exist. Then:
# Keep the file
git add src/legacy.js
# Or delete it
git rm src/legacy.js
Preventing Conflicts
| Practice | How It Helps |
|---|---|
| Pull frequently | Stay up-to-date with main, reducing divergence |
| Small, focused branches | Less code changed = fewer potential conflicts |
| Communicate with team | Avoid working on the same files simultaneously |
| Use feature flags | Changes can coexist without modifying the same lines |
| Rebase before merge | Resolves conflicts earlier, one commit at a time |
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Can't find conflict markers | Editor hid them or wrong file | Search for <<<<<<< in the file |
| Merge commit has wrong resolution | Resolved incorrectly | git revert <merge-commit>, then re-merge |
| Conflicts in binary files | Git can't diff binaries | Choose one version: git checkout --ours/--theirs <file> |
| Too many conflicts to resolve | Massive branch divergence | Consider rebasing commit-by-commit |
Best Practices
- Enable
diff3conflict style — seeing the common ancestor makes resolution much easier - Use a visual merge tool — VS Code, IntelliJ, or
git mergetool - Pull/rebase regularly — smaller, more frequent syncs = smaller conflicts
- Don't panic — conflicts are normal;
git merge --abortis always available - Test after resolving — run your tests before committing the resolution
What's Next
- Remotes, Push, Pull & Fetch — Work with remote repositories
- Undoing Changes — Undo commits, staged changes, and merges