1use std::{convert::TryInto, str::FromStr, time::SystemTime};
2
3use bstr::{BStr, BString, ByteSlice, ByteVec};
4
5use crate::{
6 spec,
7 spec::parse::{delegate, delegate::SiblingBranch, Delegate, Error},
8};
9
10pub fn parse(mut input: &BStr, delegate: &mut impl Delegate) -> Result<(), Error> {
17 use delegate::{Kind, Revision};
18 let mut delegate = InterceptRev::new(delegate);
19 let mut prev_kind = None;
20 if let Some(b'^') = input.first() {
21 input = next(input).1;
22 let kind = spec::Kind::ExcludeReachable;
23 delegate.kind(kind).ok_or(Error::Delegate)?;
24 prev_kind = kind.into();
25 }
26
27 let mut found_revision;
28 (input, found_revision) = {
29 let rest = revision(input, &mut delegate)?;
30 (rest, rest != input)
31 };
32 if delegate.done {
33 return if input.is_empty() {
34 Ok(())
35 } else {
36 Err(Error::UnconsumedInput { input: input.into() })
37 };
38 }
39 if let Some((rest, kind)) = try_range(input) {
40 if let Some(prev_kind) = prev_kind {
41 return Err(Error::KindSetTwice { prev_kind, kind });
42 }
43 if !found_revision {
44 delegate.find_ref("HEAD".into()).ok_or(Error::Delegate)?;
45 }
46 delegate.kind(kind).ok_or(Error::Delegate)?;
47 (input, found_revision) = {
48 let remainder = revision(rest.as_bstr(), &mut delegate)?;
49 (remainder, remainder != rest)
50 };
51 if !found_revision {
52 delegate.find_ref("HEAD".into()).ok_or(Error::Delegate)?;
53 }
54 }
55
56 if input.is_empty() {
57 delegate.done();
58 Ok(())
59 } else {
60 Err(Error::UnconsumedInput { input: input.into() })
61 }
62}
63
64mod intercept {
65 use bstr::{BStr, BString};
66
67 use crate::spec::parse::{delegate, Delegate};
68
69 #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
70 pub(crate) enum PrefixHintOwned {
71 MustBeCommit,
72 DescribeAnchor { ref_name: BString, generation: usize },
73 }
74
75 impl PrefixHintOwned {
76 pub fn to_ref(&self) -> delegate::PrefixHint<'_> {
77 match self {
78 PrefixHintOwned::MustBeCommit => delegate::PrefixHint::MustBeCommit,
79 PrefixHintOwned::DescribeAnchor { ref_name, generation } => delegate::PrefixHint::DescribeAnchor {
80 ref_name: ref_name.as_ref(),
81 generation: *generation,
82 },
83 }
84 }
85 }
86
87 impl<'a> From<delegate::PrefixHint<'a>> for PrefixHintOwned {
88 fn from(v: delegate::PrefixHint<'a>) -> Self {
89 match v {
90 delegate::PrefixHint::MustBeCommit => PrefixHintOwned::MustBeCommit,
91 delegate::PrefixHint::DescribeAnchor { generation, ref_name } => PrefixHintOwned::DescribeAnchor {
92 ref_name: ref_name.to_owned(),
93 generation,
94 },
95 }
96 }
97 }
98
99 pub(crate) struct InterceptRev<'a, T> {
100 pub inner: &'a mut T,
101 pub last_ref: Option<BString>, pub last_prefix: Option<(git_hash::Prefix, Option<PrefixHintOwned>)>,
103 pub done: bool,
104 }
105
106 impl<'a, T> InterceptRev<'a, T>
107 where
108 T: Delegate,
109 {
110 pub fn new(delegate: &'a mut T) -> Self {
111 InterceptRev {
112 inner: delegate,
113 last_ref: None,
114 last_prefix: None,
115 done: false,
116 }
117 }
118 }
119
120 impl<'a, T> Delegate for InterceptRev<'a, T>
121 where
122 T: Delegate,
123 {
124 fn done(&mut self) {
125 self.done = true;
126 self.inner.done()
127 }
128 }
129
130 impl<'a, T> delegate::Revision for InterceptRev<'a, T>
131 where
132 T: Delegate,
133 {
134 fn find_ref(&mut self, name: &BStr) -> Option<()> {
135 self.last_ref = name.to_owned().into();
136 self.inner.find_ref(name)
137 }
138
139 fn disambiguate_prefix(
140 &mut self,
141 prefix: git_hash::Prefix,
142 hint: Option<delegate::PrefixHint<'_>>,
143 ) -> Option<()> {
144 self.last_prefix = Some((prefix, hint.map(Into::into)));
145 self.inner.disambiguate_prefix(prefix, hint)
146 }
147
148 fn reflog(&mut self, query: delegate::ReflogLookup) -> Option<()> {
149 self.inner.reflog(query)
150 }
151
152 fn nth_checked_out_branch(&mut self, branch_no: usize) -> Option<()> {
153 self.inner.nth_checked_out_branch(branch_no)
154 }
155
156 fn sibling_branch(&mut self, kind: delegate::SiblingBranch) -> Option<()> {
157 self.inner.sibling_branch(kind)
158 }
159 }
160
161 impl<'a, T> delegate::Navigate for InterceptRev<'a, T>
162 where
163 T: Delegate,
164 {
165 fn traverse(&mut self, kind: delegate::Traversal) -> Option<()> {
166 self.inner.traverse(kind)
167 }
168
169 fn peel_until(&mut self, kind: delegate::PeelTo<'_>) -> Option<()> {
170 self.inner.peel_until(kind)
171 }
172
173 fn find(&mut self, regex: &BStr, negated: bool) -> Option<()> {
174 self.inner.find(regex, negated)
175 }
176
177 fn index_lookup(&mut self, path: &BStr, stage: u8) -> Option<()> {
178 self.inner.index_lookup(path, stage)
179 }
180 }
181
182 impl<'a, T> delegate::Kind for InterceptRev<'a, T>
183 where
184 T: Delegate,
185 {
186 fn kind(&mut self, kind: crate::spec::Kind) -> Option<()> {
187 self.inner.kind(kind)
188 }
189 }
190}
191use intercept::InterceptRev;
192
193fn try_set_prefix(delegate: &mut impl Delegate, hex_name: &BStr, hint: Option<delegate::PrefixHint<'_>>) -> Option<()> {
194 git_hash::Prefix::from_hex(hex_name.to_str().expect("hexadecimal only"))
195 .ok()
196 .and_then(|prefix| delegate.disambiguate_prefix(prefix, hint))
197}
198
199fn long_describe_prefix(name: &BStr) -> Option<(&BStr, delegate::PrefixHint<'_>)> {
200 let mut iter = name.rsplit(|b| *b == b'-');
201 let candidate = iter.by_ref().find_map(|substr| {
202 if substr.first()? != &b'g' {
203 return None;
204 };
205 let rest = substr.get(1..)?;
206 rest.iter().all(|b| b.is_ascii_hexdigit()).then(|| rest.as_bstr())
207 })?;
208
209 let candidate = iter.clone().any(|token| !token.is_empty()).then_some(candidate);
210 let hint = iter
211 .next()
212 .and_then(|gen| gen.to_str().ok().and_then(|gen| usize::from_str(gen).ok()))
213 .and_then(|generation| {
214 iter.next().map(|token| {
215 let last_token_len = token.len();
216 let first_token_ptr = iter.last().map(|token| token.as_ptr()).unwrap_or(token.as_ptr());
217 #[allow(unsafe_code)]
219 let prior_tokens_len: usize = unsafe { token.as_ptr().offset_from(first_token_ptr) }
220 .try_into()
221 .expect("positive value");
222 delegate::PrefixHint::DescribeAnchor {
223 ref_name: name[..prior_tokens_len + last_token_len].as_bstr(),
224 generation,
225 }
226 })
227 })
228 .unwrap_or(delegate::PrefixHint::MustBeCommit);
229
230 candidate.map(|c| (c, hint))
231}
232
233fn short_describe_prefix(name: &BStr) -> Option<&BStr> {
234 let mut iter = name.split(|b| *b == b'-');
235 let candidate = iter
236 .next()
237 .and_then(|prefix| prefix.iter().all(|b| b.is_ascii_hexdigit()).then(|| prefix.as_bstr()));
238 (iter.count() == 1).then_some(candidate).flatten()
239}
240
241type InsideParensRestConsumed<'a> = (std::borrow::Cow<'a, BStr>, &'a BStr, usize);
242fn parens(input: &[u8]) -> Result<Option<InsideParensRestConsumed<'_>>, Error> {
243 if input.first() != Some(&b'{') {
244 return Ok(None);
245 }
246 let mut open_braces = 0;
247 let mut ignore_next = false;
248 let mut skip_list = Vec::new();
249 for (idx, b) in input.iter().enumerate() {
250 match *b {
251 b'{' => {
252 if ignore_next {
253 ignore_next = false;
254 } else {
255 open_braces += 1
256 }
257 }
258 b'}' => {
259 if ignore_next {
260 ignore_next = false;
261 } else {
262 open_braces -= 1
263 }
264 }
265 b'\\' => {
266 skip_list.push(idx);
267 if ignore_next {
268 skip_list.pop();
269 ignore_next = false;
270 } else {
271 ignore_next = true;
272 }
273 }
274 _ => {
275 if ignore_next {
276 skip_list.pop();
277 };
278 ignore_next = false
279 }
280 }
281 if open_braces == 0 {
282 let inner: std::borrow::Cow<'_, _> = if skip_list.is_empty() {
283 input[1..idx].as_bstr().into()
284 } else {
285 let mut from = 1;
286 let mut buf = BString::default();
287 for next in skip_list.into_iter() {
288 buf.push_str(&input[from..next]);
289 from = next + 1;
290 }
291 if let Some(rest) = input.get(from..idx) {
292 buf.push_str(rest);
293 }
294 buf.into()
295 };
296 return Ok(Some((inner, input[idx + 1..].as_bstr(), idx + 1)));
297 }
298 }
299 Err(Error::UnclosedBracePair { input: input.into() })
300}
301
302fn try_parse<T: FromStr + PartialEq + Default>(input: &BStr) -> Result<Option<T>, Error> {
303 input
304 .to_str()
305 .ok()
306 .and_then(|n| {
307 n.parse().ok().map(|n| {
308 if n == T::default() && input[0] == b'-' {
309 return Err(Error::NegativeZero { input: input.into() });
310 };
311 Ok(n)
312 })
313 })
314 .transpose()
315}
316
317fn revision<'a, T>(mut input: &'a BStr, delegate: &mut InterceptRev<'_, T>) -> Result<&'a BStr, Error>
318where
319 T: Delegate,
320{
321 use delegate::{Navigate, Revision};
322 fn consume_all(res: Option<()>) -> Result<&'static BStr, Error> {
323 res.ok_or(Error::Delegate).map(|_| "".into())
324 }
325 match input.as_ref() {
326 [b':'] => return Err(Error::MissingColonSuffix),
327 [b':', b'/'] => return Err(Error::EmptyTopLevelRegex),
328 [b':', b'/', regex @ ..] => {
329 let (regex, negated) = parse_regex_prefix(regex.as_bstr())?;
330 if regex.is_empty() {
331 return Err(Error::UnconsumedInput { input: input.into() });
332 }
333 return consume_all(delegate.find(regex, negated));
334 }
335 [b':', b'0', b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 0)),
336 [b':', b'1', b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 1)),
337 [b':', b'2', b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 2)),
338 [b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 0)),
339 _ => {}
340 };
341
342 let mut sep_pos = None;
343 let mut consecutive_hex_chars = Some(0);
344 {
345 let mut cursor = input;
346 let mut ofs = 0;
347 while let Some((pos, b)) = cursor.iter().enumerate().find(|(_, b)| {
348 if b"@~^:.".contains(b) {
349 true
350 } else {
351 if let Some(num) = consecutive_hex_chars.as_mut() {
352 if b.is_ascii_hexdigit() {
353 *num += 1;
354 } else {
355 consecutive_hex_chars = None;
356 }
357 }
358 false
359 }
360 }) {
361 if *b != b'.' || cursor.get(pos + 1) == Some(&b'.') {
362 sep_pos = Some(ofs + pos);
363 break;
364 }
365 ofs += pos + 1;
366 cursor = &cursor[pos + 1..];
367 }
368 }
369
370 let name = &input[..sep_pos.unwrap_or(input.len())].as_bstr();
371 let mut sep = sep_pos.map(|pos| input[pos]);
372 let mut has_ref_or_implied_name = name.is_empty();
373 if name.is_empty() && sep == Some(b'@') && sep_pos.and_then(|pos| input.get(pos + 1)) != Some(&b'{') {
374 delegate.find_ref("HEAD".into()).ok_or(Error::Delegate)?;
375 sep_pos = sep_pos.map(|pos| pos + 1);
376 sep = match sep_pos.and_then(|pos| input.get(pos).copied()) {
377 None => return Ok("".into()),
378 Some(pos) => Some(pos),
379 };
380 } else {
381 (consecutive_hex_chars.unwrap_or(0) >= git_hash::Prefix::MIN_HEX_LEN)
382 .then(|| try_set_prefix(delegate, name, None))
383 .flatten()
384 .or_else(|| {
385 let (prefix, hint) = long_describe_prefix(name)
386 .map(|(c, h)| (c, Some(h)))
387 .or_else(|| short_describe_prefix(name).map(|c| (c, None)))?;
388 try_set_prefix(delegate, prefix, hint)
389 })
390 .or_else(|| {
391 name.is_empty().then_some(()).or_else(|| {
392 #[allow(clippy::let_unit_value)]
393 {
394 let res = delegate.find_ref(name)?;
395 has_ref_or_implied_name = true;
396 res.into()
397 }
398 })
399 })
400 .ok_or(Error::Delegate)?;
401 }
402
403 input = {
404 if let Some(b'@') = sep {
405 let past_sep = input[sep_pos.map(|pos| pos + 1).unwrap_or(input.len())..].as_bstr();
406 let (nav, rest, _consumed) = parens(past_sep)?.ok_or_else(|| Error::AtNeedsCurlyBrackets {
407 input: input[sep_pos.unwrap_or(input.len())..].into(),
408 })?;
409 let nav = nav.as_ref();
410 if let Some(n) = try_parse::<isize>(nav)? {
411 if n < 0 {
412 if name.is_empty() {
413 delegate
414 .nth_checked_out_branch(n.abs().try_into().expect("non-negative isize fits usize"))
415 .ok_or(Error::Delegate)?;
416 } else {
417 return Err(Error::RefnameNeedsPositiveReflogEntries { nav: nav.into() });
418 }
419 } else if has_ref_or_implied_name {
420 delegate
421 .reflog(delegate::ReflogLookup::Entry(
422 n.try_into().expect("non-negative isize fits usize"),
423 ))
424 .ok_or(Error::Delegate)?;
425 } else {
426 return Err(Error::ReflogLookupNeedsRefName { name: (*name).into() });
427 }
428 } else if let Some(kind) = SiblingBranch::parse(nav) {
429 if has_ref_or_implied_name {
430 delegate.sibling_branch(kind).ok_or(Error::Delegate)
431 } else {
432 Err(Error::SiblingBranchNeedsBranchName { name: (*name).into() })
433 }?
434 } else if has_ref_or_implied_name {
435 let time = nav
436 .to_str()
437 .map_err(|_| Error::Time {
438 input: nav.into(),
439 source: None,
440 })
441 .and_then(|date| {
442 git_date::parse(date, Some(SystemTime::now())).map_err(|err| Error::Time {
443 input: nav.into(),
444 source: err.into(),
445 })
446 })?;
447 delegate
448 .reflog(delegate::ReflogLookup::Date(time))
449 .ok_or(Error::Delegate)?;
450 } else {
451 return Err(Error::ReflogLookupNeedsRefName { name: (*name).into() });
452 }
453 rest
454 } else {
455 if sep_pos == Some(0) && sep == Some(b'~') {
456 return Err(Error::MissingTildeAnchor);
457 }
458 input[sep_pos.unwrap_or(input.len())..].as_bstr()
459 }
460 };
461
462 navigate(input, delegate)
463}
464
465fn navigate<'a, T>(input: &'a BStr, delegate: &mut InterceptRev<'_, T>) -> Result<&'a BStr, Error>
466where
467 T: Delegate,
468{
469 use delegate::{Kind, Navigate, Revision};
470 let mut cursor = 0;
471 while let Some(b) = input.get(cursor) {
472 cursor += 1;
473 match *b {
474 b'~' => {
475 let (number, consumed) = input
476 .get(cursor..)
477 .and_then(|past_sep| try_parse_usize(past_sep.as_bstr()).transpose())
478 .transpose()?
479 .unwrap_or((1, 0));
480 if number != 0 {
481 delegate
482 .traverse(delegate::Traversal::NthAncestor(number))
483 .ok_or(Error::Delegate)?;
484 }
485 cursor += consumed;
486 }
487 b'^' => {
488 let past_sep = input.get(cursor..);
489 if let Some((number, negative, consumed)) = past_sep
490 .and_then(|past_sep| try_parse_isize(past_sep.as_bstr()).transpose())
491 .transpose()?
492 {
493 if negative {
494 delegate
495 .traverse(delegate::Traversal::NthParent(
496 number
497 .checked_mul(-1)
498 .ok_or_else(|| Error::InvalidNumber {
499 input: past_sep.expect("present").into(),
500 })?
501 .try_into()
502 .expect("non-negative"),
503 ))
504 .ok_or(Error::Delegate)?;
505 delegate.kind(spec::Kind::RangeBetween).ok_or(Error::Delegate)?;
506 if let Some((prefix, hint)) = delegate.last_prefix.take() {
507 match hint {
508 Some(hint) => delegate.disambiguate_prefix(prefix, hint.to_ref().into()),
509 None => delegate.disambiguate_prefix(prefix, None),
510 }
511 .ok_or(Error::Delegate)?;
512 } else if let Some(name) = delegate.last_ref.take() {
513 delegate.find_ref(name.as_bstr()).ok_or(Error::Delegate)?;
514 } else {
515 return Err(Error::UnconsumedInput {
516 input: input[cursor..].into(),
517 });
518 }
519 delegate.done();
520 cursor += consumed;
521 return Ok(input[cursor..].as_bstr());
522 } else if number == 0 {
523 delegate.peel_until(delegate::PeelTo::ObjectKind(git_object::Kind::Commit))
524 } else {
525 delegate.traverse(delegate::Traversal::NthParent(
526 number.try_into().expect("positive number"),
527 ))
528 }
529 .ok_or(Error::Delegate)?;
530 cursor += consumed;
531 } else if let Some((kind, _rest, consumed)) =
532 past_sep.and_then(|past_sep| parens(past_sep).transpose()).transpose()?
533 {
534 cursor += consumed;
535 let target = match kind.as_ref().as_ref() {
536 b"commit" => delegate::PeelTo::ObjectKind(git_object::Kind::Commit),
537 b"tag" => delegate::PeelTo::ObjectKind(git_object::Kind::Tag),
538 b"tree" => delegate::PeelTo::ObjectKind(git_object::Kind::Tree),
539 b"blob" => delegate::PeelTo::ObjectKind(git_object::Kind::Blob),
540 b"object" => delegate::PeelTo::ValidObject,
541 b"" => delegate::PeelTo::RecursiveTagObject,
542 regex if regex.starts_with(b"/") => {
543 let (regex, negated) = parse_regex_prefix(regex[1..].as_bstr())?;
544 if !regex.is_empty() {
545 delegate.find(regex, negated).ok_or(Error::Delegate)?;
546 }
547 continue;
548 }
549 invalid => return Err(Error::InvalidObject { input: invalid.into() }),
550 };
551 delegate.peel_until(target).ok_or(Error::Delegate)?;
552 } else if past_sep.and_then(|i| i.first()) == Some(&b'!') {
553 delegate
554 .kind(spec::Kind::ExcludeReachableFromParents)
555 .ok_or(Error::Delegate)?;
556 delegate.done();
557 return Ok(input[cursor + 1..].as_bstr());
558 } else if past_sep.and_then(|i| i.first()) == Some(&b'@') {
559 delegate
560 .kind(spec::Kind::IncludeReachableFromParents)
561 .ok_or(Error::Delegate)?;
562 delegate.done();
563 return Ok(input[cursor + 1..].as_bstr());
564 } else {
565 delegate
566 .traverse(delegate::Traversal::NthParent(1))
567 .ok_or(Error::Delegate)?;
568 }
569 }
570 b':' => {
571 delegate
572 .peel_until(delegate::PeelTo::Path(input[cursor..].as_bstr()))
573 .ok_or(Error::Delegate)?;
574 return Ok("".into());
575 }
576 _ => return Ok(input[cursor - 1..].as_bstr()),
577 }
578 }
579 Ok("".into())
580}
581
582fn parse_regex_prefix(regex: &BStr) -> Result<(&BStr, bool), Error> {
583 Ok(match regex.strip_prefix(b"!") {
584 Some(regex) if regex.first() == Some(&b'!') => (regex.as_bstr(), false),
585 Some(regex) if regex.first() == Some(&b'-') => (regex[1..].as_bstr(), true),
586 Some(_regex) => return Err(Error::UnspecifiedRegexModifier { regex: regex.into() }),
587 None => (regex, false),
588 })
589}
590
591fn try_parse_usize(input: &BStr) -> Result<Option<(usize, usize)>, Error> {
592 let mut bytes = input.iter().peekable();
593 if bytes.peek().filter(|&&&b| b == b'-' || b == b'+').is_some() {
594 return Err(Error::SignedNumber { input: input.into() });
595 }
596 let num_digits = bytes.take_while(|b| b.is_ascii_digit()).count();
597 if num_digits == 0 {
598 return Ok(None);
599 }
600 let input = &input[..num_digits];
601 let number = try_parse(input)?.ok_or_else(|| Error::InvalidNumber { input: input.into() })?;
602 Ok(Some((number, num_digits)))
603}
604
605fn try_parse_isize(input: &BStr) -> Result<Option<(isize, bool, usize)>, Error> {
606 let mut bytes = input.iter().peekable();
607 if bytes.peek().filter(|&&&b| b == b'+').is_some() {
608 return Err(Error::SignedNumber { input: input.into() });
609 }
610 let negative = bytes.peek() == Some(&&b'-');
611 let num_digits = bytes.take_while(|b| b.is_ascii_digit() || *b == &b'-').count();
612 if num_digits == 0 {
613 return Ok(None);
614 } else if num_digits == 1 && negative {
615 return Ok(Some((-1, negative, num_digits)));
616 }
617 let input = &input[..num_digits];
618 let number = try_parse(input)?.ok_or_else(|| Error::InvalidNumber { input: input.into() })?;
619 Ok(Some((number, negative, num_digits)))
620}
621
622fn try_range(input: &BStr) -> Option<(&[u8], spec::Kind)> {
623 input
624 .strip_prefix(b"...")
625 .map(|rest| (rest, spec::Kind::ReachableToMergeBase))
626 .or_else(|| input.strip_prefix(b"..").map(|rest| (rest, spec::Kind::RangeBetween)))
627}
628
629fn next(i: &BStr) -> (u8, &BStr) {
630 let b = i[0];
631 (b, i[1..].as_bstr())
632}