git_ref_format_core/
deriv.rs

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