fix: derive worktree name from path, not branch name
Git worktree names are stored in .git/worktrees/<name> 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 <noreply@anthropic.com>
This commit is contained in:
parent
71c3d3caa9
commit
ddce9e8b03
3 changed files with 32 additions and 8 deletions
|
|
@ -346,6 +346,13 @@ pub fn create_worktree(
|
||||||
branch_name: &str,
|
branch_name: &str,
|
||||||
worktree_path: &Path,
|
worktree_path: &Path,
|
||||||
) -> Result<(), RepoError> {
|
) -> Result<(), RepoError> {
|
||||||
|
// Derive worktree name from path (directory name = slug, no slashes)
|
||||||
|
// Git worktree names are stored in .git/worktrees/<name> 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
|
// Create the branch if it doesn't exist
|
||||||
let head = repo.head()?;
|
let head = repo.head()?;
|
||||||
let head_commit = head.peel_to_commit()?;
|
let head_commit = head.peel_to_commit()?;
|
||||||
|
|
@ -358,7 +365,7 @@ pub fn create_worktree(
|
||||||
// Create the worktree
|
// Create the worktree
|
||||||
let reference = branch.into_reference();
|
let reference = branch.into_reference();
|
||||||
repo.worktree(
|
repo.worktree(
|
||||||
branch_name,
|
worktree_name,
|
||||||
worktree_path,
|
worktree_path,
|
||||||
Some(git2::WorktreeAddOptions::new().reference(Some(&reference))),
|
Some(git2::WorktreeAddOptions::new().reference(Some(&reference))),
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -366,9 +373,17 @@ pub fn create_worktree(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a worktree
|
/// Remove a worktree by path
|
||||||
pub fn remove_worktree(repo: &git2::Repository, name: &str) -> Result<(), RepoError> {
|
///
|
||||||
let worktree = repo.find_worktree(name)?;
|
/// 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)
|
// Prune the worktree (this removes the worktree but keeps the branch)
|
||||||
worktree.prune(Some(
|
worktree.prune(Some(
|
||||||
|
|
|
||||||
|
|
@ -701,6 +701,13 @@ fn create_git_worktree(
|
||||||
return Err("Worktree path already exists".to_string());
|
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/<name> 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
|
// Get HEAD commit to branch from
|
||||||
let head = repo.head().map_err(|e| format!("Failed to get HEAD: {}", e))?;
|
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))?;
|
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
|
// Create the worktree
|
||||||
repo.worktree(
|
repo.worktree(
|
||||||
branch_name,
|
worktree_name,
|
||||||
worktree_path,
|
worktree_path,
|
||||||
Some(git2::WorktreeAddOptions::new().reference(Some(&reference))),
|
Some(git2::WorktreeAddOptions::new().reference(Some(&reference))),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -380,8 +380,9 @@ pub fn handle_cleanup(state: &ProjectState, args: &Value) -> Result<Value, Serve
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove worktree from git
|
// Remove worktree from git
|
||||||
let worktree_removed = if worktree.is_some() {
|
let worktree_removed = if let Some(ref wt) = worktree {
|
||||||
blue_core::repo::remove_worktree(&repo, &branch_name).is_ok()
|
let wt_path = Path::new(&wt.worktree_path);
|
||||||
|
blue_core::repo::remove_worktree(&repo, wt_path).is_ok()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
@ -465,8 +466,9 @@ pub fn handle_remove(state: &ProjectState, args: &Value) -> Result<Value, Server
|
||||||
|
|
||||||
// Remove from git
|
// Remove from git
|
||||||
let repo_path = state.home.root.clone();
|
let repo_path = state.home.root.clone();
|
||||||
|
let wt_path = Path::new(&worktree.worktree_path);
|
||||||
if let Ok(repo) = git2::Repository::open(&repo_path) {
|
if let Ok(repo) = git2::Repository::open(&repo_path) {
|
||||||
if let Err(e) = blue_core::repo::remove_worktree(&repo, &worktree.branch_name) {
|
if let Err(e) = blue_core::repo::remove_worktree(&repo, wt_path) {
|
||||||
return Ok(json!({
|
return Ok(json!({
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": blue_core::voice::error(
|
"message": blue_core::voice::error(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue