use colored::Colorize;
use dialoguer::Confirm;
use crate::git;
pub fn run() {
println!();
println!(" Analyzing branches...");
println!();
let current = git::current_branch();
let main_branch = detect_main_branch();
let merged_result = if let Some(ref main_br) = main_branch {
git::run(&["branch", "--merged", main_br])
} else {
git::run(&["branch", "--merged"])
};
let mut safe_to_delete: Vec<String> = vec![];
let mut all_branches: Vec<String> = vec![];
let branch_list = git::run(&["branch"]);
if branch_list.success {
for line in branch_list.stdout.lines() {
let name = line.trim().trim_start_matches("* ").to_string();
if !name.is_empty() {
all_branches.push(name);
}
}
}
if merged_result.success {
for line in merged_result.stdout.lines() {
let name = line.trim().trim_start_matches("* ").to_string();
if name.is_empty() { continue; }
if name == current { continue; }
if Some(&name) == main_branch.as_ref() { continue; }
if name == "main" || name == "master" { continue; }
safe_to_delete.push(name);
}
}
let not_merged: Vec<String> = all_branches
.iter()
.filter(|b| {
**b != current
&& !safe_to_delete.contains(b)
&& **b != "main"
&& **b != "master"
})
.cloned()
.collect();
if safe_to_delete.is_empty() && not_merged.is_empty() {
println!(" {} All clean! No branches to prune.", "✔".green());
println!();
return;
}
if !safe_to_delete.is_empty() {
println!(" {}:", "Safe to delete (already merged)".green().bold());
for name in &safe_to_delete {
let age = branch_last_commit_age(name);
println!(" {} {} {}", "-".green(), name, age.dimmed());
}
println!();
}
if !not_merged.is_empty() {
println!(" {}:", "Possibly unsafe (not merged)".yellow().bold());
for name in ¬_merged {
let age = branch_last_commit_age(name);
println!(" {} {} {}", "?".yellow(), name, age.dimmed());
}
println!();
}
if safe_to_delete.is_empty() {
println!(" No safely deletable branches found.");
println!();
return;
}
let confirm = Confirm::new()
.with_prompt(format!(
" Delete {} safe branch(es)?",
safe_to_delete.len()
))
.default(false)
.interact();
match confirm {
Ok(true) => {}
_ => {
println!(" Cancelled.");
println!();
return;
}
}
println!();
let mut deleted = 0;
for name in &safe_to_delete {
let result = git::run(&["branch", "-d", name]);
if result.success {
println!(" {} Deleted '{}'", "✔".green(), name);
deleted += 1;
} else {
println!(" {} Failed to delete '{}': {}", "✖".red(), name, result.stderr);
}
}
println!();
println!(
" {} {} branch(es) cleaned up.",
"✔".green().bold(),
deleted
);
println!();
}
fn detect_main_branch() -> Option<String> {
if git::branch_exists("main") {
Some("main".to_string())
} else if git::branch_exists("master") {
Some("master".to_string())
} else {
None
}
}
fn branch_last_commit_age(branch: &str) -> String {
let result = git::run(&["log", "-1", "--format=%cr", branch]);
if result.success && !result.stdout.is_empty() {
format!("(last commit {})", result.stdout)
} else {
String::new()
}
}