tcmts.cli.merge

Types

Conflict type
A conflicted name as it is now: its definitions, each with the side it came from where that's known.
ConflictRecord type
A name a merge left conflicted, as it's kept: which definitions came from which side of the merge, each side labelled (its branch), and the merge base's definition. It's a setting of the codebase (`conflict <branch> <name>`), beside the branch's history rather than in it, so it changes no hash; it goes when the conflict is resolved.
MarkerLabels type
How a conflict's three parts are labelled between its markers.
MergePlan type
What merging `theirs` into `ours` would do, without doing it.
MergeResult type
Merged type
A definition's two sides merged three-way as text: `text` whole (a scratch file's, without its namespace line), `body` its code (or a file's lines), `imports` what code imports (one name an import), each side's changes: where they are in `body` (Stretch) and how many lines each added and removed; `conflicts`, how many are marked.
PendingMerge type
A merge not finished: what it left to settle (conflicts, definitions that stopped typechecking) keeps it apart from the branch it's into, which stays where it was (`from`) until there's nothing left. Kept as the setting `merging <branch>`.
Settled type
What settling a merge did, beyond the names it merged (settleMerge).
Stretch type
Where what one side of a merge changed (or both did) is in what's merged: its first line (from 0) and how many (none: it took lines out); `conflict`, both changed it differently: it's marked.

Server

abortMerge (cb: Codebase) => number
Drop the merge not finished into this branch: the branch is where it was all along, and what the merge recorded of its conflicts goes.
conflictLine (c: Conflict) => string
A conflict as `tcm todo` and a merge list it: each definition, labelled with its side, and how to settle it.
conflictedUses (cb: Codebase, file: string, text: string) => string[]
The conflicted names a scratch file uses (imports), but those it holds: until they're settled it can't typecheck, as a conflicted name isn't one definition to check it against.
diffDefinitions (cb: Codebase, pairs: { from: string; to: string; title: string; }[]) => void
`tcm diff #a #b`, or of a conflicted name: definitions' code as `tcm view` shows it (what they use by its name here, or elsewhere), compared line by line, each pair under its title, with two lines of context around what changed.
editConflict (cb: Codebase, cs: Conflict[], target: string | ((jsx: boolean) => string), force: boolean) => void
`tcm edit` of conflicted names: a scratch file holding each one's two sides merged three-way from the merge base's, as `tcm view` shows each (a page: its Markdown), with git's conflict markers (ours, the base's and theirs, each labelled with its side) only where both changed the same lines. Saving it with `tcm update`, once the markers are gone, resolves the conflicts to what it holds, and re-derives what used either side's, once. `target` is the file, or how to name the default one (`.tsx` for JSX): it's named after the (first) name, so conflicts edited one after another each have their own. A file there already is written over only if replacing it loses nothing (it holds what's saved, as a file `tcm update` left is). What either side imports is merged, but for what nothing in the file uses (one side's alone, that the code merged doesn't use). Said: where it is, what each side changed (where in it), and whether, merged, it typechecks as `tcm check` checks it, or what it uses that's still conflicted.
inPendingMerge (cb: Codebase, run: () => number | Promise<number>) => Promise<number>
Run a command on a branch with a merge not finished (PendingMerge): as merged, so what it changes settles the merge. Once nothing's left to settle, the branch moves to the merge (with every entry settling it); until then it goes back to where it was, and the merge waits on, as the command left it.
keepTexts (cb: Codebase, lost: string[], texts: { base: ReadonlyMap<string, string>; ours: ReadonlyMap<string…
A change one side of a merge made to a definition's comments, layout or doc where the other side changed its code (which `mergeTexts` can't keep: it's a text of the old code), merged as text into the code the merge keeps, three-way from the base's text: kept where that merges cleanly and leaves the code as it is (the same hash). Saved as an entry of `command`'s. What's kept: the definitions whose text changed, and the names now showing it.
landBranch (cb: Codebase, source: string, o: { noFastForward?: boolean | undefined; squash?: boolean | undefin…
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.
mergeApart (cb: Codebase, theirs: string, o: { command?: string | undefined; noFastForward?: boolean | undefin…
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 (PendingMerge): 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).
mergeBranch (cb: Codebase, theirs: string, command?: string, noFastForward?: boolean, squash?: boolean) => Merg…
Merge another branch into the current one, Unison's way: by history. If this branch already has the other's history there's nothing to do; if the other has this branch's, it fast-forwards; otherwise it's a three-way merge from their best common ancestor, recorded as a causal with both heads as parents (so the next merge starts from here, in any codebase that has this history), and settled (settleMerge): definitions both sides edited merged as text where they can be, and all the code re-derived against what the merge names. `noFastForward`: a merge entry even where it could fast-forward, so what came in is one entry (a landing `tcm revert` can undo whole). `squash`: one entry too, but with only this branch's head as its parent, so the other's history doesn't come in (nor go wherever this branch is pushed: only what it names now does); the other branch's work counts as here once its names are (hasWork). A branch that made releases this one doesn't have (releasesApart) keeps its history even so, a second parent of the one entry: a release is an entry of the branch's, which this branch's history must have to plan the next release from and to push it. Its `conflicts` are the names still conflicted.
mergeShown (cb: Codebase, name: string, base: DefRecord | undefined, ours: DefRecord, theirs: DefRecord, label…
Two definitions of `name` merged as text from a base's (scratchText of each; none: from nothing), three-way: code as mergeScratch merges it, a file by lines. Where both changed the same lines, conflict markers. `ns`: the namespace of the file it's written in, if not its own.
mergeText (cb: Codebase, name: string, base: string, ours: string, theirs: string) => string | undefined
Merge, as text, a definition both sides of a merge edited: its code as `tcm view` shows it (a page: its Markdown), the base's, ours and theirs, merged three-way (mergeShown), then saved over both as `tcm update` saves it, if it typechecks, with nothing it says shown. The new hash, or `undefined` where it isn't merged (the edits overlap, it doesn't typecheck): then it stays a conflict.
printSettled (s: Settled, labels: { ours: string; theirs: string; }, what: string) => void
What settling a merge (or a revert: `what`) did, after its diff: names merged as text, kept, still conflicted (each definition labelled with its side), and what no longer typechecks.
printTextMerge (r: { conflicts: string[]; lost: string[]; }, states: { ours: NameState; theirs: NameState; merged:…
What merging text versions (`mergeTexts`) couldn't do, said after a merge's (or a revert's) diff.
revertEntry (cb: Codebase, ref: string) => MergeResult
Undo one history entry (a landing, say) and nothing after it, as a new entry: history isn't rewritten. What the entry changed goes back to how it was before it, three-way: the names before it merged into the names now, from the names it made. A name changed since keeps that change; one changed since where the entry changed it too is a conflict (`tcm resolve`). Code that has come to use what it brought is re-derived against what comes back, and what no longer typechecks goes to `tcm todo`.
saveMerged (cb: Codebase, name: string, rec: DefRecord, text: string, opts?: { jsx?: boolean | undefined; text…
Save `text` at `name` (code as scratchText writes it, a file's content), as `tcm update` saves a scratch file (imports nothing in it uses taken out: what either side imports is merged, and one may be the other side's alone), with nothing it says shown: what a merge settles as text, it reports itself. `textOnly`: only if its code is as it is (only its comments, layout or doc differ). The hash the name holds then, or undefined where it isn't saved (it doesn't typecheck, or its code differs).
scratchText (cb: Codebase, rec: DefRecord, name: string, ns?: string) => string
A definition as a scratch file holds it at `name` (`tcm edit`'s text, without its namespace line; a file kept as written: its content), using what it uses by its current name, or a replaced version by what replaced it: so each side of a merge, and the base's, read as one another do. `ns`: the namespace of the file it's in, if not its own (then it says where it goes).
settleMerge (cb: Codebase, states: { base: NameState; ours: NameState; theirs: NameState; }, result: MergeResul…
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 (mergeText); 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 (recordConflicts).
silently <T>(fn: () => T) => T
Run `fn` with nothing it prints shown (a type error's report is written straight to stdout): what a merge tries on the side, a text merge that may not typecheck, it reports itself.
unsavedIn (cb: Codebase, file: string, text: string) => string[]
What a scratch file holds that isn't saved as it is: each definition whose code (with its doc and comments) isn't that of one its name holds now, as `tcm edit` writes it (a conflicted name's: either side's), by name; "other code" for anything else (a watch expression, a statement, a comment of its own). Nothing: replacing the file loses nothing, as with one `tcm update` left behind, though what it holds is a side of a conflict now.
writeMergeFiles (cb: Codebase, left: { conflicts: string[]; failed: { name: string; message: string; }[]; }, scratc…
The scratch files a merge kept apart leaves for settling it: each conflicted name's sides merged (as `tcm edit` writes them, a file named after it), and what stopped typechecking because of it, in one file (`merge-fix.ts`): only what failed itself, as what failed because something it uses did follows once that's fixed.

Shared

branchConflicts (cb: Codebase, branch: string) => Record<string, ConflictRecord>
The conflicts kept for a branch (ConflictRecord), by name as merges key it: what sync carries with the branch (beside its history, so no hash changes), so a conflict pushed or pulled is labelled as the merge that made it labelled it.
conflictAt (cb: Codebase, path: string, kind?: DefKind | undefined) => Conflict | undefined
The conflict at a name on this branch (in `kind`'s space, or whichever is conflicted), with the side each definition came from where a merge kept it (recordConflicts), followed through what re-derived it since (settling the merge, resolving another conflict): undefined if the name isn't conflicted.
conflictMarkers (text: string) => number | undefined
The line of the first conflict left marked in a file's text: markers as git writes them, each a line of its own, seven wide (then a space and a label, or nothing), in order: `<<<<<<<`, then (with the base's) `|||||||`, then `=======`, then `>>>>>>>`. Text that only mentions them (a description quoting them, a heading's underline) isn't one.
conflictPairs (c: Conflict) => { from: string; to: string; title: string; }[]
What `tcm diff <conflicted name>` compares: the base's definition with each side's (without a base, the two).
conflictSetting (cb: Codebase, key: string) => string
The setting the conflict at `key` (a name as merges key it) on this branch is kept in.
conflictedHint (uses: string[], editing: string[]) => string
What to say of a file that uses conflicted names (`uses`): settle them first, or, where it holds conflicts being settled (`editing`), edit them all together.
forgetConflict (cb: Codebase, path: string, kind: DefKind) => void
Forget a conflict's sides: it's resolved.
hasWork (cb: Codebase, branch: string, into: string) => boolean
Whether `branch` has work `into` doesn't: a name it changed since the two parted (its code, or how it reads) that `into` doesn't have as it does. Its history being part of `into`'s says no at once; a branch landed with `--squash` isn't (its entries stay its own), but what it changed is there.
merge3 (base: string[], ours: string[], theirs: string[]) => string[] | undefined
Three-way merge of lines, as `git merge-file` does (mergeLines): the merged lines, or `undefined` where both sides changed a stretch differently (a conflict).
mergeBaseState (cb: Codebase, ours: string | string[], theirs: string | string[]) => NameState
The names a three-way merge of two histories starts from: their best common ancestor's. Criss-cross merges (two branches that merged each other) leave several, and no one of them will do: each lacks what the others changed, so the merge would take that as a change one side made, and undo it. So, as git's recursive strategy does, they're merged first, oldest first, each step three-way from their own common ancestors (found the same way), into a virtual base. A conflict while building it stays one: the name holds both definitions, as git keeps conflict markers in its virtual base. Neither side then counts as unchanged for keeping one of them, so the merge never silently takes one side's choice over the other's: where both sides chose alike it agrees, where they chose differently it's a conflict, and a side that left the conflict as it was takes the other's. The virtual base is only compared against: its definitions aren't re-derived, nor saved.
mergeFrom (base: NameState, ours: NameState, theirs: NameState, irOf: (hash: string) => string) => MergeResult
Three-way merge of namespaces (mergeStates), telling what a side only re-hashed (re-derived after a dependency changed) from what it edited.
mergeLeft (cb: Codebase, before: Iterable<string>) => { conflicts: string[]; failed: { name: string; message:…
What a merge left to settle on this branch: its conflicted names, and what stopped typechecking (not what needed fixing `before` it).
mergeLines (base: string[], ours: string[], theirs: string[], labels?: MarkerLabels) => Omit<Merged, "body" | …
Three-way merge of lines, as `git merge-file --diff3` does: where only one side changed a stretch of the base, that side's lines; where both changed it alike, those. The lines both sides left in place divide it into stretches; one both changed differently (or both added to at the same place) is a conflict, written as git writes one: ours, the base's and theirs, between markers labelled with `labels`. `conflicts`: how many there are; `stretches`: where what either side changed is in what's merged; `changed`: how many lines each side added and removed.
mergeScratch (base: string, ours: string, theirs: string, labels?: MarkerLabels | undefined) => Merged
Three-way merge of a definition's text as a scratch file holds it (`tcm edit`'s, without its namespace line): its imports as a set, what either side imports (an import nothing uses is harmless; one missing isn't), and its code by lines (mergeLines), conflicts marked. `imports`: what it imports, one name an import; `stretches`: where each side's changes are in `body`, the code after them.
mergeStates (base: NameState, ours: NameState, theirs: NameState, propagated?: { ours: Set<string>; theirs: Set…
Three-way merge of namespaces. A name changed on one side takes that side's value; changed on both sides to the same thing, it agrees; changed differently, it becomes conflicted (several hashes on one name).
mergeTexts (base: ReadonlyMap<string, string>, ours: ReadonlyMap<string, string>, theirs: ReadonlyMap<string, …
The text versions a merge leaves each merged definition showing (its comments, layout and doc, which aren't in its hash), three-way as names are: a side that changed a definition's text keeps that change. `conflicts`: changed by both, differently (ours is kept). `lost`: changed by one side, whose definition the merge doesn't keep (the other side changed its code, or deleted it).
namesElsewhere (cb: Codebase) => (hash: string) => { name: string; note: string; } | undefined
Names for definitions nothing on this branch names (the other side of a merge's, an old version), for showing code that uses them: the name of what replaced it here (an older `x`), or its name on another branch (main's first). Other branches' names are read once, when first needed.
oneImportEach (lines: string[], same: (a: string, b: string) => boolean) => string[]
Two sides' import lines, each name imported once: where they import one name from two modules (one definition named twice: each side's code says it by the name it had there), or once as a type and once as a value, one stays (the value's, else the first). `same` says whether two names are of one definition; where they aren't, both stay, for the file's check to say so.
pendingMerge (cb: Codebase, branch?: string) => PendingMerge | undefined
The merge not finished into `branch` (this process's), if there is one.
planMerge (cb: Codebase, ours: string, theirs: string) => MergePlan
recordConflicts (cb: Codebase, conflicts: { key: string; sides: { hash: string; side: "ours" | "theirs" | undefined…
Keep which side each definition of a merge's conflicts came from, and the merge base's (ConflictRecord), for what lists and settles conflicts afterwards (`tcm todo`, `tcm edit`, `tcm resolve --ours`).
releasesApart (cb: Codebase, branch: string, into: string) => Release[]
The releases `branch` made that `into`'s history doesn't have: each is an entry of the branch's, so a landing squashed would leave it out, and `into` could neither plan the next release from it nor push it.
resolvedEntry (cb: Codebase, resolved: { path: string; kind: DefKind; hash: string; }[]) => string
The history entry's name for saving definitions over conflicted names: `resolve lib.k (merged)`, each with how it was settled (`ours` or `theirs` where it's that side's definition, kept whole), as `tcm history` and `tcm diff` show it.
same (a: Set<string> | undefined, b: Set<string> | undefined) => boolean
sideHash (cb: Codebase, name: string, side: "ours" | "theirs") => string
The #hash of the definition one side of a merge gave a conflicted name, as it is now (`tcm resolve --ours`): a hash copied earlier can be stale, as resolving another conflict re-derives what used it.
takeConflicts (cb: Codebase, branch: string, records: unknown, how: "add" | "replace") => number
Keep the conflict records another codebase has for a branch (as branchConflicts gives them) for `branch` here, each that's well-formed. `replace`: they're all it has for the branch now (it was pushed); `add`: each is kept for a name conflicted here (on this branch, `branch`) that has none kept (it was pulled; a merge here keeps its own). How many were kept.

Tests

testConflictMarkersAreOnlyRealOnes () => void
testConflictedHint () => void
testMerge3 () => void
testMergeLinesMarksConflicts () => void
testMergeScratchTakesBothSidesImports () => void
testMergeTexts () => void
Text versions merge three-way: each side's change is kept, both sides' changes to one definition keep ours.
testOneImportEach () => void
One definition named twice, imported by each side under its own name, is imported once; a type and a value import of one name is the value's; two definitions of one name both stay.