fix: allow PR worktrees from existing local branches

Use existing local PR branches instead of blocking worktree creation
Only block PR branches already checked out in another worktree
Clarify local branch validation messages across locales
This commit is contained in:
Bohdan Triapitsyn
2026-05-08 16:59:31 +03:00
parent a591f63ed2
commit 1a0f3bea8e
9 changed files with 37 additions and 9 deletions
@@ -195,13 +195,15 @@ export function GitHubIntegrationDialog({
worktreeName: branchName,
});
const isBlocked = result.errors.some(
(entry) => entry.code === 'branch_in_use' || entry.code === 'branch_exists'
);
const blockingError = result.errors.find((entry) => entry.code === 'branch_in_use');
setValidations(prev => new Map(prev).set(branchName, {
isValid: !isBlocked,
error: isBlocked ? t('session.githubIntegration.validation.branchAlreadyCheckedOut') : null,
isValid: !blockingError,
error: blockingError
? t(blockingError.code === 'branch_exists'
? 'session.githubIntegration.validation.branchAlreadyExists'
: 'session.githubIntegration.validation.branchAlreadyCheckedOut')
: null,
}));
} catch {
setValidations(prev => new Map(prev).set(branchName, {
@@ -129,12 +129,24 @@ const sanitizeRemoteName = (value: string): string => {
return normalized || 'pr-head';
};
const resolvePrWorktreeConfig = (pr: GitHubPullRequestSummary, remoteBranches: string[]) => {
const resolvePrWorktreeConfig = (pr: GitHubPullRequestSummary, localBranches: string[], remoteBranches: string[]) => {
const headBranch = normalizeBranchName(pr.head || '');
if (!headBranch) {
throw new Error('PR head branch is missing');
}
if (localBranches.includes(headBranch)) {
return {
existingBranch: headBranch,
setUpstream: undefined,
upstreamRemote: undefined,
upstreamBranch: undefined,
ensureRemoteName: undefined,
ensureRemoteUrl: undefined,
sourceLabel: headBranch,
};
}
const availableRemoteBranch = remoteBranches.find((remoteBranch) => {
const slashIndex = remoteBranch.indexOf('/');
if (slashIndex <= 0 || slashIndex >= remoteBranch.length - 1) {
@@ -734,11 +746,15 @@ export function NewWorktreeDialog({
// Only run server validation if we have values
if (normalizedBranch && normalizedWorktree) {
const linkedPr = mode === 'new-branch' ? newBranchState.linkedPr : null;
const prConfig = linkedPr ? resolvePrWorktreeConfig(linkedPr, localBranches, remoteBranches) : null;
const result = await validateWorktreeCreate(projectRef, {
mode: mode === 'existing-branch' ? 'existing' : 'new',
mode: mode === 'existing-branch' || prConfig ? 'existing' : 'new',
branchName: normalizedBranch,
worktreeName: normalizedWorktree,
existingBranch: mode === 'existing-branch' ? normalizedBranch : undefined,
existingBranch: prConfig?.existingBranch ?? (mode === 'existing-branch' ? normalizedBranch : undefined),
...(prConfig?.ensureRemoteName ? { ensureRemoteName: prConfig.ensureRemoteName } : {}),
...(prConfig?.ensureRemoteUrl ? { ensureRemoteUrl: prConfig.ensureRemoteUrl } : {}),
});
if (abortController.signal.aborted) return;
@@ -777,8 +793,11 @@ export function NewWorktreeDialog({
projectRef,
mode,
newBranchState.branchName,
newBranchState.linkedPr,
existingBranchState.selectedBranch,
currentState.worktreeName,
localBranches,
remoteBranches,
validation.touched,
validationAbortController,
isCreating,
@@ -846,7 +865,7 @@ export function NewWorktreeDialog({
let sourceLabel = '';
const args = (() => {
if (linkedPr) {
const prConfig = resolvePrWorktreeConfig(linkedPr, remoteBranches);
const prConfig = resolvePrWorktreeConfig(linkedPr, localBranches, remoteBranches);
sourceLabel = prConfig.sourceLabel;
return {
preferredName: normalizedBranch || normalizedWorktree,