| 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 | |
| 10 | /** |
| 11 | * How far a branch has moved from the default branch: commits it has that |
| 12 | * the default branch does not (ahead), and commits the default branch has |
| 13 | * that it does not (behind), the way `git rev-list --left-right --count` |
| 14 | * says it. |
| 15 | */ |
| 16 | export type Drift = { ahead: number; behind: number }; |
| 17 | |
| 18 | type Preview = { branch?: string | null; number?: number | null; url: string }; |
| 19 | |
| 20 | /** A commit as a branch row shows it: its first line. */ |
| 21 | export const summary = (commit: Commit | null | undefined): ActiveBranch["commit"] => |
| 22 | commit ? { hash: commit.hash, message: commit.message.split("\n")[0] ?? "", author: commit.author.name, at: commit.authoredAt } : null; |
| 23 | |
| 24 | /** |
| 25 | * The branches other than the default that are read (at most `read`, those |
| 26 | * with an open pull request first), how many there are, and the default |
| 27 | * branch's head commit. |
| 28 | */ |
| 29 | export function branchesToRead( |
| 30 | input: { defaultBranch: string; branches: Branch[]; pulls: Pick<Pull, "branch">[] }, |
| 31 | read: number, |
| 32 | ): { reading: Branch[]; total: number; mainHead: string | null } { |
| 33 | const pullOn = new Set(input.pulls.flatMap((pull) => (pull.branch ? [pull.branch] : []))); |
| 34 | const others = input.branches.filter((branch) => branch.name !== input.defaultBranch); |
| 35 | const reading = [...others.filter((b) => pullOn.has(b.name)), ...others.filter((b) => !pullOn.has(b.name))].slice(0, read); |
| 36 | const mainHead = input.branches.find((branch) => branch.name === input.defaultBranch)?.hash || null; |
| 37 | return { reading, total: others.length, mainHead }; |
| 38 | } |
| 39 | |
| 40 | /** |
| 41 | * Each branch read, newest commit first, with what repos measured (null |
| 42 | * when that could not be had: no commits, no counts), its pull request and |
| 43 | * its preview. |
| 44 | */ |
| 45 | export function activeBranches( |
| 46 | reading: Branch[], |
| 47 | measured: BranchDrifts | null, |
| 48 | input: { pulls: Pick<Pull, "branch" | "number" | "title" | "checkStatus" | "status">[]; previews: Preview[] }, |
| 49 | ): ActiveBranch[] { |
| 50 | const pullOn = new Map(input.pulls.flatMap((pull) => (pull.branch ? [[pull.branch, pull] as const] : []))); |
| 51 | const byHead = new Map((measured?.branches ?? []).map((found) => [found.head, found])); |
| 52 | return reading |
| 53 | .map((branch): ActiveBranch => { |
| 54 | const pull = pullOn.get(branch.name); |
| 55 | const found = byHead.get(branch.hash); |
| 56 | return { |
| 57 | name: branch.name, |
| 58 | commit: summary(found?.commit), |
| 59 | drift: found?.drift ?? null, |
| 60 | pull: pull ? { number: pull.number, title: pull.title, checkStatus: pull.checkStatus, draft: pull.status === "draft" } : null, |
| 61 | preview: input.previews.find((app) => app.branch === branch.name || (pull != null && app.number === pull.number))?.url ?? null, |
| 62 | }; |
| 63 | }) |
| 64 | .sort((a, b) => Date.parse(b.commit?.at ?? "0") - Date.parse(a.commit?.at ?? "0")); |
| 65 | } |