Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
129 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.

Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971/**
2 * What the Files page's About, the Contributors, Releases and Activity
3 * pages say, worked out without the server: labels, the language bar,
4 * and an activity line for each event worth one.
5 */
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)6import type { G1tEvent, LanguageShare, License, Project, WeekCommits } from "@g1t/contracts";
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb977
8/**
9 * The address the About links to: the project's homepage (its own, or its
10 * repository's website it follows), else production when it runs
11 * elsewhere. Null leaves the repository's website.
12 */
13export function projectHomepage(project: Pick<Project, "links" | "runs" | "productionUrl"> | null): string | null {
14 if (!project) return null;
15 return project.links?.homepage ?? (project.runs === "elsewhere" ? project.productionUrl : null) ?? null;
16}
17
18/** "MIT license", as the About names it; "View license" for one not recognized. */
19export function licenseLabel(license: License): string {
20 return license.spdxId ? `${license.spdxId} license` : "View license";
21}
22
23/** "12", "1.2k", "15k". */
24export function compact(n: number): string {
25 return n >= 10_000 ? `${Math.round(n / 1000)}k` : n >= 1000 ? `${(n / 1000).toFixed(1).replace(/\.0$/, "")}k` : String(n);
26}
27
28/** "1 star", "1.2k stars". */
29export function count(n: number, one: string, many = `${one}s`): string {
30 return `${compact(n)} ${n === 1 ? one : many}`;
31}
32
33/** The language bar's pieces: the largest, then the rest under 1% as Other. */
The repository sidebar reads like it should. TSX counts as TypeScript, as JSX already counted as JavaScript, so one language is not split in two (stats version 3, so every repository is read again); the languages list every one down to a tenth of a percent, the largest eight, rather than folding MDX, CSS and Shell into Other; up to five contributors are listed by name, each with their commits and card, more as avatars; and with no release published, Releases says how many tags there are, linked to them.34export function languageBar(languages: LanguageShare[], least = 0.1, most = 8): LanguageShare[] {
35 // Every language down to a tenth of a percent, the largest `most` of them; the rest as Other.
36 const sorted = [...languages].sort((a, b) => b.percent - a.percent);
37 const shown = sorted.filter((language, index) => language.percent >= least && index < most);
38 const rest = sorted.filter((language) => !shown.includes(language));
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9739 if (rest.length === 0) return shown;
40 const bytes = rest.reduce((sum, language) => sum + language.bytes, 0);
41 const percent = Math.round(rest.reduce((sum, language) => sum + language.percent, 0) * 10) / 10;
42 if (percent === 0 && shown.length > 0) return shown;
43 return [...shown, { name: "Other", color: null, bytes, percent }];
44}
45
46
47/** The tallest week, for a chart's scale: never zero. */
48export function peak(weeks: WeekCommits[]): number {
49 return Math.max(1, ...weeks.map((week) => week.commits));
50}
51
52/** A contributor's weeks laid over the repository's, missing weeks as none. */
53export function alignWeeks(all: WeekCommits[], own: WeekCommits[]): number[] {
54 const byWeek = new Map(own.map((week) => [week.week, week.commits]));
55 return all.map((week) => byWeek.get(week.week) ?? 0);
56}
57
58/** An Activity line: what happened, and the branch, tag or pull request it was to. */
59export type ActivityLine =
60 | { kind: "push"; branch: string; commit: string; before: string | null; created: boolean; defaultBranch: boolean }
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.61 | { kind: "deleted"; branch: string; commit: string }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9762 | { kind: "tag"; tag: string; commit: string }
63 | { kind: "merge"; number: number; commit: string }
64 | { kind: "renamed"; from: string; to: string }
65 | { kind: "default"; from: string; to: string };
66
67/** The events the Activity page lists. */
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.68export const ACTIVITY_TYPES = ["git.push", "git.deleted", "pull.merged", "branch.renamed", "repo.default_branch_changed"] as const;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9769
70/** One event as an Activity line, or null for one it does not list. */
71export function activityLine(event: G1tEvent): ActivityLine | null {
72 switch (event.type) {
73 case "git.push": {
74 const data = event.data as { ref: string; before?: string | null; after: string; defaultBranch: boolean };
75 if (data.ref.startsWith("refs/tags/")) return { kind: "tag", tag: data.ref.slice("refs/tags/".length), commit: data.after };
76 if (!data.ref.startsWith("refs/heads/")) return null;
77 return {
78 kind: "push",
79 branch: data.ref.slice("refs/heads/".length),
80 commit: data.after,
81 before: data.before ?? null,
82 created: !data.before,
83 defaultBranch: data.defaultBranch,
84 };
85 }
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 case "git.deleted": {
87 // A deleted tag is not listed; a deleted branch is, with the commit
88 // it pointed to, which restores it.
89 const data = event.data as { ref: string; before: string };
90 if (!data.ref.startsWith("refs/heads/")) return null;
91 return { kind: "deleted", branch: data.ref.slice("refs/heads/".length), commit: data.before };
92 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9793 case "pull.merged": {
94 const data = event.data as { number: number; commit: string };
95 return { kind: "merge", number: data.number, commit: data.commit };
96 }
97 case "branch.renamed": {
98 const data = event.data as { from: string; to: string };
99 return { kind: "renamed", from: data.from, to: data.to };
100 }
101 case "repo.default_branch_changed": {
102 const data = event.data as { from: string; to: string; renamed: boolean };
103 // A rename of the default branch says so once, as a rename.
104 return data.renamed ? null : { kind: "default", from: data.from, to: data.to };
105 }
106 default:
107 return null;
108 }
109}
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.110
111/**
112 * Whether an Activity line is about `branch`: pushed to, made, deleted or
113 * renamed, or made or replaced as the default. Merges land on the default
114 * branch (`defaultBranch`).
115 */
116export function aboutBranch(line: ActivityLine, branch: string, defaultBranch: string): boolean {
117 switch (line.kind) {
118 case "push":
119 case "deleted":
120 return line.branch === branch;
121 case "tag":
122 return false;
123 case "merge":
124 return branch === defaultBranch;
125 case "renamed":
126 case "default":
127 return line.from === branch || line.to === branch;
128 }
129}

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