use crate::git::GitRepo;
use console::style;
use inquire::MultiSelect;
pub fn prune_merged_branches(dry_run: bool) -> Result<(), Box<dyn std::error::Error>> {
let repo = GitRepo::open(".")?;
println!(
"{} {}",
style("🔍").blue().bold(),
if dry_run {
"Finding branches that would be pruned (dry run)..."
} else {
"Finding merged branches to prune..."
}
);
println!();
let branches_to_prune = find_branches_to_prune(&repo)?;
if branches_to_prune.is_empty() {
println!(
"{} No merged branches found to prune",
style("✨").green().bold()
);
return Ok(());
}
if dry_run {
show_dry_run_results(&branches_to_prune);
} else {
prune_branches(&repo, &branches_to_prune)?;
}
Ok(())
}
fn find_branches_to_prune(repo: &GitRepo) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let all_branches = repo.get_all_branches()?;
let current_branch = repo.get_current_branch()?;
let mut branches_to_prune = Vec::new();
let protected_branches = ["main", "master", "develop"];
for branch in all_branches {
if branch == current_branch {
continue;
}
if protected_branches.contains(&branch.as_str()) {
continue;
}
match repo.is_branch_merged_to_main(&branch) {
Ok(true) => {
branches_to_prune.push(branch);
}
Ok(false) => {
}
Err(e) => {
println!(
"{} Warning: Could not determine merge status for '{}': {}",
style("⚠").yellow(),
style(&branch).cyan(),
e
);
}
}
}
Ok(branches_to_prune)
}
fn show_dry_run_results(branches_to_prune: &[String]) {
println!(
"{} The following {} branches would be deleted:",
style("📋").cyan().bold(),
branches_to_prune.len()
);
println!();
for branch in branches_to_prune {
println!(
" {} {} {}",
style("🗑").red(),
style(branch).cyan().bold(),
style("(merged to main)").dim()
);
}
println!();
println!(
"{} Run without --dry-run to actually delete these branches",
style("💡").blue()
);
}
fn prune_branches(
repo: &GitRepo,
branches_to_prune: &[String],
) -> Result<(), Box<dyn std::error::Error>> {
println!(
"{} Found {} merged branches. Select which ones to delete:",
style("🗑").red().bold(),
branches_to_prune.len()
);
println!();
for branch in branches_to_prune {
println!(
" {} {} {}",
style("•").dim(),
style(branch).cyan().bold(),
style("(merged to main)").dim()
);
}
println!();
let branches_to_delete = MultiSelect::new(
"Select branches to delete:",
branches_to_prune.iter().map(|s| s.as_str()).collect(),
)
.prompt()?;
if branches_to_delete.is_empty() {
println!(
"{} No branches selected for deletion",
style("ℹ").blue().bold()
);
return Ok(());
}
println!(
"{} Deleting {} selected branches:",
style("🗑").red().bold(),
branches_to_delete.len()
);
println!();
let mut deleted_count = 0;
let mut failed_count = 0;
for branch in branches_to_delete {
match repo.delete_branch(branch) {
Ok(()) => {
println!(
" {} Deleted {}",
style("✓").green().bold(),
style(branch).cyan()
);
deleted_count += 1;
}
Err(e) => {
println!(
" {} Failed to delete {}: {}",
style("✗").red().bold(),
style(branch).cyan(),
e
);
failed_count += 1;
}
}
}
println!();
println!(
"{} Deleted {} branches{}",
style("✨").green().bold(),
deleted_count,
if failed_count > 0 {
format!(", {failed_count} failed")
} else {
String::new()
}
);
Ok(())
}