cob: Make it easy to build histories

Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
Alexis Sellier 2023-01-01 22:29:40 +01:00
parent 5ee1f42b6a
commit 2f42bc942c
No known key found for this signature in database
5 changed files with 117 additions and 4 deletions

View File

@ -38,7 +38,7 @@ pub enum CreateError {
}
impl History {
pub(crate) fn new_from_root<Id>(
pub fn new_from_root<Id>(
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<Oid> {
pub fn tips(&self) -> BTreeSet<Oid> {
self.graph
.tips()
.map(|(_, entry)| (*entry.id()).into())
.collect()
}
pub(crate) fn extend<Id>(
pub fn extend<Id>(
&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<EntryId, EntryWithClock>) -> History {

View File

@ -130,6 +130,21 @@ impl<K: Eq + Copy + Hash, V> Dag<K, V> {
.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();

View File

@ -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,

View File

@ -286,7 +286,7 @@ impl<T: FromHistory> Transaction<T> {
}
}
mod encoding {
pub mod encoding {
use serde::Serialize;
/// Serialize the change into a byte string.

67
radicle/src/cob/test.rs Normal file
View File

@ -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<A> {
history: History,
witness: PhantomData<A>,
resource: Oid,
}
impl<A: Serialize> HistoryBuilder<A> {
pub fn new(op: &Op<A>) -> HistoryBuilder<A> {
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<A>) -> &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<A> Deref for HistoryBuilder<A> {
type Target = History;
fn deref(&self) -> &Self::Target {
&self.history
}
}
/// Create a new test history.
pub fn history<A: Serialize>(op: &Op<A>) -> HistoryBuilder<A> {
HistoryBuilder::new(op)
}