diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-revision/src/spec | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip |
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-revision/src/spec')
-rw-r--r-- | vendor/gix-revision/src/spec/mod.rs | 63 | ||||
-rw-r--r-- | vendor/gix-revision/src/spec/parse/delegate.rs | 156 | ||||
-rw-r--r-- | vendor/gix-revision/src/spec/parse/function.rs | 632 | ||||
-rw-r--r-- | vendor/gix-revision/src/spec/parse/mod.rs | 64 |
4 files changed, 915 insertions, 0 deletions
diff --git a/vendor/gix-revision/src/spec/mod.rs b/vendor/gix-revision/src/spec/mod.rs new file mode 100644 index 000000000..ba24c75c0 --- /dev/null +++ b/vendor/gix-revision/src/spec/mod.rs @@ -0,0 +1,63 @@ +use crate::Spec; + +/// How to interpret a revision specification, or `revspec`. +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub enum Kind { + /// Include commits reachable from this revision, the default when parsing revision `a` for example, i.e. `a` and its ancestors. + /// Example: `a`. + IncludeReachable, + /// Exclude commits reachable from this revision, i.e. `a` and its ancestors. Example: `^a`. + ExcludeReachable, + /// Every commit that is reachable from `b` but not from `a`. Example: `a..b`. + RangeBetween, + /// Every commit reachable through either `a` or `b` but no commit that is reachable by both. Example: `a...b`. + ReachableToMergeBase, + /// Include every commit of all parents of `a`, but not `a` itself. Example: `a^@`. + IncludeReachableFromParents, + /// Exclude every commit of all parents of `a`, but not `a` itself. Example: `a^!`. + ExcludeReachableFromParents, +} + +impl Default for Kind { + fn default() -> Self { + Kind::IncludeReachable + } +} + +impl Spec { + /// Return the kind of this specification. + pub fn kind(&self) -> Kind { + match self { + Spec::Include(_) => Kind::IncludeReachable, + Spec::Exclude(_) => Kind::ExcludeReachable, + Spec::Range { .. } => Kind::RangeBetween, + Spec::Merge { .. } => Kind::ReachableToMergeBase, + Spec::IncludeOnlyParents { .. } => Kind::IncludeReachableFromParents, + Spec::ExcludeParents { .. } => Kind::ExcludeReachableFromParents, + } + } +} + +mod _impls { + use std::fmt::{Display, Formatter}; + + use crate::Spec; + + impl Display for Spec { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Spec::Include(oid) => Display::fmt(oid, f), + Spec::Exclude(oid) => write!(f, "^{oid}"), + Spec::Range { from, to } => write!(f, "{from}..{to}"), + Spec::Merge { theirs, ours } => write!(f, "{theirs}...{ours}"), + Spec::IncludeOnlyParents(from_exclusive) => write!(f, "{from_exclusive}^@"), + Spec::ExcludeParents(oid) => write!(f, "{oid}^!"), + } + } + } +} + +/// +pub mod parse; +pub use parse::function::parse; diff --git a/vendor/gix-revision/src/spec/parse/delegate.rs b/vendor/gix-revision/src/spec/parse/delegate.rs new file mode 100644 index 000000000..1833b15bf --- /dev/null +++ b/vendor/gix-revision/src/spec/parse/delegate.rs @@ -0,0 +1,156 @@ +use bstr::BStr; + +/// Usually the first methods to call when parsing a rev-spec to set an anchoring revision (which is typically a `Commit` object). +/// Methods can be called multiple time to either try input or to parse another rev-spec that is part of a range. +/// +/// In one case they will not be called at all, e.g. `@{[-]n}` indicates the current branch (what `HEAD` dereferences to), +/// without ever naming it, and so does `@{upstream}` or `@{<date>}`. +/// +/// Note that when dereferencing `HEAD` implicitly, a revision must be set for later navigation. +pub trait Revision { + /// Resolve `name` as reference which might not be a valid reference name. The name may be partial like `main` or full like + /// `refs/heads/main` solely depending on the users input. + /// Symbolic referenced should be followed till their object, but objects **must not yet** be peeled. + fn find_ref(&mut self, name: &BStr) -> Option<()>; + + /// An object prefix to disambiguate, returning `None` if it is ambiguous or wasn't found at all. + /// + /// If `hint` is set, it should be used to disambiguate multiple objects with the same prefix. + fn disambiguate_prefix(&mut self, prefix: gix_hash::Prefix, hint: Option<PrefixHint<'_>>) -> Option<()>; + + /// Lookup the reflog of the previously set reference, or dereference `HEAD` to its reference + /// to obtain the ref name (as opposed to `HEAD` itself). + /// If there is no such reflog entry, return `None`. + fn reflog(&mut self, query: ReflogLookup) -> Option<()>; + + /// When looking at `HEAD`, `branch_no` is the non-null checkout in the path, e.g. `1` means the last branch checked out, + /// `2` is the one before that. + /// Return `None` if there is no branch as the checkout history (via the reflog) isn't long enough. + fn nth_checked_out_branch(&mut self, branch_no: usize) -> Option<()>; + + /// Lookup the previously set branch or dereference `HEAD` to its reference to use its name to lookup the sibling branch of `kind` + /// in the configuration (typically in `refs/remotes/…`). The sibling branches are always local tracking branches. + /// Return `None` of no such configuration exists and no sibling could be found, which is also the case for all reference outside + /// of `refs/heads/`. + /// Note that the caller isn't aware if the previously set reference is a branch or not and might call this method even though no reference + /// is known. + fn sibling_branch(&mut self, kind: SiblingBranch) -> Option<()>; +} + +/// Combine one or more specs into a range of multiple. +pub trait Kind { + /// Set the kind of the spec, which happens only once if it happens at all. + /// In case this method isn't called, assume `Single`. + /// Reject a kind by returning `None` to stop the parsing. + /// + /// Note that ranges don't necessarily assure that a second specification will be parsed. + /// If `^rev` is given, this method is called with [`spec::Kind::RangeBetween`][crate::spec::Kind::RangeBetween] + /// and no second specification is provided. + /// + /// Note that the method can be called even if other invariants are not fulfilled, treat these as errors. + fn kind(&mut self, kind: crate::spec::Kind) -> Option<()>; +} + +/// Once an anchor is set one can adjust it using traversal methods. +pub trait Navigate { + /// Adjust the current revision to traverse the graph according to `kind`. + fn traverse(&mut self, kind: Traversal) -> Option<()>; + + /// Peel the current object until it reached `kind` or `None` if the chain does not contain such object. + fn peel_until(&mut self, kind: PeelTo<'_>) -> Option<()>; + + /// Find the first revision/commit whose message matches the given `regex` (which is never empty). + /// to see how it should be matched. + /// If `negated` is `true`, the first non-match will be a match. + /// + /// If no revision is known yet, find the _youngest_ matching commit from _any_ reference, including `HEAD`. + /// Otherwise, only find commits reachable from the currently set revision. + fn find(&mut self, regex: &BStr, negated: bool) -> Option<()>; + + /// Look up the given `path` at the given `stage` in the index returning its blob id, + /// or return `None` if it doesn't exist at this `stage`. + /// Note that this implies no revision is needed and no anchor is set yet. + /// + /// * `stage` ranges from 0 to 2, with 0 being the base, 1 being ours, 2 being theirs. + /// * `path` without prefix is relative to the root of the repository, while prefixes like `./` and `../` make it + /// relative to the current working directory. + fn index_lookup(&mut self, path: &BStr, stage: u8) -> Option<()>; +} + +/// A hint to make disambiguation when looking up prefixes possible. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +pub enum PrefixHint<'a> { + /// The prefix must be a commit. + MustBeCommit, + /// The prefix refers to a commit, anchored to a ref and a revision generation in its future. + DescribeAnchor { + /// The name of the reference, like `v1.2.3` or `main`. + ref_name: &'a BStr, + /// The future generation of the commit we look for, with 0 meaning the commit is referenced by + /// `ref_name` directly. + generation: usize, + }, +} + +/// A lookup into the reflog of a reference. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +pub enum ReflogLookup { + /// Lookup by entry, where `0` is the most recent entry, and `1` is the older one behind `0`. + Entry(usize), + /// Lookup the reflog at the given time and find the closest matching entry. + Date(gix_date::Time), +} + +/// Define how to traverse the commit graph. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +pub enum Traversal { + /// Select the given parent commit of the currently selected commit, start at `1` for the first parent. + /// The value will never be `0`. + NthParent(usize), + /// Select the given ancestor of the currently selected commit, start at `1` for the first ancestor. + /// The value will never be `0`. + NthAncestor(usize), +} + +/// Define where a tag object should be peeled to. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +pub enum PeelTo<'a> { + /// An object of the given kind. + ObjectKind(gix_object::Kind), + /// Ensure the object at hand exists and is valid (actually without peeling it), + /// without imposing any restrictions to its type. + /// The object needs to be looked up to assure that it is valid, but it doesn't need to be decoded. + ValidObject, + /// Follow an annotated tag object recursively until an object is found. + RecursiveTagObject, + /// The path to drill into as seen relative to the current tree-ish. + /// + /// Note that the path can be relative, and `./` and `../` prefixes are seen as relative to the current + /// working directory. + /// + /// The path may be empty, which makes it refer to the tree at the current revision, similar to `^{tree}`. + /// Note that paths like `../` are valid and refer to a tree as seen relative to the current working directory. + Path(&'a BStr), +} + +/// The kind of sibling branch to obtain. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +pub enum SiblingBranch { + /// The upstream branch as configured in `branch.<name>.remote` or `branch.<name>.merge`. + Upstream, + /// The upstream branch to which we would push. + Push, +} + +impl SiblingBranch { + /// Parse `input` as branch representation, if possible. + pub fn parse(input: &BStr) -> Option<Self> { + if input.eq_ignore_ascii_case(b"u") || input.eq_ignore_ascii_case(b"upstream") { + SiblingBranch::Upstream.into() + } else if input.eq_ignore_ascii_case(b"push") { + SiblingBranch::Push.into() + } else { + None + } + } +} diff --git a/vendor/gix-revision/src/spec/parse/function.rs b/vendor/gix-revision/src/spec/parse/function.rs new file mode 100644 index 000000000..4b12344c7 --- /dev/null +++ b/vendor/gix-revision/src/spec/parse/function.rs @@ -0,0 +1,632 @@ +use std::{convert::TryInto, str::FromStr, time::SystemTime}; + +use bstr::{BStr, BString, ByteSlice, ByteVec}; + +use crate::{ + spec, + spec::parse::{delegate, delegate::SiblingBranch, Delegate, Error}, +}; + +/// Parse a git [`revspec`](https://git-scm.com/docs/git-rev-parse#_specifying_revisions) and call `delegate` for each token +/// successfully parsed. +/// +/// Note that the `delegate` is expected to maintain enough state to lookup revisions properly. +/// Returns `Ok(())` if all of `input` was consumed, or the error if either the `revspec` syntax was incorrect or +/// the `delegate` failed to perform the request. +pub fn parse(mut input: &BStr, delegate: &mut impl Delegate) -> Result<(), Error> { + use delegate::{Kind, Revision}; + let mut delegate = InterceptRev::new(delegate); + let mut prev_kind = None; + if let Some(b'^') = input.first() { + input = next(input).1; + let kind = spec::Kind::ExcludeReachable; + delegate.kind(kind).ok_or(Error::Delegate)?; + prev_kind = kind.into(); + } + + let mut found_revision; + (input, found_revision) = { + let rest = revision(input, &mut delegate)?; + (rest, rest != input) + }; + if delegate.done { + return if input.is_empty() { + Ok(()) + } else { + Err(Error::UnconsumedInput { input: input.into() }) + }; + } + if let Some((rest, kind)) = try_range(input) { + if let Some(prev_kind) = prev_kind { + return Err(Error::KindSetTwice { prev_kind, kind }); + } + if !found_revision { + delegate.find_ref("HEAD".into()).ok_or(Error::Delegate)?; + } + delegate.kind(kind).ok_or(Error::Delegate)?; + (input, found_revision) = { + let remainder = revision(rest.as_bstr(), &mut delegate)?; + (remainder, remainder != rest) + }; + if !found_revision { + delegate.find_ref("HEAD".into()).ok_or(Error::Delegate)?; + } + } + + if input.is_empty() { + delegate.done(); + Ok(()) + } else { + Err(Error::UnconsumedInput { input: input.into() }) + } +} + +mod intercept { + use bstr::{BStr, BString}; + + use crate::spec::parse::{delegate, Delegate}; + + #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] + pub(crate) enum PrefixHintOwned { + MustBeCommit, + DescribeAnchor { ref_name: BString, generation: usize }, + } + + impl PrefixHintOwned { + pub fn to_ref(&self) -> delegate::PrefixHint<'_> { + match self { + PrefixHintOwned::MustBeCommit => delegate::PrefixHint::MustBeCommit, + PrefixHintOwned::DescribeAnchor { ref_name, generation } => delegate::PrefixHint::DescribeAnchor { + ref_name: ref_name.as_ref(), + generation: *generation, + }, + } + } + } + + impl<'a> From<delegate::PrefixHint<'a>> for PrefixHintOwned { + fn from(v: delegate::PrefixHint<'a>) -> Self { + match v { + delegate::PrefixHint::MustBeCommit => PrefixHintOwned::MustBeCommit, + delegate::PrefixHint::DescribeAnchor { generation, ref_name } => PrefixHintOwned::DescribeAnchor { + ref_name: ref_name.to_owned(), + generation, + }, + } + } + } + + pub(crate) struct InterceptRev<'a, T> { + pub inner: &'a mut T, + pub last_ref: Option<BString>, // TODO: smallvec to save the unnecessary allocation? Can't keep ref due to lifetime constraints in traits + pub last_prefix: Option<(gix_hash::Prefix, Option<PrefixHintOwned>)>, + pub done: bool, + } + + impl<'a, T> InterceptRev<'a, T> + where + T: Delegate, + { + pub fn new(delegate: &'a mut T) -> Self { + InterceptRev { + inner: delegate, + last_ref: None, + last_prefix: None, + done: false, + } + } + } + + impl<'a, T> Delegate for InterceptRev<'a, T> + where + T: Delegate, + { + fn done(&mut self) { + self.done = true; + self.inner.done() + } + } + + impl<'a, T> delegate::Revision for InterceptRev<'a, T> + where + T: Delegate, + { + fn find_ref(&mut self, name: &BStr) -> Option<()> { + self.last_ref = name.to_owned().into(); + self.inner.find_ref(name) + } + + fn disambiguate_prefix( + &mut self, + prefix: gix_hash::Prefix, + hint: Option<delegate::PrefixHint<'_>>, + ) -> Option<()> { + self.last_prefix = Some((prefix, hint.map(Into::into))); + self.inner.disambiguate_prefix(prefix, hint) + } + + fn reflog(&mut self, query: delegate::ReflogLookup) -> Option<()> { + self.inner.reflog(query) + } + + fn nth_checked_out_branch(&mut self, branch_no: usize) -> Option<()> { + self.inner.nth_checked_out_branch(branch_no) + } + + fn sibling_branch(&mut self, kind: delegate::SiblingBranch) -> Option<()> { + self.inner.sibling_branch(kind) + } + } + + impl<'a, T> delegate::Navigate for InterceptRev<'a, T> + where + T: Delegate, + { + fn traverse(&mut self, kind: delegate::Traversal) -> Option<()> { + self.inner.traverse(kind) + } + + fn peel_until(&mut self, kind: delegate::PeelTo<'_>) -> Option<()> { + self.inner.peel_until(kind) + } + + fn find(&mut self, regex: &BStr, negated: bool) -> Option<()> { + self.inner.find(regex, negated) + } + + fn index_lookup(&mut self, path: &BStr, stage: u8) -> Option<()> { + self.inner.index_lookup(path, stage) + } + } + + impl<'a, T> delegate::Kind for InterceptRev<'a, T> + where + T: Delegate, + { + fn kind(&mut self, kind: crate::spec::Kind) -> Option<()> { + self.inner.kind(kind) + } + } +} +use intercept::InterceptRev; + +fn try_set_prefix(delegate: &mut impl Delegate, hex_name: &BStr, hint: Option<delegate::PrefixHint<'_>>) -> Option<()> { + gix_hash::Prefix::from_hex(hex_name.to_str().expect("hexadecimal only")) + .ok() + .and_then(|prefix| delegate.disambiguate_prefix(prefix, hint)) +} + +fn long_describe_prefix(name: &BStr) -> Option<(&BStr, delegate::PrefixHint<'_>)> { + let mut iter = name.rsplit(|b| *b == b'-'); + let candidate = iter.by_ref().find_map(|substr| { + if substr.first()? != &b'g' { + return None; + }; + let rest = substr.get(1..)?; + rest.iter().all(|b| b.is_ascii_hexdigit()).then(|| rest.as_bstr()) + })?; + + let candidate = iter.clone().any(|token| !token.is_empty()).then_some(candidate); + let hint = iter + .next() + .and_then(|gen| gen.to_str().ok().and_then(|gen| usize::from_str(gen).ok())) + .and_then(|generation| { + iter.next().map(|token| { + let last_token_len = token.len(); + let first_token_ptr = iter.last().map(|token| token.as_ptr()).unwrap_or(token.as_ptr()); + // SAFETY: both pointers are definitely part of the same object + #[allow(unsafe_code)] + let prior_tokens_len: usize = unsafe { token.as_ptr().offset_from(first_token_ptr) } + .try_into() + .expect("positive value"); + delegate::PrefixHint::DescribeAnchor { + ref_name: name[..prior_tokens_len + last_token_len].as_bstr(), + generation, + } + }) + }) + .unwrap_or(delegate::PrefixHint::MustBeCommit); + + candidate.map(|c| (c, hint)) +} + +fn short_describe_prefix(name: &BStr) -> Option<&BStr> { + let mut iter = name.split(|b| *b == b'-'); + let candidate = iter + .next() + .and_then(|prefix| prefix.iter().all(|b| b.is_ascii_hexdigit()).then(|| prefix.as_bstr())); + (iter.count() == 1).then_some(candidate).flatten() +} + +type InsideParensRestConsumed<'a> = (std::borrow::Cow<'a, BStr>, &'a BStr, usize); +fn parens(input: &[u8]) -> Result<Option<InsideParensRestConsumed<'_>>, Error> { + if input.first() != Some(&b'{') { + return Ok(None); + } + let mut open_braces = 0; + let mut ignore_next = false; + let mut skip_list = Vec::new(); + for (idx, b) in input.iter().enumerate() { + match *b { + b'{' => { + if ignore_next { + ignore_next = false; + } else { + open_braces += 1 + } + } + b'}' => { + if ignore_next { + ignore_next = false; + } else { + open_braces -= 1 + } + } + b'\\' => { + skip_list.push(idx); + if ignore_next { + skip_list.pop(); + ignore_next = false; + } else { + ignore_next = true; + } + } + _ => { + if ignore_next { + skip_list.pop(); + }; + ignore_next = false + } + } + if open_braces == 0 { + let inner: std::borrow::Cow<'_, _> = if skip_list.is_empty() { + input[1..idx].as_bstr().into() + } else { + let mut from = 1; + let mut buf = BString::default(); + for next in skip_list.into_iter() { + buf.push_str(&input[from..next]); + from = next + 1; + } + if let Some(rest) = input.get(from..idx) { + buf.push_str(rest); + } + buf.into() + }; + return Ok(Some((inner, input[idx + 1..].as_bstr(), idx + 1))); + } + } + Err(Error::UnclosedBracePair { input: input.into() }) +} + +fn try_parse<T: FromStr + PartialEq + Default>(input: &BStr) -> Result<Option<T>, Error> { + input + .to_str() + .ok() + .and_then(|n| { + n.parse().ok().map(|n| { + if n == T::default() && input[0] == b'-' { + return Err(Error::NegativeZero { input: input.into() }); + }; + Ok(n) + }) + }) + .transpose() +} + +fn revision<'a, T>(mut input: &'a BStr, delegate: &mut InterceptRev<'_, T>) -> Result<&'a BStr, Error> +where + T: Delegate, +{ + use delegate::{Navigate, Revision}; + fn consume_all(res: Option<()>) -> Result<&'static BStr, Error> { + res.ok_or(Error::Delegate).map(|_| "".into()) + } + match input.as_bytes() { + [b':'] => return Err(Error::MissingColonSuffix), + [b':', b'/'] => return Err(Error::EmptyTopLevelRegex), + [b':', b'/', regex @ ..] => { + let (regex, negated) = parse_regex_prefix(regex.as_bstr())?; + if regex.is_empty() { + return Err(Error::UnconsumedInput { input: input.into() }); + } + return consume_all(delegate.find(regex, negated)); + } + [b':', b'0', b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 0)), + [b':', b'1', b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 1)), + [b':', b'2', b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 2)), + [b':', path @ ..] => return consume_all(delegate.index_lookup(path.as_bstr(), 0)), + _ => {} + }; + + let mut sep_pos = None; + let mut consecutive_hex_chars = Some(0); + { + let mut cursor = input; + let mut ofs = 0; + while let Some((pos, b)) = cursor.iter().enumerate().find(|(_, b)| { + if b"@~^:.".contains(b) { + true + } else { + if let Some(num) = consecutive_hex_chars.as_mut() { + if b.is_ascii_hexdigit() { + *num += 1; + } else { + consecutive_hex_chars = None; + } + } + false + } + }) { + if *b != b'.' || cursor.get(pos + 1) == Some(&b'.') { + sep_pos = Some(ofs + pos); + break; + } + ofs += pos + 1; + cursor = &cursor[pos + 1..]; + } + } + + let name = &input[..sep_pos.unwrap_or(input.len())].as_bstr(); + let mut sep = sep_pos.map(|pos| input[pos]); + let mut has_ref_or_implied_name = name.is_empty(); + if name.is_empty() && sep == Some(b'@') && sep_pos.and_then(|pos| input.get(pos + 1)) != Some(&b'{') { + delegate.find_ref("HEAD".into()).ok_or(Error::Delegate)?; + sep_pos = sep_pos.map(|pos| pos + 1); + sep = match sep_pos.and_then(|pos| input.get(pos).copied()) { + None => return Ok("".into()), + Some(pos) => Some(pos), + }; + } else { + (consecutive_hex_chars.unwrap_or(0) >= gix_hash::Prefix::MIN_HEX_LEN) + .then(|| try_set_prefix(delegate, name, None)) + .flatten() + .or_else(|| { + let (prefix, hint) = long_describe_prefix(name) + .map(|(c, h)| (c, Some(h))) + .or_else(|| short_describe_prefix(name).map(|c| (c, None)))?; + try_set_prefix(delegate, prefix, hint) + }) + .or_else(|| { + name.is_empty().then_some(()).or_else(|| { + #[allow(clippy::let_unit_value)] + { + let res = delegate.find_ref(name)?; + has_ref_or_implied_name = true; + res.into() + } + }) + }) + .ok_or(Error::Delegate)?; + } + + input = { + if let Some(b'@') = sep { + let past_sep = input[sep_pos.map(|pos| pos + 1).unwrap_or(input.len())..].as_bstr(); + let (nav, rest, _consumed) = parens(past_sep)?.ok_or_else(|| Error::AtNeedsCurlyBrackets { + input: input[sep_pos.unwrap_or(input.len())..].into(), + })?; + let nav = nav.as_ref(); + if let Some(n) = try_parse::<isize>(nav)? { + if n < 0 { + if name.is_empty() { + delegate + .nth_checked_out_branch(n.abs().try_into().expect("non-negative isize fits usize")) + .ok_or(Error::Delegate)?; + } else { + return Err(Error::RefnameNeedsPositiveReflogEntries { nav: nav.into() }); + } + } else if has_ref_or_implied_name { + delegate + .reflog(delegate::ReflogLookup::Entry( + n.try_into().expect("non-negative isize fits usize"), + )) + .ok_or(Error::Delegate)?; + } else { + return Err(Error::ReflogLookupNeedsRefName { name: (*name).into() }); + } + } else if let Some(kind) = SiblingBranch::parse(nav) { + if has_ref_or_implied_name { + delegate.sibling_branch(kind).ok_or(Error::Delegate) + } else { + Err(Error::SiblingBranchNeedsBranchName { name: (*name).into() }) + }? + } else if has_ref_or_implied_name { + let time = nav + .to_str() + .map_err(|_| Error::Time { + input: nav.into(), + source: None, + }) + .and_then(|date| { + gix_date::parse(date, Some(SystemTime::now())).map_err(|err| Error::Time { + input: nav.into(), + source: err.into(), + }) + })?; + delegate + .reflog(delegate::ReflogLookup::Date(time)) + .ok_or(Error::Delegate)?; + } else { + return Err(Error::ReflogLookupNeedsRefName { name: (*name).into() }); + } + rest + } else { + if sep_pos == Some(0) && sep == Some(b'~') { + return Err(Error::MissingTildeAnchor); + } + input[sep_pos.unwrap_or(input.len())..].as_bstr() + } + }; + + navigate(input, delegate) +} + +fn navigate<'a, T>(input: &'a BStr, delegate: &mut InterceptRev<'_, T>) -> Result<&'a BStr, Error> +where + T: Delegate, +{ + use delegate::{Kind, Navigate, Revision}; + let mut cursor = 0; + while let Some(b) = input.get(cursor) { + cursor += 1; + match *b { + b'~' => { + let (number, consumed) = input + .get(cursor..) + .and_then(|past_sep| try_parse_usize(past_sep.as_bstr()).transpose()) + .transpose()? + .unwrap_or((1, 0)); + if number != 0 { + delegate + .traverse(delegate::Traversal::NthAncestor(number)) + .ok_or(Error::Delegate)?; + } + cursor += consumed; + } + b'^' => { + let past_sep = input.get(cursor..); + if let Some((number, negative, consumed)) = past_sep + .and_then(|past_sep| try_parse_isize(past_sep.as_bstr()).transpose()) + .transpose()? + { + if negative { + delegate + .traverse(delegate::Traversal::NthParent( + number + .checked_mul(-1) + .ok_or_else(|| Error::InvalidNumber { + input: past_sep.expect("present").into(), + })? + .try_into() + .expect("non-negative"), + )) + .ok_or(Error::Delegate)?; + delegate.kind(spec::Kind::RangeBetween).ok_or(Error::Delegate)?; + if let Some((prefix, hint)) = delegate.last_prefix.take() { + match hint { + Some(hint) => delegate.disambiguate_prefix(prefix, hint.to_ref().into()), + None => delegate.disambiguate_prefix(prefix, None), + } + .ok_or(Error::Delegate)?; + } else if let Some(name) = delegate.last_ref.take() { + delegate.find_ref(name.as_bstr()).ok_or(Error::Delegate)?; + } else { + return Err(Error::UnconsumedInput { + input: input[cursor..].into(), + }); + } + delegate.done(); + cursor += consumed; + return Ok(input[cursor..].as_bstr()); + } else if number == 0 { + delegate.peel_until(delegate::PeelTo::ObjectKind(gix_object::Kind::Commit)) + } else { + delegate.traverse(delegate::Traversal::NthParent( + number.try_into().expect("positive number"), + )) + } + .ok_or(Error::Delegate)?; + cursor += consumed; + } else if let Some((kind, _rest, consumed)) = + past_sep.and_then(|past_sep| parens(past_sep).transpose()).transpose()? + { + cursor += consumed; + let target = match kind.as_ref().as_bytes() { + b"commit" => delegate::PeelTo::ObjectKind(gix_object::Kind::Commit), + b"tag" => delegate::PeelTo::ObjectKind(gix_object::Kind::Tag), + b"tree" => delegate::PeelTo::ObjectKind(gix_object::Kind::Tree), + b"blob" => delegate::PeelTo::ObjectKind(gix_object::Kind::Blob), + b"object" => delegate::PeelTo::ValidObject, + b"" => delegate::PeelTo::RecursiveTagObject, + regex if regex.starts_with(b"/") => { + let (regex, negated) = parse_regex_prefix(regex[1..].as_bstr())?; + if !regex.is_empty() { + delegate.find(regex, negated).ok_or(Error::Delegate)?; + } + continue; + } + invalid => return Err(Error::InvalidObject { input: invalid.into() }), + }; + delegate.peel_until(target).ok_or(Error::Delegate)?; + } else if past_sep.and_then(|i| i.first()) == Some(&b'!') { + delegate + .kind(spec::Kind::ExcludeReachableFromParents) + .ok_or(Error::Delegate)?; + delegate.done(); + return Ok(input[cursor + 1..].as_bstr()); + } else if past_sep.and_then(|i| i.first()) == Some(&b'@') { + delegate + .kind(spec::Kind::IncludeReachableFromParents) + .ok_or(Error::Delegate)?; + delegate.done(); + return Ok(input[cursor + 1..].as_bstr()); + } else { + delegate + .traverse(delegate::Traversal::NthParent(1)) + .ok_or(Error::Delegate)?; + } + } + b':' => { + delegate + .peel_until(delegate::PeelTo::Path(input[cursor..].as_bstr())) + .ok_or(Error::Delegate)?; + return Ok("".into()); + } + _ => return Ok(input[cursor - 1..].as_bstr()), + } + } + Ok("".into()) +} + +fn parse_regex_prefix(regex: &BStr) -> Result<(&BStr, bool), Error> { + Ok(match regex.strip_prefix(b"!") { + Some(regex) if regex.first() == Some(&b'!') => (regex.as_bstr(), false), + Some(regex) if regex.first() == Some(&b'-') => (regex[1..].as_bstr(), true), + Some(_regex) => return Err(Error::UnspecifiedRegexModifier { regex: regex.into() }), + None => (regex, false), + }) +} + +fn try_parse_usize(input: &BStr) -> Result<Option<(usize, usize)>, Error> { + let mut bytes = input.iter().peekable(); + if bytes.peek().filter(|&&&b| b == b'-' || b == b'+').is_some() { + return Err(Error::SignedNumber { input: input.into() }); + } + let num_digits = bytes.take_while(|b| b.is_ascii_digit()).count(); + if num_digits == 0 { + return Ok(None); + } + let input = &input[..num_digits]; + let number = try_parse(input)?.ok_or_else(|| Error::InvalidNumber { input: input.into() })?; + Ok(Some((number, num_digits))) +} + +fn try_parse_isize(input: &BStr) -> Result<Option<(isize, bool, usize)>, Error> { + let mut bytes = input.iter().peekable(); + if bytes.peek().filter(|&&&b| b == b'+').is_some() { + return Err(Error::SignedNumber { input: input.into() }); + } + let negative = bytes.peek() == Some(&&b'-'); + let num_digits = bytes.take_while(|b| b.is_ascii_digit() || *b == &b'-').count(); + if num_digits == 0 { + return Ok(None); + } else if num_digits == 1 && negative { + return Ok(Some((-1, negative, num_digits))); + } + let input = &input[..num_digits]; + let number = try_parse(input)?.ok_or_else(|| Error::InvalidNumber { input: input.into() })?; + Ok(Some((number, negative, num_digits))) +} + +fn try_range(input: &BStr) -> Option<(&[u8], spec::Kind)> { + input + .strip_prefix(b"...") + .map(|rest| (rest, spec::Kind::ReachableToMergeBase)) + .or_else(|| input.strip_prefix(b"..").map(|rest| (rest, spec::Kind::RangeBetween))) +} + +fn next(i: &BStr) -> (u8, &BStr) { + let b = i[0]; + (b, i[1..].as_bstr()) +} diff --git a/vendor/gix-revision/src/spec/parse/mod.rs b/vendor/gix-revision/src/spec/parse/mod.rs new file mode 100644 index 000000000..5a64012c6 --- /dev/null +++ b/vendor/gix-revision/src/spec/parse/mod.rs @@ -0,0 +1,64 @@ +use bstr::BString; + +use crate::spec; + +/// The error returned by [`spec::parse()`][crate::spec::parse()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("'~' needs to follow an anchor, like '@~'.")] + MissingTildeAnchor, + #[error("':' needs to be followed by either '/' and regex or the path to lookup in the HEAD tree.")] + MissingColonSuffix, + #[error("':/' must be followed by a regular expression.")] + EmptyTopLevelRegex, + #[error("Need one character after '/!', typically '-', but got {:?}", .regex)] + UnspecifiedRegexModifier { regex: BString }, + #[error("Cannot peel to {:?} - unknown target.", .input)] + InvalidObject { input: BString }, + #[error("Could not parse time {:?} for revlog lookup.", .input)] + Time { + input: BString, + source: Option<gix_date::parse::Error>, + }, + #[error("Sibling branches like 'upstream' or 'push' require a branch name with remote configuration, got {:?}", .name)] + SiblingBranchNeedsBranchName { name: BString }, + #[error("Reflog entries require a ref name, got {:?}", .name)] + ReflogLookupNeedsRefName { name: BString }, + #[error("A reference name must be followed by positive numbers in '@{{n}}', got {:?}", .nav)] + RefnameNeedsPositiveReflogEntries { nav: BString }, + #[error("Negative or explicitly positive numbers are invalid here: {:?}", .input)] + SignedNumber { input: BString }, + #[error("Could not parse number from {input:?}")] + InvalidNumber { input: BString }, + #[error("Negative zeroes are invalid: {:?} - remove the '-'", .input)] + NegativeZero { input: BString }, + #[error("The opening brace in {:?} was not matched", .input)] + UnclosedBracePair { input: BString }, + #[error("Cannot set spec kind more than once. Previous value was {:?}, now it is {:?}", .prev_kind, .kind)] + KindSetTwice { prev_kind: spec::Kind, kind: spec::Kind }, + #[error("The @ character is either standing alone or followed by `{{<content>}}`, got {:?}", .input)] + AtNeedsCurlyBrackets { input: BString }, + #[error("A portion of the input could not be parsed: {:?}", .input)] + UnconsumedInput { input: BString }, + #[error("The delegate didn't indicate success - check delegate for more information")] + Delegate, +} + +/// +pub mod delegate; + +/// A delegate to be informed about parse events, with methods split into categories. +/// +/// - **Anchors** - which revision to use as starting point for… +/// - **Navigation** - where to go once from the initial revision +/// - **Range** - to learn if the specification is for a single or multiple references, and how to combine them. +pub trait Delegate: delegate::Revision + delegate::Navigate + delegate::Kind { + /// Called at the end of a successful parsing operation. + /// It can be used as a marker to finalize internal data structures. + /// + /// Note that it will not be called if there is unconsumed input. + fn done(&mut self); +} + +pub(crate) mod function; |