use std::{collections::HashMap, convert::TryFrom as _}; use tempfile::TempDir; use crate::{ change, object::{self, Reference}, ObjectId, Store, }; use super::identity::Urn; pub mod error { use thiserror::Error; use crate::object::storage::convert; #[derive(Debug, Error)] pub enum Identity { #[error(transparent)] Json(#[from] serde_json::Error), #[error(transparent)] Git(#[from] git2::Error), #[error("'identity' was not a blob, found '{0:?}'")] NotBlob(Option), #[error("could not find 'identity' in the tree '{0}'")] NotFound(git_ext::Oid), } #[derive(Debug, Error)] pub enum Objects { #[error(transparent)] Conversion(#[from] convert::Error), #[error(transparent)] Git(#[from] git2::Error), #[error(transparent)] Format(#[from] git_ref_format::Error), } } pub struct Storage { raw: git2::Repository, _temp: TempDir, } impl Storage { pub fn new() -> Self { let temp = tempfile::tempdir().unwrap(); let raw = git2::Repository::init(temp.path()).unwrap(); let mut config = raw.config().unwrap(); config.set_str("user.name", "Terry Pratchett").unwrap(); config .set_str("user.email", "http://www.gnuterrypratchett.com") .unwrap(); Self { raw, _temp: temp } } pub fn as_raw(&self) -> &git2::Repository { &self.raw } } impl Store for Storage {} impl change::Storage for Storage { type CreateError = ::CreateError; type LoadError = ::LoadError; type ObjectId = ::ObjectId; type Author = ::Author; type Resource = ::Resource; type Signatures = ::Signatures; fn create( &self, author: Option, authority: Self::Resource, signer: &Signer, spec: change::Create, ) -> Result< change::store::Change, Self::CreateError, > where Signer: crypto::Signer, { self.as_raw().create(author, authority, signer, spec) } fn load( &self, id: Self::ObjectId, ) -> Result< change::store::Change, Self::LoadError, > { self.as_raw().load(id) } } impl object::Storage for Storage { type ObjectsError = error::Objects; type TypesError = error::Objects; type UpdateError = git2::Error; type Identifier = Urn; fn objects( &self, identifier: &Self::Identifier, typename: &crate::TypeName, object_id: &ObjectId, ) -> Result { let name = format!( "refs/rad/{}/cobs/{}/{}", identifier.to_path(), typename, object_id ); let glob = format!( "refs/rad/{}/*/cobs/{}/{}", identifier.name.as_str(), typename, object_id ); let local = { let r = self.raw.find_reference(&name)?; Some(Reference::try_from(r)?) }; let remotes = self .raw .references_glob(&glob)? .map(|r| { r.map_err(error::Objects::from) .and_then(|r| Reference::try_from(r).map_err(error::Objects::from)) }) .collect::, _>>()?; Ok(object::Objects { local, remotes }) } fn types( &self, identifier: &Self::Identifier, typename: &crate::TypeName, ) -> Result, Self::TypesError> { let mut objects = HashMap::new(); let prefix = format!("refs/rad/{}/cobs/{}", identifier.to_path(), typename); for r in self.raw.references()? { let r = r?; let name = r.name().unwrap(); let oid = r .target() .map(ObjectId::from) .expect("BUG: the cob references should be direct"); if name.starts_with(&prefix) { objects.insert( oid, object::Objects { local: Some(Reference::try_from(r)?), remotes: Vec::new(), }, ); } } Ok(objects) } fn update( &self, identifier: &Self::Identifier, typename: &crate::TypeName, object_id: &ObjectId, change: &change::Change, ) -> Result<(), Self::UpdateError> { let name = format!( "refs/rad/{}/cobs/{}/{}", identifier.to_path(), typename, object_id ); let id = *change.id(); self.raw.reference(&name, id.into(), true, "new change")?; Ok(()) } }