git_x/
clean_branches.rs

1use crate::Result;
2use crate::command::Command;
3use crate::core::git::{BranchOperations, GitOperations};
4use crate::core::safety::Safety;
5
6pub fn run(dry_run: bool) -> Result<()> {
7    let cmd = CleanBranchesCommand;
8    cmd.execute(dry_run)
9}
10
11/// Command implementation for git clean-branches
12pub struct CleanBranchesCommand;
13
14impl Command for CleanBranchesCommand {
15    type Input = bool;
16    type Output = ();
17
18    fn execute(&self, dry_run: bool) -> Result<()> {
19        run_clean_branches(dry_run)
20    }
21
22    fn name(&self) -> &'static str {
23        "clean-branches"
24    }
25
26    fn description(&self) -> &'static str {
27        "Clean up merged branches"
28    }
29
30    fn is_destructive(&self) -> bool {
31        true
32    }
33}
34
35fn run_clean_branches(dry_run: bool) -> Result<()> {
36    // Safety check: ensure working directory is clean
37    if !dry_run {
38        Safety::ensure_clean_working_directory()?;
39    }
40
41    let merged_branches = GitOperations::merged_branches()?;
42    let branches: Vec<String> = merged_branches
43        .into_iter()
44        .filter(|branch| !is_protected_branch(branch))
45        .collect();
46
47    // Safety confirmation for destructive operation
48    if !dry_run && !branches.is_empty() {
49        let details = format!(
50            "This will delete {} merged branches: {}",
51            branches.len(),
52            branches.join(", ")
53        );
54
55        if !Safety::confirm_destructive_operation("Clean merged branches", &details)? {
56            println!("Operation cancelled by user.");
57            return Ok(());
58        }
59    }
60
61    let mut deleted = Vec::new();
62
63    for branch in branches {
64        if dry_run {
65            deleted.push(branch);
66        } else {
67            match BranchOperations::delete(&branch, false) {
68                Ok(()) => {
69                    deleted.push(branch);
70                }
71                Err(_) => {
72                    // Branch deletion failed, skip it
73                }
74            }
75        }
76    }
77
78    if deleted.is_empty() {
79        println!("No merged branches to delete.");
80    } else {
81        println!("{}", format_deletion_summary(deleted.len(), dry_run));
82        for branch in deleted {
83            if dry_run {
84                let branch1 = &branch;
85                println!("{branch1}");
86            } else {
87                println!("  {branch}");
88            }
89        }
90    }
91
92    Ok(())
93}
94
95fn is_protected_branch(branch: &str) -> bool {
96    ["main", "master", "develop"].contains(&branch)
97}
98
99fn format_deletion_summary(count: usize, dry_run: bool) -> String {
100    if dry_run {
101        format!("🧪 (dry run) {count} branches would be deleted:")
102    } else {
103        format!("🧹 Deleted {count} merged branches:")
104    }
105}