Merge branch 'conflict-resolution'
# Conflicts: # src/git.ts
This commit is contained in:
commit
5c378ab239
|
|
@ -81,6 +81,29 @@ export async function teardownSession(root: string, branch: string, worktree: st
|
|||
await state.removeSession(root, branch)
|
||||
}
|
||||
|
||||
/** Generated files to skip AI resolution — accept theirs and move on (basename match so `packages/foo/bun.lock` is also covered). */
|
||||
const SKIP_RESOLVE = new Set([
|
||||
"bun.lock",
|
||||
"bun.lockb",
|
||||
"Cargo.lock",
|
||||
"composer.lock",
|
||||
"Gemfile.lock",
|
||||
"go.sum",
|
||||
"mix.lock",
|
||||
"package-lock.json",
|
||||
"Pipfile.lock",
|
||||
"pnpm-lock.yaml",
|
||||
"Podfile.lock",
|
||||
"poetry.lock",
|
||||
"pubspec.lock",
|
||||
"flake.lock",
|
||||
"gradle.lockfile",
|
||||
"npm-shrinkwrap.json",
|
||||
"Package.resolved",
|
||||
"uv.lock",
|
||||
"yarn.lock",
|
||||
])
|
||||
|
||||
/** Resolve conflict markers in files using Claude, then stage them. */
|
||||
export async function resolveConflicts(
|
||||
files: string[],
|
||||
|
|
@ -90,15 +113,24 @@ export async function resolveConflicts(
|
|||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i]
|
||||
onFile(file, i + 1, files.length)
|
||||
const content = await Bun.file(join(cwd, file)).text()
|
||||
|
||||
if (SKIP_RESOLVE.has(basename(file))) {
|
||||
await git.checkoutTheirs(file, cwd)
|
||||
await git.stageFile(file, cwd)
|
||||
continue
|
||||
}
|
||||
|
||||
const content = await Bun.file(join(cwd, file)).text().catch(() => {
|
||||
throw new Error(`Failed to read conflicted file: ${file}`)
|
||||
})
|
||||
|
||||
const resolved = await vm.claudePipe(
|
||||
content,
|
||||
"resolve this merge conflict. output ONLY the resolved file content with no markdown fences, no explanation, no surrounding text.",
|
||||
)
|
||||
|
||||
if (resolved.exitCode !== 0) {
|
||||
throw new Error(`Claude failed to resolve ${file}: ${resolved.stderr}`)
|
||||
if (resolved.exitCode !== 0 || !resolved.stdout.trim()) {
|
||||
throw new Error(`Claude failed to resolve ${file}: ${resolved.stderr.trim() || "(no output)"}`)
|
||||
}
|
||||
|
||||
await Bun.write(join(cwd, file), resolved.stdout.trimEnd() + "\n")
|
||||
|
|
@ -145,6 +177,7 @@ export async function mergeAndClose(branch: string, opts?: { force?: boolean }):
|
|||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
spin.fail(message)
|
||||
if (session) await vm.clearActivity(session.worktree, branch) // process.exit below skips finally
|
||||
await git.abortMerge(root)
|
||||
process.exit(1)
|
||||
} finally {
|
||||
|
|
|
|||
31
src/git.ts
31
src/git.ts
|
|
@ -2,6 +2,12 @@ import { existsSync } from "fs"
|
|||
import { rm } from "fs/promises"
|
||||
import { $ } from "bun"
|
||||
|
||||
/** Format a git error with a fallback for empty stderr. */
|
||||
function gitError(action: string, stderr: Buffer | string): Error {
|
||||
const msg = stderr.toString().trim()
|
||||
return new Error(`${action}: ${msg || "(no output)"}`)
|
||||
}
|
||||
|
||||
/** Get the repo root from a working directory. */
|
||||
export async function repoRoot(cwd?: string): Promise<string> {
|
||||
const result = await $`git rev-parse --show-toplevel`.cwd(cwd ?? ".").nothrow().quiet()
|
||||
|
|
@ -72,7 +78,7 @@ export async function createWorktree(branch: string, worktreePath: string, cwd:
|
|||
}
|
||||
if (result.exitCode !== 0) {
|
||||
if (switchedFromBranch) await checkout(branch, cwd).catch(() => {})
|
||||
throw new Error(`Failed to create worktree for "${branch}": ${result.stderr.toString().trim()}`)
|
||||
throw gitError(`Failed to create worktree for "${branch}"`, result.stderr)
|
||||
}
|
||||
return { branchCreated: exists !== "local" }
|
||||
}
|
||||
|
|
@ -98,7 +104,7 @@ export async function deleteLocalBranch(branch: string, cwd: string): Promise<vo
|
|||
export async function checkout(branch: string, cwd: string): Promise<void> {
|
||||
const result = await $`git checkout ${branch}`.cwd(cwd).nothrow().quiet()
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Error(`Failed to checkout branch "${branch}": ${result.stderr.toString().trim()}`)
|
||||
throw gitError(`Failed to checkout branch "${branch}"`, result.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +119,7 @@ export async function merge(branch: string, cwd: string): Promise<string[]> {
|
|||
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()}`)
|
||||
throw gitError(`Failed to merge branch "${branch}"`, result.stderr)
|
||||
}
|
||||
|
||||
/** Return the staged diff as text. */
|
||||
|
|
@ -125,20 +131,31 @@ export async function diffStaged(cwd: string): Promise<string> {
|
|||
export async function commit(message: string, cwd: string): Promise<void> {
|
||||
const result = await $`git commit -m ${message}`.cwd(cwd).nothrow().quiet()
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Error(`Failed to commit: ${result.stderr.toString().trim()}`)
|
||||
throw gitError("Failed to commit", result.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
/** Accept "theirs" version of a conflicted file. */
|
||||
export async function checkoutTheirs(file: string, cwd: string): Promise<void> {
|
||||
const result = await $`git checkout --theirs -- ${file}`.cwd(cwd).nothrow().quiet()
|
||||
if (result.exitCode !== 0) {
|
||||
throw gitError(`Failed to checkout theirs for ${file}`, result.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
/** Stage a file. */
|
||||
export async function stageFile(file: string, cwd: string): Promise<void> {
|
||||
await $`git add ${file}`.cwd(cwd).nothrow().quiet()
|
||||
const result = await $`git add ${file}`.cwd(cwd).nothrow().quiet()
|
||||
if (result.exitCode !== 0) {
|
||||
throw gitError(`Failed to stage ${file}`, result.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
/** 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()}`)
|
||||
throw gitError("Failed to commit merge", result.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +211,7 @@ export async function rebaseContinue(cwd: string): Promise<string[]> {
|
|||
const files = unmerged.trim().split("\n").filter(Boolean)
|
||||
if (files.length > 0) return files
|
||||
|
||||
throw new Error(`Rebase --continue failed: ${result.stderr.toString().trim()}`)
|
||||
throw gitError("Rebase --continue failed", result.stderr)
|
||||
}
|
||||
|
||||
/** Abort an in-progress rebase. */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user