use git2::Oid;
use std::collections::{HashMap, HashSet};
use super::GraphRow;
#[derive(Clone, Debug)]
pub(crate) struct BranchSegment {
pub id: String,
pub display_name: String,
pub row_indices: Vec<usize>,
}
struct UnionFind {
parent: Vec<usize>,
}
impl UnionFind {
fn new(n: usize) -> Self {
Self {
parent: (0..n).collect(),
}
}
fn find(&mut self, x: usize) -> usize {
if self.parent[x] != x {
self.parent[x] = self.find(self.parent[x]);
}
self.parent[x]
}
fn union(&mut self, a: usize, b: usize) {
let ra = self.find(a);
let rb = self.find(b);
if ra != rb {
if ra < rb {
self.parent[rb] = ra;
} else {
self.parent[ra] = rb;
}
}
}
}
pub(crate) fn compute_branch_segments(rows: &[GraphRow]) -> Vec<BranchSegment> {
if rows.is_empty() {
return Vec::new();
}
let oid_to_idx: HashMap<Oid, usize> =
rows.iter().enumerate().map(|(i, r)| (r.oid, i)).collect();
let mut main_trunk: HashSet<usize> = HashSet::new();
let mut cur = 0usize;
loop {
main_trunk.insert(cur);
let first_parent = rows[cur].parent_oids.first();
match first_parent.and_then(|oid| oid_to_idx.get(oid)) {
Some(&next_idx) => cur = next_idx,
None => break,
}
}
let mut uf = UnionFind::new(rows.len());
for (i, row) in rows.iter().enumerate() {
if main_trunk.contains(&i) {
continue;
}
if let Some(&parent_idx) = row.parent_oids.first().and_then(|oid| oid_to_idx.get(oid))
&& !main_trunk.contains(&parent_idx)
{
uf.union(i, parent_idx);
}
}
let mut groups: HashMap<usize, Vec<usize>> = HashMap::new();
for i in 0..rows.len() {
if main_trunk.contains(&i) {
continue;
}
let root = uf.find(i);
groups.entry(root).or_default().push(i);
}
let mut segments: Vec<BranchSegment> = Vec::new();
for (_, mut indices) in groups {
indices.sort();
let tip_idx = indices[0];
let tip = &rows[tip_idx];
let display_name = tip
.labels
.iter()
.find(|l| !l.is_tag)
.map(|l| l.name.clone())
.unwrap_or_else(|| tip.short_id.clone());
segments.push(BranchSegment {
id: tip.oid.to_string(),
display_name,
row_indices: indices,
});
}
segments.sort_by_key(|s| s.row_indices[0]);
segments
}