Skip to main content

git_worktree_manager/
messages.rs

1//! Standardized error and informational messages for git-worktree-manager.
2//!
3
4pub 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 target_not_found(target: &str) -> String {
12    format!(
13        "No worktree matches '{}' (tried name, branch, path). Use 'gw list' to see available worktrees.",
14        target
15    )
16}
17
18pub fn branch_not_found(branch: &str) -> String {
19    format!("Branch '{}' not found", branch)
20}
21
22pub fn invalid_branch_name(error_msg: &str) -> String {
23    format!(
24        "Invalid branch name: {}\nHint: Use alphanumeric characters, hyphens, and slashes. \
25         Avoid special characters like emojis, backslashes, or control characters.",
26        error_msg
27    )
28}
29
30pub fn cannot_determine_branch() -> String {
31    "Cannot determine current branch".to_string()
32}
33
34pub fn cannot_determine_base_branch() -> String {
35    "Cannot determine base branch. Specify with --base or checkout a branch first.".to_string()
36}
37
38pub fn missing_metadata(branch: &str) -> String {
39    format!(
40        "Missing metadata for branch '{}'. Was this worktree created with 'gw new'?",
41        branch
42    )
43}
44
45pub fn worktree_dir_not_found(path: &str) -> String {
46    format!("Worktree directory does not exist: {}", path)
47}
48
49pub fn cannot_delete_main_worktree() -> String {
50    "Cannot delete main repository worktree".to_string()
51}
52
53pub fn detached_head_warning() -> String {
54    "Worktree is detached or branch not found. Specify branch with --branch or skip with --force."
55        .to_string()
56}
57
58// ---------------------------------------------------------------------------
59// Status / progress messages (used in styled println! calls)
60// ---------------------------------------------------------------------------
61
62pub fn deleting_local_branch(branch: &str) -> String {
63    format!("Deleting local branch: {}", branch)
64}
65
66pub fn deleting_remote_branch(branch: &str) -> String {
67    format!("Deleting remote branch: origin/{}", branch)
68}
69
70pub fn removing_worktree(path: &std::path::Path) -> String {
71    format!("Removing worktree: {}", path.display())
72}
73
74pub fn starting_ai_tool_foreground(tool_name: &str) -> String {
75    format!("Starting {} (Ctrl+C to exit)...", tool_name)
76}
77
78pub fn starting_ai_tool_in(tool_name: &str) -> String {
79    format!("Starting {} in:", tool_name)
80}
81
82pub fn resuming_ai_tool_in(tool_name: &str) -> String {
83    format!("Resuming {} in:", tool_name)
84}
85
86pub fn switched_to_worktree(path: &std::path::Path) -> String {
87    format!("Switched to worktree: {}", path.display())
88}
89
90pub fn worktree_unlocking_stale(path: &str, pid: u32) -> String {
91    format!("info: stale lock from dead pid {pid} on {path} — unlocking")
92}
93
94pub fn worktree_locked_live_pid(path: &str, pid: u32) -> String {
95    format!(
96        "Worktree {path} is locked by pid {pid} which is still running — \
97         refusing to unlock. Stop that process or unlock manually with \
98         `git worktree unlock {path}`."
99    )
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_worktree_not_found() {
108        let msg = worktree_not_found("feature-x");
109        assert!(msg.contains("feature-x"));
110        assert!(msg.contains("gw list"));
111        assert!(msg.contains("No worktree found"));
112    }
113
114    #[test]
115    fn test_target_not_found() {
116        let msg = target_not_found("my-target");
117        assert!(msg.contains("my-target"));
118        assert!(msg.contains("name, branch, path"));
119        assert!(msg.contains("gw list"));
120    }
121
122    #[test]
123    fn test_branch_not_found() {
124        let msg = branch_not_found("my-branch");
125        assert!(msg.contains("my-branch"));
126        assert!(msg.contains("not found"));
127    }
128
129    #[test]
130    fn test_invalid_branch_name() {
131        let msg = invalid_branch_name("contains spaces");
132        assert!(msg.contains("contains spaces"));
133        assert!(msg.contains("Invalid branch name"));
134        assert!(msg.contains("Hint"));
135        assert!(msg.contains("alphanumeric"));
136    }
137
138    #[test]
139    fn test_cannot_determine_branch() {
140        let msg = cannot_determine_branch();
141        assert!(msg.contains("Cannot determine current branch"));
142    }
143
144    #[test]
145    fn test_cannot_determine_base_branch() {
146        let msg = cannot_determine_base_branch();
147        assert!(msg.contains("Cannot determine base branch"));
148        assert!(msg.contains("--base"));
149    }
150
151    #[test]
152    fn test_missing_metadata() {
153        let msg = missing_metadata("feat-login");
154        assert!(msg.contains("feat-login"));
155        assert!(msg.contains("Missing metadata"));
156        assert!(msg.contains("gw new"));
157    }
158
159    #[test]
160    fn test_worktree_dir_not_found() {
161        let msg = worktree_dir_not_found("/tmp/worktree");
162        assert!(msg.contains("/tmp/worktree"));
163        assert!(msg.contains("does not exist"));
164    }
165
166    #[test]
167    fn test_cannot_delete_main_worktree() {
168        let msg = cannot_delete_main_worktree();
169        assert!(msg.contains("Cannot delete main repository worktree"));
170    }
171
172    #[test]
173    fn test_detached_head_warning() {
174        let msg = detached_head_warning();
175        assert!(msg.contains("detached"));
176        assert!(msg.contains("--branch"));
177        assert!(msg.contains("--force"));
178    }
179
180    #[test]
181    fn test_deleting_local_branch() {
182        let msg = deleting_local_branch("feat-x");
183        assert!(msg.contains("Deleting local branch: feat-x"));
184    }
185
186    #[test]
187    fn test_deleting_remote_branch() {
188        let msg = deleting_remote_branch("feat-x");
189        assert!(msg.contains("origin/feat-x"));
190    }
191
192    #[test]
193    fn test_removing_worktree() {
194        let msg = removing_worktree(std::path::Path::new("/tmp/wt"));
195        assert!(msg.contains("Removing worktree:"));
196        assert!(msg.contains("/tmp/wt"));
197    }
198
199    #[test]
200    fn test_starting_ai_tool_foreground() {
201        let msg = starting_ai_tool_foreground("claude");
202        assert!(msg.contains("Starting claude"));
203        assert!(msg.contains("Ctrl+C"));
204    }
205
206    #[test]
207    fn test_starting_ai_tool_in() {
208        let msg = starting_ai_tool_in("claude");
209        assert_eq!(msg, "Starting claude in:");
210    }
211
212    #[test]
213    fn test_resuming_ai_tool_in() {
214        let msg = resuming_ai_tool_in("claude");
215        assert_eq!(msg, "Resuming claude in:");
216    }
217
218    #[test]
219    fn test_switched_to_worktree() {
220        let msg = switched_to_worktree(std::path::Path::new("/tmp/wt"));
221        assert!(msg.contains("Switched to worktree:"));
222        assert!(msg.contains("/tmp/wt"));
223    }
224}