Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Implement project retrieval
Alexis Sellier committed 3 years ago
commit c3b7a6fbd7ad4c70484222306ffaa2ef25781363
parent 910cd74294dc3f86851c04829bc73a37924caef1
9 files changed +152 -48
modified node/src/git.rs
@@ -1,16 +1,16 @@
use std::str::FromStr;

use git_ref_format as format;
-
use radicle_git_ext as git_ext;

use crate::collections::HashMap;
use crate::identity::UserId;
use crate::storage::refs::Refs;
use crate::storage::RemoteId;

-
pub use git_ext::Oid;
+
pub use ext::Oid;
pub use git_ref_format::{refname, RefStr, RefString};
pub use git_url::Url;
+
pub use radicle_git_ext as ext;

/// Default port of the `git` transport protocol.
pub const PROTOCOL_PORT: u16 = 9418;
@@ -118,14 +118,14 @@ pub fn set_upstream(
    let branch_merge = format!("branch.{}.merge", branch);

    config.remove_multivar(&branch_remote, ".*").or_else(|e| {
-
        if git_ext::is_not_found_err(&e) {
+
        if ext::is_not_found_err(&e) {
            Ok(())
        } else {
            Err(e)
        }
    })?;
    config.remove_multivar(&branch_merge, ".*").or_else(|e| {
-
        if git_ext::is_not_found_err(&e) {
+
        if ext::is_not_found_err(&e) {
            Ok(())
        } else {
            Err(e)
modified node/src/identity.rs
@@ -7,8 +7,9 @@ use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize};
use thiserror::Error;

-
use crate::crypto;
+
use crate::crypto::{self, Verified};
use crate::hash;
+
use crate::storage::Remotes;

pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new("Radicle.toml"));

@@ -86,6 +87,17 @@ impl fmt::Display for Did {
    }
}

+
/// A stored and verified project.
+
#[derive(Debug, Clone)]
+
pub struct Project {
+
    /// The project identifier.
+
    pub id: ProjId,
+
    /// The latest project identity document.
+
    pub doc: Doc,
+
    /// The project remotes.
+
    pub remotes: Remotes<Verified>,
+
}
+

#[derive(Error, Debug)]
pub enum DocError {
    #[error("toml: {0}")]
@@ -94,20 +106,20 @@ pub enum DocError {
    Io(#[from] io::Error),
}

-
#[derive(Serialize, Deserialize)]
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Delegate {
    pub name: String,
    pub id: Did,
}

-
#[derive(Serialize, Deserialize)]
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Doc {
    pub name: String,
    pub description: String,
    pub default_branch: String,
    pub version: u32,
    pub parent: Option<Oid>,
-
    pub delegate: NonEmpty<Delegate>,
+
    pub delegates: NonEmpty<Delegate>,
}

impl Doc {
@@ -120,6 +132,10 @@ impl Doc {

        Ok(id)
    }
+

+
    pub fn from_toml(bytes: &[u8]) -> Result<Self, toml::de::Error> {
+
        toml::from_slice(bytes)
+
    }
}

#[cfg(test)]
modified node/src/protocol.rs
@@ -19,12 +19,12 @@ use crate::address_manager::AddressManager;
use crate::clock::RefClock;
use crate::collections::{HashMap, HashSet};
use crate::crypto;
-
use crate::identity::{ProjId, UserId};
+
use crate::identity::{ProjId, Project, UserId};
use crate::protocol::config::ProjectTracking;
use crate::protocol::message::Message;
use crate::protocol::peer::{Peer, PeerError, PeerState};
use crate::storage::{self, ReadRepository, WriteRepository};
-
use crate::storage::{Inventory, ReadStorage, Remotes, WriteStorage};
+
use crate::storage::{Inventory, ReadStorage, WriteStorage};

pub use crate::protocol::config::{Config, Network};

@@ -546,7 +546,7 @@ impl<S, T, G> Iterator for Protocol<S, T, G> {
#[derive(Debug)]
pub struct Lookup {
    /// Whether the project was found locally or not.
-
    pub local: Option<Remotes<crypto::Verified>>,
+
    pub local: Option<Project>,
    /// A list of remote peers on which the project is known to exist.
    pub remote: Vec<NodeId>,
}
modified node/src/rad.rs
@@ -52,7 +52,7 @@ pub fn init<S: storage::WriteStorage>(
        default_branch: default_branch.clone(),
        version: 1,
        parent: None,
-
        delegate: NonEmpty::new(delegate),
+
        delegates: NonEmpty::new(delegate),
    };

    let filename = *identity::IDENTITY_PATH;
@@ -123,7 +123,9 @@ pub fn init<S: storage::WriteStorage>(
mod tests {
    use super::*;
    use crate::git;
+
    use crate::identity::{Delegate, Did};
    use crate::storage::git::Storage;
+
    use crate::storage::ReadStorage;
    use crate::test::crypto;

    #[test]
@@ -137,7 +139,7 @@ mod tests {
        let head = git::commit(&repo, &head, "Second commit", "anonymous").unwrap();
        let _branch = repo.branch("master", &head, false).unwrap();

-
        let (_id, _refs) = init(
+
        let (id, refs) = init(
            &repo,
            "acme",
            "Acme's repo",
@@ -145,5 +147,20 @@ mod tests {
            &mut storage,
        )
        .unwrap();
+

+
        let project = storage.get(&id).unwrap().unwrap();
+

+
        assert_eq!(project.remotes[storage.user_id()].refs, refs);
+
        assert_eq!(project.id, id);
+
        assert_eq!(project.doc.name, "acme");
+
        assert_eq!(project.doc.description, "Acme's repo");
+
        assert_eq!(project.doc.default_branch, BranchName::from("master"));
+
        assert_eq!(
+
            project.doc.delegates.first(),
+
            &Delegate {
+
                name: String::from("anonymous"),
+
                id: Did::from(*storage.user_id()),
+
            }
+
        );
    }
}
modified node/src/storage.rs
@@ -18,7 +18,7 @@ use crate::crypto::{self, Unverified, Verified};
use crate::git::Url;
use crate::git::{RefError, RefStr};
use crate::identity;
-
use crate::identity::{ProjId, ProjIdError, UserId};
+
use crate::identity::{ProjId, ProjIdError, Project, UserId};
use crate::storage::refs::Refs;

use self::refs::SignedRefs;
@@ -168,7 +168,7 @@ impl Remote<Verified> {
pub trait ReadStorage {
    fn user_id(&self) -> &UserId;
    fn url(&self) -> Url;
-
    fn get(&self, proj: &ProjId) -> Result<Option<Remotes<Verified>>, Error>;
+
    fn get(&self, proj: &ProjId) -> Result<Option<Project>, Error>;
    fn inventory(&self) -> Result<Inventory, Error>;
}

@@ -215,7 +215,7 @@ where
        self.deref().inventory()
    }

-
    fn get(&self, proj: &ProjId) -> Result<Option<Remotes<Verified>>, Error> {
+
    fn get(&self, proj: &ProjId) -> Result<Option<Project>, Error> {
        self.deref().get(proj)
    }
}
modified node/src/storage/git.rs
@@ -5,14 +5,14 @@ use std::{fmt, fs, io};

use git_ref_format::refspec;
use once_cell::sync::Lazy;
-
use radicle_git_ext as git_ext;

pub use radicle_git_ext::Oid;

use crate::collections::HashMap;
use crate::crypto::{Signer, Verified};
use crate::git;
-
use crate::identity::{ProjId, UserId};
+
use crate::identity::{self, IDENTITY_PATH};
+
use crate::identity::{ProjId, Project, UserId};
use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs};
use crate::storage::{
@@ -21,8 +21,7 @@ use crate::storage::{

use super::RemoteId;

-
pub static RADICLE_ID_REF: Lazy<refspec::PatternString> =
-
    Lazy::new(|| refspec::pattern!("heads/radicle/id"));
+
pub static RADICLE_ID_REF: Lazy<git::RefString> = Lazy::new(|| git::refname!("heads/radicle/id"));
pub static REMOTES_GLOB: Lazy<refspec::PatternString> =
    Lazy::new(|| refspec::pattern!("refs/remotes/*"));
pub static SIGNATURES_GLOB: Lazy<refspec::PatternString> =
@@ -52,19 +51,26 @@ impl ReadStorage for Storage {
        }
    }

-
    fn get(&self, id: &ProjId) -> Result<Option<Remotes<Verified>>, Error> {
+
    fn get(&self, id: &ProjId) -> Result<Option<Project>, Error> {
        // TODO: Don't create a repo here if it doesn't exist?
-
        //       Perhaps for checking we could have a `contains` method?
-
        match self.repository(id) {
-
            Ok(r) => {
-
                let remotes = r.remotes()?;
-
                if remotes.is_empty() {
-
                    Ok(None)
-
                } else {
-
                    Ok(Some(remotes))
-
                }
-
            }
-
            Err(e) => Err(e),
+
        // Perhaps for checking we could have a `contains` method?
+
        let local = self.user_id();
+
        let repo = self.repository(id)?;
+

+
        if let Some(doc) = repo.identity(local)? {
+
            let remotes = repo.remotes()?;
+

+
            // TODO: We should check that there is at least one remote, which is
+
            // the one of the local user, otherwise it means the project is in
+
            // an corrupted state.
+

+
            Ok(Some(Project {
+
                id: id.clone(),
+
                doc,
+
                remotes,
+
            }))
+
        } else {
+
            Ok(None)
        }
    }

@@ -163,7 +169,7 @@ pub struct Repository {
impl Repository {
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
        let backend = match git2::Repository::open_bare(path.as_ref()) {
-
            Err(e) if git_ext::is_not_found_err(&e) => {
+
            Err(e) if git::ext::is_not_found_err(&e) => {
                let backend = git2::Repository::init_opts(
                    path,
                    git2::RepositoryInitOptions::new()
@@ -196,6 +202,23 @@ impl Repository {
        }
        Ok(())
    }
+

+
    pub fn identity(&self, remote: &RemoteId) -> Result<Option<identity::Doc>, refs::Error> {
+
        let oid = if let Some(oid) = self.reference_oid(remote, &RADICLE_ID_REF)? {
+
            oid
+
        } else {
+
            return Ok(None);
+
        };
+

+
        let doc = match self.blob_at(oid, Path::new(&*IDENTITY_PATH)) {
+
            Err(git::ext::Error::NotFound(_)) => return Ok(None),
+
            Err(e) => return Err(e.into()),
+
            Ok(doc) => doc,
+
        };
+
        let doc = identity::Doc::from_toml(doc.content()).unwrap();
+

+
        Ok(Some(doc))
+
    }
}

impl ReadRepository for Repository {
@@ -203,8 +226,8 @@ impl ReadRepository for Repository {
        self.backend.path()
    }

-
    fn blob_at<'a>(&'a self, oid: Oid, path: &'a Path) -> Result<git2::Blob<'a>, git_ext::Error> {
-
        git_ext::Blob::At {
+
    fn blob_at<'a>(&'a self, oid: Oid, path: &'a Path) -> Result<git2::Blob<'a>, git::ext::Error> {
+
        git::ext::Blob::At {
            object: oid.into(),
            path,
        }
@@ -218,7 +241,7 @@ impl ReadRepository for Repository {
    ) -> Result<Option<git2::Reference>, git2::Error> {
        let name = format!("refs/remotes/{remote}/{name}");
        self.backend.find_reference(&name).map(Some).or_else(|e| {
-
            if git_ext::is_not_found_err(&e) {
+
            if git::ext::is_not_found_err(&e) {
                Ok(None)
            } else {
                Err(e)
@@ -228,10 +251,10 @@ impl ReadRepository for Repository {

    fn reference_oid(
        &self,
-
        user: &RemoteId,
+
        remote: &RemoteId,
        reference: &git::RefStr,
    ) -> Result<Option<Oid>, git2::Error> {
-
        let reference = self.reference(user, reference)?;
+
        let reference = self.reference(remote, reference)?;
        Ok(reference.and_then(|r| r.target().map(|o| o.into())))
    }

modified node/src/test/arbitrary.rs
@@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashSet};
use std::hash::Hash;
use std::ops::RangeBounds;

+
use nonempty::NonEmpty;
use quickcheck::Arbitrary;

use crate::collections::HashMap;
@@ -9,7 +10,7 @@ use crate::crypto::{self, Signer};
use crate::crypto::{PublicKey, SecretKey};
use crate::git;
use crate::hash;
-
use crate::identity::ProjId;
+
use crate::identity::{Delegate, Did, Doc, ProjId, Project};
use crate::storage;
use crate::storage::refs::Refs;
use crate::test::storage::MockStorage;
@@ -49,6 +50,53 @@ impl Arbitrary for MockStorage {
    }
}

+
impl Arbitrary for Project {
+
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+
        let mut buf = Vec::new();
+
        let doc = Doc::arbitrary(g);
+
        let id = doc.write(&mut buf).unwrap();
+
        let remotes = storage::Remotes::arbitrary(g);
+

+
        Self { id, doc, remotes }
+
    }
+
}
+

+
impl Arbitrary for Did {
+
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+
        Self::from(PublicKey::arbitrary(g))
+
    }
+
}
+

+
impl Arbitrary for Delegate {
+
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+
        Self {
+
            name: String::arbitrary(g),
+
            id: Did::arbitrary(g),
+
        }
+
    }
+
}
+

+
impl Arbitrary for Doc {
+
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+
        let name = String::arbitrary(g);
+
        let description = String::arbitrary(g);
+
        let default_branch = String::arbitrary(g);
+
        let version = u32::arbitrary(g);
+
        let parent = None;
+
        let delegate = Delegate::arbitrary(g);
+
        let delegates = NonEmpty::new(delegate);
+

+
        Self {
+
            name,
+
            description,
+
            default_branch,
+
            version,
+
            parent,
+
            delegates,
+
        }
+
    }
+
}
+

impl Arbitrary for Refs {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        let mut refs: BTreeMap<git::RefString, storage::Oid> = BTreeMap::new();
modified node/src/test/storage.rs
@@ -2,7 +2,7 @@ use git_url::Url;

use crate::crypto::Verified;
use crate::git;
-
use crate::identity::{ProjId, UserId};
+
use crate::identity::{ProjId, Project, UserId};
use crate::storage::refs;
use crate::storage::{
    Error, Inventory, ReadRepository, ReadStorage, Remote, Remotes, WriteRepository, WriteStorage,
@@ -10,11 +10,11 @@ use crate::storage::{

#[derive(Clone, Debug)]
pub struct MockStorage {
-
    pub inventory: Vec<(ProjId, Remotes<Verified>)>,
+
    pub inventory: Vec<Project>,
}

impl MockStorage {
-
    pub fn new(inventory: Vec<(ProjId, Remotes<Verified>)>) -> Self {
+
    pub fn new(inventory: Vec<Project>) -> Self {
        Self { inventory }
    }

@@ -38,9 +38,9 @@ impl ReadStorage for MockStorage {
        }
    }

-
    fn get(&self, proj: &ProjId) -> Result<Option<Remotes<Verified>>, Error> {
-
        if let Some((_, refs)) = self.inventory.iter().find(|(id, _)| id == proj) {
-
            return Ok(Some(refs.clone()));
+
    fn get(&self, proj: &ProjId) -> Result<Option<Project>, Error> {
+
        if let Some(proj) = self.inventory.iter().find(|p| p.id == *proj) {
+
            return Ok(Some(proj.clone()));
        }
        Ok(None)
    }
@@ -49,7 +49,7 @@ impl ReadStorage for MockStorage {
        let inventory = self
            .inventory
            .iter()
-
            .map(|(id, _)| id.clone())
+
            .map(|proj| proj.id.clone())
            .collect::<Vec<_>>();

        Ok(inventory)
modified node/src/test/tests.rs
@@ -347,9 +347,9 @@ fn prop_inventory_exchange_dense() {
            (bob_inv.inventory, bob.id()),
            (eve_inv.inventory, eve.id()),
        ] {
-
            for (proj, _) in inv {
+
            for proj in inv {
                routing
-
                    .entry(proj.clone())
+
                    .entry(proj.id.clone())
                    .or_insert_with(|| HashSet::with_hasher(rng.clone().into()))
                    .insert(*peer);
            }