From 386954b3c47fafaeec81e3cef1f5bb5fadd9090d Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Sat, 21 Feb 2026 07:54:23 -0800 Subject: [PATCH] Truncate large diffs in merge context and add error handling to git log/diff helpers --- src/commands/merge.ts | 22 +++++++++++++++------- src/git.ts | 11 ++++++++++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/commands/merge.ts b/src/commands/merge.ts index 834914e..5a62075 100644 --- a/src/commands/merge.ts +++ b/src/commands/merge.ts @@ -6,6 +6,12 @@ import { spinner } from "../spinner.ts" import { die } from "../fmt.ts" import { action as closeAction } from "./close.ts" +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)` +} + export async function action(branch: string) { const root = await git.repoRoot() const session = await state.getSession(root, branch) @@ -33,14 +39,16 @@ export async function action(branch: string) { spin.text = "Gathering merge context" const current = await git.currentBranch(root) const base = await git.mergeBase("HEAD", branch, root) - const branchLog = await git.log(`${base}..${branch}`, root) + const branchLog = await git.commitLog(`${base}..${branch}`, root) const branchDiffStat = await git.diffStat(`${base}..${branch}`, root) for (const file of conflicts) { spin.text = `Resolving ${file}` - const oursDiff = await git.fileDiff(base, "HEAD", file, root) - const theirsDiff = await git.fileDiff(base, branch, file, root) + 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 = [ @@ -48,19 +56,19 @@ export async function action(branch: string) { `Merging branch \`${branch}\` into \`${current}\``, ``, `### Commits on \`${branch}\`:`, - branchLog || "(no commits)", + (branchLog && truncate(branchLog, 50)) || "(no commits)", ``, `### Overall changes on \`${branch}\`:`, - branchDiffStat || "(no changes)", + (branchDiffStat && truncate(branchDiffStat, 100)) || "(no changes)", ``, `---`, `## Conflicted File: ${file}`, ``, `### Changes made on current branch (\`${current}\`):`, - oursDiff || "(no changes to this file)", + (oursDiff && truncate(oursDiff)) || "(no changes to this file)", ``, `### Changes made on incoming branch (\`${branch}\`):`, - theirsDiff || "(no changes to this file)", + (theirsDiff && truncate(theirsDiff)) || "(no changes to this file)", ``, `### File with conflict markers:`, content, diff --git a/src/git.ts b/src/git.ts index babcb4f..700a265 100644 --- a/src/git.ts +++ b/src/git.ts @@ -136,20 +136,29 @@ export async function mergeBase(ref1: string, ref2: string, cwd: string): Promis } /** Get a one-line-per-commit log for a revision range. */ -export async function log(range: string, cwd: string): Promise { +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}"`) + } return result.text().trim() } /** Get a diff stat summary for a revision range. */ 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}"`) + } return result.text().trim() } /** Get the diff for a specific file between two refs. */ export async function fileDiff(ref1: string, ref2: string, file: string, cwd: string): Promise { const result = await $`git diff ${ref1} ${ref2} -- ${file}`.cwd(cwd).nothrow().quiet() + if (result.exitCode !== 0) { + throw new Error(`Failed to get diff for "${file}" between "${ref1}" and "${ref2}"`) + } return result.text().trim() }