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.pytoday” 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 oursand-s oursare the same.” — they’re not.-Xis a hint to the recursive strategy;-s oursmakes 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--continuefor rebase/cherry-pick). - “What does
--abortdo?” — 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 oursvs-s ours?” —-X oursprefers our side when both changed (otherwise normal merge);-s oursrecords a merge but discards all of the other side’s changes.