#![allow(clippy::too_many_arguments)]
use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow};
use git_ext::Oid;
use radicle_dag::Dag;
pub use crate::change::{Contents, Entry, EntryId, Timestamp};
#[derive(Clone, Debug)]
pub struct History {
graph: Dag<EntryId, Entry>,
root: EntryId,
}
impl PartialEq for History {
fn eq(&self, other: &Self) -> bool {
self.tips() == other.tips()
}
}
impl Eq for History {}
impl History {
pub fn new(root: EntryId, graph: Dag<EntryId, Entry>) -> Self {
assert!(
graph.contains(&root),
"History::new: root must be present in graph"
);
Self { root, graph }
}
pub fn new_from_root(root: Entry) -> Self {
let id = *root.id();
Self::new(id, Dag::root(id, root))
}
pub fn tips(&self) -> BTreeSet<Oid> {
self.graph.tips().map(|(_, entry)| *entry.id()).collect()
}
pub fn traverse<F, A>(&self, init: A, roots: &[EntryId], mut f: F) -> A
where
F: for<'r> FnMut(A, &'r EntryId, &'r Entry) -> ControlFlow<A, A>,
{
self.graph.fold(roots, init, |acc, k, v| f(acc, k, v))
}
pub fn sorted<F>(&self, compare: F) -> impl Iterator<Item = &Entry>
where
F: FnMut(&EntryId, &EntryId) -> Ordering,
{
self.graph
.sorted_by(compare)
.into_iter()
.filter_map(|k| self.graph.get(&k))
.map(|node| &node.value)
}
pub fn extend(&mut self, change: Entry) {
let tips = self.tips();
let id = *change.id();
self.graph.node(id, change);
for tip in tips {
self.graph.dependency(id, (*tip).into());
}
}
pub fn merge(&mut self, other: Self) {
self.graph.merge(other.graph);
}
pub fn len(&self) -> usize {
self.graph.len()
}
pub fn is_empty(&self) -> bool {
self.graph.is_empty()
}
pub fn graph(&self) -> &Dag<EntryId, Entry> {
&self.graph
}
pub fn root(&self) -> &Entry {
self.graph
.get(&self.root)
.expect("History::root: the root entry must be present in the graph")
}
pub fn children_of(&self, id: &EntryId) -> Vec<EntryId> {
self.graph
.get(id)
.map(|n| n.dependents.iter().cloned().collect())
.unwrap_or_default()
}
}