mergeBranch
uses tcmts.cli.style.dim, which uses node:util
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 (#62evoehy): 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 (#4lmbku25). A branch that made releases this one doesn't have
(#rlt7l2sd) 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.
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { diffNamespaces, keyPath, printDiff, stateOf } from "#codebase/tcmts/cli/diff"; import { hasWork, keepTexts, mergeTexts, planMerge, printSettled, printTextMerge, releasesApart, settleMerge, } from "#codebase/tcmts/cli/merge"; import type { MergeResult } from "#codebase/tcmts/cli/merge"; import { bold, dim, green, yellow } from "#codebase/tcmts/cli/style"; import type { NameRow } from "#codebase/tcmts/store"; export function mergeBranch( cb: Codebase, theirs: string, command = `merge ${theirs}`, noFastForward = false, squash = false, ): MergeResult { const ours = cb.store.branch().name; const plan = planMerge(cb, ours, theirs); // (a branch's releases are entries of its history: squashed, that history wouldn't come in) const released = squash ? releasesApart(cb, theirs, ours) : []; const squashed = squash && !released.length; const none: MergeResult = { merged: stateOf(cb.store.names(undefined, true)), conflicts: [], keptOverDelete: [], patches: new Map(), }; if (plan.kind === "up-to-date" || (squashed && !hasWork(cb, theirs, ours))) { console.log(dim(`${ours} already has everything in ${theirs}: no changes`)); return none; } const before = stateOf(cb.store.names(undefined, true)); const textsBefore = cb.store.texts(); const irOf = (h: string) => cb.store.getDef(h)!.ir; if (plan.kind === "fast-forward" && !noFastForward && !squash) { cb.store.setHead(ours, plan.to, command); cb.refreshShim(); const after = stateOf(cb.store.names(undefined, true)); printDiff( cb, diffNamespaces(before, after, irOf, { before: textsBefore, after: cb.store.texts() }), before, after, `fast-forwarded ${ours} to ${theirs}`, ); return { ...none, merged: after }; } // (not fast-forwarded: theirs is what's merged, with both heads as parents) const { result, theirsHead, base, theirsNow } = plan.kind === "fast-forward" ? { theirsHead: plan.to, base: before, theirsNow: stateOf(cb.store.names(theirs, true)), result: { merged: stateOf(cb.store.names(theirs, true)), conflicts: [], keptOverDelete: [], patches: new Map<string, string>(), } satisfies MergeResult, } : { ...plan, theirsNow: plan.theirs }; // the text versions merged definitions show (their comments, layout and doc), merged three-way as names are const textsTheirs = cb.store.texts(theirs); const textsBase = plan.kind === "fast-forward" ? new Map<string, string>() : cb.store.textsAt( cb.store.causal(cb.store.mergeBase(cb.store.head(), theirsHead))!.namespace, ); const texts = plan.kind === "fast-forward" ? { texts: textsTheirs, conflicts: [], lost: [] } : mergeTexts(textsBase, textsBefore, textsTheirs, { ours: before, theirs: theirsNow, merged: result.merged, }); const rows: NameRow[] = [...result.merged].flatMap(([key, hs]) => [...hs].map((hash) => { const text = texts.texts.get(hash); return { path: keyPath(key), hash, kind: cb.store.getDef(hash)!.kind, ...(text ? { text } : {}), }; }), ); cb.store.transaction(() => { if (!squashed) cb.store.addParent(theirsHead); cb.store.setBranchNames(ours, rows, command); for (const [o, n] of result.patches) cb.store.addPatch(o, n); }); const settled = settleMerge(cb, { base, ours: before, theirs: theirsNow }, result, { ours, theirs, }); // a side's change to comments or a doc where the other changed the code: merged into that code, as text const textsKept = keepTexts( cb, texts.lost, { base: textsBase, ours: textsBefore, theirs: textsTheirs }, { ours: before, theirs: theirsNow }, `${command} (comments kept)`, ); cb.refreshShim(); const after = stateOf(cb.store.names(undefined, true)); printDiff( cb, diffNamespaces(before, after, irOf, { before: textsBefore, after: cb.store.texts() }), before, after, `merged ${theirs} into ${ours}${squashed ? " as one entry (squashed: its history stays its own)" : squash ? " as one entry, its history kept" : ""}`, ); if (released.length) console.log( dim( ` (${theirs} made ${released.map((r) => `${r.package}@${r.version}`).join(", ")}: a release is its entry, which ${ours}'s history must have, for the next release to follow it and a push to carry it)`, ), ); for (const p of result.keptOverDelete) console.log( `${yellow(" kept")} ${bold(p)} ${dim("(deleted on one side, changed on the other)")}`, ); printSettled(settled, { ours, theirs }, "merge"); for (const p of textsKept.paths) console.log( `${green(" merged as text")} ${bold(p)} ${dim("(one side changed its comments or doc, the other its code: both are kept)")}`, ); printTextMerge( { ...texts, lost: texts.lost.filter((h) => !textsKept.hashes.includes(h)) }, { ours: before, theirs: theirsNow, merged: result.merged }, { ours: ours, theirs: theirs }, ); return { ...result, conflicts: settled.conflicts.map((c) => c.key) }; }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.diff.diffNamespaces
- tcmts.cli.diff.keyPath
- tcmts.cli.diff.printDiff
- tcmts.cli.diff.stateOf
- tcmts.cli.merge.MergeResult
- tcmts.cli.merge.hasWork
- tcmts.cli.merge.keepTexts
- tcmts.cli.merge.mergeTexts
- tcmts.cli.merge.planMerge
- tcmts.cli.merge.printSettled
- tcmts.cli.merge.printTextMerge
- tcmts.cli.merge.releasesApart
- tcmts.cli.merge.settleMerge
- tcmts.cli.style.bold
- tcmts.cli.style.dim
- tcmts.cli.style.green
- tcmts.cli.style.yellow
- tcmts.store.NameRow