114 lines · 4.5 KB
import { bufferUpTo, packOffset } from "../security/push-protection";
import { isAncestor } from "./history";
import { parsePack } from "./pack";
import { withStore, ZERO_SHA } from "./store";
import type { RepoRow } from "~/lib/types";

const MAX_BUFFERED_BYTES = 32 * 1024 * 1024;
const MAX_INFLATED_BYTES = 64 * 1024 * 1024;
const MAX_WALK = 5000;
const MAX_STORE_CHECKS = 64;
const PARENT_LINE = /^parent ([0-9a-f]{40})$/gm;
const decoder = new TextDecoder();

export type RefUpdate = { oldSha: string; newSha: string; ref: string };

function commitParents(content: Uint8Array): string[] {
  const header = decoder.decode(content.subarray(0, Math.min(content.byteLength, 64 * 1024))).split("\n\n")[0];
  return [...header.matchAll(PARENT_LINE)].map((match) => match[1]);
}

async function readPackParents(bytes: Uint8Array<ArrayBuffer>, gzip: boolean): Promise<Map<string, string[]> | null> {
  let decoded = bytes;
  if (gzip) {
    const inflated = await bufferUpTo(
      new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip")),
      MAX_INFLATED_BYTES,
    );
    if (!inflated.complete) return null;
    decoded = inflated.bytes;
  }
  const offset = packOffset(decoded);
  if (offset === null) return new Map();
  const parsed = await parsePack(decoded.subarray(offset), {
    maxObjects: 100_000,
    maxObjectBytes: 16 * 1024 * 1024,
    maxTotalBytes: MAX_INFLATED_BYTES,
  });
  if (parsed.truncated) return null;
  return new Map(
    parsed.objects