refactor merge: extract lineCount helper, hoist preamble, and gracefully handle git errors

This commit is contained in:
Chris Wanstrath 2026-02-21 08:36:44 -08:00
parent c6b6f52b1f
commit 376f918a66
2 changed files with 29 additions and 26 deletions

View File

@ -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 <files> && 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 <files> && git commit --no-edit")
return
}

View File

@ -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<string> {
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<string> {
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()
}