/* oxlint-disable executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: pure git protocol parser throws are caught by git-source and converted to AppSourceError */ // Shared pkt-line parsing + refs advertisement parse. // Works in both Bun and workerd (no Node-isms; only Web APIs + Uint8Array). const td = new TextDecoder(); const te = new TextEncoder(); export function pktLine(payload: string | Uint8Array): Uint8Array { const body = typeof payload === "string" ? te.encode(payload) : payload; const len = body.length + 4; const hdr = te.encode(len.toString(16).padStart(4, "0")); const out = new Uint8Array(hdr.length + body.length); out.set(hdr, 0); out.set(body, hdr.length); return out; } export const FLUSH = te.encode("0000"); export const DELIM = te.encode("0001"); // Streaming pkt-line reader over a Uint8Array buffer. export interface PktToken { kind: "line" | "flush" | "delim"; data?: Uint8Array; } export function parsePktLines( buf: Uint8Array, options: { readonly requireComplete?: boolean } = {}, ): { tokens: PktToken[]; consumed: number } { const tokens: PktToken[] = []; let off = 0; while (off + 4 <= buf.length) { const lenHex = td.decode(buf.subarray(off, off + 4)); const len = parseInt(lenHex, 16); if (Number.isNaN(len)) throw new Error(`bad pkt-line length: ${lenHex}`); if (len === 0) { tokens.push({ kind: "flush" }); off += 4; continue; } if (len === 1) { tokens.push({ kind: "delim" }); off += 4; continue; } if (len < 4) throw new Error(`bad pkt-line length: ${lenHex}`); if (off + len > buf.length) break; // incomplete; caller supplies more const data = buf.subarray(off + 4, off + len); tokens.push({ kind: "line", data }); off += len; } if (options.requireComplete === true && off !== buf.length) { throw new Error("truncated pkt-line response"); } return { tokens, consumed: off }; } // --- Refs advertisement (protocol v1 dumb-ish smart-http info/refs) --- export interface RefAdvertisement { refs: Map; // refname -> sha headTarget?: string; // symref HEAD target (e.g. refs/heads/main) capabilities: string[]; protocolVersion: number; } // Parse the response of GET /info/refs?service=git-upload-pack (protocol v1). export function parseInfoRefs(bytes: Uint8Array): RefAdvertisement { const { tokens } = parsePktLines(bytes, { requireComplete: true }); const refs = new Map(); let capabilities: string[] = []; let headTarget: string | undefined; let sawService = false; let first = true; for (const t of tokens) { if (t.kind !== "line" || !t.data) continue; let line = td.decode(t.data); if (line.endsWith("\n")) line = line.slice(0, -1); if (line.startsWith("# service=")) { sawService = true; continue; } // first ref line carries \0-separated capabilities if (first) { const nul = line.indexOf("\0"); if (nul >= 0) { capabilities = line .slice(nul + 1) .split(" ") .filter(Boolean); line = line.slice(0, nul); } first = false; } const sp = line.indexOf(" "); if (sp < 0) continue; const sha = line.slice(0, sp); const name = line.slice(sp + 1); if (/^[0-9a-f]{40,64}$/.test(sha)) refs.set(name, sha); } // symref HEAD from capabilities: symref=HEAD:refs/heads/main for (const cap of capabilities) { if (cap.startsWith("symref=HEAD:")) headTarget = cap.slice("symref=HEAD:".length); } return { refs, headTarget, capabilities, protocolVersion: sawService ? 1 : 1 }; } // Resolve which sha a caller wants given a ref advertisement + optional ref name. export function resolveWant( adv: RefAdvertisement, ref?: string, ): { sha: string; resolvedRef: string } { if (ref) { // try exact, then heads/, then tags/ const candidates = [ref, `refs/heads/${ref}`, `refs/tags/${ref}`, `refs/${ref}`]; for (const c of candidates) { const sha = adv.refs.get(c); if (sha) return { sha, resolvedRef: c }; } // maybe they passed a sha directly if (/^[0-9a-f]{40,64}$/.test(ref)) return { sha: ref, resolvedRef: ref }; throw new Error(`ref not found: ${ref}`); } // default: HEAD symref target, else HEAD ref, else first if (adv.headTarget) { const sha = adv.refs.get(adv.headTarget); if (sha) return { sha, resolvedRef: adv.headTarget }; } const head = adv.refs.get("HEAD"); if (head) return { sha: head, resolvedRef: "HEAD" }; const first = adv.refs.entries().next(); if (!first.done) return { sha: first.value[1], resolvedRef: first.value[0] }; throw new Error("no refs advertised"); } export { td, te };