git add →
git commit (merge) or git rebase --continue (rebase).git status to list conflicted files;
git diff --diff-filter=U to see all unmerged hunks; git mergetool to open a
GUI resolver.<<<<<<<, =======,
>>>>>>>) still in files. Run git diff --check to catch
stray markers before committing.zdiff3 conflict style requires Git 2.35+. The
ort merge strategy (now default) requires Git 2.33+. As of Git 2.50,
recursive is a synonym for ort.git diff --check before
committing a merge resolution. Stray <<<<<<< markers in production code
cause syntax errors and silent data corruption.git merge,
--ours = your current branch (HEAD), --theirs = incoming. During
git rebase, --ours = the upstream branch you're rebasing onto,
--theirs = your commits being replayed. Getting this wrong silently discards the wrong
side's changes.git merge -s ours is destructive: This merge strategy creates a
merge commit but completely ignores the other branch's tree. It is not the same as
git merge -X ours (a strategy option that only resolves conflicted hunks with
HEAD). Confusing the two can silently discard an entire branch of work. [src1]--ours or --theirs entirely. [src1]package-lock.json,
yarn.lock, pnpm-lock.yaml by accepting one side and regenerating:
git checkout --theirs package-lock.json && npm install. Manual edits produce
invalid checksums.| # | Scenario | Command / Solution | Notes |
|---|---|---|---|
| 1 | List all conflicted files | git status or git diff --name-only --diff-filter=U |
Files marked UU (both modified) [src1] |
| 2 | Accept current branch for one file | git checkout --ours <file> |
"Ours" = branch you're on (HEAD) [src1, src6] |
| 3 | Accept incoming branch for one file | git checkout --theirs <file> |
"Theirs" = branch being merged in [src1, src6] |
| 4 | Accept ours for entire merge | git merge -X ours <branch> |
Silently resolves all conflicts with HEAD version [src1, src3] |
| 5 | Accept theirs for entire merge | git merge -X theirs <branch> |
Silently resolves all conflicts with incoming version [src1, src3] |
| 6 | Open GUI merge tool | git mergetool |
Uses configured tool (VS Code, vimdiff, meld) [src1, src7] |
| 7 | Show common ancestor in markers | git config merge.conflictStyle diff3 |
Adds ||||||| section with base version [src5, src6] |
| 8 | Use improved zdiff3 (recommended) | git config --global merge.conflictStyle zdiff3 |
Zealously removes common context from conflict markers (Git 2.35+) [src4, src5] |
| 9 | Enable rerere (auto-reuse resolutions) | git config --global rerere.enabled true |
Records and replays conflict resolutions [src2] |
| 10 | Abort a merge | git merge --abort |
Returns to pre-merge state [src1] |
| 11 | Abort a rebase | git rebase --abort |
Returns to pre-rebase state [src1] |
| 12 | Continue after rebase conflict | git add <file> && git rebase --continue |
Move to next commit in rebase [src1] |
| 13 | Skip conflicted commit in rebase | git rebase --skip |
Drops the conflicting commit entirely [src1] |
| 14 | View conflict history | git log --merge --oneline |
Shows commits that caused the conflict [src1] |
| 15 | AI-assisted resolution (VS Code) | Open conflicted file → "Resolve with Copilot" | Requires GitHub Copilot subscription; uses merge base as context [src7] |
START — Merge conflict encountered
├── Is this a merge or a rebase?
│ ├── MERGE
│ │ ├── Abort? → git merge --abort [src1]
│ │ ├── Accept one side for all files?
│ │ │ ├── Keep HEAD → git merge -X ours <branch> [src1, src3]
│ │ │ └── Keep incoming → git merge -X theirs <branch> [src1, src3]
│ │ └── Resolve file by file:
│ │ ├── One side wins → git checkout --ours/--theirs <file> [src1, src6]
│ │ ├── Need to combine → edit manually, remove markers [src6]
│ │ ├── Complex conflict → git mergetool (GUI) [src1, src7]
│ │ └── AI-assisted → VS Code "Resolve with Copilot" (experimental) [src7]
│ └── REBASE (conflicts appear per commit replayed)
│ ├── Abort? → git rebase --abort [src1]
│ ├── Resolve → git add <file> → git rebase --continue [src1]
│ └── Skip commit? → git rebase --skip [src1]
├── After resolving each file:
│ ├── git add <resolved-file>
│ ├── git diff --check (verify no stray markers)
│ └── git commit (merge) or git rebase --continue (rebase)
├── Lock file conflict?
│ └── Accept one side → regenerate → git checkout --theirs package-lock.json && npm install [src6]
└── Recurring conflicts? → git config --global rerere.enabled true [src2]
When a conflict occurs, Git inserts markers into the file. [src1, src5, src6]
<<<<<<< HEAD (current branch)
function greet(name) {
return `Hello, ${name}!`;
}
||||||| base (common ancestor — only with diff3/zdiff3 style)
function greet(name) {
return 'Hello, ' + name + '!';
}
======= (separator)
function greet(name, greeting = 'Hello') {
return `${greeting}, ${name}!`;
}
>>>>>>> feature/greeting-improvements (incoming branch)
Enable zdiff3 style to see the common ancestor (strongly recommended — used by
Git core developers): [src4, src5]
# Git 2.35+ — zealously removes common context from markers
git config --global merge.conflictStyle zdiff3
# Older Git — still better than default
git config --global merge.conflictStyle diff3
Verify: git config merge.conflictStyle → expected: zdiff3
git status # UU = unmerged
git diff --name-only --diff-filter=U # filenames only
git diff # all conflict hunks
git log --merge --oneline --left-right # contributing commits
Verify: git diff --name-only --diff-filter=U → lists all files needing
resolution
Edit each conflicted file, remove all conflict markers, and keep the correct code. [src1, src6]
# Edit file → remove all <<<<<<<, =======, >>>>>>> markers
git add path/to/resolved-file.js
git diff --check # verify no stray markers
git commit # finalise the merge
Verify: git diff --check → empty output means no stray markers
When one side's changes are definitively correct: [src1, src3, src6]
git checkout --ours src/config.json && git add src/config.json
git checkout --theirs src/api/client.js && git add src/api/client.js
# In rebase: ours/theirs are SWAPPED
# --ours = upstream branch; --theirs = your feature commit being replayed
Verify: git status → file should show as staged (green), no longer UU
A visual three-way merge tool is much faster for multi-hunk conflicts. [src1, src7]
# Configure VS Code as merge tool (includes 3-way merge editor + AI assist)
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
git config --global mergetool.keepBackup false
# Launch
git mergetool # all conflicted files
git mergetool path/to/file.js # specific file
Verify: git status → all conflicted files should be resolved
rerere records conflict resolutions and automatically replays them. [src2, src6]
git config --global rerere.enabled true
git config --global rerere.autoupdate true # auto-stage rerere fixes
git rerere status # show tracked conflicts
git rerere diff # show recorded resolution
git rerere forget file.js # discard a bad resolution
Verify: git config rerere.enabled → true
Full script: script-automated-conflict-detection-and-summary.sh (32 lines)
#!/bin/bash
echo "=== Git Conflict Summary ==="
echo "Branch: $(git branch --show-current)"
CONFLICT_FILES=$(git diff --name-only --diff-filter=U 2>/dev/null)
CONFLICT_COUNT=$(echo "$CONFLICT_FILES" | grep -c . 2>/dev/null || echo 0)
[ "$CONFLICT_COUNT" -eq 0 ] && echo "No conflicts" && exit 0
echo "$CONFLICT_COUNT conflicted file(s):"
while IFS= read -r file; do
[ -z "$file" ] && continue
HUNKS=$(grep -c "^<<<<<<< " "$file" 2>/dev/null || echo 0)
echo " $file ($HUNKS hunk(s))"
grep -n "^<<<<<<< \|^>>>>>>> \|^=======" "$file" 2>/dev/null | head -4 | sed 's/^/ /'
done <<< "$CONFLICT_FILES"
echo ""
echo "Resolution: edit files → git add <file> → git commit"
echo "GUI: git mergetool"
echo "Abort: git merge --abort | git rebase --abort"
Full script: python-pre-commit-hook-detect-conflict-markers.py (53 lines)
#!/usr/bin/env python3
"""
Pre-commit hook — blocks commits containing unresolved conflict markers.
Install: cp this_file .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
"""
import subprocess, sys, re
MARKERS = re.compile(r'^(<{7}|={7}|>{7}|\|{7})', re.MULTILINE)
def get_staged_files():
r = subprocess.run(['git','diff','--cached','--name-only','--diff-filter=ACMR'],
capture_output=True, text=True)
return [f.strip() for f in r.stdout.splitlines() if f.strip()]
def check_file(path):
r = subprocess.run(['git','show',f':{path}'], capture_output=True, text=True)
if r.returncode != 0: return []
return [(i+1, l.rstrip()) for i,l in enumerate(r.stdout.splitlines())
if MARKERS.match(l)]
found = False
for f in get_staged_files():
hits = check_file(f)
if hits:
found = True
print(f"Conflict markers in {f}:")
for lineno, line in hits:
print(f" Line {lineno}: {line}")
if found:
print("\nResolve conflicts first: git mergetool | git diff --check")
sys.exit(1)
sys.exit(0)
Full script: git-workflow-feature-branch-with-clean-rebase.sh (40 lines)
#!/bin/bash
FEATURE="${1:-$(git branch --show-current)}"
TARGET="${2:-main}"
echo "=== Rebasing $FEATURE onto $TARGET ==="
git fetch origin "$TARGET"
git checkout "$FEATURE"
git rebase "origin/$TARGET"
if [ $? -ne 0 ]; then
echo "Conflicts detected. For each conflicted file:"
echo " 1. Resolve in editor (remove <<<<<<< markers)"
echo " 2. git add <resolved-file>"
echo " 3. git rebase --continue"
echo ""
echo "Cancel: git rebase --abort"
echo "Skip commit: git rebase --skip"
echo "Tip: git config --global rerere.enabled true (auto-replay resolutions)"
exit 1
fi
echo "Rebase complete"
git checkout "$TARGET"
git merge --ff-only "$FEATURE"
echo "Fast-forward merge complete — push: git push origin $TARGET"
# BAD — during rebase, --ours = UPSTREAM branch (not your feature!) [src1, src6]
git rebase main
# conflict appears...
git checkout --ours conflicted-file.js # keeps main's version silently!
git add conflicted-file.js
git rebase --continue
# Your feature changes are lost with no warning!
# GOOD — understand the ours/theirs flip in rebase [src1, src6]
# Rebasing feature onto main:
# --ours = main (upstream)
# --theirs = your feature commit being replayed
git checkout --theirs conflicted-file.js
git add conflicted-file.js
git rebase --continue
# When in doubt, inspect explicitly:
git show :2:conflicted-file.js # "ours" (main)
git show :3:conflicted-file.js # "theirs" (feature commit)
# BAD — committing with stray markers [src1, src6]
git add src/utils.js
git commit -m "resolve conflict"
# <<<<<<< HEAD is now in production code!
# GOOD — verify no markers remain [src1, src6]
git diff --check
# Empty output means safe to commit
# Use pre-commit hook (see code example above) to automate this
# BAD — silently overwrites ALL conflicts with one side [src1, src3]
git merge -X theirs feature/big-refactor
# No review — even files you wanted to keep are overwritten
# GOOD — make intentional per-file decisions [src1, src3]
git merge feature/big-refactor # let it conflict
git checkout --theirs src/api/client.js # their refactor is better
git checkout --ours src/config/env.js # keep our environment config
# Manually resolve src/utils/helpers.js # both sides have useful changes
git add src/api/client.js src/config/env.js src/utils/helpers.js
git diff --check
git commit
# BAD — default conflict style shows only two sides [src4, src5]
# Without diff3/zdiff3, you can't see what the original code looked like:
# <<<<<<< HEAD
# const timeout = 5000;
# =======
# const timeout = 10000;
# >>>>>>> feature/performance
# Was it 3000 before? 5000? You don't know — forced to guess.
# GOOD — zdiff3 shows the common ancestor with minimized noise [src4, src5]
git config --global merge.conflictStyle zdiff3
# Now conflicts show the base version:
# <<<<<<< HEAD
# const timeout = 5000;
# ||||||| base
# const timeout = 3000;
# =======
# const timeout = 10000;
# >>>>>>> feature/performance
# Now you can see both sides changed from 3000 — make an informed decision.
ours/theirs meaning flips during rebase: During
git merge, ours = your current branch; during git rebase,
ours = the upstream branch you're rebasing onto. This trips up even experienced developers.
[src1, src6]merge.conflictStyle zdiff3,
you only see two sides. Enabling zdiff3 (Git 2.35+) often makes the correct resolution
immediately obvious. Git core developers use zdiff3 by default. [src4,
src5]main, the more conflicts accumulate. Keeping branches short (less than a week) and rebasing
daily dramatically reduces complexity. [src3]git rerere not enabled by default: This powerful feature is off by
default. Enable globally with rerere.enabled true. Use
--no-rerere-autoupdate if you want to review rerere's resolutions before they're staged.
[src2, src4]git mergetool creates
.orig backup files. Set mergetool.keepBackup false globally to suppress them,
and add *.orig to .gitignore. [src1, src7]git add after manual resolution: After editing a conflicted
file, you must git add it to mark it resolved. Just saving the file is not enough — Git
still considers it conflicted until staged. [src1, src6]recursive strategy removed in Git 2.50: As of Git 2.50, the
recursive merge strategy is a synonym for ort. If your CI scripts reference
-s recursive explicitly, they still work but are redirected. New scripts should use
ort directly. [src8]Full script: diagnostic-commands.sh (31 lines)
# Find conflicts
git status
git diff --name-only --diff-filter=U
git diff
git log --merge --oneline --left-right
# Inspect both sides of a conflict
git show :1:file.js # common ancestor
git show :2:file.js # ours (HEAD)
git show :3:file.js # theirs (incoming)
# Quick resolution
git checkout --ours file.js
git checkout --theirs file.js
git add file.js
# After resolving
git diff --check # verify no stray markers
git status # confirm no more UU files
# Abort
git merge --abort
git rebase --abort
git cherry-pick --abort
# rerere
git rerere status
git rerere diff
git rerere forget file.js
ls .git/rr-cache/
# Global configuration (recommended)
git config --global merge.conflictStyle zdiff3
git config --global rerere.enabled true
git config --global rerere.autoupdate true
git config --global merge.tool vscode
git config --global mergetool.keepBackup false
| Feature | Available Since | Notes |
|---|---|---|
git rerere |
Git 1.5.4 | Enable with rerere.enabled true [src2] |
merge.conflictStyle diff3 |
Git 1.6.1 | Shows common ancestor in conflict markers [src5] |
merge.conflictStyle zdiff3 |
Git 2.35 (Jan 2022) | Improved diff3 — zealously removes common context from markers [src4, src5] |
git checkout --ours/--theirs |
Git 1.6.1 | Per-file resolution [src1] |
git merge -X ours/theirs |
Git 1.7.0 | Whole-merge strategy option [src1] |
git mergetool |
Git 1.5.1 | Launch configured merge tool [src1] |
git merge --abort |
Git 1.7.4 | Before: git reset --merge [src1] |
git rebase --abort/--continue |
Git 1.7.8 | Safely cancel or continue a rebase [src1] |
ort merge strategy (default) |
Git 2.33 (Aug 2021) | Faster replacement for recursive; better rename detection [src8] |
recursive → ort redirect |
Git 2.50 (2025) | recursive is now a synonym for ort [src8] |
| VS Code AI-assisted merge | VS Code 1.105 (2025) | Requires GitHub Copilot subscription [src7] |
| Strategy | When | Why |
|---|---|---|
git merge |
Shared/public branches | Preserves full history; safe for collaborators [src3] |
git rebase |
Private feature branches | Linear history; cleaner PR diffs [src3] |
git merge -X ours/theirs |
Auto-generated files (lock files, changelogs) | One side is definitively correct [src1, src3] |
rerere |
Frequent rebases; long-lived branches | Avoid re-resolving the same conflict [src2] |
zdiff3 style |
Always | More context = better resolution decisions; recommended by Git core devs [src4, src5] |
git mergetool |
Complex multi-hunk conflicts | Visual three-way merge is faster [src7] |
| AI-assisted (VS Code Copilot) | Conflicts with clear intent on both sides | AI considers merge base + both branches; experimental, review output carefully [src7] |
git merge -X ours is not git merge -s ours:
-X ours is a strategy option (resolves conflicts with HEAD while incorporating the
merge). -s ours is a strategy (creates a merge commit but completely ignores the other
branch's tree). The latter is rarely what you want. [src1]package-lock.json,
yarn.lock, Pipfile.lock by accepting one side and regenerating:
git checkout --theirs package-lock.json && npm install.--ours or --theirs entirely. There are no partial
resolutions for binary files. [src1]rerere cache entries become stale. Evaluate whether
rerere fits your merge strategy. [src2]
ort strategy differences: The ort merge strategy (default
since Git 2.33, sole backend since 2.50) handles rename detection better than the old
recursive strategy but may produce different conflict markers for edge cases involving
directory renames. If you see unexpected conflicts after upgrading, check
git log --diff-filter=R for renames. [src8]