mergeBaseState
The names a three-way merge of two histories starts from: their best common ancestor's. Criss-cross merges (two branches that merged each other) leave several, and no one of them will do: each lacks what the others changed, so the merge would take that as a change one side made, and undo it. So, as git's recursive strategy does, they're merged first, oldest first, each step three-way from their own common ancestors (found the same way), into a virtual base. A conflict while building it stays one: the name holds both definitions, as git keeps conflict markers in its virtual base. Neither side then counts as unchanged for keeping one of them, so the merge never silently takes one side's choice over the other's: where both sides chose alike it agrees, where they chose differently it's a conflict, and a side that left the conflict as it was takes the other's. The virtual base is only compared against: its definitions aren't re-derived, nor saved.
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { stateOf } from "#codebase/tcmts/cli/diff"; import type { NameState } from "#codebase/tcmts/cli/diff"; import { mergeFrom } from "#codebase/tcmts/cli/merge"; export function mergeBaseState( cb: Codebase, ours: string | string[], theirs: string | string[], ): NameState { const irOf = (h: string) => cb.store.getDef(h)!.ir; const at = (causal: string) => stateOf(cb.store.namespaceRows(cb.store.causal(causal)!.namespace)); const [first, ...rest] = cb.store.mergeBases(ours, theirs); let state = at(first!); const merged = [first!]; for (const next of rest) { state = mergeFrom(mergeBaseState(cb, merged, next), state, at(next), irOf).merged; merged.push(next); } return state; }