import { concatBytes, FLUSH_PKT, packetText, parseAllPackets, pktLine } from "./pktline"; const PRIVATE_PREFIX = "refs/gitfor/"; const ZERO_SHA = "0".repeat(40); /** Whether `name` is a ref clients must never see: base commits bridged into a fork for its pull requests. */ export function isPrivateRef(name: string): boolean { return name.startsWith(PRIVATE_PREFIX); } function refsOf(lines: string[]): Map { const refs = new Map(); for (const line of lines) { const [sha, name] = (line.split("\0")[0] ?? "").split(" "); if (sha && name) refs.set(name, sha); } return refs; } /** Points `symref=HEAD:...` in a v0 capability list at the configured default branch. */ function withDefaultHead(caps: string, branchRef: string): string { const others = caps.split(" ").filter((cap) => cap !== "" && !cap.startsWith("symref=HEAD:")); return [...others, `symref=HEAD:${branchRef}`].join(" "); } /** * Rewrites a v0 upload-pack advertisement: * - removes `refs/gitfor/*`. Capabilities ride on the first ref line, so they move to the next kept ref * (or to a `capabilities^{}` placeholder when none is left); * - points HEAD at `defaultBranch`, because Artifacts' own HEAD never follows the repository setting. */ export function filterAdvertisement(body: Uint8Array, defaultBranch?: string): Uint8Array { const packets = parseAllPackets(body); const texts = packets.map((packet) => (packet.kind === "data" ? (packetText(packet) ?? "") : "")); const refs = refsOf(texts.filter((line) => line && !line.startsWith("#"))); const branchRef = defaultBranch ? `refs/heads/${defaultBranch}` : null; const headSha = branchRef ? refs.get(branchRef) : undefined; const out: Uint8Array[] = []; let pendingCaps: string | null = null; let sawRefs = false; for (const [index, packet] of packets.entries()) { if (packet.kind !== "data") { if (packet.kind === "flush" && sawRefs && pendingCaps !== null) { out.push(pktLine(`${ZERO_SHA} capabilities^{}\0${pendingCaps}\n`)); pendingCaps = null; } out.push(FLUSH_PKT); continue; } const line = texts[index]; if (line.startsWith("#")) { out.push(pktLine(packet.data)); continue; } sawRefs = true; let [refPart, caps] = line.split("\0"); const [sha, name = ""] = refPart.split(" "); if (isPrivateRef(name)) { if (caps !== undefined) pendingCaps = caps; continue; } if (pendingCaps !== null && caps === undefined) { caps = pendingCaps; pendingCaps = null; } if (headSha && branchRef) { if (name === "HEAD") refPart = `${headSha} HEAD`; if (caps !== undefined) caps = withDefaultHead(caps, branchRef); } const rewritten = caps === undefined ? refPart : `${refPart}\0${caps}`; out.push(rewritten === line ? pktLine(packet.data) : pktLine(`${rewritten}\n`)); void sha; } return concatBytes(out); } /** * Rewrites a protocol v2 `ls-refs` response the same way: drops `refs/gitfor/*` and points HEAD at the * default branch (` HEAD symref-target:`). */ export function filterLsRefs(body: Uint8Array, defaultBranch?: string): Uint8Array { const packets = parseAllPackets(body); const texts = packets.map((packet) => (packet.kind === "data" ? (packetText(packet) ?? "") : "")); const refs = refsOf(texts.filter(Boolean)); const branchRef = defaultBranch ? `refs/heads/${defaultBranch}` : null; const headSha = branchRef ? refs.get(branchRef) : undefined; const out: Uint8Array[] = []; for (const [index, packet] of packets.entries()) { if (packet.kind !== "data") { out.push( packet.kind === "flush" ? FLUSH_PKT : new TextEncoder().encode(packet.kind === "delim" ? "0001" : "0002"), ); continue; } const [, name = ""] = texts[index].split(" "); if (isPrivateRef(name)) continue; if (name === "HEAD" && headSha && branchRef) { out.push(pktLine(`${headSha} HEAD symref-target:${branchRef}\n`)); continue; } out.push(pktLine(packet.data)); } return concatBytes(out); }