48 lines · 1.9 KB
import { advertisement, artifactsSource, assertPublicHttpsUrl, relayPack } from "./relay";
import { pushToArtifacts } from "./writer";
import type { RepoRow } from "~/lib/types";

const MAX_SYNC_REFS = 1000;
const MAX_SYNC_BYTES = 1024 * 1024 * 1024;
const ZERO_SHA = "0".repeat(40);

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

/**
 * 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> {
  assertPublicHttpsUrl(sourceUrl);
  const [source, target] = await Promise.all([
    advertisement({ url: sourceUrl, authorization: null }),
    artifactsSource(repo).then((self) => advertisement(self)),
  ]);
  const existingNames = new Set(target.map((ref) => ref.name));
  const allMissing = source.filter((ref) => !existingNames.has(ref.name));
  const missing = allMissing.slice(0, MAX_SYNC_REFS);
  const result: RefSyncResult = { created: 0, rejected: [], skipped: allMissing.length - missing.length };
  if (missing.length === 0) return result;

  const present = new Set(target.map((ref) => ref.sha));
  const wants = [...new Set(missing.map((ref) => ref.sha))].filter((sha) => !present.has(sha));
  if (wants.length === 0) {
    await pushToArtifacts(
      repo,
      missing.map((ref) => ({ ref: ref.name, oldSha: ZERO_SHA, newSha: ref.sha })),
      [],
    );
    return { ...result, created: missing.length };
  }
  const report = await relayPack({
    source: { url: sourceUrl, authorization: null },
    target: repo,