cob: Add new history type, simplify `Content`
Keep the history type in the manifest, but no need to specify it for all individual changes. Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
8dc655758e
commit
5526eceea0
|
|
@ -96,13 +96,14 @@ impl change::Storage for git2::Repository {
|
|||
{
|
||||
let change::Create {
|
||||
typename,
|
||||
history_type,
|
||||
tips,
|
||||
message,
|
||||
contents,
|
||||
} = spec;
|
||||
let manifest = store::Manifest {
|
||||
typename,
|
||||
history_type: (&contents).into(),
|
||||
history_type,
|
||||
};
|
||||
|
||||
let revision = write_manifest(self, &manifest, &contents)?;
|
||||
|
|
@ -200,7 +201,7 @@ fn load_contents(
|
|||
manifest: &store::Manifest,
|
||||
) -> Result<entry::Contents, error::Load> {
|
||||
Ok(match manifest.history_type {
|
||||
HistoryType::Automerge => {
|
||||
HistoryType::Default | HistoryType::Automerge => {
|
||||
let contents_tree_entry = tree
|
||||
.get_name(CHANGE_BLOB_NAME)
|
||||
.ok_or_else(|| error::Load::NoChange(tree.id().into()))?;
|
||||
|
|
@ -208,7 +209,7 @@ fn load_contents(
|
|||
let contents_blob = contents_object
|
||||
.as_blob()
|
||||
.ok_or_else(|| error::Load::ChangeNotBlob(tree.id().into()))?;
|
||||
entry::Contents::automerge(contents_blob.content())
|
||||
contents_blob.content().to_owned()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ pub trait Storage {
|
|||
|
||||
pub struct Create<Id> {
|
||||
pub typename: TypeName,
|
||||
pub history_type: HistoryType,
|
||||
pub tips: Vec<Id>,
|
||||
pub message: String,
|
||||
pub contents: Contents,
|
||||
|
|
|
|||
|
|
@ -16,14 +16,17 @@ use crate::pruning_fold;
|
|||
pub mod entry;
|
||||
pub use entry::{Contents, Entry, EntryId};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum HistoryType {
|
||||
#[default]
|
||||
Default,
|
||||
Automerge,
|
||||
}
|
||||
|
||||
/// The DAG of changes making up the history of a collaborative object.
|
||||
///
|
||||
/// The `Author` represents the content address for the author of the change entry.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct History {
|
||||
graph: petgraph::Graph<Entry, (), petgraph::Directed, u32>,
|
||||
|
|
|
|||
|
|
@ -7,34 +7,9 @@ use git_ext::Oid;
|
|||
|
||||
use crate::pruning_fold;
|
||||
|
||||
use super::HistoryType;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
|
||||
pub enum Contents {
|
||||
Automerge(Vec<u8>),
|
||||
}
|
||||
|
||||
impl Contents {
|
||||
pub fn automerge(bytes: &[u8]) -> Self {
|
||||
Self::Automerge(bytes.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Contents> for HistoryType {
|
||||
fn from(c: &Contents) -> Self {
|
||||
match c {
|
||||
Contents::Automerge(..) => HistoryType::Automerge,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Contents {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Automerge(bytes) => bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Entry contents.
|
||||
/// This is the change payload.
|
||||
pub type Contents = Vec<u8>;
|
||||
|
||||
/// A unique identifier for a history entry.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
|
||||
// Linking Exception. For full terms see the included LICENSE file.
|
||||
|
||||
use crate::Store;
|
||||
use crate::{HistoryType, Store};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -11,6 +11,8 @@ use super::*;
|
|||
pub struct Create<Author> {
|
||||
/// The identity of the author for this object's first change.
|
||||
pub author: Option<Author>,
|
||||
/// The type of history that will be used for this object.
|
||||
pub history_type: HistoryType,
|
||||
/// The CRDT history to initialize this object with.
|
||||
pub contents: Contents,
|
||||
/// The typename for this object.
|
||||
|
|
@ -23,6 +25,7 @@ impl<Author> Create<Author> {
|
|||
fn create_spec(&self) -> change::Create<git_ext::Oid> {
|
||||
change::Create {
|
||||
typename: self.typename.clone(),
|
||||
history_type: self.history_type,
|
||||
tips: Vec::new(),
|
||||
message: self.message.clone(),
|
||||
contents: self.contents.clone(),
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
// Linking Exception. For full terms see the included LICENSE file.
|
||||
|
||||
use crate::{
|
||||
change, change_graph::ChangeGraph, identity::Identity, CollaborativeObject, Contents, ObjectId,
|
||||
Store, TypeName,
|
||||
change, change_graph::ChangeGraph, identity::Identity, CollaborativeObject, Contents,
|
||||
HistoryType, ObjectId, Store, TypeName,
|
||||
};
|
||||
|
||||
use super::error;
|
||||
|
|
@ -14,6 +14,8 @@ use super::error;
|
|||
pub struct Update<Author> {
|
||||
/// The identity of the author for the update of this object.
|
||||
pub author: Option<Author>,
|
||||
/// The type of history that will be used for this object.
|
||||
pub history_type: HistoryType,
|
||||
/// The CRDT changes to add to the object.
|
||||
pub changes: Contents,
|
||||
/// The object ID of the object to be updated.
|
||||
|
|
@ -60,6 +62,7 @@ where
|
|||
author,
|
||||
ref typename,
|
||||
object_id,
|
||||
history_type,
|
||||
changes,
|
||||
message,
|
||||
} = args;
|
||||
|
|
@ -89,6 +92,7 @@ where
|
|||
signer,
|
||||
change::Create {
|
||||
tips: object.tips().iter().cloned().collect(),
|
||||
history_type,
|
||||
contents: changes.clone(),
|
||||
typename: typename.clone(),
|
||||
message,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use quickcheck::Arbitrary;
|
|||
use radicle_crypto::Signer;
|
||||
|
||||
use crate::{
|
||||
create, get, history, list, object, test::arbitrary::Invalid, update, Create, ObjectId,
|
||||
create, get, list, object, test::arbitrary::Invalid, update, Create, HistoryType, ObjectId,
|
||||
TypeName, Update,
|
||||
};
|
||||
|
||||
|
|
@ -22,7 +22,6 @@ fn roundtrip() {
|
|||
project: proj,
|
||||
person: terry.clone(),
|
||||
};
|
||||
let contents = history::Contents::Automerge(Vec::new());
|
||||
let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
|
||||
let cob = create(
|
||||
&storage,
|
||||
|
|
@ -31,7 +30,8 @@ fn roundtrip() {
|
|||
&proj.identifier(),
|
||||
Create {
|
||||
author: Some(terry),
|
||||
contents,
|
||||
history_type: HistoryType::Automerge,
|
||||
contents: Vec::new(),
|
||||
typename: typename.clone(),
|
||||
message: "creating xyz.rad.issue".to_string(),
|
||||
},
|
||||
|
|
@ -63,7 +63,8 @@ fn list_cobs() {
|
|||
&proj.identifier(),
|
||||
Create {
|
||||
author: Some(terry.clone()),
|
||||
contents: history::Contents::Automerge(b"issue 1".to_vec()),
|
||||
history_type: HistoryType::Automerge,
|
||||
contents: b"issue 1".to_vec(),
|
||||
typename: typename.clone(),
|
||||
message: "creating xyz.rad.issue".to_string(),
|
||||
},
|
||||
|
|
@ -77,7 +78,8 @@ fn list_cobs() {
|
|||
&proj.identifier(),
|
||||
Create {
|
||||
author: Some(terry),
|
||||
contents: history::Contents::Automerge(b"issue 2".to_vec()),
|
||||
history_type: HistoryType::Automerge,
|
||||
contents: b"issue 2".to_vec(),
|
||||
typename: typename.clone(),
|
||||
message: "commenting xyz.rad.issue".to_string(),
|
||||
},
|
||||
|
|
@ -103,7 +105,6 @@ fn update_cob() {
|
|||
project: proj,
|
||||
person: terry.clone(),
|
||||
};
|
||||
let contents = history::Contents::Automerge(Vec::new());
|
||||
let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
|
||||
let cob = create(
|
||||
&storage,
|
||||
|
|
@ -112,7 +113,8 @@ fn update_cob() {
|
|||
&proj.identifier(),
|
||||
Create {
|
||||
author: Some(terry.clone()),
|
||||
contents,
|
||||
history_type: HistoryType::Automerge,
|
||||
contents: Vec::new(),
|
||||
typename: typename.clone(),
|
||||
message: "creating xyz.rad.issue".to_string(),
|
||||
},
|
||||
|
|
@ -130,7 +132,8 @@ fn update_cob() {
|
|||
&proj.identifier(),
|
||||
Update {
|
||||
author: Some(terry),
|
||||
changes: history::Contents::Automerge(b"issue 1".to_vec()),
|
||||
changes: b"issue 1".to_vec(),
|
||||
history_type: HistoryType::Automerge,
|
||||
object_id: *cob.id(),
|
||||
typename: typename.clone(),
|
||||
message: "commenting xyz.rad.issue".to_string(),
|
||||
|
|
@ -170,7 +173,8 @@ fn traverse_cobs() {
|
|||
&terry_proj.identifier(),
|
||||
Create {
|
||||
author: Some(terry),
|
||||
contents: history::Contents::Automerge(b"issue 1".to_vec()),
|
||||
contents: b"issue 1".to_vec(),
|
||||
history_type: HistoryType::Automerge,
|
||||
typename: typename.clone(),
|
||||
message: "creating xyz.rad.issue".to_string(),
|
||||
},
|
||||
|
|
@ -192,7 +196,8 @@ fn traverse_cobs() {
|
|||
&neil_proj.identifier(),
|
||||
Update {
|
||||
author: Some(neil),
|
||||
changes: history::Contents::Automerge(b"issue 2".to_vec()),
|
||||
changes: b"issue 2".to_vec(),
|
||||
history_type: HistoryType::Automerge,
|
||||
object_id: *cob.id(),
|
||||
typename,
|
||||
message: "commenting on xyz.rad.issue".to_string(),
|
||||
|
|
@ -210,7 +215,7 @@ fn traverse_cobs() {
|
|||
|
||||
if let (Some(author), Some(project)) = (author, project) {
|
||||
if project.delegate_check(&author) {
|
||||
acc.push(entry.contents().as_ref().to_vec())
|
||||
acc.push(entry.contents().to_vec())
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(acc)
|
||||
|
|
@ -220,7 +225,7 @@ fn traverse_cobs() {
|
|||
|
||||
// traverse over the history and filter by changes that were only authorized by neil
|
||||
let contents = updated.history().traverse(Vec::new(), |mut acc, entry| {
|
||||
acc.push(entry.contents().as_ref().to_vec());
|
||||
acc.push(entry.contents().to_vec());
|
||||
ControlFlow::Continue(acc)
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub mod value;
|
|||
|
||||
pub use cob::{
|
||||
identity, object::collaboration::error, CollaborativeObject, Contents, Create, Entry, History,
|
||||
ObjectId, TypeName, Update,
|
||||
HistoryType, ObjectId, TypeName, Update,
|
||||
};
|
||||
pub use shared::Timestamp;
|
||||
pub use store::Store;
|
||||
|
|
|
|||
|
|
@ -135,16 +135,13 @@ impl FromHistory for Issue {
|
|||
|
||||
fn from_history(history: &History) -> Result<Self, Error> {
|
||||
let doc = history.traverse(Automerge::new(), |mut doc, entry| {
|
||||
match entry.contents() {
|
||||
Contents::Automerge(bytes) => {
|
||||
match automerge::Change::from_bytes(bytes.clone()) {
|
||||
Ok(change) => {
|
||||
doc.apply_changes([change]).ok();
|
||||
}
|
||||
Err(_err) => {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
let bytes = entry.contents();
|
||||
match automerge::Change::from_bytes(bytes.clone()) {
|
||||
Ok(change) => {
|
||||
doc.apply_changes([change]).ok();
|
||||
}
|
||||
Err(_err) => {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(doc)
|
||||
|
|
@ -379,7 +376,7 @@ mod events {
|
|||
.map_err(|failure| failure.error)?
|
||||
.result;
|
||||
|
||||
Ok(Contents::Automerge(doc.save_incremental()))
|
||||
Ok(doc.save_incremental())
|
||||
}
|
||||
|
||||
pub fn comment(
|
||||
|
|
@ -414,7 +411,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = issue.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok(Contents::Automerge(change))
|
||||
Ok(change)
|
||||
}
|
||||
|
||||
pub fn lifecycle(
|
||||
|
|
@ -439,7 +436,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = issue.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok(Contents::Automerge(change))
|
||||
Ok(change)
|
||||
}
|
||||
|
||||
pub fn label(
|
||||
|
|
@ -466,7 +463,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = issue.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok(Contents::Automerge(change))
|
||||
Ok(change)
|
||||
}
|
||||
|
||||
pub fn reply(
|
||||
|
|
@ -503,7 +500,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = issue.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok(Contents::Automerge(change))
|
||||
Ok(change)
|
||||
}
|
||||
|
||||
pub fn react(
|
||||
|
|
@ -546,7 +543,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = issue.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok(Contents::Automerge(change))
|
||||
Ok(change)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,16 +43,13 @@ impl TryFrom<&History> for Label {
|
|||
|
||||
fn try_from(history: &History) -> Result<Self, Self::Error> {
|
||||
let doc = history.traverse(Automerge::new(), |mut doc, entry| {
|
||||
match entry.contents() {
|
||||
Contents::Automerge(bytes) => {
|
||||
match automerge::Change::from_bytes(bytes.clone()) {
|
||||
Ok(change) => {
|
||||
doc.apply_changes([change]).ok();
|
||||
}
|
||||
Err(_err) => {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
let bytes = entry.contents();
|
||||
match automerge::Change::from_bytes(bytes.clone()) {
|
||||
Ok(change) => {
|
||||
doc.apply_changes([change]).ok();
|
||||
}
|
||||
Err(_err) => {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(doc)
|
||||
|
|
@ -143,7 +140,7 @@ mod events {
|
|||
)
|
||||
.map_err(|failure| failure.error)?;
|
||||
|
||||
Ok(Contents::Automerge(doc.save_incremental()))
|
||||
Ok(doc.save_incremental())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -161,16 +161,13 @@ impl TryFrom<&History> for Patch {
|
|||
|
||||
fn try_from(history: &History) -> Result<Self, Self::Error> {
|
||||
let doc = history.traverse(Automerge::new(), |mut doc, entry| {
|
||||
match entry.contents() {
|
||||
Contents::Automerge(bytes) => {
|
||||
match automerge::Change::from_bytes(bytes.clone()) {
|
||||
Ok(change) => {
|
||||
doc.apply_changes([change]).ok();
|
||||
}
|
||||
Err(_err) => {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
let bytes = entry.contents();
|
||||
match automerge::Change::from_bytes(bytes.clone()) {
|
||||
Ok(change) => {
|
||||
doc.apply_changes([change]).ok();
|
||||
}
|
||||
Err(_err) => {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(doc)
|
||||
|
|
@ -750,7 +747,7 @@ mod events {
|
|||
.map_err(|failure| failure.error)?
|
||||
.result;
|
||||
|
||||
Ok(Contents::Automerge(doc.save_incremental()))
|
||||
Ok(doc.save_incremental())
|
||||
}
|
||||
|
||||
pub fn comment(
|
||||
|
|
@ -788,7 +785,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = patch.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok(Contents::Automerge(change))
|
||||
Ok(change)
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
|
|
@ -817,7 +814,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = patch.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok((revision_ix, Contents::Automerge(change)))
|
||||
Ok((revision_ix, change))
|
||||
}
|
||||
|
||||
pub fn reply(
|
||||
|
|
@ -857,7 +854,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = patch.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok(Contents::Automerge(change))
|
||||
Ok(change)
|
||||
}
|
||||
|
||||
pub fn review(
|
||||
|
|
@ -888,7 +885,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = patch.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok(((), Contents::Automerge(change)))
|
||||
Ok(((), change))
|
||||
}
|
||||
|
||||
pub fn merge(
|
||||
|
|
@ -921,7 +918,7 @@ mod events {
|
|||
#[allow(clippy::unwrap_used)]
|
||||
let change = patch.get_last_local_change().unwrap().raw_bytes().to_vec();
|
||||
|
||||
Ok(Contents::Automerge(change))
|
||||
Ok(change)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use crate::cob::shared::Author;
|
|||
use crate::cob::transaction::TransactionError;
|
||||
use crate::cob::CollaborativeObject;
|
||||
use crate::cob::{issue, label, patch};
|
||||
use crate::cob::{Contents, Create, History, ObjectId, TypeName, Update};
|
||||
use crate::cob::{Contents, Create, History, HistoryType, ObjectId, TypeName, Update};
|
||||
use crate::crypto::PublicKey;
|
||||
use crate::git;
|
||||
use crate::identity::project;
|
||||
|
|
@ -134,6 +134,7 @@ impl<'a, T: FromHistory> Store<'a, T> {
|
|||
Update {
|
||||
author: Some(cob::Author::from(*signer.public_key())),
|
||||
object_id,
|
||||
history_type: HistoryType::Automerge,
|
||||
typename: T::type_name().clone(),
|
||||
message: message.to_owned(),
|
||||
changes,
|
||||
|
|
@ -154,6 +155,7 @@ impl<'a, T: FromHistory> Store<'a, T> {
|
|||
&self.project,
|
||||
Create {
|
||||
author: Some(cob::Author::from(*signer.public_key())),
|
||||
history_type: HistoryType::Automerge,
|
||||
typename: T::type_name().clone(),
|
||||
message: message.to_owned(),
|
||||
contents,
|
||||
|
|
@ -182,11 +184,7 @@ impl<'a, T: FromHistory> Store<'a, T> {
|
|||
};
|
||||
|
||||
let doc = cob.history().traverse(Vec::new(), |mut doc, entry| {
|
||||
match entry.contents() {
|
||||
Contents::Automerge(bytes) => {
|
||||
doc.extend(bytes);
|
||||
}
|
||||
}
|
||||
doc.extend(entry.contents());
|
||||
ControlFlow::Continue(doc)
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue