g1t/apps/web/app/lib/provenance.server.ts
| 1 | import type { Pull, RepoPath, Viewer } from "@g1t/contracts"; |
| 2 | |
| 3 | import { repos, work } from "./services.server"; |
| 4 | |
| 5 | /** How far back the default branch is read to place a commit. */ |
| 6 | const HISTORY_READ = 300; |
| 7 | |
| 8 | /** |
| 9 | * The merged pull request a commit arrived in: the one whose head it is, |
| 10 | * or else the one whose stretch of the default branch's history holds it, |
| 11 | * from its head back to where it started. One read of pull requests and at |
| 12 | * most one of history, however many pull requests there are. |
| 13 | */ |
| 14 | export async function pullForCommit(path: RepoPath, viewer: Viewer, hash: string): Promise<Pull | null> { |
| 15 | const closed = await work.listPulls(path, viewer, "closed"); |
| 16 | if (!closed.ok) return null; |
| 17 | const merged = closed.value.filter((pull) => pull.status === "merged" && pull.headCommit); |
| 18 | const direct = merged.find((pull) => pull.headCommit === hash); |
| 19 | if (direct) return direct; |
| 20 | |
| 21 | const log = await repos.log(path, viewer, null, HISTORY_READ); |
| 22 | if (!log.ok) return null; |
| 23 | const position = new Map(log.value.map((commit, index) => [commit.hash, index])); |
| 24 | const at = position.get(hash); |
| 25 | if (at == null) return null; |
| 26 | // Newest first: a pull request's commits sit at or after its head and |
| 27 | // before the commit it started from. |
| 28 | let best: { pull: Pull; head: number } | null = null; |
| 29 | for (const pull of merged) { |
| 30 | const head = position.get(pull.headCommit!); |
| 31 | if (head == null || head > at) continue; |
| 32 | const base = pull.mergeBase ? position.get(pull.mergeBase) : undefined; |
| 33 | if (base != null && base <= at) continue; |
| 34 | if (!best || head > best.head) best = { pull, head }; |
| 35 | } |
| 36 | return best?.pull ?? null; |
| 37 | } |