Git & GitHub

Resolving Merge Conflicts

13 min Lesson 28 of 35

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:

  1. What do the two versions do differently?
  2. How would you resolve this conflict?
  3. What tests would you run after resolving?

Solution:

  1. Differences: HEAD version gives 10% discount for orders over $100. Incoming version gives 15% discount for premium users (regardless of total).
  2. 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; }
  3. 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!