Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
heartwood crates radicle-cob src test storage.rs
use std::{collections::BTreeMap, convert::TryFrom as _};

use radicle_git_ref_format::refname;

use fmt::Component;
use tempfile::TempDir;

use crate::{
    ObjectId, Store, change,
    object::{self, Reference},
    signatures,
};

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),
    }

    #[derive(Debug, Error)]
    pub enum Objects {
        #[error(transparent)]
        Conversion(#[from] convert::Error),
        #[error(transparent)]
        Git(#[from] git2::Error),
        #[error(transparent)]
        Format(#[from] fmt::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_opts(
            temp.path(),
            git2::RepositoryInitOptions::new().external_template(false),
        )
        .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 StoreError = <git2::Repository as change::Storage>::StoreError;
    type LoadError = <git2::Repository as change::Storage>::LoadError;

    type ObjectId = <git2::Repository as change::Storage>::ObjectId;
    type Parent = <git2::Repository as change::Storage>::Parent;
    type Signatures = <git2::Repository as change::Storage>::Signatures;

    fn store<Signer>(
        &self,
        authority: Option<Self::Parent>,
        parents: Vec<Self::Parent>,
        signer: &Signer,
        spec: change::Template<Self::ObjectId>,
    ) -> Result<
        change::store::Entry<Self::Parent, Self::ObjectId, Self::Signatures>,
        Self::StoreError,
    >
    where
        Signer: signature::Signer<signatures::ExtendedSignature>,
    {
        self.as_raw().store(authority, parents, signer, spec)
    }

    fn load(
        &self,
        id: Self::ObjectId,
    ) -> Result<change::store::Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>
    {
        self.as_raw().load(id)
    }

    fn parents_of(&self, id: &oid::Oid) -> Result<Vec<radicle_oid::Oid>, Self::LoadError> {
        Ok(self
            .as_raw()
            .find_commit(id.into())?
            .parent_ids()
            .map(oid::Oid::from)
            .collect::<Vec<_>>())
    }

    fn manifest_of(&self, id: &oid::Oid) -> Result<crate::Manifest, Self::LoadError> {
        self.as_raw().manifest_of(id)
    }
}

impl object::Storage for Storage {
    type ObjectsError = error::Objects;
    type TypesError = error::Objects;
    type UpdateError = git2::Error;
    type RemoveError = git2::Error;

    type Namespace = crypto::PublicKey;

    fn objects(
        &self,
        typename: &crate::TypeName,
        object_id: &ObjectId,
    ) -> Result<object::Objects, Self::ObjectsError> {
        let glob = format!("refs/rad/*/cobs/{typename}/{object_id}");
        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::<Result<Vec<_>, _>>()?;
        Ok(remotes.into())
    }

    fn types(
        &self,
        typename: &crate::TypeName,
    ) -> Result<BTreeMap<ObjectId, object::Objects>, Self::TypesError> {
        let mut objects = BTreeMap::new();
        for r in self.raw.references_glob("refs/rad/*")? {
            let r = r?;
            let name = r.name().unwrap();
            println!("NAME: {name}");
            let oid = r
                .target()
                .map(ObjectId::from)
                .expect("BUG: the cob references should be direct");
            if name.contains(typename.as_str()) {
                let reference = Reference::try_from(r)?;
                objects
                    .entry(oid)
                    .and_modify(|objs: &mut object::Objects| objs.push(reference.clone()))
                    .or_insert_with(|| object::Objects::new(reference));
            }
        }
        Ok(objects)
    }

    fn update(
        &self,
        namespace: &Self::Namespace,
        typename: &crate::TypeName,
        object_id: &ObjectId,
        entry: &change::EntryId,
    ) -> Result<(), Self::UpdateError> {
        let name = refname!("refs/rad")
            .join(Component::from(namespace))
            .join(refname!("cobs"))
            .join::<Component>(typename.into())
            .join::<Component>(object_id.into());
        self.raw
            .reference(&name, (*entry).into(), true, "new change")?;
        Ok(())
    }

    fn remove(
        &self,
        namespace: &Self::Namespace,
        typename: &crate::TypeName,
        object_id: &ObjectId,
    ) -> Result<(), Self::RemoveError> {
        let name = refname!("refs/rad")
            .join(Component::from(namespace))
            .join(refname!("cobs"))
            .join::<Component>(typename.into())
            .join::<Component>(object_id.into());
        self.raw.find_reference(&name)?.delete()?;

        Ok(())
    }
}