fix: Merge worktree conflict when target branch already checked out

Use a temp branch + update-ref to avoid "already checked out" error
when merging into the default branch. Also show actual branch name
in the Merge & Push button instead of "Default".
This commit is contained in:
Lukas May
2026-03-06 10:54:42 +01:00
parent bdc95bcb26
commit 1bc3f85d6a
2 changed files with 16 additions and 4 deletions

View File

@@ -31,19 +31,27 @@ export class SimpleGitBranchManager implements BranchManager {
}
async mergeBranch(repoPath: string, sourceBranch: string, targetBranch: string): Promise<MergeResult> {
// Use an ephemeral worktree for merge safety
// Use an ephemeral worktree with a temp branch for merge safety.
// We can't check out targetBranch directly — it may already be checked out
// in the clone's main working tree or an agent worktree.
const tmpPath = mkdtempSync(join(tmpdir(), 'cw-merge-'));
const repoGit = simpleGit(repoPath);
const tempBranch = `cw-merge-${Date.now()}`;
try {
// Create ephemeral worktree on target branch
await repoGit.raw(['worktree', 'add', tmpPath, targetBranch]);
// Create worktree with a temp branch starting at targetBranch's commit
await repoGit.raw(['worktree', 'add', '-b', tempBranch, tmpPath, targetBranch]);
const wtGit = simpleGit(tmpPath);
try {
await wtGit.merge([sourceBranch, '--no-edit']);
// Update the real target branch ref to the merge result.
// update-ref bypasses the "branch is checked out" guard.
const mergeCommit = (await wtGit.revparse(['HEAD'])).trim();
await repoGit.raw(['update-ref', `refs/heads/${targetBranch}`, mergeCommit]);
log.info({ repoPath, sourceBranch, targetBranch }, 'merge completed cleanly');
return { success: true, message: `Merged ${sourceBranch} into ${targetBranch}` };
} catch (mergeErr) {
@@ -73,6 +81,10 @@ export class SimpleGitBranchManager implements BranchManager {
try { rmSync(tmpPath, { recursive: true, force: true }); } catch { /* ignore */ }
try { await repoGit.raw(['worktree', 'prune']); } catch { /* ignore */ }
}
// Delete the temp branch
try {
await repoGit.raw(['branch', '-D', tempBranch]);
} catch { /* ignore — may already be cleaned up */ }
}
}

View File

@@ -214,7 +214,7 @@ export function InitiativeReview({ initiativeId, onCompleted }: InitiativeReview
) : (
<GitMerge className="h-3.5 w-3.5" />
)}
Merge & Push to Default
Merge & Push to {targetBranch || "default"}
</Button>
</div>
</div>