From 5f59493c702ca57442659f8eae27384ec514698a Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Wed, 1 Mar 2023 15:40:30 +0100 Subject: [PATCH] cob: Don't require a local fork to exist Before this change, the identity document would be loaded for the COB signer. This meant that a fork would be needed for that signer. After this change, it's no longer necessary, since the identity doc head is computed from available remotes. While making those changes, it was also apparent that some of the identity-related functions had bad names and error types, this was fixed as well. --- radicle-cli/src/commands/checkout.rs | 2 +- radicle-cli/src/commands/clone.rs | 8 +- radicle-cli/src/commands/merge.rs | 2 +- radicle-cli/src/commands/review.rs | 2 +- radicle-cob/src/identity.rs | 19 ----- radicle-cob/src/lib.rs | 6 -- radicle-cob/src/object/collaboration.rs | 2 +- .../src/object/collaboration/create.rs | 9 +- .../src/object/collaboration/update.rs | 13 ++- radicle-cob/src/test/identity/person.rs | 11 +-- radicle-cob/src/test/identity/project.rs | 9 -- radicle-cob/src/tests.rs | 14 ++-- radicle-httpd/src/api/error.rs | 4 +- radicle-node/src/service.rs | 9 +- radicle-node/src/worker.rs | 4 +- radicle/src/cob.rs | 4 +- radicle/src/cob/store.rs | 14 ++-- radicle/src/identity.rs | 39 ++++++--- radicle/src/rad.rs | 10 +-- radicle/src/storage.rs | 22 ++--- radicle/src/storage/git.rs | 84 ++++++++----------- radicle/src/test/storage.rs | 17 ++-- 22 files changed, 127 insertions(+), 177 deletions(-) delete mode 100644 radicle-cob/src/identity.rs diff --git a/radicle-cli/src/commands/checkout.rs b/radicle-cli/src/commands/checkout.rs index d5b3a58d..80bf937a 100644 --- a/radicle-cli/src/commands/checkout.rs +++ b/radicle-cli/src/commands/checkout.rs @@ -94,7 +94,7 @@ pub fn execute(options: Options, profile: &Profile) -> anyhow::Result { let remote = options.remote.unwrap_or(*profile.id()); let doc = storage .repository(id)? - .identity_of(&remote) + .identity_doc_of(&remote) .context("project could not be found in local storage")?; let payload = doc.project()?; let path = PathBuf::from(payload.name()); diff --git a/radicle-cli/src/commands/clone.rs b/radicle-cli/src/commands/clone.rs index 36ecd1a4..35d0474e 100644 --- a/radicle-cli/src/commands/clone.rs +++ b/radicle-cli/src/commands/clone.rs @@ -7,14 +7,14 @@ use anyhow::anyhow; use thiserror::Error; use radicle::git::raw; -use radicle::identity::doc; use radicle::identity::doc::{DocError, Id}; +use radicle::identity::{doc, IdentityError}; use radicle::node; use radicle::node::{FetchResult, Handle as _, Node}; use radicle::prelude::*; use radicle::rad; use radicle::storage; -use radicle::storage::git::{ProjectError, Storage}; +use radicle::storage::git::Storage; use crate::commands::rad_checkout as checkout; use crate::project; @@ -151,7 +151,7 @@ pub enum CloneError { #[error("payload: {0}")] Payload(#[from] doc::PayloadError), #[error("project error: {0}")] - Project(#[from] ProjectError), + Identity(#[from] IdentityError), #[error("no seeds found for {0}")] NotFound(Id), } @@ -223,7 +223,7 @@ pub fn clone( } } - let doc = repository.identity_of(&me)?; + let doc = repository.identity_doc_of(&me)?; let proj = doc.project()?; let path = Path::new(proj.name()); diff --git a/radicle-cli/src/commands/merge.rs b/radicle-cli/src/commands/merge.rs index ff7fff3e..63c2ca7c 100644 --- a/radicle-cli/src/commands/merge.rs +++ b/radicle-cli/src/commands/merge.rs @@ -137,7 +137,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { let signer = term::signer(&profile)?; let repository = profile.storage.repository(id)?; let _project = repository - .identity_of(profile.id()) + .identity_doc_of(profile.id()) .context(format!("couldn't load project {id} from local state"))?; let repository = profile.storage.repository(id)?; let mut patches = Patches::open(*profile.id(), &repository)?; diff --git a/radicle-cli/src/commands/review.rs b/radicle-cli/src/commands/review.rs index b84886bc..a8e939c3 100644 --- a/radicle-cli/src/commands/review.rs +++ b/radicle-cli/src/commands/review.rs @@ -137,7 +137,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { let signer = term::signer(&profile)?; let repository = profile.storage.repository(id)?; let _project = repository - .identity_of(profile.id()) + .identity_doc_of(profile.id()) .context(format!("couldn't load project {id} from local state"))?; let mut patches = Patches::open(*profile.id(), &repository)?; diff --git a/radicle-cob/src/identity.rs b/radicle-cob/src/identity.rs deleted file mode 100644 index 0968a2b3..00000000 --- a/radicle-cob/src/identity.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2022 The Radicle Link Contributors -// -// This file is part of radicle-link, distributed under the GPLv3 with Radicle -// Linking Exception. For full terms see the included LICENSE file. - -use git_ext::Oid; - -/// An [`Identity`] represents a content addressed identity -/// (i.e. expected to be stored in a git backend). -/// -/// It should have a unique, stable, content addressable identifier. -pub trait Identity { - type Identifier; - - /// Provide the content address for the given identity. This is - /// expected to be the latest address for the identity at the time - /// of use. - fn content_id(&self) -> Oid; -} diff --git a/radicle-cob/src/lib.rs b/radicle-cob/src/lib.rs index b486a779..08e8f41c 100644 --- a/radicle-cob/src/lib.rs +++ b/radicle-cob/src/lib.rs @@ -46,10 +46,6 @@ //! represents the type of resource the collaborative objects are //! relating to, for example a software project. //! -//! This `Resource` must implement [`identity::Identity`] to allow the -//! internal logic to reference the resource's content-address in -//! `git` as well as the stable identifier used for the resource. -//! //! ## History Traversal //! //! The [`History`] of a [`CollaborativeObject`] -- accessed via @@ -89,8 +85,6 @@ mod trailers; pub mod change; pub use change::Change; -pub mod identity; - pub mod history; pub use history::{Contents, Entry, History}; diff --git a/radicle-cob/src/object/collaboration.rs b/radicle-cob/src/object/collaboration.rs index 2983f963..47d58242 100644 --- a/radicle-cob/src/object/collaboration.rs +++ b/radicle-cob/src/object/collaboration.rs @@ -8,7 +8,7 @@ use std::collections::BTreeSet; use git_ext::Oid; use crate::change::store::Manifest; -use crate::{change, identity::Identity, History, ObjectId, TypeName}; +use crate::{change, History, ObjectId, TypeName}; pub mod error; diff --git a/radicle-cob/src/object/collaboration/create.rs b/radicle-cob/src/object/collaboration/create.rs index 966210bb..3393957c 100644 --- a/radicle-cob/src/object/collaboration/create.rs +++ b/radicle-cob/src/object/collaboration/create.rs @@ -51,21 +51,20 @@ impl Create { /// /// The `args` are the metadata for this [`CollaborativeObject`]. See /// [`Create`] for further information. -pub fn create( +pub fn create( storage: &S, signer: &G, - resource: &Resource, + resource: Oid, identifier: &S::Identifier, args: Create, ) -> Result where S: Store, G: crypto::Signer, - Resource: Identity, { let Create { ref typename, .. } = &args; let init_change = storage - .store(resource.content_id(), signer, args.template()) + .store(resource, signer, args.template()) .map_err(error::Create::from)?; let object_id = init_change.id().into(); @@ -76,7 +75,7 @@ where let history = History::new_from_root( *init_change.id(), init_change.signature.key, - resource.content_id(), + resource, init_change.contents, init_change.timestamp, ); diff --git a/radicle-cob/src/object/collaboration/update.rs b/radicle-cob/src/object/collaboration/update.rs index c504092b..a8ac2979 100644 --- a/radicle-cob/src/object/collaboration/update.rs +++ b/radicle-cob/src/object/collaboration/update.rs @@ -3,12 +3,10 @@ // This file is part of radicle-link, distributed under the GPLv3 with Radicle // Linking Exception. For full terms see the included LICENSE file. +use git_ext::Oid; use nonempty::NonEmpty; -use crate::{ - change, change_graph::ChangeGraph, identity::Identity, CollaborativeObject, ObjectId, Store, - TypeName, -}; +use crate::{change, change_graph::ChangeGraph, CollaborativeObject, ObjectId, Store, TypeName}; use super::error; @@ -44,17 +42,16 @@ pub struct Update { /// /// The `args` are the metadata for this [`CollaborativeObject`] /// udpate. See [`Update`] for further information. -pub fn update( +pub fn update( storage: &S, signer: &G, - resource: &Resource, + resource: Oid, identifier: &S::Identifier, args: Update, ) -> Result where S: Store, G: crypto::Signer, - Resource: Identity, { let Update { ref typename, @@ -73,7 +70,7 @@ where .ok_or(error::Update::NoSuchObject)?; let change = storage.store( - resource.content_id(), + resource, signer, change::Template { tips: object.tips().iter().cloned().collect(), diff --git a/radicle-cob/src/test/identity/person.rs b/radicle-cob/src/test/identity/person.rs index 45ab286e..2ec89c02 100644 --- a/radicle-cob/src/test/identity/person.rs +++ b/radicle-cob/src/test/identity/person.rs @@ -1,10 +1,9 @@ use git_ext::Oid; use serde::{Deserialize, Serialize}; -use crate::identity::Identity; use crate::test::storage::{self, Storage}; -use super::{Name, Urn}; +use super::Name; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Person { @@ -57,11 +56,3 @@ impl Person { &self.payload.name } } - -impl Identity for Person { - type Identifier = Urn; - - fn content_id(&self) -> Oid { - self.content_id - } -} diff --git a/radicle-cob/src/test/identity/project.rs b/radicle-cob/src/test/identity/project.rs index 705f6ffd..1b813ee0 100644 --- a/radicle-cob/src/test/identity/project.rs +++ b/radicle-cob/src/test/identity/project.rs @@ -3,7 +3,6 @@ use std::collections::BTreeSet; use git_ext::Oid; use serde::{Deserialize, Serialize}; -use crate::identity::Identity; use crate::test; use crate::test::storage::{self, Storage}; @@ -74,11 +73,3 @@ impl Project { &self.payload.name } } - -impl Identity for RemoteProject { - type Identifier = Urn; - - fn content_id(&self) -> Oid { - self.project.content_id - } -} diff --git a/radicle-cob/src/tests.rs b/radicle-cob/src/tests.rs index e2a244f8..e2fe3ecd 100644 --- a/radicle-cob/src/tests.rs +++ b/radicle-cob/src/tests.rs @@ -26,7 +26,7 @@ fn roundtrip() { let cob = create( &storage, &signer, - &proj, + proj.project.content_id, &proj.identifier(), Create { history_type: "test".to_string(), @@ -58,7 +58,7 @@ fn list_cobs() { let issue_1 = create( &storage, &signer, - &proj, + proj.project.content_id, &proj.identifier(), Create { history_type: "test".to_string(), @@ -72,7 +72,7 @@ fn list_cobs() { let issue_2 = create( &storage, &signer, - &proj, + proj.project.content_id, &proj.identifier(), Create { history_type: "test".to_string(), @@ -106,7 +106,7 @@ fn update_cob() { let cob = create( &storage, &signer, - &proj, + proj.project.content_id, &proj.identifier(), Create { history_type: "test".to_string(), @@ -124,7 +124,7 @@ fn update_cob() { let updated = update( &storage, &signer, - &proj, + proj.project.content_id, &proj.identifier(), Update { changes: nonempty!(b"issue 1".to_vec()), @@ -164,7 +164,7 @@ fn traverse_cobs() { let cob = create( &storage, &terry_signer, - &terry_proj, + terry_proj.project.content_id, &terry_proj.identifier(), Create { contents: nonempty!(b"issue 1".to_vec()), @@ -186,7 +186,7 @@ fn traverse_cobs() { let updated = update( &storage, &neil_signer, - &neil_proj, + neil_proj.project.content_id, &neil_proj.identifier(), Update { changes: nonempty!(b"issue 2".to_vec()), diff --git a/radicle-httpd/src/api/error.rs b/radicle-httpd/src/api/error.rs index 8f87a62e..b37f802c 100644 --- a/radicle-httpd/src/api/error.rs +++ b/radicle-httpd/src/api/error.rs @@ -38,9 +38,9 @@ pub enum Error { #[error(transparent)] CobStore(#[from] radicle::cob::store::Error), - /// Git project error. + /// Identity error. #[error(transparent)] - GitProject(#[from] radicle::storage::git::ProjectError), + Identity(#[from] radicle::identity::IdentityError), /// Project doc error. #[error(transparent)] diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index 0a7859d3..9d3a9c3a 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -25,6 +25,7 @@ use crate::address::AddressBook; use crate::clock::Timestamp; use crate::crypto; use crate::crypto::{Signer, Verified}; +use crate::identity::IdentityError; use crate::identity::{Doc, Id}; use crate::node; use crate::node::{Address, Features, FetchResult, Seed, Seeds}; @@ -1277,8 +1278,8 @@ pub trait ServiceState { fn sessions(&self) -> &Sessions; /// Get the current inventory. fn inventory(&self) -> Result; - /// Get a project from storage, using the local node's key. - fn get(&self, proj: Id) -> Result>, storage::ProjectError>; + /// Get a repository from storage, using the local node's key. + fn get(&self, proj: Id) -> Result>, IdentityError>; /// Get the clock. fn clock(&self) -> &LocalTime; /// Get the clock mutably. @@ -1303,7 +1304,7 @@ where self.storage.inventory() } - fn get(&self, proj: Id) -> Result>, storage::ProjectError> { + fn get(&self, proj: Id) -> Result>, IdentityError> { self.storage.get(&self.node_id(), proj) } @@ -1385,7 +1386,7 @@ pub enum LookupError { #[error(transparent)] Routing(#[from] routing::Error), #[error(transparent)] - Project(#[from] storage::ProjectError), + Identity(#[from] IdentityError), } /// Information on a peer, that we may or may not be connected to. diff --git a/radicle-node/src/worker.rs b/radicle-node/src/worker.rs index 8602a7ce..a9d90592 100644 --- a/radicle-node/src/worker.rs +++ b/radicle-node/src/worker.rs @@ -8,7 +8,7 @@ use netservices::tunnel::Tunnel; use netservices::{NetSession, SplitIo}; use radicle::crypto::Signer; -use radicle::identity::Id; +use radicle::identity::{Id, IdentityError}; use radicle::storage::{Namespaces, ReadRepository, RefUpdate, WriteRepository, WriteStorage}; use radicle::{git, Storage}; use reactor::poller::popol; @@ -46,7 +46,7 @@ pub enum FetchError { #[error(transparent)] Io(#[from] io::Error), #[error(transparent)] - Project(#[from] storage::ProjectError), + Identity(#[from] IdentityError), } impl FetchError { diff --git a/radicle/src/cob.rs b/radicle/src/cob.rs index 68af6b0a..59eead44 100644 --- a/radicle/src/cob.rs +++ b/radicle/src/cob.rs @@ -11,8 +11,8 @@ pub mod test; pub use cob::{create, get, list, remove, update}; pub use cob::{ - history::entry::EntryBlob, identity::Identity, object::collaboration::error, - CollaborativeObject, Contents, Create, Entry, History, ObjectId, TypeName, Update, + history::entry::EntryBlob, object::collaboration::error, CollaborativeObject, Contents, Create, + Entry, History, ObjectId, TypeName, Update, }; pub use common::*; pub use op::{ActorId, Op, OpId}; diff --git a/radicle/src/cob/store.rs b/radicle/src/cob/store.rs index 25a4f701..6119ad45 100644 --- a/radicle/src/cob/store.rs +++ b/radicle/src/cob/store.rs @@ -10,17 +10,15 @@ use rand::rngs::StdRng; use rand::{RngCore as _, SeedableRng}; use serde::{Deserialize, Serialize}; -use crate::cob; use crate::cob::common::Author; use crate::cob::op::{Nonce, Op, OpId, Ops}; use crate::cob::CollaborativeObject; use crate::cob::{ActorId, Create, History, ObjectId, TypeName, Update}; use crate::crypto::PublicKey; use crate::git; -use crate::identity; -use crate::identity::Identity; use crate::prelude::*; use crate::storage::git as storage; +use crate::{cob, identity}; /// History type for standard radicle COBs. pub const HISTORY_TYPE: &str = "radicle"; @@ -91,7 +89,7 @@ pub enum Error { /// Storage for collaborative objects of a specific type `T` in a single repository. pub struct Store<'a, T> { whoami: PublicKey, - identity: Identity, + parent: git::Oid, raw: &'a storage::Repository, witness: PhantomData, rng: StdRng, @@ -107,10 +105,10 @@ impl<'a, T> Store<'a, T> { /// Open a new generic store. pub fn open(whoami: PublicKey, store: &'a storage::Repository) -> Result { let rng = rng::std(); - let identity = Identity::load(&whoami, store)?; + let identity = store.identity()?; Ok(Self { - identity, + parent: identity.current, whoami, raw: store, witness: PhantomData, @@ -151,7 +149,7 @@ where cob::update( self.raw, signer, - &self.identity, + self.parent, signer.public_key(), Update { object_id, @@ -175,7 +173,7 @@ where let cob = cob::create( self.raw, signer, - &self.identity, + self.parent, signer.public_key(), Create { history_type: HISTORY_TYPE.to_owned(), diff --git a/radicle/src/identity.rs b/radicle/src/identity.rs index fe7f3e1d..0a8e68e7 100644 --- a/radicle/src/identity.rs +++ b/radicle/src/identity.rs @@ -10,11 +10,12 @@ use thiserror::Error; use crate::crypto; use crate::crypto::{Signature, Verified}; use crate::git; -use crate::storage::{ReadRepository, RemoteId}; +use crate::storage; +use crate::storage::{refs, ReadRepository, RemoteId}; pub use crypto::PublicKey; pub use did::Did; -pub use doc::{Doc, Id, IdError}; +pub use doc::{Doc, Id, IdError, PayloadError}; pub use project::Project; /// Untrusted, well-formed input. @@ -30,18 +31,39 @@ pub enum IdentityError { Git(#[from] git2::Error), #[error("git: {0}")] GitExt(#[from] git::Error), + #[error("identity branches diverge from each other")] + BranchesDiverge, #[error("root hash `{0}` does not match project")] MismatchedRoot(Oid), + #[error("the identity branch is missing")] + MissingBranch, #[error("the document root is missing")] MissingRoot, #[error("root commit is missing one or more delegate signatures")] MissingRootSignatures, + #[error(transparent)] + Payload(#[from] PayloadError), #[error("commit signature for {0} is invalid: {1}")] InvalidSignature(PublicKey, crypto::Error), #[error("threshold not reached: {0} signatures for a threshold of {1}")] ThresholdNotReached(usize, usize), #[error("identity document error: {0}")] Doc(#[from] doc::DocError), + #[error(transparent)] + Refs(#[from] refs::Error), + #[error(transparent)] + Storage(#[from] storage::Error), +} + +impl IdentityError { + /// Whether this error is caused by something not being found. + pub fn is_not_found(&self) -> bool { + match self { + Self::Doc(doc) => doc.is_not_found(), + Self::Refs(refs) => refs.is_not_found(), + _ => false, + } + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -62,14 +84,6 @@ pub struct Identity { pub signatures: HashMap, } -impl radicle_cob::identity::Identity for Identity { - type Identifier = Oid; - - fn content_id(&self) -> Oid { - self.current - } -} - impl Identity { pub fn verified(self, id: doc::Id) -> Result, IdentityError> { // The root hash must be equal to the id. @@ -94,6 +108,11 @@ impl Identity { repo: &R, ) -> Result, IdentityError> { let head = Doc::::head(remote, repo)?; + + Self::load_at(head, repo) + } + + pub fn load_at(head: Oid, repo: &R) -> Result, IdentityError> { let mut history = repo.revwalk(head)?.collect::>(); // Retrieve root document. diff --git a/radicle/src/rad.rs b/radicle/src/rad.rs index 23440e86..d9f161ff 100644 --- a/radicle/src/rad.rs +++ b/radicle/src/rad.rs @@ -8,11 +8,11 @@ use thiserror::Error; use crate::crypto::{Signer, Verified}; use crate::git; -use crate::identity::doc; use crate::identity::doc::{DocError, Id}; use crate::identity::project::Project; +use crate::identity::{doc, IdentityError}; use crate::storage::git::transport; -use crate::storage::git::{ProjectError, Repository, Storage}; +use crate::storage::git::{Repository, Storage}; use crate::storage::refs::SignedRefs; use crate::storage::WriteRepository; use crate::storage::{BranchName, ReadRepository as _, RemoteId}; @@ -26,7 +26,7 @@ pub enum InitError { #[error("doc: {0}")] Doc(#[from] DocError), #[error("project: {0}")] - Project(#[from] storage::git::ProjectError), + Identity(#[from] IdentityError), #[error("project payload: {0}")] ProjectPayload(String), #[error("git: {0}")] @@ -102,7 +102,7 @@ pub enum ForkError { #[error("project `{0}` was not found in storage")] NotFound(Id), #[error("project identity error: {0}")] - InvalidIdentity(#[from] storage::git::ProjectError), + InvalidIdentity(#[from] IdentityError), #[error("project identity document error: {0}")] Doc(#[from] DocError), #[error("git: invalid reference")] @@ -199,7 +199,7 @@ pub enum CheckoutError { #[error("project `{0}` was not found in storage")] NotFound(Id), #[error("project error: {0}")] - Project(#[from] ProjectError), + Identity(#[from] IdentityError), } /// Checkout a project from storage as a working copy. diff --git a/radicle/src/storage.rs b/radicle/src/storage.rs index af49e8e6..e6392d1e 100644 --- a/radicle/src/storage.rs +++ b/radicle/src/storage.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use crypto::{PublicKey, Signer, Unverified, Verified}; -pub use git::{ProjectError, VerifyError}; +pub use git::VerifyError; pub use radicle_git_ext::Oid; use crate::collections::HashMap; @@ -18,7 +18,7 @@ use crate::git::ext as git_ext; use crate::git::{Qualified, RefError, RefString}; use crate::identity; use crate::identity::doc::DocError; -use crate::identity::{Id, IdError}; +use crate::identity::{Id, IdError, IdentityError}; use crate::storage::refs::Refs; use self::refs::SignedRefs; @@ -98,7 +98,7 @@ pub enum FetchError { Storage(#[from] Error), // TODO: This should wrap a more specific error. #[error("repository head: {0}")] - SetHead(#[from] ProjectError), + SetHead(#[from] IdentityError), } pub type RemoteId = PublicKey; @@ -269,9 +269,9 @@ pub trait ReadStorage { &self, remote: &RemoteId, rid: Id, - ) -> Result>, ProjectError>; + ) -> Result>, IdentityError>; /// Check whether storage contains a repository. - fn contains(&self, rid: &Id) -> Result; + fn contains(&self, rid: &Id) -> Result; /// Get the inventory of repositories hosted under this storage. fn inventory(&self) -> Result; /// Open or create a read-only repository. @@ -314,14 +314,14 @@ pub trait ReadRepository { /// head using [`ReadRepository::canonical_head`]. /// /// Returns the [`Oid`] as well as the qualified reference name. - fn head(&self) -> Result<(Qualified, Oid), ProjectError>; + fn head(&self) -> Result<(Qualified, Oid), IdentityError>; /// Compute the canonical head of this repository. /// /// Ignores any existing `HEAD` reference. /// /// Returns the [`Oid`] as well as the qualified reference name. - fn canonical_head(&self) -> Result<(Qualified, Oid), ProjectError>; + fn canonical_head(&self) -> Result<(Qualified, Oid), IdentityError>; /// Get the `reference` for the given `remote`. /// @@ -357,14 +357,14 @@ pub trait ReadRepository { fn remotes(&self) -> Result, refs::Error>; /// Get the repository's identity document. - fn identity_doc(&self) -> Result<(Oid, identity::Doc), ProjectError>; + fn identity_doc(&self) -> Result<(Oid, identity::Doc), IdentityError>; } /// Allows read-write access to a repository. pub trait WriteRepository: ReadRepository { /// Set the repository head to the canonical branch. /// This computes the head based on the delegate set. - fn set_head(&self) -> Result; + fn set_head(&self) -> Result; /// Sign the repository's refs under the `refs/rad/sigrefs` branch. fn sign_refs(&self, signer: &G) -> Result, Error>; /// Get the underlying git repository. @@ -386,7 +386,7 @@ where self.deref().path_of(rid) } - fn contains(&self, rid: &Id) -> Result { + fn contains(&self, rid: &Id) -> Result { self.deref().contains(rid) } @@ -398,7 +398,7 @@ where &self, remote: &RemoteId, proj: Id, - ) -> Result>, ProjectError> { + ) -> Result>, IdentityError> { self.deref().get(remote, proj) } diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs index b55e552d..63274140 100644 --- a/radicle/src/storage/git.rs +++ b/radicle/src/storage/git.rs @@ -11,7 +11,7 @@ use once_cell::sync::Lazy; use crate::git; use crate::identity; -use crate::identity::{doc, Doc, Id}; +use crate::identity::{Doc, Id}; use crate::identity::{Identity, IdentityError, Project}; use crate::storage::refs; use crate::storage::refs::{Refs, SignedRefs}; @@ -58,37 +58,6 @@ impl<'a> TryFrom> for Ref { } } -// TODO: Is this is the wrong place for this type? -#[derive(Error, Debug)] -pub enum ProjectError { - #[error("identity branches diverge from each other")] - BranchesDiverge, - #[error("identity branches missing")] - MissingHeads, - #[error("storage error: {0}")] - Storage(#[from] Error), - #[error("identity document error: {0}")] - Doc(#[from] doc::DocError), - #[error("payload error: {0}")] - Payload(#[from] doc::PayloadError), - #[error("git: {0}")] - Git(#[from] git2::Error), - #[error("git: {0}")] - GitExt(#[from] git::Error), - #[error("refs: {0}")] - Refs(#[from] refs::Error), -} - -impl ProjectError { - /// Whether this error is caused by the project not being found. - pub fn is_not_found(&self) -> bool { - match self { - Self::Doc(doc) => doc.is_not_found(), - _ => false, - } - } -} - #[derive(Debug, Clone)] pub struct Storage { path: PathBuf, @@ -105,7 +74,7 @@ impl ReadStorage for Storage { paths::repository(&self, rid) } - fn contains(&self, rid: &Id) -> Result { + fn contains(&self, rid: &Id) -> Result { if paths::repository(&self, rid).exists() { let _ = self.repository(*rid)?.head()?; return Ok(true); @@ -113,13 +82,13 @@ impl ReadStorage for Storage { Ok(false) } - fn get(&self, remote: &RemoteId, proj: Id) -> Result>, ProjectError> { + fn get(&self, remote: &RemoteId, proj: Id) -> Result>, IdentityError> { let repo = match self.repository(proj) { Ok(doc) => doc, Err(e) if e.is_not_found() => return Ok(None), Err(e) => return Err(e.into()), }; - match repo.identity_of(remote) { + match repo.identity_doc_of(remote) { Ok(doc) => Ok(Some(doc)), Err(e) if e.is_not_found() => Ok(None), Err(e) => Err(e), @@ -300,18 +269,24 @@ impl Repository { Ok(refs) } - pub fn identity(&self, remote: &RemoteId) -> Result, IdentityError> { + pub fn identity_of(&self, remote: &RemoteId) -> Result, IdentityError> { Identity::load(remote, self) } - pub fn project_of(&self, remote: &RemoteId) -> Result { - let doc = self.identity_of(remote)?; + pub fn identity(&self) -> Result, IdentityError> { + let head = self.identity_head()?; + + Identity::load_at(head, self) + } + + pub fn project_of(&self, remote: &RemoteId) -> Result { + let doc = self.identity_doc_of(remote)?; let proj = doc.project()?; Ok(proj) } - pub fn identity_of(&self, remote: &RemoteId) -> Result, ProjectError> { + pub fn identity_doc_of(&self, remote: &RemoteId) -> Result, IdentityError> { let (doc, _) = identity::Doc::load(remote, self)?; let verified = doc.verified()?; @@ -319,8 +294,18 @@ impl Repository { } /// Return the canonical identity [`git::Oid`] and document. - pub fn identity_doc(&self) -> Result<(Oid, identity::Doc), ProjectError> { + pub fn identity_doc(&self) -> Result<(Oid, identity::Doc), IdentityError> { + let head = self.identity_head()?; + + Doc::::load_at(head, self) + .map(|(doc, _)| (head, doc)) + .map_err(IdentityError::from) + } + + /// Return the canonical identity branch head. + pub fn identity_head(&self) -> Result { let mut heads = Vec::new(); + for remote in self.remote_ids()? { let remote = remote?; let oid = Doc::::head(&remote, self)?; @@ -328,7 +313,7 @@ impl Repository { heads.push(oid.into()); } // Keep track of the longest identity branch. - let mut longest = heads.pop().ok_or(ProjectError::MissingHeads)?; + let mut longest = heads.pop().ok_or(IdentityError::MissingBranch)?; for head in &heads { let base = self.raw().merge_base(*head, longest)?; @@ -360,13 +345,10 @@ impl Repository { // o (base) // | // - return Err(ProjectError::BranchesDiverge); + return Err(IdentityError::BranchesDiverge); } } - - Doc::::load_at(longest.into(), self) - .map(|(doc, _)| (longest.into(), doc)) - .map_err(ProjectError::from) + Ok(longest.into()) } pub fn remote_ids( @@ -484,7 +466,7 @@ impl ReadRepository for Repository { return Err(VerifyError::MissingRef(remote, name)); } // Verify identity history of remote. - self.identity(&remote)?.verified(self.id)?; + self.identity_of(&remote)?.verified(self.id)?; } Ok(()) @@ -554,11 +536,11 @@ impl ReadRepository for Repository { Ok(Remotes::from_iter(remotes)) } - fn identity_doc(&self) -> Result<(Oid, identity::Doc), ProjectError> { + fn identity_doc(&self) -> Result<(Oid, identity::Doc), IdentityError> { Repository::identity_doc(self) } - fn head(&self) -> Result<(Qualified, Oid), ProjectError> { + fn head(&self) -> Result<(Qualified, Oid), IdentityError> { // If `HEAD` is already set locally, just return that. if let Ok(head) = self.backend.head() { if let Ok((name, oid)) = git::refs::qualified_from(&head) { @@ -568,7 +550,7 @@ impl ReadRepository for Repository { self.canonical_head() } - fn canonical_head(&self) -> Result<(Qualified, Oid), ProjectError> { + fn canonical_head(&self) -> Result<(Qualified, Oid), IdentityError> { // TODO: In the `fork` function for example, we call Repository::project_identity again, // This should only be necessary once. let (_, doc) = self.identity_doc()?; @@ -595,7 +577,7 @@ impl ReadRepository for Repository { } impl WriteRepository for Repository { - fn set_head(&self) -> Result { + fn set_head(&self) -> Result { let head_ref = refname!("HEAD"); let (branch_ref, head) = self.canonical_head()?; diff --git a/radicle/src/test/storage.rs b/radicle/src/test/storage.rs index 0cbb0dcb..d166de14 100644 --- a/radicle/src/test/storage.rs +++ b/radicle/src/test/storage.rs @@ -6,6 +6,7 @@ use radicle_git_ext as git_ext; use crate::crypto::{Signer, Verified}; use crate::identity::doc::{Doc, Id}; +use crate::identity::IdentityError; pub use crate::storage::*; @@ -42,15 +43,11 @@ impl ReadStorage for MockStorage { self.path().join(rid.canonical()) } - fn contains(&self, rid: &Id) -> Result { + fn contains(&self, rid: &Id) -> Result { Ok(self.inventory.contains_key(rid)) } - fn get( - &self, - _remote: &RemoteId, - proj: Id, - ) -> Result>, git::ProjectError> { + fn get(&self, _remote: &RemoteId, proj: Id) -> Result>, IdentityError> { Ok(self.inventory.get(&proj).cloned()) } @@ -86,11 +83,11 @@ impl ReadRepository for MockRepository { Ok(true) } - fn head(&self) -> Result<(fmt::Qualified, Oid), ProjectError> { + fn head(&self) -> Result<(fmt::Qualified, Oid), IdentityError> { todo!() } - fn canonical_head(&self) -> Result<(fmt::Qualified, Oid), ProjectError> { + fn canonical_head(&self) -> Result<(fmt::Qualified, Oid), IdentityError> { todo!() } @@ -148,7 +145,7 @@ impl ReadRepository for MockRepository { fn identity_doc( &self, - ) -> Result<(Oid, crate::identity::Doc), git::ProjectError> { + ) -> Result<(Oid, crate::identity::Doc), IdentityError> { todo!() } } @@ -158,7 +155,7 @@ impl WriteRepository for MockRepository { todo!() } - fn set_head(&self) -> Result { + fn set_head(&self) -> Result { todo!() }