revertEntry

tcmts.cli.merge.revertEntry · #czrwixt2 · term · runs on the server

uses tcmts.cli.main.causalArg, which uses tcmts.cli.main.releaseAt, which uses tcmts.cli.manifests.packagesIn, which uses tcmts.cli.config.configValue, which uses node:path

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.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { diffNamespaces, keyPath, printDiff, stateOf } from "#codebase/tcmts/cli/diff";
import { causalArg, stateAt } from "#codebase/tcmts/cli/main";
import { mergeStates, mergeTexts, printSettled, printTextMerge, settleMerge } from "#codebase/tcmts/cli/merge";
import type { MergeResult } from "#codebase/tcmts/cli/merge";
import { bold, dim, yellow } from "#codebase/tcmts/cli/style";
import { UsageError } from "#codebase/tcmts/cli/ui";
import { shortHash } from "#codebase/tcmts/core/hash";
import { entryTitle } from "#codebase/tcmts/store";
import type { NameRow } from "#codebase/tcmts/store";

export function revertEntry(cb: Codebase, ref: string): MergeResult {
  const entry = causalArg(cb, ref);
  const c = cb.store.causal(entry)!;
  const parent = c.parents[0];
  if (!parent)
    throw new UsageError(
      `${shortHash(entry)} is where history starts: there's nothing before it to go back to`,
    );
  if (!cb.store.isAncestor(entry, cb.store.head()))
    throw new UsageError(
      `${shortHash(entry)} isn't in this branch's history (\`tcm history\` lists it)`,
    );
  const irOf = (h: string) => cb.store.getDef(h)!.ir;
  const made = stateAt(cb, entry);
  const back = stateAt(cb, parent);
  const before = stateOf(cb.store.names(undefined, true));
  const textsBefore = cb.store.texts();
  const result = mergeStates(made, before, back, {
    ours: new Set(diffNamespaces(made, before, irOf).propagated.map((p) => p.path)),
    theirs: new Set(diffNamespaces(made, back, irOf).propagated.map((p) => p.path)),
  });
  // and the text versions they show (comments, layout, doc), the same way: what the entry changed goes back
  const states = { ours: before, theirs: back, merged: result.merged };
  const texts = mergeTexts(
    cb.store.textsAt(c.namespace),
    textsBefore,
    cb.store.textsAt(cb.store.causal(parent)!.namespace),
    states,
  );
  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(() => {
    cb.store.setBranchNames(cb.store.branch().name, rows, `revert ${shortHash(entry)}`);
    for (const [o, n] of result.patches) cb.store.addPatch(o, n);
  });
  const settled = settleMerge(cb, { base: made, ours: before, theirs: back }, result, {
    ours: "changed since",
    theirs: "as before it",
  });
  cb.refreshShim();
  const after = stateOf(cb.store.names(undefined, true));
  printDiff(
    cb,
    diffNamespaces(before, after, irOf, { before: textsBefore, after: cb.store.texts() }),
    before,
    after,
    `reverted ${shortHash(entry)} (${entryTitle(c)})`,
  );
  for (const p of result.keptOverDelete)
    console.log(
      `${yellow("  kept")} ${bold(p)} ${dim("(changed since, though the entry added it)")}`,
    );
  printSettled(settled, { ours: "changed since", theirs: "as before it" }, "revert");
  printTextMerge(texts, states, { ours: "the branch now", theirs: "as before it" });
  return { ...result, conflicts: settled.conflicts.map((c) => c.key) };
}

uses

used by