Skip to main content

git_gardener/commands/
add.rs

1use crate::config::Config;
2use crate::error::{GitGardenerError, Result};
3use crate::git::GitWorktree;
4use crate::hooks::HookExecutor;
5
6pub struct AddCommand {
7    pub branch: String,
8    pub new_branch: bool,
9    pub commit: Option<String>,
10}
11
12impl AddCommand {
13    pub fn new(
14        branch: String,
15        new_branch: bool,
16        commit: Option<String>,
17    ) -> Self {
18        Self {
19            branch,
20            new_branch,
21            commit,
22        }
23    }
24    
25    pub fn execute(&self) -> Result<()> {
26        let git_worktree = GitWorktree::new()?;
27        let repo_root = git_worktree.get_repository_root()?;
28        
29        // 設定ファイルを読み込む(存在しない場合はデフォルト設定を使用)
30        let config_path = repo_root.join(".gardener.yml");
31        let config = if config_path.exists() {
32            Config::load_from_file(&config_path)?
33        } else {
34            Config::default()
35        };
36        
37        // ブランチが既に存在するかチェック
38        if !self.new_branch && !git_worktree.branch_exists(&self.branch)? {
39            return Err(GitGardenerError::Custom(
40                format!(
41                    "Branch '{}' does not exist. Use -b flag to create a new branch.",
42                    self.branch
43                )
44            ));
45        }
46        
47        // worktreeのパスを決定(wtpスタイル)
48        let base_dir = config.defaults.root_dir.unwrap_or_else(|| ".gardener".to_string());
49        let worktree_path = repo_root
50            .join(&base_dir)
51            .join(&self.branch);
52        
53        // worktreeの名前を決定(パスのベース名)
54        let worktree_name = self.branch.clone();
55        
56        // 既存のworktreeをチェック
57        let existing_worktrees = git_worktree.list_worktrees()?;
58        if existing_worktrees.iter().any(|w| w.name == worktree_name || w.path == worktree_path) {
59            return Err(GitGardenerError::WorktreeExists {
60                name: worktree_name,
61            });
62        }
63        
64        // worktreeを作成
65        println!("Creating worktree for branch '{}'...", self.branch);
66        
67        // 親ディレクトリを作成
68        if let Some(parent) = worktree_path.parent() {
69            std::fs::create_dir_all(parent)?;
70        }
71        
72        git_worktree.create_worktree_with_commit(
73            &worktree_name,
74            &worktree_path,
75            &self.branch,
76            self.new_branch,
77            self.commit.as_deref(),
78        )?;
79        
80        println!("✓ Created worktree at {}", worktree_path.display());
81        
82        // post_createフックの実行
83        if let Some(ref hooks) = config.hooks {
84            if let Some(ref post_create) = hooks.post_create {
85                let hook_executor = HookExecutor::new();
86                hook_executor.execute_hooks(&worktree_path, &self.branch, post_create)?;
87            }
88        }
89        
90        Ok(())
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use tempfile::tempdir;
98    use std::fs;
99    use std::process::Command;
100
101    fn setup_git_repo() -> tempfile::TempDir {
102        let temp_dir = tempdir().unwrap();
103        let repo_path = temp_dir.path();
104        
105        // Git リポジトリを初期化
106        Command::new("git")
107            .args(&["init"])
108            .current_dir(repo_path)
109            .output()
110            .expect("Failed to init git repo");
111        
112        // 設定
113        Command::new("git")
114            .args(&["config", "user.name", "Test User"])
115            .current_dir(repo_path)
116            .output()
117            .unwrap();
118        
119        Command::new("git")
120            .args(&["config", "user.email", "test@example.com"])
121            .current_dir(repo_path)
122            .output()
123            .unwrap();
124        
125        // 初期ファイルとコミットを作成
126        fs::write(repo_path.join("README.md"), "# Test Repo").unwrap();
127        
128        Command::new("git")
129            .args(&["add", "."])
130            .current_dir(repo_path)
131            .output()
132            .unwrap();
133        
134        Command::new("git")
135            .args(&["commit", "-m", "Initial commit"])
136            .current_dir(repo_path)
137            .output()
138            .unwrap();
139        
140        temp_dir
141    }
142
143    #[test]
144    fn test_add_command_new_creates_instance() {
145        // What: AddCommand::newが正しくインスタンスを作成するかテスト
146        let cmd = AddCommand::new("test-branch".to_string(), true, None);
147        
148        assert_eq!(cmd.branch, "test-branch");
149        assert_eq!(cmd.new_branch, true);
150        assert_eq!(cmd.commit, None);
151    }
152
153    #[test]
154    fn test_add_command_new_with_commit() {
155        // What: AddCommand::newがcommitオプション付きでインスタンスを作成するかテスト
156        let cmd = AddCommand::new(
157            "feature-branch".to_string(), 
158            false, 
159            Some("abc123".to_string())
160        );
161        
162        assert_eq!(cmd.branch, "feature-branch");
163        assert_eq!(cmd.new_branch, false);
164        assert_eq!(cmd.commit, Some("abc123".to_string()));
165    }
166
167    #[test]
168    fn test_add_command_fails_without_git_repo() {
169        // What: Gitリポジトリでない場所でAddCommandが失敗するかテスト
170        let temp_dir = tempdir().unwrap();
171        std::env::set_current_dir(temp_dir.path()).unwrap();
172        
173        let cmd = AddCommand::new("test".to_string(), true, None);
174        let result = cmd.execute();
175        
176        assert!(result.is_err());
177        assert!(matches!(result.unwrap_err(), GitGardenerError::NotInRepository));
178    }
179
180    #[test]
181    fn test_add_command_fails_for_nonexistent_branch() {
182        // What: 存在しないブランチに対してnew_branch=falseの場合に失敗するかテスト
183        let temp_dir = setup_git_repo();
184        std::env::set_current_dir(temp_dir.path()).unwrap();
185        
186        let cmd = AddCommand::new("nonexistent-branch".to_string(), false, None);
187        let result = cmd.execute();
188        
189        assert!(result.is_err());
190        let error_msg = format!("{}", result.unwrap_err());
191        assert!(error_msg.contains("does not exist"));
192        assert!(error_msg.contains("Use -b flag"));
193    }
194}