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:

javascript
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. 1.Markers not removed - Conflict markers still in file
  2. 2.Wrong merge tool - Tool didn't properly resolve
  3. 3.Index state corrupt - Git index has stale conflict state
  4. 4.Multiple conflicts - Additional conflicts in same file
  5. 5.Whitespace issues - Hidden characters in markers
  6. 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

CheckCommandExpected
Statusgit statusNo unmerged paths
Markersgrep "<<<<<<< "No output
Stagedgit diff --checkNo conflicts
Indexgit ls-files -sStage 0 only
Committedgit logMerge commit
Pushedgit pushSuccessful

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 ```

  • [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)