Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle: Split out `SignRepository` trait
Alexis Sellier committed 2 years ago
commit 4634913b66bfc5bc82f354ced8ac059808d45615
parent ff7e9ded1707aeba59d4c5db2795a0805c003413
11 files changed +278 -115
modified radicle-node/src/test/environment.rs
@@ -27,7 +27,7 @@ use radicle::profile;
use radicle::profile::Home;
use radicle::profile::Profile;
use radicle::rad;
-
use radicle::storage::{ReadRepository, ReadStorage as _, WriteRepository};
+
use radicle::storage::{ReadRepository, ReadStorage as _, SignRepository as _};
use radicle::test::fixtures;
use radicle::Storage;

modified radicle-remote-helper/src/push.rs
@@ -11,10 +11,10 @@ use radicle::crypto::{PublicKey, Signer};
use radicle::node;
use radicle::node::{Handle, NodeId};
use radicle::prelude::Id;
+
use radicle::storage;
use radicle::storage::git::cob::object::ParseObjectId;
use radicle::storage::git::transport::local::Url;
-
use radicle::storage::WriteRepository;
-
use radicle::storage::{self, ReadRepository};
+
use radicle::storage::{ReadRepository, SignRepository as _, WriteRepository};
use radicle::Profile;
use radicle::{git, rad};
use radicle_cli::terminal as cli;
modified radicle-tools/src/rad-push.rs
@@ -1,6 +1,9 @@
use std::path::Path;

-
use radicle::{node::Handle, storage::ReadStorage, storage::WriteRepository};
+
use radicle::{
+
    node::Handle,
+
    storage::{ReadStorage, SignRepository, WriteRepository},
+
};

fn main() -> anyhow::Result<()> {
    let cwd = Path::new(".").canonicalize()?;
modified radicle/src/cob/patch.rs
@@ -1152,7 +1152,7 @@ pub struct PatchMut<'a, 'g, R> {

impl<'a, 'g, R> PatchMut<'a, 'g, R>
where
-
    R: WriteRepository + cob::Store,
+
    R: ReadRepository + SignRepository + cob::Store,
{
    pub fn new(
        id: ObjectId,
@@ -1392,7 +1392,7 @@ impl<'a, R> Deref for Patches<'a, R> {

impl<'a, R> Patches<'a, R>
where
-
    R: WriteRepository + cob::Store,
+
    R: ReadRepository + cob::Store,
{
    /// Open an patches store.
    pub fn open(repository: &'a R) -> Result<Self, store::Error> {
@@ -1401,52 +1401,6 @@ where
        Ok(Self { raw })
    }

-
    /// Open a new patch.
-
    pub fn create<'g, G: Signer>(
-
        &'g mut self,
-
        title: impl ToString,
-
        description: impl ToString,
-
        target: MergeTarget,
-
        base: impl Into<git::Oid>,
-
        oid: impl Into<git::Oid>,
-
        tags: &[Tag],
-
        signer: &G,
-
    ) -> Result<PatchMut<'a, 'g, R>, Error> {
-
        self._create(
-
            title,
-
            description,
-
            target,
-
            base,
-
            oid,
-
            tags,
-
            State::default(),
-
            signer,
-
        )
-
    }
-

-
    /// Draft a patch. This patch will be created in a [`State::Draft`] state.
-
    pub fn draft<'g, G: Signer>(
-
        &'g mut self,
-
        title: impl ToString,
-
        description: impl ToString,
-
        target: MergeTarget,
-
        base: impl Into<git::Oid>,
-
        oid: impl Into<git::Oid>,
-
        tags: &[Tag],
-
        signer: &G,
-
    ) -> Result<PatchMut<'a, 'g, R>, Error> {
-
        self._create(
-
            title,
-
            description,
-
            target,
-
            base,
-
            oid,
-
            tags,
-
            State::Draft,
-
            signer,
-
        )
-
    }
-

    /// Patches count by state.
    pub fn counts(&self) -> Result<PatchCounts, store::Error> {
        let all = self.all()?;
@@ -1488,25 +1442,10 @@ where
        self.raw.get(id).map(|r| r.map(|(p, _)| p))
    }

-
    /// Get a patch mutably.
-
    pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result<PatchMut<'a, 'g, R>, store::Error> {
-
        let (patch, clock) = self
-
            .raw
-
            .get(id)?
-
            .ok_or_else(move || store::Error::NotFound(TYPENAME.clone(), *id))?;
-

-
        Ok(PatchMut {
-
            id: *id,
-
            clock,
-
            patch,
-
            store: self,
-
        })
-
    }
-

    /// Get proposed patches.
    pub fn proposed(
        &self,
-
    ) -> Result<impl Iterator<Item = (PatchId, Patch, clock::Lamport)> + 'a, Error> {
+
    ) -> Result<impl Iterator<Item = (PatchId, Patch, clock::Lamport)> + '_, Error> {
        let all = self.all()?;

        Ok(all
@@ -1524,6 +1463,72 @@ where
            .proposed()?
            .filter(move |(_, p, _)| p.author().id() == who))
    }
+
}
+

+
impl<'a, R> Patches<'a, R>
+
where
+
    R: ReadRepository + SignRepository + cob::Store,
+
{
+
    /// Open a new patch.
+
    pub fn create<'g, G: Signer>(
+
        &'g mut self,
+
        title: impl ToString,
+
        description: impl ToString,
+
        target: MergeTarget,
+
        base: impl Into<git::Oid>,
+
        oid: impl Into<git::Oid>,
+
        tags: &[Tag],
+
        signer: &G,
+
    ) -> Result<PatchMut<'a, 'g, R>, Error> {
+
        self._create(
+
            title,
+
            description,
+
            target,
+
            base,
+
            oid,
+
            tags,
+
            State::default(),
+
            signer,
+
        )
+
    }
+

+
    /// Draft a patch. This patch will be created in a [`State::Draft`] state.
+
    pub fn draft<'g, G: Signer>(
+
        &'g mut self,
+
        title: impl ToString,
+
        description: impl ToString,
+
        target: MergeTarget,
+
        base: impl Into<git::Oid>,
+
        oid: impl Into<git::Oid>,
+
        tags: &[Tag],
+
        signer: &G,
+
    ) -> Result<PatchMut<'a, 'g, R>, Error> {
+
        self._create(
+
            title,
+
            description,
+
            target,
+
            base,
+
            oid,
+
            tags,
+
            State::Draft,
+
            signer,
+
        )
+
    }
