A three-way merge resolves two divergent versions of a file (ours and
theirs) against their common ancestor (base), line by line:
- Neither side changed a line, or both changed it to the SAME new value: no conflict -- use that value.
- Only ONE side changed the line: that side's change wins automatically.
- BOTH sides changed the line, to DIFFERENT values: a conflict.
Scope note: this exercise works at fixed line positions -- base,
ours, and theirs are all the same length, and merging is purely
about each side's content change at a given position (not line
insertions/deletions, which need a full diff-alignment pass first --
see the real-world grounding note below for where that lives in actual
Git).
Consecutive conflicting lines must be coalesced into ONE conflict block (not a separate marker pair per line), using the same textual format Git embeds directly into a file's content:
<<<<<<< OURS
<ours' lines for this block>
=======
<theirs' lines for this block>
>>>>>>> THEIRS
Implement three_way_merge(base, ours, theirs), returning a
MergeResult(lines, has_conflict) (given -- don't modify it):
lines is the merged file with any conflict blocks embedded as marker
lines, and has_conflict is True iff at least one conflict block was
produced. Raise ValueError if base, ours, and theirs aren't all
the same length.
Example
base = ["a", "b", "c"]
ours = ["a", "OURS", "c"]
theirs = ["a", "THEIRS", "c"]
three_way_merge(base, ours, theirs).lines
# -> ["a", "<<<<<<< OURS", "OURS", "=======", "THEIRS", ">>>>>>> THEIRS", "c"]
Constraints
- Adjacent conflicting positions form ONE block, not one block per position -- check the "consecutive conflicts" test carefully.
- The auto-resolve rule always picks the CHANGED value when only one
side changed (never silently falls back to
base).
Real-world grounding
- Git's line-level three-way file merge (the exact
only-one-side-changed-wins / both-changed-differently-conflicts
policy, plus
<<<<<<</=======/>>>>>>>marker formatting), used by bothgit merge-fileand the recursive/ort merge strategies:xdiff/xmerge.c - Git's current default merge strategy, which runs this kind of
per-file content merge as one step inside a much larger tree-level
merge (also handling renames, directory/file conflicts, and which
files even need a three-way merge in the first place):
merge-ort.c
Real Git first diffs base against ours and base against theirs
(so insertions and deletions shift line numbers correctly on each side
independently) before ever comparing lines to each other -- this
exercise's fixed-position model implements the core merge POLICY that
runs once that alignment step is done.