cob: Remove `author` from `Entry`

It was no longer used.

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-11-30 12:18:52 +01:00
parent 5a47023ac0
commit dd7052926a
No known key found for this signature in database
20 changed files with 58 additions and 322 deletions

View File

@ -28,7 +28,6 @@ pub mod error {
use thiserror::Error; use thiserror::Error;
use crate::signatures::error::Signatures; use crate::signatures::error::Signatures;
use crate::trailers;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Create { pub enum Create {
@ -69,8 +68,6 @@ pub mod error {
#[error("the 'change' found at '{0}' has more than one signature")] #[error("the 'change' found at '{0}' has more than one signature")]
TooManySignatures(Oid), TooManySignatures(Oid),
#[error(transparent)] #[error(transparent)]
AuthorTrailer(#[from] trailers::error::InvalidAuthorTrailer),
#[error(transparent)]
ResourceTrailer(#[from] super::trailers::error::InvalidResourceTrailer), ResourceTrailer(#[from] super::trailers::error::InvalidResourceTrailer),
#[error("non utf-8 characters in commit message")] #[error("non utf-8 characters in commit message")]
Utf8(#[from] FromUtf8Error), Utf8(#[from] FromUtf8Error),
@ -84,13 +81,11 @@ impl change::Storage for git2::Repository {
type LoadError = error::Load; type LoadError = error::Load;
type ObjectId = Oid; type ObjectId = Oid;
type Author = Oid;
type Resource = Oid; type Resource = Oid;
type Signatures = Signature; type Signatures = Signature;
fn create<Signer>( fn create<Signer>(
&self, &self,
author: Option<Self::Author>,
resource: Self::Resource, resource: Self::Resource,
signer: &Signer, signer: &Signer,
spec: store::Create<Self::ObjectId>, spec: store::Create<Self::ObjectId>,
@ -119,20 +114,11 @@ impl change::Storage for git2::Repository {
Signature::from((*key, sig)) Signature::from((*key, sig))
}; };
let id = write_commit( let id = write_commit(self, resource, tips, message, signature.clone(), tree)?;
self,
author,
resource,
tips,
message,
signature.clone(),
tree,
)?;
Ok(Change { Ok(Change {
id, id,
revision: revision.into(), revision: revision.into(),
signature, signature,
author,
resource, resource,
manifest, manifest,
contents, contents,
@ -141,7 +127,7 @@ impl change::Storage for git2::Repository {
fn load(&self, id: Self::ObjectId) -> Result<Change, Self::LoadError> { fn load(&self, id: Self::ObjectId) -> Result<Change, Self::LoadError> {
let commit = Commit::read(self, id.into())?; let commit = Commit::read(self, id.into())?;
let (author, resource) = parse_trailers(commit.trailers())?; let resource = parse_resource_trailer(commit.trailers())?;
let mut signatures = Signatures::try_from(&commit)? let mut signatures = Signatures::try_from(&commit)?
.into_iter() .into_iter()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -160,7 +146,6 @@ impl change::Storage for git2::Repository {
id, id,
revision: tree.id().into(), revision: tree.id().into(),
signature: signature.into(), signature: signature.into(),
author,
resource, resource,
manifest, manifest,
contents, contents,
@ -168,26 +153,21 @@ impl change::Storage for git2::Repository {
} }
} }
fn parse_trailers<'a>( fn parse_resource_trailer<'a>(
mut trailers: impl Iterator<Item = &'a OwnedTrailer>, trailers: impl Iterator<Item = &'a OwnedTrailer>,
) -> Result<(Option<Oid>, Oid), error::Load> { ) -> Result<Oid, error::Load> {
let (author, resource) = trailers.try_fold((None, None), |(author, resource), trailer| { for trailer in trailers {
match trailers::AuthorCommitTrailer::try_from(trailer) { match trailers::ResourceCommitTrailer::try_from(trailer) {
Ok(trailer) => Ok((Some(trailer.oid().into()), resource)), Err(trailers::error::InvalidResourceTrailer::WrongToken) => {
Err(err) => match err { continue;
trailers::error::InvalidAuthorTrailer::NoTrailer }
| trailers::error::InvalidAuthorTrailer::NoValue => Ok((author, resource)), Err(err) => return Err(err.into()),
trailers::error::InvalidAuthorTrailer::WrongToken => { Ok(resource) => return Ok(resource.oid().into()),
let resource = trailers::ResourceCommitTrailer::try_from(trailer)?;
Ok((author, Some(resource.oid().into())))
}
err => Err(error::Load::from(err)),
},
} }
})?; }
let resource = resource Err(error::Load::from(
.ok_or_else(|| error::Load::from(trailers::error::InvalidResourceTrailer::NoTrailer))?; trailers::error::InvalidResourceTrailer::NoTrailer,
Ok((author, resource)) ))
} }
fn load_manifest( fn load_manifest(
@ -223,7 +203,6 @@ fn load_contents(
fn write_commit<O>( fn write_commit<O>(
repo: &git2::Repository, repo: &git2::Repository,
author: Option<O>,
resource: O, resource: O,
tips: Vec<O>, tips: Vec<O>,
message: String, message: String,
@ -233,16 +212,12 @@ fn write_commit<O>(
where where
O: AsRef<git2::Oid>, O: AsRef<git2::Oid>,
{ {
let author = author.map(|author| *author.as_ref());
let resource = *resource.as_ref(); let resource = *resource.as_ref();
let mut parents = tips.iter().map(|o| *o.as_ref()).collect::<Vec<_>>(); let mut parents = tips.iter().map(|o| *o.as_ref()).collect::<Vec<_>>();
parents.push(resource); parents.push(resource);
parents.extend(author);
let mut trailers: Vec<OwnedTrailer> = let trailers: Vec<OwnedTrailer> = vec![trailers::ResourceCommitTrailer::from(resource).into()];
vec![trailers::ResourceCommitTrailer::from(resource).into()];
trailers.extend(author.map(|author| trailers::AuthorCommitTrailer::from(author).into()));
{ {
let author = repo.signature()?; let author = repo.signature()?;

View File

@ -13,4 +13,4 @@ use crate::signatures::Signature;
/// 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, Oid, Signature>; pub type Change = store::Change<Oid, Oid, Signature>;

View File

@ -14,21 +14,16 @@ pub trait Storage {
type LoadError: Error + Send + Sync + 'static; type LoadError: Error + Send + Sync + 'static;
type ObjectId; type ObjectId;
type Author;
type Resource; type Resource;
type Signatures; type Signatures;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn create<Signer>( fn create<Signer>(
&self, &self,
author: Option<Self::Author>,
authority: Self::Resource, authority: Self::Resource,
signer: &Signer, signer: &Signer,
spec: Create<Self::ObjectId>, spec: Create<Self::ObjectId>,
) -> Result< ) -> Result<Change<Self::Resource, Self::ObjectId, Self::Signatures>, Self::CreateError>
Change<Self::Author, Self::Resource, Self::ObjectId, Self::Signatures>,
Self::CreateError,
>
where where
Signer: crypto::Signer; Signer: crypto::Signer;
@ -36,10 +31,7 @@ pub trait Storage {
fn load( fn load(
&self, &self,
id: Self::ObjectId, id: Self::ObjectId,
) -> Result< ) -> Result<Change<Self::Resource, Self::ObjectId, Self::Signatures>, Self::LoadError>;
Change<Self::Author, Self::Resource, Self::ObjectId, Self::Signatures>,
Self::LoadError,
>;
} }
pub struct Create<Id> { pub struct Create<Id> {
@ -51,7 +43,7 @@ pub struct Create<Id> {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Change<Author, Resource, Id, Signature> { pub struct Change<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`.
@ -59,9 +51,6 @@ pub struct Change<Author, Resource, Id, Signature> {
/// The cryptographic signature(s) and their public keys of the /// The cryptographic signature(s) and their public keys of the
/// authors. /// authors.
pub signature: Signature, pub signature: Signature,
/// The author of this change. The `Author` is expected to be a
/// content address to look up the identity of the author.
pub author: Option<Author>,
/// The parent resource that this change lives under. For example, /// The parent resource that this change lives under. For example,
/// this change could be for a patch of a project. /// this change could be for a patch of a project.
pub resource: Resource, pub resource: Resource,
@ -72,7 +61,7 @@ pub struct Change<Author, Resource, Id, Signature> {
pub contents: Contents, pub contents: Contents,
} }
impl<Author, Resource, Id, S> fmt::Display for Change<Author, Resource, Id, S> impl<Resource, Id, S> fmt::Display for Change<Resource, Id, S>
where where
Id: fmt::Display, Id: fmt::Display,
{ {
@ -81,15 +70,11 @@ where
} }
} }
impl<Author, Resource, Id, Signatures> Change<Author, Resource, Id, Signatures> { impl<Resource, Id, Signatures> Change<Resource, Id, Signatures> {
pub fn id(&self) -> &Id { pub fn id(&self) -> &Id {
&self.id &self.id
} }
pub fn author(&self) -> &Option<Author> {
&self.author
}
pub fn typename(&self) -> &TypeName { pub fn typename(&self) -> &TypeName {
&self.manifest.typename &self.manifest.typename
} }
@ -103,7 +88,7 @@ impl<Author, Resource, Id, Signatures> Change<Author, Resource, Id, Signatures>
} }
} }
impl<A, R, Id> Change<A, R, Id, signatures::Signatures> impl<R, Id> Change<R, Id, signatures::Signatures>
where where
Id: AsRef<[u8]>, Id: AsRef<[u8]>,
{ {
@ -114,7 +99,7 @@ where
} }
} }
impl<A, R, Id> Change<A, R, Id, signatures::Signature> impl<R, Id> Change<R, Id, signatures::Signature>
where where
Id: AsRef<[u8]>, Id: AsRef<[u8]>,
{ {

View File

@ -37,7 +37,7 @@ impl ChangeGraph {
oid: &ObjectId, oid: &ObjectId,
) -> Option<ChangeGraph> ) -> Option<ChangeGraph>
where where
S: change::Storage<ObjectId = Oid, Author = Oid, Resource = Oid, Signatures = Signature>, S: change::Storage<ObjectId = Oid, Resource = Oid, Signatures = Signature>,
{ {
log::info!("loading object '{}' '{}'", typename, oid); log::info!("loading object '{}' '{}'", typename, oid);
let mut builder = GraphBuilder::default(); let mut builder = GraphBuilder::default();
@ -170,7 +170,6 @@ impl GraphBuilder {
commit: object::Commit, commit: object::Commit,
change: Change, change: Change,
) -> impl Iterator<Item = (object::Commit, Oid)> + '_ { ) -> impl Iterator<Item = (object::Commit, Oid)> + '_ {
let author_commit = *change.author();
let resource_commit = *change.resource(); let resource_commit = *change.resource();
let commit_id = commit.id; let commit_id = commit.id;
if let Entry::Vacant(e) = self.node_indices.entry(commit_id) { if let Entry::Vacant(e) = self.node_indices.entry(commit_id) {
@ -178,10 +177,7 @@ impl GraphBuilder {
e.insert(ix); e.insert(ix);
} }
commit.parents.into_iter().filter_map(move |parent| { commit.parents.into_iter().filter_map(move |parent| {
if Some(parent.id) != author_commit if parent.id != resource_commit && !self.has_edge(parent.id, commit_id) {
&& parent.id != resource_commit
&& !self.has_edge(parent.id, commit_id)
{
Some((parent, commit_id)) Some((parent, commit_id))
} else { } else {
None None

View File

@ -67,7 +67,6 @@ fn evaluate_change(
Ok(history::Entry::new( Ok(history::Entry::new(
*change.id(), *change.id(),
change.signature.key, change.signature.key,
*change.author(),
change.resource, change.resource,
child_commits.iter().cloned(), child_commits.iter().cloned(),
change.contents().clone(), change.contents().clone(),

View File

@ -42,7 +42,6 @@ impl History {
pub(crate) fn new_from_root<Id>( pub(crate) fn new_from_root<Id>(
id: Id, id: Id,
actor: PublicKey, actor: PublicKey,
author: Option<Oid>,
resource: Oid, resource: Oid,
contents: Contents, contents: Contents,
) -> Self ) -> Self
@ -53,7 +52,6 @@ impl History {
let root_entry = Entry { let root_entry = Entry {
id, id,
actor, actor,
author,
resource, resource,
children: vec![], children: vec![],
contents, contents,
@ -120,7 +118,6 @@ impl History {
&mut self, &mut self,
new_id: Id, new_id: Id,
new_actor: PublicKey, new_actor: PublicKey,
new_author: Option<Oid>,
new_resource: Oid, new_resource: Oid,
new_contents: Contents, new_contents: Contents,
) where ) where
@ -131,7 +128,6 @@ impl History {
let new_entry = Entry::new( let new_entry = Entry::new(
new_id, new_id,
new_actor, new_actor,
new_author,
new_resource, new_resource,
std::iter::empty::<git2::Oid>(), std::iter::empty::<git2::Oid>(),
new_contents, new_contents,

View File

@ -45,9 +45,6 @@ pub struct Entry {
pub(super) id: EntryId, pub(super) id: EntryId,
/// The actor that authored this entry. /// The actor that authored this entry.
pub(super) actor: PublicKey, pub(super) actor: PublicKey,
/// The content-address for this entry's author.
/// TODO: This shouldn't be here?
pub(super) author: Option<Oid>,
/// The content-address for the resource this entry lives under. /// The content-address for the resource this entry lives under.
pub(super) resource: Oid, pub(super) resource: Oid,
/// The child entries for this entry. /// The child entries for this entry.
@ -60,7 +57,6 @@ impl Entry {
pub fn new<Id1, Id2, ChildIds>( pub fn new<Id1, Id2, ChildIds>(
id: Id1, id: Id1,
actor: PublicKey, actor: PublicKey,
author: Option<Oid>,
resource: Oid, resource: Oid,
children: ChildIds, children: ChildIds,
contents: Contents, contents: Contents,
@ -73,7 +69,6 @@ impl Entry {
Self { Self {
id: id.into(), id: id.into(),
actor, actor,
author,
resource, resource,
children: children.into_iter().map(|id| id.into()).collect(), children: children.into_iter().map(|id| id.into()).collect(),
contents, contents,
@ -95,11 +90,6 @@ impl Entry {
&self.actor &self.actor
} }
/// The `Oid` of the author that made this change.
pub fn author(&self) -> Option<&Oid> {
self.author.as_ref()
}
/// The contents of this change /// The contents of this change
pub fn contents(&self) -> &Contents { pub fn contents(&self) -> &Contents {
&self.contents &self.contents

View File

@ -8,17 +8,10 @@ use git_ext::Oid;
/// An [`Identity`] represents a content addressed identity /// An [`Identity`] represents a content addressed identity
/// (i.e. expected to be stored in a git backend). /// (i.e. expected to be stored in a git backend).
/// ///
/// It should have: /// It should have a unique, stable, content addressable identifier.
/// * A delegate system
/// * A content addressable identifier
/// * A unique, stable identifier
pub trait Identity { pub trait Identity {
type Identifier; type Identifier;
/// Confirm that the given [`crypto::PublicKey`] is a delegate for
/// the identity.
fn is_delegate(&self, delegation: &crypto::PublicKey) -> bool;
/// Provide the content address for the given identity. This is /// Provide the content address for the given identity. This is
/// expected to be the latest address for the identity at the time /// expected to be the latest address for the identity at the time
/// of use. /// of use.

View File

@ -61,11 +61,11 @@
//! 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::author`] //! This traversal is also the point at which the [`Entry::actor`]
//! 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 `author`'s change may be //! filtering logic. For example, a specific `actor`'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 `author`'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
@ -132,7 +132,6 @@ where
CreateError = git::change::error::Create, CreateError = git::change::error::Create,
LoadError = git::change::error::Load, LoadError = git::change::error::Load,
ObjectId = git_ext::Oid, ObjectId = git_ext::Oid,
Author = git_ext::Oid,
Resource = git_ext::Oid, Resource = git_ext::Oid,
Signatures = Signature, Signatures = Signature,
>, >,

View File

@ -8,9 +8,7 @@ use crate::Store;
use super::*; use super::*;
/// The metadata required for creating a new [`CollaborativeObject`]. /// The metadata required for creating a new [`CollaborativeObject`].
pub struct Create<Author> { pub struct Create {
/// 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. /// The type of history that will be used for this object.
pub history_type: String, pub history_type: String,
/// The CRDT history to initialize this object with. /// The CRDT history to initialize this object with.
@ -21,7 +19,7 @@ pub struct Create<Author> {
pub message: String, pub message: String,
} }
impl<Author> Create<Author> { impl Create {
fn create_spec(&self) -> change::Create<git_ext::Oid> { fn create_spec(&self) -> change::Create<git_ext::Oid> {
change::Create { change::Create {
typename: self.typename.clone(), typename: self.typename.clone(),
@ -51,46 +49,31 @@ impl<Author> Create<Author> {
/// ///
/// The `args` are the metadata for this [`CollaborativeObject`]. See /// The `args` are the metadata for this [`CollaborativeObject`]. See
/// [`Create`] for further information. /// [`Create`] for further information.
pub fn create<S, G, Author, Resource>( pub fn create<S, G, Resource>(
storage: &S, storage: &S,
signer: &G, signer: &G,
resource: &Resource, resource: &Resource,
identifier: &S::Identifier, identifier: &S::Identifier,
args: Create<Author>, args: Create,
) -> Result<CollaborativeObject, error::Create> ) -> Result<CollaborativeObject, error::Create>
where where
S: Store, S: Store,
G: crypto::Signer, G: crypto::Signer,
Author: Identity,
Author::Identifier: Clone + PartialEq,
Resource: Identity, Resource: Identity,
{ {
let Create { let Create {
author,
ref contents, ref contents,
ref typename, ref typename,
.. ..
} = &args; } = &args;
let content = match author {
None => None,
Some(author) => {
if !author.is_delegate(signer.public_key()) {
return Err(error::Create::SignerIsNotAuthor);
} else {
Some(author.content_id())
}
}
};
let init_change = storage let init_change = storage
.create(content, resource.content_id(), signer, args.create_spec()) .create(resource.content_id(), signer, args.create_spec())
.map_err(error::Create::from)?; .map_err(error::Create::from)?;
let history = History::new_from_root( let history = History::new_from_root(
*init_change.id(), *init_change.id(),
init_change.signature.key, init_change.signature.key,
content,
resource.content_id(), resource.content_id(),
contents.clone(), contents.clone(),
); );

View File

@ -11,9 +11,7 @@ use crate::{
use super::error; use super::error;
/// The data required to update an object /// The data required to update an object
pub struct Update<Author> { pub struct Update {
/// 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. /// The type of history that will be used for this object.
pub history_type: String, pub history_type: String,
/// The CRDT changes to add to the object. /// The CRDT changes to add to the object.
@ -44,22 +42,19 @@ pub struct Update<Author> {
/// ///
/// The `args` are the metadata for this [`CollaborativeObject`] /// The `args` are the metadata for this [`CollaborativeObject`]
/// udpate. See [`Update`] for further information. /// udpate. See [`Update`] for further information.
pub fn update<S, G, Resource, Author>( pub fn update<S, G, Resource>(
storage: &S, storage: &S,
signer: &G, signer: &G,
resource: &Resource, resource: &Resource,
identifier: &S::Identifier, identifier: &S::Identifier,
args: Update<Author>, args: Update,
) -> Result<CollaborativeObject, error::Update> ) -> Result<CollaborativeObject, error::Update>
where where
S: Store, S: Store,
G: crypto::Signer, G: crypto::Signer,
Author: Identity,
Author::Identifier: Clone + PartialEq,
Resource: Identity, Resource: Identity,
{ {
let Update { let Update {
author,
ref typename, ref typename,
object_id, object_id,
history_type, history_type,
@ -67,18 +62,6 @@ where
message, message,
} = args; } = args;
let author = match author {
None => None,
Some(author) => {
// TODO: Remove?
if !author.is_delegate(signer.public_key()) {
return Err(error::Update::SignerIsNotAuthor);
} else {
Some(author.content_id())
}
}
};
let existing_refs = storage let existing_refs = storage
.objects(typename, &object_id) .objects(typename, &object_id)
.map_err(|err| error::Update::Refs { err: Box::new(err) })?; .map_err(|err| error::Update::Refs { err: Box::new(err) })?;
@ -88,7 +71,6 @@ where
.ok_or(error::Update::NoSuchObject)?; .ok_or(error::Update::NoSuchObject)?;
let change = storage.create( let change = storage.create(
author,
resource.content_id(), resource.content_id(),
signer, signer,
change::Create { change::Create {
@ -99,13 +81,9 @@ where
message, message,
}, },
)?; )?;
object.history.extend( object
change.id, .history
change.signature.key, .extend(change.id, change.signature.key, change.resource, changes);
author,
change.resource,
changes,
);
storage storage
.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) })?;

View File

@ -53,52 +53,14 @@ impl Person {
}) })
} }
pub fn key(&self) -> crypto::PublicKey {
self.payload.key
}
pub fn name(&self) -> &Name { pub fn name(&self) -> &Name {
&self.payload.name &self.payload.name
} }
pub fn find_by_oid(
repo: &git2::Repository,
id: Oid,
) -> Result<Option<Person>, storage::error::Identity> {
match repo.find_commit(id.into()) {
Ok(commit) => from_commit(repo, commit),
Err(err) if err.code() == git2::ErrorCode::NotFound => Ok(None),
Err(err) => Err(err.into()),
}
}
}
fn from_commit(
repo: &git2::Repository,
commit: git2::Commit,
) -> Result<Option<Person>, storage::error::Identity> {
let tree = commit.tree()?;
let entry = tree
.get_name("identity")
.ok_or_else(|| storage::error::Identity::NotFound(tree.id().into()))?;
let blob = match entry.to_object(repo)?.into_blob() {
Ok(blob) => blob,
Err(other) => return Err(storage::error::Identity::NotBlob(other.kind())),
};
let payload = serde_json::de::from_slice(blob.content())?;
Ok(Some(Person {
payload,
content_id: commit.id().into(),
}))
} }
impl Identity for Person { impl Identity for Person {
type Identifier = Urn; type Identifier = Urn;
fn is_delegate(&self, delegation: &crypto::PublicKey) -> bool {
self.key() == *delegation
}
fn content_id(&self) -> Oid { fn content_id(&self) -> Oid {
self.content_id self.content_id
} }

View File

@ -70,56 +70,14 @@ impl Project {
}) })
} }
pub fn delegates(&self) -> &BTreeSet<crypto::PublicKey> {
&self.payload.delegates
}
pub fn delegate_check(&self, person: &test::Person) -> bool {
self.payload.delegates.contains(&person.key())
}
pub fn name(&self) -> &Name { pub fn name(&self) -> &Name {
&self.payload.name &self.payload.name
} }
pub fn find_by_oid(
repo: &git2::Repository,
id: Oid,
) -> Result<Option<Self>, storage::error::Identity> {
match repo.find_commit(id.into()) {
Ok(commit) => from_commit(repo, commit),
Err(err) if err.code() == git2::ErrorCode::NotFound => Ok(None),
Err(err) => Err(err.into()),
}
}
}
fn from_commit(
repo: &git2::Repository,
commit: git2::Commit,
) -> Result<Option<Project>, storage::error::Identity> {
let tree = commit.tree()?;
let entry = tree
.get_name("identity")
.ok_or_else(|| storage::error::Identity::NotFound(tree.id().into()))?;
let blob = match entry.to_object(repo)?.into_blob() {
Ok(blob) => blob,
Err(other) => return Err(storage::error::Identity::NotBlob(other.kind())),
};
let payload = serde_json::de::from_slice(blob.content())?;
Ok(Some(Project {
payload,
content_id: commit.id().into(),
}))
} }
impl Identity for RemoteProject { impl Identity for RemoteProject {
type Identifier = Urn; type Identifier = Urn;
fn is_delegate(&self, delegation: &crypto::PublicKey) -> bool {
self.project.delegates().contains(delegation)
}
fn content_id(&self) -> Oid { fn content_id(&self) -> Oid {
self.project.content_id self.project.content_id
} }

View File

@ -21,10 +21,6 @@ pub mod error {
Json(#[from] serde_json::Error), Json(#[from] serde_json::Error),
#[error(transparent)] #[error(transparent)]
Git(#[from] git2::Error), Git(#[from] git2::Error),
#[error("'identity' was not a blob, found '{0:?}'")]
NotBlob(Option<git2::ObjectType>),
#[error("could not find 'identity' in the tree '{0}'")]
NotFound(git_ext::Oid),
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -67,31 +63,29 @@ impl change::Storage for Storage {
type LoadError = <git2::Repository as change::Storage>::LoadError; type LoadError = <git2::Repository as change::Storage>::LoadError;
type ObjectId = <git2::Repository as change::Storage>::ObjectId; type ObjectId = <git2::Repository as change::Storage>::ObjectId;
type Author = <git2::Repository as change::Storage>::Author;
type Resource = <git2::Repository as change::Storage>::Resource; type Resource = <git2::Repository as change::Storage>::Resource;
type Signatures = <git2::Repository as change::Storage>::Signatures; type Signatures = <git2::Repository as change::Storage>::Signatures;
fn create<Signer>( fn create<Signer>(
&self, &self,
author: Option<Self::Author>,
authority: Self::Resource, authority: Self::Resource,
signer: &Signer, signer: &Signer,
spec: change::Create<Self::ObjectId>, spec: change::Create<Self::ObjectId>,
) -> Result< ) -> Result<
change::store::Change<Self::Author, Self::Resource, Self::ObjectId, Self::Signatures>, change::store::Change<Self::Resource, Self::ObjectId, Self::Signatures>,
Self::CreateError, Self::CreateError,
> >
where where
Signer: crypto::Signer, Signer: crypto::Signer,
{ {
self.as_raw().create(author, authority, signer, spec) self.as_raw().create(authority, signer, spec)
} }
fn load( fn load(
&self, &self,
id: Self::ObjectId, id: Self::ObjectId,
) -> Result< ) -> Result<
change::store::Change<Self::Author, Self::Resource, Self::ObjectId, Self::Signatures>, change::store::Change<Self::Resource, Self::ObjectId, Self::Signatures>,
Self::LoadError, Self::LoadError,
> { > {
self.as_raw().load(id) self.as_raw().load(id)

View File

@ -19,7 +19,7 @@ fn roundtrip() {
let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap(); let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap();
let proj = test::RemoteProject { let proj = test::RemoteProject {
project: proj, project: proj,
person: terry.clone(), person: terry,
}; };
let typename = "xyz.rad.issue".parse::<TypeName>().unwrap(); let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
let cob = create( let cob = create(
@ -28,7 +28,6 @@ fn roundtrip() {
&proj, &proj,
&proj.identifier(), &proj.identifier(),
Create { Create {
author: Some(terry),
history_type: "test".to_string(), history_type: "test".to_string(),
contents: Vec::new(), contents: Vec::new(),
typename: typename.clone(), typename: typename.clone(),
@ -52,7 +51,7 @@ fn list_cobs() {
let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap(); let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap();
let proj = test::RemoteProject { let proj = test::RemoteProject {
project: proj, project: proj,
person: terry.clone(), person: terry,
}; };
let typename = "xyz.rad.issue".parse::<TypeName>().unwrap(); let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
let issue_1 = create( let issue_1 = create(
@ -61,7 +60,6 @@ fn list_cobs() {
&proj, &proj,
&proj.identifier(), &proj.identifier(),
Create { Create {
author: Some(terry.clone()),
history_type: "test".to_string(), history_type: "test".to_string(),
contents: b"issue 1".to_vec(), contents: b"issue 1".to_vec(),
typename: typename.clone(), typename: typename.clone(),
@ -76,7 +74,6 @@ fn list_cobs() {
&proj, &proj,
&proj.identifier(), &proj.identifier(),
Create { Create {
author: Some(terry),
history_type: "test".to_string(), history_type: "test".to_string(),
contents: b"issue 2".to_vec(), contents: b"issue 2".to_vec(),
typename: typename.clone(), typename: typename.clone(),
@ -102,7 +99,7 @@ fn update_cob() {
let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap(); let proj = test::Project::new(&storage, "discworld", *signer.public_key()).unwrap();
let proj = test::RemoteProject { let proj = test::RemoteProject {
project: proj, project: proj,
person: terry.clone(), person: terry,
}; };
let typename = "xyz.rad.issue".parse::<TypeName>().unwrap(); let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
let cob = create( let cob = create(
@ -111,7 +108,6 @@ fn update_cob() {
&proj, &proj,
&proj.identifier(), &proj.identifier(),
Create { Create {
author: Some(terry.clone()),
history_type: "test".to_string(), history_type: "test".to_string(),
contents: Vec::new(), contents: Vec::new(),
typename: typename.clone(), typename: typename.clone(),
@ -130,7 +126,6 @@ fn update_cob() {
&proj, &proj,
&proj.identifier(), &proj.identifier(),
Update { Update {
author: Some(terry),
changes: b"issue 1".to_vec(), changes: b"issue 1".to_vec(),
history_type: "test".to_string(), history_type: "test".to_string(),
object_id: *cob.id(), object_id: *cob.id(),
@ -158,11 +153,11 @@ fn traverse_cobs() {
let proj = test::Project::new(&storage, "discworld", *terry_signer.public_key()).unwrap(); let proj = test::Project::new(&storage, "discworld", *terry_signer.public_key()).unwrap();
let terry_proj = test::RemoteProject { let terry_proj = test::RemoteProject {
project: proj.clone(), project: proj.clone(),
person: terry.clone(), person: terry,
}; };
let neil_proj = test::RemoteProject { let neil_proj = test::RemoteProject {
project: proj, project: proj,
person: neil.clone(), person: neil,
}; };
let typename = "xyz.rad.issue".parse::<TypeName>().unwrap(); let typename = "xyz.rad.issue".parse::<TypeName>().unwrap();
let cob = create( let cob = create(
@ -171,7 +166,6 @@ fn traverse_cobs() {
&terry_proj, &terry_proj,
&terry_proj.identifier(), &terry_proj.identifier(),
Create { Create {
author: Some(terry),
contents: b"issue 1".to_vec(), contents: b"issue 1".to_vec(),
history_type: "test".to_string(), history_type: "test".to_string(),
typename: typename.clone(), typename: typename.clone(),
@ -194,7 +188,6 @@ fn traverse_cobs() {
&neil_proj, &neil_proj,
&neil_proj.identifier(), &neil_proj.identifier(),
Update { Update {
author: Some(neil),
changes: b"issue 2".to_vec(), changes: b"issue 2".to_vec(),
history_type: "test".to_string(), history_type: "test".to_string(),
object_id: *cob.id(), object_id: *cob.id(),
@ -206,16 +199,8 @@ 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 = updated.history().traverse(Vec::new(), |mut acc, entry| { let contents = updated.history().traverse(Vec::new(), |mut acc, entry| {
let author = match entry.author() { if entry.actor() == terry_signer.public_key() {
Some(author) => test::Person::find_by_oid(storage.as_raw(), *author).unwrap(), acc.push(entry.contents().to_vec());
None => None,
};
let project = test::Project::find_by_oid(storage.as_raw(), entry.resource()).unwrap();
if let (Some(author), Some(project)) = (author, project) {
if project.delegate_check(&author) {
acc.push(entry.contents().to_vec())
}
} }
ControlFlow::Continue(acc) ControlFlow::Continue(acc)
}); });

View File

@ -3,20 +3,14 @@
// This file is part of radicle-link, distributed under the GPLv3 with Radicle // This file is part of radicle-link, distributed under the GPLv3 with Radicle
// Linking Exception. For full terms see the included LICENSE file. // Linking Exception. For full terms see the included LICENSE file.
mod author_commit {
super::oid_trailer! {AuthorCommitTrailer, "Rad-Author"}
}
mod resource_identity { mod resource_identity {
super::oid_trailer! {ResourceCommitTrailer, "Rad-Resource"} super::oid_trailer! {ResourceCommitTrailer, "Rad-Resource"}
} }
pub mod error { pub mod error {
pub use super::author_commit::Error as InvalidAuthorTrailer;
pub use super::resource_identity::Error as InvalidResourceTrailer; pub use super::resource_identity::Error as InvalidResourceTrailer;
} }
pub use author_commit::AuthorCommitTrailer;
pub use resource_identity::ResourceCommitTrailer; pub use resource_identity::ResourceCommitTrailer;
/// A macro for generating boilerplate From and TryFrom impls for trailers which /// A macro for generating boilerplate From and TryFrom impls for trailers which

View File

@ -13,48 +13,7 @@ pub use common::*;
use radicle_cob as cob; use radicle_cob as cob;
use radicle_git_ext::Oid; use radicle_git_ext::Oid;
use crate::{ use crate::{identity::project::Identity, storage::git::Repository};
identity::{project::Identity, Did},
node::NodeId,
storage::git::Repository,
};
/// The `Author` of a [`create`] or [`update`].
///
/// **Note**: `Author` implements [`identity::Identity`], but since it
/// is not content-addressed, the [`identity::Identity::content_id`]
/// returns [`git2::Oid::zero`]. This means that if the `author` is
/// set in the updates the history entries for those changes will
/// contain the zero `Oid` for the `author` field.
pub struct Author {
did: Did,
}
impl From<Did> for Author {
fn from(did: Did) -> Self {
Self { did }
}
}
impl From<NodeId> for Author {
fn from(node_id: NodeId) -> Self {
Self {
did: Did::from(node_id),
}
}
}
impl identity::Identity for Author {
type Identifier = String;
fn is_delegate(&self, delegation: &crypto::PublicKey) -> bool {
*self.did == *delegation
}
fn content_id(&self) -> Oid {
git2::Oid::zero().into()
}
}
/// Create a new [`CollaborativeObject`]. /// Create a new [`CollaborativeObject`].
/// ///
@ -62,8 +21,7 @@ impl identity::Identity for Author {
/// stored under. /// stored under.
/// ///
/// The `signer` is used to cryptographically sign the changes made /// The `signer` is used to cryptographically sign the changes made
/// for this update. **Note** that the public key for the signer must /// for this update.
/// match the key of the `Author` -- if it is set.
/// ///
/// The `project` is used to store its content-address in the history /// The `project` is used to store its content-address in the history
/// of changes for the collaborative object. /// of changes for the collaborative object.
@ -74,7 +32,7 @@ pub fn create<G>(
repository: &Repository, repository: &Repository,
signer: &G, signer: &G,
project: &Identity<Oid>, project: &Identity<Oid>,
args: Create<Author>, args: Create,
) -> Result<CollaborativeObject, error::Create> ) -> Result<CollaborativeObject, error::Create>
where where
G: crypto::Signer, G: crypto::Signer,
@ -118,8 +76,7 @@ pub fn list(
/// stored under. /// stored under.
/// ///
/// The `signer` is used to cryptographically sign the changes made /// The `signer` is used to cryptographically sign the changes made
/// for this update. **Note** that the public key for the signer must /// for this update.
/// match the key of the `Author` -- if it is set.
/// ///
/// The `project` is used to store its content-address in the history /// The `project` is used to store its content-address in the history
/// of changes for the collaborative object. /// of changes for the collaborative object.
@ -130,7 +87,7 @@ pub fn update<G>(
repository: &Repository, repository: &Repository,
signer: &G, signer: &G,
project: &Identity<Oid>, project: &Identity<Oid>,
args: Update<Author>, args: Update,
) -> Result<CollaborativeObject, error::Update> ) -> Result<CollaborativeObject, error::Update>
where where
G: crypto::Signer, G: crypto::Signer,

View File

@ -103,7 +103,6 @@ where
signer, signer,
&self.project, &self.project,
Update { Update {
author: Some(cob::Author::from(*signer.public_key())),
object_id, object_id,
history_type: HISTORY_TYPE.to_owned(), history_type: HISTORY_TYPE.to_owned(),
typename: T::type_name().clone(), typename: T::type_name().clone(),
@ -127,7 +126,6 @@ where
signer, signer,
&self.project, &self.project,
Create { Create {
author: Some(cob::Author::from(*signer.public_key())),
history_type: HISTORY_TYPE.to_owned(), history_type: HISTORY_TYPE.to_owned(),
typename: T::type_name().clone(), typename: T::type_name().clone(),
message: message.to_owned(), message: message.to_owned(),

View File

@ -406,10 +406,6 @@ pub struct Identity<I> {
impl radicle_cob::identity::Identity for Identity<Oid> { impl radicle_cob::identity::Identity for Identity<Oid> {
type Identifier = Oid; type Identifier = Oid;
fn is_delegate(&self, delegation: &crypto::PublicKey) -> bool {
self.doc.delegates.iter().any(|d| d.matches(delegation))
}
fn content_id(&self) -> Oid { fn content_id(&self) -> Oid {
self.current self.current
} }

View File

@ -681,13 +681,11 @@ impl change::Storage for Repository {
type LoadError = <git2::Repository as change::Storage>::LoadError; type LoadError = <git2::Repository as change::Storage>::LoadError;
type ObjectId = <git2::Repository as change::Storage>::ObjectId; type ObjectId = <git2::Repository as change::Storage>::ObjectId;
type Author = <git2::Repository as change::Storage>::Author;
type Resource = <git2::Repository as change::Storage>::Resource; type Resource = <git2::Repository as change::Storage>::Resource;
type Signatures = <git2::Repository as change::Storage>::Signatures; type Signatures = <git2::Repository as change::Storage>::Signatures;
fn create<Signer>( fn create<Signer>(
&self, &self,
author: Option<Self::Author>,
authority: Self::Resource, authority: Self::Resource,
signer: &Signer, signer: &Signer,
spec: change::Create<Self::ObjectId>, spec: change::Create<Self::ObjectId>,
@ -695,7 +693,7 @@ impl change::Storage for Repository {
where where
Signer: crypto::Signer, Signer: crypto::Signer,
{ {
self.backend.create(author, authority, signer, spec) self.backend.create(authority, signer, spec)
} }
fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> { fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> {