Skip to content
82 linesCodeBlameRaw
1/**
2 * Active branches as the overview and the Branches page show them, from
3 * the repos service's `branch_drift` answer. The counting itself is done
4 * there (services/repos/src/drift.rs), kept by the pair of head commits.
5 */
6import type { Branch, BranchDrifts, Commit, Pull } from "@g1t/contracts";
7
8import type { ActiveBranch } from "../components/branches";
9import type { ShownCommit } from "./commit-people";
10
11/**
12 * How far a branch has moved from the default branch: commits it has that
13 * the default branch does not (ahead), and commits the default branch has
14 * that it does not (behind), the way `git rev-list --left-right --count`
15 * says it.
16 */
17export type Drift = { ahead: number; behind: number };
18
19type Preview = { branch?: string | null; number?: number | null; url: string };
20
21/** A commit's people, as lib/commit-people.ts `showCommit` finds them. */
22export type People = (commit: Commit) => Pick<ShownCommit, "author" | "coAuthors">;
23
24/** Nobody matched to an account: the name on the commit. */
25const byName: People = (commit) => ({
26 author: { kind: "author", name: commit.author.name, username: null, avatar: null },
27 coAuthors: [],
28});
29
30/**
31 * A commit as a branch row shows it: its first line, and its people as
32 * `people` finds them (lib/branches.server.ts passes the accounts).
33 */
34export function summary(commit: Commit | null | undefined, people: People = byName): ActiveBranch["commit"] {
35 if (!commit) return null;
36 const shown = people(commit);
37 return { hash: commit.hash, message: commit.message.split("\n")[0] ?? "", author: shown.author, coAuthors: shown.coAuthors, at: commit.authoredAt };
38}
39
40/**
41 * The branches other than the default that are read (at most `read`, those
42 * with an open pull request first), how many there are, and the default
43 * branch's head commit.
44 */
45export function branchesToRead(
46 input: { defaultBranch: string; branches: Branch[]; pulls: Pick<Pull, "branch">[] },
47 read: number,
48): { reading: Branch[]; total: number; mainHead: string | null } {
49 const pullOn = new Set(input.pulls.flatMap((pull) => (pull.branch ? [pull.branch] : [])));
50 const others = input.branches.filter((branch) => branch.name !== input.defaultBranch);
51 const reading = [...others.filter((b) => pullOn.has(b.name)), ...others.filter((b) => !pullOn.has(b.name))].slice(0, read);
52 const mainHead = input.branches.find((branch) => branch.name === input.defaultBranch)?.hash || null;
53 return { reading, total: others.length, mainHead };
54}
55
56/**
57 * Each branch read, newest commit first, with what repos measured (null
58 * when that could not be had: no commits, no counts), its pull request and
59 * its preview.
60 */
61export function activeBranches(
62 reading: Branch[],
63 measured: BranchDrifts | null,
64 input: { pulls: Pick<Pull, "branch" | "number" | "title" | "checkStatus" | "status">[]; previews: Preview[] },
65 people: People = byName,
66): ActiveBranch[] {
67 const pullOn = new Map(input.pulls.flatMap((pull) => (pull.branch ? [[pull.branch, pull] as const] : [])));
68 const byHead = new Map((measured?.branches ?? []).map((found) => [found.head, found]));
69 return reading
70 .map((branch): ActiveBranch => {
71 const pull = pullOn.get(branch.name);
72 const found = byHead.get(branch.hash);
73 return {
74 name: branch.name,
75 commit: summary(found?.commit, people),
76 drift: found?.drift ?? null,
77 pull: pull ? { number: pull.number, title: pull.title, checkStatus: pull.checkStatus, draft: pull.status === "draft" } : null,
78 preview: input.previews.find((app) => app.branch === branch.name || (pull != null && app.number === pull.number))?.url ?? null,
79 };
80 })
81 .sort((a, b) => Date.parse(b.commit?.at ?? "0") - Date.parse(a.commit?.at ?? "0"));
82}