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(());
}
let root = patch.id.into();
let root = *patch.id;
let target = patch.target();
patch.transaction("Edit", &signer, |tx| {

View File

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

View File

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

View File

@ -3,11 +3,11 @@
use git_ext::Oid;
pub mod store;
pub use store::{Storage, Template};
pub use store::{Contents, EntryId, Storage, Template, Timestamp};
use crate::signatures::ExtendedSignature;
/// 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)
/// 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 serde::{Deserialize, Serialize};
use crate::{
history::{Contents, Timestamp},
signatures, TypeName,
};
use crate::{signatures, TypeName};
/// Change storage.
/// Change entry storage.
pub trait Storage {
type StoreError: Error + Send + Sync + 'static;
type LoadError: Error + Send + Sync + 'static;
@ -20,7 +17,7 @@ pub trait Storage {
type Parent;
type Signatures;
/// Store a new change.
/// Store a new change entry.
#[allow(clippy::type_complexity)]
fn store<G>(
&self,
@ -28,16 +25,16 @@ pub trait Storage {
parents: Vec<Self::Parent>,
signer: &G,
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
G: crypto::Signer;
/// Load a change.
/// Load a change entry.
#[allow(clippy::type_complexity)]
fn load(
&self,
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.
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError>;
@ -52,8 +49,18 @@ pub struct Template<Id> {
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)]
pub struct Change<Resource, Id, Signature> {
pub struct Entry<Resource, Id, Signature> {
/// The content address of the `Change` itself.
pub id: Id,
/// The content address of the tree of the `Change`.
@ -75,7 +82,7 @@ pub struct Change<Resource, Id, Signature> {
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
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 {
&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
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
Id: AsRef<[u8]>,
{
pub fn valid_signatures(&self) -> bool {
self.signature.verify(self.revision.as_ref())
}
pub fn author(&self) -> &crypto::PublicKey {
&self.signature.key
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]

View File

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

View File

@ -3,13 +3,9 @@
use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow};
use git_ext::Oid;
use radicle_crypto::PublicKey;
use radicle_dag::Dag;
pub mod entry;
pub use entry::{Contents, Entry, EntryId, Timestamp};
use crate::Manifest;
pub use crate::change::{Contents, Entry, EntryId, Timestamp};
/// The DAG of changes making up the history of a collaborative object.
#[derive(Clone, Debug)]
@ -36,32 +32,11 @@ impl History {
Self { root, graph }
}
pub fn new_from_root<Id>(
id: Id,
actor: PublicKey,
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,
};
/// Create a new history from a root entry.
pub fn new_from_root(root: Entry) -> Self {
let id = *root.id();
Self {
root: id,
graph: Dag::root(id, root),
}
Self::new(id, Dag::root(id, root))
}
/// Get the current history timestamp.
@ -69,17 +44,14 @@ impl History {
pub fn timestamp(&self) -> Timestamp {
self.graph
.tips()
.map(|(_, n)| n.timestamp())
.map(|(_, n)| n.timestamp)
.max()
.unwrap_or_default()
}
/// Get all the tips of the graph.
pub fn tips(&self) -> BTreeSet<Oid> {
self.graph
.tips()
.map(|(_, entry)| (*entry.id()).into())
.collect()
self.graph.tips().map(|(_, entry)| *entry.id()).collect()
}
/// A topological (parents before children) traversal of the dependency
@ -109,34 +81,14 @@ impl History {
}
/// Extend this history with a new entry.
pub fn extend<Id>(
&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>,
{
pub fn extend(&mut self, change: Entry) {
let tips = self.tips();
let new_id = new_id.into();
let new_entry = Entry::new(
new_id,
new_actor,
new_resource,
new_contents,
new_parents,
new_timestamp,
manifest,
);
let id = *change.id();
self.graph.node(new_id, new_entry);
self.graph.node(id, change);
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
//! 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
//!
//! The basic types that are found in `radicle-cob` are:
@ -55,15 +52,14 @@
//! automerge document and deserialize into an application defined
//! 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
//! 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
//! point that the `actor`'s change can be filtered out from the
//! final product of the traversal.
//!
//! [automerge]: https://automerge.org
//! [RFC-0662]: https://github.com/radicle-dev/radicle-link/blob/master/docs/rfc/0662-collaborative-objects.adoc
#[cfg(test)]
extern crate qcheck;
@ -81,11 +77,11 @@ mod change_graph;
mod trailers;
pub mod change;
pub use change::store::{Embed, Manifest, Version};
pub use change::Change;
pub use change::store::{Contents, Embed, Manifest, Version};
pub use change::Entry;
pub mod history;
pub use history::{Contents, Entry, History};
pub use history::History;
pub mod signatures;
use signatures::ExtendedSignature;

View File

@ -36,7 +36,7 @@ impl Create {
/// Create a new [`CollaborativeObject`].
///
/// 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.
///
/// The `signer` is expected to be a cryptographic signing key. This
@ -74,14 +74,7 @@ where
.update(identifier, &type_name, &object_id, &init_change)
.map_err(|err| error::Create::Refs { err: Box::new(err) })?;
let history = History::new_from_root(
*init_change.id(),
init_change.signature.key,
resource,
init_change.contents,
init_change.timestamp,
init_change.manifest,
);
let history = History::new_from_root(init_change);
Ok(CollaborativeObject {
manifest: Manifest::new(type_name, version),

View File

@ -7,7 +7,7 @@ use super::error;
/// Get a [`CollaborativeObject`], if it exists.
///
/// 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.
///
/// 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
///
/// 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.
///
/// 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`].
///
/// 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.
///
/// The `typename` is the type of objects to be listed.

View File

@ -7,7 +7,7 @@ use super::error;
/// Remove a [`crate::CollaborativeObject`].
///
/// 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.
///
/// 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`].
///
/// 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.
///
/// The `signer` is expected to be a cryptographic signing key. This
@ -99,21 +99,14 @@ where
.update(identifier, typename, &object_id, &change)
.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(
change.id,
change.signature.key,
change.resource,
change.contents,
parents.clone(),
change.timestamp,
change.manifest,
);
object.history.extend(change);
Ok(Updated {
object,
head: change.id,
head,
parents,
})
}

View File

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

View File

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

View File

@ -129,7 +129,7 @@ impl store::FromHistory for Issue {
let root = history.root();
// 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 issue = legacy.into();
@ -673,10 +673,10 @@ mod test {
let mut issue_bob = bob_issues.get_mut(&id).unwrap();
issue_bob
.comment("Bob's reply", id.into(), vec![], &t.bob.signer)
.comment("Bob's reply", *id, vec![], &t.bob.signer)
.unwrap();
issue_alice
.comment("Alice's reply", id.into(), vec![], &t.alice.signer)
.comment("Alice's reply", *id, vec![], &t.alice.signer)
.unwrap();
assert_eq!(issue_bob.comments().count(), 2);
@ -704,7 +704,7 @@ mod test {
t.eve.repo.fetch(&t.alice);
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();
t.bob.repo.fetch(&t.eve);
@ -721,7 +721,7 @@ mod test {
let (first, _) = issue_bob.comments().next().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);
}
@ -1212,7 +1212,7 @@ mod test {
issue
.comment(
"Here's a binary file",
issue.id.into(),
*issue.id,
[embed3.clone()],
&node.signer,
)

View File

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

View File

@ -469,7 +469,7 @@ impl store::FromHistory for Patch {
let root = history.root();
// 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 patch = legacy.into();
@ -1379,7 +1379,7 @@ where
revision: RevisionId,
signer: &G,
) -> Result<EntryId, Error> {
if revision == RevisionId::from(self.id) {
if revision == RevisionId::from(*self.id) {
return Err(Error::RootRevision(revision));
}
self.transaction("Redact revision", signer, |tx| tx.redact(revision))
@ -2223,7 +2223,7 @@ mod test {
assert_eq!(patch.revisions().count(), 2);
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);
// The patch's root must always exist.

View File

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

View File

@ -58,14 +58,14 @@ impl change::Storage for Repository {
parents: Vec<Self::Parent>,
signer: &Signer,
spec: change::Template<Self::ObjectId>,
) -> Result<cob::Change, Self::StoreError>
) -> Result<cob::Entry, Self::StoreError>
where
Signer: crypto::Signer,
{
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)
}
@ -139,7 +139,7 @@ impl cob::object::Storage for Repository {
identifier: &Self::Identifier,
typename: &cob::TypeName,
object_id: &cob::ObjectId,
change: &cob::Change,
change: &cob::Entry,
) -> Result<(), Self::UpdateError> {
self.backend.reference(
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>,
signer: &Signer,
spec: change::Template<Self::ObjectId>,
) -> Result<cob::Change, Self::StoreError>
) -> Result<cob::Entry, Self::StoreError>
where
Signer: crypto::Signer,
{
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)
}
@ -387,7 +387,7 @@ impl<'a> cob::object::Storage for DraftStore<'a> {
identifier: &Self::Identifier,
typename: &cob::TypeName,
object_id: &cob::ObjectId,
change: &cob::Change,
change: &cob::Entry,
) -> Result<(), Self::UpdateError> {
self.repo.backend.reference(
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 {
self::oid().into()
self::oid()
}
pub fn refstring(len: usize) -> git::RefString {