git_ref_format_core/
refspec.rs

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