147 lines · 5.6 KB
import { concatBytes, FLUSH_PKT, packetText, parseAllPackets, pktLine } from "./pktline";
import { isValidRefName } from "./writer";

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 };
export type RefCommand = { name: string; sha: string; oldSha?: string };

const SHA_PATTERN = /^[0-9a-f]{40}$/;

/** A branch or tag whose name passes `git check-ref-format`, so a remote cannot smuggle odd names in. */
function isSyncableRef(name: string): boolean {
  const prefix = ["refs/heads/", "refs/tags/"].find((candidate) => name.startsWith(candidate));
  return prefix !== undefined && isValidRefName(name.slice(prefix.length));
}

/** 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, includeAll = false, includePeeled = false): 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_PATTERN.test(sha) || sha === ZERO_SHA) continue;
    if (name.endsWith(PEELED_SUFFIX) && !(includeAll && includePeeled)) continue;
    if (includeAll || isSyncableRef(name)) 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> {