| 39 | 39 | | import { |
| 40 | 40 | | type Fact, |
| 41 | 41 | | type Merged, |
| 42 | | − | pushedCommits, |
| 42 | + | placePushes, |
| 43 | 43 | | type NeedRow, |
| 44 | 44 | | type QuickAction, |
| 45 | 45 | | RUN_LABEL, |
| ⋯ |
| 152 | 152 | | return data<DelegateResult>({ error: null, notStarted: refused }); |
| 153 | 153 | | } |
| 154 | 154 | | |
| 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; |
| 158 | 164 | | |
| 159 | 165 | | /** |
| 160 | 166 | | * The commits people pushed straight to a project's default branch in the |
| ⋯ |
| 163 | 169 | | */ |
| 164 | 170 | | async function directCommits(repo: Repo, viewer: Viewer): Promise<{ hash: string; at: string }[]> { |
| 165 | 171 | | 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. |
| 169 | 186 | | 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); |
| 180 | 190 | | } |
| 181 | 191 | | |
| 192 | + | |
| 182 | 193 | | export async function loader({ context, request }: Route.LoaderArgs) { |
| 183 | 194 | | const viewer = getViewer(context); |
| 184 | 195 | | if (!viewer) { |