import { checkProtections } from "../git/proxy";
import { findForcePushes } from "../git/force-push";
import { isHiddenRef } from "../git/hidden-refs";
import type { RefCommand } from "../git/ref-sync-protocol";
import type { ScreenResult } from "../git/relay";
import { scanPushBody } from "../security/push-protection";
import type { RepoRow } from "~/lib/types";
const ZERO_SHA = "0".repeat(40);
const HIDDEN = "refs/pull and refs/gitfor are managed by gitfor";
const FAST_FORWARD_ONLY = "mirror is fast-forward only; an admin can enable force mirroring";
const SECRETS = "push declined due to secrets";
export type MirrorPolicy = { force: boolean; pusherId: string | null };
/** The push gates, injectable so the mirror policy can be tested without a repository store. */
export type MirrorGates = {
checkProtections: typeof checkProtections;
findForcePushes: typeof findForcePushes;
scanPushBody: typeof scanPushBody;
};
const DEFAULT_GATES: MirrorGates = { checkProtections, findForcePushes, scanPushBody };
/** Hidden refs are dropped before anything is fetched: a mirror can never write gitfor's bookkeeping refs. */
export function withoutHiddenRefs(commands: RefCommand[]): { commands: RefCommand[]; rejected: Map<string, string> } {
const rejected = new Map<string, string>();
const kept = commands.filter((command) => {
if (!isHiddenRef(command.name)) return true;
rejected.set(command.name, HIDDEN);
return false;
});
return { commands: kept, rejected };
}
/** Updates of existing refs (not creations or deletions), the ones that can be non-fast-forward. */
/** Refusals that will repeat on every run until someone changes a rule or the upstream history. */
export function isPolicyRefusal(reason: string): boolean {
return (
[HIDDEN, FAST_FORWARD_ONLY, SECRETS, "protected"].some((marker) => reason.includes(marker)) &&