import { listRefs, resolveRevision } from "../../git/refs"; import type { ResolvedRevision } from "../../git/refs"; import { assertVisibleRevision } from "../../git/reachable"; import { withStore } from "../../git/store"; import { notFoundError } from "../../errors"; import type { RepoRow, TreeEntry } from "~/lib/types"; export const MAX_FILE_BYTES = 1024 * 1024; const MAX_TREE_ENTRIES = 1000; const decoder = new TextDecoder("utf-8", { fatal: true }); /** Resolves a branch, tag or reachable commit (default branch when omitted). Hidden commits stay hidden. */ export async function resolveRef(repo: RepoRow, ref: string | undefined): Promise { const refs = await listRefs(repo); const revision = resolveRevision(refs, ref || repo.default_branch); if (!revision) throw notFoundError(`Unknown ref ${ref ?? repo.default_branch}`); await assertVisibleRevision(repo, revision); return revision; } export async function listTreeAt(repo: RepoRow, revision: ResolvedRevision, path: string) { return withStore(repo.artifacts_name, async (store) => { const commit = await store.commit(revision.sha); const entry = commit ? await store.entryAtPath(commit.treeSha, path) : null; if (!entry || entry.type !== "tree") throw notFoundError("Directory not found"); const entries: TreeEntry[] = await store.listTree(entry.sha, entry.path); return { entries: entries.slice(0, MAX_TREE_ENTRIES), truncated: entries.length > MAX_TREE_ENTRIES }; }); } export type FileRead = { path: string; sha: string; size: number; binary: boolean; text: string | null; base64: string | null; }; /** Reads one blob; text when it decodes as UTF-8, base64 otherwise, and never more than MAX_FILE_BYTES. */ export async function readFileAt(repo: RepoRow, revision: ResolvedRevision, path: string): Promise { return withStore(repo.artifacts_name, async (store) => { const commit = await store.commit(revision.sha); const entry = commit ? await store.entryAtPath(commit.treeSha, path) : null; if (!entry || entry.type === "tree" || entry.type === "gitlink") throw notFoundError("File not found"); const bytes = await store.blob(entry.sha); if (!bytes) throw notFoundError("File not found"); if (bytes.byteLength > MAX_FILE_BYTES) { return { path: entry.path, sha: entry.sha, size: bytes.byteLength, binary: false, text: null, base64: null }; } try { const text = decoder.decode(bytes); if (text.includes("\u0000")) throw new Error("binary"); return { path: entry.path, sha: entry.sha, size: bytes.byteLength, binary: false, text, base64: null }; } catch { let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return { path: entry.path, sha: entry.sha, size: bytes.byteLength, binary: true, text: null, base64: btoa(binary), }; } }); }