/src/gitoxide/gix-refspec/src/match_group/util.rs
Line | Count | Source |
1 | | use std::{borrow::Cow, ops::Range}; |
2 | | |
3 | | use bstr::{BStr, BString, ByteSlice, ByteVec}; |
4 | | use gix_hash::ObjectId; |
5 | | |
6 | | use crate::{match_group::Item, RefSpecRef}; |
7 | | |
8 | | /// A type keeping enough information about a ref-spec to be able to efficiently match it against multiple matcher items. |
9 | | #[derive(Debug)] |
10 | | pub struct Matcher<'a> { |
11 | | pub(crate) lhs: Option<Needle<'a>>, |
12 | | pub(crate) rhs: Option<Needle<'a>>, |
13 | | } |
14 | | |
15 | | impl<'a> Matcher<'a> { |
16 | | /// Match the lefthand-side `item` against this spec and return `(true, Some<rhs>)` to gain the other, |
17 | | /// transformed righthand-side of the match as configured by the refspec. |
18 | | /// Or return `(true, None)` if there was no `rhs` but the `item` matched. |
19 | | /// Lastly, return `(false, None)` if `item` didn't match at all. |
20 | | /// |
21 | | /// This may involve resolving a glob with an allocation, as the destination is built using the matching portion of a glob. |
22 | 0 | pub fn matches_lhs(&self, item: Item<'_>) -> (bool, Option<Cow<'a, BStr>>) { |
23 | 0 | match (self.lhs, self.rhs) { |
24 | 0 | (Some(lhs), None) => (lhs.matches(item).is_match(), None), |
25 | 0 | (Some(lhs), Some(rhs)) => lhs.matches(item).into_match_outcome(rhs, item), |
26 | 0 | (None, _) => (false, None), |
27 | | } |
28 | 0 | } |
29 | | |
30 | | /// Match the righthand-side `item` against this spec and return `(true, Some<lhs>)` to gain the other, |
31 | | /// transformed lefthand-side of the match as configured by the refspec. |
32 | | /// Or return `(true, None)` if there was no `lhs` but the `item` matched. |
33 | | /// Lastly, return `(false, None)` if `item` didn't match at all. |
34 | | /// |
35 | | /// This may involve resolving a glob with an allocation, as the destination is built using the matching portion of a glob. |
36 | 0 | pub fn matches_rhs(&self, item: Item<'_>) -> (bool, Option<Cow<'a, BStr>>) { |
37 | 0 | match (self.lhs, self.rhs) { |
38 | 0 | (None, Some(rhs)) => (rhs.matches(item).is_match(), None), |
39 | 0 | (Some(lhs), Some(rhs)) => rhs.matches(item).into_match_outcome(lhs, item), |
40 | 0 | (_, None) => (false, None), |
41 | | } |
42 | 0 | } |
43 | | } |
44 | | |
45 | | #[derive(Debug, Copy, Clone)] |
46 | | pub(crate) enum Needle<'a> { |
47 | | FullName(&'a BStr), |
48 | | PartialName(&'a BStr), |
49 | | Glob { name: &'a BStr, asterisk_pos: usize }, |
50 | | Pattern(&'a BStr), |
51 | | Object(ObjectId), |
52 | | } |
53 | | |
54 | | enum Match { |
55 | | /// There was no match. |
56 | | None, |
57 | | /// No additional data is provided as part of the match. |
58 | | Normal, |
59 | | /// The range of text to copy from the originating item name |
60 | | GlobRange(Range<usize>), |
61 | | } |
62 | | |
63 | | impl Match { |
64 | 0 | fn is_match(&self) -> bool { |
65 | 0 | !matches!(self, Match::None) |
66 | 0 | } |
67 | 0 | fn into_match_outcome<'a>(self, destination: Needle<'a>, item: Item<'_>) -> (bool, Option<Cow<'a, BStr>>) { |
68 | 0 | let arg = match self { |
69 | 0 | Match::None => return (false, None), |
70 | 0 | Match::Normal => None, |
71 | 0 | Match::GlobRange(range) => Some((range, item)), |
72 | | }; |
73 | 0 | (true, destination.to_bstr_replace(arg).into()) |
74 | 0 | } |
75 | | } |
76 | | |
77 | | impl<'a> Needle<'a> { |
78 | | #[inline] |
79 | 0 | fn matches(&self, item: Item<'_>) -> Match { |
80 | 0 | match self { |
81 | 0 | Needle::FullName(name) => { |
82 | 0 | if *name == item.full_ref_name { |
83 | 0 | Match::Normal |
84 | | } else { |
85 | 0 | Match::None |
86 | | } |
87 | | } |
88 | 0 | Needle::PartialName(name) => crate::spec::expand_partial_name(name, |expanded| { |
89 | 0 | (expanded == item.full_ref_name).then_some(Match::Normal) |
90 | 0 | }) |
91 | 0 | .unwrap_or(Match::None), |
92 | 0 | Needle::Glob { name, asterisk_pos } => { |
93 | 0 | match item.full_ref_name.get(..*asterisk_pos) { |
94 | 0 | Some(full_name_portion) if full_name_portion != name[..*asterisk_pos] => { |
95 | 0 | return Match::None; |
96 | | } |
97 | 0 | None => return Match::None, |
98 | 0 | _ => {} |
99 | | } |
100 | 0 | let tail = &name[*asterisk_pos + 1..]; |
101 | 0 | if !item.full_ref_name.ends_with(tail) { |
102 | 0 | return Match::None; |
103 | 0 | } |
104 | 0 | let end = item.full_ref_name.len() - tail.len(); |
105 | 0 | Match::GlobRange(*asterisk_pos..end) |
106 | | } |
107 | 0 | Needle::Pattern(pattern) => { |
108 | 0 | if gix_glob::wildmatch( |
109 | 0 | pattern, |
110 | 0 | item.full_ref_name, |
111 | | gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL, |
112 | | ) { |
113 | 0 | Match::Normal |
114 | | } else { |
115 | 0 | Match::None |
116 | | } |
117 | | } |
118 | 0 | Needle::Object(id) => { |
119 | 0 | if *id == item.target { |
120 | 0 | return Match::Normal; |
121 | 0 | } |
122 | 0 | match item.object { |
123 | 0 | Some(object) if object == *id => Match::Normal, |
124 | 0 | _ => Match::None, |
125 | | } |
126 | | } |
127 | | } |
128 | 0 | } |
129 | | |
130 | 0 | fn to_bstr_replace(self, range: Option<(Range<usize>, Item<'_>)>) -> Cow<'a, BStr> { |
131 | 0 | match (self, range) { |
132 | 0 | (Needle::FullName(name), None) => Cow::Borrowed(name), |
133 | 0 | (Needle::PartialName(name), None) => Cow::Owned({ |
134 | 0 | let mut base: BString = "refs/".into(); |
135 | 0 | if !(name.starts_with(b"tags/") || name.starts_with(b"remotes/")) { |
136 | 0 | base.push_str("heads/"); |
137 | 0 | } |
138 | 0 | base.push_str(name); |
139 | 0 | base |
140 | | }), |
141 | 0 | (Needle::Glob { name, asterisk_pos }, Some((range, item))) => { |
142 | 0 | let mut buf = Vec::with_capacity(name.len() + range.len() - 1); |
143 | 0 | buf.push_str(&name[..asterisk_pos]); |
144 | 0 | buf.push_str(&item.full_ref_name[range]); |
145 | 0 | buf.push_str(&name[asterisk_pos + 1..]); |
146 | 0 | Cow::Owned(buf.into()) |
147 | | } |
148 | 0 | (Needle::Object(id), None) => { |
149 | 0 | let mut name = id.to_string(); |
150 | 0 | name.insert_str(0, "refs/heads/"); |
151 | 0 | Cow::Owned(name.into()) |
152 | | } |
153 | 0 | (Needle::Pattern(name), None) => Cow::Borrowed(name), |
154 | 0 | (Needle::Glob { .. }, None) => unreachable!("BUG: no range provided for glob pattern"), |
155 | | (Needle::Pattern(_), Some(_)) => { |
156 | 0 | unreachable!("BUG: range provided for pattern, but patterns don't use ranges") |
157 | | } |
158 | | (_, Some(_)) => { |
159 | 0 | unreachable!("BUG: range provided even though needle wasn't a glob. Globs are symmetric.") |
160 | | } |
161 | | } |
162 | 0 | } |
163 | | |
164 | 0 | pub fn to_bstr(self) -> Cow<'a, BStr> { |
165 | 0 | self.to_bstr_replace(None) |
166 | 0 | } |
167 | | } |
168 | | |
169 | | impl<'a> From<&'a BStr> for Needle<'a> { |
170 | 0 | fn from(v: &'a BStr) -> Self { |
171 | 0 | if let Some(pos) = v.find_byte(b'*') { |
172 | 0 | Needle::Glob { |
173 | 0 | name: v, |
174 | 0 | asterisk_pos: pos, |
175 | 0 | } |
176 | 0 | } else if v.starts_with(b"refs/") { |
177 | 0 | Needle::FullName(v) |
178 | 0 | } else if let Ok(id) = gix_hash::ObjectId::from_hex(v) { |
179 | 0 | Needle::Object(id) |
180 | | } else { |
181 | 0 | Needle::PartialName(v) |
182 | | } |
183 | 0 | } |
184 | | } |
185 | | |
186 | | impl<'a> From<RefSpecRef<'a>> for Matcher<'a> { |
187 | 0 | fn from(v: RefSpecRef<'a>) -> Self { |
188 | 0 | let mut m = Matcher { |
189 | 0 | lhs: v.src.map(Into::into), |
190 | 0 | rhs: v.dst.map(Into::into), |
191 | 0 | }; |
192 | 0 | if m.rhs.is_none() { |
193 | 0 | if let Some(src) = v.src { |
194 | 0 | if must_use_pattern_matching(src) { |
195 | 0 | m.lhs = Some(Needle::Pattern(src)); |
196 | 0 | } |
197 | 0 | } |
198 | 0 | } |
199 | 0 | m |
200 | 0 | } |
201 | | } |
202 | | |
203 | | /// Check if a pattern is complex enough to require wildmatch instead of simple glob matching |
204 | 0 | fn must_use_pattern_matching(pattern: &BStr) -> bool { |
205 | 0 | let asterisk_count = pattern.iter().filter(|&&b| b == b'*').count(); |
206 | 0 | if asterisk_count > 1 { |
207 | 0 | return true; |
208 | 0 | } |
209 | 0 | pattern |
210 | 0 | .iter() |
211 | 0 | .any(|&b| b == b'?' || b == b'[' || b == b']' || b == b'\\') |
212 | 0 | } |