Skip to main content

git_gardener/commands/
remove.rs

1use 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        // worktreeの情報を取得
22        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        // worktreeを削除
33        git_worktree.remove_worktree(&worktree_info.name, false)?;
34        
35        println!("✓ Removed worktree '{}'", self.worktree);
36        
37        // --with-branchが指定されていればブランチも削除
38        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        // Git リポジトリを初期化
67        Command::new("git")
68            .args(&["init"])
69            .current_dir(repo_path)
70            .output()
71            .expect("Failed to init git repo");
72        
73        // 設定
74        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        // 初期ファイルとコミットを作成
87        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        // テスト用のworktreeを作成
102        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        // What: RemoveCommand::newが正しくインスタンスを作成するかテスト
115        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        // What: Gitリポジトリでない場所でRemoveCommandが失敗するかテスト
127        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        // What: 存在しないワークツリーに対して失敗するかテスト
140        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        // What: ブランチ名でワークツリーを削除できるかテスト
156        let temp_dir = setup_git_repo_with_worktree();
157        std::env::set_current_dir(temp_dir.path()).unwrap();
158        
159        // worktreeが存在することを確認
160        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        // worktreeが削除されたことを確認
170        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        // What: --with-branchフラグでブランチも削除されるかテスト
177        let temp_dir = setup_git_repo_with_worktree();
178        std::env::set_current_dir(temp_dir.path()).unwrap();
179        
180        // ブランチが存在することを確認
181        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        // ブランチが削除されたことを確認
194        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}