From ddce9e8b03c8153ee33f33dc2ca8173945916ca3 Mon Sep 17 00:00:00 2001 From: Eric Garcia Date: Fri, 30 Jan 2026 19:09:13 -0500 Subject: [PATCH] fix: derive worktree name from path, not branch name Git worktree names are stored in .git/worktrees/ and cannot contain slashes. The code was using branch names like "feature/slug" or "rfc/name" as worktree names, which git2 rejects silently. Now the worktree name is derived from the path's directory name (the slug), which is always a simple identifier without slashes. Co-Authored-By: Claude Opus 4.5 --- crates/blue-core/src/repo.rs | 23 +++++++++++++++++++---- crates/blue-mcp/src/handlers/realm.rs | 9 ++++++++- crates/blue-mcp/src/handlers/worktree.rs | 8 +++++--- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/crates/blue-core/src/repo.rs b/crates/blue-core/src/repo.rs index 7f6745c..bd12f9c 100644 --- a/crates/blue-core/src/repo.rs +++ b/crates/blue-core/src/repo.rs @@ -346,6 +346,13 @@ pub fn create_worktree( branch_name: &str, worktree_path: &Path, ) -> Result<(), RepoError> { + // Derive worktree name from path (directory name = slug, no slashes) + // Git worktree names are stored in .git/worktrees/ and cannot contain slashes + let worktree_name = worktree_path + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| RepoError::Git(git2::Error::from_str("Invalid worktree path")))?; + // Create the branch if it doesn't exist let head = repo.head()?; let head_commit = head.peel_to_commit()?; @@ -358,7 +365,7 @@ pub fn create_worktree( // Create the worktree let reference = branch.into_reference(); repo.worktree( - branch_name, + worktree_name, worktree_path, Some(git2::WorktreeAddOptions::new().reference(Some(&reference))), )?; @@ -366,9 +373,17 @@ pub fn create_worktree( Ok(()) } -/// Remove a worktree -pub fn remove_worktree(repo: &git2::Repository, name: &str) -> Result<(), RepoError> { - let worktree = repo.find_worktree(name)?; +/// Remove a worktree by path +/// +/// Derives the worktree name from the path's directory name. +pub fn remove_worktree(repo: &git2::Repository, worktree_path: &Path) -> Result<(), RepoError> { + // Derive worktree name from path (same as create_worktree) + let worktree_name = worktree_path + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| RepoError::Git(git2::Error::from_str("Invalid worktree path")))?; + + let worktree = repo.find_worktree(worktree_name)?; // Prune the worktree (this removes the worktree but keeps the branch) worktree.prune(Some( diff --git a/crates/blue-mcp/src/handlers/realm.rs b/crates/blue-mcp/src/handlers/realm.rs index 14ac9de..83e41f8 100644 --- a/crates/blue-mcp/src/handlers/realm.rs +++ b/crates/blue-mcp/src/handlers/realm.rs @@ -701,6 +701,13 @@ fn create_git_worktree( return Err("Worktree path already exists".to_string()); } + // Derive worktree name from path (directory name = slug, no slashes) + // Git worktree names are stored in .git/worktrees/ and cannot contain slashes + let worktree_name = worktree_path + .file_name() + .and_then(|n| n.to_str()) + .ok_or("Invalid worktree path")?; + // Get HEAD commit to branch from let head = repo.head().map_err(|e| format!("Failed to get HEAD: {}", e))?; let commit = head.peel_to_commit().map_err(|e| format!("Failed to get commit: {}", e))?; @@ -720,7 +727,7 @@ fn create_git_worktree( // Create the worktree repo.worktree( - branch_name, + worktree_name, worktree_path, Some(git2::WorktreeAddOptions::new().reference(Some(&reference))), ) diff --git a/crates/blue-mcp/src/handlers/worktree.rs b/crates/blue-mcp/src/handlers/worktree.rs index be72c60..e89faab 100644 --- a/crates/blue-mcp/src/handlers/worktree.rs +++ b/crates/blue-mcp/src/handlers/worktree.rs @@ -380,8 +380,9 @@ pub fn handle_cleanup(state: &ProjectState, args: &Value) -> Result Result