How git's octopus merge works, for a tool that must match it
Statement#
git merge a b c with several heads runs the octopus strategy, git-merge-octopus.sh. To match git exactly, a reimplementation must follow the script, not a generic three-way merge:
- For each head, in order: find
common=$(git merge-base --all <head> <merge result so far>). Whencommoncontains the head, printAlready up to date with <name>and skip it. - While no real merge has happened yet and
commonis the current result, fast-forward: printFast-forwarding to: <name>(git 2.50), rungit read-tree -u -m HEAD <head>, and continue. - Otherwise print
Trying simple merge with <name>and rungit read-tree -u -m --aggressive <all merge bases> <result tree> <head>. It passes every merge base at once; it does not build a virtual merge base like ort/recursive. Whengit write-treethen fails, printSimple merge did not work, trying automatic merge.and rungit merge-index -o git-merge-one-file -a. - A file-level conflict on any head but the last stops the strategy:
Automated merge did not work.andShould not be doing an octopus.. git's merge then restores the tree toHEAD, printsMerge with strategy octopus failed.on stderr with no prefix, and exits 2. - A conflict on the last head leaves conflict markers and exits 1 with
Automatic merge failed; fix conflicts and then commit the result.on stdout. merge-one-fileprintsAuto-merging <path>on stdout andERROR: content conflict in <path>on stderr;merge-indexaddsfatal: merge program failedon stderr. The conflict markers are labelled with random.merge_file_XXXXXXtemp names, so tests must mask them.
On a criss-cross history, a virtual merge base lets a merge succeed where git's octopus fails with a content conflict, so "improving" on git here breaks compatibility.
Evidence#
rgit, 2026-09-28, git 2.50: four histories (clean, conflict on the last head, conflict on a middle head, criss-cross) run with git merge a b c and rgit merge a b c. After rgit's octopus was rebuilt on native read-tree, merge-one-file and write-tree, stdout, stderr, exit codes (0, 1, 2, 2), git status --porcelain, the file content (markers masked) and the new commit's tree matched git in all four. Test octopus_merges_like_gits_script in crates/rgit-cli/tests/sequencer.rs, commit b1a7ca6.