summaryrefslogtreecommitdiffstats
path: root/vendor/gix-revision/src/spec
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-revision/src/spec
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-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.rs63
-rw-r--r--vendor/gix-revision/src/spec/parse/delegate.rs156
-rw-r--r--vendor/gix-revision/src/spec/parse/function.rs632
-rw-r--r--vendor/gix-revision/src/spec/parse/mod.rs64
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;