testCrissCrossMerge
Criss-cross merges: two branches that merged each other have two best common ancestors, each lacking the other's change. Merged from the newer one alone, the other's change looks like no change, so a side undoing it is lost; merged into a virtual base first, it isn't. And a conflict between them that the two merges resolved differently is a conflict again. (The two merges must differ: the same merge both ways is the same causal, one ancestor.)
import { okIn, tcmIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; export const testCrissCrossMerge = () => { const dir = tempDir("criss-cross"); const run = (...args: string[]) => tcmIn(dir, ...args); const tcm = (...args: string[]) => okIn(dir, ...args); const update = (code: string) => { fs.writeFileSync(path.join(dir, "scratch.ts"), `// tcm: namespace lib\n${code}\n`); tcm("update"); }; const hashesAt = (name: string) => [ ...tcm("ls", name).matchAll(new RegExp(`^${name.replace(".", "\\.")}\\s+#([a-z2-7]+)`, "gm")), ].map((m) => m[1]!); tcm("init"); update(`export const f = () => 0;\nexport const g = () => 0;\nexport const k = () => 0;`); tcm("branch", "a"); tcm("branch", "b"); tcm("switch", "a"); update(`export const f = () => 1;\nexport const k = () => 1;`); const ka = hashesAt("lib.k")[0]!; tcm("branch", "a1"); // a as it is now, for b to merge update(`export const h = () => 3;`); // (a moves on before merging b) tcm("switch", "b"); update(`export const g = () => 2;\nexport const k = () => 2;`); // (the newer of the two) const kb = hashesAt("lib.k")[0]!; // each merges the other's change, keeping its own k tcm("switch", "a"); assert.match(run("merge", "b").out, /conflict lib\.k/); tcm("resolve", "lib.k", `#${ka}`); tcm("switch", "b"); assert.match(run("merge", "a1").out, /conflict lib\.k/); tcm("resolve", "lib.k", `#${kb}`); // then each undoes the other's change update(`export const f = () => 0;`); tcm("switch", "a"); update(`export const g = () => 0;`); const merged = run("merge", "b"); assert.doesNotMatch(merged.out, /conflict lib\.[fg]\b/); assert.equal(tcm("run", "lib.f").trim(), "0", "b undid a's f"); assert.equal(tcm("run", "lib.g").trim(), "0", "a undid b's g"); assert.equal(tcm("run", "lib.h").trim(), "3"); assert.equal(merged.status, 1, "a conflict makes merge exit non-zero"); assert.match(merged.out, /conflict lib\.k/, "the two merges resolved k differently"); assert.deepEqual(hashesAt("lib.k").sort(), [ka, kb].sort()); }