backend / git / 12_conflict_resolution.md

Merge Conflict Resolution

6 interview angles 5 min read source

Merge Conflict Resolution

A conflict happens when git can’t auto-merge changes — typically when two branches modified the same lines (or one deleted a file the other modified). You resolve by editing the files, staging, and continuing.

What a conflict looks like

def greet(name):
<<<<<<< HEAD
    return f"Hello, {name}!"
=======
    return f"Hi, {name}."
>>>>>>> feature/casual-greeting
Marker Meaning
<<<<<<< HEAD start of “ours” (current branch / target of the merge)
======= divider
>>>>>>> branch-name end of “theirs” (incoming branch / source)

You edit the file to whatever the correct combined code is, remove all the markers, save.

The basic resolution flow

git merge feature                       # or: git rebase / cherry-pick / pull / stash pop
# CONFLICT (content): Merge conflict in greet.py

# edit greet.py to remove markers and write the right code
git add greet.py
git status                              # confirm all conflicts resolved
git commit                              # for merge; or git rebase --continue / cherry-pick --continue

For rebase / cherry-pick / merge / pull, the continuation command is the operation’s --continue form.

Bail-out commands

Operation Abort
git merge git merge --abort
git rebase git rebase --abort
git cherry-pick git cherry-pick --abort
git revert git revert --abort
git pull (merge) git merge --abort
git pull --rebase git rebase --abort
git stash pop edit conflicts and git checkout -- <file> for the unfinished bits; stash stays in list

“Ours” vs “theirs” — which is which?

Confusing because it inverts depending on operation:

Operation “ours” (HEAD) “theirs” (incoming)
git merge feature (on main) main feature
git rebase main (on feature) main (the new base) feature (your work being replayed)
git cherry-pick X current branch the picked commit

So during a rebase, “theirs” is your own work being applied — counterintuitive.

Auto-resolve to one side

git checkout --ours file.py             # keep our version entirely
git checkout --theirs file.py           # keep their version entirely
git add file.py

Or for the whole merge:

git merge feature -X ours               # auto-prefer ours when both changed
git merge feature -X theirs             # auto-prefer theirs when both changed

-X ours is not the same as -s ours:

  • -X ours (option to recursive strategy): when both sides changed, prefer ours; otherwise normal merge.
  • -s ours (strategy): record the merge but discard ALL changes from the other branch (just makes a parent pointer). Used to “merge” without taking any actual changes.

Visual diff/merge tools

Three-way diffs are easier in a tool than in vim with conflict markers.

git mergetool                           # opens configured tool
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

Common tools: VS Code’s built-in merge editor, Beyond Compare, Meld, Kdiff3, IntelliJ.

git config --global merge.conflictStyle diff3

diff3 style adds the common ancestor between the two sides:

<<<<<<< HEAD
return f"Hello, {name}!"
||||||| common-ancestor
return f"Hi {name}"
=======
return f"Hi, {name}."
>>>>>>> feature

Often makes resolution obvious (you see what each side changed from).

zdiff3 (newer) is even better — collapses identical hunks within the conflict.

rerere — REuse REcorded REsolution

Git can remember how you resolved a conflict and replay the same resolution next time the same conflict appears.

git config --global rerere.enabled true

Enable it once, forget it exists. Pays off massively when:

  • Rebasing a branch repeatedly (each rebase, same conflicts).
  • Long-lived feature branches that conflict with main on the same files.
  • Cherry-picking a series across release branches.

Conflict in a delete

CONFLICT (modify/delete): file.py deleted in HEAD and modified in feature.

Two options:

git rm file.py                          # accept the delete
git add file.py                         # keep the modified version

Then continue.

Binary file conflicts

Git can’t 3-way-merge binaries. You pick one side wholesale:

git checkout --ours image.png
git add image.png

For “shared editable” binary types (.docx, design files), use git LFS or just convention.

Conflict during git stash pop

git stash pop
# CONFLICT

Resolve, stage. The stash stays in git stash list (in case you want to retry). When confirmed clean: git stash drop.

Avoiding conflicts in the first place

  • Rebase your branch on main daily. Small frequent merges resolve trivially; big ones resolve painfully.
  • Keep PRs small and short-lived. A 2-day branch rarely conflicts; a 2-week branch always does.
  • Split files by concern. If two devs always edit the same 1000-line file, that file is too big.
  • Lock contentious resources. For things git can’t merge (binaries, generated files), have a single owner or use git LFS file locking.
  • Communicate. “I’m refactoring users/serializers.py today” prevents two PRs colliding.

A common conflict pattern: imports

<<<<<<< HEAD
from .models import User
from .serializers import UserSerializer, ProfileSerializer
=======
from .models import User, Profile
from .serializers import UserSerializer, AuthSerializer
>>>>>>> feature

Almost always: take the union. Auto-resolvable by linters / isort. Pre-commit hooks that auto-format imports cut import-conflicts to near zero.

Common interview confusions

  • -X ours and -s ours are the same.” — they’re not. -X is a hint to the recursive strategy; -s ours makes a “merge” that discards the other side entirely.
  • “Conflict markers in committed code is fine if tests pass.” — they’re syntax errors in most languages; tests can’t pass. But people commit them via git add . carelessly. Pre-commit hook to grep for <<<<<<< saves the day.
  • “Merge conflict means git is broken.” — it means two changes overlapped and git is asking you to decide. Working as intended.

Interview angle

  • “Walk me through resolving a merge conflict.” — git stops mid-merge, shows files with <<<<<<< markers; edit the file to the right combined state, remove markers, git add, git commit (or --continue for rebase/cherry-pick).
  • “What does --abort do?” — returns to the state before the operation started (pre-merge HEAD, pre-rebase HEAD).
  • “During a rebase, which side is ‘ours’?” — counterintuitively, “ours” = the new base (main), “theirs” = your commits being replayed. Inverted from a normal merge.
  • “What’s git rerere?” — records conflict resolutions and replays them automatically next time. Big win for long-lived branches that rebase repeatedly.
  • “How would you reduce merge conflicts on the team?” — small short-lived PRs, daily rebase onto main, auto-formatting (so style differences don’t conflict), splitting hot files, pre-commit hooks blocking commits with conflict markers.
  • -X ours vs -s ours?”-X ours prefers our side when both changed (otherwise normal merge); -s ours records a merge but discards all of the other side’s changes.