use crate::tasks::git::branch::delete_branch;
use crate::tasks::git::branch::get_branch_name;
use crate::tasks::git::branch::shorten_branch_ref;
use crate::tasks::git::checkout::checkout_branch;
use crate::tasks::git::cherry::unmerged_commits;
use crate::tasks::git::errors::GitError as E;
use crate::tasks::git::status::ensure_repo_clean;
use crate::utils::files;
use color_eyre::eyre::Result;
use git2::Branch;
use git2::BranchType;
use git2::Repository;
use tracing::debug;
use tracing::trace;
pub(super) fn prune_merged_branches(repo: &Repository, remote_name: &str) -> Result<bool> {
let branches_to_prune = branches_to_prune(repo)?;
if branches_to_prune.is_empty() {
debug!("Nothing to prune.");
return Ok(false);
}
ensure_repo_clean(repo)?;
debug!(
"Pruning branches in '{}': {:?}",
files::to_utf8_path(repo.workdir().ok_or(E::NoGitDirFound)?)?,
&branches_to_prune
.iter()
.map(get_branch_name)
.collect::<Result<Vec<String>>>()?,
);
for mut branch in branches_to_prune {
debug!("Pruning branch: {}", get_branch_name(&branch)?);
if branch.is_head() {
let remote_ref_name = format!("refs/remotes/{remote_name}/HEAD");
let remote_ref = repo.find_reference(&remote_ref_name)?;
let remote_head = remote_ref.symbolic_target().ok_or(E::NoHeadSet)?;
let short_branch = shorten_branch_ref(remote_head);
let short_branch = short_branch.trim_start_matches(&format!("{remote_name}/"));
let branch_name = format!("refs/heads/{short_branch}");
checkout_branch(repo, &branch_name, short_branch, remote_name, false)?;
}
delete_branch(repo, &mut branch)?;
}
Ok(true)
}
fn branches_to_prune(repo: &Repository) -> Result<Vec<Branch<'_>>> {
let mut branches_to_prune = Vec::new();
let mut remote_branches = Vec::new();
for branch in repo.branches(Some(BranchType::Remote))? {
remote_branches.push(get_branch_name(&branch?.0)?);
}
debug!("Remote branches: {remote_branches:?}");
for branch in repo.branches(Some(BranchType::Local))? {
let branch = branch?.0;
let branch_name = get_branch_name(&branch)?;
let branch_suffix = format!("/{branch_name}");
if remote_branches.iter().any(|b| b.ends_with(&branch_suffix)) {
trace!("Not pruning {branch_name} as it has a matching remote-tracking branch.",);
continue;
}
if let Ok(upstream_branch) = branch.upstream() {
if unmerged_commits(repo, &upstream_branch, &branch)? {
trace!("Not pruning {branch_name} as it has unmerged commits.");
continue;
}
} else {
trace!("Not pruning {branch_name} as it has no upstream branch.");
continue;
}
branches_to_prune.push(branch);
}
Ok(branches_to_prune)
}