Merge branch 'fix-conflicts'

This commit is contained in:
Chris Wanstrath 2026-02-19 09:50:04 -08:00
commit 4d95deebb3
2 changed files with 71 additions and 5 deletions

View File

@ -144,8 +144,50 @@ program
.action(async (branch: string) => {
const root = await git.repoRoot()
await git.merge(branch, root)
const conflicts = await git.merge(branch, root)
if (conflicts.length === 0) {
console.log(`Merged ${branch} into current branch`)
await closeAction(branch)
return
}
// Resolve conflicts with Claude
console.log(`Merge conflicts in ${conflicts.length} file(s). Resolving with Claude...`)
const spin = spinner("Starting container")
try {
await vm.ensure((msg) => { spin.text = msg })
for (const file of conflicts) {
spin.text = `Resolving ${file}`
const content = await Bun.file(join(root, file)).text()
const tmpPath = join(homedir(), '.sandlot', '.conflict-tmp')
await Bun.write(tmpPath, content)
const resolved = await vm.exec(
join(homedir(), '.sandlot'),
'cat /sandlot/.conflict-tmp | claude -p "resolve this merge conflict. output ONLY the resolved file content with no markdown fences, no explanation, no surrounding text."',
)
await Bun.file(tmpPath).unlink().catch(() => {})
if (resolved.exitCode !== 0) {
throw new Error(`Claude failed to resolve ${file}: ${resolved.stderr}`)
}
await Bun.write(join(root, file), resolved.stdout + "\n")
await git.stageFile(file, root)
}
await git.commitMerge(root)
spin.succeed(`Resolved ${conflicts.length} conflict(s) and merged ${branch}`)
} catch (err) {
spin.fail(String((err as Error).message ?? err))
await git.abortMerge(root)
process.exit(1)
}
await closeAction(branch)
})

View File

@ -81,10 +81,34 @@ export async function checkout(branch: string, cwd: string): Promise<void> {
}
}
/** Merge a branch into the current branch. */
export async function merge(branch: string, cwd: string): Promise<void> {
/** Merge a branch into the current branch. Returns conflicted file paths, or empty array if clean. */
export async function merge(branch: string, cwd: string): Promise<string[]> {
const result = await $`git merge ${branch}`.cwd(cwd).nothrow().quiet()
if (result.exitCode !== 0) {
if (result.exitCode === 0) return []
// Check for unmerged (conflicted) files
const unmerged = await $`git diff --name-only --diff-filter=U`.cwd(cwd).nothrow().quiet().text()
const files = unmerged.trim().split("\n").filter(Boolean)
if (files.length > 0) return files
// Not a conflict — some other merge failure
throw new Error(`Failed to merge branch "${branch}": ${result.stderr.toString().trim()}`)
}
/** Stage a file. */
export async function stageFile(file: string, cwd: string): Promise<void> {
await $`git add ${file}`.cwd(cwd).nothrow().quiet()
}
/** Finalize a merge commit after resolving conflicts. */
export async function commitMerge(cwd: string): Promise<void> {
const result = await $`git commit --no-edit`.cwd(cwd).nothrow().quiet()
if (result.exitCode !== 0) {
throw new Error(`Failed to commit merge: ${result.stderr.toString().trim()}`)
}
}
/** Abort an in-progress merge. */
export async function abortMerge(cwd: string): Promise<void> {
await $`git merge --abort`.cwd(cwd).nothrow().quiet()
}