| 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 | */ |
| 6 | import type { Branch, BranchDrifts, Commit, Pull } from "@g1t/contracts"; |
| 7 | |
| 8 | import type { ActiveBranch } from "../components/branches"; |
| 9 | import 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 | */ |
| 17 | export type Drift = { ahead: number; behind: number }; |
| 18 | |
| 19 | type Preview = { branch?: string | null; number?: number | null; url: string }; |
| 20 | |
| 21 | /** A commit's people, as lib/commit-people.ts `showCommit` finds them. */ |
| 22 | export type People = (commit: Commit) => Pick<ShownCommit, "author" | "coAuthors">; |
| 23 | |
| 24 | /** Nobody matched to an account: the name on the commit. */ |
| 25 | const 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 | */ |
| 34 | export 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 | */ |
| 45 | export 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 | */ |
| 61 | export 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 | } |