What's Actually Happening
Git merge conflicts persist even after attempts to resolve them. The conflict markers remain, or Git reports conflicts when none are visible.
The Error You'll See
Persistent conflict:
```bash $ git status
Unmerged paths: (use "git add <file>..." to mark resolution) both modified: src/main.js ```
Conflict markers visible:
function hello() {
<<<<<<< HEAD
console.log("Hello from main");
=======
console.log("Hello from feature");
>>>>>>> feature-branch
}Cannot commit:
```bash $ git commit
error: Committing is not possible because you have unmerged files. ```
Resolution not recognized:
```bash $ git add src/main.js $ git status
Unmerged paths: both modified: src/main.js ```
Why This Happens
- 1.Markers not removed - Conflict markers still in file
- 2.Wrong merge tool - Tool didn't properly resolve
- 3.Index state corrupt - Git index has stale conflict state
- 4.Multiple conflicts - Additional conflicts in same file
- 5.Whitespace issues - Hidden characters in markers
- 6.Encoding problems - File encoding mismatch
Step 1: Check Current Status
```bash # Check merge status: git status
# List conflicted files: git diff --name-only --diff-filter=U
# Show conflict details: git diff --check
# Check for conflict markers: grep -r "^<<<<<<< " --include="*.js" .
# Check merge state: cat .git/MERGE_HEAD 2>/dev/null cat .git/MERGE_MSG 2>/dev/null
# Check if in merge: test -d .git/rebase-merge && echo "In rebase" test -f .git/MERGE_HEAD && echo "In merge" ```
Step 2: View Conflict Details
```bash # View conflicts in file: git diff src/main.js
# Show conflict with more context: git diff -U10 src/main.js
# View both versions: git show :2:src/main.js > ours.js # Our version git show :3:src/main.js > theirs.js # Their version
# Compare side by side: diff -y ours.js theirs.js
# Use git mergetool: git mergetool src/main.js
# List all conflict hunks: grep -n "^<<<<<<< " src/main.js ```
Step 3: Remove Conflict Markers
```bash # Find all conflict markers: grep -n "<<<<<<< |======= |>>>>>>>" src/main.js
# Example output: # 10:<<<<<<< HEAD # 15:======= # 20:>>>>>>> feature
# Manually edit to resolve: # Remove markers and choose correct code
# After editing, verify no markers: grep -c "<<<<<<< " src/main.js # Should return 0
# Automated cleanup (use with caution): sed -i '/^<<<<<<< /d; /^=======/d; /^>>>>>>> /d' src/main.js # Removes markers but keeps both versions!
# Better: Use awk for resolution: # Keep "ours" version: awk '/^<<<<<<< /,/^======= /{next} /^>>>>>>>/,/^<<<<<<< /{next} 1' src/main.js > resolved.js
# Keep "theirs" version: awk '/^<<<<<<< /,/^>>>>>>> /{if(/^======= /){next}} 1' src/main.js > resolved.js ```
Step 4: Proper Resolution Workflow
```bash # Step 1: View the conflict: git diff src/main.js
# Step 2: Edit the file to resolve: vim src/main.js # Remove conflict markers # Keep desired code
# Step 3: Verify resolution: git diff src/main.js # Should show no conflict markers
# Step 4: Stage the resolved file: git add src/main.js
# Step 5: Verify staged: git status # Should show "modified: src/main.js" (not unmerged)
# Step 6: Check all conflicts resolved: git diff --check # No output = all resolved
# Step 7: Complete the merge: git commit
# Or use -m for message: git commit -m "Merge feature-branch" ```
Step 5: Use Merge Tools
```bash # Configure merge tool: git config --global merge.tool vimdiff
# Available tools: # vimdiff, meld, kdiff3, tkdiff, code, etc.
# Run merge tool: git mergetool src/main.js
# For VS Code: git config --global merge.tool code git config --global mergetool.code.cmd 'code --wait $MERGED'
# Vimdiff layout: # +----------------+----------------+----------------+ # | LOCAL | BASE | REMOTE | # +----------------+----------------+----------------+ # | MERGED | # +-------------------------------------------------+
# In vimdiff: # :diffg RE " get from REMOTE # :diffg BA " get from BASE # :diffg LO " get from LOCAL # :wqa " write and quit
# Use specific tool: git mergetool --tool=vimdiff src/main.js ```
Step 6: Fix Index State
```bash # If file shows unmerged after resolution:
# Check file stages: git ls-files -s src/main.js
# Output shows stages: # 100644 abc123... 1 src/main.js # Stage 1: base # 100644 def456... 2 src/main.js # Stage 2: ours # 100644 ghi789... 3 src/main.js # Stage 3: theirs
# Remove unmerged entries: git rm --cached src/main.js
# Re-add resolved file: git add src/main.js
# Check stage 0 (resolved): git ls-files -s src/main.js # Should show only stage 0
# Or use git update-index: git update-index --cacheinfo 100644,$(git hash-object -w src/main.js),src/main.js ```
Step 7: Handle Binary Conflicts
```bash # Binary file conflicts show differently: $ git status both modified: image.png
# View versions: git show :2:image.png > image-ours.png git show :3:image.png > image-theirs.png
# Choose one version: git checkout --ours image.png # or git checkout --theirs image.png
# Add resolved binary: git add image.png
# Use specific merge driver: echo "*.png binary" >> .gitattributes echo "*.png merge=keepTheir" >> .gitattributes git config merge.keepTheir.name "always keep theirs" git config merge.keepTheir.driver "cp %B %A" ```
Step 8: Abort and Restart Merge
```bash # If merge is corrupted:
# Abort current merge: git merge --abort
# Or manually reset: git reset --hard HEAD
# Check state after abort: git status
# Restart merge: git merge feature-branch
# For rebase: git rebase --abort
# For cherry-pick: git cherry-pick --abort
# Clean up untracked files: git clean -fd ```
Step 9: Use Checkout Strategies
```bash # Accept ours for specific file: git checkout --ours src/main.js
# Accept theirs for specific file: git checkout --theirs src/main.js
# Accept ours for all conflicts: git checkout --ours . git add .
# Accept theirs for all conflicts: git checkout --theirs . git add .
# Use merge strategies: git merge -X ours feature-branch # Prefer ours git merge -X theirs feature-branch # Prefer theirs
# For specific paths with strategy: git merge-file -p src/main.js base.js theirs.js ```
Step 10: Prevent Future Conflicts
```bash # Enable rerere (reuse recorded resolution): git config --global rerere.enabled true
# View recorded resolutions: git rerere status
# Clear recorded resolutions: git rerere gc
# Use merge driver for specific files: # In .gitattributes: *.generated merge=generated
# Configure driver: git config merge.generated.name "generated merge" git config merge.generated.driver "echo 'Auto-generated' > %A"
# Frequent integration to reduce conflicts: # Merge main into feature regularly: git checkout feature-branch git merge main
# Use rebase instead: git checkout feature-branch git fetch origin git rebase origin/main ```
Git Merge Conflict Resolution Checklist
| Check | Command | Expected |
|---|---|---|
| Status | git status | No unmerged paths |
| Markers | grep "<<<<<<< " | No output |
| Staged | git diff --check | No conflicts |
| Index | git ls-files -s | Stage 0 only |
| Committed | git log | Merge commit |
| Pushed | git push | Successful |
Verify the Fix
```bash # After resolving conflict
# 1. Check no conflicts git diff --check // No output
# 2. Verify status clean git status // All changes staged, no unmerged
# 3. Check file content cat src/main.js | grep -c "<<<<<<< " // 0 (no markers)
# 4. Commit the merge git commit -m "Merge feature-branch" // Merge commit created
# 5. Verify commit git log --oneline -1 // Merge commit visible
# 6. Test code npm test // All tests pass ```
Related Issues
- [Fix Git Cherry Pick Conflict](/articles/fix-git-cherry-pick-conflict)
- [Fix Git Blame Not Working](/articles/fix-git-blame-not-working)
- [Fix Git Push Rejected Non Fast Forward](/articles/fix-git-push-rejected-non-fast-forward)