Skip to content

Compare changes

Choose two branches to see what one has that the other does not, then open a pull request for it.

Open a pull request

1 commit

3 files+68−170/3 viewed
+17−0
33
44 import {
55 type Merged,
6+ placePushes,
67 change,
78 checksFact,
89 confidenceAsk,
370371 assert.match(why, /self-hosted runner/);
371372 assert.doesNotMatch(why, /agent/);
372373 });
374+
375+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 } });
377+ // 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")];
379+ 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" } },
383+ ];
384+ assert.deepEqual(placePushes(history, pushes), [
385+ { hash: "m1", at: "2026-10-05T12:00:00Z" },
386+ { hash: "w2", at: "2026-10-07T12:00:00Z" },
387+ { hash: "w1", at: "2026-10-07T12:00:00Z" },
388+ ]);
389+});
+23−0
448448 return brought.filter((commit) => commit.parents.length <= 1 && !isAgent(commit.author.name));
449449 }
450450
451+/**
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.
456+ */
457+export function placePushes<C extends { hash: string; parents: string[]; author: { name: string } }>(
458+ history: C[],
459+ pushes: { time: string; data: { after: string; before?: string } }[],
460+): { hash: string; at: string }[] {
461+ const index = new Map(history.map((commit, i) => [commit.hash, i]));
462+ const seen = new Map<string, string>();
463+ for (const push of [...pushes].reverse()) {
464+ const start = index.get(push.data.after);
465+ if (start === undefined) continue;
466+ 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);
469+ }
470+ }
471+ return [...seen].map(([hash, at]) => ({ hash, at }));
472+}
473+
451474 export type Merged = {
452475 repo: RepoPath;
453476 number: number;
+28−17
3939 import {
4040 type Fact,
4141 type Merged,
42− pushedCommits,
42+ placePushes,
4343 type NeedRow,
4444 type QuickAction,
4545 RUN_LABEL,
152152 return data<DelegateResult>({ error: null, notStarted: refused });
153153 }
154154
155−/** Pushes to the default branch read for the week, per project, and commits read back from each. */
156−const PUSHES_READ = 40;
157−const PUSH_DEPTH = 60;
155+/**
156+ * Push events read per project, a page at a time, back two weeks. Pushes
157+ * to every branch are in the log, so a fixed count (it was 40) lost a busy
158+ * week's earlier days to agents' branches.
159+ */
160+const PUSH_PAGE = 200;
161+const PUSH_PAGES = 5;
162+/** Commits of the default branch read once, to place each push's commits. */
163+const HISTORY_READ = 1000;
158164
159165 /**
160166 * The commits people pushed straight to a project's default branch in the
163169 */
164170 async function directCommits(repo: Repo, viewer: Viewer): Promise<{ hash: string; at: string }[]> {
165171 const since = Date.now() - 14 * TIME.DAY;
166− const pushes = (await eventLog.list({ repoId: repo.id, types: ["git.push"], limit: PUSHES_READ })).filter(
167− (event): event is G1tEvent<"git.push"> => event.type === "git.push" && event.data.defaultBranch && Date.parse(event.time) >= since,
168− );
172+ const pushes: G1tEvent<"git.push">[] = [];
173+ let before: string | undefined;
174+ for (let page = 0; page < PUSH_PAGES; page++) {
175+ const batch = await eventLog.list({ repoId: repo.id, types: ["git.push"], limit: PUSH_PAGE, ...(before ? { before } : {}) });
176+ for (const event of batch) {
177+ if (event.type === "git.push" && event.data.defaultBranch && Date.parse(event.time) >= since) pushes.push(event as G1tEvent<"git.push">);
178+ }
179+ const oldest = batch.at(-1);
180+ if (batch.length < PUSH_PAGE || !oldest || Date.parse(oldest.time) < since) break;
181+ before = oldest.id;
182+ }
183+ if (pushes.length === 0) return [];
184+ // One read of the branch from the newest push back; each push brought
185+ // what lies between its `after` and its `before` in that history.
169186 const path = { namespace: repo.namespace, name: repo.name };
170− const read = await Promise.all(
171− pushes.map(async (push) => {
172− const history = await reposApi.log(path, viewer, push.data.after, PUSH_DEPTH).catch(() => null);
173− return history?.ok ? pushedCommits(history.value, push.data.before).map((commit) => ({ hash: commit.hash, at: push.time })) : [];
174− }),
175− );
176− // A commit pushed twice (after a force push, say) counts once, at its first landing.
177− const seen = new Map<string, string>();
178− for (const commit of read.flat().reverse()) if (!seen.has(commit.hash)) seen.set(commit.hash, commit.at);
179− return [...seen].map(([hash, at]) => ({ hash, at }));
187+ const history = await reposApi.log(path, viewer, pushes[0].data.after, HISTORY_READ).catch(() => null);
188+ if (!history?.ok) return [];
189+ return placePushes(history.value, pushes);
180190 }
181191
192+
182193 export async function loader({ context, request }: Route.LoaderArgs) {
183194 const viewer = getViewer(context);
184195 if (!viewer) {