git_gardener/commands/
add.rs1use 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 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 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 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 let worktree_name = self.branch.clone();
55
56 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 println!("Creating worktree for branch '{}'...", self.branch);
66
67 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 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 Command::new("git")
107 .args(&["init"])
108 .current_dir(repo_path)
109 .output()
110 .expect("Failed to init git repo");
111
112 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 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 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 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 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 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}