From 2f42bc942cb124d8f8b05e1adb8a90a289cab9ba Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sun, 1 Jan 2023 22:29:40 +0100 Subject: [PATCH] cob: Make it easy to build histories Signed-off-by: Alexis Sellier --- radicle-cob/src/history.rs | 10 ++++-- radicle-dag/src/lib.rs | 39 ++++++++++++++++++++++ radicle/src/cob.rs | 3 ++ radicle/src/cob/store.rs | 2 +- radicle/src/cob/test.rs | 67 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 radicle/src/cob/test.rs diff --git a/radicle-cob/src/history.rs b/radicle-cob/src/history.rs index 4e534828..d2e6142f 100644 --- a/radicle-cob/src/history.rs +++ b/radicle-cob/src/history.rs @@ -38,7 +38,7 @@ pub enum CreateError { } impl History { - pub(crate) fn new_from_root( + pub fn new_from_root( id: Id, actor: PublicKey, resource: Oid, @@ -114,14 +114,14 @@ impl History { pruning_fold::pruning_fold(init, items, f) } - pub(crate) fn tips(&self) -> BTreeSet { + pub fn tips(&self) -> BTreeSet { self.graph .tips() .map(|(_, entry)| (*entry.id()).into()) .collect() } - pub(crate) fn extend( + pub fn extend( &mut self, new_id: Id, new_actor: PublicKey, @@ -152,6 +152,10 @@ impl History { self.graph.dependency(new_id, (*tip).into()); } } + + pub fn merge(&mut self, other: Self) { + self.graph.merge(other.graph); + } } fn create_dag<'a>(root: &'a EntryId, entries: &'a HashMap) -> History { diff --git a/radicle-dag/src/lib.rs b/radicle-dag/src/lib.rs index fe2934b0..033318ff 100644 --- a/radicle-dag/src/lib.rs +++ b/radicle-dag/src/lib.rs @@ -130,6 +130,21 @@ impl Dag { .filter_map(|k| self.graph.get(k).map(|n| (k, n))) } + /// Merge a DAG into this one. + /// + /// If a key exists in both graphs, its value is set to that of the other graph. + pub fn merge(&mut self, other: Self) { + for k in other.tips.into_iter() { + self.tips.insert(k); + } + for k in other.roots.into_iter() { + self.roots.insert(k); + } + for (k, v) in other.graph.into_iter() { + self.graph.insert(k, v); + } + } + /// Return a topological ordering of the graph's nodes, using the given RNG. /// Graphs with more than one partial order will return an arbitrary topological ordering. /// @@ -238,6 +253,30 @@ mod tests { assert!(expected.contains(&sorted.as_slice())); } + #[test] + fn test_merge() { + let mut a = Dag::new(); + let mut b = Dag::new(); + let mut c = Dag::new(); + + a.node(0, ()); + a.node(1, ()); + a.dependency(1, 0); + + b.node(0, ()); + b.node(2, ()); + b.dependency(2, 0); + + c.merge(a); + c.merge(b); + + assert!(c.get(&0).is_some()); + assert!(c.get(&1).is_some()); + assert!(c.get(&2).is_some()); + assert!(c.has_dependency(&1, &0)); + assert!(c.has_dependency(&2, &0)); + } + #[test] fn test_diamond() { let mut dag = Dag::new(); diff --git a/radicle/src/cob.rs b/radicle/src/cob.rs index edee3642..4ce033ac 100644 --- a/radicle/src/cob.rs +++ b/radicle/src/cob.rs @@ -5,6 +5,9 @@ pub mod patch; pub mod store; pub mod thread; +#[cfg(test)] +pub mod test; + pub use cob::{create, get, list, remove, update}; pub use cob::{ identity, object::collaboration::error, CollaborativeObject, Contents, Create, Entry, History, diff --git a/radicle/src/cob/store.rs b/radicle/src/cob/store.rs index cc1df74f..03630a1f 100644 --- a/radicle/src/cob/store.rs +++ b/radicle/src/cob/store.rs @@ -286,7 +286,7 @@ impl Transaction { } } -mod encoding { +pub mod encoding { use serde::Serialize; /// Serialize the change into a byte string. diff --git a/radicle/src/cob/test.rs b/radicle/src/cob/test.rs new file mode 100644 index 00000000..a76cd7a9 --- /dev/null +++ b/radicle/src/cob/test.rs @@ -0,0 +1,67 @@ +use std::marker::PhantomData; +use std::ops::Deref; + +use nonempty::NonEmpty; +use serde::Serialize; + +use crate::cob::op::Op; +use crate::cob::store::encoding; +use crate::cob::History; +use crate::git::Oid; +use crate::test::arbitrary; + +/// Convenience type for building histories. +#[derive(Debug, Clone)] +pub struct HistoryBuilder { + history: History, + witness: PhantomData, + resource: Oid, +} + +impl HistoryBuilder { + pub fn new(op: &Op) -> HistoryBuilder { + let entry = arbitrary::oid(); + let resource = arbitrary::oid(); + let contents = encoding::encode(&op.action).unwrap(); + + Self { + history: History::new_from_root( + entry, + op.author, + resource, + NonEmpty::new(contents), + op.timestamp.as_secs(), + ), + resource, + witness: PhantomData, + } + } + + pub fn append(&mut self, op: &Op) -> &mut Self { + self.history.extend( + arbitrary::oid(), + op.author, + self.resource, + NonEmpty::new(encoding::encode(&op.action).unwrap()), + op.timestamp.as_secs(), + ); + self + } + + pub fn merge(&mut self, other: Self) { + self.history.merge(other.history); + } +} + +impl Deref for HistoryBuilder { + type Target = History; + + fn deref(&self) -> &Self::Target { + &self.history + } +} + +/// Create a new test history. +pub fn history(op: &Op) -> HistoryBuilder { + HistoryBuilder::new(op) +}