import { env } from "cloudflare:workers";
import { mergeBudget, mergeRootTrees } from "../git/merge";
import type { MergeConflict } from "../git/merge";
import { MergeTooComplexError } from "../git/merge3";
import { makeCommit, signatureNow } from "../git/objects";
import type { RawObject } from "../git/objects";
import { compareCommits } from "../git/history";
import type { GitStore } from "../git/store";
import { publicUrl } from "../events/emit";
import { oursResolutions } from "./resolve";
import type { Relation } from "./plan";
import type { ConflictStrategy } from "~/lib/mirror";
const MAX_LISTED_PATHS = 20;
/** How `local` and `remote` relate, from their merge base. */
export async function relate(store: GitStore, local: string, remote: string): Promise<Relation> {
const comparison = await compareCommits(store, local, store, remote);
if (comparison.mergeBase === null) return comparison.truncated ? "unknown" : "unrelated";
if (comparison.mergeBase === local) return "remote-ahead";
if (comparison.mergeBase === remote) return "local-ahead";
return "diverged";
}
export type MirrorMerge =
| { ok: true; sha: string; objects: RawObject[]; resolved: string[] }
| { ok: false; reason: string; paths: string[] };
function mirrorSignature() {
const host = new URL(publicUrl("/")).hostname;
return signatureNow(`${env.FORGE_NAME || "gitfor"} mirror`, `mirror@${host}`);
}
function describe(conflicts: MergeConflict[]): string[] {
return conflicts.slice(0, MAX_LISTED_PATHS).map((conflict) => `${conflict.path} (${conflict.kind})`);
}
/**
* Merges diverged `local` and `remote` tips into one commit both sides can fast-forward to. Non-overlapping
* changes merge cleanly; overlapping hunks either stop the merge (`merge`) or keep the preferred side's lines