pub mod git; pub mod refs; use std::collections::hash_map; use std::marker::PhantomData; use std::ops::Deref; use std::path::Path; use std::{fmt, io}; use radicle_git_ext as git_ext; use thiserror::Error; pub use radicle_git_ext::Oid; use crate::collections::HashMap; use crate::crypto; use crate::crypto::{PublicKey, Signer, Unverified, Verified}; use crate::git::Url; use crate::git::{RefError, RefStr, RefString}; use crate::identity; use crate::identity::{Id, IdError, Project}; use crate::storage::refs::Refs; use self::refs::SignedRefs; pub type BranchName = String; pub type Inventory = Vec; /// Storage error. #[derive(Error, Debug)] pub enum Error { #[error("invalid git reference")] InvalidRef, #[error("git reference error: {0}")] Ref(#[from] RefError), #[error(transparent)] Refs(#[from] refs::Error), #[error("git: {0}")] Git(#[from] git2::Error), #[error("id: {0}")] Id(#[from] IdError), #[error("i/o: {0}")] Io(#[from] io::Error), #[error("doc: {0}")] Doc(#[from] identity::DocError), #[error("invalid repository head")] InvalidHead, } pub type RemoteId = PublicKey; /// An update to a reference. #[derive(Debug, Clone, PartialEq, Eq)] pub enum RefUpdate { Updated { name: RefString, old: Oid, new: Oid }, Created { name: RefString, oid: Oid }, Deleted { name: RefString, oid: Oid }, Skipped { name: RefString, oid: Oid }, } impl RefUpdate { pub fn from(name: RefString, old: impl Into, new: impl Into) -> Self { let old = old.into(); let new = new.into(); if old.is_zero() { Self::Created { name, oid: new } } else if new.is_zero() { Self::Deleted { name, oid: old } } else if old != new { Self::Updated { name, old, new } } else { Self::Skipped { name, oid: old } } } } impl fmt::Display for RefUpdate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Updated { name, old, new } => { write!(f, "~ {:.7}..{:.7} {}", old, new, name) } Self::Created { name, oid } => { write!(f, "* 0000000..{:.7} {}", oid, name) } Self::Deleted { name, oid } => { write!(f, "- {:.7}..0000000 {}", oid, name) } Self::Skipped { name, oid } => { write!(f, "= {:.7}..{:.7} {}", oid, oid, name) } } } } /// Project remotes. Tracks the git state of a project. #[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>; fn deref(&self) -> &Self::Target { &self.0 } } impl Remotes { pub fn new(remotes: HashMap>) -> Self { Self(remotes) } } impl Remotes { pub fn unverified(self) -> Remotes { Remotes( self.into_iter() .map(|(id, r)| (id, r.unverified())) .collect(), ) } } impl Default for Remotes { fn default() -> Self { Self(HashMap::default()) } } impl IntoIterator for Remotes { type Item = (RemoteId, Remote); type IntoIter = hash_map::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl From> for HashMap { fn from(other: Remotes) -> Self { let mut remotes = HashMap::with_hasher(fastrand::Rng::new().into()); for (k, v) in other.into_iter() { remotes.insert(k, v.refs.into()); } remotes } } /// A project remote. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Remote { /// ID of remote. pub id: PublicKey, /// Git references published under this remote, and their hashes. pub refs: SignedRefs, /// Whether this remote is of a project delegate. pub delegate: bool, /// Whether the remote is verified or not, ie. whether its signed refs were checked. verified: PhantomData, } impl Remote { pub fn new(id: PublicKey, refs: impl Into>) -> Self { Self { id, refs: refs.into(), delegate: false, verified: PhantomData, } } } impl Remote { pub fn verified(self) -> Result, crypto::Error> { let refs = self.refs.verified(&self.id)?; Ok(Remote { id: self.id, refs, delegate: self.delegate, verified: PhantomData, }) } } impl Remote { pub fn unverified(self) -> Remote { Remote { id: self.id, refs: self.refs.unverified(), delegate: self.delegate, verified: PhantomData, } } } pub trait ReadStorage { fn url(&self) -> Url; fn get(&self, remote: &RemoteId, proj: &Id) -> Result, Error>; fn inventory(&self) -> Result; } pub trait WriteStorage<'r>: ReadStorage { type Repository: WriteRepository<'r>; fn repository(&self, proj: &Id) -> Result; fn sign_refs( &self, repository: &Self::Repository, signer: G, ) -> Result, Error>; } 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( &self, remote: &RemoteId, reference: &RefStr, ) -> Result, git2::Error>; fn reference_oid( &self, remote: &RemoteId, reference: &RefStr, ) -> Result, git2::Error>; fn references(&self, remote: &RemoteId) -> Result; fn remote(&self, remote: &RemoteId) -> Result, refs::Error>; fn remotes(&'r self) -> Result; } pub trait WriteRepository<'r>: ReadRepository<'r> { fn fetch(&mut self, url: &Url) -> Result, git2::Error>; fn raw(&self) -> &git2::Repository; } impl ReadStorage for T where T: Deref, S: ReadStorage + 'static, { fn url(&self) -> Url { self.deref().url() } fn inventory(&self) -> Result { self.deref().inventory() } fn get(&self, remote: &RemoteId, proj: &Id) -> Result, Error> { self.deref().get(remote, proj) } } impl<'r, T, S> WriteStorage<'r> for T where T: Deref, S: WriteStorage<'r> + 'static, { type Repository = S::Repository; fn repository(&self, proj: &Id) -> Result { self.deref().repository(proj) } fn sign_refs( &self, repository: &S::Repository, signer: G, ) -> Result, Error> { self.deref().sign_refs(repository, signer) } } #[cfg(test)] mod tests { #[test] fn test_storage() {} }