// Copyright © 2021 The Radicle Link Contributors // // This file is part of radicle-link, distributed under the GPLv3 with Radicle // Linking Exception. For full terms see the included LICENSE file. use std::{ borrow::Cow, fmt::{self, Display}, ops::Deref, }; use crate::{ lit, name, refspec::{PatternStr, QualifiedPattern}, Component, RefStr, RefString, }; /// A fully-qualified refname. /// /// A refname is qualified _iff_ it starts with "refs/" and has at least three /// components. This implies that a [`Qualified`] ref has a category, such as /// "refs/heads/main". #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct Qualified<'a>(pub(crate) Cow<'a, RefStr>); impl<'a> Qualified<'a> { /// Infallibly create a [`Qualified`] from components. /// /// Note that the "refs/" prefix is implicitly added, so `a` is the second /// [`Component`]. Mirroring [`Self::non_empty_components`], providing /// two [`Component`]s guarantees well-formedness of the [`Qualified`]. /// `tail` may be empty. /// /// # Example /// /// ```no_run /// use git_ref_format::{component, Qualified}; /// /// assert_eq!( /// "refs/heads/main", /// Qualified::from_components(component::HEADS, component::MAIN, None).as_str() /// ) /// ``` pub fn from_components<'b, 'c, 'd, A, B, C>(a: A, b: B, tail: C) -> Self where A: Into>, B: Into>, C: IntoIterator>, { let mut inner = name::REFS.join(a.into()).and(b.into()); inner.extend(tail); Self(inner.into()) } pub fn from_refstr(r: impl Into>) -> Option { Self::_from_refstr(r.into()) } fn _from_refstr(r: Cow<'a, RefStr>) -> Option { let mut iter = r.iter(); match (iter.next()?, iter.next()?, iter.next()?) { ("refs", _, _) => Some(Qualified(r)), _ => None, } } #[inline] pub fn as_str(&self) -> &str { self.as_ref() } #[inline] pub fn join<'b, R>(&self, other: R) -> Qualified<'b> where R: AsRef, { Qualified(self.0.join(other).into()) } pub fn to_pattern

