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 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 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, ¤t_branch, &protected_branches))
37 .collect();
38
39 if branches.is_empty() {
40 println!("{}", format_no_branches_to_prune_message());
41 return;
42 }
43
44 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
60const DEFAULT_PROTECTED_BRANCHES: &[&str] = &["main", "master", "develop"];
62
63pub fn get_default_protected_branches() -> &'static [&'static str] {
64 DEFAULT_PROTECTED_BRANCHES
65}
66
67pub 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
76pub 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
90pub fn clean_git_branch_name(branch: &str) -> String {
92 branch.trim().trim_start_matches("* ").to_string()
93}
94
95pub 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
104pub fn get_git_branch_delete_args(branch: &str) -> [&str; 3] {
106 ["branch", "-d", branch]
107}
108
109pub fn format_branch_deleted_message(branch: &str) -> String {
111 format!("🧹 Deleted merged branch '{branch}'")
112}
113
114pub fn format_branch_delete_failed_message(branch: &str) -> String {
116 format!("⚠️ Failed to delete branch '{branch}'")
117}
118
119pub fn format_no_branches_to_prune_message() -> &'static str {
121 "✅ No merged branches to prune."
122}