Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
feat: add module radicle_ci_broker::ergo
Lars Wirzenius committed 8 months ago
commit 4b232bd238f2ff952f798b9f23cbe1ff2e7395e1
parent 80db71a
2 files changed +188 -0
added src/ergo.rs
@@ -0,0 +1,187 @@
+
//! An ergonomic wrapper around the `radicle` crate.
+
//!
+
//! The purpose of this module is to make it more convenient to use
+
//! the `radicle` crate to access a [Radicle](https://radicle.xyz/)
+
//! node and information in the node. It is not, in any way, meant to
+
//! be a replacement for using the official crate directly.
+

+
use std::str::FromStr;
+

+
use radicle::{
+
    cob::patch::{cache::Patches, Patch, PatchId},
+
    git::Oid,
+
    identity::{Project, RepoId},
+
    profile::Profile,
+
    storage::{git::Repository, ReadStorage, RepositoryInfo},
+
};
+

+
/// A Radicle node.
+
///
+
/// This type represents a Radicle node, and exists to cache some
+
/// stuff so it doesn't need to be re-loaded on every function call.
+
/// Especially the node profile.
+
pub struct Radicle {
+
    profile: Profile,
+
}
+

+
impl Radicle {
+
    /// Create a new [`Radicle`]. This may fail.
+
    pub fn new() -> Result<Self, ErgoError> {
+
        Ok(Self {
+
            profile: Profile::load().map_err(ErgoError::LoadProfile)?,
+
        })
+
    }
+

+
    /// Return the loaded profile.
+
    pub fn profile(&self) -> &Profile {
+
        &self.profile
+
    }
+

+
    /// List all repositories on a node.
+
    pub fn repositories(&self) -> Result<Vec<RepositoryInfo>, ErgoError> {
+
        self.profile
+
            .storage
+
            .repositories()
+
            .map_err(ErgoError::ListRepositories)
+
    }
+

+
    /// Load information about a specific repository.
+
    pub fn repository(&self, repo_id: &RepoId) -> Result<Repository, ErgoError> {
+
        self.profile
+
            .storage
+
            .repository(*repo_id)
+
            .map_err(|err| ErgoError::LoadRepo(*repo_id, Box::new(err)))
+
    }
+

+
    /// Load a repository by name, if the name is unique.
+
    pub fn repository_by_name(&self, wanted: &str) -> Result<Repository, ErgoError> {
+
        let matching: Result<Vec<RepoId>, ErgoError> = self
+
            .repositories()?
+
            .iter()
+
            .filter_map(|ri| match self.project(&ri.rid) {
+
                Ok(project) if project.name() == wanted => Some(Ok(ri.rid)),
+
                Err(err) => Some(Err(err)),
+
                _ => None,
+
            })
+
            .collect();
+
        let matching = matching?;
+

+
        match matching[..] {
+
            [] => Err(ErgoError::NoRepositoryWithName(wanted.to_string())),
+
            [_] => {
+
                let repo_id = matching[0];
+
                let repo = self.repository(&repo_id)?;
+
                Ok(repo)
+
            }
+
            [_, _, ..] => Err(ErgoError::NameIsNotUnique(wanted.to_string())),
+
        }
+
    }
+

+
    /// Load the project payload in the identity document of a
+
    /// repository.
+
    pub fn project(&self, repo_id: &RepoId) -> Result<Project, ErgoError> {
+
        let repo = self.repository(repo_id)?;
+
        repo.project()
+
            .map_err(|err| ErgoError::LoadProject(*repo_id, Box::new(err)))
+
    }
+

+
    /// Load all patches in a repository.
+
    pub fn patches(&self, repo_id: &RepoId) -> Result<Vec<(PatchId, Patch)>, ErgoError> {
+
        let repo = self.repository(repo_id)?;
+
        let patches = self
+
            .profile
+
            .home
+
            .patches(&repo)
+
            .map_err(|err| ErgoError::LoadPathces(*repo_id, Box::new(err)))?;
+
        let mut items = vec![];
+
        let list = patches
+
            .list()
+
            .map_err(|err| ErgoError::ListCache(*repo_id, err))?;
+
        for result in list {
+
            let (id, patch) = result.map_err(|err| ErgoError::CacheListItem(*repo_id, err))?;
+
            items.push((id, patch));
+
        }
+
        Ok(items)
+
    }
+

+
    /// Load a specific patch.
+
    pub fn patch(&self, repo_id: &RepoId, patch_id: &PatchId) -> Result<Patch, ErgoError> {
+
        let repo = self.repository(repo_id)?;
+
        let patches = self
+
            .profile
+
            .home
+
            .patches(&repo)
+
            .map_err(|err| ErgoError::LoadPathces(*repo_id, Box::new(err)))?;
+
        patches
+
            .get(patch_id)
+
            .map_err(|err| ErgoError::GetPatch(*repo_id, *patch_id, Box::new(err)))?
+
            .ok_or(ErgoError::NoSuchPatch(*repo_id, *patch_id))
+
    }
+

+
    /// Resolve a shortened patch ID into a full patch ID.
+
    pub fn resolve_patch_id(&self, repo_id: &RepoId, id: &str) -> Result<PatchId, ErgoError> {
+
        let repo = self.repository(repo_id)?;
+
        let object = repo
+
            .backend
+
            .revparse_single(id)
+
            .map_err(|err| ErgoError::ResolvePatchId(id.to_string(), err))?;
+
        Ok(PatchId::from(object.id()))
+
    }
+

+
    /// Resolve a short commit or name into a full commit ID.
+
    pub fn resolve_commit(&self, repo_id: &RepoId, gitref: &str) -> Result<Oid, ErgoError> {
+
        if let Ok(oid) = Oid::from_str(gitref) {
+
            Ok(oid)
+
        } else {
+
            let repo = self.repository(repo_id)?;
+
            let object = repo
+
                .backend
+
                .revparse_single(gitref)
+
                .map_err(|err| ErgoError::ResolveCommit(gitref.to_string(), *repo_id, err))?;
+
            Ok(Oid::from(object.id()))
+
        }
+
    }
+
}
+

