use super::types::{
ExternalMergeInfo, MergedBranch, ReconcileResult, ReparentedBranch, StaleBranches,
};
use crate::error::Result;
use crate::traits::StateStore;
pub fn reconcile_merged(
state: &impl StateStore,
merged_prs: &[ExternalMergeInfo],
) -> Result<ReconcileResult> {
if merged_prs.is_empty() {
return Ok(ReconcileResult::default());
}
let mut stack = state.load_stack()?;
let mut result = ReconcileResult::default();
let sorted_merged = topological_sort_merged(merged_prs, &stack);
for merge_info in sorted_merged {
let children: Vec<String> = stack
.children_of(&merge_info.branch_name)
.iter()
.map(|b| b.name.to_string())
.collect();
for child_name in children {
if let Some(child) = stack.find_branch_mut(&child_name) {
let old_parent = child
.parent
.as_ref()
.map_or_else(String::new, ToString::to_string);
let pr_number = child.pr;
let new_parent = crate::BranchName::new(merge_info.merged_into.clone())?;
child.parent = Some(new_parent);
result.reparented.push(ReparentedBranch {
name: child_name,
old_parent,
new_parent: merge_info.merged_into.clone(),
pr_number,
});
}
}
stack.remove_branch(&merge_info.branch_name);
result.merged.push(MergedBranch {
name: merge_info.branch_name.clone(),
pr_number: merge_info.pr_number,
merged_into: merge_info.merged_into.clone(),
});
}
state.save_stack(&stack)?;
Ok(result)
}
pub fn remove_stale_branches(
repo: &impl rung_git::GitOps,
state: &impl StateStore,
) -> Result<StaleBranches> {
let mut stack = state.load_stack()?;
let mut removed = Vec::new();
let missing: Vec<String> = stack
.branches
.iter()
.filter(|b| !repo.branch_exists(&b.name))
.map(|b| b.name.to_string())
.collect();
if missing.is_empty() {
return Ok(StaleBranches::default());
}
let parent_map: std::collections::HashMap<String, Option<crate::BranchName>> = stack
.branches
.iter()
.map(|b| (b.name.to_string(), b.parent.clone()))
.collect();
for missing_name in &missing {
let mut resolved_parent = parent_map.get(missing_name).and_then(Clone::clone);
while let Some(ref parent) = resolved_parent {
let parent_str = parent.to_string();
if !missing.contains(&parent_str) {
break;
}
resolved_parent = parent_map.get(&parent_str).and_then(Clone::clone);
}
for branch in &mut stack.branches {
if branch.parent.as_ref().is_some_and(|p| p == missing_name) {
branch.parent.clone_from(&resolved_parent);
}
}
removed.push(missing_name.clone());
}
stack
.branches
.retain(|b| !missing.contains(&b.name.to_string()));
state.save_stack(&stack)?;
Ok(StaleBranches { removed })
}
fn topological_sort_merged<'a>(
merged_prs: &'a [ExternalMergeInfo],
stack: &crate::stack::Stack,
) -> Vec<&'a ExternalMergeInfo> {
let merged_names: std::collections::HashSet<&str> =
merged_prs.iter().map(|m| m.branch_name.as_str()).collect();
let parent_map: std::collections::HashMap<&str, Option<&str>> = stack
.branches
.iter()
.map(|b| (b.name.as_str(), b.parent.as_deref()))
.collect();
let mut result = Vec::with_capacity(merged_prs.len());
let mut remaining: Vec<&ExternalMergeInfo> = merged_prs.iter().collect();
let mut processed: std::collections::HashSet<&str> = std::collections::HashSet::new();
while !remaining.is_empty() {
let prev_len = remaining.len();
remaining.retain(|merge_info| {
let branch_name = merge_info.branch_name.as_str();
let parent = parent_map.get(branch_name).copied().flatten();
let parent_ready =
parent.is_none_or(|p| !merged_names.contains(p) || processed.contains(p));
if parent_ready {
processed.insert(branch_name);
result.push(*merge_info);
false } else {
true }
});
if remaining.len() == prev_len {
result.append(&mut remaining);
break;
}
}
result
}