Skip to content
147 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 commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)1/**
2 * Who a commit is by, as g1t shows it: the account its author address
3 * belongs to, else the name written on the commit.
4 *
5 * Git records a name and an address; g1t matches the address to an
6 * account by a confirmed address or the account's noreply address
7 * (`email_owners` in services/identity), never an unconfirmed one. The
8 * address itself never reaches the page: a commit is shown with the
9 * person, not the address, so a private address stays private.
10 */
11import type { Blame, Commit, Contributor, EmailOwner } from "@g1t/contracts";
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.12import { codingAgentById, codingAgentFor } from "@g1t/contracts/coding-agents";
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)13
14/**
15 * One person on a commit. `user` is an account; `g1t` is g1t itself;
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.16 * `ghost` is an account that was deleted; `agent` is a known coding agent
17 * or bot, by the address on the commit (@g1t/contracts coding-agents);
18 * `author` is nobody on g1t, shown by the name on the commit, with a card
19 * that says so.
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)20 */
21export type CommitPerson = {
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.22 kind: "user" | "g1t" | "ghost" | "agent" | "author";
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)23 /** The name written on the commit. Beside an account it is a hint. */
24 name: string;
25 /** The account's username; null for `author`. */
26 username: string | null;
27 /** The account's uploaded avatar, by hash. */
28 avatar: string | null;
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.29 /** The coding agent's id, for an `agent`. */
30 agent?: string | null;
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)31};
32
33/** A commit as the page gets it: its people, never their addresses. */
34export type ShownCommit = Omit<Commit, "author"> & {
35 author: CommitPerson;
36 /** From `Co-authored-by` trailers, in order. */
37 coAuthors: CommitPerson[];
38};
39
40/** Who last changed each line, with the commits' people. */
41export type ShownBlame = Omit<Blame, "commits"> & { commits: ShownCommit[] };
42
43/** A commit as a file list's row shows it: no people at all. */
44export type FileCommit = Pick<Commit, "hash" | "message" | "authoredAt">;
45
46/** Each entry's last commit; `complete` is false when some were not reached. */
47export type FileCommits = { entries: { name: string; commit: FileCommit }[]; complete: boolean };
48
49/**
50 * The addresses on g1t's own commits: its noreply address, and those its
51 * agents and merge queue used before (as "g1t agent"), which history
52 * keeps. Every one is shown as g1t.
53 */
54export const G1T_COMMIT_EMAILS: ReadonlySet<string> = new Set([
55 "g1t@users.noreply.g1t.sh",
56 "agent@g1t.sh",
57 "queue@g1t.sh",
58 "mergecheck@g1t.sh",
59]);
60
61const CO_AUTHOR = /^co-authored-by:\s*(.*?)\s*<([^>]*)>\s*$/i;
62
63/** The name and address in each `Co-authored-by` trailer of a message. */
64export function coAuthorsOf(message: string): { name: string; email: string }[] {
65 const lines = message.trim().split("\n");
66 const last = lines.slice(lines.lastIndexOf("") + 1);
67 const found: { name: string; email: string }[] = [];
68 for (const line of last) {
69 const match = CO_AUTHOR.exec(line.trim());
70 if (match) found.push({ name: match[1]!.trim(), email: match[2]!.trim() });
71 }
72 return found;
73}
74
75/** Lowercased and trimmed, as `email_owners` keys them. */
76export function normalizeEmail(email: string): string {
77 return email.trim().toLowerCase();
78}
79
80/**
81 * Every distinct address the commits name, authors and co-authors, that
82 * needs asking about: g1t's own are known already.
83 */
84export function addressesToMatch(commits: readonly Pick<Commit, "author" | "message">[]): string[] {
85 const seen = new Set<string>();
86 const add = (email: string) => {
87 const one = normalizeEmail(email);
88 if (one && one.includes("@") && !G1T_COMMIT_EMAILS.has(one)) seen.add(one);
89 };
90 for (const commit of commits) {
91 add(commit.author.email);
92 for (const co of coAuthorsOf(commit.message)) add(co.email);
93 }
94 return [...seen];
95}
96
97/** The person a name and address on a commit belong to. */
98export function personFor(name: string, email: string, owners: Readonly<Record<string, EmailOwner>>): CommitPerson {
99 const written = name.trim() || "Unknown";
100 const address = normalizeEmail(email);
101 if (G1T_COMMIT_EMAILS.has(address)) return { kind: "g1t", name: written, username: "g1t", avatar: null };
102 const owner = address ? owners[address] : undefined;
103 if (owner?.username === "ghost") return { kind: "ghost", name: written, username: "ghost", avatar: null };
104 if (owner) return { kind: "user", name: written, username: owner.username, avatar: owner.avatar };
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.105 const agent = codingAgentFor(address);
106 if (agent) return { kind: "agent", name: written, username: null, avatar: null, agent: agent.id };
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)107 return { kind: "author", name: written, username: null, avatar: null };
108}
109
110/** A commit with its people found and its author's address left out. */
111export function showCommit(commit: Commit, owners: Readonly<Record<string, EmailOwner>>): ShownCommit {
112 const { author, ...rest } = commit;
113 return {
114 ...rest,
115 author: personFor(author.name, author.email, owners),
116 coAuthors: coAuthorsOf(commit.message).map((co) => personFor(co.name, co.email, owners)),
117 };
118}
119
120/**
121 * A repository's contributor as a person on its commits. The repos
122 * service tallies them by the same rules (services/repos/src/contributors.rs:
123 * `email_owners`, then g1t's addresses, then the name), so the About, the
124 * Contributors page and every commit show one person the same way.
125 */
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.126export function contributorPerson(contributor: Pick<Contributor, "kind" | "name" | "username" | "avatar" | "agent">): CommitPerson {
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)127 if (contributor.kind === "g1t") return { kind: "g1t", name: contributor.name, username: "g1t", avatar: null };
128 if (contributor.kind === "user" && contributor.username === "ghost") return { kind: "ghost", name: contributor.name, username: "ghost", avatar: null };
129 if (contributor.kind === "user" && contributor.username) {
130 return { kind: "user", name: contributor.name, username: contributor.username, avatar: contributor.avatar ?? null };
131 }
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.132 if (contributor.kind === "agent" && codingAgentById(contributor.agent)) {
133 return { kind: "agent", name: contributor.name, username: null, avatar: null, agent: contributor.agent ?? null };
134 }
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)135 return { kind: "author", name: contributor.name, username: null, avatar: null };
136}
137
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.138/** What a person on a commit is called on the page: an agent by its own name, whatever the commit wrote. */
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)139export function shownName(person: CommitPerson): string {
Coding agents show as themselves on commits and contributors: Claude, Codex, Copilot, Gemini, Jules, Cursor, Devin, Aider, Dependabot, Renovate and GitHub Actions are known by the address they write on their commits (never by name), shown with their maker's mark, their own name and an agent or bot label, and a card saying what each is. Anyone else not on g1t gets a card too, saying so and how to claim the commits by adding the address. Contributor lists are worked out again (stats version 2) so an agent counts once.140 if (person.kind === "agent") return codingAgentById(person.agent)?.name ?? person.name;
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)141 return person.username ?? person.name;
142}
143
144/** Where a person on a commit links to, if anywhere. */
145export function profileHref(person: CommitPerson): string | null {
146 return person.kind === "user" && person.username ? `/u/${person.username}` : null;
147}

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