git_worktree_manager/operations/
path_cmd.rs1use crate::error::{CwError, Result};
5use crate::git;
6use crate::messages;
7use crate::registry;
8
9pub fn worktree_path(
11 branch: Option<&str>,
12 global_mode: bool,
13 list_branches: bool,
14 interactive: bool,
15) -> Result<()> {
16 if interactive {
17 return interactive_path_selection(global_mode);
18 }
19
20 if list_branches {
21 return list_branch_names(global_mode);
22 }
23
24 let branch = branch.ok_or_else(|| {
25 CwError::Git(
26 "branch argument is required (unless --list-branches or --interactive is used)"
27 .to_string(),
28 )
29 })?;
30
31 if global_mode {
32 return resolve_global_path(branch);
33 }
34
35 let repo = git::get_repo_root(None)?;
37 let normalized = git::normalize_branch_name(branch);
38 let path = git::find_worktree_by_branch(&repo, branch)?
39 .or(git::find_worktree_by_branch(
40 &repo,
41 &format!("refs/heads/{}", normalized),
42 )?)
43 .ok_or_else(|| CwError::Git(messages::worktree_not_found(branch)))?;
44
45 println!("{}", path.display());
46 Ok(())
47}
48
49fn list_branch_names(global_mode: bool) -> Result<()> {
50 if global_mode {
51 let repos = registry::get_all_registered_repos();
52 for (name, repo_path) in &repos {
53 if !repo_path.exists() {
54 continue;
55 }
56 if let Ok(worktrees) = git::get_feature_worktrees(Some(repo_path)) {
57 for (branch, _) in &worktrees {
58 println!("{}:{}", name, branch);
59 }
60 }
61 }
62 } else if let Ok(repo) = git::get_repo_root(None) {
63 if let Ok(worktrees) = git::parse_worktrees(&repo) {
64 for (branch, _) in &worktrees {
65 let normalized = git::normalize_branch_name(branch);
66 if normalized != "(detached)" {
67 println!("{}", normalized);
68 }
69 }
70 }
71 }
72 Ok(())
73}
74
75fn resolve_global_path(branch: &str) -> Result<()> {
76 let repos = registry::get_all_registered_repos();
77
78 let (repo_filter, branch_target) = if let Some((r, b)) = branch.split_once(':') {
80 (Some(r), b)
81 } else {
82 (None, branch)
83 };
84
85 let mut matches: Vec<(std::path::PathBuf, String, String)> = Vec::new();
86
87 for (name, repo_path) in &repos {
88 if let Some(filter) = repo_filter {
89 if name != filter {
90 continue;
91 }
92 }
93 if !repo_path.exists() {
94 continue;
95 }
96
97 if let Ok(Some(path)) = git::find_worktree_by_branch(repo_path, branch_target) {
98 matches.push((path, branch_target.to_string(), name.clone()));
99 } else if let Ok(Some(path)) =
100 git::find_worktree_by_branch(repo_path, &format!("refs/heads/{}", branch_target))
101 {
102 matches.push((path, branch_target.to_string(), name.clone()));
103 }
104 }
105
106 if matches.is_empty() {
107 return Err(CwError::Git(format!(
108 "No worktree found for '{}' in any registered repository",
109 branch
110 )));
111 }
112
113 if matches.len() == 1 {
114 println!("{}", matches[0].0.display());
115 return Ok(());
116 }
117
118 eprintln!("Multiple worktrees found for '{}':", branch);
120 for (path, branch_name, repo_name) in &matches {
121 eprintln!(" {}:{} ({})", repo_name, branch_name, path.display());
122 }
123 eprintln!("Use 'repo:branch' notation to disambiguate.");
124 Err(CwError::Git(format!(
125 "Multiple worktrees found for '{}'",
126 branch
127 )))
128}
129
130fn interactive_path_selection(global_mode: bool) -> Result<()> {
131 let mut entries: Vec<(String, String)> = Vec::new(); if global_mode {
134 let repos = registry::get_all_registered_repos();
135 for (name, repo_path) in &repos {
136 if !repo_path.exists() {
137 continue;
138 }
139 if let Ok(worktrees) = git::parse_worktrees(repo_path) {
140 let repo_resolved = git::canonicalize_or(repo_path);
141 for (branch, path) in &worktrees {
142 let normalized = git::normalize_branch_name(branch);
143 let path_resolved = git::canonicalize_or(path);
144 if path_resolved == repo_resolved {
145 entries.insert(
146 0,
147 (
148 format!("{} (root)", name),
149 path.to_string_lossy().to_string(),
150 ),
151 );
152 } else if normalized != "(detached)" {
153 entries.push((
154 format!("{}:{}", name, normalized),
155 path.to_string_lossy().to_string(),
156 ));
157 }
158 }
159 }
160 }
161 } else {
162 let repo = git::get_main_repo_root(None)?;
163 let worktrees = git::parse_worktrees(&repo)?;
164 let repo_resolved = git::canonicalize_or(&repo);
165
166 for (branch, path) in &worktrees {
167 let normalized = git::normalize_branch_name(branch);
168 let path_resolved = git::canonicalize_or(path);
169 if path_resolved == repo_resolved {
170 let label = if normalized.is_empty() || normalized == "(detached)" {
171 "main (root)".to_string()
172 } else {
173 format!("{} (root)", normalized)
174 };
175 entries.insert(0, (label, path.to_string_lossy().to_string()));
176 } else if normalized != "(detached)" {
177 entries.push((normalized.to_string(), path.to_string_lossy().to_string()));
178 }
179 }
180 }
181
182 if entries.is_empty() {
183 eprintln!("No worktrees found.");
184 std::process::exit(1);
185 }
186
187 if entries.len() == 1 {
188 println!("{}", entries[0].1);
189 return Ok(());
190 }
191
192 match crate::tui::arrow_select(&entries, "Select worktree:", 0) {
194 Some(selected_path) => {
195 println!("{}", selected_path);
196 Ok(())
197 }
198 None => {
199 std::process::exit(1);
200 }
201 }
202}