testACommentChangeStaysOnItsBranch
A session that changes only a definition's doc and comments (the same code, the same hash) changes its own branch:
main reads it as before, the session's history has the change as an entry (tcm diff shows it), revert and reset
undo it alone, and merging lands it (main into it first, three-way), beside what main did meanwhile.
import { okIn, plainEnv, strip, tcmBin, tempDir } from "#codebase/tcmts/cli/test/e2e/harness"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; export const testACommentChangeStaysOnItsBranch = () => { const dir = tempDir("comment change"); const tcm = (...args: string[]) => okIn(dir, ...args); const inSession = (...args: string[]) => { const r = spawnSync(process.execPath, [tcmBin(), ...args], { cwd: dir, encoding: "utf8", env: { ...plainEnv(), TCM_BRANCH: "session/s" }, }); const out = strip(r.stdout + r.stderr); if (r.status !== 0) throw new Error(`tcm ${args.join(" ")} (in the session) exited ${r.status}:\n${out}`); return out; }; const scratch = (text: string) => fs.writeFileSync(path.join(dir, "scratch.ts"), text); tcm("init"); scratch( `// tcm: namespace lib\n/** Squares a number. */\nexport const square = (x: number): number => x * x;\n`, ); tcm("add"); const hash = /#([a-z2-7]{8})/.exec(tcm("view", "lib.square"))![1]!; tcm("branch", "session/s"); scratch( `// tcm: namespace lib\n/** The square of a number. */\nexport const square = (x: number): number =>\n // x²\n x * x;\n`, ); assert.match(inSession("update", "./scratch.ts"), /lib\.square/); const changed = inSession("view", "lib.square"); assert.match(changed, /The square of a number[\s\S]*x²/); assert.ok(changed.includes(`#${hash}`), "the same hash"); // main reads it as before, and finds only what it has const onMain = tcm("view", "lib.square"); assert.match(onMain, /Squares a number/); assert.doesNotMatch(onMain, /x²|The square/); assert.match(inSession("search", "x²"), /lib\.square/); assert.doesNotMatch(tcm("search", "x²"), /lib\.square/); // the session's history has it, as an entry of its own assert.match( inSession("history", "1"), /update lib\.square\s+lib\.square\s+#?[a-z2-7]+ reads differently/, ); assert.match( inSession("diff"), /comments, layout or doc changed[\s\S]*lib\.square[\s\S]*\+ .*x²/, ); // revert undoes it alone; reset to the entry that made it brings it back assert.match(inSession("revert", "0"), /reverted cleanly/); assert.match(inSession("view", "lib.square"), /Squares a number/); assert.match(inSession("reset", "1"), /1 reading as they did/); assert.match(inSession("view", "lib.square"), /x²/); // main does something else meanwhile; the merge lands both scratch(`// tcm: namespace lib\nexport const half = (x: number): number => x / 2;\n`); tcm("add"); const landed = tcm("merge", "session/s"); // (landing: main into the session first, three-way, then the session on main) assert.match(landed, /merged main into session\/s[\s\S]*merged cleanly/); assert.match(landed, /comments, layout or doc changed[\s\S]*lib\.square/); assert.match(tcm("view", "lib.square"), /The square of a number[\s\S]*x²/); assert.match(tcm("ls", "lib"), /lib\.half/); }