diff

tcmts.cli.main.diff · #dqoinrfg · term · runs on the server

uses tcmts.cli.style.dim, which uses node:util

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { diffNamespaces, printDiff, stateOf } from "#codebase/tcmts/cli/diff";
import { causalArg, isHistoryRef, stateAt } from "#codebase/tcmts/cli/main";
import { conflictAt, conflictPairs, diffDefinitions } from "#codebase/tcmts/cli/merge";
import { dim } from "#codebase/tcmts/cli/style";
import { UsageError } from "#codebase/tcmts/cli/ui";
import { shortHash } from "#codebase/tcmts/core/hash";

export function diff(cb: Codebase, args: string[]) {
  const now = stateOf(cb.store.names(undefined, true));
  const irOf = (h: string) => cb.store.getDef(h)!.ir;
  // (and the text versions each side shows: a definition can read differently with the same code)
  const textsAt = (causal: string) => cb.store.textsAt(cb.store.causal(causal)!.namespace);
  if (args.length === 0) {
    const head = cb.store.causal(cb.store.head())!;
    if (!head.parents.length) return console.log(dim("no changes yet"));
    const before = stateAt(cb, head.parents[0]!);
    const texts = { before: textsAt(head.parents[0]!), after: cb.store.texts() };
    return printDiff(
      cb,
      diffNamespaces(before, now, irOf, texts),
      before,
      now,
      `changes from \`${head.command}\``,
    );
  }
  // two definitions by #hash, or a conflicted name's: their code, line by line
  const defs = args.map((a) => (a.startsWith("#") ? cb.store.hashesWithPrefix(a.slice(1)) : []));
  if (args.length === 2 && defs.every((d) => d.length === 1)) {
    const [a, b] = defs.map((d) => d[0]!);
    return diffDefinitions(cb, [
      { from: a!, to: b!, title: `${shortHash(a!)} → ${shortHash(b!)}` },
    ]);
  }
  const conflict =
    args.length === 1 && !cb.store.hasBranch(args[0]!) ? conflictAt(cb, args[0]!) : undefined;
  if (conflict) return diffDefinitions(cb, conflictPairs(conflict));
  if (args.length === 1 && isHistoryRef(args[0]!) && !cb.store.hasBranch(args[0]!)) {
    const c = causalArg(cb, args[0]!);
    const before = stateAt(cb, c);
    const label = /@\d/.test(args[0]!) ? args[0]! : shortHash(c);
    const texts = { before: textsAt(c), after: cb.store.texts() };
    return printDiff(
      cb,
      diffNamespaces(before, now, irOf, texts),
      before,
      now,
      `changes since ${label}`,
    );
  }
  const [from, to] = args.length === 1 ? [cb.store.branch().name, args[0]!] : [args[0]!, args[1]!];
  const side = (ref: string) => {
    if (cb.store.hasBranch(ref)) return stateOf(cb.store.names(ref));
    if (isHistoryRef(ref)) return stateAt(cb, causalArg(cb, ref));
    if (!cb.store.hasBranch(ref))
      throw new UsageError(
        `no branch named '${ref}' (or history entry: a number or #hash, see \`tcm history\`)`,
      );
    return stateOf(cb.store.names(ref));
  };
  const sideTexts = (ref: string) =>
    cb.store.hasBranch(ref) ? cb.store.texts(ref) : textsAt(causalArg(cb, ref));
  const [before, after] = [side(from), side(to)];
  const texts = { before: sideTexts(from), after: sideTexts(to) };
  printDiff(cb, diffNamespaces(before, after, irOf, texts), before, after, `${from} → ${to}`);
}

uses

used by