git_gardener/commands/
remove.rs1use crate::error::Result;
2use crate::git::GitWorktree;
3use std::process::Command;
4
5pub struct RemoveCommand {
6 pub worktree: String,
7 pub with_branch: bool,
8}
9
10impl RemoveCommand {
11 pub fn new(worktree: String, with_branch: bool) -> Self {
12 Self {
13 worktree,
14 with_branch,
15 }
16 }
17
18 pub fn execute(&self) -> Result<()> {
19 let git_worktree = GitWorktree::new()?;
20
21 let worktrees = git_worktree.list_worktrees()?;
23 let worktree_info = worktrees
24 .iter()
25 .find(|w| w.name == self.worktree || w.branch == self.worktree)
26 .ok_or_else(|| crate::error::GitGardenerError::WorktreeNotFound {
27 name: self.worktree.clone()
28 })?;
29
30 let branch_name = worktree_info.branch.clone();
31
32 git_worktree.remove_worktree(&worktree_info.name, false)?;
34
35 println!("✓ Removed worktree '{}'", self.worktree);
36
37 if self.with_branch {
39 let output = Command::new("git")
40 .args(&["branch", "-D", &branch_name])
41 .output()?;
42
43 if output.status.success() {
44 println!("✓ Removed branch '{}'", branch_name);
45 } else {
46 let error_msg = String::from_utf8_lossy(&output.stderr);
47 eprintln!("Failed to remove branch '{}': {}", branch_name, error_msg);
48 }
49 }
50
51 Ok(())
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use tempfile::tempdir;
59 use std::fs;
60 use std::process::Command;
61
62 fn setup_git_repo_with_worktree() -> tempfile::TempDir {
63 let temp_dir = tempdir().unwrap();
64 let repo_path = temp_dir.path();
65
66 Command::new("git")
68 .args(&["init"])
69 .current_dir(repo_path)
70 .output()
71 .expect("Failed to init git repo");
72
73 Command::new("git")
75 .args(&["config", "user.name", "Test User"])
76 .current_dir(repo_path)
77 .output()
78 .unwrap();
79
80 Command::new("git")
81 .args(&["config", "user.email", "test@example.com"])
82 .current_dir(repo_path)
83 .output()
84 .unwrap();
85
86 fs::write(repo_path.join("README.md"), "# Test Repo").unwrap();
88
89 Command::new("git")
90 .args(&["add", "."])
91 .current_dir(repo_path)
92 .output()
93 .unwrap();
94
95 Command::new("git")
96 .args(&["commit", "-m", "Initial commit"])
97 .current_dir(repo_path)
98 .output()
99 .unwrap();
100
101 let worktree_path = repo_path.join("feature-test");
103 Command::new("git")
104 .args(&["worktree", "add", "-b", "feature-test", &worktree_path.to_string_lossy()])
105 .current_dir(repo_path)
106 .output()
107 .unwrap();
108
109 temp_dir
110 }
111
112 #[test]
113 fn test_remove_command_new_creates_instance() {
114 let cmd = RemoveCommand::new("test-branch".to_string(), false);
116 assert_eq!(cmd.worktree, "test-branch");
117 assert_eq!(cmd.with_branch, false);
118
119 let cmd = RemoveCommand::new("test-branch".to_string(), true);
120 assert_eq!(cmd.worktree, "test-branch");
121 assert_eq!(cmd.with_branch, true);
122 }
123
124 #[test]
125 fn test_remove_command_fails_without_git_repo() {
126 let temp_dir = tempdir().unwrap();
128 std::env::set_current_dir(temp_dir.path()).unwrap();
129
130 let cmd = RemoveCommand::new("test".to_string(), false);
131 let result = cmd.execute();
132
133 assert!(result.is_err());
134 assert!(matches!(result.unwrap_err(), crate::error::GitGardenerError::NotInRepository));
135 }
136
137 #[test]
138 fn test_remove_command_fails_for_nonexistent_worktree() {
139 let temp_dir = setup_git_repo_with_worktree();
141 std::env::set_current_dir(temp_dir.path()).unwrap();
142
143 let cmd = RemoveCommand::new("nonexistent-worktree".to_string(), false);
144 let result = cmd.execute();
145
146 assert!(result.is_err());
147 assert!(matches!(
148 result.unwrap_err(),
149 crate::error::GitGardenerError::WorktreeNotFound { .. }
150 ));
151 }
152
153 #[test]
154 fn test_remove_command_removes_worktree_by_branch_name() {
155 let temp_dir = setup_git_repo_with_worktree();
157 std::env::set_current_dir(temp_dir.path()).unwrap();
158
159 let git_worktree = GitWorktree::new().unwrap();
161 let worktrees_before = git_worktree.list_worktrees().unwrap();
162 assert!(worktrees_before.iter().any(|w| w.branch == "feature-test"));
163
164 let cmd = RemoveCommand::new("feature-test".to_string(), false);
165 let result = cmd.execute();
166
167 assert!(result.is_ok());
168
169 let worktrees_after = git_worktree.list_worktrees().unwrap();
171 assert!(!worktrees_after.iter().any(|w| w.branch == "feature-test"));
172 }
173
174 #[test]
175 fn test_remove_command_with_branch_flag() {
176 let temp_dir = setup_git_repo_with_worktree();
178 std::env::set_current_dir(temp_dir.path()).unwrap();
179
180 let output = Command::new("git")
182 .args(&["branch", "--list", "feature-test"])
183 .current_dir(temp_dir.path())
184 .output()
185 .unwrap();
186 assert!(String::from_utf8_lossy(&output.stdout).contains("feature-test"));
187
188 let cmd = RemoveCommand::new("feature-test".to_string(), true);
189 let result = cmd.execute();
190
191 assert!(result.is_ok());
192
193 let output = Command::new("git")
195 .args(&["branch", "--list", "feature-test"])
196 .current_dir(temp_dir.path())
197 .output()
198 .unwrap();
199 assert!(!String::from_utf8_lossy(&output.stdout).contains("feature-test"));
200 }
201}