import { triggerPush } from "../events/push-trigger";
import { receivePackBody } from "../git/ref-sync-protocol";
import type { RelaySource } from "../git/relay";
import { pushToArtifacts } from "../git/writer";
import { screenMirrorPush } from "../integrations/mirror-screen";
import { CONFLICT_PREFIX, ZERO_SHA } from "./plan";
import { pushToRemote } from "./remote";
import type { Plan } from "./sync";
import type { SyncReport } from "~/lib/mirror";
import type { RepoRow } from "~/lib/types";
const MAX_GATE_PASSES = 3;
const MAX_TRIGGERED_REFS = 50;
function emptyPack(): ReadableStream<Uint8Array> {
return new ReadableStream({ start: (controller) => controller.close() });
}
/**
* Applies forge-side updates through the push gates (branch protection as the configuring admin with write
* permission, push protection), dropping refused refs and re-screening the rest. A refused merge is not
* pushed to the remote either, so the two sides never end up with different merge results.
* Applied refs fire normal push events, so CI, webhooks and integrations run. Returns ref → new sha.
*/
export async function applyLocal(input: {
repo: RepoRow;
plan: Plan;
pusherId: string;
report: SyncReport;
/** Called before push events fire, so the mirror can recognize its own writes when they come back. */
onWrite?: (written: Map<string, string>) => Promise<void>;
}): Promise<Map<string, string>> {
const { repo, plan, report } = input;
let commands = plan.local;
let clean = false;
for (let pass = 0; pass < MAX_GATE_PASSES && commands.length > 0 && !clean; pass++) {
const screened = await screenMirrorPush(
repo,
commands,
receivePackBody(commands, emptyPack()),