git_worktree_manager/
messages.rs1pub fn worktree_not_found(branch: &str) -> String {
5 format!(
6 "No worktree found for branch '{}'. Use 'gw list' to see available worktrees.",
7 branch
8 )
9}
10
11pub fn branch_not_found(branch: &str) -> String {
12 format!("Branch '{}' not found", branch)
13}
14
15pub fn invalid_branch_name(error_msg: &str) -> String {
16 format!(
17 "Invalid branch name: {}\nHint: Use alphanumeric characters, hyphens, and slashes. \
18 Avoid special characters like emojis, backslashes, or control characters.",
19 error_msg
20 )
21}
22
23pub fn cannot_determine_branch() -> String {
24 "Cannot determine current branch".to_string()
25}
26
27pub fn cannot_determine_base_branch() -> String {
28 "Cannot determine base branch. Specify with --base or checkout a branch first.".to_string()
29}
30
31pub fn missing_metadata(branch: &str) -> String {
32 format!(
33 "Missing metadata for branch '{}'. Was this worktree created with 'gw new'?",
34 branch
35 )
36}
37
38pub fn base_repo_not_found(path: &str) -> String {
39 format!("Base repository not found at: {}", path)
40}
41
42pub fn worktree_dir_not_found(path: &str) -> String {
43 format!("Worktree directory does not exist: {}", path)
44}
45
46pub fn rebase_failed(
47 worktree_path: &str,
48 rebase_target: &str,
49 conflicted_files: Option<&[String]>,
50) -> String {
51 let mut msg = format!(
52 "Rebase failed. Please resolve conflicts manually:\n cd {}\n git rebase {}",
53 worktree_path, rebase_target
54 );
55 if let Some(files) = conflicted_files {
56 msg.push_str(&format!("\n\nConflicted files ({}):", files.len()));
57 for file in files {
58 msg.push_str(&format!("\n \u{2022} {}", file));
59 }
60 msg.push_str("\n\nTip: Use --ai-merge flag to get AI assistance with conflicts");
61 }
62 msg
63}
64
65pub fn merge_failed(base_path: &str, feature_branch: &str) -> String {
66 format!(
67 "Fast-forward merge failed. Manual intervention required:\n cd {}\n git merge {}",
68 base_path, feature_branch
69 )
70}
71
72pub fn pr_creation_failed(stderr: &str) -> String {
73 format!("Failed to create pull request: {}", stderr)
74}
75
76pub fn gh_cli_not_found() -> String {
77 "GitHub CLI (gh) is required to create pull requests.\n\
78 Install it from: https://cli.github.com/"
79 .to_string()
80}
81
82pub fn cannot_delete_main_worktree() -> String {
83 "Cannot delete main repository worktree".to_string()
84}
85
86pub fn stash_not_found(stash_ref: &str) -> String {
87 format!(
88 "Stash '{}' not found. Use 'gw stash list' to see available stashes.",
89 stash_ref
90 )
91}
92
93pub fn backup_not_found(backup_id: &str, branch: &str) -> String {
94 format!("Backup '{}' not found for branch '{}'", backup_id, branch)
95}
96
97pub fn import_file_not_found(import_file: &str) -> String {
98 format!("Import file not found: {}", import_file)
99}
100
101pub fn detached_head_warning() -> String {
102 "Worktree is detached or branch not found. Specify branch with --branch or skip with --force."
103 .to_string()
104}
105
106pub fn rebase_in_progress(branch: &str, target: &str) -> String {
111 format!("Rebasing {} onto {}...", branch, target)
112}
113
114pub fn pushing_to_origin(branch: &str) -> String {
115 format!("Pushing {} to origin...", branch)
116}
117
118pub fn deleting_local_branch(branch: &str) -> String {
119 format!("Deleting local branch: {}", branch)
120}
121
122pub fn deleting_remote_branch(branch: &str) -> String {
123 format!("Deleting remote branch: origin/{}", branch)
124}
125
126pub fn removing_worktree(path: &std::path::Path) -> String {
127 format!("Removing worktree: {}", path.display())
128}
129
130pub fn cleanup_complete(deleted: u32) -> String {
131 format!("* Cleanup complete! Deleted {} worktree(s)", deleted)
132}
133
134pub fn starting_ai_tool_foreground(tool_name: &str) -> String {
135 format!("Starting {} (Ctrl+C to exit)...", tool_name)
136}
137
138pub fn starting_ai_tool_in(tool_name: &str) -> String {
139 format!("Starting {} in:", tool_name)
140}
141
142pub fn resuming_ai_tool_in(tool_name: &str) -> String {
143 format!("Resuming {} in:", tool_name)
144}
145
146pub fn switched_to_worktree(path: &std::path::Path) -> String {
147 format!("Switched to worktree: {}", path.display())
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_worktree_not_found() {
156 let msg = worktree_not_found("feature-x");
157 assert!(msg.contains("feature-x"));
158 assert!(msg.contains("gw list"));
159 assert!(msg.contains("No worktree found"));
160 }
161
162 #[test]
163 fn test_branch_not_found() {
164 let msg = branch_not_found("my-branch");
165 assert!(msg.contains("my-branch"));
166 assert!(msg.contains("not found"));
167 }
168
169 #[test]
170 fn test_invalid_branch_name() {
171 let msg = invalid_branch_name("contains spaces");
172 assert!(msg.contains("contains spaces"));
173 assert!(msg.contains("Invalid branch name"));
174 assert!(msg.contains("Hint"));
175 assert!(msg.contains("alphanumeric"));
176 }
177
178 #[test]
179 fn test_cannot_determine_branch() {
180 let msg = cannot_determine_branch();
181 assert!(msg.contains("Cannot determine current branch"));
182 }
183
184 #[test]
185 fn test_cannot_determine_base_branch() {
186 let msg = cannot_determine_base_branch();
187 assert!(msg.contains("Cannot determine base branch"));
188 assert!(msg.contains("--base"));
189 }
190
191 #[test]
192 fn test_missing_metadata() {
193 let msg = missing_metadata("feat-login");
194 assert!(msg.contains("feat-login"));
195 assert!(msg.contains("Missing metadata"));
196 assert!(msg.contains("gw new"));
197 }
198
199 #[test]
200 fn test_base_repo_not_found() {
201 let msg = base_repo_not_found("/tmp/repo");
202 assert!(msg.contains("/tmp/repo"));
203 assert!(msg.contains("Base repository not found"));
204 }
205
206 #[test]
207 fn test_worktree_dir_not_found() {
208 let msg = worktree_dir_not_found("/tmp/worktree");
209 assert!(msg.contains("/tmp/worktree"));
210 assert!(msg.contains("does not exist"));
211 }
212
213 #[test]
214 fn test_rebase_failed_without_conflicts() {
215 let msg = rebase_failed("/tmp/wt", "main", None);
216 assert!(msg.contains("Rebase failed"));
217 assert!(msg.contains("cd /tmp/wt"));
218 assert!(msg.contains("git rebase main"));
219 assert!(!msg.contains("Conflicted files"));
220 }
221
222 #[test]
223 fn test_rebase_failed_with_conflicts() {
224 let files = vec!["src/main.rs".to_string(), "Cargo.toml".to_string()];
225 let msg = rebase_failed("/tmp/wt", "main", Some(&files));
226 assert!(msg.contains("Rebase failed"));
227 assert!(msg.contains("cd /tmp/wt"));
228 assert!(msg.contains("git rebase main"));
229 assert!(msg.contains("Conflicted files (2)"));
230 assert!(msg.contains("src/main.rs"));
231 assert!(msg.contains("Cargo.toml"));
232 assert!(msg.contains("--ai-merge"));
233 }
234
235 #[test]
236 fn test_rebase_failed_with_empty_conflicts() {
237 let files: Vec<String> = vec![];
238 let msg = rebase_failed("/tmp/wt", "main", Some(&files));
239 assert!(msg.contains("Conflicted files (0)"));
240 }
241
242 #[test]
243 fn test_merge_failed() {
244 let msg = merge_failed("/tmp/base", "feature-api");
245 assert!(msg.contains("Fast-forward merge failed"));
246 assert!(msg.contains("cd /tmp/base"));
247 assert!(msg.contains("git merge feature-api"));
248 }
249
250 #[test]
251 fn test_pr_creation_failed() {
252 let msg = pr_creation_failed("permission denied");
253 assert!(msg.contains("Failed to create pull request"));
254 assert!(msg.contains("permission denied"));
255 }
256
257 #[test]
258 fn test_gh_cli_not_found() {
259 let msg = gh_cli_not_found();
260 assert!(msg.contains("GitHub CLI (gh)"));
261 assert!(msg.contains("https://cli.github.com/"));
262 }
263
264 #[test]
265 fn test_cannot_delete_main_worktree() {
266 let msg = cannot_delete_main_worktree();
267 assert!(msg.contains("Cannot delete main repository worktree"));
268 }
269
270 #[test]
271 fn test_stash_not_found() {
272 let msg = stash_not_found("stash@{0}");
273 assert!(msg.contains("stash@{0}"));
274 assert!(msg.contains("gw stash list"));
275 }
276
277 #[test]
278 fn test_backup_not_found() {
279 let msg = backup_not_found("abc123", "feature-x");
280 assert!(msg.contains("abc123"));
281 assert!(msg.contains("feature-x"));
282 assert!(msg.contains("not found"));
283 }
284
285 #[test]
286 fn test_import_file_not_found() {
287 let msg = import_file_not_found("/tmp/export.json");
288 assert!(msg.contains("/tmp/export.json"));
289 assert!(msg.contains("Import file not found"));
290 }
291
292 #[test]
293 fn test_detached_head_warning() {
294 let msg = detached_head_warning();
295 assert!(msg.contains("detached"));
296 assert!(msg.contains("--branch"));
297 assert!(msg.contains("--force"));
298 }
299
300 #[test]
301 fn test_rebase_in_progress() {
302 let msg = rebase_in_progress("feat-x", "main");
303 assert!(msg.contains("Rebasing feat-x onto main"));
304 }
305
306 #[test]
307 fn test_pushing_to_origin() {
308 let msg = pushing_to_origin("feat-x");
309 assert!(msg.contains("Pushing feat-x to origin"));
310 }
311
312 #[test]
313 fn test_deleting_local_branch() {
314 let msg = deleting_local_branch("feat-x");
315 assert!(msg.contains("Deleting local branch: feat-x"));
316 }
317
318 #[test]
319 fn test_deleting_remote_branch() {
320 let msg = deleting_remote_branch("feat-x");
321 assert!(msg.contains("origin/feat-x"));
322 }
323
324 #[test]
325 fn test_removing_worktree() {
326 let msg = removing_worktree(std::path::Path::new("/tmp/wt"));
327 assert!(msg.contains("Removing worktree:"));
328 assert!(msg.contains("/tmp/wt"));
329 }
330
331 #[test]
332 fn test_cleanup_complete() {
333 let msg = cleanup_complete(3);
334 assert!(msg.contains("3 worktree(s)"));
335 }
336
337 #[test]
338 fn test_starting_ai_tool_foreground() {
339 let msg = starting_ai_tool_foreground("claude");
340 assert!(msg.contains("Starting claude"));
341 assert!(msg.contains("Ctrl+C"));
342 }
343
344 #[test]
345 fn test_starting_ai_tool_in() {
346 let msg = starting_ai_tool_in("claude");
347 assert_eq!(msg, "Starting claude in:");
348 }
349
350 #[test]
351 fn test_resuming_ai_tool_in() {
352 let msg = resuming_ai_tool_in("claude");
353 assert_eq!(msg, "Resuming claude in:");
354 }
355
356 #[test]
357 fn test_switched_to_worktree() {
358 let msg = switched_to_worktree(std::path::Path::new("/tmp/wt"));
359 assert!(msg.contains("Switched to worktree:"));
360 assert!(msg.contains("/tmp/wt"));
361 }
362}