cob: Simplify `Entry` and `Change` types

Instead of having two types, we simply have one, called `Entry`, which
we rename from `Change`.

There was no real benefit to having two almost identical types, but lots
of added complexity. This patch simplifies the crate by removing one of
the types.
This commit is contained in:
cloudhead 2023-08-22 16:38:46 +02:00
parent f6bd7a3dc6
commit 205c15caa6
No known key found for this signature in database
24 changed files with 131 additions and 355 deletions

View File

@ -36,7 +36,7 @@ pub fn run(
return Ok(()); return Ok(());
} }
let root = patch.id.into(); let root = *patch.id;
let target = patch.target(); let target = patch.target();
patch.transaction("Edit", &signer, |tx| { patch.transaction("Edit", &signer, |tx| {

View File

@ -145,7 +145,7 @@ pub fn timeline(
for (revision_id, revision) in patch.revisions() { for (revision_id, revision) in patch.revisions() {
// Don't show an "update" line for the first revision. // Don't show an "update" line for the first revision.
if **revision_id != **patch_id { if *revision_id != **patch_id {
timeline.push(( timeline.push((
revision.timestamp(), revision.timestamp(),
term::Line::spaced( term::Line::spaced(

View File

@ -12,11 +12,10 @@ use once_cell::sync::Lazy;
use radicle_git_ext::commit::trailers::OwnedTrailer; use radicle_git_ext::commit::trailers::OwnedTrailer;
use crate::change::store::Version; use crate::change::store::Version;
use crate::history::entry::Timestamp;
use crate::signatures; use crate::signatures;
use crate::{ use crate::{
change::{self, store, Change}, change,
history::entry, change::{store, Contents, Entry, Timestamp},
signatures::{ExtendedSignature, Signatures}, signatures::{ExtendedSignature, Signatures},
trailers, Embed, trailers, Embed,
}; };
@ -99,7 +98,7 @@ impl change::Storage for git2::Repository {
parents: Vec<Self::Parent>, parents: Vec<Self::Parent>,
signer: &Signer, signer: &Signer,
spec: store::Template<Self::ObjectId>, spec: store::Template<Self::ObjectId>,
) -> Result<Change, Self::StoreError> ) -> Result<Entry, Self::StoreError>
where where
Signer: crypto::Signer, Signer: crypto::Signer,
{ {
@ -129,7 +128,7 @@ impl change::Storage for git2::Repository {
tree, tree,
)?; )?;
Ok(Change { Ok(Entry {
id, id,
revision: revision.into(), revision: revision.into(),
signature, signature,
@ -149,7 +148,7 @@ impl change::Storage for git2::Repository {
.collect::<Vec<_>>()) .collect::<Vec<_>>())
} }
fn load(&self, id: Self::ObjectId) -> Result<Change, Self::LoadError> { fn load(&self, id: Self::ObjectId) -> Result<Entry, Self::LoadError> {
let commit = Commit::read(self, id.into())?; let commit = Commit::read(self, id.into())?;
let timestamp = git2::Time::from(commit.committer().time).seconds() as u64; let timestamp = git2::Time::from(commit.committer().time).seconds() as u64;
let resource = parse_resource_trailer(commit.trailers())?; let resource = parse_resource_trailer(commit.trailers())?;
@ -172,7 +171,7 @@ impl change::Storage for git2::Repository {
let manifest = load_manifest(self, &tree)?; let manifest = load_manifest(self, &tree)?;
let contents = load_contents(self, &tree)?; let contents = load_contents(self, &tree)?;
Ok(Change { Ok(Entry {
id, id,
revision: tree.id().into(), revision: tree.id().into(),
signature: ExtendedSignature::new(key, sig), signature: ExtendedSignature::new(key, sig),
@ -220,10 +219,7 @@ fn load_manifest(
}) })
} }
fn load_contents( fn load_contents(repo: &git2::Repository, tree: &git2::Tree) -> Result<Contents, error::Load> {
repo: &git2::Repository,
tree: &git2::Tree,
) -> Result<entry::Contents, error::Load> {
let ops = tree let ops = tree
.iter() .iter()
.filter_map(|entry| { .filter_map(|entry| {

View File

@ -3,11 +3,11 @@
use git_ext::Oid; use git_ext::Oid;
pub mod store; pub mod store;
pub use store::{Storage, Template}; pub use store::{Contents, EntryId, Storage, Template, Timestamp};
use crate::signatures::ExtendedSignature; use crate::signatures::ExtendedSignature;
/// A single change in the change graph. The layout of changes in the repository /// A single change in the change graph. The layout of changes in the repository
/// is specified in the RFC (docs/rfc/0662-collaborative-objects.adoc) /// is specified in the RFC (docs/rfc/0662-collaborative-objects.adoc)
/// under "Change Commits". /// under "Change Commits".
pub type Change = store::Change<Oid, Oid, ExtendedSignature>; pub type Entry = store::Entry<Oid, Oid, ExtendedSignature>;

View File

@ -6,12 +6,9 @@ use nonempty::NonEmpty;
use radicle_git_ext::Oid; use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{signatures, TypeName};
history::{Contents, Timestamp},
signatures, TypeName,
};
/// Change storage. /// Change entry storage.
pub trait Storage { pub trait Storage {
type StoreError: Error + Send + Sync + 'static; type StoreError: Error + Send + Sync + 'static;
type LoadError: Error + Send + Sync + 'static; type LoadError: Error + Send + Sync + 'static;
@ -20,7 +17,7 @@ pub trait Storage {
type Parent; type Parent;
type Signatures; type Signatures;
/// Store a new change. /// Store a new change entry.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn store<G>( fn store<G>(
&self, &self,
@ -28,16 +25,16 @@ pub trait Storage {
parents: Vec<Self::Parent>, parents: Vec<Self::Parent>,
signer: &G, signer: &G,
template: Template<Self::ObjectId>, template: Template<Self::ObjectId>,
) -> Result<Change<Self::Parent, Self::ObjectId, Self::Signatures>, Self::StoreError> ) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::StoreError>
where where
G: crypto::Signer; G: crypto::Signer;
/// Load a change. /// Load a change entry.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn load( fn load(
&self, &self,
id: Self::ObjectId, id: Self::ObjectId,
) -> Result<Change<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>; ) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>;
/// Returns the parents of the object with the specified ID. /// Returns the parents of the object with the specified ID.
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError>; fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError>;
@ -52,8 +49,18 @@ pub struct Template<Id> {
pub contents: NonEmpty<Vec<u8>>, pub contents: NonEmpty<Vec<u8>>,
} }
/// Entry contents.
/// This is the change payload.
pub type Contents = NonEmpty<Vec<u8>>;
/// Local time in seconds since epoch.
pub type Timestamp = u64;
/// A unique identifier for a history entry.
pub type EntryId = Oid;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Change<Resource, Id, Signature> { pub struct Entry<Resource, Id, Signature> {
/// The content address of the `Change` itself. /// The content address of the `Change` itself.
pub id: Id, pub id: Id,
/// The content address of the tree of the `Change`. /// The content address of the tree of the `Change`.
@ -75,7 +82,7 @@ pub struct Change<Resource, Id, Signature> {
pub timestamp: Timestamp, pub timestamp: Timestamp,
} }
impl<Resource, Id, S> fmt::Display for Change<Resource, Id, S> impl<Resource, Id, S> fmt::Display for Entry<Resource, Id, S>
where where
Id: fmt::Display, Id: fmt::Display,
{ {
@ -84,7 +91,7 @@ where
} }
} }
impl<Resource, Id, Signatures> Change<Resource, Id, Signatures> { impl<Resource, Id, Signatures> Entry<Resource, Id, Signatures> {
pub fn id(&self) -> &Id { pub fn id(&self) -> &Id {
&self.id &self.id
} }
@ -102,7 +109,7 @@ impl<Resource, Id, Signatures> Change<Resource, Id, Signatures> {
} }
} }
impl<R, Id> Change<R, Id, signatures::Signatures> impl<R, Id> Entry<R, Id, signatures::Signatures>
where where
Id: AsRef<[u8]>, Id: AsRef<[u8]>,
{ {
@ -113,13 +120,17 @@ where
} }
} }
impl<R, Id> Change<R, Id, signatures::ExtendedSignature> impl<R, Id> Entry<R, Id, signatures::ExtendedSignature>
where where
Id: AsRef<[u8]>, Id: AsRef<[u8]>,
{ {
pub fn valid_signatures(&self) -> bool { pub fn valid_signatures(&self) -> bool {
self.signature.verify(self.revision.as_ref()) self.signature.verify(self.revision.as_ref())
} }
pub fn author(&self) -> &crypto::PublicKey {
&self.signature.key
}
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -7,14 +7,14 @@ use git_ext::Oid;
use radicle_dag::Dag; use radicle_dag::Dag;
use crate::{ use crate::{
change, history::EntryId, object, signatures::ExtendedSignature, Change, CollaborativeObject, change, history::EntryId, object, signatures::ExtendedSignature, CollaborativeObject, Entry,
Entry, History, ObjectId, TypeName, History, ObjectId, TypeName,
}; };
/// The graph of changes for a particular collaborative object /// The graph of changes for a particular collaborative object
pub(super) struct ChangeGraph { pub(super) struct ChangeGraph {
object_id: ObjectId, object_id: ObjectId,
graph: Dag<Oid, Change>, graph: Dag<Oid, Entry>,
} }
impl ChangeGraph { impl ChangeGraph {
@ -96,20 +96,7 @@ impl ChangeGraph {
if !change.valid_signatures() { if !change.valid_signatures() {
return ControlFlow::Break(graph); return ControlFlow::Break(graph);
} }
let entry = Entry::new( let entry = change.value.clone();
*change.id(),
change.signature.key,
change.resource,
change.contents().clone(),
change
.parents
.iter()
.cloned()
.map(|oid| oid.into())
.collect(),
change.timestamp,
change.manifest.clone(),
);
let id = *entry.id(); let id = *entry.id();
graph.node(id, entry); graph.node(id, entry);
@ -141,7 +128,7 @@ impl ChangeGraph {
} }
struct GraphBuilder { struct GraphBuilder {
graph: Dag<Oid, Change>, graph: Dag<Oid, Entry>,
} }
impl Default for GraphBuilder { impl Default for GraphBuilder {
@ -157,7 +144,7 @@ impl GraphBuilder {
&mut self, &mut self,
storage: &S, storage: &S,
commit_id: Oid, commit_id: Oid,
change: Change, change: Entry,
) -> Result<Vec<(Oid, Oid)>, S::LoadError> ) -> Result<Vec<(Oid, Oid)>, S::LoadError>
where where
S: change::Storage<ObjectId = Oid, Parent = Oid, Signatures = ExtendedSignature>, S: change::Storage<ObjectId = Oid, Parent = Oid, Signatures = ExtendedSignature>,

View File

@ -3,13 +3,9 @@
use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow}; use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow};
use git_ext::Oid; use git_ext::Oid;
use radicle_crypto::PublicKey;
use radicle_dag::Dag; use radicle_dag::Dag;
pub mod entry; pub use crate::change::{Contents, Entry, EntryId, Timestamp};
pub use entry::{Contents, Entry, EntryId, Timestamp};
use crate::Manifest;
/// The DAG of changes making up the history of a collaborative object. /// The DAG of changes making up the history of a collaborative object.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -36,32 +32,11 @@ impl History {
Self { root, graph } Self { root, graph }
} }
pub fn new_from_root<Id>( /// Create a new history from a root entry.
id: Id, pub fn new_from_root(root: Entry) -> Self {
actor: PublicKey, let id = *root.id();
resource: Oid,
contents: Contents,
timestamp: Timestamp,
manifest: Manifest,
) -> Self
where
Id: Into<EntryId>,
{
let id = id.into();
let root = Entry {
id,
actor,
resource,
contents,
parents: vec![],
timestamp,
manifest,
};
Self { Self::new(id, Dag::root(id, root))
root: id,
graph: Dag::root(id, root),
}
} }
/// Get the current history timestamp. /// Get the current history timestamp.
@ -69,17 +44,14 @@ impl History {
pub fn timestamp(&self) -> Timestamp { pub fn timestamp(&self) -> Timestamp {
self.graph self.graph
.tips() .tips()
.map(|(_, n)| n.timestamp()) .map(|(_, n)| n.timestamp)
.max() .max()
.unwrap_or_default() .unwrap_or_default()
} }
/// Get all the tips of the graph. /// Get all the tips of the graph.
pub fn tips(&self) -> BTreeSet<Oid> { pub fn tips(&self) -> BTreeSet<Oid> {
self.graph self.graph.tips().map(|(_, entry)| *entry.id()).collect()
.tips()
.map(|(_, entry)| (*entry.id()).into())
.collect()
} }
/// A topological (parents before children) traversal of the dependency /// A topological (parents before children) traversal of the dependency
@ -109,34 +81,14 @@ impl History {
} }
/// Extend this history with a new entry. /// Extend this history with a new entry.
pub fn extend<Id>( pub fn extend(&mut self, change: Entry) {
&mut self,
new_id: Id,
new_actor: PublicKey,
new_resource: Oid,
new_contents: Contents,
new_parents: Vec<EntryId>,
new_timestamp: Timestamp,
manifest: Manifest,
) where
Id: Into<EntryId>,
{
let tips = self.tips(); let tips = self.tips();
let new_id = new_id.into(); let id = *change.id();
let new_entry = Entry::new(
new_id,
new_actor,
new_resource,
new_contents,
new_parents,
new_timestamp,
manifest,
);
self.graph.node(new_id, new_entry); self.graph.node(id, change);
for tip in tips { for tip in tips {
self.graph.dependency(new_id, (*tip).into()); self.graph.dependency(id, (*tip).into());
} }
} }

View File

@ -1,162 +0,0 @@
// Copyright © 2021 The Radicle Link Contributors
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use git_ext::Oid;
use nonempty::NonEmpty;
use radicle_crypto::PublicKey;
use serde::{Deserialize, Serialize};
use crate::{object, Manifest, ObjectId, Version};
/// Entry contents.
/// This is the change payload.
pub type Contents = NonEmpty<Vec<u8>>;
/// Local time in seconds since epoch.
pub type Timestamp = u64;
/// A unique identifier for a history entry.
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct EntryId(Oid);
impl fmt::Display for EntryId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for EntryId {
type Err = git_ext::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let oid = git_ext::Oid::try_from(s)?;
Ok(Self(oid))
}
}
impl From<git2::Oid> for EntryId {
fn from(id: git2::Oid) -> Self {
Self(id.into())
}
}
impl From<Oid> for EntryId {
fn from(id: Oid) -> Self {
Self(id)
}
}
impl From<EntryId> for Oid {
fn from(EntryId(id): EntryId) -> Self {
id
}
}
impl From<&EntryId> for object::ObjectId {
fn from(EntryId(id): &EntryId) -> Self {
id.into()
}
}
impl From<ObjectId> for EntryId {
fn from(id: ObjectId) -> Self {
Self(*id)
}
}
impl Deref for EntryId {
type Target = Oid;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// One entry in the dependency graph for a change
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Entry {
/// The identifier for this entry
pub(super) id: EntryId,
/// The actor that authored this entry.
pub(super) actor: PublicKey,
/// The content-address for the resource this entry lives under.
/// If the resource was updated, this should point to its latest version.
pub(super) resource: Oid,
/// Parent entries.
pub(super) parents: Vec<EntryId>,
/// The contents of this entry.
pub(super) contents: Contents,
/// The entry timestamp, as seconds since epoch.
pub(super) timestamp: Timestamp,
/// COB manifest.
pub(super) manifest: Manifest,
}
impl Entry {
pub fn new<Id>(
id: Id,
actor: PublicKey,
resource: Oid,
contents: Contents,
parents: Vec<EntryId>,
timestamp: Timestamp,
manifest: Manifest,
) -> Self
where
Id: Into<EntryId>,
{
Self {
id: id.into(),
actor,
resource,
contents,
timestamp,
parents,
manifest,
}
}
/// The current `Oid` of the resource this change lives under.
pub fn resource(&self) -> Oid {
self.resource
}
/// Parent entries.
pub fn parents(&self) -> &[EntryId] {
&self.parents
}
/// The public key of the actor.
pub fn actor(&self) -> &PublicKey {
&self.actor
}
/// The COB version of this entry.
pub fn version(&self) -> &Version {
&self.manifest.version
}
/// The COB manifest.
pub fn manifest(&self) -> &Manifest {
&self.manifest
}
/// The entry timestamp.
pub fn timestamp(&self) -> Timestamp {
self.timestamp
}
/// The contents of this change
pub fn contents(&self) -> &Contents {
&self.contents
}
/// Entry ID.
pub fn id(&self) -> &EntryId {
&self.id
}
}

View File

@ -6,9 +6,6 @@
//! Collaborative objects are graphs of CRDTs. The current CRDTs that //! Collaborative objects are graphs of CRDTs. The current CRDTs that
//! is intended to be used are specifically [automerge] CRDTs. //! is intended to be used are specifically [automerge] CRDTs.
//! //!
//! The initial design is proposed at [RFC-0662], and this
//! implementation keeps to most of its design principle.
//!
//! ## Basic Types //! ## Basic Types
//! //!
//! The basic types that are found in `radicle-cob` are: //! The basic types that are found in `radicle-cob` are:
@ -55,15 +52,14 @@
//! automerge document and deserialize into an application defined //! automerge document and deserialize into an application defined
//! object. //! object.
//! //!
//! This traversal is also the point at which the [`Entry::actor`] //! This traversal is also the point at which the [`Entry::author`]
//! and [`Entry::resource`] can be retrieved to apply any kind of //! and [`Entry::resource`] can be retrieved to apply any kind of
//! filtering logic. For example, a specific `actor`'s change may be //! filtering logic. For example, a specific `author`'s change may be
//! egregious, spouting terrible libel about Radicle. It is at this //! egregious, spouting terrible libel about Radicle. It is at this
//! point that the `actor`'s change can be filtered out from the //! point that the `actor`'s change can be filtered out from the
//! final product of the traversal. //! final product of the traversal.
//! //!
//! [automerge]: https://automerge.org //! [automerge]: https://automerge.org
//! [RFC-0662]: https://github.com/radicle-dev/radicle-link/blob/master/docs/rfc/0662-collaborative-objects.adoc
#[cfg(test)] #[cfg(test)]
extern crate qcheck; extern crate qcheck;
@ -81,11 +77,11 @@ mod change_graph;
mod trailers; mod trailers;
pub mod change; pub mod change;
pub use change::store::{Embed, Manifest, Version}; pub use change::store::{Contents, Embed, Manifest, Version};
pub use change::Change; pub use change::Entry;
pub mod history; pub mod history;
pub use history::{Contents, Entry, History}; pub use history::History;
pub mod signatures; pub mod signatures;
use signatures::ExtendedSignature; use signatures::ExtendedSignature;

View File

@ -36,7 +36,7 @@ impl Create {
/// Create a new [`CollaborativeObject`]. /// Create a new [`CollaborativeObject`].
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Change`]s at content-addressable locations. Please see /// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information. /// [`Store`] for further information.
/// ///
/// The `signer` is expected to be a cryptographic signing key. This /// The `signer` is expected to be a cryptographic signing key. This
@ -74,14 +74,7 @@ where
.update(identifier, &type_name, &object_id, &init_change) .update(identifier, &type_name, &object_id, &init_change)
.map_err(|err| error::Create::Refs { err: Box::new(err) })?; .map_err(|err| error::Create::Refs { err: Box::new(err) })?;
let history = History::new_from_root( let history = History::new_from_root(init_change);
*init_change.id(),
init_change.signature.key,
resource,
init_change.contents,
init_change.timestamp,
init_change.manifest,
);
Ok(CollaborativeObject { Ok(CollaborativeObject {
manifest: Manifest::new(type_name, version), manifest: Manifest::new(type_name, version),

View File

@ -7,7 +7,7 @@ use super::error;
/// Get a [`CollaborativeObject`], if it exists. /// Get a [`CollaborativeObject`], if it exists.
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Change`]s at content-addressable locations. Please see /// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information. /// [`Store`] for further information.
/// ///
/// The `typename` is the type of object to be found, while the /// The `typename` is the type of object to be found, while the

View File

@ -27,7 +27,7 @@ pub struct ChangeGraphInfo {
/// is mostly useful for debugging and testing /// is mostly useful for debugging and testing
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Change`]s at content-addressable locations. Please see /// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information. /// [`Store`] for further information.
/// ///
/// The `typename` is the type of object to be found, while the `oid` /// The `typename` is the type of object to be found, while the `oid`

View File

@ -7,7 +7,7 @@ use super::error;
/// List a set of [`CollaborativeObject`]. /// List a set of [`CollaborativeObject`].
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Change`]s at content-addressable locations. Please see /// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information. /// [`Store`] for further information.
/// ///
/// The `typename` is the type of objects to be listed. /// The `typename` is the type of objects to be listed.

View File

@ -7,7 +7,7 @@ use super::error;
/// Remove a [`crate::CollaborativeObject`]. /// Remove a [`crate::CollaborativeObject`].
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Change`]s at content-addressable locations. Please see /// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information. /// [`Store`] for further information.
/// ///
/// The `typename` is the type of object to be found, while the /// The `typename` is the type of object to be found, while the

View File

@ -38,7 +38,7 @@ pub struct Update {
/// Update an existing [`CollaborativeObject`]. /// Update an existing [`CollaborativeObject`].
/// ///
/// The `storage` is the backing storage for storing /// The `storage` is the backing storage for storing
/// [`crate::Change`]s at content-addressable locations. Please see /// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information. /// [`Store`] for further information.
/// ///
/// The `signer` is expected to be a cryptographic signing key. This /// The `signer` is expected to be a cryptographic signing key. This
@ -99,21 +99,14 @@ where
.update(identifier, typename, &object_id, &change) .update(identifier, typename, &object_id, &change)
.map_err(|err| error::Update::Refs { err: Box::new(err) })?; .map_err(|err| error::Update::Refs { err: Box::new(err) })?;
let parents: Vec<EntryId> = change.parents.into_iter().map(|oid| oid.into()).collect(); let parents = change.parents.to_vec();
let head = change.id;
object.history.extend( object.history.extend(change);
change.id,
change.signature.key,
change.resource,
change.contents,
parents.clone(),
change.timestamp,
change.manifest,
);
Ok(Updated { Ok(Updated {
object, object,
head: change.id, head,
parents, parents,
}) })
} }

View File

@ -5,7 +5,7 @@ use std::{collections::BTreeMap, error::Error};
use git_ext::ref_format::RefString; use git_ext::ref_format::RefString;
use git_ext::Oid; use git_ext::Oid;
use crate::change::Change; use crate::change::Entry;
use crate::{ObjectId, TypeName}; use crate::{ObjectId, TypeName};
/// The [`Reference`]s that refer to the commits that make up a /// The [`Reference`]s that refer to the commits that make up a
@ -78,7 +78,7 @@ pub trait Storage {
identifier: &Self::Identifier, identifier: &Self::Identifier,
typename: &TypeName, typename: &TypeName,
object_id: &ObjectId, object_id: &ObjectId,
change: &Change, change: &Entry,
) -> Result<(), Self::UpdateError>; ) -> Result<(), Self::UpdateError>;
/// Remove a ref to a particular collaborative object /// Remove a ref to a particular collaborative object

View File

@ -73,7 +73,7 @@ impl change::Storage for Storage {
signer: &Signer, signer: &Signer,
spec: change::Template<Self::ObjectId>, spec: change::Template<Self::ObjectId>,
) -> Result< ) -> Result<
change::store::Change<Self::Parent, Self::ObjectId, Self::Signatures>, change::store::Entry<Self::Parent, Self::ObjectId, Self::Signatures>,
Self::StoreError, Self::StoreError,
> >
where where
@ -85,10 +85,8 @@ impl change::Storage for Storage {
fn load( fn load(
&self, &self,
id: Self::ObjectId, id: Self::ObjectId,
) -> Result< ) -> Result<change::store::Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>
change::store::Change<Self::Parent, Self::ObjectId, Self::Signatures>, {
Self::LoadError,
> {
self.as_raw().load(id) self.as_raw().load(id)
} }
@ -156,7 +154,7 @@ impl object::Storage for Storage {
identifier: &Self::Identifier, identifier: &Self::Identifier,
typename: &crate::TypeName, typename: &crate::TypeName,
object_id: &ObjectId, object_id: &ObjectId,
change: &change::Change, change: &change::Entry,
) -> Result<(), Self::UpdateError> { ) -> Result<(), Self::UpdateError> {
let name = format!( let name = format!(
"refs/rad/{}/cobs/{}/{}", "refs/rad/{}/cobs/{}/{}",

View File

@ -213,7 +213,7 @@ fn traverse_cobs() {
// traverse over the history and filter by changes that were only authorized by terry // traverse over the history and filter by changes that were only authorized by terry
let contents = object.history().traverse(Vec::new(), |mut acc, _, entry| { let contents = object.history().traverse(Vec::new(), |mut acc, _, entry| {
if entry.actor() == terry_signer.public_key() { if entry.author() == terry_signer.public_key() {
acc.push(entry.contents().head.clone()); acc.push(entry.contents().head.clone());
} }
ControlFlow::Continue(acc) ControlFlow::Continue(acc)

View File

@ -129,7 +129,7 @@ impl store::FromHistory for Issue {
let root = history.root(); let root = history.root();
// Deprecated. Remove when we drop legacy support. // Deprecated. Remove when we drop legacy support.
if root.manifest().is_legacy() { if root.manifest.is_legacy() {
let legacy = super::legacy::issue::Issue::from_history(history, repo)?; let legacy = super::legacy::issue::Issue::from_history(history, repo)?;
let issue = legacy.into(); let issue = legacy.into();
@ -673,10 +673,10 @@ mod test {
let mut issue_bob = bob_issues.get_mut(&id).unwrap(); let mut issue_bob = bob_issues.get_mut(&id).unwrap();
issue_bob issue_bob
.comment("Bob's reply", id.into(), vec![], &t.bob.signer) .comment("Bob's reply", *id, vec![], &t.bob.signer)
.unwrap(); .unwrap();
issue_alice issue_alice
.comment("Alice's reply", id.into(), vec![], &t.alice.signer) .comment("Alice's reply", *id, vec![], &t.alice.signer)
.unwrap(); .unwrap();
assert_eq!(issue_bob.comments().count(), 2); assert_eq!(issue_bob.comments().count(), 2);
@ -704,7 +704,7 @@ mod test {
t.eve.repo.fetch(&t.alice); t.eve.repo.fetch(&t.alice);
let eve_reply = issue_eve let eve_reply = issue_eve
.comment("Eve's reply", id.into(), vec![], &t.eve.signer) .comment("Eve's reply", *id, vec![], &t.eve.signer)
.unwrap(); .unwrap();
t.bob.repo.fetch(&t.eve); t.bob.repo.fetch(&t.eve);
@ -721,7 +721,7 @@ mod test {
let (first, _) = issue_bob.comments().next().unwrap(); let (first, _) = issue_bob.comments().next().unwrap();
let (last, _) = issue_bob.comments().last().unwrap(); let (last, _) = issue_bob.comments().last().unwrap();
assert_eq!(*first, issue_alice.id.into()); assert_eq!(*first, *issue_alice.id);
assert_eq!(*last, eve_reply); assert_eq!(*last, eve_reply);
} }
@ -1212,7 +1212,7 @@ mod test {
issue issue
.comment( .comment(
"Here's a binary file", "Here's a binary file",
issue.id.into(), *issue.id,
[embed3.clone()], [embed3.clone()],
&node.signer, &node.signer,
) )

View File

@ -84,11 +84,11 @@ impl From<Entry> for Op<Vec<u8>> {
Self { Self {
id: *entry.id(), id: *entry.id(),
actions: entry.contents().clone(), actions: entry.contents().clone(),
author: *entry.actor(), author: *entry.author(),
parents: entry.parents().to_owned(), parents: entry.parents.to_owned(),
timestamp: Timestamp::from_secs(entry.timestamp()), timestamp: Timestamp::from_secs(entry.timestamp),
identity: entry.resource(), identity: entry.resource,
manifest: entry.manifest().clone(), manifest: entry.manifest.clone(),
} }
} }
} }
@ -101,13 +101,13 @@ where
fn try_from(entry: &'a Entry) -> Result<Self, Self::Error> { fn try_from(entry: &'a Entry) -> Result<Self, Self::Error> {
let id = *entry.id(); let id = *entry.id();
let identity = entry.resource(); let identity = *entry.resource();
let actions: Vec<_> = entry let actions: Vec<_> = entry
.contents() .contents()
.iter() .iter()
.map(|blob| serde_json::from_slice(blob.as_slice())) .map(|blob| serde_json::from_slice(blob.as_slice()))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let manifest = entry.manifest().clone(); let manifest = entry.manifest.clone();
// SAFETY: Entry is guaranteed to have at least one operation. // SAFETY: Entry is guaranteed to have at least one operation.
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
@ -115,9 +115,9 @@ where
let op = Op { let op = Op {
id, id,
actions, actions,
author: *entry.actor(), author: *entry.author(),
timestamp: Timestamp::from_secs(entry.timestamp()), timestamp: Timestamp::from_secs(entry.timestamp),
parents: entry.parents().to_owned(), parents: entry.parents.to_owned(),
identity, identity,
manifest, manifest,
}; };

View File

@ -469,7 +469,7 @@ impl store::FromHistory for Patch {
let root = history.root(); let root = history.root();
// Deprecated. Remove when we drop legacy support. // Deprecated. Remove when we drop legacy support.
if root.manifest().is_legacy() { if root.manifest.is_legacy() {
let legacy = super::legacy::patch::Patch::from_history(history, repo)?; let legacy = super::legacy::patch::Patch::from_history(history, repo)?;
let patch = legacy.into(); let patch = legacy.into();
@ -1379,7 +1379,7 @@ where
revision: RevisionId, revision: RevisionId,
signer: &G, signer: &G,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error> {
if revision == RevisionId::from(self.id) { if revision == RevisionId::from(*self.id) {
return Err(Error::RootRevision(revision)); return Err(Error::RootRevision(revision));
} }
self.transaction("Redact revision", signer, |tx| tx.redact(revision)) self.transaction("Redact revision", signer, |tx| tx.redact(revision))
@ -2223,7 +2223,7 @@ mod test {
assert_eq!(patch.revisions().count(), 2); assert_eq!(patch.revisions().count(), 2);
patch.redact(revision_id, &alice.signer).unwrap(); patch.redact(revision_id, &alice.signer).unwrap();
assert_eq!(patch.latest().0, &RevisionId::from(patch_id)); assert_eq!(patch.latest().0, &RevisionId::from(*patch_id));
assert_eq!(patch.revisions().count(), 1); assert_eq!(patch.revisions().count(), 1);
// The patch's root must always exist. // The patch's root must always exist.

View File

@ -2,6 +2,7 @@ use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use nonempty::NonEmpty; use nonempty::NonEmpty;
use radicle_crypto::ssh::ExtendedSignature;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::cob::op::Op; use crate::cob::op::Op;
@ -58,18 +59,24 @@ where
{ {
pub fn new<G: Signer>(action: &T::Action, time: Timestamp, signer: &G) -> HistoryBuilder<T> { pub fn new<G: Signer>(action: &T::Action, time: Timestamp, signer: &G) -> HistoryBuilder<T> {
let resource = arbitrary::oid(); let resource = arbitrary::oid();
let revision = arbitrary::oid();
let (data, root) = encoded::<T, _>(action, time, [], signer); let (data, root) = encoded::<T, _>(action, time, [], signer);
let manifest = Manifest::new(T::type_name().clone(), Version::default()); let manifest = Manifest::new(T::type_name().clone(), Version::default());
let signature = signer.sign(data.as_slice());
let signature = ExtendedSignature::new(*signer.public_key(), signature);
let change = Entry {
id: root,
signature,
resource,
contents: NonEmpty::new(data),
timestamp: time.as_secs(),
revision,
parents: vec![],
manifest,
};
Self { Self {
history: History::new_from_root( history: History::new_from_root(change),
root,
*signer.public_key(),
resource,
NonEmpty::new(data),
time.as_secs(),
manifest,
),
time, time,
resource, resource,
witness: PhantomData, witness: PhantomData,
@ -87,18 +94,23 @@ where
pub fn commit<G: Signer>(&mut self, action: &T::Action, signer: &G) -> git::ext::Oid { pub fn commit<G: Signer>(&mut self, action: &T::Action, signer: &G) -> git::ext::Oid {
let timestamp = self.time; let timestamp = self.time;
let tips = self.tips(); let tips = self.tips();
let revision = arbitrary::oid();
let (data, oid) = encoded::<T, _>(action, timestamp, tips, signer); let (data, oid) = encoded::<T, _>(action, timestamp, tips, signer);
let manifest = Manifest::new(T::type_name().clone(), Version::default()); let manifest = Manifest::new(T::type_name().clone(), Version::default());
let signature = signer.sign(data.as_slice());
self.history.extend( let signature = ExtendedSignature::new(*signer.public_key(), signature);
oid, let change = Entry {
*signer.public_key(), id: oid,
self.resource, signature,
NonEmpty::new(data), resource: self.resource,
vec![], contents: NonEmpty::new(data),
timestamp.as_secs(), timestamp: timestamp.as_secs(),
revision,
parents: vec![],
manifest, manifest,
); };
self.history.extend(change);
oid oid
} }
} }

View File

@ -58,14 +58,14 @@ impl change::Storage for Repository {
parents: Vec<Self::Parent>, parents: Vec<Self::Parent>,
signer: &Signer, signer: &Signer,
spec: change::Template<Self::ObjectId>, spec: change::Template<Self::ObjectId>,
) -> Result<cob::Change, Self::StoreError> ) -> Result<cob::Entry, Self::StoreError>
where where
Signer: crypto::Signer, Signer: crypto::Signer,
{ {
self.backend.store(authority, parents, signer, spec) self.backend.store(authority, parents, signer, spec)
} }
fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> { fn load(&self, id: Self::ObjectId) -> Result<cob::Entry, Self::LoadError> {
self.backend.load(id) self.backend.load(id)
} }
@ -139,7 +139,7 @@ impl cob::object::Storage for Repository {
identifier: &Self::Identifier, identifier: &Self::Identifier,
typename: &cob::TypeName, typename: &cob::TypeName,
object_id: &cob::ObjectId, object_id: &cob::ObjectId,
change: &cob::Change, change: &cob::Entry,
) -> Result<(), Self::UpdateError> { ) -> Result<(), Self::UpdateError> {
self.backend.reference( self.backend.reference(
git::refs::storage::cob(identifier, typename, object_id).as_str(), git::refs::storage::cob(identifier, typename, object_id).as_str(),
@ -202,14 +202,14 @@ impl<'a> change::Storage for DraftStore<'a> {
parents: Vec<Self::Parent>, parents: Vec<Self::Parent>,
signer: &Signer, signer: &Signer,
spec: change::Template<Self::ObjectId>, spec: change::Template<Self::ObjectId>,
) -> Result<cob::Change, Self::StoreError> ) -> Result<cob::Entry, Self::StoreError>
where where
Signer: crypto::Signer, Signer: crypto::Signer,
{ {
self.repo.backend.store(authority, parents, signer, spec) self.repo.backend.store(authority, parents, signer, spec)
} }
fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> { fn load(&self, id: Self::ObjectId) -> Result<cob::Entry, Self::LoadError> {
self.repo.backend.load(id) self.repo.backend.load(id)
} }
@ -387,7 +387,7 @@ impl<'a> cob::object::Storage for DraftStore<'a> {
identifier: &Self::Identifier, identifier: &Self::Identifier,
typename: &cob::TypeName, typename: &cob::TypeName,
object_id: &cob::ObjectId, object_id: &cob::ObjectId,
change: &cob::Change, change: &cob::Entry,
) -> Result<(), Self::UpdateError> { ) -> Result<(), Self::UpdateError> {
self.repo.backend.reference( self.repo.backend.reference(
git::refs::storage::draft::cob(identifier, typename, object_id).as_str(), git::refs::storage::draft::cob(identifier, typename, object_id).as_str(),

View File

@ -28,7 +28,7 @@ pub fn oid() -> storage::Oid {
} }
pub fn entry_id() -> cob::EntryId { pub fn entry_id() -> cob::EntryId {
self::oid().into() self::oid()
} }
pub fn refstring(len: usize) -> git::RefString { pub fn refstring(len: usize) -> git::RefString {