Radish alpha
r
Git libraries for Radicle
Radicle
Git (anonymous pull)
Log in to clone via SSH
git-ref-format: add QualifiedPattern and NamespacedPattern
Fintan Halpenny committed 3 years ago
commit 6479ba091fa3ee3b16fba61138cfd8c6ec6d985d
parent b98c9aac03229823ea8165a298b958d08f2d36e4
4 files changed +366 -3
modified git-ref-format/core/src/deriv.rs
@@ -9,7 +9,14 @@ use std::{
    ops::Deref,
};

-
use crate::{lit, name, Component, RefStr, RefString};
+
use crate::{
+
    lit,
+
    name,
+
    refspec::{PatternStr, QualifiedPattern},
+
    Component,
+
    RefStr,
+
    RefString,
+
};

/// A fully-qualified refname.
///
@@ -74,6 +81,13 @@ impl<'a> Qualified<'a> {
        Qualified(self.0.join(other).into())
    }

+
    pub fn to_pattern<P>(&self, pattern: P) -> QualifiedPattern
+
    where
+
        P: AsRef<PatternStr>,
+
    {
+
        QualifiedPattern(Cow::Owned(RefStr::to_pattern(self, pattern.as_ref())))
+
    }
+

    #[inline]
    pub fn to_namespaced(&self) -> Option<Namespaced> {
        self.0.as_ref().into()
modified git-ref-format/core/src/refspec.rs
@@ -13,7 +13,7 @@ use std::{

use thiserror::Error;

-
use crate::{check, RefStr, RefString};
+
use crate::{check, lit, RefStr, RefString};

mod iter;
pub use iter::{Component, Components, Iter};
@@ -54,6 +54,16 @@ impl PatternStr {
    }

    #[inline]
+
    pub fn qualified(&self) -> Option<QualifiedPattern> {
+
        QualifiedPattern::from_patternstr(self)
+
    }
+

+
    #[inline]
+
    pub fn to_namespaced(&self) -> Option<NamespacedPattern> {
+
        self.into()
+
    }
+

+
    #[inline]
    pub fn iter(&self) -> Iter {
        self.0.split('/')
    }
@@ -292,3 +302,259 @@ impl Display for PatternString {
        f.write_str(&self.0)
    }
}
+

+
/// A fully-qualified refspec.
+
///
+
/// A refspec is qualified _iff_ it starts with "refs/" and has at least three
+
/// components. This implies that a [`QualifiedPattern`] ref has a category,
+
/// such as "refs/heads/*".
+
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
+
pub struct QualifiedPattern<'a>(pub(crate) Cow<'a, PatternStr>);
+

+
impl<'a> QualifiedPattern<'a> {
+
    pub fn from_patternstr(r: impl Into<Cow<'a, PatternStr>>) -> Option<Self> {
+
        Self::_from_patternstr(r.into())
+
    }
+

+
    fn _from_patternstr(r: Cow<'a, PatternStr>) -> Option<Self> {
+
        let mut iter = r.iter();
+
        match (iter.next()?, iter.next()?, iter.next()?) {
+
            ("refs", _, _) => Some(QualifiedPattern(r)),
+
            _ => None,
+
        }
+
    }
+

+
    #[inline]
+
    pub fn as_str(&self) -> &str {
+
        self.as_ref()
+
    }
+

+
    #[inline]
+
    pub fn join<'b, R>(&self, other: R) -> QualifiedPattern<'b>
+
    where
+
        R: AsRef<RefStr>,
+
    {
+
        QualifiedPattern(Cow::Owned(self.0.join(other)))
+
    }
+

+
    #[inline]
+
    pub fn to_namespaced(&self) -> Option<NamespacedPattern> {
+
        self.0.as_ref().into()
+
    }
+

+
    /// Add a namespace.
+
    ///
+
    /// Creates a new [`NamespacedPattern`] by prefxing `self` with
+
    /// "refs/namespaces/<ns>".
+
    pub fn with_namespace<'b>(
+
        &self,
+
        ns: Component<'b>,
+
    ) -> Result<NamespacedPattern<'a>, DuplicateGlob> {
+
        PatternString::from_components(
+
            IntoIterator::into_iter([lit::Refs.into(), lit::Namespaces.into(), ns])
+
                .chain(self.components()),
+
        )
+
        .map(|pat| NamespacedPattern(Cow::Owned(pat)))
+
    }
+

+
    /// Like [`Self::non_empty_components`], but with string slices.
+
    pub fn non_empty_iter(&self) -> (&str, &str, &str, Iter) {
+
        let mut iter = self.iter();
+
        (
+
            iter.next().unwrap(),
+
            iter.next().unwrap(),
+
            iter.next().unwrap(),
+
            iter,
+
        )
+
    }
+

+
    /// Return the first three [`Component`]s, and a possibly empty iterator
+
    /// over the remaining ones.
+
    ///
+
    /// A qualified ref is guaranteed to have at least three components, which
