Resolving Merge Conflicts
Merge conflicts are a natural part of collaborative development. They occur when Git cannot automatically merge changes from different branches. In this lesson, you'll learn how to understand, resolve, and prevent merge conflicts with confidence.
What Are Merge Conflicts?
A merge conflict occurs when two branches have made different changes to the same part of a file, and Git cannot determine which change to keep.
Remember: Conflicts are not errors - they're Git asking you to make a decision about which changes to keep.
Common Scenarios That Cause Conflicts
1. Same Line, Different Changes
Branch A: $price = 100;
Branch B: $price = 150;
→ Conflict! Git doesn't know which price to use
2. One Deletes, One Modifies
Branch A: Deletes function
Branch B: Modifies same function
→ Conflict! Git doesn't know whether to delete or keep
3. Moved Files
Branch A: Renames file to new-name.php
Branch B: Edits old-name.php
→ Conflict! Git may lose track of changes
4. Binary Files
Branch A: Updates logo.png
Branch B: Updates logo.png differently
→ Conflict! Cannot merge binary files automatically
Recognizing a Merge Conflict
Git will notify you when a conflict occurs:
$ git merge feature/new-checkout
Auto-merging app/Http/Controllers/CheckoutController.php
CONFLICT (content): Merge conflict in app/Http/Controllers/CheckoutController.php
Automatic merge failed; fix conflicts and then commit the result.
$ git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: app/Http/Controllers/CheckoutController.php
Important: Do not panic when you see conflicts! This is normal. Take a deep breath and follow the resolution steps.
Conflict Markers Explained
Git marks conflicts in your files with special markers:
<<<<<<< HEAD
$taxRate = 0.08; // Current branch (main)
=======
$taxRate = 0.10; // Incoming branch (feature/new-checkout)
>>>>>>> feature/new-checkout
Conflict Marker Breakdown:
<<<<<<< HEAD
↓ Your current branch's version (what you have now)
=======
↓ Separator between versions
>>>>>>> branch-name
↓ The incoming branch's version (what you're merging in)
Resolving Conflicts Manually
Step-by-step conflict resolution process:
Step 1: Identify Conflicted Files
$ git status
Unmerged paths:
both modified: app/Http/Controllers/CheckoutController.php
both modified: resources/views/checkout.blade.php
Step 2: Open Each File and Find Conflict Markers
Look for <<<<<<<, =======, and >>>>>>> markers
Step 3: Decide Which Changes to Keep
Options:
a) Keep current branch version (HEAD)
b) Keep incoming branch version
c) Keep both changes
d) Write new code combining both
Step 4: Remove Conflict Markers
Delete <<<<<<<, =======, and >>>>>>> lines
Keep only the final code you want
Step 5: Mark as Resolved
$ git add app/Http/Controllers/CheckoutController.php
Step 6: Complete the Merge
$ git commit -m "Merge feature/new-checkout, resolve tax rate conflict"
Conflict Resolution Examples
Example 1: Choose One Side
// BEFORE (with conflict)
<<<<<<< HEAD
$shippingCost = 5.00;
=======
$shippingCost = 7.50;
>>>>>>> feature/update-shipping
// AFTER (resolved - keep incoming change)
$shippingCost = 7.50;
Example 2: Keep Both Changes
// BEFORE
<<<<<<< HEAD
use App\Models\Order;
=======
use App\Models\Payment;
>>>>>>> feature/add-payment
// AFTER (keep both imports)
use App\Models\Order;
use App\Models\Payment;
Example 3: Combine Logic
// BEFORE
<<<<<<< HEAD
if ($order->total > 100) {
$discount = 10;
}
=======
if ($order->isPremium()) {
$discount = 15;
}
>>>>>>> feature/premium-discount
// AFTER (combine conditions)
if ($order->total > 100 && $order->isPremium()) {
$discount = 15;
} elseif ($order->total > 100) {
$discount = 10;
}
Pro Tip: After resolving, always test your code! Conflicts can introduce subtle bugs if not resolved carefully.
Using VS Code Merge Editor
VS Code provides a visual interface for resolving conflicts:
VS Code Conflict Interface:
When you open a conflicted file, you'll see:
┌─────────────────────────────────────────┐
│ Accept Current Change | Accept Incoming │
│ Accept Both Changes | Compare Changes │
├─────────────────────────────────────────┤
│ <<<<<<< HEAD (Current Change) │
│ $price = 100; │
│ ======= │
│ $price = 150; // (Incoming Change) │
│ >>>>>>> feature/update-pricing │
└─────────────────────────────────────────┘
Click Options:
• Accept Current Change → Keep HEAD version
• Accept Incoming Change → Keep branch version
• Accept Both Changes → Keep both (one after another)
• Compare Changes → Side-by-side comparison
Tip: VS Code's 3-way merge editor (available in recent versions) shows base, current, and incoming side-by-side for easier comparison.
Using Git GUI Tools
Popular Merge Tools:
1. GitKraken
- Visual conflict resolution
- Drag-and-drop interface
- Great for beginners
2. Sourcetree
- Free from Atlassian
- Built-in merge tool
- Shows conflict context
3. Beyond Compare
- Professional diff/merge tool
- 3-way merge view
- Paid but powerful
4. P4Merge (Perforce)
- Free 3-way merge tool
- Clean interface
- Cross-platform
Configure Git to Use External Tool:
# Set up P4Merge as merge tool
git config --global merge.tool p4merge
git config --global mergetool.p4merge.path "/path/to/p4merge"
# Launch merge tool for conflicts
git mergetool
Aborting a Merge
If you want to cancel the merge and start over:
# Abort the merge (before commit)
$ git merge --abort
# Returns to state before merge started
# No changes committed, working directory restored
When to Abort:
- Too many conflicts to resolve now
- Wrong branch was merged
- Need to discuss with team first
- Want to pull latest changes first
Warning: git merge --abort only works before you complete the merge commit. After committing, use git reset instead (but be careful!).
Handling Conflicts During Rebase
Rebase conflicts are resolved similarly but have different commands:
$ git rebase main
CONFLICT (content): Merge conflict in app/Services/PaymentService.php
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
Rebase Conflict Resolution:
# 1. Resolve conflicts in files (same as merge)
# 2. Stage resolved files
$ git add app/Services/PaymentService.php
# 3. Continue rebase (not commit!)
$ git rebase --continue
# 4. Repeat for each conflicted commit
Rebase-Specific Commands:
git rebase --continue # Move to next commit
git rebase --skip # Skip current commit
git rebase --abort # Cancel rebase
Preventing Merge Conflicts
Best Practices to Minimize Conflicts:
1. Communicate with Team
- Discuss who's working on what
- Avoid editing same files simultaneously
- Use Slack/Discord to coordinate
2. Pull Frequently
- git pull origin main daily
- Stay synchronized with main branch
- Smaller diffs = fewer conflicts
3. Keep Branches Short-Lived
- Merge feature branches quickly
- Don't let branches drift too far
- Trunk-Based Development helps
4. Break Large Changes into Smaller PRs
- Easier to merge incrementally
- Conflicts are smaller and isolated
- Faster reviews
5. Use Code Formatting Tools
- PHP CS Fixer, Prettier, etc.
- Consistent formatting reduces conflicts
- Run before committing
6. Modular Code Architecture
- Separate concerns into different files
- Reduces chance of editing same file
- Better separation of responsibilities
7. Rebase Feature Branches Regularly
- Keep feature branch up-to-date with main
- Resolve conflicts incrementally
- Easier than one big conflict at the end
git checkout feature/my-feature
git fetch origin
git rebase origin/main
Pro Tip: If you're working on a file someone else is actively editing, consider pair programming or taking turns to avoid conflicts entirely.
Testing After Conflict Resolution
Always verify your merged code works correctly:
Post-Merge Testing Checklist:
☐ Run automated tests
php artisan test
☐ Check application still runs
php artisan serve
Visit affected pages in browser
☐ Test specific functionality changed
Test checkout process if you merged payment code
☐ Check for syntax errors
php artisan route:list (Laravel check)
php -l file.php (syntax check)
☐ Review the final merged code
git diff HEAD~1
Make sure it makes sense
☐ Get team member to sanity-check
Quick review of conflict resolution
Advanced: Using git rerere
rerere = "Reuse Recorded Resolution"
# Enable rerere globally
git config --global rerere.enabled true
How it Works:
- Git records how you resolved conflicts
- If same conflict appears again, Git auto-resolves it
- Useful when rebasing or cherry-picking repeatedly
Example:
1. Resolve conflict in file.php
2. Git records your resolution
3. Later, same conflict occurs during rebase
4. Git automatically applies your previous resolution
5. You just verify and continue
Communication During Conflicts
When to Talk to Team:
❌ Don't Silently Resolve Everything
- If unsure, ask the original author
- Complex logic conflicts need discussion
- Don't delete someone's code without asking
✅ Good Communication:
"Hey @john, I'm merging my branch and got a conflict
in PaymentController. You changed calculateTax() to
use $rate * 1.1, but I changed it to use $rate * 1.15.
Which is correct, or should we combine them?"
✅ Document in Commit Message:
"Merge feature/tax-update, resolved conflict in
calculateTax() by keeping the 1.15 multiplier as
agreed with @john in Slack"
Practice Exercise:
Scenario: You're merging a feature branch and encounter this conflict:
<<<<<<< HEAD
public function calculateDiscount($total) {
if ($total > 100) {
return $total * 0.10;
}
return 0;
}
=======
public function calculateDiscount($total, $user) {
if ($user->isPremium()) {
return $total * 0.15;
}
return 0;
}
>>>>>>> feature/premium-users
Tasks:
- What do the two versions do differently?
- How would you resolve this conflict?
- What tests would you run after resolving?
Solution:
- Differences: HEAD version gives 10% discount for orders over $100. Incoming version gives 15% discount for premium users (regardless of total).
- Resolution: Combine both conditions:
public function calculateDiscount($total, $user = null) {
// Premium users get 15% discount
if ($user && $user->isPremium()) {
return $total * 0.15;
}
// Regular users get 10% on orders over $100
if ($total > 100) {
return $total * 0.10;
}
return 0;
}
- Tests:
- Test non-premium user with $50 order (expect $0)
- Test non-premium user with $150 order (expect $15)
- Test premium user with $50 order (expect $7.50)
- Test premium user with $150 order (expect $22.50)
Summary
In this lesson, you learned:
- Merge conflicts occur when Git cannot automatically merge changes
- Conflict markers (<<<<<<<, =======, >>>>>>>) show competing versions
- Resolve conflicts by choosing one version, keeping both, or combining them
- Use VS Code merge editor or GUI tools for visual conflict resolution
- Always test code after resolving conflicts
- Prevent conflicts through communication, frequent pulls, and short-lived branches
- Use
git merge --abort to cancel a problematic merge
Next Up: In the next lesson, we'll explore Git Hooks to automate quality checks and enforce standards!