+

+
    /// Get a patch mutably.
+
    pub fn get_mut<'g>(&'g mut self, id: &ObjectId) -> Result<PatchMut<'a, 'g, R>, store::Error> {
+
        let (patch, clock) = self
+
            .raw
+
            .get(id)?
+
            .ok_or_else(move || store::Error::NotFound(TYPENAME.clone(), *id))?;
+

+
        Ok(PatchMut {
+
            id: *id,
+
            clock,
+
            patch,
+
            store: self,
+
        })
+
    }

    /// Create a patch. This is an internal function used by `create` and `draft`.
    fn _create<'g, G: Signer>(
modified radicle/src/cob/store.rs
@@ -14,6 +14,7 @@ use crate::cob::{ActorId, Create, EntryId, History, ObjectId, TypeName, Update,
use crate::git;
use crate::prelude::*;
use crate::storage::git as storage;
+
use crate::storage::SignRepository;
use crate::{cob, identity};

/// History type for standard radicle COBs.
@@ -155,7 +156,7 @@ impl<'a, T, R: ReadRepository> Store<'a, T, R> {

impl<'a, T, R> Store<'a, T, R>
where
-
    R: WriteRepository + cob::Store,
+
    R: ReadRepository + SignRepository + cob::Store,
    T: FromHistory,
    T::Action: Serialize,
{
@@ -220,6 +221,34 @@ where
        Ok((*cob.id(), object, clock))
    }

+
    /// Remove an object.
+
    pub fn remove<G: Signer>(&self, id: &ObjectId, signer: &G) -> Result<(), Error> {
+
        let name = git::refs::storage::cob(signer.public_key(), T::type_name(), id);
+
        match self
+
            .repo
+
            .reference_oid(signer.public_key(), &name.strip_namespace())
+
        {
+
            Ok(_) => {
+
                cob::remove(self.repo, signer.public_key(), T::type_name(), id)?;
+
                self.repo.sign_refs(signer).map_err(Error::SignRefs)?;
+
                Ok(())
+
            }
+
            Err(git::Error::NotFound(_)) => Ok(()),
+
            Err(git::Error::Git(err)) if err.code() == git::raw::ErrorCode::NotFound => Ok(()),
+
            Err(err) => Err(Error::RefLookup {
+
                name: name.to_ref_string(),
+
                err,
+
            }),
+
        }
+
    }
+
}
+

+
impl<'a, T, R> Store<'a, T, R>
+
where
+
    R: ReadRepository + cob::Store,
+
    T: FromHistory,
+
    T::Action: Serialize,
+
{
    /// Get an object.
    pub fn get(&self, id: &ObjectId) -> Result<Option<(T, Lamport)>, Error> {
        let cob = cob::get(self.repo, T::type_name(), id)?;
@@ -259,27 +288,6 @@ where

        Ok(raw.len())
    }
-

-
    /// Remove an object.
-
    pub fn remove<G: Signer>(&self, id: &ObjectId, signer: &G) -> Result<(), Error> {
-
        let name = git::refs::storage::cob(signer.public_key(), T::type_name(), id);
-
        match self
-
            .repo
-
            .reference_oid(signer.public_key(), &name.strip_namespace())
-
        {
-
            Ok(_) => {
-
                cob::remove(self.repo, signer.public_key(), T::type_name(), id)?;
-
                self.repo.sign_refs(signer).map_err(Error::SignRefs)?;
-
                Ok(())
-
            }
-
            Err(git::Error::NotFound(_)) => Ok(()),
-
            Err(git::Error::Git(err)) if err.code() == git::raw::ErrorCode::NotFound => Ok(()),
-
            Err(err) => Err(Error::RefLookup {
-
                name: name.to_ref_string(),
-
                err,
-
            }),
-
        }
-
    }
}

/// Allows operations to be batched atomically.
@@ -310,7 +318,7 @@ impl<T: FromHistory> Transaction<T> {
    where
        G: Signer,
        F: FnOnce(&mut Self) -> Result<(), Error>,
-
        R: WriteRepository + cob::Store,
+
        R: ReadRepository + SignRepository + cob::Store,
        T::Action: Serialize + Clone,
    {
        let actor = *signer.public_key();
@@ -350,7 +358,7 @@ impl<T: FromHistory> Transaction<T> {
        signer: &G,
    ) -> Result<(Vec<cob::Op<T::Action>>, Lamport, EntryId), Error>
    where
-
        R: WriteRepository + cob::Store,
+
        R: ReadRepository + SignRepository + cob::Store,
        T::Action: Serialize + Clone,
    {
        let actions = NonEmpty::from_vec(self.actions)
modified radicle/src/lib.rs
@@ -33,7 +33,9 @@ pub mod prelude {
    pub use identity::{project::Project, Did, Doc, Id};
    pub use node::{Alias, NodeId, Timestamp};
    pub use profile::Profile;
-
    pub use storage::{BranchName, ReadRepository, ReadStorage, WriteRepository, WriteStorage};
+
    pub use storage::{
+
        BranchName, ReadRepository, ReadStorage, SignRepository, WriteRepository, WriteStorage,
+
    };
}

pub mod env {
modified radicle/src/rad.rs
@@ -15,7 +15,7 @@ use crate::identity::{doc, IdentityError};
use crate::storage::git::transport;
use crate::storage::git::Repository;
use crate::storage::refs::SignedRefs;
-
use crate::storage::{BranchName, ReadRepository as _, RemoteId};
+
use crate::storage::{BranchName, ReadRepository as _, RemoteId, SignRepository as _};
use crate::storage::{WriteRepository, WriteStorage};
use crate::{identity, storage};

modified radicle/src/storage.rs
@@ -450,18 +450,22 @@ pub trait ReadRepository: Sized {
}

/// Allows read-write access to a repository.
-
pub trait WriteRepository: ReadRepository {
+
pub trait WriteRepository: ReadRepository + SignRepository {
    /// Set the repository head to the canonical branch.
    /// This computes the head based on the delegate set.
    fn set_head(&self) -> Result<Oid, IdentityError>;
    /// Set the repository 'rad/id' to the canonical commit, agreed by quorum.
    fn set_identity_head(&self) -> Result<Oid, IdentityError>;
-
    /// Sign the repository's refs under the `refs/rad/sigrefs` branch.
-
    fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, Error>;
    /// Get the underlying git repository.
    fn raw(&self) -> &git2::Repository;
}

+
/// Allows signing refs.
+
pub trait SignRepository {
+
    /// Sign the repository's refs under the `refs/rad/sigrefs` branch.
+
    fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, Error>;
+
}
+

impl<T, S> ReadStorage for T
where
    T: Deref<Target = S>,
modified radicle/src/storage/git.rs
@@ -16,7 +16,8 @@ use crate::identity::{Identity, IdentityError, Project};
use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs};
use crate::storage::{
-
    Inventory, ReadRepository, ReadStorage, Remote, Remotes, WriteRepository, WriteStorage,
+
    Inventory, ReadRepository, ReadStorage, Remote, Remotes, SignRepository, WriteRepository,
+
    WriteStorage,
};

pub use crate::git::*;
@@ -628,6 +629,12 @@ impl WriteRepository for Repository {
        Ok(head)
    }

+
    fn raw(&self) -> &git2::Repository {
+
        &self.backend
+
    }
+
}
+

+
impl SignRepository for Repository {
    fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, Error> {
        let remote = signer.public_key();
        let refs = self.references_of(remote)?;
@@ -637,10 +644,6 @@ impl WriteRepository for Repository {

        Ok(signed)
    }
-

-
    fn raw(&self) -> &git2::Repository {
-
        &self.backend
-
    }
}

pub mod trailers {
@@ -703,7 +706,7 @@ mod tests {
    use super::*;
    use crate::git;
    use crate::storage::refs::SIGREFS_BRANCH;
-
    use crate::storage::{ReadRepository, ReadStorage, WriteRepository};
+
    use crate::storage::{ReadRepository, ReadStorage};
    use crate::test::arbitrary;
    use crate::test::fixtures;

modified radicle/src/storage/git/cob.rs
@@ -4,10 +4,18 @@ use std::collections::BTreeMap;
use cob::object::Objects;
use radicle_cob as cob;
use radicle_cob::change;
+
use storage::SignRepository;

-
use crate::git;
+
use crate::storage;
use crate::storage::Error;
-
use crate::storage::ReadRepository;
+
use crate::storage::{
+
    git::{Remote, Remotes, VerifyError},
+
    ReadRepository, Verified,
+
};
+
use crate::{
+
    git, identity,
+
    identity::{doc::DocError, IdentityError},
+
};

pub use crate::git::*;
pub use cob::*;
@@ -165,14 +173,24 @@ impl cob::object::Storage for Repository {
}

/// Stores draft collaborative objects.
-
pub struct DraftStore {
+
///
+
// This storage backend for COBs stores changes in a `draft/cobs/*` namespace,
+
// which allows for some of the features needed for code review. For
+
// example, users can draft comments and later decide to publish them.
+
pub struct DraftStore<'a> {
    remote: RemoteId,
-
    repo: Repository,
+
    repo: &'a Repository,
}

-
impl cob::Store for DraftStore {}
+
impl<'a> DraftStore<'a> {
+
    pub fn new(remote: RemoteId, repo: &'a Repository) -> Self {
+
        Self { remote, repo }
+
    }
+
}

-
impl change::Storage for DraftStore {
+
impl<'a> cob::Store for DraftStore<'a> {}
+

+
impl<'a> change::Storage for DraftStore<'a> {
    type StoreError = <git2::Repository as change::Storage>::StoreError;
    type LoadError = <git2::Repository as change::Storage>::LoadError;

@@ -202,7 +220,125 @@ impl change::Storage for DraftStore {
    }
}

-
impl cob::object::Storage for DraftStore {
+
impl<'a> SignRepository for DraftStore<'a> {
+
    fn sign_refs<G: crypto::Signer>(
+
        &self,
+
        signer: &G,
+
    ) -> Result<storage::refs::SignedRefs<Verified>, Error> {
+
        self.repo.sign_refs(signer)
+
    }
+
}
+

+
impl<'a> ReadRepository for DraftStore<'a> {
+
    fn id(&self) -> identity::Id {
+
        self.repo.id()
+
    }
+

+
    fn is_empty(&self) -> Result<bool, git2::Error> {
+
        self.repo.is_empty()
+
    }
+

+
    fn head(&self) -> Result<(fmt::Qualified, Oid), identity::IdentityError> {
+
        self.repo.head()
+
    }
+

+
    fn canonical_head(&self) -> Result<(fmt::Qualified, Oid), identity::IdentityError> {
+
        self.repo.canonical_head()
+
    }
+

+
    fn validate_remote(
+
        &self,
+
        remote: &Remote<Verified>,
+
    ) -> Result<Vec<fmt::RefString>, VerifyError> {
+
        self.repo.validate_remote(remote)
+
    }
+

+
    fn path(&self) -> &std::path::Path {
+
        self.repo.path()
+
    }
+

+
    fn remote(&self, id: &RemoteId) -> Result<Remote<Verified>, storage::refs::Error> {
+
        self.repo.remote(id)
+
    }
+

+
    fn remotes(&self) -> Result<Remotes<Verified>, storage::refs::Error> {
+
        ReadRepository::remotes(self.repo)
+
    }
+

+
    fn commit(&self, oid: Oid) -> Result<git2::Commit, git_ext::Error> {
+
        self.repo.commit(oid)
+
    }
+

+
    fn revwalk(&self, head: Oid) -> Result<git2::Revwalk, git2::Error> {
+
        self.repo.revwalk(head)
+
    }
+

+
    fn is_ancestor_of(&self, ancestor: Oid, head: Oid) -> Result<bool, git_ext::Error> {
+
        self.repo.is_ancestor_of(ancestor, head)
+
    }
+

+
    fn blob_at<'b>(
+
        &'b self,
+
        oid: git_ext::Oid,
+
        path: &'b std::path::Path,
+
    ) -> Result<git2::Blob<'b>, git_ext::Error> {
+
        self.repo.blob_at(oid, path)
+
    }
+

+
    fn reference(
+
        &self,
+
        remote: &RemoteId,
+
        reference: &git::Qualified,
+
    ) -> Result<git2::Reference, git_ext::Error> {
+
        self.repo.reference(remote, reference)
+
    }
+

+
    fn reference_oid(
+
        &self,
+
        remote: &RemoteId,
+
        reference: &git::Qualified,
+
    ) -> Result<git_ext::Oid, git_ext::Error> {
+
        self.repo.reference_oid(remote, reference)
+
    }
+

+
    fn references_of(&self, remote: &RemoteId) -> Result<crate::storage::refs::Refs, Error> {
+
        self.repo.references_of(remote)
+
    }
+

+
    fn references_glob(
+
        &self,
+
        pattern: &git::PatternStr,
+
    ) -> Result<Vec<(fmt::Qualified, Oid)>, git::ext::Error> {
+
        self.repo.references_glob(pattern)
+
    }
+

+
    fn identity_doc(
+
        &self,
+
    ) -> Result<(Oid, crate::identity::Doc<crate::crypto::Unverified>), IdentityError> {
+
        self.repo.identity_doc()
+
    }
+

+
    fn identity_doc_at(
+
        &self,
+
        head: Oid,
+
    ) -> Result<crate::identity::Doc<crate::crypto::Unverified>, DocError> {
+
        self.repo.identity_doc_at(head)
+
    }
+

+
    fn identity_head(&self) -> Result<Oid, IdentityError> {
+
        self.repo.identity_head()
+
    }
+

+
    fn canonical_identity_head(&self) -> Result<Oid, IdentityError> {
+
        self.repo.canonical_identity_head()
+
    }
+

+
    fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, git::ext::Error> {
+
        self.repo.merge_base(left, right)
+
    }
+
}
+

+
impl<'a> cob::object::Storage for DraftStore<'a> {
    type ObjectsError = ObjectsError;
    type TypesError = git::ext::Error;
    type UpdateError = git2::Error;
@@ -276,6 +412,6 @@ impl cob::object::Storage for DraftStore {
            git::refs::storage::draft::cob(identifier, typename, object_id).as_str(),
        )?;

-
        reference.delete().map_err(Self::RemoveError::from)
+
        reference.delete()
    }
}
modified radicle/src/test/storage.rs
@@ -248,14 +248,16 @@ impl WriteRepository for MockRepository {
        todo!()
    }

+
    fn set_identity_head(&self) -> Result<Oid, IdentityError> {
+
        todo!()
+
    }
+
}
+

+
impl SignRepository for MockRepository {
    fn sign_refs<G: Signer>(
        &self,
        _signer: &G,
    ) -> Result<crate::storage::refs::SignedRefs<Verified>, Error> {
        todo!()
    }
-

-
    fn set_identity_head(&self) -> Result<Oid, IdentityError> {
-
        todo!()
-
    }
}