mergeApart
uses tcmts.cli.merge.mergeBranch, which uses tcmts.cli.style.dim, which uses node:util
Merge theirs into this branch, and only if nothing's left to settle: a merge that leaves conflicts, or definitions
that stop typechecking, doesn't move the branch. With keep, it waits apart (#yisipbwb): scratch files hold
what to settle (each conflict's sides merged, as tcm edit merges them, and what stopped typechecking), and saving
them makes the merge once nothing's left (inPendingMerge); tcm merge --abort drops it. Without, it's simply
not made (a landing on main, which no one settles there).
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { mergeBranch, mergeLeft, pendingMerge, writeMergeFiles } from "#codebase/tcmts/cli/merge"; import type { MergeResult, PendingMerge } from "#codebase/tcmts/cli/merge"; import { bold, dim, red, yellow } from "#codebase/tcmts/cli/style"; import { UsageError } from "#codebase/tcmts/cli/ui"; export function mergeApart( cb: Codebase, theirs: string, o: { command?: string; noFastForward?: boolean; squash?: boolean; keep: boolean; scratch?: (f?: string, fallback?: string) => string; }, ): MergeResult & { left: ReturnType<typeof mergeLeft> } { const branch = cb.store.branch().name; const waiting = pendingMerge(cb, branch); if (waiting) throw new UsageError( `${branch} is merging ${waiting.theirs} still: finish that (\`tcm todo\` says what's left) or drop it (\`tcm merge --abort\`) first`, ); cb.store.endBatch(); const from = cb.store.head(); const work = cb.store.workOf(branch); const keys = new Set(cb.store.settings(`conflict ${branch} `).map(([k]) => k)); const r = mergeBranch(cb, theirs, o.command, o.noFastForward, o.squash); cb.store.endBatch(); const none = { conflicts: [], failed: [] }; if (cb.store.head() === from) return { ...r, left: none }; const left = mergeLeft( cb, work.todo.map((t) => t.hash), ); if (!left.conflicts.length && !left.failed.length) return { ...r, left }; // what to settle, written while the merge's names are there to read if (o.keep && o.scratch) writeMergeFiles(cb, left, o.scratch); const pending: PendingMerge = { theirs, from, head: cb.store.head(), work: cb.store.workOf(branch), before: work.todo.map((t) => t.hash), conflictKeys: cb.store .settings(`conflict ${branch} `) .map(([k]) => k) .filter((k) => !keys.has(k)), }; cb.store.setHead(branch, from, `merge ${theirs} (kept apart)`, true); cb.store.setWorkOf(branch, work); if (o.keep) cb.store.setSetting(`merging ${branch}`, JSON.stringify(pending)); else for (const k of pending.conflictKeys) cb.store.deleteSetting(k); cb.refreshShim(true); console.log( `\n${red(o.keep ? "not merged yet" : "not merged")} ${bold(theirs)} into ${bold(branch)}: ${[ left.conflicts.length && `${left.conflicts.length} conflicted`, left.failed.length && `${left.failed.length} no longer typecheck${left.failed.length === 1 ? "s" : ""}`, ] .filter(Boolean) .join(", ")} merged. ${branch} is as it was.`, ); for (const c of left.conflicts.slice(0, 20)) console.log(` ${yellow("conflicted")} ${bold(c)}`); for (const f of left.failed.slice(0, 20)) console.log(` ${yellow("needs fixing")} ${bold(f.name)}: ${f.message.split("\n")[0]}`); if (left.conflicts.length + left.failed.length > 40) console.log(dim(" … and more (`tcm todo` lists them)")); if (o.keep) console.log( dim( `The merge waits apart: commands on ${branch} work on it as merged (\`tcm todo\` says what's left), and once a\ncommand leaves nothing to settle, ${branch} moves to it. The scratch files above hold what to settle: change them,\nthen \`tcm update <file>\`. \`tcm merge --abort\` drops the merge.`, ), ); return { ...r, left, conflicts: left.conflicts }; }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.merge.MergeResult
- tcmts.cli.merge.PendingMerge
- tcmts.cli.merge.mergeBranch
- tcmts.cli.merge.mergeLeft
- tcmts.cli.merge.pendingMerge
- tcmts.cli.merge.writeMergeFiles
- tcmts.cli.style.bold
- tcmts.cli.style.dim
- tcmts.cli.style.red
- tcmts.cli.style.yellow
- tcmts.cli.ui.UsageError