+
    /// this method provides a witness of. This is useful eg. for pattern
+
    /// matching on the prefix.
+
    pub fn non_empty_components(&self) -> (Component, Component, Component, Components) {
+
        let mut cs = self.components();
+
        (
+
            cs.next().unwrap(),
+
            cs.next().unwrap(),
+
            cs.next().unwrap(),
+
            cs,
+
        )
+
    }
+

+
    #[inline]
+
    pub fn to_owned<'b>(&self) -> QualifiedPattern<'b> {
+
        QualifiedPattern(Cow::Owned(self.0.clone().into_owned()))
+
    }
+

+
    #[inline]
+
    pub fn into_owned<'b>(self) -> QualifiedPattern<'b> {
+
        QualifiedPattern(Cow::Owned(self.0.into_owned()))
+
    }
+

+
    #[inline]
+
    pub fn into_patternstring(self) -> PatternString {
+
        self.into()
+
    }
+
}
+

+
impl Deref for QualifiedPattern<'_> {
+
    type Target = PatternStr;
+

+
    #[inline]
+
    fn deref(&self) -> &Self::Target {
+
        &self.0
+
    }
+
}
+

+
impl AsRef<PatternStr> for QualifiedPattern<'_> {
+
    #[inline]
+
    fn as_ref(&self) -> &PatternStr {
+
        self
+
    }
+
}
+

+
impl AsRef<str> for QualifiedPattern<'_> {
+
    #[inline]
+
    fn as_ref(&self) -> &str {
+
        self.0.as_str()
+
    }
+
}
+

+
impl AsRef<Self> for QualifiedPattern<'_> {
+
    #[inline]
+
    fn as_ref(&self) -> &Self {
+
        self
+
    }
+
}
+

+
impl<'a> From<QualifiedPattern<'a>> for Cow<'a, PatternStr> {
+
    #[inline]
+
    fn from(q: QualifiedPattern<'a>) -> Self {
+
        q.0
+
    }
+
}
+

+
impl From<QualifiedPattern<'_>> for PatternString {
+
    #[inline]
+
    fn from(q: QualifiedPattern) -> Self {
+
        q.0.into_owned()
+
    }
+
}
+

+
/// A [`PatternString`] ref under a git namespace.
+
///
+
/// A ref is namespaced if it starts with "refs/namespaces/", another path
+
/// component, and "refs/". Eg.
+
///
+
///     refs/namespaces/xyz/refs/heads/main
+
///
+
/// Note that namespaces can be nested, so the result of
+
/// [`NamespacedPattern::strip_namespace`] may be convertible to a
+
/// [`NamespacedPattern`] again. For example:
+
///
+
/// ```no_run
+
/// let full = pattern!("refs/namespaces/a/refs/namespaces/b/refs/heads/*");
+
/// let namespaced = full.to_namespaced().unwrap();
+
/// let strip_first = namespaced.strip_namespace();
+
/// let nested = strip_first.namespaced().unwrap();
+
/// let strip_second = nested.strip_namespace();
+
///
+
/// assert_eq!("a", namespaced.namespace().as_str());
+
/// assert_eq!("b", nested.namespace().as_str());
+
/// assert_eq!("refs/namespaces/b/refs/heads/*", strip_first.as_str());
+
/// assert_eq!("refs/heads/*", strip_second.as_str());
+
/// ```
+
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
+
pub struct NamespacedPattern<'a>(Cow<'a, PatternStr>);
+

+
impl<'a> NamespacedPattern<'a> {
+
    pub fn namespace(&self) -> Component {
+
        self.components().nth(2).unwrap()
+
    }
+

+
    pub fn strip_namespace(&self) -> PatternString {
+
        PatternString::from_components(self.components().skip(3))
+
            .expect("BUG: NamespacedPattern was constructed with a duplicate glob")
+
    }
+

+
    pub fn strip_namespace_recursive(&self) -> PatternString {
+
        let mut strip = self.strip_namespace();
+
        while let Some(ns) = strip.to_namespaced() {
+
            strip = ns.strip_namespace();
+
        }
+
        strip
+
    }
+

+
    #[inline]
+
    pub fn to_owned<'b>(&self) -> NamespacedPattern<'b> {
+
        NamespacedPattern(Cow::Owned(self.0.clone().into_owned()))
+
    }
+

+
    #[inline]
+
    pub fn into_owned<'b>(self) -> NamespacedPattern<'b> {
+
        NamespacedPattern(Cow::Owned(self.0.into_owned()))
+
    }
+

+
    #[inline]
+
    pub fn into_qualified(self) -> QualifiedPattern<'a> {
+
        self.into()
+
    }
+
}
+

+
impl Deref for NamespacedPattern<'_> {
+
    type Target = PatternStr;
+

+
    #[inline]
+
    fn deref(&self) -> &Self::Target {
+
        self.borrow()
+
    }
+
}
+

