pub fn build_parent_candidates(
all_branches: &[String],
source: &str,
descendants: &[String],
trunk: &str,
) -> Vec<String> {
let mut candidates: Vec<String> = all_branches
.iter()
.filter(|n| n.as_str() != source && !descendants.contains(*n))
.cloned()
.collect();
candidates.sort();
if let Some(pos) = candidates.iter().position(|n| n == trunk) {
let t = candidates.remove(pos);
candidates.insert(0, t);
}
candidates
}
#[cfg(test)]
mod tests {
use super::build_parent_candidates;
fn names(values: &[&str]) -> Vec<String> {
values.iter().map(|s| s.to_string()).collect()
}
#[test]
fn excludes_source_and_descendants() {
let all = names(&["main", "a", "b", "c", "sibling"]);
let descendants = names(&["b", "c"]);
let got = build_parent_candidates(&all, "a", &descendants, "main");
assert_eq!(got, names(&["main", "sibling"]));
}
#[test]
fn pins_trunk_to_top_then_sorts_remainder() {
let all = names(&["z-branch", "sibling", "main", "apple"]);
let got = build_parent_candidates(&all, "feat-self", &[], "main");
assert_eq!(got, names(&["main", "apple", "sibling", "z-branch"]));
}
#[test]
fn sorts_alphabetically_when_trunk_absent() {
let all = names(&["z", "a", "m"]);
let got = build_parent_candidates(&all, "feat", &[], "unlisted-trunk");
assert_eq!(got, names(&["a", "m", "z"]));
}
#[test]
fn empty_when_everything_is_source_or_descendant() {
let all = names(&["a", "b"]);
let got = build_parent_candidates(&all, "a", &names(&["b"]), "a");
assert!(got.is_empty());
}
}