1use crate::core::{git::*, safety::Safety, validation::Validate};
2use crate::domain::GitRepository;
3use crate::{GitXError, Result};
4
5pub struct BranchManager {
7 _repository: GitRepository,
8}
9
10impl BranchManager {
11 pub fn new(repository: GitRepository) -> Self {
13 Self {
14 _repository: repository,
15 }
16 }
17
18 pub fn create_branch(&self, request: CreateBranchRequest) -> Result<BranchCreationResult> {
20 Validate::branch_name(&request.name)?;
22
23 if let Some(ref base) = request.from {
24 if !GitOperations::commit_exists(base)? {
25 return Err(GitXError::GitCommand(format!(
26 "Base branch or ref '{base}' does not exist"
27 )));
28 }
29 }
30
31 if BranchOperations::exists(&request.name)? {
33 return Err(GitXError::GitCommand(format!(
34 "Branch '{}' already exists",
35 request.name
36 )));
37 }
38
39 let backup_branch = if request.create_backup {
41 Some(Safety::create_backup_branch(Some("pre-create"))?)
42 } else {
43 None
44 };
45
46 BranchOperations::create(&request.name, request.from.as_deref())?;
48
49 Ok(BranchCreationResult {
50 branch_name: request.name,
51 base_commit: request.from,
52 backup_branch,
53 switched: true,
54 })
55 }
56
57 pub fn delete_branches(&self, request: DeleteBranchesRequest) -> Result<BranchDeletionResult> {
59 let mut deleted = Vec::new();
60 let mut failed = Vec::new();
61 let mut protected = Vec::new();
62
63 let protected_branches = ["main", "master", "develop"];
65
66 for branch in &request.branches {
67 if protected_branches.contains(&branch.as_str()) {
68 protected.push(branch.clone());
69 continue;
70 }
71
72 if request.dry_run {
73 deleted.push(branch.clone()); } else {
75 match BranchOperations::delete(branch, request.force) {
76 Ok(_) => deleted.push(branch.clone()),
77 Err(_) => failed.push(branch.clone()),
78 }
79 }
80 }
81
82 Ok(BranchDeletionResult {
83 deleted,
84 failed,
85 protected,
86 dry_run: request.dry_run,
87 })
88 }
89
90 pub fn get_recent_branches(
92 &self,
93 request: RecentBranchesRequest,
94 ) -> Result<RecentBranchesResult> {
95 let all_recent = GitOperations::recent_branches(request.limit)?;
96
97 let current_branch = GitOperations::current_branch()?;
99 let protected_branches = ["main", "master", "develop"];
100
101 let filtered_branches = all_recent
102 .into_iter()
103 .filter(|branch| {
104 if request.exclude_current && branch == ¤t_branch {
105 return false;
106 }
107 if request.exclude_protected && protected_branches.contains(&branch.as_str()) {
108 return false;
109 }
110 true
111 })
112 .collect();
113
114 Ok(RecentBranchesResult {
115 branches: filtered_branches,
116 current_branch,
117 })
118 }
119
120 pub fn switch_branch(&self, request: SwitchBranchRequest) -> Result<BranchSwitchResult> {
122 if !BranchOperations::exists(&request.branch_name)? {
124 return Err(GitXError::GitCommand(format!(
125 "Branch '{}' does not exist",
126 request.branch_name
127 )));
128 }
129
130 if request.strict_mode && !GitOperations::is_working_directory_clean()? {
132 return Err(GitXError::GitCommand(
133 "Working directory has uncommitted changes. Use --force or commit/stash changes."
134 .to_string(),
135 ));
136 }
137
138 let previous_branch = GitOperations::current_branch()?;
139
140 let checkpoint = if request.create_checkpoint {
142 Some(Safety::create_checkpoint(Some(&format!(
143 "Before switching to {}",
144 request.branch_name
145 )))?)
146 } else {
147 None
148 };
149
150 BranchOperations::switch(&request.branch_name)?;
152
153 Ok(BranchSwitchResult {
154 previous_branch,
155 new_branch: request.branch_name,
156 checkpoint,
157 })
158 }
159
160 pub fn rename_branch(&self, request: RenameBranchRequest) -> Result<BranchRenameResult> {
162 Validate::branch_name(&request.new_name)?;
163
164 let current_branch = GitOperations::current_branch()?;
165
166 if BranchOperations::exists(&request.new_name)? {
167 return Err(GitXError::GitCommand(format!(
168 "Branch '{}' already exists",
169 request.new_name
170 )));
171 }
172
173 let backup_branch = if request.create_backup {
175 Some(Safety::create_backup_branch(Some("pre-rename"))?)
176 } else {
177 None
178 };
179
180 BranchOperations::rename(&request.new_name)?;
181
182 Ok(BranchRenameResult {
183 old_name: current_branch,
184 new_name: request.new_name,
185 backup_branch,
186 })
187 }
188
189 pub fn clean_merged_branches(
191 &self,
192 request: CleanBranchesRequest,
193 ) -> Result<CleanBranchesResult> {
194 let merged_branches = GitOperations::merged_branches()?;
195 let current_branch = GitOperations::current_branch()?;
196 let protected_branches = ["main", "master", "develop"];
197
198 let candidates: Vec<String> = merged_branches
199 .into_iter()
200 .filter(|branch| branch != ¤t_branch)
201 .filter(|branch| !protected_branches.contains(&branch.as_str()))
202 .collect();
203
204 if candidates.is_empty() {
205 return Ok(CleanBranchesResult {
206 candidates: vec![],
207 deleted: vec![],
208 failed: vec![],
209 dry_run: request.dry_run,
210 });
211 }
212
213 let mut deleted = Vec::new();
214 let mut failed = Vec::new();
215
216 if request.dry_run {
217 deleted = candidates.clone();
219 } else {
220 if request.confirm_deletion {
222 let details = format!(
223 "This will delete {} merged branches: {}",
224 candidates.len(),
225 candidates.join(", ")
226 );
227
228 if !Safety::confirm_destructive_operation("Clean merged branches", &details)? {
229 return Ok(CleanBranchesResult {
230 candidates: candidates.clone(),
231 deleted: vec![],
232 failed: vec![],
233 dry_run: false,
234 });
235 }
236 }
237
238 for branch in &candidates {
240 match BranchOperations::delete(branch, false) {
241 Ok(_) => deleted.push(branch.clone()),
242 Err(_) => failed.push(branch.clone()),
243 }
244 }
245 }
246
247 Ok(CleanBranchesResult {
248 candidates,
249 deleted,
250 failed,
251 dry_run: request.dry_run,
252 })
253 }
254}
255
256#[derive(Debug, Clone)]
259pub struct CreateBranchRequest {
260 pub name: String,
261 pub from: Option<String>,
262 pub create_backup: bool,
263}
264
265#[derive(Debug, Clone)]
266pub struct BranchCreationResult {
267 pub branch_name: String,
268 pub base_commit: Option<String>,
269 pub backup_branch: Option<String>,
270 pub switched: bool,
271}
272
273#[derive(Debug, Clone)]
274pub struct DeleteBranchesRequest {
275 pub branches: Vec<String>,
276 pub force: bool,
277 pub dry_run: bool,
278}
279
280#[derive(Debug, Clone)]
281pub struct BranchDeletionResult {
282 pub deleted: Vec<String>,
283 pub failed: Vec<String>,
284 pub protected: Vec<String>,
285 pub dry_run: bool,
286}
287
288#[derive(Debug, Clone)]
289pub struct RecentBranchesRequest {
290 pub limit: Option<usize>,
291 pub exclude_current: bool,
292 pub exclude_protected: bool,
293}
294
295#[derive(Debug, Clone)]
296pub struct RecentBranchesResult {
297 pub branches: Vec<String>,
298 pub current_branch: String,
299}
300
301#[derive(Debug, Clone)]
302pub struct SwitchBranchRequest {
303 pub branch_name: String,
304 pub strict_mode: bool,
305 pub create_checkpoint: bool,
306}
307
308#[derive(Debug, Clone)]
309pub struct BranchSwitchResult {
310 pub previous_branch: String,
311 pub new_branch: String,
312 pub checkpoint: Option<String>,
313}
314
315#[derive(Debug, Clone)]
316pub struct RenameBranchRequest {
317 pub new_name: String,
318 pub create_backup: bool,
319}
320
321#[derive(Debug, Clone)]
322pub struct BranchRenameResult {
323 pub old_name: String,
324 pub new_name: String,
325 pub backup_branch: Option<String>,
326}
327
328#[derive(Debug, Clone)]
329pub struct CleanBranchesRequest {
330 pub dry_run: bool,
331 pub confirm_deletion: bool,
332}
333
334#[derive(Debug, Clone)]
335pub struct CleanBranchesResult {
336 pub candidates: Vec<String>,
337 pub deleted: Vec<String>,
338 pub failed: Vec<String>,
339 pub dry_run: bool,
340}
341
342impl CleanBranchesResult {
343 pub fn summary(&self) -> String {
345 if self.dry_run {
346 format!("Would delete {} branches", self.candidates.len())
347 } else {
348 format!(
349 "Deleted {} branches, {} failed",
350 self.deleted.len(),
351 self.failed.len()
352 )
353 }
354 }
355}