diff --git a/src/commands/rebase.ts b/src/commands/rebase.ts index 3689a1a..ff7b220 100644 --- a/src/commands/rebase.ts +++ b/src/commands/rebase.ts @@ -16,23 +16,23 @@ export async function action(branch: string) { } const main = await git.mainBranch(root) - const spin = spinner("Fetching origin", branch) + const fetchSpin = spinner("Fetching origin", branch) await $`git -C ${root} fetch origin ${main}`.nothrow().quiet() - spin.text = `Rebasing onto origin/${main}` + fetchSpin.text = `Rebasing onto origin/${main}` let conflicts = await git.rebase(`origin/${main}`, worktree) if (conflicts.length === 0) { - spin.succeed(`Rebased ${branch} onto ${main}`) + fetchSpin.succeed(`Rebased ${branch} onto ${main}`) return } - spin.stop() + fetchSpin.stop() console.log(`◆ Rebase conflicts in ${conflicts.length} file(s). Resolving with Claude...`) - const spin2 = spinner("Starting container", branch) + const resolveSpin = spinner("Starting container", branch) try { - await vm.ensure((msg) => { spin2.text = msg }) + await vm.ensure((msg) => { resolveSpin.text = msg }) let round = 1 while (conflicts.length > 0) { @@ -41,16 +41,16 @@ export async function action(branch: string) { } await resolveConflicts(conflicts, worktree, (file) => { - spin2.text = `Resolving ${file} (round ${round})` + resolveSpin.text = `Resolving ${file} (round ${round})` }) conflicts = await git.rebaseContinue(worktree) round++ } - spin2.succeed(`Rebased ${branch} onto ${main} (resolved ${round - 1} conflict round(s))`) + resolveSpin.succeed(`Rebased ${branch} onto ${main} (resolved ${round - 1} conflict round(s))`) } catch (err) { - spin2.fail(String((err as Error).message ?? err)) + resolveSpin.fail(String((err as Error).message ?? err)) await git.rebaseAbort(worktree) process.exit(1) } diff --git a/src/git.ts b/src/git.ts index eb609b4..1f5c475 100644 --- a/src/git.ts +++ b/src/git.ts @@ -121,6 +121,12 @@ export async function abortMerge(cwd: string): Promise { /** Rebase the current branch onto another. Returns conflicted file paths, or empty array if clean. */ export async function rebase(onto: string, cwd: string): Promise { + // Bail early if a rebase is already in progress + const inProgress = await $`git -C ${cwd} rev-parse --verify --quiet REBASE_HEAD`.nothrow().quiet() + if (inProgress.exitCode === 0) { + throw new Error(`A rebase is already in progress. Run "git -C ${cwd} rebase --abort" to cancel it first.`) + } + const result = await $`git rebase ${onto}`.cwd(cwd).nothrow().quiet() if (result.exitCode === 0) return [] @@ -128,7 +134,9 @@ export async function rebase(onto: string, cwd: string): Promise { const files = unmerged.trim().split("\n").filter(Boolean) if (files.length > 0) return files - throw new Error(`Failed to rebase onto "${onto}": ${result.stderr.toString().trim()}`) + // No conflicts but rebase still failed — include stderr for diagnostics + const stderr = result.stderr.toString().trim() + throw new Error(`Rebase onto "${onto}" failed: ${stderr || "(no output from git)"}`) } /** Continue a rebase after resolving conflicts. Returns conflicted files for the next commit, or empty if done. */