119 lines · 5.3 KB
import { advertisement, artifactsSource, relayPack } from "../git/relay";
import type { RelaySource } from "../git/relay";
import type { AdvertisedRef, RefCommand } from "../git/ref-sync-protocol";
import { pushToArtifacts } from "../git/writer";
import { loadRepoById } from "../repos";
import { gitAuthorization, installationToken } from "./github-app";
import type { OutboundResult } from "./outbound";
import { parseConfig } from "./store";
import type { IntegrationRow } from "./store";
import type { DeliverInput } from "./deliver";
import type { RepoRow } from "~/lib/types";

const MAX_MIRROR_REFS = 1000;
const MAX_MIRROR_BYTES = 1024 * 1024 * 1024;
const ZERO_SHA = "0".repeat(40);
const GITHUB_REPO = /^[A-Za-z0-9-]{1,39}\/[A-Za-z0-9._-]{1,100}$/;

export type MirrorResult = { updated: number; rejected: string[]; skipped: number };

export function isGithubRepoName(value: string): boolean {
  return GITHUB_REPO.test(value) && !value.includes("..");
}

/** Refs to send so `target` matches `source`; never deletes, and caps the count so one sync stays bounded. */
export function planMirror(
  source: AdvertisedRef[],
  target: AdvertisedRef[],
): { commands: RefCommand[]; skipped: number } {
  const current = new Map(target.map((ref) => [ref.name, ref.sha]));
  const changed = source.filter((ref) => current.get(ref.name) !== ref.sha);
  const commands = changed.slice(0, MAX_MIRROR_REFS).map((ref) => ({
    name: ref.name,
    sha: ref.sha,
    oldSha: current.get(ref.name) ?? ZERO_SHA,
  }));
  return { commands, skipped: changed.length - commands.length };
}

async function githubRemote(row: IntegrationRow): Promise<RelaySource> {
  const config = parseConfig(row);