diff --git a/src/commands/merge.ts b/src/commands/merge.ts index 5a62075..26408e5 100644 --- a/src/commands/merge.ts +++ b/src/commands/merge.ts @@ -6,6 +6,8 @@ import { spinner } from "../spinner.ts" import { die } from "../fmt.ts" import { action as closeAction } from "./close.ts" +const MAX_DIFF_LINES = 300 + function truncate(text: string, maxLines = 200): string { const lines = text.split("\n") if (lines.length <= maxLines) return text @@ -42,13 +44,41 @@ export async function action(branch: string) { const branchLog = await git.commitLog(`${base}..${branch}`, root) const branchDiffStat = await git.diffStat(`${base}..${branch}`, root) - for (const file of conflicts) { + // Fetch diffs for all conflicted files and partition by complexity + spin.text = "Checking conflict complexity" + const fileDiffs = await Promise.all( + conflicts.map(async (file) => { + const [oursDiff, theirsDiff] = await Promise.all([ + git.fileDiff(base, "HEAD", file, root), + git.fileDiff(base, branch, file, root), + ]) + return { file, oursDiff, theirsDiff } + }), + ) + + const resolvable: typeof fileDiffs = [] + const skipped: string[] = [] + for (const entry of fileDiffs) { + const oursLines = entry.oursDiff ? entry.oursDiff.split("\n").length : 0 + const theirsLines = entry.theirsDiff ? entry.theirsDiff.split("\n").length : 0 + if (oursLines > MAX_DIFF_LINES || theirsLines > MAX_DIFF_LINES) { + skipped.push(entry.file) + } else { + resolvable.push(entry) + } + } + + if (resolvable.length === 0) { + await git.abortMerge(root) + spin.fail("All conflicts are too complex for auto-resolution") + console.log("\nThe following files need manual resolution:") + for (const file of skipped) console.log(` - ${file}`) + process.exit(1) + } + + for (const { file, oursDiff, theirsDiff } of resolvable) { spin.text = `Resolving ${file}` - const [oursDiff, theirsDiff] = await Promise.all([ - git.fileDiff(base, "HEAD", file, root), - git.fileDiff(base, branch, file, root), - ]) const content = await Bun.file(join(root, file)).text() const context = [ @@ -87,6 +117,14 @@ export async function action(branch: string) { await git.stageFile(file, root) } + if (skipped.length > 0) { + spin.succeed(`Resolved ${resolvable.length} of ${conflicts.length} conflict(s)`) + console.log("\nThe following files are too complex for auto-resolution:") + for (const file of skipped) console.log(` - ${file}`) + console.log("\nResolve them manually, then run: git commit --no-edit") + return + } + await git.commitMerge(root) spin.succeed(`Resolved ${conflicts.length} conflict(s) and merged ${branch}`) } catch (err) {