git_ref_format_core/
deriv.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Display},
4    ops::Deref,
5};
6
7use crate::{
8    lit, name,
9    refspec::{PatternStr, QualifiedPattern},
10    Component, RefStr, RefString,
11};
12
13/// A fully-qualified refname.
14///
15/// A refname is qualified _iff_ it starts with "refs/" and has at least three
16/// components. This implies that a [`Qualified`] ref has a category, such as
17/// "refs/heads/main".
18#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
19pub struct Qualified<'a>(pub(crate) Cow<'a, RefStr>);
20
21impl<'a> Qualified<'a> {
22    /// Infallibly create a [`Qualified`] from components.
23    ///
24    /// Note that the "refs/" prefix is implicitly added, so `a` is the second
25    /// [`Component`]. Mirroring [`Self::non_empty_components`], providing
26    /// two [`Component`]s guarantees well-formedness of the [`Qualified`].
27    /// `tail` may be empty.
28    ///
29    /// # Example
30    ///
31    /// ```no_run
32    /// use git_ref_format::{component, Qualified};
33    ///
34    /// assert_eq!(
35    ///     "refs/heads/main",
36    ///     Qualified::from_components(component::HEADS, component::MAIN, None).as_str()
37    /// )
38    /// ```
39    pub fn from_components<'b, 'c, 'd, A, B, C>(a: A, b: B, tail: C) -> Self
40    where
41        A: Into<Component<'b>>,
42        B: Into<Component<'c>>,
43        C: IntoIterator<Item = Component<'d>>,
44    {
45        let mut inner = name::REFS.join(a.into()).and(b.into());
46        inner.extend(tail);
47
48        Self(inner.into())
49    }
50
51    pub fn from_refstr(r: impl Into<Cow<'a, RefStr>>) -> Option<Self> {
52        Self::_from_refstr(r.into())
53    }
54
55    fn _from_refstr(r: Cow<'a, RefStr>) -> Option<Self> {
56        let mut iter = r.iter();
57        match (iter.next()?, iter.next()?, iter.next()?) {
58            ("refs", _, _) => Some(Qualified(r)),
59            _ => None,
60        }
61    }
62
63    #[inline]
64    pub fn as_str(&self) -> &str {
65        self.as_ref()
66    }
67
68    #[inline]
69    pub fn join<'b, R>(&self, other: R) -> Qualified<'b>
70    where
71        R: AsRef<RefStr>,
72    {
73        Qualified(self.0.join(other).into())
74    }
75
76    pub fn to_pattern<P>(&'a self, pattern: P) -> QualifiedPattern<'a>
77    where
78        P: AsRef<PatternStr>,
79    {
80        QualifiedPattern(Cow::Owned(RefStr::to_pattern(self, pattern.as_ref())))
81    }
82
83    #[inline]
84    pub fn to_namespaced(&'a self) -> Option<Namespaced<'a>> {
85        self.0.as_ref().into()
86    }
87
88    /// Add a namespace.
89    ///
90    /// Creates a new [`Namespaced`] by prefxing `self` with
91    /// `refs/namespaces/<ns>`.
92    pub fn with_namespace<'b>(&self, ns: Component<'b>) -> Namespaced<'a> {
93        Namespaced(Cow::Owned(
94            IntoIterator::into_iter([lit::Refs.into(), lit::Namespaces.into(), ns])
95                .chain(self.0.components())
96                .collect(),
97        ))
98    }
99
100    /// Like [`Self::non_empty_components`], but with string slices.
101    pub fn non_empty_iter(&'a self) -> (&'a str, &'a str, &'a str, name::Iter<'a>) {
102        let mut iter = self.iter();
103        (
104            iter.next().unwrap(),
105            iter.next().unwrap(),
106            iter.next().unwrap(),
107            iter,
108        )
109    }
110
111    /// Return the first three [`Component`]s, and a possibly empty iterator
112    /// over the remaining ones.
113    ///
114    /// A qualified ref is guaranteed to have at least three components, which
115    /// this method provides a witness of. This is useful eg. for pattern
116    /// matching on the prefix.
117    pub fn non_empty_components(
118        &'a self,
119    ) -> (
120        Component<'a>,
121        Component<'a>,
122        Component<'a>,
123        name::Components<'a>,
124    ) {
125        let mut cs = self.components();
126        (
127            cs.next().unwrap(),
128            cs.next().unwrap(),
129            cs.next().unwrap(),
130            cs,
131        )
132    }
133
134    #[inline]
135    pub fn to_owned<'b>(&self) -> Qualified<'b> {
136        Qualified(Cow::Owned(self.0.clone().into_owned()))
137    }
138
139    #[inline]
140    pub fn into_owned<'b>(self) -> Qualified<'b> {
141        Qualified(Cow::Owned(self.0.into_owned()))
142    }
143
144    #[inline]
145    pub fn into_refstring(self) -> RefString {
146        self.into()
147    }
148}
149
150impl Deref for Qualified<'_> {
151    type Target = RefStr;
152
153    #[inline]
154    fn deref(&self) -> &Self::Target {
155        &self.0
156    }
157}
158
159impl AsRef<RefStr> for Qualified<'_> {
160    #[inline]
161    fn as_ref(&self) -> &RefStr {
162        self
163    }
164}
165
166impl AsRef<str> for Qualified<'_> {
167    #[inline]
168    fn as_ref(&self) -> &str {
169        self.0.as_str()
170    }
171}
172
173impl AsRef<Self> for Qualified<'_> {
174    #[inline]
175    fn as_ref(&self) -> &Self {
176        self
177    }
178}
179
180impl<'a> From<Qualified<'a>> for Cow<'a, RefStr> {
181    #[inline]
182    fn from(q: Qualified<'a>) -> Self {
183        q.0
184    }
185}
186
187impl From<Qualified<'_>> for RefString {
188    #[inline]
189    fn from(q: Qualified) -> Self {
190        q.0.into_owned()
191    }
192}
193
194impl<T, U> From<(lit::Refs, T, U)> for Qualified<'_>
195where
196    T: AsRef<RefStr>,
197    U: AsRef<RefStr>,
198{
199    #[inline]
200    fn from((refs, cat, name): (lit::Refs, T, U)) -> Self {
201        let refs: &RefStr = refs.into();
202        Self(Cow::Owned(refs.join(cat).and(name)))
203    }
204}
205
206impl<T> From<lit::RefsHeads<T>> for Qualified<'_>
207where
208    T: AsRef<RefStr>,
209{
210    #[inline]
211    fn from((refs, heads, name): lit::RefsHeads<T>) -> Self {
212        Self(Cow::Owned(
213            IntoIterator::into_iter([Component::from(refs), heads.into()])
214                .collect::<RefString>()
215                .and(name),
216        ))
217    }
218}
219
220impl<T> From<lit::RefsTags<T>> for Qualified<'_>
221where
222    T: AsRef<RefStr>,
223{
224    #[inline]
225    fn from((refs, tags, name): lit::RefsTags<T>) -> Self {
226        Self(Cow::Owned(
227            IntoIterator::into_iter([Component::from(refs), tags.into()])
228                .collect::<RefString>()
229                .and(name),
230        ))
231    }
232}
233
234impl<T> From<lit::RefsNotes<T>> for Qualified<'_>
235where
236    T: AsRef<RefStr>,
237{
238    #[inline]
239    fn from((refs, notes, name): lit::RefsNotes<T>) -> Self {
240        Self(Cow::Owned(
241            IntoIterator::into_iter([Component::from(refs), notes.into()])
242                .collect::<RefString>()
243                .and(name),
244        ))
245    }
246}
247
248impl<T> From<lit::RefsRemotes<T>> for Qualified<'_>
249where
250    T: AsRef<RefStr>,
251{
252    #[inline]
253    fn from((refs, remotes, name): lit::RefsRemotes<T>) -> Self {
254        Self(Cow::Owned(
255            IntoIterator::into_iter([Component::from(refs), remotes.into()])
256                .collect::<RefString>()
257                .and(name),
258        ))
259    }
260}
261
262impl Display for Qualified<'_> {
263    #[inline]
264    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
265        self.0.fmt(f)
266    }
267}
268
269/// A [`Qualified`] ref under a git namespace.
270///
271/// A ref is namespaced if it starts with "refs/namespaces/", another path
272/// component, and "refs" or "HEAD". Eg.
273///
274///     refs/namespaces/xyz/refs/heads/main
275///     refs/namespaces/xyz/HEAD
276///
277/// Note that namespaces can be nested, so the result of
278/// [`Namespaced::strip_namespace`] may be convertible to a [`Namespaced`]
279/// again. For example:
280///
281/// ```no_run
282/// let full = refname!("refs/namespaces/a/refs/namespaces/b/refs/heads/main");
283/// let namespaced = full.namespaced().unwrap();
284/// let strip_first = namespaced.strip_namespace();
285/// let nested = strip_first.namespaced().unwrap();
286/// let strip_second = nested.strip_namespace();
287///
288/// assert_eq!("a", namespaced.namespace().as_str());
289/// assert_eq!("b", nested.namespace().as_str());
290/// assert_eq!("refs/namespaces/b/refs/heads/main", strip_first.as_str());
291/// assert_eq!("refs/heads/main", strip_second.as_str());
292/// ```
293#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
294pub struct Namespaced<'a>(Cow<'a, RefStr>);
295
296impl<'a> Namespaced<'a> {
297    pub fn namespace(&'a self) -> Component<'a> {
298        self.components().nth(2).unwrap()
299    }
300
301    pub fn strip_namespace<'b>(&self) -> Qualified<'b> {
302        const REFS_NAMESPACES: &RefStr = RefStr::from_str("refs/namespaces");
303
304        Qualified(Cow::Owned(
305            self.strip_prefix(REFS_NAMESPACES)
306                .unwrap()
307                .components()
308                .skip(1)
309                .collect(),
310        ))
311    }
312
313    pub fn strip_namespace_recursive<'b>(&self) -> Qualified<'b> {
314        let mut strip = self.strip_namespace();
315        while let Some(ns) = strip.to_namespaced() {
316            strip = ns.strip_namespace();
317        }
318        strip
319    }
320
321    #[inline]
322    pub fn to_owned<'b>(&self) -> Namespaced<'b> {
323        Namespaced(Cow::Owned(self.0.clone().into_owned()))
324    }
325
326    #[inline]
327    pub fn into_owned<'b>(self) -> Namespaced<'b> {
328        Namespaced(Cow::Owned(self.0.into_owned()))
329    }
330
331    #[inline]
332    pub fn into_qualified(self) -> Qualified<'a> {
333        self.into()
334    }
335}
336
337impl Deref for Namespaced<'_> {
338    type Target = RefStr;
339
340    #[inline]
341    fn deref(&self) -> &Self::Target {
342        &self.0
343    }
344}
345
346impl AsRef<RefStr> for Namespaced<'_> {
347    #[inline]
348    fn as_ref(&self) -> &RefStr {
349        self
350    }
351}
352
353impl AsRef<str> for Namespaced<'_> {
354    #[inline]
355    fn as_ref(&self) -> &str {
356        self.0.as_str()
357    }
358}
359
360impl<'a> From<Namespaced<'a>> for Qualified<'a> {
361    #[inline]
362    fn from(ns: Namespaced<'a>) -> Self {
363        Self(ns.0)
364    }
365}
366
367impl<'a> From<&'a RefStr> for Option<Namespaced<'a>> {
368    fn from(rs: &'a RefStr) -> Self {
369        let mut cs = rs.iter();
370        match (cs.next()?, cs.next()?, cs.next()?, cs.next()?) {
371            ("refs", "namespaces", _, "refs" | "HEAD") => Some(Namespaced(Cow::from(rs))),
372
373            _ => None,
374        }
375    }
376}
377
378impl<'a, T> From<lit::RefsNamespaces<'_, T>> for Namespaced<'static>
379where
380    T: Into<Component<'a>>,
381{
382    #[inline]
383    fn from((refs, namespaces, namespace, name): lit::RefsNamespaces<T>) -> Self {
384        Self(Cow::Owned(
385            IntoIterator::into_iter([refs.into(), namespaces.into(), namespace.into()])
386                .collect::<RefString>()
387                .and(name),
388        ))
389    }
390}
391
392impl Display for Namespaced<'_> {
393    #[inline]
394    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
395        self.0.fmt(f)
396    }
397}