import { env } from "cloudflare:workers"; import { kvCached, kvPut } from "./cache"; import { sha256Hex } from "../crypto"; import type { GitStore } from "../git/store"; import { toBrief } from "~/lib/code-intel"; import type { CommitBrief } from "~/lib/code-intel"; import type { CommitSummary } from "~/lib/types"; const PAGE_SIZE = 30; const WALK_BUDGET_PER_REQUEST = 300; const MAX_PATH_MATCHES = 1000; const MAX_LAST_COMMIT_WALK = 400; const MAX_INLINE_KEY_PATH = 200; /** KV keys are capped at 512 bytes, so long paths are hashed. */ export async function pathKey(path: string): Promise { return path.length <= MAX_INLINE_KEY_PATH ? path : `h:${await sha256Hex(path)}`; } async function pathSha(store: GitStore, commit: CommitSummary, path: string): Promise { const entry = await store.entryAtPath(commit.treeSha, path); return entry?.sha ?? null; } type PathLogState = { matches: CommitBrief[]; cursor: string | null; cursorPathSha: string | null }; export type PathHistoryPage = { commits: CommitBrief[]; hasMore: boolean; capped: boolean }; /** * First-parent history of commits that changed `path`. The walk resumes from a KV-stored cursor so * each request reads at most WALK_BUDGET_PER_REQUEST commits, however deep the requested page. */ export async function pathHistory( store: GitStore, headSha: string, path: string, page: number, ): Promise { const key = `path-log:v1:${store.artifactsName}:${headSha}:${await pathKey(path)}`; const stored = await env.KV.get(key, "json"); const state: PathLogState = stored ?? { matches: [], cursor: headSha, cursorPathSha: null }; const needed = page * PAGE_SIZE + 1; let budget = WALK_BUDGET_PER_REQUEST; let advanced = false; while (state.cursor && state.matches.length < needed && state.matches.length < MAX_PATH_MATCHES && budget-- > 0) { advanced = true; const commit = await store.commit(state.cursor); const own = commit ? (state.cursorPathSha ?? (await pathSha(store, commit, path))) : null; if (!commit || own === null) { state.cursor = null; break; } const parent = commit.parents[0] ? await store.commit(commit.parents[0]) : null; const parentOwn = parent ? (parent.treeSha === commit.treeSha ? own : await pathSha(store, parent, path)) : null; if (own !== parentOwn) state.matches.push(toBrief(commit)); state.cursor = parent && parentOwn !== null ? parent.sha : null; state.cursorPathSha = parentOwn; } if (advanced) await kvPut(key, state); const start = (page - 1) * PAGE_SIZE; const capped = state.matches.length >= MAX_PATH_MATCHES; return { commits: state.matches.slice(start, start + PAGE_SIZE), hasMore: state.matches.length > start + PAGE_SIZE || (state.cursor !== null && !capped), capped, }; } type DirListing = { sha: string; entries: Map }; async function dirListing(store: GitStore, commit: CommitSummary, dir: string): Promise { const entry = await store.entryAtPath(commit.treeSha, dir); if (!entry || entry.type !== "tree") return null; const entries = (await store.tree(entry.sha)) ?? []; return { sha: entry.sha, entries: new Map(entries.map((item) => [item.name, item.hash])) }; } async function computeLastCommits( store: GitStore, headSha: string, dir: string, ): Promise> { const result: Record = {}; let current = await store.commit(headSha); let currentDir = current ? await dirListing(store, current, dir) : null; if (!current || !currentDir) return result; const pending = new Set(currentDir.entries.keys()); for (const name of pending) result[name] = null; for (let step = 0; step < MAX_LAST_COMMIT_WALK && pending.size > 0 && current && currentDir; step++) { const parent: CommitSummary | null = current.parents[0] ? await store.commit(current.parents[0]) : null; const parentDir: DirListing | null = !parent ? null : parent.treeSha === current.treeSha ? currentDir : await dirListing(store, parent, dir); if (!parentDir || parentDir.sha !== currentDir.sha) { for (const name of [...pending]) { if (parentDir?.entries.get(name) === currentDir.entries.get(name)) continue; result[name] = toBrief(current); pending.delete(name); } } current = parent; currentDir = parentDir; } return result; } /** Latest first-parent commit touching each entry of a directory, keyed by entry name; null past the walk bound. */ export async function lastCommitsForDirectory( store: GitStore, headSha: string, dir: string, ): Promise> { const key = `last-commit:v1:${store.artifactsName}:${headSha}:${await pathKey(dir)}`; return kvCached(key, () => computeLastCommits(store, headSha, dir)); }