Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
Make `ReadStorage` more usable
Alexis Sellier committed 3 years ago
commit 950fae209d954ba946e74ed4990077b99da197ce
parent 89fcc6de1cc4ebdadf3b3c8542f0b6464090cfc0
31 files changed +134 -71
modified radicle-cli/src/commands/assign.rs
@@ -83,7 +83,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let signer = term::signer(&profile)?;
    let storage = &profile.storage;
    let (_, id) = radicle::rad::cwd()?;
-
    let repo = storage.repository(id)?;
+
    let repo = storage.repository_mut(id)?;
    let mut issues = issue::Issues::open(*signer.public_key(), &repo)?;

    let mut issue = issues.get_mut(&options.id).map_err(|err| match err {
modified radicle-cli/src/commands/checkout.rs
@@ -7,7 +7,6 @@ use anyhow::Context as _;
use radicle::prelude::*;
use radicle::storage::git::transport;
use radicle::storage::RemoteId;
-
use radicle::storage::WriteStorage;

use crate::project;
use crate::terminal as term;
modified radicle-cli/src/commands/clone.rs
@@ -15,7 +15,6 @@ use radicle::prelude::*;
use radicle::rad;
use radicle::storage;
use radicle::storage::git::{ProjectError, Storage};
-
use radicle::storage::WriteStorage;

use crate::commands::rad_checkout as checkout;
use crate::project;
modified radicle-cli/src/commands/comment.rs
@@ -10,7 +10,6 @@ use radicle::cob::store;
use radicle::cob::thread;
use radicle::prelude::*;
use radicle::storage;
-
use radicle::storage::WriteStorage;

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
modified radicle-cli/src/commands/delegate/add.rs
@@ -19,7 +19,7 @@ where
        .get(&profile.public_key, id)?
        .context("No project with such ID exists")?;

-
    let repo = storage.repository(id)?;
+
    let repo = storage.repository_mut(id)?;

    if !project.is_delegate(me) {
        return Err(anyhow::anyhow!(
modified radicle-cli/src/commands/delegate/remove.rs
@@ -19,7 +19,7 @@ where
        .get(&profile.public_key, id)?
        .context("No project with such ID exists")?;

-
    let repo = storage.repository(id)?;
+
    let repo = storage.repository_mut(id)?;

    if !project.is_delegate(me) {
        return Err(anyhow::anyhow!(
modified radicle-cli/src/commands/edit.rs
@@ -4,7 +4,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Context as _};

use radicle::identity::Id;
-
use radicle::storage::{ReadStorage, WriteRepository, WriteStorage};
+
use radicle::storage::{ReadStorage, WriteRepository};

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
modified radicle-cli/src/commands/id.rs
@@ -5,7 +5,7 @@ use radicle::cob::identity::{self, Proposal, ProposalId, Proposals, Revision, Re
use radicle::git::Oid;
use radicle::identity::Identity;
use radicle::prelude::Doc;
-
use radicle::storage::WriteStorage as _;
+
use radicle::storage::ReadStorage as _;
use radicle_crypto::{PublicKey, Verified};

use crate::terminal::args::{Args, Error, Help};
modified radicle-cli/src/commands/inspect.rs
@@ -11,7 +11,7 @@ use json_color::{Color, Colorizer};
use radicle::crypto::Unverified;
use radicle::identity::Untrusted;
use radicle::identity::{Doc, Id};
-
use radicle::storage::{ReadRepository, ReadStorage, WriteStorage};
+
use radicle::storage::{ReadRepository, ReadStorage};

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
modified radicle-cli/src/commands/issue.rs
@@ -198,7 +198,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let signer = term::signer(&profile)?;
    let storage = &profile.storage;
    let (_, id) = radicle::rad::cwd()?;
-
    let repo = storage.repository(id)?;
+
    let repo = storage.repository_mut(id)?;
    let mut issues = Issues::open(*signer.public_key(), &repo)?;

    match options.op {
modified radicle-cli/src/commands/ls.rs
@@ -3,7 +3,7 @@ use std::ffi::OsString;
use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};

-
use radicle::storage::{ReadRepository, WriteStorage};
+
use radicle::storage::{ReadRepository, ReadStorage};

pub const HELP: Help = Help {
    name: "ls",
modified radicle-cli/src/commands/track.rs
@@ -4,7 +4,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Context as _};

use radicle::node::{Handle, NodeId};
-
use radicle::storage::WriteStorage;
+
use radicle::storage::ReadStorage;

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
modified radicle-cli/src/commands/unassign.rs
@@ -83,7 +83,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let signer = term::signer(&profile)?;
    let storage = &profile.storage;
    let (_, id) = radicle::rad::cwd()?;
-
    let repo = storage.repository(id)?;
+
    let repo = storage.repository_mut(id)?;
    let mut issues = issue::Issues::open(*signer.public_key(), &repo)?;

    let mut issue = issues.get_mut(&options.id).map_err(|err| match err {
modified radicle-cli/src/commands/untrack.rs
@@ -5,7 +5,7 @@ use anyhow::{anyhow, Context as _};
use radicle::identity::Id;
use radicle::node::Handle;
use radicle::prelude::*;
-
use radicle::storage::WriteStorage;
+
use radicle::storage::ReadStorage;

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
modified radicle-cli/tests/commands.rs
@@ -6,7 +6,7 @@ use std::{thread, time};
use radicle::git;
use radicle::prelude::Id;
use radicle::profile::Home;
-
use radicle::storage::{ReadRepository, WriteStorage};
+
use radicle::storage::{ReadRepository, ReadStorage};
use radicle::test::fixtures;

use radicle_cli_test::TestFormula;
modified radicle-httpd/src/api.rs
@@ -14,7 +14,7 @@ use tower_http::cors::{self, CorsLayer};

use radicle::cob::issue::Issues;
use radicle::identity::Id;
-
use radicle::storage::{ReadRepository, WriteStorage};
+
use radicle::storage::{ReadRepository, ReadStorage};
use radicle::Profile;

mod auth;
modified radicle-httpd/src/api/test.rs
@@ -13,7 +13,7 @@ use tower::ServiceExt;
use radicle::cob::issue::Issues;
use radicle::cob::patch::{MergeTarget, Patches};
use radicle::git::raw as git2;
-
use radicle::storage::WriteStorage;
+
use radicle::storage::ReadStorage;
use radicle_cli::commands::rad_init;
use radicle_crypto::ssh::keystore::MemorySigner;
use radicle_crypto::Signer;
modified radicle-httpd/src/api/v1/delegates.rs
@@ -5,7 +5,7 @@ use axum::{Json, Router};

use radicle::cob::issue::Issues;
use radicle::identity::Did;
-
use radicle::storage::{ReadRepository, WriteStorage};
+
use radicle::storage::{ReadRepository, ReadStorage};

use crate::api::axum_extra::{Path, Query};
use crate::api::error::Error;
modified radicle-httpd/src/api/v1/projects.rs
@@ -17,7 +17,7 @@ use radicle::cob::patch::Patches;
use radicle::cob::{thread, ActorId, Tag};
use radicle::identity::Id;
use radicle::node::NodeId;
-
use radicle::storage::{git::paths, ReadRepository, WriteStorage};
+
use radicle::storage::{git::paths, ReadRepository, ReadStorage};
use radicle_surf::{Glob, Oid, Repository};

use crate::api::axum_extra::{Path, Query};
modified radicle-node/src/service/message.rs
@@ -10,7 +10,7 @@ use crate::service::filter::Filter;
use crate::service::{NodeId, Timestamp};
use crate::storage;
use crate::storage::refs::SignedRefs;
-
use crate::storage::{ReadRepository, WriteStorage};
+
use crate::storage::{ReadRepository, ReadStorage};
use crate::wire;

/// Maximum number of addresses which can be announced to other nodes.
@@ -154,8 +154,14 @@ pub struct RefsAnnouncement {

impl RefsAnnouncement {
    /// Check if this announcement is "fresh", meaning if it contains refs we do not have.
-
    pub fn is_fresh<S: WriteStorage>(&self, storage: S) -> Result<bool, storage::Error> {
-
        let repo = storage.repository(self.rid)?;
+
    pub fn is_fresh<S: ReadStorage>(&self, storage: S) -> Result<bool, storage::Error> {
+
        let repo = match storage.repository(self.rid) {
+
            // If the repo doesn't exist, we consider this announcement "fresh", since we
+
            // obviously don't have the refs.
+
            Err(e) if e.is_not_found() => return Ok(true),
+
            Err(e) => return Err(e),
+
            Ok(r) => r,
+
        };

        for (remote_id, theirs) in self.refs.iter() {
            if let Ok(ours) = repo.remote(remote_id) {
modified radicle-node/src/test/simulator.rs
@@ -411,7 +411,11 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
                    Input::Fetched(f, result) => {
                        let result = Rc::try_unwrap(result).unwrap();
                        if f.initiated {
-
                            let mut repo = p.storage().repository(f.rid).unwrap();
+
                            let mut repo = match p.storage().repository_mut(f.rid) {
+
                                Ok(repo) => repo,
+
                                Err(e) if e.is_not_found() => p.storage().create(f.rid).unwrap(),
+
                                Err(e) => panic!("Failed to open repository: {e}"),
+
                            };
                            fetch(&mut repo, &f.remote, f.namespaces.clone()).unwrap();
                        }
                        p.fetched(f, result);
modified radicle-node/src/tests/e2e.rs
@@ -2,7 +2,7 @@ use std::{thread, time};

use radicle::crypto::Signer;
use radicle::node::{FetchResult, Handle as _};
-
use radicle::storage::{ReadRepository, WriteStorage};
+
use radicle::storage::{ReadRepository, ReadStorage};
use radicle::{assert_matches, rad};

use crate::service;
modified radicle-node/src/worker.rs
@@ -166,7 +166,11 @@ impl<G: Signer + Ecdh + 'static> Worker<G> {
        fetch: &Fetch,
        tunnel: &mut Tunnel<WireSession<G>>,
    ) -> Result<Vec<RefUpdate>, FetchError> {
-
        let repo = self.storage.repository(fetch.rid)?;
+
        let repo = match self.storage.repository_mut(fetch.rid) {
+
            Ok(r) => Ok(r),
+
            Err(e) if e.is_not_found() => self.storage.create(fetch.rid),
+
            Err(e) => Err(e),
+
        }?;
        let tunnel_addr = tunnel.local_addr()?;
        let mut cmd = process::Command::new("git");
        cmd.current_dir(repo.path())
modified radicle-remote-helper/src/lib.rs
@@ -55,7 +55,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Box<dyn std::error::Error +
    // Default to profile key.
    let namespace = url.namespace.unwrap_or(profile.public_key);

-
    let proj = profile.storage.repository(url.repo)?;
+
    let proj = profile.storage.repository_mut(url.repo)?;
    if proj.is_empty()? {
        return Err(Error::RepositoryNotFound(proj.path().to_path_buf()).into());
    }
modified radicle-tools/src/rad-push.rs
@@ -1,6 +1,6 @@
use std::path::Path;

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

fn main() -> anyhow::Result<()> {
    let cwd = Path::new(".").canonicalize()?;
modified radicle/src/identity.rs
@@ -154,7 +154,7 @@ mod test {
    use crate::crypto::PublicKey;
    use crate::rad;
    use crate::storage::git::Storage;
-
    use crate::storage::{ReadStorage, WriteRepository, WriteStorage};
+
    use crate::storage::{ReadStorage, WriteRepository};
    use crate::test::fixtures;

    use super::did::Did;
modified radicle/src/identity/doc.rs
@@ -371,7 +371,7 @@ mod test {
    use crate::rad;
    use crate::storage::git::transport;
    use crate::storage::git::Storage;
-
    use crate::storage::WriteStorage;
+
    use crate::storage::WriteStorage as _;
    use crate::test::arbitrary;
    use crate::test::fixtures;

@@ -414,7 +414,7 @@ mod test {
        let storage = Storage::open(tempdir.path().join("storage")).unwrap();
        let remote = arbitrary::gen::<RemoteId>(1);
        let proj = arbitrary::gen::<Id>(1);
-
        let repo = storage.repository(proj).unwrap();
+
        let repo = storage.create(proj).unwrap();
        let oid = git2::Oid::from_str("2d52a53ce5e4f141148a5f770cfd3ead2d6a45b8").unwrap();

        let err = Doc::<Unverified>::head(&remote, &repo).unwrap_err();
modified radicle/src/rad.rs
@@ -14,7 +14,8 @@ use crate::identity::project::Project;
use crate::storage::git::transport;
use crate::storage::git::{ProjectError, Repository, Storage};
use crate::storage::refs::SignedRefs;
-
use crate::storage::{BranchName, ReadRepository as _, RemoteId, WriteRepository as _};
+
use crate::storage::WriteRepository;
+
use crate::storage::{BranchName, ReadRepository as _, RemoteId};
use crate::{identity, storage};

/// Name of the radicle storage remote.
@@ -129,7 +130,7 @@ pub fn fork_remote<G: Signer, S: storage::WriteStorage>(
        .get(remote, proj)?
        .ok_or(ForkError::NotFound(proj))?;
    let project = doc.project()?;
-
    let repository = storage.repository(proj)?;
+
    let repository = storage.repository_mut(proj)?;

    let raw = repository.raw();
    let remote_head = raw.refname_to_id(&git::refs::storage::branch(
@@ -162,7 +163,7 @@ pub fn fork<G: Signer, S: storage::WriteStorage>(
    storage: &S,
) -> Result<(), ForkError> {
    let me = signer.public_key();
-
    let repository = storage.repository(proj)?;
+
    let repository = storage.repository_mut(proj)?;
    // TODO: We should get the id branch pointer from a stored canonical reference.
    let (canonical_id, _) = repository.identity_doc()?;
    let (canonical_branch, canonical_head) = repository.head()?;
@@ -302,7 +303,7 @@ mod tests {
    use crate::identity::Did;
    use crate::storage::git::transport;
    use crate::storage::git::Storage;
-
    use crate::storage::{ReadStorage, WriteStorage};
+
    use crate::storage::ReadStorage;
    use crate::test::fixtures;

    use super::*;
modified radicle/src/storage.rs
@@ -70,6 +70,18 @@ pub enum Error {
    Io(#[from] io::Error),
}

+
impl Error {
+
    /// Whether this error is caused by something not being found.
+
    pub fn is_not_found(&self) -> bool {
+
        match self {
+
            Self::Io(e) if e.kind() == io::ErrorKind::NotFound => true,
+
            Self::Git(e) if git::is_not_found_err(e) => true,
+
            Self::Doc(e) if e.is_not_found() => true,
+
            _ => false,
+
        }
+
    }
+
}
+

/// Fetch error.
#[derive(Error, Debug)]
#[allow(clippy::large_enum_variant)]
@@ -246,6 +258,8 @@ impl Remote<Verified> {

/// Read-only operations on a storage instance.
pub trait ReadStorage {
+
    type Repository: ReadRepository;
+

    /// Get the storage base path.
    fn path(&self) -> &Path;
    /// Get an identity document of a repository under a given remote.
@@ -258,13 +272,18 @@ pub trait ReadStorage {
    fn contains(&self, rid: &Id) -> Result<bool, ProjectError>;
    /// Get the inventory of repositories hosted under this storage.
    fn inventory(&self) -> Result<Inventory, Error>;
+
    /// Open or create a read-only repository.
+
    fn repository(&self, rid: Id) -> Result<Self::Repository, Error>;
}

/// Allows access to individual storage repositories.
pub trait WriteStorage: ReadStorage {
-
    type Repository: WriteRepository;
+
    type RepositoryMut: WriteRepository;

-
    fn repository(&self, proj: Id) -> Result<Self::Repository, Error>;
+
    /// Open a read-write repository.
+
    fn repository_mut(&self, rid: Id) -> Result<Self::RepositoryMut, Error>;
+
    /// Create a read-write repository.
+
    fn create(&self, rid: Id) -> Result<Self::RepositoryMut, Error>;
}

/// Allows read-only access to a repository.
@@ -355,6 +374,8 @@ where
    T: Deref<Target = S>,
    S: ReadStorage + 'static,
{
+
    type Repository = S::Repository;
+

    fn path(&self) -> &Path {
        self.deref().path()
    }
@@ -374,6 +395,10 @@ where
    ) -> Result<Option<identity::Doc<Verified>>, ProjectError> {
        self.deref().get(remote, proj)
    }
+

+
    fn repository(&self, rid: Id) -> Result<Self::Repository, Error> {
+
        self.deref().repository(rid)
+
    }
}

impl<T, S> WriteStorage for T
@@ -381,10 +406,14 @@ where
    T: Deref<Target = S>,
    S: WriteStorage + 'static,
{
-
    type Repository = S::Repository;
+
    type RepositoryMut = S::RepositoryMut;
+

+
    fn repository_mut(&self, rid: Id) -> Result<Self::RepositoryMut, Error> {
+
        self.deref().repository_mut(rid)
+
    }

-
    fn repository(&self, proj: Id) -> Result<Self::Repository, Error> {
-
        self.deref().repository(proj)
+
    fn create(&self, rid: Id) -> Result<Self::RepositoryMut, Error> {
+
        self.deref().create(rid)
    }
}

modified radicle/src/storage/git.rs
@@ -95,6 +95,8 @@ pub struct Storage {
}

impl ReadStorage for Storage {
+
    type Repository = Repository;
+

    fn path(&self) -> &Path {
        self.path.as_path()
    }
@@ -108,27 +110,37 @@ impl ReadStorage for Storage {
    }

    fn get(&self, remote: &RemoteId, proj: Id) -> Result<Option<Doc<Verified>>, ProjectError> {
-
        // TODO: Don't create a repo here if it doesn't exist?
-
        // Perhaps for checking we could have a `contains` method?
-
        match self.repository(proj)?.identity_of(remote) {
+
        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) {
            Ok(doc) => Ok(Some(doc)),
-

-
            Err(err) if err.is_not_found() => Ok(None),
-
            Err(err) => Err(err),
+
            Err(e) if e.is_not_found() => Ok(None),
+
            Err(e) => Err(e),
        }
    }

    fn inventory(&self) -> Result<Inventory, Error> {
        self.repositories()
    }
+

+
    fn repository(&self, rid: Id) -> Result<Self::Repository, Error> {
+
        Repository::open(paths::repository(self, &rid), rid)
+
    }
}

impl WriteStorage for Storage {
-
    type Repository = Repository;
+
    type RepositoryMut = Repository;

-
    fn repository(&self, rid: Id) -> Result<Self::Repository, Error> {
+
    fn repository_mut(&self, rid: Id) -> Result<Self::RepositoryMut, Error> {
        Repository::open(paths::repository(self, &rid), rid)
    }
+

+
    fn create(&self, rid: Id) -> Result<Self::RepositoryMut, Error> {
+
        Repository::create(paths::repository(self, &rid), rid)
+
    }
}

impl Storage {
@@ -207,27 +219,27 @@ pub enum VerifyError {
}

impl Repository {
+
    /// Open an existing repository.
    pub fn open<P: AsRef<Path>>(path: P, id: Id) -> Result<Self, Error> {
-
        let backend = match git2::Repository::open_bare(path.as_ref()) {
-
            Err(e) if ext::is_not_found_err(&e) => {
-
                let backend = git2::Repository::init_opts(
-
                    &path,
-
                    git2::RepositoryInitOptions::new()
-
                        .bare(true)
-
                        .no_reinit(true)
-
                        .external_template(false),
-
                )?;
-
                let mut config = backend.config()?;
-

-
                // TODO: Get ahold of user name and/or key.
-
                config.set_str("user.name", "radicle")?;
-
                config.set_str("user.email", "radicle@localhost")?;
-

-
                Ok(backend)
-
            }
-
            Ok(repo) => Ok(repo),
-
            Err(e) => Err(e),
-
        }?;
+
        let backend = git2::Repository::open_bare(path.as_ref())?;
+

+
        Ok(Self { id, backend })
+
    }
+

+
    /// Create a new repository.
+
    pub fn create<P: AsRef<Path>>(path: P, id: Id) -> Result<Self, Error> {
+
        let backend = git2::Repository::init_opts(
+
            &path,
+
            git2::RepositoryInitOptions::new()
+
                .bare(true)
+
                .no_reinit(true)
+
                .external_template(false),
+
        )?;
+
        let mut config = backend.config()?;
+

+
        // TODO: Get ahold of user name and/or key.
+
        config.set_str("user.name", "radicle")?;
+
        config.set_str("user.email", "radicle@localhost")?;

        Ok(Self { id, backend })
    }
@@ -241,7 +253,7 @@ impl Repository {
    ) -> Result<(Self, git::Oid), Error> {
        let (doc_oid, doc) = doc.encode()?;
        let id = Id::from(doc_oid);
-
        let repo = Self::open(paths::repository(storage, &id), id)?;
+
        let repo = Self::create(paths::repository(storage, &id), id)?;
        let oid = Doc::init(
            doc.as_slice(),
            remote,
@@ -730,7 +742,7 @@ mod tests {
        let storage = Storage::open(tmp.path()).unwrap();
        let proj_id = arbitrary::gen::<Id>(1);
        let alice = *signer.public_key();
-
        let project = storage.repository(proj_id).unwrap();
+
        let project = storage.create(proj_id).unwrap();
        let backend = &project.backend;
        let sig = git2::Signature::now(&alice.to_string(), "anonymous@radicle.xyz").unwrap();
        let head = git::initial_commit(backend, &sig).unwrap();
modified radicle/src/test/storage.rs
@@ -32,6 +32,8 @@ impl MockStorage {
}

impl ReadStorage for MockStorage {
+
    type Repository = MockRepository;
+

    fn path(&self) -> &Path {
        self.path.as_path()
    }
@@ -51,12 +53,20 @@ impl ReadStorage for MockStorage {
    fn inventory(&self) -> Result<Inventory, Error> {
        Ok(self.inventory.keys().cloned().collect::<Vec<_>>())
    }
+

+
    fn repository(&self, _proj: Id) -> Result<Self::Repository, Error> {
+
        Ok(MockRepository {})
+
    }
}

impl WriteStorage for MockStorage {
-
    type Repository = MockRepository;
+
    type RepositoryMut = MockRepository;

-
    fn repository(&self, _proj: Id) -> Result<Self::Repository, Error> {
+
    fn repository_mut(&self, _rid: Id) -> Result<Self::RepositoryMut, Error> {
+
        Ok(MockRepository {})
+
    }
+

+
    fn create(&self, _rid: Id) -> Result<Self::RepositoryMut, Error> {
        Ok(MockRepository {})
    }
}