diff --git a/node/src/protocol.rs b/node/src/protocol.rs index bd9ac9a0..23a89757 100644 --- a/node/src/protocol.rs +++ b/node/src/protocol.rs @@ -24,7 +24,7 @@ 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, WriteStorage}; +use crate::storage::{Inventory, WriteStorage}; pub use crate::protocol::config::{Config, Network}; @@ -81,7 +81,7 @@ pub struct Protocol { start_time: LocalTime, } -impl Protocol { +impl<'r, T: WriteStorage<'r>, S: address_book::Store, G: crypto::Signer> Protocol { pub fn new( config: Config, clock: RefClock, @@ -253,9 +253,9 @@ impl P } } -impl nakamoto::Protocol for Protocol +impl<'r, S, T, G> nakamoto::Protocol for Protocol where - T: ReadStorage + WriteStorage + 'static, + T: WriteStorage<'r> + 'static, S: address_book::Store, G: crypto::Signer, { @@ -572,9 +572,9 @@ pub struct Context { rng: Rng, } -impl Context +impl<'r, S, T, G> Context where - T: storage::ReadStorage + storage::WriteStorage, + T: storage::WriteStorage<'r>, G: crypto::Signer, { pub(crate) fn new( diff --git a/node/src/protocol/peer.rs b/node/src/protocol/peer.rs index 696f6a34..de1cbcaa 100644 --- a/node/src/protocol/peer.rs +++ b/node/src/protocol/peer.rs @@ -95,13 +95,13 @@ impl Peer { self.attempts = 0; } - pub fn received( + pub fn received<'r, S, T, G>( &mut self, envelope: Envelope, ctx: &mut Context, ) -> Result, PeerError> where - T: storage::ReadStorage + storage::WriteStorage, + T: storage::WriteStorage<'r>, G: crypto::Signer, { if envelope.magic != ctx.config.network.magic() { diff --git a/node/src/rad.rs b/node/src/rad.rs index ecf6c4f4..fca6f416 100644 --- a/node/src/rad.rs +++ b/node/src/rad.rs @@ -33,7 +33,7 @@ pub enum InitError { } /// Initialize a new radicle project from a git repository. -pub fn init( +pub fn init<'r, S: storage::WriteStorage<'r>>( repo: &git2::Repository, name: &str, description: &str, diff --git a/node/src/storage.rs b/node/src/storage.rs index b0eef3d7..ee0aabeb 100644 --- a/node/src/storage.rs +++ b/node/src/storage.rs @@ -53,6 +53,12 @@ pub type RemoteId = UserId; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Remotes(HashMap>); +impl FromIterator<(RemoteId, Remote)> for Remotes { + fn from_iter)>>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + impl Deref for Remotes { type Target = HashMap>; @@ -159,14 +165,17 @@ pub trait ReadStorage { fn inventory(&self) -> Result; } -pub trait WriteStorage: ReadStorage { - type Repository: WriteRepository; +pub trait WriteStorage<'r>: ReadStorage { + type Repository: WriteRepository<'r>; fn repository(&self, proj: &ProjId) -> Result; fn sign_refs(&self, repository: &Self::Repository) -> Result, Error>; } -pub trait ReadRepository { +pub trait ReadRepository<'r> { + type Remotes: Iterator), refs::Error>> + 'r; + + fn is_empty(&self) -> Result; fn path(&self) -> &Path; fn blob_at<'a>(&'a self, oid: Oid, path: &'a Path) -> Result, git_ext::Error>; fn reference( @@ -177,10 +186,10 @@ pub trait ReadRepository { fn reference_oid(&self, user: &UserId, reference: &RefStr) -> Result, git2::Error>; fn references(&self, user: &UserId) -> Result; fn remote(&self, user: &UserId) -> Result, refs::Error>; - fn remotes(&self) -> Result, refs::Error>; + fn remotes(&'r self) -> Result; } -pub trait WriteRepository: ReadRepository { +pub trait WriteRepository<'r>: ReadRepository<'r> { fn fetch(&mut self, url: &Url) -> Result<(), git2::Error>; fn raw(&self) -> &git2::Repository; } @@ -207,10 +216,10 @@ where } } -impl WriteStorage for T +impl<'r, T, S> WriteStorage<'r> for T where T: DerefMut, - S: WriteStorage + 'static, + S: WriteStorage<'r> + 'static, { type Repository = S::Repository; diff --git a/node/src/storage/git.rs b/node/src/storage/git.rs index 8eb74baf..2f3a1cf5 100644 --- a/node/src/storage/git.rs +++ b/node/src/storage/git.rs @@ -8,7 +8,6 @@ use once_cell::sync::Lazy; pub use radicle_git_ext::Oid; -use crate::collections::HashMap; use crate::crypto::{Signer, Verified}; use crate::git; use crate::identity::{self, IDENTITY_PATH}; @@ -16,7 +15,7 @@ use crate::identity::{ProjId, Project, UserId}; use crate::storage::refs; use crate::storage::refs::{Refs, SignedRefs}; use crate::storage::{ - Error, Inventory, ReadRepository, ReadStorage, Remote, Remotes, WriteRepository, WriteStorage, + Error, Inventory, ReadRepository, ReadStorage, Remote, WriteRepository, WriteStorage, }; use super::RemoteId; @@ -58,7 +57,7 @@ impl ReadStorage for Storage { let repo = self.repository(id)?; if let Some(doc) = repo.identity(local)? { - let remotes = repo.remotes()?; + let remotes = repo.remotes()?.collect::>()?; // 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 @@ -79,7 +78,7 @@ impl ReadStorage for Storage { } } -impl WriteStorage for Storage { +impl<'r> WriteStorage<'r> for Storage { type Repository = Repository; fn repository(&self, proj: &ProjId) -> Result { @@ -221,7 +220,14 @@ impl Repository { } } -impl ReadRepository for Repository { +impl<'r> ReadRepository<'r> for Repository { + type Remotes = Box), refs::Error>> + 'r>; + + fn is_empty(&self) -> Result { + let some = self.remotes()?.next().is_some(); + Ok(!some) + } + fn path(&self) -> &Path { self.backend.path() } @@ -281,24 +287,23 @@ impl ReadRepository for Repository { Ok(refs.into()) } - fn remotes(&self) -> Result, refs::Error> { - // TODO: Should use the id glob here instead of signature. - let refs = self.backend.references_glob(SIGNATURES_GLOB.as_str())?; - let mut remotes = HashMap::default(); + fn remotes(&'r self) -> Result { + let iter = self.backend.references_glob(SIGNATURES_GLOB.as_str())?.map( + |reference| -> Result<(RemoteId, Remote), refs::Error> { + let r = reference?; + let name = r.name().ok_or(refs::Error::InvalidRef)?; + let (id, _) = git::parse_ref::(name)?; + let remote = self.remote(&id)?; - for r in refs { - let r = r?; - let name = r.name().ok_or(refs::Error::InvalidRef)?; - let (id, _) = git::parse_ref::(name)?; - let remote = self.remote(&id)?; + Ok((id, remote)) + }, + ); - remotes.insert(id, remote); - } - Ok(Remotes::new(remotes)) + Ok(Box::new(iter)) } } -impl WriteRepository for Repository { +impl<'r> WriteRepository<'r> for Repository { /// Fetch all remotes of a project from the given URL. fn fetch(&mut self, url: &git::Url) -> Result<(), git2::Error> { // TODO: Have function to fetch specific remotes. @@ -367,7 +372,13 @@ mod tests { for remote in refs.values_mut() { remote.remove(&*SIGNATURE_REF).unwrap(); } - assert_eq!(refs, remotes.into()); + + let remotes = remotes + .map(|remote| remote.map(|(id, r): (RemoteId, Remote)| (id, r.refs.into()))) + .collect::>() + .unwrap(); + + assert_eq!(refs, remotes); } #[test] @@ -377,7 +388,8 @@ mod tests { let bob = Storage::open(tmp.path().join("bob"), MockSigner::default()).unwrap(); let inventory = alice.inventory().unwrap(); let proj = inventory.first().unwrap(); - let remotes = alice.repository(proj).unwrap().remotes().unwrap(); + let repo = alice.repository(proj).unwrap(); + let remotes = repo.remotes().unwrap(); let refname = git::refname!("heads/master"); // Have Bob fetch Alice's refs. @@ -391,7 +403,8 @@ mod tests { }) .unwrap(); - for (id, _) in remotes.into_iter() { + for remote in remotes { + let (id, _) = remote.unwrap(); let alice_repo = alice.repository(proj).unwrap(); let alice_oid = alice_repo.reference(&id, &refname).unwrap().unwrap(); diff --git a/node/src/storage/refs.rs b/node/src/storage/refs.rs index 1ce5b8fa..24a9710a 100644 --- a/node/src/storage/refs.rs +++ b/node/src/storage/refs.rs @@ -193,9 +193,9 @@ impl SignedRefs { } impl SignedRefs { - pub fn load(remote: &RemoteId, repo: &S) -> Result + pub fn load<'r, S>(remote: &RemoteId, repo: &S) -> Result where - S: ReadRepository, + S: ReadRepository<'r>, { if let Some(oid) = repo.reference_oid(remote, &SIGNATURE_REF)? { Self::load_at(oid, remote, repo) @@ -204,9 +204,9 @@ impl SignedRefs { } } - pub fn load_at(oid: Oid, remote: &RemoteId, repo: &S) -> Result + pub fn load_at<'r, S>(oid: Oid, remote: &RemoteId, repo: &S) -> Result where - S: storage::ReadRepository, + S: storage::ReadRepository<'r>, { let refs = repo.blob_at(oid, Path::new(REFS_BLOB_PATH))?; let signature = repo.blob_at(oid, Path::new(SIGNATURE_BLOB_PATH))?; @@ -228,7 +228,7 @@ impl SignedRefs { /// Save the signed refs to disk. /// This creates a new commit on the signed refs branch, and updates the branch pointer. - pub fn save( + pub fn save<'r, S: WriteRepository<'r>>( &self, // TODO: This should be part of the signed refs. remote: &RemoteId, diff --git a/node/src/test/peer.rs b/node/src/test/peer.rs index 3cc983a7..07285d2f 100644 --- a/node/src/test/peer.rs +++ b/node/src/test/peer.rs @@ -13,7 +13,7 @@ use crate::decoder::Decoder; use crate::protocol::config::*; use crate::protocol::message::*; use crate::protocol::*; -use crate::storage::{ReadStorage, WriteStorage}; +use crate::storage::WriteStorage; use crate::test::crypto::MockSigner; use crate::*; @@ -32,9 +32,9 @@ pub struct Peer { initialized: bool, } -impl simulator::Peer> for Peer +impl<'r, S> simulator::Peer> for Peer where - S: ReadStorage + WriteStorage + 'static, + S: WriteStorage<'r> + 'static, { fn init(&mut self) { self.initialize() @@ -59,9 +59,9 @@ impl DerefMut for Peer { } } -impl Peer +impl<'r, S> Peer where - S: ReadStorage + WriteStorage + 'static, + S: WriteStorage<'r> + 'static, { pub fn new(name: &'static str, ip: impl Into, storage: S) -> Self { Self::config( diff --git a/node/src/test/storage.rs b/node/src/test/storage.rs index 5def510a..96e16131 100644 --- a/node/src/test/storage.rs +++ b/node/src/test/storage.rs @@ -5,7 +5,7 @@ use crate::git; use crate::identity::{ProjId, Project, UserId}; use crate::storage::refs; use crate::storage::{ - Error, Inventory, ReadRepository, ReadStorage, Remote, Remotes, WriteRepository, WriteStorage, + Error, Inventory, ReadRepository, ReadStorage, Remote, RemoteId, WriteRepository, WriteStorage, }; #[derive(Clone, Debug)] @@ -56,7 +56,7 @@ impl ReadStorage for MockStorage { } } -impl WriteStorage for MockStorage { +impl WriteStorage<'_> for MockStorage { type Repository = MockRepository; fn repository(&self, _proj: &ProjId) -> Result { @@ -73,7 +73,13 @@ impl WriteStorage for MockStorage { pub struct MockRepository {} -impl ReadRepository for MockRepository { +impl ReadRepository<'_> for MockRepository { + type Remotes = std::iter::Empty), refs::Error>>; + + fn is_empty(&self) -> Result { + Ok(true) + } + fn path(&self) -> &std::path::Path { todo!() } @@ -82,7 +88,7 @@ impl ReadRepository for MockRepository { todo!() } - fn remotes(&self) -> Result, refs::Error> { + fn remotes(&self) -> Result { todo!() } @@ -115,7 +121,7 @@ impl ReadRepository for MockRepository { } } -impl WriteRepository for MockRepository { +impl WriteRepository<'_> for MockRepository { fn fetch(&mut self, _url: &Url) -> Result<(), git2::Error> { Ok(()) }