pull

tcmts.cli.sync.pull · #vqzgpewz · term · runs on the server

uses tcmts.cli.sync.openRemote, which uses process

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { diffNamespaces, printDiff, stateOf } from "#codebase/tcmts/cli/diff";
import { mergeApart, takeConflicts } from "#codebase/tcmts/cli/merge";
import { dim } from "#codebase/tcmts/cli/style";
import { fetchHistory, fetchLints, fetchReleases, fetchResults, openRemote } from "#codebase/tcmts/cli/sync";

export async function pull(cb: Codebase, spec: string, remoteBranch: string | undefined) {
  const { remote, name } = openRemote(cb, spec);
  if (!name) throw new Error(`pull needs a configured remote: \`tcm remote add <name> ${spec}\``);
  const branch = remoteBranch ?? cb.store.branch().name;
  const theirs = await remote.branch(branch);
  if (!theirs) throw new Error(`${name} has no branch '${branch}'`);
  const stored = await fetchHistory(cb, remote, theirs.head);
  const tracking = `${name}/${branch}`;
  cb.store.setHead(tracking, theirs.head, `pull ${name}`);
  if (stored) console.log(dim(`received ${stored} definition(s), verified by re-hashing`));
  // and what their tests came to, where they ran as they would here: they needn't run again
  const results = await fetchResults(cb, remote, theirs.names);
  if (results) console.log(dim(`received ${results} test result(s)`));
  // and what linting found, which needn't be done again here
  const lints = await fetchLints(cb, remote, theirs.names);
  if (lints) console.log(dim(`received ${lints} lint result(s)`));
  const released = await fetchReleases(cb, remote);
  if (released.length) console.log(dim(`received release(s) ${released.join(", ")}`));

  const local = cb.store.branch().name;
  const mine = cb.store.head();
  if (cb.store.isAncestor(theirs.head, mine)) {
    return console.log(
      dim(
        mine === theirs.head
          ? `${local} is up to date with ${tracking}`
          : `${local} already has everything in ${tracking}; \`tcm push ${name}\` publishes what's new here`,
      ),
    );
  }
  if (!cb.store.isAncestor(mine, theirs.head)) {
    // both sides changed since they last shared history: merge theirs in (one that leaves something to settle waits
    // apart, main as it was); `push` then publishes the result
    const r = mergeApart(cb, tracking, { command: `pull ${name} (merge)`, keep: local !== "main" });
    if (r.left.conflicts.length || r.left.failed.length) {
      if (local === "main")
        console.log(
          dim(
            `  (main isn't left with something to settle: pull ${tracking} into a branch of main's, settle it there, and land that)`,
          ),
        );
      return;
    }
    // (conflicts theirs has that are still conflicts here keep their sides; the merge labels its own)
    takeConflicts(cb, local, theirs.conflicts, "add");
    console.log(dim(`  \`tcm push ${name}\` to publish the merge`));
    return;
  }
  const before = stateOf(cb.store.names(undefined, true));
  cb.store.setHead(local, theirs.head, `pull ${name}`);
  // (and the sides of its conflicts, as its merge labelled them)
  takeConflicts(cb, local, theirs.conflicts, "add");
  cb.refreshShim();
  const after = stateOf(cb.store.names(undefined, true));
  printDiff(
    cb,
    diffNamespaces(before, after, (h) => cb.store.getDef(h)!.ir),
    before,
    after,
    `fast-forwarded ${local} to ${tracking}`,
  );
}

uses

used by