git_x/
prune_branches.rs

1use std::io::{BufRead, BufReader};
2use std::process::{Command, exit};
3
4pub fn run(except: Option<String>) {
5    let protected_branches = get_all_protected_branches(except.as_deref());
6
7    // Step 1: Get current branch
8    let output = Command::new("git")
9        .args(["rev-parse", "--abbrev-ref", "HEAD"])
10        .output()
11        .expect("Failed to get current branch");
12
13    if !output.status.success() {
14        eprintln!("Error: Could not determine current branch.");
15        exit(1);
16    }
17
18    let current_branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
19
20    // Step 2: Get merged branches
21    let output = Command::new("git")
22        .args(["branch", "--merged"])
23        .output()
24        .expect("Failed to get merged branches");
25
26    if !output.status.success() {
27        eprintln!("Error: Failed to list merged branches.");
28        exit(1);
29    }
30
31    let reader = BufReader::new(output.stdout.as_slice());
32    let branches: Vec<String> = reader
33        .lines()
34        .map_while(Result::ok)
35        .map(|b| clean_git_branch_name(&b))
36        .filter(|b| !is_branch_protected(b, &current_branch, &protected_branches))
37        .collect();
38
39    if branches.is_empty() {
40        println!("{}", format_no_branches_to_prune_message());
41        return;
42    }
43
44    // Step 3: Delete branches
45    for branch in branches {
46        let delete_args = get_git_branch_delete_args(&branch);
47        let status = Command::new("git")
48            .args(delete_args)
49            .status()
50            .expect("Failed to delete branch");
51
52        if status.success() {
53            println!("{}", format_branch_deleted_message(&branch));
54        } else {
55            eprintln!("{}", format_branch_delete_failed_message(&branch));
56        }
57    }
58}
59
60// Helper function to get default protected branches
61const DEFAULT_PROTECTED_BRANCHES: &[&str] = &["main", "master", "develop"];
62
63pub fn get_default_protected_branches() -> &'static [&'static str] {
64    DEFAULT_PROTECTED_BRANCHES
65}
66
67// Helper function to parse except string into vec
68pub fn parse_except_branches(except: &str) -> Vec<String> {
69    except
70        .split(',')
71        .map(|s| s.trim().to_string())
72        .filter(|s| !s.is_empty())
73        .collect()
74}
75
76// Helper function to get all protected branches
77pub fn get_all_protected_branches(except: Option<&str>) -> Vec<String> {
78    let mut protected: Vec<String> = DEFAULT_PROTECTED_BRANCHES
79        .iter()
80        .map(|&s| s.to_string())
81        .collect();
82
83    if let Some(except_str) = except {
84        protected.extend(parse_except_branches(except_str));
85    }
86
87    protected
88}
89
90// Helper function to clean branch name from git output
91pub fn clean_git_branch_name(branch: &str) -> String {
92    branch.trim().trim_start_matches("* ").to_string()
93}
94
95// Helper function to check if branch should be protected
96pub fn is_branch_protected(
97    branch: &str,
98    current_branch: &str,
99    protected_branches: &[String],
100) -> bool {
101    branch == current_branch || protected_branches.iter().any(|pb| pb == branch)
102}
103
104// Helper function to get git branch delete args
105pub fn get_git_branch_delete_args(branch: &str) -> [&str; 3] {
106    ["branch", "-d", branch]
107}
108
109// Helper function to format success message
110pub fn format_branch_deleted_message(branch: &str) -> String {
111    format!("🧹 Deleted merged branch '{branch}'")
112}
113
114// Helper function to format failure message
115pub fn format_branch_delete_failed_message(branch: &str) -> String {
116    format!("⚠️ Failed to delete branch '{branch}'")
117}
118
119// Helper function to format no branches message
120pub fn format_no_branches_to_prune_message() -> &'static str {
121    "✅ No merged branches to prune."
122}