use crate::error::{CwError, Result};
use crate::git;
use crate::messages;
use crate::registry;
pub fn worktree_path(
branch: Option<&str>,
global_mode: bool,
list_branches: bool,
interactive: bool,
) -> Result<()> {
if interactive {
return interactive_path_selection(global_mode);
}
if list_branches {
return list_branch_names(global_mode);
}
let branch = branch.ok_or_else(|| {
CwError::Git(
"branch argument is required (unless --list-branches or --interactive is used)"
.to_string(),
)
})?;
if global_mode {
return resolve_global_path(branch);
}
let repo = git::get_repo_root(None)?;
let normalized = git::normalize_branch_name(branch);
let path = git::find_worktree_by_branch(&repo, branch)?
.or(git::find_worktree_by_branch(
&repo,
&format!("refs/heads/{}", normalized),
)?)
.ok_or_else(|| CwError::Git(messages::worktree_not_found(branch)))?;
println!("{}", path.display());
Ok(())
}
fn list_branch_names(global_mode: bool) -> Result<()> {
if global_mode {
let repos = registry::get_all_registered_repos();
for (name, repo_path) in &repos {
if !repo_path.exists() {
continue;
}
if let Ok(worktrees) = git::get_feature_worktrees(Some(repo_path)) {
for (branch, _) in &worktrees {
println!("{}:{}", name, branch);
}
}
}
} else if let Ok(repo) = git::get_repo_root(None) {
if let Ok(worktrees) = git::parse_worktrees(&repo) {
for (branch, _) in &worktrees {
let normalized = git::normalize_branch_name(branch);
if normalized != "(detached)" {
println!("{}", normalized);
}
}
}
}
Ok(())
}
fn resolve_global_path(branch: &str) -> Result<()> {
let repos = registry::get_all_registered_repos();
let (repo_filter, branch_target) = if let Some((r, b)) = branch.split_once(':') {
(Some(r), b)
} else {
(None, branch)
};
let mut matches: Vec<(std::path::PathBuf, String, String)> = Vec::new();
for (name, repo_path) in &repos {
if let Some(filter) = repo_filter {
if name != filter {
continue;
}
}
if !repo_path.exists() {
continue;
}
if let Ok(Some(path)) = git::find_worktree_by_branch(repo_path, branch_target) {
matches.push((path, branch_target.to_string(), name.clone()));
} else if let Ok(Some(path)) =
git::find_worktree_by_branch(repo_path, &format!("refs/heads/{}", branch_target))
{
matches.push((path, branch_target.to_string(), name.clone()));
}
}
if matches.is_empty() {
return Err(CwError::Git(format!(
"No worktree found for '{}' in any registered repository",
branch
)));
}
if matches.len() == 1 {
println!("{}", matches[0].0.display());
return Ok(());
}
eprintln!("Multiple worktrees found for '{}':", branch);
for (path, branch_name, repo_name) in &matches {
eprintln!(" {}:{} ({})", repo_name, branch_name, path.display());
}
eprintln!("Use 'repo:branch' notation to disambiguate.");
Err(CwError::Git(format!(
"Multiple worktrees found for '{}'",
branch
)))
}
fn interactive_path_selection(global_mode: bool) -> Result<()> {
let mut entries: Vec<(String, String)> = Vec::new();
if global_mode {
let repos = registry::get_all_registered_repos();
for (name, repo_path) in &repos {
if !repo_path.exists() {
continue;
}
if let Ok(worktrees) = git::parse_worktrees(repo_path) {
let repo_resolved = git::canonicalize_or(repo_path);
for (branch, path) in &worktrees {
let normalized = git::normalize_branch_name(branch);
let path_resolved = git::canonicalize_or(path);
if path_resolved == repo_resolved {
entries.insert(
0,
(
format!("{} (root)", name),
path.to_string_lossy().to_string(),
),
);
} else if normalized != "(detached)" {
entries.push((
format!("{}:{}", name, normalized),
path.to_string_lossy().to_string(),
));
}
}
}
}
} else {
let repo = git::get_main_repo_root(None)?;
let worktrees = git::parse_worktrees(&repo)?;
let repo_resolved = git::canonicalize_or(&repo);
for (branch, path) in &worktrees {
let normalized = git::normalize_branch_name(branch);
let path_resolved = git::canonicalize_or(path);
if path_resolved == repo_resolved {
let label = if normalized.is_empty() || normalized == "(detached)" {
"main (root)".to_string()
} else {
format!("{} (root)", normalized)
};
entries.insert(0, (label, path.to_string_lossy().to_string()));
} else if normalized != "(detached)" {
entries.push((normalized.to_string(), path.to_string_lossy().to_string()));
}
}
}
if entries.is_empty() {
eprintln!("No worktrees found.");
std::process::exit(1);
}
if entries.len() == 1 {
println!("{}", entries[0].1);
return Ok(());
}
match crate::tui::arrow_select(&entries, "Select worktree:", 0) {
Some(selected_path) => {
println!("{}", selected_path);
Ok(())
}
None => {
std::process::exit(1);
}
}
}