(&self, pattern: P) -> QualifiedPattern where P: AsRef, { QualifiedPattern(Cow::Owned(RefStr::to_pattern(self, pattern.as_ref()))) } #[inline] pub fn to_namespaced(&self) -> Option { self.0.as_ref().into() } /// Add a namespace. /// /// Creates a new [`Namespaced`] by prefxing `self` with /// "refs/namespaces/". pub fn with_namespace<'b>(&self, ns: Component<'b>) -> Namespaced<'a> { Namespaced(Cow::Owned( IntoIterator::into_iter([lit::Refs.into(), lit::Namespaces.into(), ns]) .chain(self.0.components()) .collect(), )) } /// Like [`Self::non_empty_components`], but with string slices. pub fn non_empty_iter(&self) -> (&str, &str, &str, name::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, name::Components) { let mut cs = self.components(); ( cs.next().unwrap(), cs.next().unwrap(), cs.next().unwrap(), cs, ) } #[inline] pub fn to_owned<'b>(&self) -> Qualified<'b> { Qualified(Cow::Owned(self.0.clone().into_owned())) } #[inline] pub fn into_owned<'b>(self) -> Qualified<'b> { Qualified(Cow::Owned(self.0.into_owned())) } #[inline] pub fn into_refstring(self) -> RefString { self.into() } } impl Deref for Qualified<'_> { type Target = RefStr; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for Qualified<'_> { #[inline] fn as_ref(&self) -> &RefStr { self } } impl AsRef for Qualified<'_> { #[inline] fn as_ref(&self) -> &str { self.0.as_str() } } impl AsRef for Qualified<'_> { #[inline] fn as_ref(&self) -> &Self { self } } impl<'a> From> for Cow<'a, RefStr> { #[inline] fn from(q: Qualified<'a>) -> Self { q.0 } } impl From> for RefString { #[inline] fn from(q: Qualified) -> Self { q.0.into_owned() } } impl From<(lit::Refs, T, U)> for Qualified<'_> where T: AsRef, U: AsRef, { #[inline] fn from((refs, cat, name): (lit::Refs, T, U)) -> Self { let refs: &RefStr = refs.into(); Self(Cow::Owned(refs.join(cat).and(name))) } } impl From> for Qualified<'_> where T: AsRef, { #[inline] fn from((refs, heads, name): lit::RefsHeads) -> Self { Self(Cow::Owned( IntoIterator::into_iter([Component::from(refs), heads.into()]) .collect::() .and(name), )) } } impl From> for Qualified<'_> where T: AsRef, { #[inline] fn from((refs, tags, name): lit::RefsTags) -> Self { Self(Cow::Owned( IntoIterator::into_iter([Component::from(refs), tags.into()]) .collect::() .and(name), )) } } impl From> for Qualified<'_> where T: AsRef, { #[inline] fn from((refs, notes, name): lit::RefsNotes) -> Self { Self(Cow::Owned( IntoIterator::into_iter([Component::from(refs), notes.into()]) .collect::() .and(name), )) } } impl From> for Qualified<'_> where T: AsRef, { #[inline] fn from((refs, remotes, name): lit::RefsRemotes) -> Self { Self(Cow::Owned( IntoIterator::into_iter([Component::from(refs), remotes.into()]) .collect::() .and(name), )) } } impl Display for Qualified<'_> { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } } /// A [`Qualified`] 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 /// [`Namespaced::strip_namespace`] may be convertible to a [`Namespaced`] /// again. For example: /// /// ```no_run /// let full = refname!("refs/namespaces/a/refs/namespaces/b/refs/heads/main"); /// let namespaced = full.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/main", strip_first.as_str()); /// assert_eq!("refs/heads/main", strip_second.as_str()); /// ``` #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct Namespaced<'a>(Cow<'a, RefStr>); impl<'a> Namespaced<'a> { pub fn namespace(&self) -> Component { self.components().nth(2).unwrap() } pub fn strip_namespace<'b>(&self) -> Qualified<'b> { const REFS_NAMESPACES: &RefStr = RefStr::from_str("refs/namespaces"); Qualified(Cow::Owned( self.strip_prefix(REFS_NAMESPACES) .unwrap() .components() .skip(1) .collect(), )) } pub fn strip_namespace_recursive<'b>(&self) -> Qualified<'b> { 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) -> Namespaced<'b> { Namespaced(Cow::Owned(self.0.clone().into_owned())) } #[inline] pub fn into_owned<'b>(self) -> Namespaced<'b> { Namespaced(Cow::Owned(self.0.into_owned())) } #[inline] pub fn into_qualified(self) -> Qualified<'a> { self.into() } } impl Deref for Namespaced<'_> { type Target = RefStr; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for Namespaced<'_> { #[inline] fn as_ref(&self) -> &RefStr { self } } impl AsRef for Namespaced<'_> { #[inline] fn as_ref(&self) -> &str { self.0.as_str() } } impl<'a> From> for Qualified<'a> { #[inline] fn from(ns: Namespaced<'a>) -> Self { Self(ns.0) } } impl<'a> From<&'a RefStr> for Option> { fn from(rs: &'a RefStr) -> Self { let mut cs = rs.iter(); match (cs.next()?, cs.next()?, cs.next()?, cs.next()?) { ("refs", "namespaces", _, "refs") => Some(Namespaced(Cow::from(rs))), _ => None, } } } impl<'a, T> From> for Namespaced<'static> where T: Into>, { #[inline] fn from((refs, namespaces, namespace, name): lit::RefsNamespaces) -> Self { Self(Cow::Owned( IntoIterator::into_iter([refs.into(), namespaces.into(), namespace.into()]) .collect::() .and(name), )) } } impl Display for Namespaced<'_> { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } }