cob: Make it easy to build histories
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
parent
5ee1f42b6a
commit
2f42bc942c
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ impl<T: FromHistory> Transaction<T> {
|
|||
}
|
||||
}
|
||||
|
||||
mod encoding {
|
||||
pub mod encoding {
|
||||
use serde::Serialize;
|
||||
|
||||
/// Serialize the change into a byte string.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
Loading…
Reference in New Issue