radicle-heartwood-lfs/node/src/storage.rs

291 lines
7.4 KiB
Rust

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<Id>;
/// 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<Oid>, new: impl Into<Oid>) -> 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<V>(HashMap<RemoteId, Remote<V>>);
impl<V> FromIterator<(RemoteId, Remote<V>)> for Remotes<V> {
fn from_iter<T: IntoIterator<Item = (RemoteId, Remote<V>)>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl<V> Deref for Remotes<V> {
type Target = HashMap<RemoteId, Remote<V>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<V> Remotes<V> {
pub fn new(remotes: HashMap<RemoteId, Remote<V>>) -> Self {
Self(remotes)
}
}
impl Remotes<Verified> {
pub fn unverified(self) -> Remotes<Unverified> {
Remotes(
self.into_iter()
.map(|(id, r)| (id, r.unverified()))
.collect(),
)
}
}
impl<V> Default for Remotes<V> {
fn default() -> Self {
Self(HashMap::default())
}
}
impl<V> IntoIterator for Remotes<V> {
type Item = (RemoteId, Remote<V>);
type IntoIter = hash_map::IntoIter<RemoteId, Remote<V>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<V> From<Remotes<V>> for HashMap<RemoteId, Refs> {
fn from(other: Remotes<V>) -> 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<V> {
/// ID of remote.
pub id: PublicKey,
/// Git references published under this remote, and their hashes.
pub refs: SignedRefs<V>,
/// 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<V>,
}
impl<V> Remote<V> {
pub fn new(id: PublicKey, refs: impl Into<SignedRefs<V>>) -> Self {
Self {
id,
refs: refs.into(),
delegate: false,
verified: PhantomData,
}
}
}
impl Remote<Unverified> {
pub fn verified(self) -> Result<Remote<Verified>, crypto::Error> {
let refs = self.refs.verified(&self.id)?;
Ok(Remote {
id: self.id,
refs,
delegate: self.delegate,
verified: PhantomData,
})
}
}
impl Remote<Verified> {
pub fn unverified(self) -> Remote<Unverified> {
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<Option<Project>, Error>;
fn inventory(&self) -> Result<Inventory, Error>;
}
pub trait WriteStorage<'r>: ReadStorage {
type Repository: WriteRepository<'r>;
fn repository(&self, proj: &Id) -> Result<Self::Repository, Error>;
fn sign_refs<G: Signer>(
&self,
repository: &Self::Repository,
signer: G,
) -> Result<SignedRefs<Verified>, Error>;
}
pub trait ReadRepository<'r> {
type Remotes: Iterator<Item = Result<(RemoteId, Remote<Verified>), refs::Error>> + 'r;
fn is_empty(&self) -> Result<bool, git2::Error>;
fn path(&self) -> &Path;
fn blob_at<'a>(&'a self, oid: Oid, path: &'a Path) -> Result<git2::Blob<'a>, git_ext::Error>;
fn reference(
&self,
remote: &RemoteId,
reference: &RefStr,
) -> Result<Option<git2::Reference>, git2::Error>;
fn reference_oid(
&self,
remote: &RemoteId,
reference: &RefStr,
) -> Result<Option<Oid>, git2::Error>;
fn references(&self, remote: &RemoteId) -> Result<Refs, Error>;
fn remote(&self, remote: &RemoteId) -> Result<Remote<Verified>, refs::Error>;
fn remotes(&'r self) -> Result<Self::Remotes, git2::Error>;
}
pub trait WriteRepository<'r>: ReadRepository<'r> {
fn fetch(&mut self, url: &Url) -> Result<Vec<RefUpdate>, git2::Error>;
fn raw(&self) -> &git2::Repository;
}
impl<T, S> ReadStorage for T
where
T: Deref<Target = S>,
S: ReadStorage + 'static,
{
fn url(&self) -> Url {
self.deref().url()
}
fn inventory(&self) -> Result<Inventory, Error> {
self.deref().inventory()
}
fn get(&self, remote: &RemoteId, proj: &Id) -> Result<Option<Project>, Error> {
self.deref().get(remote, proj)
}
}
impl<'r, T, S> WriteStorage<'r> for T
where
T: Deref<Target = S>,
S: WriteStorage<'r> + 'static,
{
type Repository = S::Repository;
fn repository(&self, proj: &Id) -> Result<Self::Repository, Error> {
self.deref().repository(proj)
}
fn sign_refs<G: Signer>(
&self,
repository: &S::Repository,
signer: G,
) -> Result<SignedRefs<Verified>, Error> {
self.deref().sign_refs(repository, signer)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_storage() {}
}