1use std::{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<(gix_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<T> Delegate for InterceptRev<'_, 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<T> delegate::Revision for InterceptRev<'_, 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: gix_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<T> delegate::Navigate for InterceptRev<'_, 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<T> delegate::Kind for InterceptRev<'_, 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 gix_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(u8::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_or(token.as_ptr(), <[_]>::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(u8::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_bytes() {
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 const SEPARATORS: &[u8] = b"~^:.";
348 while let Some((pos, b)) = cursor.iter().enumerate().find(|(pos, b)| {
349 if **b == b'@' {
350 if cursor.len() == 1 {
351 return true;
352 }
353 let next = cursor.get(pos + 1);
354 let next_next = cursor.get(pos + 2);
355 if *pos != 0 && (next, next_next) == (Some(&b'.'), Some(&b'.')) {
356 return false;
357 }
358 next == Some(&b'{') || next.is_some_and(|b| SEPARATORS.contains(b))
359 } else if SEPARATORS.contains(b) {
360 true
361 } else {
362 if let Some(num) = consecutive_hex_chars.as_mut() {
363 if b.is_ascii_hexdigit() {
364 *num += 1;
365 } else {
366 consecutive_hex_chars = None;
367 }
368 }
369 false
370 }
371 }) {
372 if *b != b'.' || cursor.get(pos + 1) == Some(&b'.') {
373 sep_pos = Some(ofs + pos);
374 break;
375 }
376 ofs += pos + 1;
377 cursor = &cursor[pos + 1..];
378 }
379 }
380
381 let name = &input[..sep_pos.unwrap_or(input.len())].as_bstr();
382 let mut sep = sep_pos.map(|pos| input[pos]);
383 let mut has_ref_or_implied_name = name.is_empty();
384 if name.is_empty() && sep == Some(b'@') && sep_pos.and_then(|pos| input.get(pos + 1)) != Some(&b'{') {
385 delegate.find_ref("HEAD".into()).ok_or(Error::Delegate)?;
386 sep_pos = sep_pos.map(|pos| pos + 1);
387 sep = match sep_pos.and_then(|pos| input.get(pos).copied()) {
388 None => return Ok("".into()),
389 Some(pos) => Some(pos),
390 };
391 } else {
392 (consecutive_hex_chars.unwrap_or(0) >= gix_hash::Prefix::MIN_HEX_LEN)
393 .then(|| try_set_prefix(delegate, name, None))
394 .flatten()
395 .or_else(|| {
396 let (prefix, hint) = long_describe_prefix(name)
397 .map(|(c, h)| (c, Some(h)))
398 .or_else(|| short_describe_prefix(name).map(|c| (c, None)))?;
399 try_set_prefix(delegate, prefix, hint)
400 })
401 .or_else(|| {
402 name.is_empty().then_some(()).or_else(|| {
403 #[allow(clippy::let_unit_value)]
404 {
405 let res = delegate.find_ref(name)?;
406 has_ref_or_implied_name = true;
407 res.into()
408 }
409 })
410 })
411 .ok_or(Error::Delegate)?;
412 }
413
414 input = {
415 if let Some(b'@') = sep {
416 let past_sep = input[sep_pos.map_or(input.len(), |pos| pos + 1)..].as_bstr();
417 let (nav, rest, _consumed) = parens(past_sep)?.ok_or_else(|| Error::AtNeedsCurlyBrackets {
418 input: input[sep_pos.unwrap_or(input.len())..].into(),
419 })?;
420 let nav = nav.as_ref();
421 if let Some(n) = try_parse::<isize>(nav)? {
422 if n < 0 {
423 if name.is_empty() {
424 delegate
425 .nth_checked_out_branch(n.unsigned_abs())
426 .ok_or(Error::Delegate)?;
427 } else {
428 return Err(Error::RefnameNeedsPositiveReflogEntries { nav: nav.into() });
429 }
430 } else if has_ref_or_implied_name {
431 delegate
432 .reflog(if n >= 100000000 {
433 let time = nav
434 .to_str()
435 .map_err(|_| Error::Time {
436 input: nav.into(),
437 source: None,
438 })
439 .and_then(|date| {
440 gix_date::parse(date, None).map_err(|err| Error::Time {
441 input: nav.into(),
442 source: err.into(),
443 })
444 })?;
445 delegate::ReflogLookup::Date(time)
446 } else {
447 delegate::ReflogLookup::Entry(n.try_into().expect("non-negative isize fits usize"))
448 })
449 .ok_or(Error::Delegate)?;
450 } else {
451 return Err(Error::ReflogLookupNeedsRefName { name: (*name).into() });
452 }
453 } else if let Some(kind) = SiblingBranch::parse(nav) {
454 if has_ref_or_implied_name {
455 delegate.sibling_branch(kind).ok_or(Error::Delegate)
456 } else {
457 Err(Error::SiblingBranchNeedsBranchName { name: (*name).into() })
458 }?;
459 } else if has_ref_or_implied_name {
460 let time = nav
461 .to_str()
462 .map_err(|_| Error::Time {
463 input: nav.into(),
464 source: None,
465 })
466 .and_then(|date| {
467 gix_date::parse(date, Some(SystemTime::now())).map_err(|err| Error::Time {
468 input: nav.into(),
469 source: err.into(),
470 })
471 })?;
472 delegate
473 .reflog(delegate::ReflogLookup::Date(time))
474 .ok_or(Error::Delegate)?;
475 } else {
476 return Err(Error::ReflogLookupNeedsRefName { name: (*name).into() });
477 }
478 rest
479 } else {
480 if sep_pos == Some(0) && sep == Some(b'~') {
481 return Err(Error::MissingTildeAnchor);
482 }
483 input[sep_pos.unwrap_or(input.len())..].as_bstr()
484 }
485 };
486
487 navigate(input, delegate)
488}
489
490fn navigate<'a, T>(input: &'a BStr, delegate: &mut InterceptRev<'_, T>) -> Result<&'a BStr, Error>
491where
492 T: Delegate,
493{
494 use delegate::{Kind, Navigate, Revision};
495 let mut cursor = 0;
496 while let Some(b) = input.get(cursor) {
497 cursor += 1;
498 match *b {
499 b'~' => {
500 let (number, consumed) = input
501 .get(cursor..)
502 .and_then(|past_sep| try_parse_usize(past_sep.as_bstr()).transpose())
503 .transpose()?
504 .unwrap_or((1, 0));
505 if number != 0 {
506 delegate
507 .traverse(delegate::Traversal::NthAncestor(number))
508 .ok_or(Error::Delegate)?;
509 }
510 cursor += consumed;
511 }
512 b'^' => {
513 let past_sep = input.get(cursor..);
514 if let Some((number, negative, consumed)) = past_sep
515 .and_then(|past_sep| try_parse_isize(past_sep.as_bstr()).transpose())
516 .transpose()?
517 {
518 if negative {
519 delegate
520 .traverse(delegate::Traversal::NthParent(
521 number
522 .checked_mul(-1)
523 .ok_or_else(|| Error::InvalidNumber {
524 input: past_sep.expect("present").into(),
525 })?
526 .try_into()
527 .expect("non-negative"),
528 ))
529 .ok_or(Error::Delegate)?;
530 delegate.kind(spec::Kind::RangeBetween).ok_or(Error::Delegate)?;
531 if let Some((prefix, hint)) = delegate.last_prefix.take() {
532 match hint {
533 Some(hint) => delegate.disambiguate_prefix(prefix, hint.to_ref().into()),
534 None => delegate.disambiguate_prefix(prefix, None),
535 }
536 .ok_or(Error::Delegate)?;
537 } else if let Some(name) = delegate.last_ref.take() {
538 delegate.find_ref(name.as_bstr()).ok_or(Error::Delegate)?;
539 } else {
540 return Err(Error::UnconsumedInput {
541 input: input[cursor..].into(),
542 });
543 }
544 delegate.done();
545 cursor += consumed;
546 return Ok(input[cursor..].as_bstr());
547 } else if number == 0 {
548 delegate.peel_until(delegate::PeelTo::ObjectKind(gix_object::Kind::Commit))
549 } else {
550 delegate.traverse(delegate::Traversal::NthParent(
551 number.try_into().expect("positive number"),
552 ))
553 }
554 .ok_or(Error::Delegate)?;
555 cursor += consumed;
556 } else if let Some((kind, _rest, consumed)) =
557 past_sep.and_then(|past_sep| parens(past_sep).transpose()).transpose()?
558 {
559 cursor += consumed;
560 let target = match kind.as_ref().as_bytes() {
561 b"commit" => delegate::PeelTo::ObjectKind(gix_object::Kind::Commit),
562 b"tag" => delegate::PeelTo::ObjectKind(gix_object::Kind::Tag),
563 b"tree" => delegate::PeelTo::ObjectKind(gix_object::Kind::Tree),
564 b"blob" => delegate::PeelTo::ObjectKind(gix_object::Kind::Blob),
565 b"object" => delegate::PeelTo::ValidObject,
566 b"" => delegate::PeelTo::RecursiveTagObject,
567 regex if regex.starts_with(b"/") => {
568 let (regex, negated) = parse_regex_prefix(regex[1..].as_bstr())?;
569 if !regex.is_empty() {
570 delegate.find(regex, negated).ok_or(Error::Delegate)?;
571 }
572 continue;
573 }
574 invalid => return Err(Error::InvalidObject { input: invalid.into() }),
575 };
576 delegate.peel_until(target).ok_or(Error::Delegate)?;
577 } else if past_sep.and_then(<[_]>::first) == Some(&b'!') {
578 delegate
579 .kind(spec::Kind::ExcludeReachableFromParents)
580 .ok_or(Error::Delegate)?;
581 delegate.done();
582 return Ok(input[cursor + 1..].as_bstr());
583 } else if past_sep.and_then(<[_]>::first) == Some(&b'@') {
584 delegate
585 .kind(spec::Kind::IncludeReachableFromParents)
586 .ok_or(Error::Delegate)?;
587 delegate.done();
588 return Ok(input[cursor + 1..].as_bstr());
589 } else {
590 delegate
591 .traverse(delegate::Traversal::NthParent(1))
592 .ok_or(Error::Delegate)?;
593 }
594 }
595 b':' => {
596 delegate
597 .peel_until(delegate::PeelTo::Path(input[cursor..].as_bstr()))
598 .ok_or(Error::Delegate)?;
599 return Ok("".into());
600 }
601 _ => return Ok(input[cursor - 1..].as_bstr()),
602 }
603 }
604 Ok("".into())
605}
606
607fn parse_regex_prefix(regex: &BStr) -> Result<(&BStr, bool), Error> {
608 Ok(match regex.strip_prefix(b"!") {
609 Some(regex) if regex.first() == Some(&b'!') => (regex.as_bstr(), false),
610 Some(regex) if regex.first() == Some(&b'-') => (regex[1..].as_bstr(), true),
611 Some(_regex) => return Err(Error::UnspecifiedRegexModifier { regex: regex.into() }),
612 None => (regex, false),
613 })
614}
615
616fn try_parse_usize(input: &BStr) -> Result<Option<(usize, usize)>, Error> {
617 let mut bytes = input.iter().peekable();
618 if bytes.peek().filter(|&&&b| b == b'-' || b == b'+').is_some() {
619 return Err(Error::SignedNumber { input: input.into() });
620 }
621 let num_digits = bytes.take_while(|b| b.is_ascii_digit()).count();
622 if num_digits == 0 {
623 return Ok(None);
624 }
625 let input = &input[..num_digits];
626 let number = try_parse(input)?.ok_or_else(|| Error::InvalidNumber { input: input.into() })?;
627 Ok(Some((number, num_digits)))
628}
629
630fn try_parse_isize(input: &BStr) -> Result<Option<(isize, bool, usize)>, Error> {
631 let mut bytes = input.iter().peekable();
632 if bytes.peek().filter(|&&&b| b == b'+').is_some() {
633 return Err(Error::SignedNumber { input: input.into() });
634 }
635 let negative = bytes.peek() == Some(&&b'-');
636 let num_digits = bytes.take_while(|b| b.is_ascii_digit() || *b == &b'-').count();
637 if num_digits == 0 {
638 return Ok(None);
639 } else if num_digits == 1 && negative {
640 return Ok(Some((-1, negative, num_digits)));
641 }
642 let input = &input[..num_digits];
643 let number = try_parse(input)?.ok_or_else(|| Error::InvalidNumber { input: input.into() })?;
644 Ok(Some((number, negative, num_digits)))
645}
646
647fn try_range(input: &BStr) -> Option<(&[u8], spec::Kind)> {
648 input
649 .strip_prefix(b"...")
650 .map(|rest| (rest, spec::Kind::ReachableToMergeBase))
651 .or_else(|| input.strip_prefix(b"..").map(|rest| (rest, spec::Kind::RangeBetween)))
652}
653
654fn next(i: &BStr) -> (u8, &BStr) {
655 let b = i[0];
656 (b, i[1..].as_bstr())
657}