120 lines · 4.4 KB
import { concatBytes, FLUSH_PKT, packetText, parseAllPackets, pktLine } from "./pktline";

const ZERO_SHA = "0000000000000000000000000000000000000000";
const PEELED_SUFFIX = "^{}";
const PACK_MAGIC = [0x50, 0x41, 0x43, 0x4b];
const MAX_PREAMBLE_BYTES = 64 * 1024;
const decoder = new TextDecoder();

export type AdvertisedRef = { name: string; sha: string };

/** Every branch and tag in a v0 upload-pack advertisement, using the tag object (not the peeled commit) for annotated tags. */
export function parseSyncableRefs(body: Uint8Array): AdvertisedRef[] {
  const refs: AdvertisedRef[] = [];
  for (const packet of parseAllPackets(body)) {
    const line = packetText(packet);
    if (line === null || line.startsWith("#")) continue;
    const [refPart] = line.split("\0");
    const [sha, name] = refPart.split(" ");
    if (!sha || !name || sha === ZERO_SHA || name.endsWith(PEELED_SUFFIX)) continue;
    if (name.startsWith("refs/heads/") || name.startsWith("refs/tags/")) refs.push({ name, sha });
  }
  return refs;
}

/**
 * A stateless v0 upload-pack request. No side-band and no thin-pack, so the response is a plain,
 * self-contained packfile that receive-pack will accept verbatim.
 */
export function uploadPackRequest(wants: string[], haves: string[]): Uint8Array<ArrayBuffer> {
  const lines: Uint8Array[] = wants.map((sha, index) =>
    pktLine(index === 0 ? `want ${sha} ofs-delta no-progress agent=gitfor\n` : `want ${sha}\n`),
  );
  lines.push(FLUSH_PKT);
  for (const sha of haves) lines.push(pktLine(`have ${sha}\n`));
  lines.push(pktLine("done\n"));
  return concatBytes(lines);
}

function packStart(buffer: Uint8Array): number {
  for (let index = 0; index + PACK_MAGIC.length <= buffer.byteLength; index++) {