Reading diff3 and zdiff3 conflict markers Jump to heading

Git’s default conflict markers show two versions of a block — yours and theirs — and leave you to work out what each side changed. That is often impossible from two snapshots alone. If one side reads timeout = 45 and the other timeout = 30, you cannot tell whether one person raised it from 30, the other lowered it from 45, or both changed it from 60. The ancestor version answers that instantly, and Git can include it in the markers. With merge.conflictStyle set to diff3 or the newer zdiff3, every conflict shows what the code looked like before either side touched it. This page explains how to read the extra section and why it changes how you resolve conflicts, within 3-way merge fundamentals.

When to use this approach Jump to heading

  • You resolve conflicts regularly and sometimes cannot tell which side changed what.
  • Conflicts involve values or small edits where both sides look plausible.
  • You review other people’s conflict resolutions and want to see what the original was.
  • Your team uses rebase heavily, where the meaning of “ours” and “theirs” flips — the ancestor view removes the need to remember which is which; see ours and theirs during merge vs rebase.

Step 1 — Turn on zdiff3 Jump to heading

zdiff3 (Git 2.35 and newer) is diff3 with one improvement: lines that are identical at the start or end of both sides are moved outside the conflict, so the markers enclose only what actually differs. Use diff3 on older Git.

git config --global merge.conflictStyle zdiff3     # Git 2.35+
# git config --global merge.conflictStyle diff3    # older Git
git config --get merge.conflictStyle

The setting affects merge, rebase, cherry-pick, revert and stash application — every operation that can produce conflict markers.

Step 2 — Recognise the three sections Jump to heading

With an ancestor-style setting, a conflict has three sections instead of two. The middle one, introduced by |||||||, is the merge base: the version both sides started from.

<<<<<<< HEAD
retry_limit = 5
timeout = 45
||||||| merge base
retry_limit = 3
timeout = 30
=======
retry_limit = 3
timeout = 60
>>>>>>> feature/slow-backends
The three sections of a diff3 conflictThe first section is the current branch's version, the middle section is the common ancestor both sides started from, and the last section is the incoming branch's version. Comparing each side with the middle shows exactly what each side changed.read each side against the middle<<<<<<< ours (HEAD)retry_limit = 5, timeout = 45||||||| merge baseretry_limit = 3, timeout = 30======= theirsretry_limit = 3, timeout = 60ours changed both values; theirs changed only the timeout

Read it as two diffs against the middle. Ours changed retry_limit from 3 to 5 and timeout from 30 to 45. Theirs left retry_limit alone and changed timeout from 30 to 60. Now the conflict is clear: both sides raised the timeout, by different amounts, and only ours touched the retry limit.

# Verification: produce a real conflict and look at the markers
git merge feature/slow-backends || sed -n '/<<<<<<</,/>>>>>>>/p' config/backends.ini

Step 3 — Resolve by combining intents, not picking a side Jump to heading

With two-way markers, the temptation is to pick one side. With the base visible, you can see each side’s intent and usually combine them. In the example, keep ours’ retry limit (theirs did not care about it) and decide the timeout deliberately — both wanted it higher, so the higher value, or a conversation, is the right answer.

retry_limit = 5
timeout = 60
Resolving with and without the ancestorWith only two sides visible, the resolver tends to pick one whole side and silently drop the other side's change. With the ancestor visible, the resolver sees that only one side changed the retry limit and keeps it, while treating the timeout as a real disagreement.Two-way markersdiff3 / zdiff3retry_limitpick 5 or 3, guessonly ours changed: keep 5timeoutpick 45 or 60, guessboth raised: real decisionriskdrop a change silentlyeach change accounted forthe ancestor turns a guess into two small, answerable questions

A useful habit: for each line in the conflict, ask “did ours change it, did theirs, or both?” If only one side changed a line, take that side’s version. Only lines changed by both sides need judgement.

Step 4 — Use zdiff3 to shrink noisy conflicts Jump to heading

When both sides change the same region and happen to make some identical edits, diff3 includes the identical lines inside the conflict, making it longer than it needs to be. zdiff3 moves common leading and trailing lines out.

# diff3: identical first and last lines are inside the conflict
<<<<<<< HEAD
import logging
import requests
from app import config
||||||| merge base
import requests
=======
import logging
import httpx
from app import config
>>>>>>> feature/httpx

# zdiff3: only the line that truly differs remains in conflict
import logging
<<<<<<< HEAD
import requests
||||||| merge base
import requests
=======
import httpx
>>>>>>> feature/httpx
from app import config

Now it is obvious: ours did not change the HTTP client import at all (it matches the base), so theirs’ change to httpx should win.

Step 5 — Recover the base when markers are not enough Jump to heading

Sometimes the conflict is in a file too large or too restructured to read in markers. All three versions are available as index stages while the conflict is unresolved, so you can open them side by side or diff them directly.

git show :1:src/client.py > /tmp/base.py     # stage 1: merge base
git show :2:src/client.py > /tmp/ours.py     # stage 2: ours
git show :3:src/client.py > /tmp/theirs.py   # stage 3: theirs
diff -u /tmp/base.py /tmp/theirs.py          # what did they change?
The index stages during a conflictWhile a path is conflicted, the index holds three versions of it: stage one is the merge base, stage two is ours and stage three is theirs. Any of them can be printed with git show, which is the fallback when markers are unreadable.:1:pathmerge base:2:pathours:3:paththeirsgit checkout --conflict=zdiff3 path re-creates the markers if you mangled them

If you edited the markers into a mess and want to start the file again, git checkout --conflict=zdiff3 -- path regenerates the conflict markers from the stages without touching other files.

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Does zdiff3 change how Git merges, or only how it displays conflicts? Jump to heading

Only the display. The merge result is identical; zdiff3 changes which lines appear inside the markers. It is safe to set globally.

Why does the base section sometimes look unrelated to either side? Jump to heading

When there are several merge bases — after criss-cross merges — Git first merges the bases into a virtual ancestor, and the base section shows that synthetic version. The situation is explained in resolving criss-cross merges.

Do merge tools show the base too? Jump to heading

Most three-way merge tools show it as a separate pane. Configuring one is covered in configuring git mergetool for three-way resolution.