import type { MergeConflict } from "../git/merge"; const MARKER_START = /^<{7}(?: |\n|$)/; const MARKER_MIDDLE = /^={7}\n?$/; const MARKER_END = /^>{7}(?: |\n|$)/; const LINE = /[^\n]*\n|[^\n]+$/g; /** * Resolves every conflict block in merged marker text by keeping the `ours` side, leaving the cleanly * merged lines around it untouched. Returns null when the markers don't parse into `expected` blocks * (for example when the file itself contained marker-like lines), so the caller can treat it as a conflict. */ export function keepOurs(text: string, expected: number): string | null { const out: string[] = []; let state: "outside" | "ours" | "theirs" = "outside"; let blocks = 0; for (const line of text.match(LINE) ?? []) { if (state === "outside") { if (MARKER_START.test(line)) state = "ours"; else if (MARKER_MIDDLE.test(line) || MARKER_END.test(line)) return null; else out.push(line); } else if (state === "ours") { if (MARKER_MIDDLE.test(line)) state = "theirs"; else if (MARKER_START.test(line) || MARKER_END.test(line)) return null; else out.push(line); } else if (MARKER_END.test(line)) { state = "outside"; blocks++; } else if (MARKER_START.test(line) || MARKER_MIDDLE.test(line)) { return null; } } return state === "outside" && blocks === expected ? out.join("") : null; } /** * Replacement contents that resolve text conflicts in favor of `ours`. Conflicts without marker text * already resolve in the merged tree: binary and file/directory keep `ours`, modify/delete keeps the * modified file so no edit is lost. * Null when some text conflict could not be resolved mechanically. */ export function oursResolutions(conflicts: MergeConflict[]): Map | null { const resolutions = new Map(); for (const conflict of conflicts) { if (conflict.content === null) continue; const resolved = keepOurs(conflict.content, conflict.hunks?.length ?? 0); if (resolved === null) return null; resolutions.set(conflict.path, resolved); } return resolutions; }