settleMerge
uses tcmts.cli.merge.mergeText, which uses tcmts.cli.merge.silently, which uses process
After a merge's names are set (and its patches recorded), make every definition on the branch use what the
branch names. A name a side deleted that the other side's code uses is kept. A definition both sides edited is
merged as text where the edits are apart and the result typechecks (#ipxzlfa3); otherwise it stays a
conflict, and code derived from either side keeps using that side's definition, until tcm resolve keeps one.
Then everything that used what the merge replaced is re-derived against what replaced it, in dependency order:
what doesn't typecheck goes to tcm todo. Merges and reverts both settle so. The conflicts left are kept with
each definition's side, as labels call them, and the base's (#alvelgcx).
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { keyPath, stateOf } from "#codebase/tcmts/cli/diff"; import type { NameState } from "#codebase/tcmts/cli/diff"; import { mergeText, recordConflicts } from "#codebase/tcmts/cli/merge"; import type { MergeResult, Settled } from "#codebase/tcmts/cli/merge"; export function settleMerge( cb: Codebase, states: { base: NameState; ours: NameState; theirs: NameState }, result: MergeResult, labels: { ours: string; theirs: string }, ): Settled { const todoBefore = new Set(cb.store.todos().map((t) => t.hash)); const kind = (h: string) => cb.store.getDef(h)!.kind; // a side's code using a name the other deleted: the name stays (that code would use what nothing names) const keptForUse: string[] = []; const named = new Map<string, string>(); for (const s of [states.ours, states.theirs]) for (const [key, hs] of s) for (const h of hs) if (!result.merged.has(key)) named.set(h, key); if (named.size) { const restore = new Map<string, string>(); for (const u of cb.store.unnamedUses()) if (named.has(u.uses) && !result.patches.has(u.uses)) restore.set(u.uses, named.get(u.uses)!); if (restore.size) cb.store.transaction(() => { for (const [h, key] of restore) { // (a merge's names are the merge's, a library's among them) cb.store.unguarded(() => cb.store.addName(keyPath(key), h, kind(h), "merge (kept: still used)"), ); keptForUse.push(key); } }); } // both sides edited it: merged as text, if the edits are apart and it typechecks const textMerged: string[] = []; const one = (s: Set<string> | undefined) => (s?.size === 1 ? [...s][0] : undefined); const byText = (key: string) => { const [b, o, t] = [states.base, states.ours, states.theirs].map((s) => one(s.get(key))); return !!(b && o && t && mergeText(cb, keyPath(key), b, o, t)); }; let tried = result.conflicts.filter((key) => { if (!byText(key)) return true; textMerged.push(key); return false; }); // what used a definition the merge replaced (each side's code, the other's changes) is re-derived against its // replacement if (result.patches.size) cb.propagate(new Map(result.patches)); // a text merge that didn't typecheck against the code as it was mid-merge is tried again against it settled, as // `tcm check` would check it (and again while that merges more: one can use another) for (let more = true; more && tried.length;) { more = false; const now = stateOf(cb.store.names(undefined, true)); tried = tried.filter((key) => { if ((now.get(key)?.size ?? 0) < 2 || !byText(key)) return true; textMerged.push(key); more = true; return false; }); } const after = stateOf(cb.store.names(undefined, true)); // (a side's definition re-derived since is still that side's) const sideOf = (key: string, h: string): "ours" | "theirs" | undefined => { const is = (s: NameState) => [...(s.get(key) ?? [])].some((x) => x === h || cb.store.patched(x) === h); return is(states.ours) ? "ours" : is(states.theirs) ? "theirs" : undefined; }; const conflicts = result.conflicts .filter((key) => (after.get(key)?.size ?? 0) > 1) .map((key) => ({ key, sides: [...after.get(key)!].map((hash) => ({ hash, side: sideOf(key, hash) })), })); const live = cb.liveHashes(); const failed = cb.store .todos() .filter((t) => !todoBefore.has(t.hash) && live.has(t.hash)) .flatMap((t) => cb.store.pathsOf(t.hash)); recordConflicts(cb, conflicts, states.base, labels); return { textMerged, keptForUse, conflicts, failed }; }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.diff.NameState
- tcmts.cli.diff.keyPath
- tcmts.cli.diff.stateOf
- tcmts.cli.merge.MergeResult
- tcmts.cli.merge.Settled
- tcmts.cli.merge.mergeText
- tcmts.cli.merge.recordConflicts