Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
feat: module for manipulating Git refs
Lars Wirzenius committed 1 year ago
commit 9222f6d3323084fc3335b483fe5b076d7a821704
parent b0adc80
2 files changed +143 -0
modified src/lib.rs
@@ -21,6 +21,7 @@ pub mod notif;
pub mod pages;
pub mod queueadd;
pub mod queueproc;
+
pub mod refs;
pub mod run;
pub mod sensitive;
pub mod test;
added src/refs.rs
@@ -0,0 +1,142 @@
+
//! Git reference names, namespaced and not.
+

+
use radicle::git::{BranchName, Component, Namespaced, Qualified, RefStr, RefString};
+

+
/// Convert a plain branch name (`main`) from a Git ref
+
pub fn branch_ref(name: &RefStr) -> Result<BranchName, RefError> {
+
    if name.starts_with("refs/") {
+
        return Err(RefError::RefsInName(name.to_ref_string()));
+
    }
+
    ref_string(name)
+
}
+

+
/// Create a [`BranchName`] from a string slice.
+
pub fn branch_from_str(s: &str) -> Result<BranchName, RefError> {
+
    branch_ref(&ref_string(s)?)
+
}
+

+
/// Convert a branch name to a [`Qualified`]: `refs/heads/main`.
+
pub fn qualified_branch(name: &BranchName) -> Result<Qualified, RefError> {
+
    if name.starts_with("refs/") {
+
        return Err(RefError::RefsInName(name.to_ref_string()));
+
    }
+
    let qualified_name = ref_string(&format!("refs/heads/{name}"))?;
+
    let x = Qualified::from_refstr(qualified_name);
+
    x.ok_or(RefError::QualifiedCreate(name.clone()))
+
}
+

+
/// Create a [`RefString`] from a [`String`].
+
pub fn ref_string(s: &str) -> Result<RefString, RefError> {
+
    RefString::try_from(s).map_err(|err| RefError::RefStrCreate(s.into(), err))
+
}
+

+
/// Create a name spaced branch name.
+
pub fn namespaced_branch<'a>(
+
    ns: &RefStr,
+
    branch: &'a BranchName,
+
) -> Result<Namespaced<'a>, RefError> {
+
    let ns = Component::from_refstr(ns).ok_or(RefError::NamespaceName(ns.to_ref_string()))?;
+
    Ok(qualified_branch(branch)?.with_namespace(ns))
+
}
+

+
/// Create a name spaced ref name from a string slice.
+
pub fn namespaced_from_str(s: &str) -> Result<Namespaced, RefError> {
+
    assert!(s.starts_with("refs/namespaces/"));
+
    let rs = ref_string(s)?;
+
    Ok(rs
+
        .to_namespaced()
+
        .ok_or(RefError::NamespacedCreate(s.into()))?
+
        .to_owned())
+
}
+

+
/// Extract a [`BranchName`] from a name spaced branch.
+
pub fn branch_from_namespaced(ns: &Namespaced) -> Result<BranchName, RefError> {
+
    let plain_ref = ns.strip_namespace();
+

+
    let (refs, heads, first, rest) = plain_ref.non_empty_components();
+
    if refs.as_str() != "refs" || heads.as_str() != "heads" {
+
        return Err(RefError::NotABranch(ns.to_ref_string()));
+
    }
+
    Ok(BranchName::from_iter(
+
        [first.into_inner().to_ref_string()]
+
            .iter()
+
            .cloned()
+
            .chain(rest.map(|c| c.into_inner().to_ref_string())),
+
    ))
+
}
+

+
/// All errors from Git reference manipulation.
+
#[derive(Debug, thiserror::Error, Eq, PartialEq)]
+
pub enum RefError {
+
    /// Branch name starts with "refs/", but a plain name is wanted.
+
    #[error("programming error: plain branch name must not start with refs/: {0:?}")]
+
    RefsInName(RefString),
+

+
    /// Failed to create a [`RefString`] from a string.
+
    #[error("failed to create a RefStr from a string {0:?}")]
+
    RefStrCreate(String, #[source] radicle::git::fmt::Error),
+

+
    /// Can't create a [`Qualified`] value from a [`BranchName`].
+
    #[error("failed to create a qualified branch name from a branch ref {0:?}")]
+
    QualifiedCreate(BranchName),
+

+
    /// Can't create a [`Namespaced`] value from a string slice.
+
    #[error("failed to create a name spaced Git ref from {0:?}")]
+
    NamespacedCreate(String),
+

+
    /// Name spaced branch name does not start with `refs/heads`".
+
    #[error("failed to get branch name from {0:?})")]
+
    NotABranch(RefString),
+

+
    /// Can't create a [`Component`] from a name space name.
+
    #[error("failed to create a name space component from its name {0:?}")]
+
    NamespaceName(RefString),
+
}
+

+
#[cfg(test)]
+
#[allow(clippy::unwrap_used)]
+
mod test {
+
    use super::*;
+

+
    #[test]
+
    fn ref_string_from_plain_branch_name() {
+
        assert_eq!(ref_string("main").map(|x| x.to_string()), Ok("main".into()));
+
    }
+

+
    #[test]
+
    fn plain_branch_name() {
+
        assert_eq!(
+
            branch_ref(&ref_string("main").unwrap()).map(|x| x.to_string()),
+
            Ok("main".into())
+
        );
+
    }
+

+
    #[test]
+
    fn qualified_branch_name_from_plain() {
+
        let name = branch_ref(&ref_string("main").unwrap()).unwrap();
+
        assert_eq!(
+
            qualified_branch(&name).map(|x| x.to_string()),
+
            Ok("refs/heads/main".into())
+
        );
+
    }
+

+
    #[test]
+
    fn namespaced_branch_from_plain() {
+
        let branch = branch_ref(&ref_string("main").unwrap()).unwrap();
+
        let ns = ref_string("node1").unwrap();
+
        let name = namespaced_branch(&ns, &branch);
+
        assert_eq!(
+
            name.map(|x| x.to_string()),
+
            Ok("refs/namespaces/node1/refs/heads/main".into())
+
        );
+
    }
+

+
    #[test]
+
    fn extracts_branch_namespaced_branch() {
+
        let branch = branch_ref(&ref_string("main").unwrap()).unwrap();
+
        let ns = ref_string("node1").unwrap();
+
        let name = namespaced_branch(&ns, &branch).unwrap();
+
        let extracted = branch_from_namespaced(&name);
+
        assert_eq!(extracted.map(|x| x.to_string()), Ok("main".into()));
+
    }
+
}