Add pre-flight check for target branch existence before PR creation (vibe-kanban) (#1341)

* Excellent! I've successfully implemented the pre-flight check for target branch existence before PR creation. Here's what I did:

## Summary

I've added a pre-flight check that verifies the target branch exists on the remote before attempting to create a pull request. This provides a clear, early error message instead of failing during the GitHub API call.

### Changes Made

1. **Added `fetch_and_check_branch_exists()` method** in `crates/services/src/services/git.rs:1414-1446`:
   - Fetches from the remote to update tracking refs
   - Checks if the target branch exists (both local and remote branches)
   - Uses best-effort fetching (doesn't fail if fetch fails)
   - Returns `Ok(true)` if branch exists, `Ok(false)` if not

2. **Added pre-flight check** in `crates/server/src/routes/task_attempts.rs:639-667`:
   - Placed before pushing the branch to GitHub
   - Fetches and verifies the target branch exists
   - Returns a clear error message if the branch doesn't exist
   - Continues with PR creation even if the check fails (fallback to GitHub API error)
   - Includes logging for debugging

### Key Features

- **Fail-fast approach**: Catches missing target branch before wasting time on the GitHub API call
- **Clear error messages**: Users get an actionable error message: "Target branch 'X' does not exist on remote. Please ensure the branch exists before creating a pull request."
- **Graceful degradation**: If the fetch/check fails for any reason, the code continues and lets the GitHub API handle the error (existing fallback behavior)
- **Works with all git remotes**: Not GitHub-specific, uses git fetch
- **Performance trade-off**: Adds 1-3 seconds for the fetch operation, but provides better UX

### Testing

All checks passed:
-  Rust compilation (cargo clippy)
-  TypeScript type generation (npm run generate-types:check)

The implementation follows the "Option A - Fetch-then-check" approach from the task description, providing early detection of missing target branches while maintaining backward compatibility with the existing error handling.

* Cleanup script changes for task attempt 607cef96-2669-46e6-bee9-fd2d524be0bd

* Perfect! All the review comments have been addressed:

## Changes Made

1. **Simplified `fetch_and_check_branch_exists()` in git.rs** - Now uses the existing `Self::find_branch()` helper method instead of manually calling `repo.find_branch()` twice, making the code cleaner and more consistent with the rest of the codebase.

2. **Removed all comments** from both files - The code is now self-explanatory without the verbose comments.

3. **Removed all logging** from task_attempts.rs - No more `tracing::info!`, `tracing::warn!`, or `tracing::error!` calls for the pre-flight check.

4. **Simplified control flow** - Changed from a match statement to a cleaner if-let pattern as suggested by clippy.

The implementation is now more concise while maintaining the same functionality:
- Fetches from remote and checks if the target branch exists
- Returns a clear error if the branch doesn't exist
- Silently continues on success or fetch errors (letting GitHub API handle any remaining issues)

All compilation checks pass successfully.

* Perfect! All checks pass. Let me create a summary of the changes:

I've successfully added a typed error for the missing target branch case. Here's what was changed:

1. **Added `TargetBranchNotFound` variant to `GitHubServiceError`** in `crates/services/src/services/github.rs:38-40`:
   - Takes a `String` parameter for the branch name
   - Includes a clear error message
   - Marked with `#[ts(skip)]` since it has a parameter

2. **Updated `is_api_data()` method** in `crates/services/src/services/github.rs:93`:
   - Added `GitHubServiceError::TargetBranchNotFound(_)` to the match pattern
   - This ensures the error is properly typed and won't be retried

3. **Updated `create_github_pr()`** in `crates/server/src/routes/task_attempts.rs:643-645`:
   - Replaced the string error message with `GitHubServiceError::TargetBranchNotFound(target_branch.clone())`
   - Now uses `ApiResponse::error_with_data()` instead of `ApiResponse::error()`, consistent with other typed errors in the function

- **Type-safe error handling**: Frontend can now properly discriminate this error type
- **Consistent with existing patterns**: Follows the same pattern as `TokenInvalid`, `InsufficientPermissions`, etc.
- **Better API design**: The error is now part of the function's return type signature

All compilation and type generation checks pass successfully!

* Great! All checks pass. Let me create a summary:

I've successfully added frontend error handling for the `target_branch_not_found` error. Here's what was done:

1. **Added translation key** in `frontend/src/i18n/locales/en/tasks.json:345`:
   - Key: `createPrDialog.errors.targetBranchNotFound`
   - Message: `"Target branch '{{branch}}' does not exist on remote. Please ensure the branch exists before creating a pull request."`
   - Uses interpolation to inject the branch name dynamically

2. **Added error handler** in `frontend/src/components/dialogs/tasks/CreatePRDialog.tsx:187-195`:
   - Checks if `result.error.type === 'target_branch_not_found'`
   - Extracts the `branch` field from the error object
   - Displays localized error message with the branch name interpolated
   - Clears the GH CLI helper UI state

When a user tries to create a PR with a non-existent target branch:

1. **Backend pre-flight check** fetches from remote and checks if the target branch exists
2. **If branch doesn't exist**: Returns `CreatePrError::TargetBranchNotFound { branch: "xyz" }`
3. **Frontend receives** typed error with discriminant `type: "target_branch_not_found"` and `branch: "xyz"`
4. **Dialog displays**: "Target branch 'xyz' does not exist on remote. Please ensure the branch exists before creating a pull request."

All checks pass (TypeScript compilation, linting, type generation).

* Excellent! All checks pass. Here's the summary:

I've successfully added the `targetBranchNotFound` translation key to all 3 missing locale files:

1. **Spanish (es)** - `frontend/src/i18n/locales/es/tasks.json:121`:
   - `"La rama objetivo '{{branch}}' no existe en el remoto. Por favor, asegúrese de que la rama exista antes de crear una solicitud de extracción."`

2. **Japanese (ja)** - `frontend/src/i18n/locales/ja/tasks.json:121`:
   - `"ターゲットブランチ '{{branch}}' がリモートに存在しません。プルリクエストを作成する前にブランチが存在することを確認してください。"`

3. **Korean (ko)** - `frontend/src/i18n/locales/ko/tasks.json:121`:
   - `"대상 브랜치 '{{branch}}'이(가) 원격에 존재하지 않습니다. 풀 리퀘스트를 생성하기 전에 브랜치가 존재하는지 확인하세요."`

All translations follow the same pattern with the `{{branch}}` interpolation parameter and maintain consistency with the existing error messages in each locale.

All checks pass (TypeScript compilation, frontend and backend checks)!

Fix i18n

* Perfect! All checks pass. Here's the complete summary:

## Summary

I've successfully fixed the branch name format handling issue in `check_remote_branch_exists()`.

### The Problem

The method only tried the exact branch name provided (e.g., "main"), but git2's `find_branch()` with `BranchType::Remote` requires the full remote branch name (e.g., "origin/main"). This meant:
-  User provides "main" → check fails (should succeed if origin/main exists)
-  User provides "origin/main" → check succeeds

### The Fix

Updated `check_remote_branch_exists()` in `crates/services/src/services/git.rs:1414-1428` to try both formats:

1. **First attempt**: Try the branch name as-is (handles "origin/main" format)
2. **Fallback**: Prepend default remote name (handles "main" format → "origin/main")

This follows the same pattern used by `find_branch()`, `find_branch_type()`, and `check_branch_exists()` throughout the codebase.

### Complete Implementation

The full pre-flight check implementation now includes:

**Backend:**
-  Fetches from remote to get latest refs
-  Checks if target branch exists (handles both "main" and "origin/main" formats)
-  Returns typed error `CreatePrError::TargetBranchNotFound { branch }`
-  Handles auth errors during fetch → `CreatePrError::GitCliNotLoggedIn`
-  Handles missing git CLI → `CreatePrError::GitCliNotInstalled`

**Frontend:**
-  Handles `target_branch_not_found` error type in CreatePRDialog
-  Displays localized error message with branch name
-  Translations added for all 4 locales (en, es, ja, ko)

All checks pass!

* Lint

* Change pre-flight check for create PR (vibe-kanban 4f79fbc8)

to use git ls-remote --branches to check for remote branche existence instead of libgit2. To do this add an appropriate method to crates/services/src/services/git/cli.rs and modify the preflight check.

* Perfect! All checks pass. The implementation is now complete:

## Complete Implementation Summary

### What Was Built

A pre-flight check for PR creation that verifies the target branch exists on the remote before attempting to create the pull request.

### Key Changes

**Backend:**
1. **`check_remote_branch_exists()`** in `git.rs:1414-1428` - Checks if a branch exists remotely, handles both "main" and "origin/main" formats
2. **`fetch_and_check_branch_exists()`** in `git.rs:1430-1439` - Fetches from remote then checks existence, propagates errors
3. **`CreatePrError::TargetBranchNotFound`** in `task_attempts.rs:616` - New typed error variant with branch name
4. **Pre-flight check** in `create_github_pr()` in `task_attempts.rs:639-662` - Validates target branch exists, handles auth errors

**Frontend:**
1. **Error handler** in `CreatePRDialog.tsx:187-195` - Displays localized error message
2. **Translations** in all 4 locales (en, es, ja, ko) - Localized error messages with branch name interpolation

### What Works

-  Detects missing target branches before push/PR creation
-  Handles "main" and "origin/main" branch name formats
-  Fails fast on auth errors during fetch
-  Returns typed errors the frontend can handle
-  Displays clear, localized error messages with the branch name
-  All checks pass (Rust clippy, TypeScript, type generation)
This commit is contained in:
Alex Netsch
2025-11-24 15:24:52 +00:00
committed by GitHub
parent f651c64f7d
commit 1ef162416d
9 changed files with 85 additions and 5 deletions

View File

@@ -189,6 +189,14 @@ const CreatePRDialogImpl = NiceModal.create<CreatePRDialogProps>(
setError(result.message || t(gitCliErrorKey));
setGhCliHelp(null);
return;
} else if (result.error.type === 'target_branch_not_found') {
setError(
t('createPrDialog.errors.targetBranchNotFound', {
branch: result.error.branch,
})
);
setGhCliHelp(null);
return;
}
}

View File

@@ -343,7 +343,8 @@
"repoNotFoundOrNoAccess": "Repository not found or no access. Please check your repository access and ensure you are authenticated.",
"failedToCreate": "Failed to create GitHub PR",
"gitCliNotLoggedIn": "Git is not authenticated. Run \"gh auth login\" (or configure Git credentials) and try again.",
"gitCliNotInstalled": "Git CLI is not installed. Install Git to create a PR."
"gitCliNotInstalled": "Git CLI is not installed. Install Git to create a PR.",
"targetBranchNotFound": "Target branch '{{branch}}' does not exist on remote. Please ensure the branch exists before creating a pull request."
},
"loginRequired": {
"title": "Sign in to create a pull request",

View File

@@ -119,7 +119,8 @@
"repoNotFoundOrNoAccess": "Repositorio no encontrado o sin acceso. Por favor verifica el acceso al repositorio y asegúrate de estar autenticado.",
"failedToCreate": "Error al crear PR de GitHub",
"gitCliNotLoggedIn": "Git no está autenticado. Ejecuta \"gh auth login\" (o configura las credenciales de Git) e inténtalo de nuevo.",
"gitCliNotInstalled": "Git CLI no está instalado. Instala Git para crear una PR."
"gitCliNotInstalled": "Git CLI no está instalado. Instala Git para crear una PR.",
"targetBranchNotFound": "La rama objetivo '{{branch}}' no existe en el remoto. Por favor, asegúrese de que la rama exista antes de crear una solicitud de extracción."
},
"loginRequired": {
"title": "Inicia sesión para crear un pull request",

View File

@@ -119,7 +119,8 @@
"repoNotFoundOrNoAccess": "リポジトリが見つからないか、アクセス権がありません。リポジトリへのアクセス権を確認し、認証されていることを確認してください。",
"failedToCreate": "GitHub PRの作成に失敗しました",
"gitCliNotLoggedIn": "Gitが認証されていません。\"gh auth login\" を実行するかGitの認証情報を設定してから再試行してください。",
"gitCliNotInstalled": "Git CLIがインストールされていません。PRを作成するにはGitをインストールしてください。"
"gitCliNotInstalled": "Git CLIがインストールされていません。PRを作成するにはGitをインストールしてください。",
"targetBranchNotFound": "ターゲットブランチ '{{branch}}' がリモートに存在しません。プルリクエストを作成する前にブランチが存在することを確認してください。"
},
"loginRequired": {
"title": "プルリクエストを作成するにはサインインしてください",

View File

@@ -119,7 +119,8 @@
"repoNotFoundOrNoAccess": "저장소를 찾을 수 없거나 액세스 권한이 없습니다. 저장소 액세스를 확인하고 인증되었는지 확인하세요.",
"failedToCreate": "GitHub PR 생성에 실패했습니다",
"gitCliNotLoggedIn": "Git이 인증되지 않았습니다. \"gh auth login\"을 실행하거나 Git 자격 증명을 설정한 후 다시 시도하세요.",
"gitCliNotInstalled": "Git CLI가 설치되어 있지 않습니다. PR을 생성하려면 Git을 설치하세요."
"gitCliNotInstalled": "Git CLI가 설치되어 있지 않습니다. PR을 생성하려면 Git을 설치하세요.",
"targetBranchNotFound": "대상 브랜치 '{{branch}}'이(가) 원격에 존재하지 않습니다. 풀 리퀘스트를 생성하기 전에 브랜치가 존재하는지 확인하세요."
},
"loginRequired": {
"title": "Pull Request를 만들려면 로그인하세요",