node: Implement project retrieval
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
parent
910cd74294
commit
c3b7a6fbd7
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue