Resolving Merge Conflicts in Git: A Complete Guide
Master merge conflict resolution — understand conflict markers, resolution strategies, and tools for handling conflicts efficiently in team environments.
Introduction
Merge conflicts are inevitable in collaborative development. They occur when Git cannot automatically reconcile changes from two branches that modified the same lines of the same file. Rather than being errors, conflicts are Git’s way of saying “I need human judgment here.”
The fear of conflicts often leads teams to avoid branching, which defeats the purpose of using Git. Experienced developers don’t avoid conflicts — they resolve them quickly and confidently. This guide teaches you how to read conflict markers, choose resolution strategies, and use tools that make the process painless.
Understanding conflict resolution is also essential for code quality. A conflict is often a signal that two developers are working on overlapping concerns, and resolving it well requires understanding both changes in context.
When to Use / When Not to Use
When to Resolve Conflicts
- Feature integration — merging completed features that touched shared code
- Release stabilization — integrating hotfixes into branches that have moved forward
- Rebase operations — replaying commits onto a changed base
- Cherry-picking — applying individual commits to different branch contexts
- Pull updates — incorporating upstream changes into your working branch
When Not to Resolve Conflicts Blindly
- Without understanding both sides — never accept “theirs” or “ours” without reviewing
- In binary files — resolve by choosing which version to keep, not by editing
- During a rushed deployment — conflicts deserve careful attention; don’t rush
- Without testing — always run tests after resolving conflicts
Core Concepts
When Git encounters a conflict, it marks the conflicting regions in the file with special markers. Understanding these markers is the first step to resolution.
<<<<<<< HEAD
console.log("Hello from main branch");
=======
console.log("Hello from feature branch");
>>>>>>> feature-x
The markers divide the conflict into three sections:
<<<<<<< HEAD— your current branch’s version=======— the separator between versions>>>>>>> feature-x— the incoming branch’s version
graph TD
Merge["git merge feature-x"] --> Conflict{"Conflict detected?"}
Conflict -->|No| Done["Merge complete"]
Conflict -->|Yes| Marked["Files marked with conflict markers"]
Marked --> Status["git status shows 'both modified'"]
Status --> Choose{"Resolution approach"}
Choose -->|Manual| Edit["Edit file, remove markers"]
Choose -->|Tool| Mergetool["git mergetool"]
Choose -->|Accept ours| Ours["git checkout --ours file"]
Choose -->|Accept theirs| Theirs["git checkout --theirs file"]
Edit --> Stage["git add file"]
Mergetool --> Stage
Ours --> Stage
Theirs --> Stage
Stage --> Commit["git commit"]
Commit --> Done
Architecture or Flow Diagram
flowchart LR
A["Branch A:\nfunction greet() {\n return 'Hello';\n}"] --> Merge{"git merge\nBranch B"}
B["Branch B:\nfunction greet() {\n return 'Hi there';\n}"] --> Merge
Merge --> Conflict["CONFLICT:\n<<<<<<< HEAD\n return 'Hello';\n=======\n return 'Hi there';\n>>>>>>> branch-b"]
Conflict --> Options{"Resolution"}
Options -->|Keep A| ResultA["return 'Hello';"]
Options -->|Keep B| ResultB["return 'Hi there';"]
Options -->|Combine| ResultC["return greeting || 'Hello';"]
Options -->|Rewrite| ResultD["return getGreeting();"]
Step-by-Step Guide / Deep Dive
Identifying Conflicts
# After a failed merge, check status
git status
# Output shows conflicted files:
# both modified: src/utils/helpers.js
# both modified: src/components/App.tsx
# List only conflicted files
git diff --name-only --diff-filter=U
Reading Conflict Markers
A conflict can involve more than two versions when using --conflict=merge:
<<<<<<< ours
console.log("Version from main");
||||||| base
console.log("Original version");
=======
console.log("Version from feature");
>>>>>>> theirs
This three-way view shows the original base, your version, and the incoming version — invaluable for understanding what changed on each side.
Manual Resolution
# 1. Open the conflicted file in your editor
# 2. Find the conflict markers (<<<<<<<, =======, >>>>>>>)
# 3. Edit the file to produce the desired result
# 4. Remove all conflict markers
# 5. Stage the resolved file
git add src/utils/helpers.js
# 6. Complete the merge
git commit
Using Mergetool
# Launch configured merge tool
git mergetool
# Common tools you can configure:
git config merge.tool vscode
git config merge.tool vimdiff
git config merge.tool meld
git config merge.tool kdiff3
# Configure VS Code as mergetool
git config merge.tool vscode
git config mergetool.vscode.cmd 'code --wait $MERGED'
Resolution Strategies
# Accept your version entirely
git checkout --ours src/utils/helpers.js
git add src/utils/helpers.js
# Accept their version entirely
git checkout --theirs src/utils/helpers.js
git add src/utils/helpers.js
# Show conflict markers with base version
git config merge.conflictStyle diff3
git merge feature-x
# Abort the entire merge and start over
git merge --abort
Resolving During Rebase
# During git rebase, conflicts pause the rebase
# After resolving each conflict:
git add <resolved-files>
git rebase --continue
# Skip the current conflicting commit
git rebase --skip
# Abort the rebase entirely
git rebase --abort
Production Failure Scenarios + Mitigations
| Scenario | Impact | Mitigation |
|---|---|---|
| Resolving by accepting wrong side | Lost functionality, bugs | Always review both sides; run tests after resolution |
| Missing conflict markers in binary files | Corrupted binary | Choose one version entirely; don’t attempt manual edit |
| Partial resolution (markers left) | Syntax errors, broken builds | Search for <<<<<< before committing; use pre-commit hooks |
| Conflict in lock files (package-lock.json) | Dependency resolution failures | Regenerate lock file after resolving package.json conflicts |
| Rebase conflict cascade | Multiple sequential conflicts | Consider merge instead of rebase for heavily diverged branches |
Emergency Recovery
# If you committed a bad resolution
git reset --soft HEAD~1
# Fix the resolution
git add <files>
git commit --amend
# If the merge is already pushed
git revert <merge-commit-sha>
# Re-merge with correct resolution
Trade-offs
| Approach | Pros | Cons |
|---|---|---|
| Manual resolution | Full control, understanding | Time-consuming, error-prone |
| Mergetool | Visual comparison, guided | Requires tool setup, learning curve |
| Accept ours/theirs | Fast | Loses potentially important changes |
| diff3 conflict style | Shows original base | More verbose, can be overwhelming |
| Abort and re-strategize | Clean slate | Loses partial resolution work |
| Regenerate lock files | Correct dependency tree | May update unrelated dependencies |
Implementation Snippets
# Pre-commit hook to catch leftover conflict markers
#!/bin/bash
if git diff --cached --check | grep -q "conflict marker"; then
echo "ERROR: Conflict markers detected in staged files"
exit 1
fi
# Find all files with conflict markers
grep -rl "<<<<<<< " --include="*.js" --include="*.ts" --include="*.py" .
# VS Code mergetool configuration
git config merge.tool vscode
git config mergetool.vscode.cmd 'code --wait $MERGED'
git config mergetool.keepBackup false
# Quick resolution workflow
git status --short | grep "^UU" | awk '{print $2}' | xargs -I {} code {}
# Resolve in editor, then:
git add -A && git commit -m "Resolve merge conflicts"
Observability Checklist
- Logs: Record conflict resolution in commit messages with context
- Metrics: Track conflict frequency per file (hotspot detection)
- Alerts: Alert on repeated conflicts in the same files
- Traces: Link conflict resolutions to PR discussions
- Dashboards: Display conflict rate trends per team member
Security/Compliance Notes
- Conflict resolutions should be reviewed — they’re a common place for accidental security regressions
- Never resolve conflicts by blindly accepting one side in security-critical files
- Audit conflict resolutions in regulated environments for compliance
- Be cautious with conflicts in authentication or authorization code
- Document resolution decisions for audit trails
Common Pitfalls / Anti-Patterns
- Leaving conflict markers — the most common mistake; always search for
<<<<<<<before committing - Accepting “theirs” without review — you might discard important security fixes or bug patches
- Resolving without running tests — conflicts often create subtle logic errors that only tests catch
- Ignoring the base version — understanding what the original code did helps you make the right choice
- Resolving lock file conflicts manually — regenerate them with
npm installor equivalent - Panic and abort everything — conflicts are normal; learn to resolve them rather than avoiding merges
Quick Recap Checklist
- Identify conflicted files with
git status - Read conflict markers:
<<<<<<<,=======,>>>>>>> - Use
diff3style to see the base version - Resolve manually or with
git mergetool - Stage resolved files with
git add - Complete merge with
git commit - Run tests after every conflict resolution
- Search for leftover markers before pushing
Interview Q&A
<<<<<<<, =======, and >>>>>>> represent?<<<<<<< HEAD marks the start of your current branch's version. ======= separates the two conflicting versions. >>>>>>> branch-name marks the end of the incoming branch's version. All markers must be removed before the file is valid.
git checkout --ours and git checkout --theirs?--ours keeps the version from your current branch (the branch you're merging into). --theirs keeps the version from the incoming branch (the branch being merged). Both commands stage the chosen version, after which you run git add and git commit.
diff3 conflict style and why is it useful?Run git config merge.conflictStyle diff3. This adds a ||||||| section showing the original base version between the two conflicting versions. It's useful because seeing what the code looked like before either change helps you understand the intent of both modifications and make a better resolution decision.
package-lock.json or similar lock files?Don't resolve lock file conflicts manually. After resolving package.json conflicts, delete the lock file and regenerate it: rm package-lock.json && npm install. This ensures the lock file is consistent with the resolved dependency versions.
git merge --abort do and when should you use it?It cancels the merge operation and restores the repository to its pre-merge state. Use it when the conflict is too complex to resolve on the spot, when you realize you merged the wrong branch, or when you want to try a different strategy (like rebase instead of merge).
Architecture: How Git Detects Conflicts
Git detects conflicts through a three-way merge algorithm. It finds the merge base (common ancestor), then computes diffs from the base to each branch. When both branches modify the same lines, Git cannot determine which change to keep.
graph TD
Base["Merge Base\n(common ancestor)"] -->|diff A| BranchA["Branch A changes\nlines 10-15"]
Base -->|diff B| BranchB["Branch B changes\nlines 12-18"]
BranchA --> Overlap{"Overlapping\nchanges?"}
BranchB --> Overlap
Overlap -->|Yes, same lines| Conflict["CONFLICT\nManual resolution required"]
Overlap -->|No, different lines| AutoMerge["Auto-merged\nno conflict"]
Overlap -->|Adjacent lines| AutoMerge
classDef node fill:#16213e,color:#00fff9
class Base,BranchA,BranchB,Conflict,AutoMerge node
The merge base is found using Git’s commit graph traversal. If branches have multiple common ancestors (criss-cross merges), Git’s recursive strategy creates a virtual merge base.
Production Failure: Incorrect Conflict Resolution Causing Silent Data Corruption
Scenario: A developer resolves a merge conflict in a configuration file by accepting “theirs” without reviewing. The incoming branch had disabled a security validation check that the current branch relied on. The merge completes cleanly, tests pass (because the test suite didn’t cover this edge case), and the change deploys to production.
Impact: Security validation silently disabled in production. No compile errors, no test failures — the bug only manifests when an attacker exploits the missing validation.
Mitigation:
- Never accept “ours” or “theirs” without reading both sides
- Enable
diff3conflict style to see the original base version - Run full test suites after every conflict resolution
- Require a second reviewer for merges with conflicts in security-critical files
- Use
git diff --checkto verify no conflict markers remain
# Always use diff3 to see the base version
git config --global merge.conflictStyle diff3
# After resolving, verify no markers remain
git diff --check
grep -rn "<<<<<<< \|=======\|>>>>>>>" --include="*.py" --include="*.js" --include="*.ts" .
Implementation: Mergetool Configuration
Visual merge tools make conflict resolution significantly easier by showing all three versions side by side:
# VS Code as mergetool (recommended for most teams)
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
git config --global mergetool.keepBackup false
# Meld (excellent free 3-way merge tool)
git config --global merge.tool meld
git config --global mergetool.keepBackup false
# kdiff3 (powerful, handles complex conflicts well)
git config --global merge.tool kdiff3
git config --global mergetool.keepBackup false
# Usage
git mergetool # launches configured tool for each conflict
git mergetool --tool=meld # override tool for this session
Tool comparison:
| Tool | Platform | 3-Way View | Learning Curve | Cost |
|---|---|---|---|---|
| VS Code | All | Yes (with extension) | Low | Free |
| Meld | Linux/Windows/macOS | Yes | Low | Free |
| kdiff3 | All | Yes | Medium | Free |
| Beyond Compare | All | Yes | Low | Paid |
| vimdiff | All | Yes (terminal) | High | Free |
Summary Checklist
- Conflicts occur when both branches modify the same lines
- Use
diff3style to see the base version during resolution - Always review both sides — never blindly accept “ours” or “theirs”
- Configure a visual mergetool (VS Code, Meld, kdiff3)
- Run
git diff --checkto verify no markers remain - Run full test suite after every conflict resolution
- Get a second review for conflicts in security-critical files
- Use
git mergetoolfor visual resolution instead of manual editing
Resources
Category
Related Posts
Git Merge and Merge Strategies Explained
Deep dive into Git merge strategies — fast-forward, three-way, recursive, ours, subtree. Learn when each strategy applies and how to control merge behavior.
Git Remote Management: Adding, Removing, and Configuring Remotes
Master git remote operations — adding, removing, renaming remotes, managing multiple remotes, and configuring remote URLs for effective collaboration.
Pull Requests and Code Review: Git Collaboration Best Practices
Master pull request workflows and code review — writing effective PR descriptions, review best practices, collaboration patterns, and team workflows.