Events of one type are read by repository and type, and the home page reads a short page of pushes and history first (events 0007)
4 files+49−50/4 viewed
| 4 | 4 | import { | |
| 5 | 5 | type Merged, | |
| 6 | 6 | placePushes, | |
| 7 | + | historyCovers, | |
| 7 | 8 | change, | |
| 8 | 9 | checksFact, | |
| 9 | 10 | confidenceAsk, | |
| ⋯ | |||
| 204 | 205 | assert.deepEqual(placePushes(history, pushes), [{ hash: "m1", at: "2026-10-05T12:00:00Z" }]); | |
| 205 | 206 | }); | |
| 206 | 207 | ||
| 208 | + | test("a short read of the branch is enough when it reaches the oldest push", () => { | |
| 209 | + | const h = (...hashes: string[]) => hashes.map((hash) => ({ hash })); | |
| 210 | + | const pushes = [ | |
| 211 | + | { time: "2026-10-07T12:00:00Z", data: { after: "c1", before: "c2" } }, | |
| 212 | + | { time: "2026-10-05T12:00:00Z", data: { after: "c2", before: "c3" } }, | |
| 213 | + | ]; | |
| 214 | + | // Holds the oldest push's before. | |
| 215 | + | assert.ok(historyCovers(h("c1", "c2", "c3"), pushes, 3)); | |
| 216 | + | // Full, and the oldest push reaches past it: read further. | |
| 217 | + | assert.ok(!historyCovers(h("c1", "c2", "x"), pushes, 3)); | |
| 218 | + | // Shorter than asked: the whole branch. | |
| 219 | + | assert.ok(historyCovers(h("c1", "c2"), pushes, 3)); | |
| 220 | + | // The oldest push made the branch: only the whole branch will do. | |
| 221 | + | assert.ok(!historyCovers(h("c1", "c2", "c3"), [{ time: "", data: { after: "c1" } }], 3)); | |
| 222 | + | }); | |
| 223 | + | ||
| 207 | 224 | test("a change landed without a person when g1t merged it", () => { | |
| 208 | 225 | assert.ok(landedByAgents({ mergedBy: "g1t" })); | |
| 209 | 226 | assert.ok(landedByAgents({ mergedBy: "g1t" })); | |
| 503 | 503 | return [...seen].flatMap(([hash, at]) => (at ? [{ hash, at }] : [])); | |
| 504 | 504 | } | |
| 505 | 505 | ||
| 506 | + | /** | |
| 507 | + | * Whether a read of the default branch reaches back far enough to place | |
| 508 | + | * every push, newest first: it holds the oldest push's `before`, or it is | |
| 509 | + | * the whole branch. A shorter read than `asked` is the whole branch. | |
| 510 | + | */ | |
| 511 | + | export function historyCovers(history: { hash: string }[], pushes: PushRecord[], asked: number): boolean { | |
| 512 | + | if (history.length < asked) return true; | |
| 513 | + | const oldest = pushes.at(-1)?.data.before; | |
| 514 | + | return oldest != null && history.some((commit) => commit.hash === oldest); | |
| 515 | + | } | |
| 516 | + | ||
| 506 | 517 | /** A merged pull request, as the week counts it. */ | |
| 507 | 518 | export type Merged = { | |
| 508 | 519 | repo: RepoPath; |
| 57 | 57 | usd, | |
| 58 | 58 | waitingRows, | |
| 59 | 59 | weekOf, | |
| 60 | + | historyCovers, | |
| 60 | 61 | who, | |
| 61 | 62 | whyFor, | |
| 62 | 63 | withConfidence, | |
| ⋯ | |||
| 161 | 162 | */ | |
| 162 | 163 | const PUSH_PAGE = 200; | |
| 163 | 164 | const PUSH_PAGES = 5; | |
| 164 | − | /** Commits of the default branch read once, to place each push's commits. */ | |
| 165 | + | /** The first page is smaller: most projects push far less in two weeks. */ | |
| 166 | + | const FIRST_PUSH_PAGE = 50; | |
| 167 | + | /** | |
| 168 | + | * Commits of the default branch read to place each push's commits: a | |
| 169 | + | * short read first, which covers most projects' fortnight, and the longer | |
| 170 | + | * one only when it does not reach the oldest push. | |
| 171 | + | */ | |
| 172 | + | const HISTORY_FIRST = 200; | |
| 165 | 173 | const HISTORY_READ = 1000; | |
| 166 | 174 | ||
| 167 | 175 | /** | |
| ⋯ | |||
| 174 | 182 | const pushes: G1tEvent<"git.push">[] = []; | |
| 175 | 183 | let before: string | undefined; | |
| 176 | 184 | for (let page = 0; page < PUSH_PAGES; page++) { | |
| 177 | − | const batch = await eventLog.list({ repoId: repo.id, types: ["git.push"], limit: PUSH_PAGE, ...(before ? { before } : {}) }); | |
| 185 | + | const limit = page === 0 ? FIRST_PUSH_PAGE : PUSH_PAGE; | |
| 186 | + | const batch = await eventLog.list({ repoId: repo.id, types: ["git.push"], limit, ...(before ? { before } : {}) }); | |
| 178 | 187 | for (const event of batch) { | |
| 179 | 188 | if (event.type === "git.push" && event.data.defaultBranch && Date.parse(event.time) >= since) pushes.push(event as G1tEvent<"git.push">); | |
| 180 | 189 | } | |
| 181 | 190 | const oldest = batch.at(-1); | |
| 182 | − | if (batch.length < PUSH_PAGE || !oldest || Date.parse(oldest.time) < since) break; | |
| 191 | + | if (batch.length < limit || !oldest || Date.parse(oldest.time) < since) break; | |
| 183 | 192 | before = oldest.id; | |
| 184 | 193 | } | |
| 185 | 194 | if (pushes.length === 0) return []; | |
| 186 | − | // One read of the branch from the newest push back; each push brought | |
| 195 | + | // A read of the branch from the newest push back; each push brought | |
| 187 | 196 | // what lies between its `after` and its `before` in that history. | |
| 188 | 197 | const path = { namespace: repo.namespace, name: repo.name }; | |
| 189 | − | const history = await reposApi.log(path, viewer, pushes[0].data.after, HISTORY_READ).catch(() => null); | |
| 198 | + | const read = (limit: number) => reposApi.log(path, viewer, pushes[0].data.after, limit).catch(() => null); | |
| 199 | + | let history = await read(HISTORY_FIRST); | |
| 200 | + | if (history?.ok && !historyCovers(history.value, pushes, HISTORY_FIRST)) history = await read(HISTORY_READ); | |
| 190 | 201 | if (!history?.ok) return []; | |
| 191 | 202 | // g1t's own commits are told by their author address, never by name. | |
| 192 | 203 | return placePushes(history.value, pushes, (commit) => G1T_COMMIT_EMAILS.has(normalizeEmail(commit.author.email))); | |
| 1 | + | -- A repository's events of some types, newest first: the home page's pushes, | |
| 2 | + | -- the overview's feed and the Activity tab. By repository, type and id | |
| 3 | + | -- those are ranges, rather than a walk back through every event the | |
| 4 | + | -- repository ever had. | |
| 5 | + | CREATE INDEX IF NOT EXISTS events_repo_type ON events (repo_id, type, id); |