git_ref_format_core/
refspec.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::{Borrow, Cow},
8    convert::TryFrom,
9    fmt::{self, Display, Write as _},
10    iter::FromIterator,
11    ops::Deref,
12};
13
14use thiserror::Error;
15
16use crate::{check, lit, Namespaced, Qualified, RefStr, RefString};
17
18mod iter;
19pub use iter::{component, Component, Components, Iter};
20
21pub const STAR: &PatternStr = PatternStr::from_str("*");
22
23const CHECK_OPTS: check::Options = check::Options {
24    allow_onelevel: true,
25    allow_pattern: true,
26};
27
28#[repr(transparent)]
29#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
30pub struct PatternStr(str);
31
32impl PatternStr {
33    #[inline]
34    pub fn try_from_str(s: &str) -> Result<&Self, check::Error> {
35        TryFrom::try_from(s)
36    }
37
38    #[inline]
39    pub fn as_str(&self) -> &str {
40        self
41    }
42
43    pub fn join<R>(&self, other: R) -> PatternString
44    where
45        R: AsRef<RefStr>,
46    {
47        self._join(other.as_ref())
48    }
49
50    fn _join(&self, other: &RefStr) -> PatternString {
51        let mut buf = self.to_owned();
52        buf.push(other);
53        buf
54    }
55
56    #[inline]
57    pub fn qualified(&self) -> Option<QualifiedPattern> {
58        QualifiedPattern::from_patternstr(self)
59    }
60
61    #[inline]
62    pub fn to_namespaced(&self) -> Option<NamespacedPattern> {
63        self.into()
64    }
65
66    #[inline]
67    pub fn iter(&self) -> Iter {
68        self.0.split('/')
69    }
70
71    #[inline]
72    pub fn components(&self) -> Components {
73        Components::from(self)
74    }
75
76    pub(crate) const fn from_str(s: &str) -> &PatternStr {
77        unsafe { &*(s as *const str as *const PatternStr) }
78    }
79}
80
81impl Deref for PatternStr {
82    type Target = str;
83
84    #[inline]
85    fn deref(&self) -> &Self::Target {
86        &self.0
87    }
88}
89
90impl AsRef<str> for PatternStr {
91    #[inline]
92    fn as_ref(&self) -> &str {
93        self
94    }
95}
96
97impl AsRef<Self> for PatternStr {
98    #[inline]
99    fn as_ref(&self) -> &Self {
100        self
101    }
102}
103
104impl<'a> TryFrom<&'a str> for &'a PatternStr {
105    type Error = check::Error;
106
107    #[inline]
108    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
109        check::ref_format(CHECK_OPTS, s).map(|()| PatternStr::from_str(s))
110    }
111}
112
113impl<'a> From<&'a RefStr> for &'a PatternStr {
114    #[inline]
115    fn from(rs: &'a RefStr) -> Self {
116        PatternStr::from_str(rs.as_str())
117    }
118}
119
120impl<'a> From<&'a PatternStr> for Cow<'a, PatternStr> {
121    #[inline]
122    fn from(p: &'a PatternStr) -> Cow<'a, PatternStr> {
123        Cow::Borrowed(p)
124    }
125}
126
127impl Display for PatternStr {
128    #[inline]
129    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130        f.write_str(self)
131    }
132}
133
134#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
135pub struct PatternString(pub(crate) String);
136
137impl PatternString {
138    #[inline]
139    pub fn as_str(&self) -> &str {
140        self.as_ref()
141    }
142
143    #[inline]
144    pub fn as_pattern_str(&self) -> &PatternStr {
145        self.as_ref()
146    }
147
148    #[inline]
149    pub fn from_components<'a, T>(iter: T) -> Result<Self, DuplicateGlob>
150    where
151        T: IntoIterator<Item = Component<'a>>,
152    {
153        iter.into_iter().collect()
154    }
155
156    #[inline]
157    pub fn and<R>(mut self, other: R) -> Self
158    where
159        R: AsRef<RefStr>,
160    {
161        self._push(other.as_ref());
162        self
163    }
164
165    #[inline]
166    pub fn push<R>(&mut self, other: R)
167    where
168        R: AsRef<RefStr>,
169    {
170        self._push(other.as_ref())
171    }
172
173    fn _push(&mut self, other: &RefStr) {
174        self.0.push('/');
175        self.0.push_str(other.as_str());
176    }
177
178    #[inline]
179    pub fn pop(&mut self) -> bool {
180        match self.0.rfind('/') {
181            None => false,
182            Some(idx) => {
183                self.0.truncate(idx);
184                true
185            }
186        }
187    }
188}
189
190impl Deref for PatternString {
191    type Target = PatternStr;
192
193    #[inline]
194    fn deref(&self) -> &Self::Target {
195        self.borrow()
196    }
197}
198
199impl AsRef<PatternStr> for PatternString {
200    #[inline]
201    fn as_ref(&self) -> &PatternStr {
202        self
203    }
204}
205
206impl AsRef<str> for PatternString {
207    #[inline]
208    fn as_ref(&self) -> &str {
209        self.0.as_str()
210    }
211}
212
213impl Borrow<PatternStr> for PatternString {
214    #[inline]
215    fn borrow(&self) -> &PatternStr {
216        PatternStr::from_str(self.0.as_str())
217    }
218}
219
220impl ToOwned for PatternStr {
221    type Owned = PatternString;
222
223    #[inline]
224    fn to_owned(&self) -> Self::Owned {
225        PatternString(self.0.to_owned())
226    }
227}
228
229impl From<RefString> for PatternString {
230    #[inline]
231    fn from(rs: RefString) -> Self {
232        Self(rs.into())
233    }
234}
235
236impl<'a> From<&'a PatternString> for Cow<'a, PatternStr> {
237    #[inline]
238    fn from(p: &'a PatternString) -> Cow<'a, PatternStr> {
239        Cow::Borrowed(p.as_ref())
240    }
241}
242
243impl From<PatternString> for String {
244    #[inline]
245    fn from(p: PatternString) -> Self {
246        p.0
247    }
248}
249
250impl TryFrom<&str> for PatternString {
251    type Error = check::Error;
252
253    #[inline]
254    fn try_from(s: &str) -> Result<Self, Self::Error> {
255        PatternStr::try_from_str(s).map(ToOwned::to_owned)
256    }
257}
258
259impl TryFrom<String> for PatternString {
260    type Error = check::Error;
261
262    #[inline]
263    fn try_from(s: String) -> Result<Self, Self::Error> {
264        check::ref_format(CHECK_OPTS, s.as_str()).map(|()| PatternString(s))
265    }
266}
267
268#[derive(Debug, Error)]
269#[error("more than one '*' encountered")]
270pub struct DuplicateGlob;
271
272impl<'a> FromIterator<Component<'a>> for Result<PatternString, DuplicateGlob> {
273    fn from_iter<T>(iter: T) -> Self
274    where
275        T: IntoIterator<Item = Component<'a>>,
276    {
277        use Component::*;
278
279        let mut buf = String::new();
280        let mut seen_glob = false;
281        for c in iter {
282            if let Glob(_) = c {
283                if seen_glob {
284                    return Err(DuplicateGlob);
285                }
286
287                seen_glob = true;
288            }
289
290            buf.push_str(c.as_str());
291            buf.push('/');
292        }
293        buf.truncate(buf.len() - 1);
294
295        Ok(PatternString(buf))
296    }
297}
298
299impl Display for PatternString {
300    #[inline]
301    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
302        f.write_str(&self.0)
303    }
304}
305
306/// A fully-qualified refspec.
307///
308/// A refspec is qualified _iff_ it starts with "refs/" and has at least three
309/// components. This implies that a [`QualifiedPattern`] ref has a category,
310/// such as "refs/heads/*".
311#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
312pub struct QualifiedPattern<'a>(pub(crate) Cow<'a, PatternStr>);
313
314impl<'a> QualifiedPattern<'a> {
315    pub fn from_patternstr(r: impl Into<Cow<'a, PatternStr>>) -> Option<Self> {
316        Self::_from_patternstr(r.into())
317    }
318
319    fn _from_patternstr(r: Cow<'a, PatternStr>) -> Option<Self> {
320        let mut iter = r.iter();
321        match (iter.next()?, iter.next()?, iter.next()?) {
322            ("refs", _, _) => Some(QualifiedPattern(r)),
323            _ => None,
324        }
325    }
326
327    #[inline]
328    pub fn as_str(&self) -> &str {
329        self.as_ref()
330    }
331
332    #[inline]
333    pub fn join<'b, R>(&self, other: R) -> QualifiedPattern<'b>
334    where
335        R: AsRef<RefStr>,
336    {
337        QualifiedPattern(Cow::Owned(self.0.join(other)))
338    }
339
340    #[inline]
341    pub fn to_namespaced(&self) -> Option<NamespacedPattern> {
342        self.0.as_ref().into()
343    }
344
345    /// Add a namespace.
346    ///
347    /// Creates a new [`NamespacedPattern`] by prefxing `self` with
348    /// `refs/namespaces/<ns>`.
349    pub fn with_namespace<'b>(
350        &self,
351        ns: Component<'b>,
352    ) -> Result<NamespacedPattern<'a>, DuplicateGlob> {
353        PatternString::from_components(
354            IntoIterator::into_iter([lit::Refs.into(), lit::Namespaces.into(), ns])
355                .chain(self.components()),
356        )
357        .map(|pat| NamespacedPattern(Cow::Owned(pat)))
358    }
359
360    /// Like [`Self::non_empty_components`], but with string slices.
361    pub fn non_empty_iter(&self) -> (&str, &str, &str, Iter) {
362        let mut iter = self.iter();
363        (
364            iter.next().unwrap(),
365            iter.next().unwrap(),
366            iter.next().unwrap(),
367            iter,
368        )
369    }
370
371    /// Return the first three [`Component`]s, and a possibly empty iterator
372    /// over the remaining ones.
373    ///
374    /// A qualified ref is guaranteed to have at least three components, which
375    /// this method provides a witness of. This is useful eg. for pattern
376    /// matching on the prefix.
377    pub fn non_empty_components(&self) -> (Component, Component, Component, Components) {
378        let mut cs = self.components();
379        (
380            cs.next().unwrap(),
381            cs.next().unwrap(),
382            cs.next().unwrap(),
383            cs,
384        )
385    }
386
387    #[inline]
388    pub fn to_owned<'b>(&self) -> QualifiedPattern<'b> {
389        QualifiedPattern(Cow::Owned(self.0.clone().into_owned()))
390    }
391
392    #[inline]
393    pub fn into_owned<'b>(self) -> QualifiedPattern<'b> {
394        QualifiedPattern(Cow::Owned(self.0.into_owned()))
395    }
396
397    #[inline]
398    pub fn into_patternstring(self) -> PatternString {
399        self.into()
400    }
401}
402
403impl Deref for QualifiedPattern<'_> {
404    type Target = PatternStr;
405
406    #[inline]
407    fn deref(&self) -> &Self::Target {
408        &self.0
409    }
410}
411
412impl AsRef<PatternStr> for QualifiedPattern<'_> {
413    #[inline]
414    fn as_ref(&self) -> &PatternStr {
415        self
416    }
417}
418
419impl AsRef<str> for QualifiedPattern<'_> {
420    #[inline]
421    fn as_ref(&self) -> &str {
422        self.0.as_str()
423    }
424}
425
426impl AsRef<Self> for QualifiedPattern<'_> {
427    #[inline]
428    fn as_ref(&self) -> &Self {
429        self
430    }
431}
432
433impl Display for QualifiedPattern<'_> {
434    #[inline]
435    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
436        self.0.fmt(f)
437    }
438}
439
440impl<'a> From<Qualified<'a>> for QualifiedPattern<'a> {
441    #[inline]
442    fn from(q: Qualified<'a>) -> Self {
443        Self(Cow::Owned(q.into_refstring().into()))
444    }
445}
446
447impl<'a> From<QualifiedPattern<'a>> for Cow<'a, PatternStr> {
448    #[inline]
449    fn from(q: QualifiedPattern<'a>) -> Self {
450        q.0
451    }
452}
453
454impl From<QualifiedPattern<'_>> for PatternString {
455    #[inline]
456    fn from(q: QualifiedPattern) -> Self {
457        q.0.into_owned()
458    }
459}
460
461/// A [`PatternString`] ref under a git namespace.
462///
463/// A ref is namespaced if it starts with "refs/namespaces/", another path
464/// component, and "refs/". Eg.
465///
466///     refs/namespaces/xyz/refs/heads/main
467///
468/// Note that namespaces can be nested, so the result of
469/// [`NamespacedPattern::strip_namespace`] may be convertible to a
470/// [`NamespacedPattern`] again. For example:
471///
472/// ```no_run
473/// let full = pattern!("refs/namespaces/a/refs/namespaces/b/refs/heads/*");
474/// let namespaced = full.to_namespaced().unwrap();
475/// let strip_first = namespaced.strip_namespace();
476/// let nested = strip_first.namespaced().unwrap();
477/// let strip_second = nested.strip_namespace();
478///
479/// assert_eq!("a", namespaced.namespace().as_str());
480/// assert_eq!("b", nested.namespace().as_str());
481/// assert_eq!("refs/namespaces/b/refs/heads/*", strip_first.as_str());
482/// assert_eq!("refs/heads/*", strip_second.as_str());
483/// ```
484#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
485pub struct NamespacedPattern<'a>(Cow<'a, PatternStr>);
486
487impl<'a> NamespacedPattern<'a> {
488    pub fn namespace(&self) -> Component {
489        self.components().nth(2).unwrap()
490    }
491
492    pub fn strip_namespace(&self) -> PatternString {
493        PatternString::from_components(self.components().skip(3))
494            .expect("BUG: NamespacedPattern was constructed with a duplicate glob")
495    }
496
497    pub fn strip_namespace_recursive(&self) -> PatternString {
498        let mut strip = self.strip_namespace();
499        while let Some(ns) = strip.to_namespaced() {
500            strip = ns.strip_namespace();
501        }
502        strip
503    }
504
505    #[inline]
506    pub fn to_owned<'b>(&self) -> NamespacedPattern<'b> {
507        NamespacedPattern(Cow::Owned(self.0.clone().into_owned()))
508    }
509
510    #[inline]
511    pub fn into_owned<'b>(self) -> NamespacedPattern<'b> {
512        NamespacedPattern(Cow::Owned(self.0.into_owned()))
513    }
514
515    #[inline]
516    pub fn into_qualified(self) -> QualifiedPattern<'a> {
517        self.into()
518    }
519}
520
521impl Deref for NamespacedPattern<'_> {
522    type Target = PatternStr;
523
524    #[inline]
525    fn deref(&self) -> &Self::Target {
526        self.borrow()
527    }
528}
529
530impl AsRef<PatternStr> for NamespacedPattern<'_> {
531    #[inline]
532    fn as_ref(&self) -> &PatternStr {
533        self.0.as_ref()
534    }
535}
536
537impl AsRef<str> for NamespacedPattern<'_> {
538    #[inline]
539    fn as_ref(&self) -> &str {
540        self.0.as_str()
541    }
542}
543
544impl Borrow<PatternStr> for NamespacedPattern<'_> {
545    #[inline]
546    fn borrow(&self) -> &PatternStr {
547        PatternStr::from_str(self.0.as_str())
548    }
549}
550
551impl<'a> From<Namespaced<'a>> for NamespacedPattern<'a> {
552    #[inline]
553    fn from(ns: Namespaced<'a>) -> Self {
554        NamespacedPattern(Cow::Owned(ns.to_ref_string().into()))
555    }
556}
557
558impl<'a> From<NamespacedPattern<'a>> for QualifiedPattern<'a> {
559    #[inline]
560    fn from(ns: NamespacedPattern<'a>) -> Self {
561        Self(ns.0)
562    }
563}
564
565impl<'a> From<&'a PatternStr> for Option<NamespacedPattern<'a>> {
566    fn from(rs: &'a PatternStr) -> Self {
567        let mut cs = rs.iter();
568        match (cs.next()?, cs.next()?, cs.next()?, cs.next()?) {
569            ("refs", "namespaces", _, "refs") => Some(NamespacedPattern(Cow::from(rs))),
570
571            _ => None,
572        }
573    }
574}
575
576impl Display for NamespacedPattern<'_> {
577    #[inline]
578    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
579        self.0.fmt(f)
580    }
581}
582
583/// A Git [refspec].
584///
585/// **Note** that this is a simplified version of a [refspec] where
586/// the `src` and `dst` are required and there is no way to construct
587/// a negative refspec, e.g. `^refs/heads/no-thanks`.
588///
589/// [refspec]: https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
590#[derive(Clone, Debug, PartialEq, Eq)]
591pub struct Refspec<T, U> {
592    pub src: T,
593    pub dst: U,
594    pub force: bool,
595}
596
597impl<T, U> fmt::Display for Refspec<T, U>
598where
599    T: fmt::Display,
600    U: fmt::Display,
601{
602    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603        if self.force {
604            f.write_char('+')?;
605        }
606        write!(f, "{}:{}", self.src, self.dst)
607    }
608}