diff --git a/packages/ui/src/components/session/GitHubIntegrationDialog.tsx b/packages/ui/src/components/session/GitHubIntegrationDialog.tsx index 0deacae2..e80b537a 100644 --- a/packages/ui/src/components/session/GitHubIntegrationDialog.tsx +++ b/packages/ui/src/components/session/GitHubIntegrationDialog.tsx @@ -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, { diff --git a/packages/ui/src/components/session/NewWorktreeDialog.tsx b/packages/ui/src/components/session/NewWorktreeDialog.tsx index 90f271d5..15bff4f4 100644 --- a/packages/ui/src/components/session/NewWorktreeDialog.tsx +++ b/packages/ui/src/components/session/NewWorktreeDialog.tsx @@ -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, diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index c353d00c..d37cf241 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -1323,6 +1323,7 @@ export const dict = { 'session.githubIntegration.error.notConnected': 'GitHub not connected', 'session.githubIntegration.error.loadDataFailed': 'Failed to load data', 'session.githubIntegration.validation.branchAlreadyCheckedOut': 'Branch is already checked out in a worktree', + 'session.githubIntegration.validation.branchAlreadyExists': 'Branch already exists locally', 'session.githubIntegration.validation.failed': 'Validation failed', 'chat.fileAttachment.toast.attachFailed': 'Failed to attach file', 'chat.fileAttachment.toast.someFilesSkipped': 'Some files were skipped:\n{summary}', diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index 4ae14555..e975342f 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -1289,6 +1289,7 @@ export const dict: Record = { "session.githubIntegration.error.notConnected": "GitHub no está conectado", "session.githubIntegration.error.loadDataFailed": "No se pudieron cargar los datos", "session.githubIntegration.validation.branchAlreadyCheckedOut": "La rama ya está en uso en un worktree", + "session.githubIntegration.validation.branchAlreadyExists": "La rama ya existe localmente", "session.githubIntegration.validation.failed": "No se pudo validar", "chat.fileAttachment.toast.attachFailed": "No se pudo adjuntar el archivo", "chat.fileAttachment.toast.someFilesSkipped": "Algunos archivos se omitieron:\n{summary}", diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index dee46a18..5dc7c9e7 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -1325,6 +1325,7 @@ export const dict: Record = { 'session.githubIntegration.error.notConnected': 'GitHub에 연결되어 있지 않습니다', 'session.githubIntegration.error.loadDataFailed': '데이터를 불러오지 못했습니다', 'session.githubIntegration.validation.branchAlreadyCheckedOut': '브랜치가 이미 워크트리에 체크아웃되어 있습니다', + 'session.githubIntegration.validation.branchAlreadyExists': '브랜치가 이미 로컬에 있습니다', 'session.githubIntegration.validation.failed': '유효성 검사에 실패했습니다', 'chat.fileAttachment.toast.attachFailed': '첨부 파일 실패', 'chat.fileAttachment.toast.someFilesSkipped': '일부 파일을 건너뛰었습니다:\n{summary}', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index 9ac6c423..f542017e 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -390,6 +390,7 @@ export const dict: Record = { 'session.githubIntegration.error.notConnected': 'GitHub nie jest połączony', 'session.githubIntegration.error.loadDataFailed': 'Nie udało się załadować danych', 'session.githubIntegration.validation.branchAlreadyCheckedOut': 'Gałąź jest już wyewidencjonowana w drzewie pracy', + 'session.githubIntegration.validation.branchAlreadyExists': 'Gałąź już istnieje lokalnie', 'session.githubIntegration.validation.failed': 'Walidacja nie powiodła się', 'chat.fileAttachment.toast.attachFailed': 'Nie udało się dołączyć pliku', 'chat.fileAttachment.toast.someFilesSkipped': 'Niektóre pliki zostały pominięte:\n{summary}', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index 366a1262..5a4a5da0 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -1289,6 +1289,7 @@ export const dict: Record = { "session.githubIntegration.error.notConnected": "GitHub não está conectado", "session.githubIntegration.error.loadDataFailed": "Não foi possível carregar os dados", "session.githubIntegration.validation.branchAlreadyCheckedOut": "A branch já está em uso em um worktree", + "session.githubIntegration.validation.branchAlreadyExists": "A branch já existe localmente", "session.githubIntegration.validation.failed": "Não foi possível validar", "chat.fileAttachment.toast.attachFailed": "Não foi possível anexar o arquivo", "chat.fileAttachment.toast.someFilesSkipped": "Alguns arquivos foram omitidos:\n{summary}", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index 23bd7f04..d2258504 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -1289,6 +1289,7 @@ export const dict: Record = { "session.githubIntegration.error.notConnected": "GitHub не підключено", "session.githubIntegration.error.loadDataFailed": "Не вдалося завантажити дані", "session.githubIntegration.validation.branchAlreadyCheckedOut": "Гілку вже відкрито в worktree", + "session.githubIntegration.validation.branchAlreadyExists": "Гілка вже існує локально", "session.githubIntegration.validation.failed": "Помилка перевірки", "chat.fileAttachment.toast.attachFailed": "Не вдалося прикріпити файл", "chat.fileAttachment.toast.someFilesSkipped": "Деякі файли були пропущені:\n{summary}", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index e121369b..9772183b 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -1289,6 +1289,7 @@ export const dict: Record = { 'session.githubIntegration.error.notConnected': 'GitHub 未连接', 'session.githubIntegration.error.loadDataFailed': '加载数据失败', 'session.githubIntegration.validation.branchAlreadyCheckedOut': '该分支已在某个 worktree 中检出', + 'session.githubIntegration.validation.branchAlreadyExists': '该分支已在本地存在', 'session.githubIntegration.validation.failed': '校验失败', 'chat.fileAttachment.toast.attachFailed': '附加文件失败', 'chat.fileAttachment.toast.someFilesSkipped': '以下文件被跳过:\n{summary}',