From 376f918a66dce5094bc0e2fc9043f870b44b8034 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sat, 21 Feb 2026 08:36:44 -0800 Subject: [PATCH] refactor merge: extract lineCount helper, hoist preamble, and gracefully handle git errors --- src/commands/merge.ts | 43 +++++++++++++++++++++++++------------------ src/git.ts | 12 ++++-------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/commands/merge.ts b/src/commands/merge.ts index 26408e5..00f7ace 100644 --- a/src/commands/merge.ts +++ b/src/commands/merge.ts @@ -8,10 +8,15 @@ 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 - return lines.slice(0, maxLines).join("\n") + `\n... (truncated, ${lines.length - maxLines} more lines)` +function lineCount(text: string): number { + if (!text) return 0 + return text.split("\n").length +} + +function truncate(text: string, maxLines = MAX_DIFF_LINES): string { + const count = lineCount(text) + if (count <= maxLines) return text + return text.split("\n").slice(0, maxLines).join("\n") + `\n... (truncated, ${count - maxLines} more lines)` } export async function action(branch: string) { @@ -59,9 +64,7 @@ export async function action(branch: string) { 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) { + if (lineCount(entry.oursDiff) > MAX_DIFF_LINES || lineCount(entry.theirsDiff) > MAX_DIFF_LINES) { skipped.push(entry.file) } else { resolvable.push(entry) @@ -69,27 +72,31 @@ export async function action(branch: string) { } 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) + console.log("\nResolve them manually, then run: git add && git commit --no-edit") + return } + const preamble = [ + `## Merge Context`, + `Merging branch \`${branch}\` into \`${current}\``, + ``, + `### Commits on \`${branch}\`:`, + (branchLog && truncate(branchLog, 50)) || "(no commits)", + ``, + `### Overall changes on \`${branch}\`:`, + (branchDiffStat && truncate(branchDiffStat, 100)) || "(no changes)", + ].join("\n") + for (const { file, oursDiff, theirsDiff } of resolvable) { spin.text = `Resolving ${file}` const content = await Bun.file(join(root, file)).text() const context = [ - `## Merge Context`, - `Merging branch \`${branch}\` into \`${current}\``, - ``, - `### Commits on \`${branch}\`:`, - (branchLog && truncate(branchLog, 50)) || "(no commits)", - ``, - `### Overall changes on \`${branch}\`:`, - (branchDiffStat && truncate(branchDiffStat, 100)) || "(no changes)", + preamble, ``, `---`, `## Conflicted File: ${file}`, @@ -121,7 +128,7 @@ export async function action(branch: string) { 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") + console.log("\nResolve them manually, then run: git add && git commit --no-edit") return } diff --git a/src/git.ts b/src/git.ts index 700a265..faee459 100644 --- a/src/git.ts +++ b/src/git.ts @@ -135,21 +135,17 @@ export async function mergeBase(ref1: string, ref2: string, cwd: string): Promis return result.text().trim() } -/** Get a one-line-per-commit log for a revision range. */ +/** Get a one-line-per-commit log for a revision range. Returns empty string on failure. */ export async function commitLog(range: string, cwd: string): Promise { const result = await $`git log --oneline ${range}`.cwd(cwd).nothrow().quiet() - if (result.exitCode !== 0) { - throw new Error(`Failed to get log for range "${range}"`) - } + if (result.exitCode !== 0) return "" return result.text().trim() } -/** Get a diff stat summary for a revision range. */ +/** Get a diff stat summary for a revision range. Returns empty string on failure. */ export async function diffStat(range: string, cwd: string): Promise { const result = await $`git diff --stat ${range}`.cwd(cwd).nothrow().quiet() - if (result.exitCode !== 0) { - throw new Error(`Failed to get diff stat for range "${range}"`) - } + if (result.exitCode !== 0) return "" return result.text().trim() }