MergeBox

tcmts.share.MergeBox · #tow2arop · term · runs on the server

uses tcmts.share.shareNow, which uses process

Whether it merges, and what the viewer may do about it: merge (the maintainer), close or reopen (theirs too).

import type { Mergeable } from "#codebase/tcmts/cli/contributions";
import { shortHash } from "#codebase/tcmts/core/hash";
import { ActButton, at, shareNow } from "#codebase/tcmts/share";
import type { Contribution } from "#codebase/tcmts/store";
import { Link } from "react-aria-components";

export function MergeBox({ c, m, tests }: { c: Contribution; m: Mergeable | null; tests: number }) {
  const s = shareNow();
  const mine = s.caller.role === "maintainer" || (s.caller.role === "contributor" && c.source.startsWith(`@${s.caller.handle}/`));
  if (c.status === "merged")
    return (
      <div className="mergebox">
        <span className="big">⑂</span>
        <div>
          <b>
            Merged into <code>{c.target}</code>
          </b>
          <div className="muted">
            as <a className="hash" href={at(`/history/${c.mergedAs ?? ""}`)}>{shortHash(c.mergedAs ?? "")}</a>
          </div>
        </div>
      </div>
    );
  if (c.status === "closed")
    return (
      <div className="mergebox">
        <span className="big">✕</span>
        <div>
          <b>Closed without merging</b>
        </div>
        {mine && (
          <span className="right">
            <ActButton id={c.id} what="reopen" label="Reopen" />
          </span>
        )}
      </div>
    );
  const good = !!m && m.kind !== "conflicts";
  const title = !m ? "Its branches are gone" : m.kind === "conflicts" ? `Conflicts with ${c.target}` : m.kind === "up-to-date" ? `${c.target} already has these changes` : m.kind === "fast-forward" ? "Merges as a fast-forward" : "Merges cleanly";
  const detail = !m
    ? ""
    : m.kind === "conflicts"
      ? `In ${m.names.join(", ")}. The contributor brings ${c.target} in (tcm pull origin ${c.target} on the branch), settles them, and pushes again.`
      : `${m.kind === "clean" && m.asText?.length ? `${m.asText.join(", ")} merged as text: tried, and checked to typecheck, before it lands. ` : "No conflicts. "}${tests ? `${tests} test${tests > 1 ? "s" : ""} reach this change: they run again wherever it's pulled.` : ""}`;
  return (
    <div className={`mergebox ${good ? "good" : "blocked"}`}>
      <span className={`big ${good ? "ok" : "bad"}`}>{good ? "✓" : "✕"}</span>
      <div>
        <b>{title}</b>
        <div className="muted">{detail}</div>
      </div>
      <span className="right" style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
        {s.caller.role === "maintainer" && good && <ActButton id={c.id} what="merge" label={`Merge into ${c.target}`} kind="primary" />}
        {mine && <ActButton id={c.id} what="close" label="Close" kind="danger" />}
        {s.caller.role === "reader" && (
          <Link className="btn" href={at(`/sign-in?then=${encodeURIComponent(`/contributions/${c.id}`)}`)}>
            Sign in to review
          </Link>
        )}
      </span>
    </div>
  );
}

uses

used by