# Git Worktree Implementation Specification This document describes the technical implementation of Git worktree support in VibeTunnel. ## Architecture Overview VibeTunnel's worktree support is built on three main components: 1. **Backend API** - Git operations and worktree management 2. **Frontend UI** - Session creation and worktree visualization 3. **Git Hooks** - Automatic synchronization and follow mode ## Backend Implementation ### Core Services **GitService** (`web/src/server/services/git-service.ts`) - Not implemented as a service, Git operations are embedded in routes - Client-side GitService exists at `web/src/client/services/git-service.ts` **Worktree Routes** (`web/src/server/routes/worktrees.ts`) - `GET /api/worktrees` - List all worktrees with stats and follow mode status - `POST /api/worktrees` - Create new worktree - `DELETE /api/worktrees/:branch` - Remove worktree - `POST /api/worktrees/switch` - Switch branch and enable follow mode - `POST /api/worktrees/follow` - Enable/disable follow mode for a branch **Git Routes** (`web/src/server/routes/git.ts`) - `GET /api/git/repo-info` - Get repository information - `POST /api/git/event` - Process git hook events (internal use) - `GET /api/git/follow` - Check follow mode status for a repository - `GET /api/git/notifications` - Get pending notifications ### Key Functions ```typescript // List worktrees with extended information async function listWorktreesWithStats(repoPath: string): Promise // Create worktree with automatic path generation async function createWorktree( repoPath: string, branch: string, path: string, baseBranch?: string ): Promise // Handle branch switching with safety checks async function switchBranch( repoPath: string, branch: string ): Promise ``` ### Git Operations All Git operations use Node.js `child_process.execFile` for security: ```typescript import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; const execFileAsync = promisify(execFile); // Execute git commands safely async function execGit(args: string[], options?: { cwd?: string }) { return execFileAsync('git', args, { ...options, timeout: 30000, maxBuffer: 10 * 1024 * 1024, // 10MB }); } ``` ### Follow Mode Implementation Follow mode uses Git hooks and git config for state management: 1. **State Storage**: Git config `vibetunnel.followWorktree` ```bash # Follow mode stores the worktree path in main repository git config vibetunnel.followWorktree "/path/to/worktree" # Check follow mode status git config vibetunnel.followWorktree # Disable follow mode git config --unset vibetunnel.followWorktree ``` 2. **Git Hooks**: Installed in BOTH main repo and worktree - `post-checkout`: Detects branch switches - `post-commit`: Detects new commits - `post-merge`: Detects merge operations 3. **Event Processing**: Hooks execute `vt git event` command 4. **Synchronization Logic**: - Worktree events → Main repo syncs (branch, commits, checkouts) - Main repo commits → Worktree syncs (commits only) - Main repo branch switch → Auto-unfollow ## Frontend Implementation ### Components **SessionCreateForm** (`web/src/client/components/session-create-form.ts`) - Branch/worktree selection UI - Smart branch switching logic - Warning displays for conflicts **WorktreeManager** (`web/src/client/components/worktree-manager.ts`) - Dedicated worktree management UI - Follow mode controls - Worktree deletion and branch switching - **Note**: Does not include UI for creating new worktrees ### State Management ```typescript // Session creation state @state() private currentBranch: string = ''; @state() private selectedBaseBranch: string = ''; @state() private selectedWorktree?: string; @state() private availableWorktrees: Worktree[] = []; // Branch switching state @state() private branchSwitchWarning?: string; @state() private isLoadingBranches = false; @state() private isLoadingWorktrees = false; ``` ### Branch Selection Logic The new session dialog implements smart branch handling: 1. **No Worktree Selected**: ```typescript if (selectedBaseBranch !== currentBranch) { try { await gitService.switchBranch(repoPath, selectedBaseBranch); effectiveBranch = selectedBaseBranch; } catch (error) { // Show warning, use current branch this.branchSwitchWarning = "Cannot switch due to uncommitted changes"; effectiveBranch = currentBranch; } } ``` 2. **Worktree Selected**: ```typescript // Use worktree's path and branch effectiveWorkingDir = worktreeInfo.path; effectiveBranch = selectedWorktree; // No branch switching occurs ``` ### UI Updates Dynamic labels based on context: ```typescript ${this.selectedWorktree ? 'Base Branch for Worktree:' : 'Switch to Branch:'} ``` Help text explaining behavior: ```typescript ${this.selectedWorktree ? 'New worktree branch will be created from this branch' : this.selectedBaseBranch !== this.currentBranch ? `Session will start on ${this.selectedBaseBranch} (currently on ${this.currentBranch})` : `Current branch: ${this.currentBranch}` } ``` ## Git Hook Integration ### Hook Installation Automatic hook installation on repository access: ```typescript // Install hooks when checking Git repository async function installGitHooks(repoPath: string): Promise { const hooks = ['post-commit', 'post-checkout']; for (const hook of hooks) { await installHook(repoPath, hook); } } ``` ### Hook Script The hook implementation uses the `vt` command: ```bash #!/bin/sh # VibeTunnel Git hook - post-checkout # This hook notifies VibeTunnel when Git events occur # Check if vt command is available if command -v vt >/dev/null 2>&1; then # Run in background to avoid blocking Git operations vt git event & fi # Always exit successfully exit 0 ``` The `vt git event` command: - Sends the repository path to the server via `POST /api/git/event` - Server determines what changed by examining current git state - Triggers branch synchronization if follow mode is enabled - Sends notifications to connected sessions - Runs in background to avoid blocking git operations ### Follow Mode Logic The git event handler determines sync behavior based on event source: ```typescript // Get follow mode configuration const followWorktree = await getGitConfig(mainRepoPath, 'vibetunnel.followWorktree'); if (!followWorktree) return; // Follow mode not enabled // Determine if event is from main repo or worktree const eventPath = req.body.repoPath; const isFromWorktree = eventPath === followWorktree; const isFromMain = eventPath === mainRepoPath; if (isFromWorktree) { // Worktree → Main sync switch (event) { case 'checkout': // Sync branch or commit to main const target = req.body.branch || req.body.commit; await execGit(['checkout', target], { cwd: mainRepoPath }); break; case 'commit': case 'merge': // Pull changes to main await execGit(['fetch'], { cwd: mainRepoPath }); await execGit(['merge', 'FETCH_HEAD'], { cwd: mainRepoPath }); break; } } else if (isFromMain) { // Main → Worktree sync switch (event) { case 'checkout': // Branch switch in main = stop following await unsetGitConfig(mainRepoPath, 'vibetunnel.followWorktree'); sendNotification('Follow mode disabled - switched branches in main repository'); break; case 'commit': // Sync commit to worktree await execGit(['fetch'], { cwd: followWorktree }); await execGit(['merge', 'FETCH_HEAD'], { cwd: followWorktree }); break; } } ``` ## Data Models ### Worktree The Worktree interface differs between backend and frontend: **Backend** (`web/src/server/routes/worktrees.ts`): ```typescript interface Worktree { path: string; branch: string; HEAD: string; detached: boolean; prunable?: boolean; locked?: boolean; lockedReason?: string; // Extended stats commitsAhead?: number; filesChanged?: number; insertions?: number; deletions?: number; hasUncommittedChanges?: boolean; } ``` **Frontend** (`web/src/client/services/git-service.ts`): ```typescript interface Worktree extends BackendWorktree { // UI helpers - added dynamically by routes isMainWorktree?: boolean; isCurrentWorktree?: boolean; } ``` The UI helper fields are computed dynamically in the worktree routes based on the current repository path and are not stored in the backend data model. ### Session with Git Info ```typescript interface Session { id: string; name: string; command: string[]; workingDir: string; // Git information (from shared/types.ts) gitRepoPath?: string; gitBranch?: string; gitAheadCount?: number; gitBehindCount?: number; gitHasChanges?: boolean; gitIsWorktree?: boolean; gitMainRepoPath?: string; } ``` ## Error Handling ### Common Errors 1. **Uncommitted Changes** ```typescript if (hasUncommittedChanges) { throw new Error('Cannot switch branches with uncommitted changes'); } ``` 2. **Branch Already Checked Out** ```typescript // Git automatically prevents this // Error: "fatal: 'branch' is already checked out at '/path/to/worktree'" ``` 3. **Worktree Path Exists** ```typescript if (await pathExists(worktreePath)) { throw new Error(`Path already exists: ${worktreePath}`); } ``` ### Error Recovery - Show user-friendly warnings - Fallback to safe defaults - Never lose user work - Log detailed errors for debugging ## Performance Considerations ### Caching - Worktree list cached for 5 seconds - Branch list cached per repository - Git status cached with debouncing ### Optimization ```typescript // Parallel operations where possible const [branches, worktrees] = await Promise.all([ loadBranches(repoPath), loadWorktrees(repoPath) ]); // Debounced Git checks this.gitCheckDebounceTimer = setTimeout(() => { this.checkGitRepository(); }, 500); ``` ## Security ### Command Injection Prevention All Git commands use array arguments: ```typescript // Safe execFile('git', ['checkout', branchName]) // Never use string concatenation // execFile('git checkout ' + branchName) // DANGEROUS ``` ### Path Validation ```typescript // Resolve and validate paths const absolutePath = path.resolve(repoPath); if (!absolutePath.startsWith(allowedBasePath)) { throw new Error('Invalid repository path'); } ``` ## Worktree Creation Currently, worktree creation is handled through terminal commands rather than UI: ```bash # Create a new worktree for an existing branch git worktree add ../feature-branch feature-branch # Create a new worktree with a new branch git worktree add -b new-feature ../new-feature main ``` ### UI Support Status 1. **WorktreeManager** (`web/src/client/components/worktree-manager.ts`) - No creation UI, only management of existing worktrees - Provides worktree switching, deletion, and follow mode controls - Shows worktree status (commits ahead, uncommitted changes) 2. **SessionCreateForm** (`web/src/client/components/session-create-form.ts`) - Has worktree creation support through the git-branch-selector component - ✅ Creates worktrees and updates UI state properly - ✅ Selects newly created worktree after creation - ✅ Clears loading states and resets form on completion - ✅ Comprehensive branch name validation - ✅ Specific error messages for common failures - ⚠️ Uses simplistic path generation (repo path + branch slug) - ❌ No path customization UI - ❌ No option to create from specific base branch in UI 3. **Path Generation** (`web/src/client/components/session-create-form/git-utils.ts:100-103`) - Simple approach: `${repoPath}-${branchSlug}` - Branch names sanitized to alphanumeric + hyphens/underscores - No user customization of worktree location ### Missing Features from Spec 1. **Worktree Path Customization** - Current: Auto-generated paths only - Spec: Should allow custom path input - Impact: Users cannot organize worktrees in custom locations 2. **Base Branch Selection in UI** - Current: Uses selected base branch from dropdown - Missing: No explicit UI to choose base branch during worktree creation - Workaround: Select base branch first, then create worktree 3. **Comprehensive E2E Tests** - Unit tests exist: `worktrees.test.ts`, `git-hooks.test.ts` - Integration tests exist: `worktree-workflows.test.ts` - Missing: Full E2E tests for UI worktree creation flow ## Testing ### Unit Tests - `worktrees.test.ts` - Route handlers - `git-hooks.test.ts` - Hook installation - `session-create-form.test.ts` - UI logic ### Integration Tests - `worktree-workflows.test.ts` - Full workflows - `follow-mode.test.ts` - Follow mode scenarios ### E2E Tests - Create worktree via UI - Switch branches with warnings - Follow mode synchronization ## Implementation Summary ### ✅ Fully Implemented 1. **Backend API** - All planned endpoints functional - List, create, delete, switch, follow mode operations - Git hook integration for automatic branch following - Proper error handling and validation 2. **Follow Mode** - Complete implementation - Git config storage (`vibetunnel.followBranch`) - Automatic branch synchronization via hooks - UI controls in WorktreeManager and SessionCreateForm 3. **Basic Worktree Creation** - Functional with recent fixes - Create new worktrees from SessionCreateForm - Branch name validation - UI state management - Error handling with specific messages ### ⚠️ Partially Implemented 1. **Path Generation** - Simplified version only - Auto-generates paths as `${repoPath}-${branchSlug}` - No user customization option - Works for basic use cases 2. **Testing** - Good coverage but missing E2E - Unit tests for routes and utilities - Integration tests for workflows - Missing: Full E2E tests with UI interactions ### ❌ Not Implemented 1. **Advanced Worktree Creation UI** - Custom path input field - Path validation and suggestions - Preview of final worktree location 2. **WorktreeManager Creation UI** - No worktree creation in management view - Must use SessionCreateForm or terminal 3. **Worktree Templates/Presets** - No saved worktree configurations - No quick-create from templates