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:
@@ -31,19 +31,27 @@ export class SimpleGitBranchManager implements BranchManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async mergeBranch(repoPath: string, sourceBranch: string, targetBranch: string): Promise<MergeResult> {
|
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 tmpPath = mkdtempSync(join(tmpdir(), 'cw-merge-'));
|
||||||
const repoGit = simpleGit(repoPath);
|
const repoGit = simpleGit(repoPath);
|
||||||
|
const tempBranch = `cw-merge-${Date.now()}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create ephemeral worktree on target branch
|
// Create worktree with a temp branch starting at targetBranch's commit
|
||||||
await repoGit.raw(['worktree', 'add', tmpPath, targetBranch]);
|
await repoGit.raw(['worktree', 'add', '-b', tempBranch, tmpPath, targetBranch]);
|
||||||
|
|
||||||
const wtGit = simpleGit(tmpPath);
|
const wtGit = simpleGit(tmpPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wtGit.merge([sourceBranch, '--no-edit']);
|
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');
|
log.info({ repoPath, sourceBranch, targetBranch }, 'merge completed cleanly');
|
||||||
return { success: true, message: `Merged ${sourceBranch} into ${targetBranch}` };
|
return { success: true, message: `Merged ${sourceBranch} into ${targetBranch}` };
|
||||||
} catch (mergeErr) {
|
} catch (mergeErr) {
|
||||||
@@ -73,6 +81,10 @@ export class SimpleGitBranchManager implements BranchManager {
|
|||||||
try { rmSync(tmpPath, { recursive: true, force: true }); } catch { /* ignore */ }
|
try { rmSync(tmpPath, { recursive: true, force: true }); } catch { /* ignore */ }
|
||||||
try { await repoGit.raw(['worktree', 'prune']); } 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 */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export function InitiativeReview({ initiativeId, onCompleted }: InitiativeReview
|
|||||||
) : (
|
) : (
|
||||||
<GitMerge className="h-3.5 w-3.5" />
|
<GitMerge className="h-3.5 w-3.5" />
|
||||||
)}
|
)}
|
||||||
Merge & Push to Default
|
Merge & Push to {targetBranch || "default"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user