Refactor branch fetching to use repo IDs instead of project IDs (Vibe Kanban) (#1560)
* All tasks are complete. Here's a summary of the changes made:
## Summary
Successfully refactored the project branches API to fetch branches using repo IDs instead of project IDs.
### Files Created
- `frontend/src/hooks/useRepoBranches.ts` - New hook that fetches branches for a single repository using `repoApi.getBranches(repoId)`
### Files Modified
1. **`frontend/src/hooks/index.ts`** - Added export for `useRepoBranches`
2. **`frontend/src/components/dialogs/tasks/GitActionsDialog.tsx`**
- Removed `useState` for `repoBranches` and `loadingBranches`
- Removed `useEffect` that called `projectsApi.getBranches(effectiveProjectId)`
- Removed `useMemo` that filtered branches by `selectedRepoId`
- Now uses `useRepoBranches(selectedRepoId)` to fetch branches directly for the selected repo from the task attempt
3. **`frontend/src/components/dialogs/tasks/CreatePRDialog.tsx`**
- Removed `projectId` from props interface and component destructuring
- Removed `useState` for `branches` and `branchesLoading`
- Removed branch-fetching logic from useEffect
- Now uses `useRepoBranches(repoId)` to fetch branches directly for the specified repo
- Split useEffect into two: one for form initialization, one for setting default base branch
4. **`frontend/src/components/tasks/Toolbar/GitOperations.tsx`**
- Removed `projectId` from `CreatePRDialog.show()` call
### Benefits
- More efficient - fetches only branches for the specific repo needed instead of all project repos
- Better caching - React Query caches branches per repo for efficient reuse
- Cleaner code - removed manual filtering logic from components
- Type safety - hooks return `GitBranch[]` directly instead of `RepositoryBranches[]`
* All tasks complete. Here's a summary of the changes made:
## Summary
Successfully decoupled the branches API from project IDs.
### Changes Made
**1. `frontend/src/hooks/useBranches.ts`** - Refactored to accept `repos: Array<{ id: string; name: string }>` instead of `projectId`
- Now uses `useQueries` to fetch branches for multiple repos in parallel
- Reuses the same query keys as `useRepoBranches` for cache consistency
**2. `frontend/src/hooks/useRepoBranchSelection.ts`** - Refactored to accept `repos: Repo[]` instead of `projectId`
- Removed internal fetch of project repos (caller now provides them)
- Removed `projectRepos` from return value
- Uses refactored `useBranches(repos)`
**3. `frontend/src/components/dialogs/tasks/CreateAttemptDialog.tsx`**
- Added `useQuery` to fetch project repos separately
- Passes `repos` to `useRepoBranchSelection`
**4. `frontend/src/components/dialogs/tasks/TaskFormDialog.tsx`**
- Already fetched `projectRepos`
- Updated to pass `projectRepos` to refactored `useBranches`
**5. `frontend/src/components/layout/Navbar.tsx`**
- Replaced `useBranches(projectId)` with `useQuery` for `projectsApi.getRepositories`
- Only needed repo count, not branches
**6. `frontend/src/components/projects/ProjectCard.tsx`**
- Replaced `useBranches(project.id)` with `useQuery` for `projectsApi.getRepositories`
- Only needed repo count, not branches
### Benefits
- Branch hooks no longer require a project ID
- Repos can come from any source (project, attempt, or future sources without projects)
- Better separation of concerns
- Enables future task attempts without projects attached
* Cleanup script changes for task attempt 6b5b9208-565d-4620-9fe3-20791db77d7a
* The fix is already applied. Type checks pass with `Repo[]` instead of the loose `Array<{ id: string; name: string }>` type.
This makes it explicit that `useBranches` expects actual `Repo` objects (with `Repo.id` being the repository's UUID), not project-repo junction records.
* All done. The refactor is complete:
**Summary of changes:**
1. **Created `useProjectRepos` hook** (`frontend/src/hooks/useProjectRepos.ts`) - A reusable hook that fetches project repositories with proper query key management
2. **Updated 5 files** to use the new hook instead of duplicating the `useQuery` pattern:
- `CreateAttemptDialog.tsx`
- `TaskFormDialog.tsx`
- `Navbar.tsx`
- `ProjectCard.tsx`
- `NoServerContent.tsx`
3. **Earlier refactors** (from before the summary):
- `useBranches.ts` - Now accepts `repos: Repo[]` instead of `projectId`
- `useRepoBranchSelection.ts` - Now accepts `repos: Repo[]` instead of `projectId`
This enables future task attempts without projects attached, as branch fetching is now decoupled from project IDs.
* No more usages. The branches in `ProjectTasks.tsx` are used for the diffs panel - showing available branches for git operations when viewing an attempt's diffs.
* Cleanup script changes for task attempt 6b5b9208-565d-4620-9fe3-20791db77d7a
* All done. The refactor is complete:
**Summary of changes:**
`GitOperations` now fetches its own branches using `useRepoBranches(selectedRepoId)` instead of receiving them as a prop. This:
1. Removes prop drilling through `ProjectTasks` → `DiffsPanelContainer` → `DiffsPanel` → `GitOperations`
2. Removes the need for `ProjectTasks` to fetch all project repos just to get branches for the selected repo
3. Makes `GitOperations` self-contained - it already has `selectedRepoId` from `useAttemptRepo`, so it can fetch its own branches
4. Simplifies `GitActionsDialog` which no longer needs to fetch branches either
* All done. Here's a summary of the changes:
**Removed `projectId` from GitOperations flow:**
1. **`useRebase.ts`** - Changed param from `projectId` to `repoId`, updated invalidation to use `repoBranchKeys.byRepo(repoId)`
2. **`useChangeTargetBranch.ts`** - Changed param from `projectId` to `repoId`, updated invalidation to use `repoBranchKeys.byRepo(repoId)`
3. **`useMerge.ts`** - Updated invalidation to use `repoBranchKeys.all` instead of `['projectBranches']`
4. **`useGitOperations.ts`** - Changed param from `projectId` to `repoId`
5. **`GitOperations.tsx`** - Removed `projectId` prop, now passes `selectedRepoId` to `useGitOperations`
6. **`GitActionsDialog.tsx`** - Removed `projectId` from props and internal logic
7. **`DiffsPanelContainer`** (in ProjectTasks.tsx) - Removed `projectId` prop
8. **Callers of `GitActionsDialog.show`** - Removed `projectId` from `NextActionCard.tsx` and `actions-dropdown.tsx`
* The cleanup is complete. Here's a summary of what was done:
**Removed dead project branches code:**
1. **Backend** (`crates/server/src/routes/projects.rs`):
- Removed `get_project_branches` handler and route
- Removed `RepositoryBranches` and `ProjectBranchesResponse` structs
- Fixed unused `Serialize` import warning
2. **Type Generation** (`crates/server/src/bin/generate_types.rs`):
- Removed exports for `ProjectBranchesResponse` and `RepositoryBranches`
3. **Frontend Hooks** (`frontend/src/hooks/useBranches.ts`):
- Defined local `RepositoryBranches` type instead of importing from shared/types
4. **Frontend API** (`frontend/src/lib/api.ts`):
- Removed `projectsApi.getBranches` method
- Removed `RepositoryBranches` and `ProjectBranchesResponse` imports
Both TypeScript and Rust compilation pass without errors.
* Done. Removed the `RepositoryBranches` type and simplified `useBranches` to return `Map<string, GitBranch[]>`. Changes:
1. **`useBranches.ts`** - Returns `Map<string, GitBranch[]>` instead of `RepositoryBranches[]`
2. **`useRepoBranchSelection.ts`** - Uses `branchMap.get(repo.id) ?? []` instead of `.find()`
3. **`TaskFormDialog.tsx`** - Uses `branchMap.get(repo.id) ?? []` instead of `.find()`
Type checks pass.
* Done. Deleted `useBranches` entirely and consolidated everything into `useRepoBranchSelection`. Summary:
1. **Deleted** `frontend/src/hooks/useBranches.ts`
2. **Updated** `useRepoBranchSelection.ts` - inlined the `useQueries` logic directly
3. **Updated** `hooks/index.ts` - removed `useBranches` export
4. **Updated** `TaskFormDialog.tsx` - now uses `useRepoBranchSelection` instead of duplicating the config-building logic
Type checks pass.
* types
This commit is contained in:
@@ -103,8 +103,6 @@ fn generate_types_content() -> String {
|
||||
server::routes::config::CheckEditorAvailabilityResponse::decl(),
|
||||
server::routes::config::CheckAgentAvailabilityQuery::decl(),
|
||||
server::routes::oauth::CurrentUserResponse::decl(),
|
||||
server::routes::projects::RepositoryBranches::decl(),
|
||||
server::routes::projects::ProjectBranchesResponse::decl(),
|
||||
server::routes::task_attempts::CreateFollowUpAttempt::decl(),
|
||||
server::routes::task_attempts::ChangeTargetBranchRequest::decl(),
|
||||
server::routes::task_attempts::ChangeTargetBranchResponse::decl(),
|
||||
|
||||
@@ -14,9 +14,9 @@ use db::models::{
|
||||
repo::Repo,
|
||||
};
|
||||
use deployment::Deployment;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use services::services::{
|
||||
file_search_cache::SearchQuery, git::GitBranch, project::ProjectServiceError,
|
||||
file_search_cache::SearchQuery, project::ProjectServiceError,
|
||||
remote_client::CreateRemoteProjectPayload,
|
||||
};
|
||||
use ts_rs::TS;
|
||||
@@ -28,20 +28,6 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{DeploymentImpl, error::ApiError, middleware::load_project_middleware};
|
||||
|
||||
/// Branches for a single repository
|
||||
#[derive(Debug, Serialize, TS)]
|
||||
pub struct RepositoryBranches {
|
||||
pub repository_id: Uuid,
|
||||
pub repository_name: String,
|
||||
pub branches: Vec<GitBranch>,
|
||||
}
|
||||
|
||||
/// Response containing branches grouped by repository
|
||||
#[derive(Debug, Serialize, TS)]
|
||||
pub struct ProjectBranchesResponse {
|
||||
pub repositories: Vec<RepositoryBranches>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, TS)]
|
||||
pub struct LinkToExistingRequest {
|
||||
pub remote_project_id: Uuid,
|
||||
@@ -66,33 +52,6 @@ pub async fn get_project(
|
||||
Ok(ResponseJson(ApiResponse::success(project)))
|
||||
}
|
||||
|
||||
pub async fn get_project_branches(
|
||||
Extension(project): Extension<Project>,
|
||||
State(deployment): State<DeploymentImpl>,
|
||||
) -> Result<ResponseJson<ApiResponse<ProjectBranchesResponse>>, ApiError> {
|
||||
let repositories = deployment
|
||||
.project()
|
||||
.get_repositories(&deployment.db().pool, project.id)
|
||||
.await?;
|
||||
|
||||
let mut repo_branches = Vec::with_capacity(repositories.len());
|
||||
|
||||
for repo in repositories {
|
||||
let branches = deployment.git().get_all_branches(&repo.path)?;
|
||||
repo_branches.push(RepositoryBranches {
|
||||
repository_id: repo.id,
|
||||
repository_name: repo.name,
|
||||
branches,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(ResponseJson(ApiResponse::success(
|
||||
ProjectBranchesResponse {
|
||||
repositories: repo_branches,
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
pub async fn link_project_to_existing_remote(
|
||||
Extension(project): Extension<Project>,
|
||||
State(deployment): State<DeploymentImpl>,
|
||||
@@ -583,7 +542,6 @@ pub fn router(deployment: &DeploymentImpl) -> Router<DeploymentImpl> {
|
||||
get(get_project).put(update_project).delete(delete_project),
|
||||
)
|
||||
.route("/remote/members", get(get_project_remote_members))
|
||||
.route("/branches", get(get_project_branches))
|
||||
.route("/search", get(search_project_files))
|
||||
.route("/open-editor", post(open_project_in_editor))
|
||||
.route(
|
||||
|
||||
Reference in New Issue
Block a user