import { env } from "cloudflare:workers"; import { all, first } from "../db"; import { notFoundError } from "../errors"; import { reachableViaFetch } from "./commit-graph"; import { listRefs } from "./refs"; import type { ResolvedRevision } from "./refs"; import { withStore } from "./store"; import type { GitStore } from "./store"; import type { CommitSummary, RepoRow } from "~/lib/types"; const MAX_COMMIT_READS = 3000; const MAX_LOG_PAGES = 10; const LOG_PAGE = 1000; const MAX_TIPS = 50; /** Commits are only reachable from descendants, which are (nearly always) committed later. Allow skew. */ const CLOCK_SKEW_MS = 24 * 60 * 60 * 1000; const REACHABLE_TTL_SECONDS = 60 * 60 * 24; const UNREACHABLE_TTL_SECONDS = 5 * 60; type ReachRepo = Pick; /** First-parent history of the default branch, paged server-side by Artifacts: cheap for most links. */ async function onDefaultBranch(store: GitStore, branch: string, target: CommitSummary): Promise { for (let page = 0; page < MAX_LOG_PAGES; page++) { const commits = await store.log(branch, LOG_PAGE, page * LOG_PAGE); if (commits.some((commit) => commit.sha === target.sha)) return true; const oldest = commits.at(-1); if (commits.length < LOG_PAGE || !oldest || oldest.committedAt < target.committedAt - CLOCK_SKEW_MS) return false; } return false; } /** Date-pruned walk from every visible tip: stops descending once commits are older than the target. */ async function fromTips(store: GitStore, tips: string[], target: CommitSummary): Promise { const cutoff = target.committedAt - CLOCK_SKEW_MS; const seen = new Set(); const queue = [...tips]; let reads = 0; for (let index = 0; index < queue.length; index++) { const sha = queue[index]; if (sha === target.sha) return true; if (seen.has(sha)) continue; seen.add(sha); if (++reads > MAX_COMMIT_READS) return false; const commit = await store.commit(sha); if (!commit || commit.committedAt < cutoff) continue; queue.push(...commit.parents); } return false; } async function knownPullCommit(repoId: string, sha: string): Promise { const row = await first<{ found: number }>( `SELECT 1 AS found FROM pull_requests WHERE (repo_id = ?1 OR head_repo_id = ?1) AND (head_sha = ?2 OR base_sha = ?2 OR merge_base_sha = ?2 OR merge_commit_sha = ?2) LIMIT 1`, repoId, sha, ); return row !== null; } /** * Whether a commit SHA may be browsed: it must be reachable from a visible branch or tag or an open pull * request's head (or be a pull request's recorded head/base/merge commit), never only from hidden refs or * dangling after a force push. * Results are cached; walks are bounded and fail closed. */ export async function isReachableCommit(repo: ReachRepo, sha: string): Promise { const refs = await listRefs(repo); // listRefs keeps each annotated tag's peeled `^{}` commit, so tags walk from commits, not tag objects. const tips = [...refs.branches, ...refs.tags].map((ref) => ref.sha); if (tips.includes(sha)) return true; const key = `reach:v2:${repo.id}:${sha}`; const cached = await env.KV.get(key); if (cached !== null) return cached === "1"; let reachable = await knownPullCommit(repo.id, sha); if (!reachable) { reachable = await withStore(repo.artifacts_name, async (store) => { const target = await store.commit(sha); if (!target) return false; if (await onDefaultBranch(store, repo.default_branch, target)) return true; const pullHeads = await all<{ head_sha: string }>( `SELECT p.head_sha FROM pull_requests p JOIN issues i ON i.id = p.issue_id WHERE p.repo_id = ? AND i.state = 'open' ORDER BY i.updated_at DESC LIMIT ?`, repo.id, MAX_TIPS, ); const walkTips = [...new Set([...tips, ...pullHeads.map((pull) => pull.head_sha)])].slice(0, MAX_TIPS * 2); const fetched = await reachableViaFetch(repo.artifacts_name, walkTips, target.sha); if (fetched !== null) return fetched; return fromTips(store, walkTips, target); }); } await env.KV.put(key, reachable ? "1" : "0", { expirationTtl: reachable ? REACHABLE_TTL_SECONDS : UNREACHABLE_TTL_SECONDS, }); return reachable; } /** Throws not-found for SHA revisions that are not reachable from visible refs; branches and tags pass. */ export async function assertVisibleRevision(repo: ReachRepo, revision: ResolvedRevision): Promise { if (revision.kind !== "commit") return; if (!(await isReachableCommit(repo, revision.sha))) throw notFoundError("Commit not found"); }