Auto-resolve merge conflicts using Claude when merging branches
This commit is contained in:
parent
3c77c43999
commit
8cbeede82b
46
src/cli.ts
46
src/cli.ts
|
|
@ -143,8 +143,50 @@ program
|
||||||
.action(async (branch: string) => {
|
.action(async (branch: string) => {
|
||||||
const root = await git.repoRoot()
|
const root = await git.repoRoot()
|
||||||
|
|
||||||
await git.merge(branch, root)
|
const conflicts = await git.merge(branch, root)
|
||||||
console.log(`Merged ${branch} into current branch`)
|
|
||||||
|
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)
|
await closeAction(branch)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
30
src/git.ts
30
src/git.ts
|
|
@ -81,10 +81,34 @@ export async function checkout(branch: string, cwd: string): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Merge a branch into the current branch. */
|
/** 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<void> {
|
export async function merge(branch: string, cwd: string): Promise<string[]> {
|
||||||
const result = await $`git merge ${branch}`.cwd(cwd).nothrow().quiet()
|
const result = await $`git merge ${branch}`.cwd(cwd).nothrow().quiet()
|
||||||
|
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) {
|
if (result.exitCode !== 0) {
|
||||||
throw new Error(`Failed to merge branch "${branch}": ${result.stderr.toString().trim()}`)
|
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()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user