+
impl AsRef<PatternStr> for NamespacedPattern<'_> {
+
    #[inline]
+
    fn as_ref(&self) -> &PatternStr {
+
        self.0.as_ref()
+
    }
+
}
+

+
impl AsRef<str> for NamespacedPattern<'_> {
+
    #[inline]
+
    fn as_ref(&self) -> &str {
+
        self.0.as_str()
+
    }
+
}
+

+
impl Borrow<PatternStr> for NamespacedPattern<'_> {
+
    #[inline]
+
    fn borrow(&self) -> &PatternStr {
+
        PatternStr::from_str(self.0.as_str())
+
    }
+
}
+

+
impl<'a> From<NamespacedPattern<'a>> for QualifiedPattern<'a> {
+
    #[inline]
+
    fn from(ns: NamespacedPattern<'a>) -> Self {
+
        Self(ns.0)
+
    }
+
}
+

+
impl<'a> From<&'a PatternStr> for Option<NamespacedPattern<'a>> {
+
    fn from(rs: &'a PatternStr) -> Self {
+
        let mut cs = rs.iter();
+
        match (cs.next()?, cs.next()?, cs.next()?, cs.next()?) {
+
            ("refs", "namespaces", _, "refs") => Some(NamespacedPattern(Cow::from(rs))),
+

+
            _ => None,
+
        }
+
    }
+
}
+

+
impl Display for NamespacedPattern<'_> {
+
    #[inline]
+
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+
        self.0.fmt(f)
+
    }
+
}
modified git-ref-format/core/src/refspec/iter.rs
@@ -6,7 +6,7 @@
use std::fmt::{self, Display};

use super::PatternStr;
-
use crate::RefStr;
+
use crate::{lit, RefStr};

pub type Iter<'a> = std::str::Split<'a, char>;

@@ -40,6 +40,13 @@ impl Display for Component<'_> {
    }
}

+
impl<T: lit::Lit> From<T> for Component<'static> {
+
    #[inline]
+
    fn from(_: T) -> Self {
+
        Self::Normal(T::NAME)
+
    }
+
}
+

#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(Clone)]
pub struct Components<'a> {
modified git-ref-format/t/src/tests.rs
@@ -266,6 +266,82 @@ fn with_pattern_and() {
}

#[test]
+
fn pattern_is_qualified() {
+
    assert!(refspec::pattern!("refs/heads/*").qualified().is_some())
+
}
+

+
#[test]
+
fn pattern_is_not_qualified() {
+
    assert!(refspec::pattern!("heads/*").qualified().is_none())
+
}
+

+
#[test]
+
fn pattern_with_namespaced() {
+
    assert_eq!(
+
        "refs/namespaces/a/refs/heads/*",
+
        refspec::pattern!("refs/heads/*")
+
            .qualified()
+
            .unwrap()
+
            .with_namespace(refspec::Component::Normal(refname!("a").as_ref()))
+
            .unwrap()
+
            .as_str(),
+
    )
+
}
+

+
#[test]
+
fn pattern_not_namespaced_because_not_qualified() {
+
    assert!(refspec::pattern!("refs/namespaces/foo/*")
+
        .to_namespaced()
+
        .is_none())
+
}
+

+
#[test]
+
fn pattern_namespaced() {
+
    assert!(refspec::pattern!("refs/namespaces/a/refs/foo/*")
+
        .to_namespaced()
+
        .is_some())
+
}
+

+
#[test]
+
fn pattern_strip_namespace() {
+
    assert_eq!(
+
        "refs/rad/*",
+
        refspec::pattern!("refs/namespaces/xyz/refs/rad/*")
+
            .to_namespaced()
+
            .unwrap()
+
            .strip_namespace()
+
            .as_str()
+
    )
+
}
+

+
#[test]
+
fn pattern_strip_nested_namespaces() {
+
    let full = refspec::pattern!("refs/namespaces/a/refs/namespaces/b/refs/heads/*");
+
    let namespaced = full.to_namespaced().unwrap();
+
    let strip_first = namespaced.strip_namespace();
+
    let nested = strip_first.to_namespaced().unwrap();
+
    let strip_second = nested.strip_namespace();
+

+
    assert_eq!("a", namespaced.namespace().as_str());
+
    assert_eq!("b", nested.namespace().as_str());
+
    assert_eq!("refs/namespaces/b/refs/heads/*", strip_first.as_str());
+
    assert_eq!("refs/heads/*", strip_second.as_str());
+
}
+

+
#[test]
+
fn pattern_qualified_with_namespace() {
+
    assert_eq!(
+
        "refs/namespaces/a/refs/heads/*",
+
        refspec::pattern!("refs/heads/*")
+
            .qualified()
+
            .unwrap()
+
            .with_namespace(refspec::Component::Normal(refname!("a").as_ref()))
+
            .unwrap()
+
            .as_str(),
+
    )
+
}
+

+
#[test]
fn collect() {
    assert_eq!(
        "refs/heads/main",