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!() }