93 lines · 3.9 KB
import { basicAuthHeader, repoTokenSecret } from "./artifacts";
import { invalidateRefs } from "./refs";
import { pushToArtifacts } from "./writer";
import {
  packStream,
  parseReportStatus,
  parseSyncableRefs,
  receivePackBody,
  uploadPackRequest,
} from "./ref-sync-protocol";
import type { AdvertisedRef } from "./ref-sync-protocol";
import { badRequest } from "../errors";
import type { RepoRow } from "~/lib/types";

const MAX_SYNC_REFS = 1000;
const MAX_HAVES = 256;
const ADVERTISEMENT_TIMEOUT_MS = 30_000;
const USER_AGENT = "git/gitfor";

export type RefSyncResult = { created: number; rejected: string[]; skipped: number };

async function advertisement(url: string, authorization: string | null): Promise<AdvertisedRef[]> {
  const headers: Record<string, string> = { "user-agent": USER_AGENT };
  if (authorization) headers.authorization = authorization;
  const response = await fetch(`${url.replace(/\/$/, "")}/info/refs?service=git-upload-pack`, {
    headers,
    signal: AbortSignal.timeout(ADVERTISEMENT_TIMEOUT_MS),
  });
  if (!response.ok) throw badRequest(`Could not list refs at ${url} (HTTP ${response.status})`);
  return parseSyncableRefs(new Uint8Array(await response.arrayBuffer()));
}

/**
 * Artifacts imports only copy one branch. This copies every other branch and tag from the source by
 * relaying a packfile straight from the source's upload-pack into Artifacts' receive-pack, so nothing
 * is buffered in the Worker. Existing refs are never overwritten.
 */
export async function syncRefsFromSource(repo: RepoRow, sourceUrl: string): Promise<RefSyncResult> {
  if (!sourceUrl.startsWith("https://")) throw badRequest("Only https:// sources can be synced");
  const readSecret = await repoTokenSecret(repo, "read");