Skip to content
193 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Usage lists Other once: kinds of work without words of their own are added into it1/**
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.2 * Branches as the overview and the Branches page show them, from the
3 * repos service's `branch_drift` answer. The counting itself is done
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 254 * 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";
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)9import type { ShownCommit } from "./commit-people";
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2510
11/**
Usage, Billing settings and prepaid AI credit; fixes from the UX audit12 * 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`
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2515 * says it.
Usage, Billing settings and prepaid AI credit; fixes from the UX audit16 */
17export type Drift = { ahead: number; behind: number };
18
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2519type Preview = { branch?: string | null; number?: number | null; url: string };
Usage, Billing settings and prepaid AI credit; fixes from the UX audit20
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.21type PullOn = Pick<Pull, "branch" | "number" | "title" | "checkStatus" | "status">;
22
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)23/** A commit's people, as lib/commit-people.ts `showCommit` finds them. */
24export type People = (commit: Commit) => Pick<ShownCommit, "author" | "coAuthors">;
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2525
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)26/** Nobody matched to an account: the name on the commit. */
27const byName: People = (commit) => ({
28 author: { kind: "author", name: commit.author.name, username: null, avatar: null },
29 coAuthors: [],
30});
31
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.32/** Whether a pull request is still open: ready for review or a draft. */
33const isOpen = (pull: Pick<Pull, "status">) => pull.status === "open" || pull.status === "draft";
34
Usage, Billing settings and prepaid AI credit; fixes from the UX audit35/**
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)36 * A commit as a branch row shows it: its first line, and its people as
37 * `people` finds them (lib/branches.server.ts passes the accounts).
38 */
39export function summary(commit: Commit | null | undefined, people: People = byName): ActiveBranch["commit"] {
40 if (!commit) return null;
41 const shown = people(commit);
42 return { hash: commit.hash, message: commit.message.split("\n")[0] ?? "", author: shown.author, coAuthors: shown.coAuthors, at: commit.authoredAt };
43}
44
45/**
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2546 * The branches other than the default that are read (at most `read`, those
47 * with an open pull request first), how many there are, and the default
48 * branch's head commit.
Usage lists Other once: kinds of work without words of their own are added into it49 */
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2550export function branchesToRead(
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.51 input: { defaultBranch: string; branches: Branch[]; pulls: Pick<Pull, "branch" | "status">[] },
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2552 read: number,
53): { reading: Branch[]; total: number; mainHead: string | null } {
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.54 const pullOn = new Set(input.pulls.flatMap((pull) => (pull.branch && isOpen(pull) ? [pull.branch] : [])));
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2555 const others = input.branches.filter((branch) => branch.name !== input.defaultBranch);
56 const reading = [...others.filter((b) => pullOn.has(b.name)), ...others.filter((b) => !pullOn.has(b.name))].slice(0, read);
57 const mainHead = input.branches.find((branch) => branch.name === input.defaultBranch)?.hash || null;
58 return { reading, total: others.length, mainHead };
Usage lists Other once: kinds of work without words of their own are added into it59}
60
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2561/**
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.62 * Each branch's pull request: the open one, else the newest that was
63 * merged or closed. `pulls` come newest first, as `listPulls` answers.
64 */
65function pullsByBranch(pulls: PullOn[]): Map<string, PullOn> {
66 const found = new Map<string, PullOn>();
67 for (const pull of pulls) {
68 if (!pull.branch) continue;
69 const kept = found.get(pull.branch);
70 if (!kept || (!isOpen(kept) && isOpen(pull))) found.set(pull.branch, pull);
71 }
72 return found;
73}
74
75/**
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2576 * Each branch read, newest commit first, with what repos measured (null
77 * when that could not be had: no commits, no counts), its pull request and
78 * its preview.
79 */
80export function activeBranches(
81 reading: Branch[],
82 measured: BranchDrifts | null,
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.83 input: { pulls: PullOn[]; previews: Preview[] },
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)84 people: People = byName,
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2585): ActiveBranch[] {
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.86 const pullOn = pullsByBranch(input.pulls);
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2587 const byHead = new Map((measured?.branches ?? []).map((found) => [found.head, found]));
88 return reading
89 .map((branch): ActiveBranch => {
90 const pull = pullOn.get(branch.name);
91 const found = byHead.get(branch.hash);
92 return {
93 name: branch.name,
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.94 head: branch.hash,
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)95 commit: summary(found?.commit, people),
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2596 drift: found?.drift ?? null,
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.97 pull: pull ? { number: pull.number, title: pull.title, checkStatus: pull.checkStatus, draft: pull.status === "draft", status: pull.status } : null,
98 preview: input.previews.find((app) => app.branch === branch.name || (pull != null && isOpen(pull) && app.number === pull.number))?.url ?? null,
Merge project overview: one branch_drift call, spliced histories, cached tags, 6 repos calls instead of 2599 };
100 })
101 .sort((a, b) => Date.parse(b.commit?.at ?? "0") - Date.parse(a.commit?.at ?? "0"));
Usage lists Other once: kinds of work without words of their own are added into it102}
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.103
104// --- The Branches page: its tabs and sections --------------------------------
105
106/** The Branches page's tabs, kept in the address as `?tab=`. */
107export const BRANCH_TABS = ["overview", "yours", "active", "stale", "all"] as const;
108export type BranchTab = (typeof BRANCH_TABS)[number];
109
110/** A tab from the address: the overview for anything else. */
111export function branchTab(value: string | null | undefined): BranchTab {
112 return (BRANCH_TABS as readonly string[]).includes(value ?? "") ? (value as BranchTab) : "overview";
113}
114
115/** A branch whose last commit is older than this is stale. */
116export const STALE_DAYS = 90;
117
118/** Whether a branch's last commit is more than `STALE_DAYS` old. A branch whose commit was not read is not. */
119export function isStale(branch: Pick<ActiveBranch, "commit">, now: number): boolean {
120 const at = branch.commit ? Date.parse(branch.commit.at) : Number.NaN;
121 return Number.isFinite(at) && at < now - STALE_DAYS * 86_400_000;
122}
123
124/** Whether `username` made a branch's latest commit. */
125export function isYours(branch: Pick<ActiveBranch, "commit">, username: string | null | undefined): boolean {
126 return Boolean(username) && branch.commit?.author.username?.toLowerCase() === username?.toLowerCase();
127}
128
129/**
130 * Whether deleting a branch loses nothing the default branch lacks: every
131 * commit on it is on the default branch, or its pull request was merged.
132 * Such a branch is deleted without asking.
133 */
134export function isMerged(branch: Pick<ActiveBranch, "drift" | "pull">): boolean {
135 return branch.drift?.ahead === 0 || branch.pull?.status === "merged";
136}
137
138export type BranchSection = {
139 tab: Exclude<BranchTab, "overview">;
140 title: string;
141 /** Under the title, when it needs saying. */
142 note?: string;
143 branches: ActiveBranch[];
144 /** How many more the section's own tab lists. */
145 more: number;
146};
147
148/** How many branches each section of the overview shows. */
149export const OVERVIEW_SHOWN = 5;
150
151/**
152 * The sections a tab lists, besides the default branch: the overview
153 * shows a few of the viewer's branches, the active ones and the stale ones; each other
154 * tab lists its own.
155 */
156export function branchSections(
157 branches: ActiveBranch[],
158 tab: BranchTab,
159 viewer: { username: string | null; now: number },
160 shown = OVERVIEW_SHOWN,
161): BranchSection[] {
162 const yours = branches.filter((branch) => isYours(branch, viewer.username));
163 const active = branches.filter((branch) => !isStale(branch, viewer.now));
164 const stale = branches.filter((branch) => isStale(branch, viewer.now));
165 const whole = (section: Omit<BranchSection, "more">): BranchSection => ({ ...section, more: 0 });
166 switch (tab) {
167 case "overview": {
168 const few = (section: Omit<BranchSection, "more">): BranchSection => ({
169 ...section,
170 branches: section.branches.slice(0, shown),
171 more: Math.max(0, section.branches.length - shown),
172 });
173 return [
174 ...(yours.length > 0 ? [few({ tab: "yours", title: "Your branches", branches: yours })] : []),
175 few({ tab: "active", title: "Active branches", branches: active }),
176 ...(stale.length > 0 ? [few({ tab: "stale", title: "Stale branches", branches: stale })] : []),
177 ];
178 }
179 case "yours":
180 return [whole({ tab, title: "Your branches", note: "Branches whose latest commit is yours.", branches: yours })];
181 case "active":
182 return [whole({ tab, title: "Active branches", note: `Commits in the last ${STALE_DAYS} days.`, branches: active })];
183 case "stale":
184 return [whole({ tab, title: "Stale branches", note: `No commits in the last ${STALE_DAYS} days.`, branches: stale })];
185 case "all":
186 return [whole({ tab, title: "All branches", branches })];
187 }
188}
189
190/** The longest behind or ahead count among `branches`, which a full bar stands for. */
191export function driftScale(branches: Pick<ActiveBranch, "drift">[]): number {
192 return Math.max(1, ...branches.map((branch) => Math.max(branch.drift?.ahead ?? 0, branch.drift?.behind ?? 0)));
193}

This file's history is long; its oldest lines are credited to the oldest commit read.