75 lines · 3.0 KB
import { guardedFetch, relayPack, trimUrl } from "../git/relay";
import type { RelaySource } from "../git/relay";
import { parseReportStatus, receivePackBody } from "../git/ref-sync-protocol";
import type { RefCommand } from "../git/ref-sync-protocol";
import { badRequest } from "../errors";
import { ZERO_SHA } from "./plan";

const MAX_PUSH_BYTES = 1024 * 1024 * 1024;
const PUSH_TIMEOUT_MS = 10 * 60_000;

export type RemoteReport = { accepted: Set<string>; rejected: Map<string, string> };

function emptyPack(): ReadableStream<Uint8Array> {
  return new ReadableStream({ start: (controller) => controller.close() });
}

function splitReport(commands: RefCommand[], rejected: string[]): RemoteReport {
  const refused = new Map<string, string>();
  for (const entry of rejected) {
    const space = entry.indexOf(" ");
    const name = space === -1 ? entry : entry.slice(0, space);
    refused.set(name, space === -1 ? "rejected by the remote" : entry.slice(space + 1));
  }
  return {
    accepted: new Set(commands.map((command) => command.name).filter((name) => !refused.has(name))),
    rejected: refused,
  };
}

/** Deletions carry no packfile, so they go straight to receive-pack instead of through relayPack. */
async function sendDeletions(remote: RelaySource, commands: RefCommand[]): Promise<string[]> {
  const headers: Record<string, string> = {
    "content-type": "application/x-git-receive-pack-request",
    accept: "application/x-git-receive-pack-result",
    "user-agent": "git/gitfor",
  };
  if (remote.authorization) headers.authorization = remote.authorization;
  const response = await guardedFetch(remote, `${trimUrl(remote.url)}/git-receive-pack`, {
    method: "POST",
    headers,