Skip to content

Commit

Mission control: a push is a person's by the account that made it, not the name on its commits

People's direct pushes to the default branch were told from g1t's by the commit author's name, so a person whose git name is "g1t" lost their commits, and g1t pushing under another name counted as a person. placePushes now reads each git.push event's actor: g1t's accounts (usr_g1t_agent, g1t_policy) and a workflow job's token (causedByJob) bring no people's commits. Within a person's push, commits g1t wrote are told by their author address (G1T_COMMIT_EMAILS), never the name. The git.push contract type gains causedByJob, which the event already carried. Tests: a person named "g1t" counts, g1t under a person's name does not, job pushes do not. Docs: mission control says how a push is counted.

syntaqxcommitted Parentcbc5e17Browse files
5 files+95−320/5 viewed
+4−1
566566 their own*. An agent's change landed without you when g1t merged it, by
567567 auto-merge or from the [merge queue](/guides/merge-queue/), with no person
568568 pressing merge. People's changes are their merged pull requests and the
569−commits they pushed straight to the default branch. **Review N that need you** jumps to the
569+commits they pushed straight to the default branch. A push is a person's
570+by the account that signed in to make it, not by the name on its commits:
571+pushes by g1t or a workflow job's token, and commits g1t wrote, are not
572+counted as people's. **Review N that need you** jumps to the
570573 list, and **New issue** opens a new issue in the project you pick.
571574
572575 | Across the top | What it counts |
+33−11
2121 sortRows,
2222 stallReason,
2323 summaryLine,
24+ pushedByPerson,
2425 pushedCommits,
2526 waitingRows,
2627 weekOf,
175176 files: [],
176177 });
177178
178−test("a push to the default branch counts a person's own commits, not merges or agents'", () => {
179− const c = (hash: string, author: string, parents = 1) => ({ hash, author: { name: author }, parents: Array(parents).fill("p") });
180− const history = [c("e", "Chase"), c("d", "g1t"), c("m", "g1t", 2), c("b", "Chase"), c("a", "Chase")];
181− assert.deepEqual(pushedCommits(history, "a").map((x) => x.hash), ["e", "b"]);
179+test("a push to the default branch counts a person's own commits, not merges or g1t's", () => {
180+ const c = (hash: string, email: string, parents = 1) => ({ hash, author: { name: "g1t", email }, parents: Array(parents).fill("p") });
181+ // Every commit is named "g1t": the name decides nothing, the address does.
182+ const history = [c("e", "chase@example.com"), c("d", "agent@g1t.sh"), c("m", "chase@example.com", 2), c("b", "chase@example.com"), c("a", "chase@example.com")];
183+ const byG1t = (commit: { author: { email: string } }) => commit.author.email === "agent@g1t.sh";
184+ assert.deepEqual(pushedCommits(history, "a", byG1t).map((x) => x.hash), ["e", "b"]);
182185 // Where it pointed before was not read: everything read counts.
183− assert.deepEqual(pushedCommits(history, "zz").map((x) => x.hash), ["e", "b", "a"]);
186+ assert.deepEqual(pushedCommits(history, "zz", byG1t).map((x) => x.hash), ["e", "b", "a"]);
187+});
188+
189+test("who pushed is the account that signed in, not the name on the commits", () => {
190+ assert.ok(pushedByPerson({ actor: "usr_chase", data: {} }));
191+ assert.ok(pushedByPerson({ actor: null, data: {} }));
192+ assert.ok(!pushedByPerson({ actor: "usr_g1t_agent", data: {} }));
193+ assert.ok(!pushedByPerson({ actor: "g1t_policy", data: {} }));
194+ // A workflow job's own token is not a person either.
195+ assert.ok(!pushedByPerson({ actor: "usr_chase", data: { causedByJob: "run_1" } }));
196+
197+ const c = (hash: string, name: string) => ({ hash, parents: ["p"], author: { name, email: `${hash}@example.com` } });
198+ // A person whose git name is "g1t" pushed m1; g1t pushed w1 under a person's name.
199+ const history = [c("w1", "Chase Pierce"), c("m1", "g1t"), c("old", "Chase Pierce")];
200+ const pushes = [
201+ { time: "2026-10-07T12:00:00Z", actor: "usr_g1t_agent", data: { after: "w1", before: "m1" } },
202+ { time: "2026-10-05T12:00:00Z", actor: "usr_chase", data: { after: "m1", before: "old" } },
203+ ];
204+ assert.deepEqual(placePushes(history, pushes), [{ hash: "m1", at: "2026-10-05T12:00:00Z" }]);
184205 });
185206
186207 test("a change landed without a person when g1t merged it", () => {
373394 });
374395
375396 test("each push to the default branch places the commits it brought, at its time, from one read of the history", () => {
376− const c = (hash: string, parents = ["p"], author = "Chase Pierce") => ({ hash, parents, author: { name: author } });
397+ const c = (hash: string, parents = ["p"], email = "chase@example.com") => ({ hash, parents, author: { name: "Chase Pierce", email } });
377398 // Newest first: a Wednesday push of two, a Monday push of one, a merge, and g1t's own commit.
378− const history = [c("w2"), c("w1"), c("m1"), c("merge", ["a", "b"]), c("bot", ["p"], "g1t"), c("old")];
399+ const history = [c("w2"), c("w1"), c("m1"), c("merge", ["a", "b"]), c("bot", ["p"], "g1t@users.noreply.g1t.sh"), c("old")];
379400 const pushes = [
380− { time: "2026-10-07T12:00:00Z", data: { after: "w2", before: "m1" } },
381− { time: "2026-10-05T12:00:00Z", data: { after: "m1", before: "old" } },
382− { time: "2026-10-01T12:00:00Z", data: { after: "gone", before: "older" } },
401+ { time: "2026-10-07T12:00:00Z", actor: "usr_chase", data: { after: "w2", before: "m1" } },
402+ { time: "2026-10-05T12:00:00Z", actor: "usr_chase", data: { after: "m1", before: "old" } },
403+ { time: "2026-10-01T12:00:00Z", actor: "usr_chase", data: { after: "gone", before: "older" } },
383404 ];
384− assert.deepEqual(placePushes(history, pushes), [
405+ const byG1t = (commit: { author: { email: string } }) => commit.author.email.endsWith("@users.noreply.g1t.sh");
406+ assert.deepEqual(placePushes(history, pushes, byG1t), [
385407 { hash: "m1", at: "2026-10-05T12:00:00Z" },
386408 { hash: "w2", at: "2026-10-07T12:00:00Z" },
387409 { hash: "w1", at: "2026-10-07T12:00:00Z" },
+50−17
432432
433433 // --- Landed -----------------------------------------------------------------
434434
435−/** A merged pull request, as the week counts it. */
436435 /**
437− * The commits a push to the default branch brought, from the history at its
438− * `after` (newest first) back to its `before`: people's own, not merges (a
439− * pull request landing) and not agents'. A push whose `before` is not in
440− * what was read gives what was read.
436+ * The accounts g1t itself acts as on the event log: its agent, and the
437+ * policy that merges and pushes for it. An event's `actor` is an account
438+ * id, so this is what tells g1t apart, whatever name a commit carries.
439+ */
440+export const G1T_ACCOUNT_IDS: ReadonlySet<string> = new Set(["usr_g1t_agent", "g1t_policy"]);
441+
442+/** A `git.push` from the log, as far as placing its commits needs it. */
443+export type PushRecord = {
444+ time: string;
445+ /** The account that pushed; null when the log does not say. */
446+ actor?: string | null;
447+ data: { after: string; before?: string; causedByJob?: string };
448+};
449+
450+/**
451+ * Whether a person pushed: not g1t or one of its agents (by the account
452+ * that signed in to push), and not a workflow job's own token.
441453 */
442−export function pushedCommits<C extends { hash: string; parents: string[]; author: { name: string } }>(
454+export function pushedByPerson(push: Pick<PushRecord, "actor" | "data">): boolean {
455+ if (push.data.causedByJob) return false;
456+ return !(push.actor && G1T_ACCOUNT_IDS.has(push.actor));
457+}
458+
459+/**
460+ * The commits a person's push to the default branch brought, from the
461+ * history at its `after` (newest first) back to its `before`: their own,
462+ * not merges (a pull request landing) and not g1t's (`byG1t`: a commit
463+ * g1t wrote, told by its author address, which a person may fast-forward
464+ * onto the branch). A push whose `before` is not in what was read gives
465+ * what was read. Who pushed is the caller's to decide (`pushedByPerson`);
466+ * a commit's author name says nothing about it.
467+ */
468+export function pushedCommits<C extends { hash: string; parents: string[] }>(
443469 history: C[],
444470 before: string | undefined,
471+ byG1t: (commit: C) => boolean = () => false,
445472 ): C[] {
446473 const end = before ? history.findIndex((commit) => commit.hash === before) : -1;
447474 const brought = end === -1 ? history : history.slice(0, end);
448− return brought.filter((commit) => commit.parents.length <= 1 && !isAgent(commit.author.name));
475+ return brought.filter((commit) => commit.parents.length <= 1 && !byG1t(commit));
449476 }
450477
451478 /**
452− * Each commit of `history` (newest first) a push in `pushes` (newest
453− * first) brought, at that push's time. A commit pushed twice (after a
454− * force push, say) counts once, at its first landing; a push whose `after`
455− * is no longer in the history (rewritten) brings nothing.
479+ * Each commit of `history` (newest first) a person's push in `pushes`
480+ * (newest first) brought, at that push's time. A commit pushed twice
481+ * (after a force push, say) counts once, at its first landing; a push
482+ * whose `after` is no longer in the history (rewritten) brings nothing,
483+ * and g1t's own pushes bring nothing here: pull requests count those.
456484 */
457−export function placePushes<C extends { hash: string; parents: string[]; author: { name: string } }>(
485+export function placePushes<C extends { hash: string; parents: string[] }>(
458486 history: C[],
459− pushes: { time: string; data: { after: string; before?: string } }[],
487+ pushes: PushRecord[],
488+ byG1t: (commit: C) => boolean = () => false,
460489 ): { hash: string; at: string }[] {
461490 const index = new Map(history.map((commit, i) => [commit.hash, i]));
462− const seen = new Map<string, string>();
491+ // Each commit's first landing: the time, or null when g1t landed it.
492+ const seen = new Map<string, string | null>();
463493 for (const push of [...pushes].reverse()) {
464494 const start = index.get(push.data.after);
465495 if (start === undefined) continue;
466496 const end = push.data.before ? index.get(push.data.before) : undefined;
467− for (const commit of pushedCommits(history.slice(start, end ?? history.length), undefined)) {
468− if (!seen.has(commit.hash)) seen.set(commit.hash, push.time);
497+ const range = history.slice(start, end ?? history.length);
498+ const at = pushedByPerson(push) ? push.time : null;
499+ for (const commit of pushedCommits(range, undefined, byG1t)) {
500+ if (!seen.has(commit.hash)) seen.set(commit.hash, at);
469501 }
470502 }
471− return [...seen].map(([hash, at]) => ({ hash, at }));
503+ return [...seen].flatMap(([hash, at]) => (at ? [{ hash, at }] : []));
472504 }
473505
506+/** A merged pull request, as the week counts it. */
474507 export type Merged = {
475508 repo: RepoPath;
476509 number: number;
+3−1
3737 stuckMinutes,
3838 waitedFor,
3939 } from "../lib/mission";
40+import { G1T_COMMIT_EMAILS, normalizeEmail } from "../lib/commit-people";
4041 import {
4142 type Fact,
4243 type Merged,
187188 const path = { namespace: repo.namespace, name: repo.name };
188189 const history = await reposApi.log(path, viewer, pushes[0].data.after, HISTORY_READ).catch(() => null);
189190 if (!history?.ok) return [];
190− return placePushes(history.value, pushes);
191+ // g1t's own commits are told by their author address, never by name.
192+ return placePushes(history.value, pushes, (commit) => G1T_COMMIT_EMAILS.has(normalizeEmail(commit.author.email)));
191193 }
192194
193195
+5−2
177177 * One branch moved by a push. `ref` is the full ref, `after` the commit it
178178 * points to now, and `defaultBranch` whether it is the default branch.
179179 */
180− /** `before` is where the ref pointed before; absent for a new branch or tag. */
181− "git.push": { repoId: string; ref: string; before?: string; after: string; defaultBranch: boolean };
182180 /**
181+ * `before` is where the ref pointed before; absent for a new branch or
182+ * tag. `causedByJob` is set when a workflow job's token pushed: the run's id.
183+ */
184+ "git.push": { repoId: string; ref: string; before?: string; after: string; defaultBranch: boolean; causedByJob?: string };
185+ /**
183186 * `author` is who opened it: g1t, for one its agent filed while at work,
184187 * with `requestedBy` the person it was working for. Every issue and pull
185188 * request event carries both.