+
/// Errors from the `Radicle` type.
+
#[derive(Debug, thiserror::Error)]
+
pub enum ErgoError {
+
    #[error("failed to load Radicle profile")]
+
    LoadProfile(#[source] radicle::profile::Error),
+

+
    #[error("failed to list repositories in Radicle node storage")]
+
    ListRepositories(#[source] radicle::storage::Error),
+

+
    #[error("failed to load info from Radicle node storage for repository {0}")]
+
    LoadRepo(RepoId, #[source] Box<radicle::storage::RepositoryError>),
+

+
    #[error("failed to load project info from Radicle node storage for repository {0}")]
+
    LoadProject(RepoId, #[source] Box<radicle::storage::RepositoryError>),
+

+
    #[error("failed to load patch list from Radicle node storage for repository {0}")]
+
    LoadPathces(RepoId, #[source] Box<radicle::profile::Error>),
+

+
    #[error("failed to list patches for repository {0}")]
+
    ListCache(RepoId, #[source] radicle::patch::cache::Error),
+

+
    #[error("failed to list info for patch {0}")]
+
    CacheListItem(RepoId, #[source] radicle::patch::cache::Error),
+

+
    #[error("failed to resolve patch id {0:?} in repository {1}")]
+
    ResolvePatchId(String, #[source] radicle::storage::git::raw::Error),
+

+
    #[error("failed to resolve commit id {0:?} in repository {1}")]
+
    ResolveCommit(String, RepoId, #[source] radicle::storage::git::raw::Error),
+

+
    #[error("failed to load patch {1} from Radicle node storage for repository {0}")]
+
    GetPatch(RepoId, PatchId, #[source] Box<radicle::patch::cache::Error>),
+

+
    #[error("no patch {1} in repository {0}")]
+
    NoSuchPatch(RepoId, PatchId),
+

+
    #[error("no repository called {0:?}")]
+
    NoRepositoryWithName(String),
+

+
    #[error("repository name is not unique: {0:?}")]
+
    NameIsNotUnique(String),
+
}
modified src/lib.rs
@@ -15,6 +15,7 @@ pub mod ci_event_source;
pub mod cob;
pub mod config;
pub mod db;
+
pub mod ergo;
pub mod filter;
pub mod logger;
pub mod msg;