landBranch
uses node:child_process
Land source on this branch (the codebase's own: main), as a session lands: this branch is merged into source
first, there (a merge it doesn't finish waits there, this branch untouched); then what needs fixing on source, and
the tests the change reaches, run there; and only if nothing's left and they pass does source land here, where a
merge that wouldn't be clean (this branch moved meanwhile) isn't made. Each step on source is a tcm of its own
(TCM_BRANCH), as an agent's session takes it.
import { TCM_COMMAND } from "#codebase/tcmts/cli/checkers"; import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { mergeApart } from "#codebase/tcmts/cli/merge"; import { bold, dim, red, yellow } from "#codebase/tcmts/cli/style"; import { UsageError } from "#codebase/tcmts/cli/ui"; import { spawnSync } from "node:child_process"; export function landBranch( cb: Codebase, source: string, o: { noFastForward?: boolean; squash?: boolean }, ): number { const into = cb.store.branch().name; if (!cb.store.hasBranch(source)) throw new UsageError(`no branch named '${source}'`); const self = ((globalThis as unknown as Record<symbol, unknown>)[TCM_COMMAND] as | string[] | undefined) ?? [process.execPath, process.argv[1]!]; // (what each says is said here, as this tcm says things: an agent's server hears it) const on = (args: string[], show = true) => { const r = spawnSync(self[0]!, [...self.slice(1), ...args], { env: { ...process.env, TCM_BRANCH: source }, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 64 * 1024 * 1024, }); if (show && (r.stdout || r.stderr)) console.log(`${r.stdout ?? ""}${r.stderr ?? ""}`.trimEnd()); return r; }; console.log(bold(`landing ${source} on ${into}: ${into} into ${source} first`)); const up = on(["merge", into]); if (up.status !== 0) { console.log( red( `\nnothing landed on ${into}: merging it into ${source} leaves something to settle there (TCM_BRANCH=${source} tcm todo), and once it's settled, merge again`, ), ); return 1; } // what both changed, merged as text: clean, but worth reading before it lands (tests can't tell what each meant) const both = [ ...new Set( [ ...`${up.stdout ?? ""}`.replace(/\x1b\[[0-9;]*m/g, "").matchAll(/merged as text (\S+)/g), ].map((m) => m[1]!), ), ]; if (both.length) console.log( `${yellow("both changed")} ${both.map((b) => bold(b)).join(", ")} ${dim(`(merged as text, cleanly: worth reading on ${source} before it lands)`)}`, ); const todo = on(["todo"], false); const said = (todo.stdout ?? "").trim(); if (said && said !== "nothing to do") { console.log( `\n${said}\n${red(`nothing landed on ${into}: fix these on ${source}, then merge again`)}`, ); return 1; } console.log(bold(`\ntesting ${source}`)); const tests = on(["test"]); if (tests.status !== 0) { console.log(red(`\nnothing landed on ${into}: these fail on ${source}`)); return 1; } console.log(bold(`\nlanding ${source} on ${into}`)); const was = cb.store.head(); const r = mergeApart(cb, source, { ...o, keep: false }); if (r.left.conflicts.length || r.left.failed.length) { console.log( red( `${into} moved meanwhile, and merging ${source} into it isn't clean: merge again (${into} comes into ${source} first)`, ), ); return 1; } // squashed, the landing isn't in source's history: merged into it (no names change), so the next merge of this // branch into source starts from the landing, not from before it, where each name source changed again since // would conflict with its own landed version if ( o.squash && cb.store.head() !== was && !cb.store.isAncestor(cb.store.head(source), cb.store.head()) ) { const after = on(["merge", into], false); if (after.status !== 0) console.log( `${`${after.stdout ?? ""}${after.stderr ?? ""}`.trimEnd()}\n${yellow(`landed, but merging ${into} into ${source} after it leaves something to settle there (TCM_BRANCH=${source} tcm todo)`)}`, ); } return 0; }
uses
- tcmts.cli.checkers.TCM_COMMAND
- tcmts.cli.codebase.Codebase
- tcmts.cli.merge.mergeApart
- tcmts.cli.style.bold
- tcmts.cli.style.dim
- tcmts.cli.style.red
- tcmts.cli.style.yellow
- tcmts.cli.ui.UsageError