summaryrefslogtreecommitdiffstats
path: root/vendor/gix-revision/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
commit7e5d7eea9c580ef4b41a765bde624af431942b96 (patch)
tree2c0d9ca12878fc4525650aa4e54d77a81a07cc09 /vendor/gix-revision/src
parentAdding debian version 1.70.0+dfsg1-9. (diff)
downloadrustc-7e5d7eea9c580ef4b41a765bde624af431942b96.tar.xz
rustc-7e5d7eea9c580ef4b41a765bde624af431942b96.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')
-rw-r--r--vendor/gix-revision/src/describe.rs431
-rw-r--r--vendor/gix-revision/src/lib.rs19
-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
-rw-r--r--vendor/gix-revision/src/types.rs48
7 files changed, 1413 insertions, 0 deletions
diff --git a/vendor/gix-revision/src/describe.rs b/vendor/gix-revision/src/describe.rs
new file mode 100644
index 000000000..55cc3deef
--- /dev/null
+++ b/vendor/gix-revision/src/describe.rs
@@ -0,0 +1,431 @@
+use std::{
+ borrow::Cow,
+ fmt::{Display, Formatter},
+};
+
+use bstr::BStr;
+use gix_hashtable::HashMap;
+
+/// The positive result produced by [describe()][function::describe()].
+#[derive(Debug, Clone)]
+pub struct Outcome<'name> {
+ /// The name of the tag or branch that is closest to the commit `id`.
+ ///
+ /// If `None`, no name was found but it was requested to provide the `id` itself as fallback.
+ pub name: Option<Cow<'name, BStr>>,
+ /// The input commit object id that we describe.
+ pub id: gix_hash::ObjectId,
+ /// The number of commits that are between the tag or branch with `name` and `id`.
+ /// These commits are all in the future of the named tag or branch.
+ pub depth: u32,
+ /// The mapping between object ids and their names initially provided by the describe call.
+ pub name_by_oid: HashMap<gix_hash::ObjectId, Cow<'name, BStr>>,
+ /// The amount of commits we traversed.
+ pub commits_seen: u32,
+}
+
+impl<'a> Outcome<'a> {
+ /// Turn this outcome into a structure that can display itself in the typical `git describe` format.
+ pub fn into_format(self, hex_len: usize) -> Format<'a> {
+ Format {
+ name: self.name,
+ id: self.id,
+ hex_len,
+ depth: self.depth,
+ long: false,
+ dirty_suffix: None,
+ }
+ }
+}
+
+/// A structure implementing `Display`, producing a `git describe` like string.
+#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
+pub struct Format<'a> {
+ /// The name of the branch or tag to display, as is.
+ ///
+ /// If `None`, the `id` will be displayed as a fallback.
+ pub name: Option<Cow<'a, BStr>>,
+ /// The `id` of the commit to describe.
+ pub id: gix_hash::ObjectId,
+ /// The amount of hex characters to use to display `id`.
+ pub hex_len: usize,
+ /// The amount of commits between `name` and `id`, where `id` is in the future of `name`.
+ pub depth: u32,
+ /// If true, the long form of the describe string will be produced even if `id` lies directly on `name`,
+ /// hence has a depth of 0.
+ pub long: bool,
+ /// If `Some(suffix)`, it will be appended to the describe string.
+ /// This should be set if the working tree was determined to be dirty.
+ pub dirty_suffix: Option<String>,
+}
+
+impl<'a> Format<'a> {
+ /// Return true if the `name` is directly associated with `id`, i.e. there are no commits between them.
+ pub fn is_exact_match(&self) -> bool {
+ self.depth == 0
+ }
+
+ /// Set this instance to print in long mode, that is if `depth` is 0, it will still print the whole
+ /// long form even though it's not quite necessary.
+ ///
+ /// Otherwise, it is allowed to shorten itself.
+ pub fn long(&mut self, long: bool) -> &mut Self {
+ self.long = long;
+ self
+ }
+}
+
+impl<'a> Display for Format<'a> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ if let Some(name) = self.name.as_deref() {
+ if !self.long && self.is_exact_match() {
+ name.fmt(f)?;
+ } else {
+ write!(f, "{}-{}-g{}", name, self.depth, self.id.to_hex_with_len(self.hex_len))?;
+ }
+ } else {
+ self.id.to_hex_with_len(self.hex_len).fmt(f)?;
+ }
+
+ if let Some(suffix) = &self.dirty_suffix {
+ write!(f, "-{suffix}")?;
+ }
+ Ok(())
+ }
+}
+
+type Flags = u32;
+const MAX_CANDIDATES: usize = std::mem::size_of::<Flags>() * 8;
+
+/// The options required to call [`describe()`][function::describe()].
+#[derive(Clone, Debug)]
+pub struct Options<'name> {
+ /// The candidate names from which to determine the `name` to use for the describe string,
+ /// as a mapping from a commit id and the name associated with it.
+ pub name_by_oid: HashMap<gix_hash::ObjectId, Cow<'name, BStr>>,
+ /// The amount of names we will keep track of. Defaults to the maximum of 32.
+ ///
+ /// If the number is exceeded, it will be capped at 32 and defaults to 10.
+ pub max_candidates: usize,
+ /// If no candidate for naming, always show the abbreviated hash. Default: false.
+ pub fallback_to_oid: bool,
+ /// Only follow the first parent during graph traversal. Default: false.
+ ///
+ /// This may speed up the traversal at the cost of accuracy.
+ pub first_parent: bool,
+}
+
+impl<'name> Default for Options<'name> {
+ fn default() -> Self {
+ Options {
+ max_candidates: 10, // the same number as git uses, otherwise we perform worse by default on big repos
+ name_by_oid: Default::default(),
+ fallback_to_oid: false,
+ first_parent: false,
+ }
+ }
+}
+
+/// The error returned by the [`describe()`][function::describe()] function.
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error<E>
+where
+ E: std::error::Error + Send + Sync + 'static,
+{
+ #[error("Commit {} could not be found during graph traversal", .oid.to_hex())]
+ Find {
+ #[source]
+ err: Option<E>,
+ oid: gix_hash::ObjectId,
+ },
+ #[error("A commit could not be decoded during traversal")]
+ Decode(#[from] gix_object::decode::Error),
+}
+
+pub(crate) mod function {
+ use std::{borrow::Cow, cmp::Ordering, collections::VecDeque, iter::FromIterator};
+
+ use bstr::BStr;
+ use gix_hash::oid;
+ use gix_hashtable::{hash_map, HashMap};
+ use gix_object::CommitRefIter;
+
+ use super::{Error, Outcome};
+ use crate::describe::{Flags, Options, MAX_CANDIDATES};
+
+ /// Given a `commit` id, traverse the commit graph and collect candidate names from the `name_by_oid` mapping to produce
+ /// an `Outcome`, which converted [`into_format()`][Outcome::into_format()] will produce a typical `git describe` string.
+ ///
+ /// Note that the `name_by_oid` map is returned in the [`Outcome`], which can be forcefully returned even if there was no matching
+ /// candidate by setting `fallback_to_oid` to true.
+ pub fn describe<'name, Find, E>(
+ commit: &oid,
+ mut find: Find,
+ Options {
+ name_by_oid,
+ mut max_candidates,
+ fallback_to_oid,
+ first_parent,
+ }: Options<'name>,
+ ) -> Result<Option<Outcome<'name>>, Error<E>>
+ where
+ Find: for<'b> FnMut(&oid, &'b mut Vec<u8>) -> Result<Option<CommitRefIter<'b>>, E>,
+ E: std::error::Error + Send + Sync + 'static,
+ {
+ max_candidates = max_candidates.min(MAX_CANDIDATES);
+ if let Some(name) = name_by_oid.get(commit) {
+ return Ok(Some(Outcome {
+ name: name.clone().into(),
+ id: commit.to_owned(),
+ depth: 0,
+ name_by_oid,
+ commits_seen: 0,
+ }));
+ }
+
+ if max_candidates == 0 || name_by_oid.is_empty() {
+ return if fallback_to_oid {
+ Ok(Some(Outcome {
+ id: commit.to_owned(),
+ name: None,
+ name_by_oid,
+ depth: 0,
+ commits_seen: 0,
+ }))
+ } else {
+ Ok(None)
+ };
+ }
+
+ let mut buf = Vec::new();
+ let mut parent_buf = Vec::new();
+
+ let mut queue = VecDeque::from_iter(Some((commit.to_owned(), u32::MAX)));
+ let mut candidates = Vec::new();
+ let mut commits_seen = 0;
+ let mut gave_up_on_commit = None;
+ let mut seen = HashMap::<gix_hash::ObjectId, Flags>::default();
+ seen.insert(commit.to_owned(), 0u32);
+
+ while let Some((commit, _commit_time)) = queue.pop_front() {
+ commits_seen += 1;
+ if let Some(name) = name_by_oid.get(&commit) {
+ if candidates.len() < max_candidates {
+ let identity_bit = 1 << candidates.len();
+ candidates.push(Candidate {
+ name: name.clone(),
+ commits_in_its_future: commits_seen - 1,
+ identity_bit,
+ order: candidates.len(),
+ });
+ *seen.get_mut(&commit).expect("inserted") |= identity_bit;
+ } else {
+ gave_up_on_commit = Some(commit);
+ break;
+ }
+ }
+
+ let flags = seen[&commit];
+ for candidate in candidates
+ .iter_mut()
+ .filter(|c| (flags & c.identity_bit) != c.identity_bit)
+ {
+ candidate.commits_in_its_future += 1;
+ }
+
+ if queue.is_empty() && !candidates.is_empty() {
+ // single-trunk history that waits to be replenished.
+ // Abort early if the best-candidate is in the current commits past.
+ let mut shortest_depth = Flags::MAX;
+ let mut best_candidates_at_same_depth = 0_u32;
+ for candidate in &candidates {
+ match candidate.commits_in_its_future.cmp(&shortest_depth) {
+ Ordering::Less => {
+ shortest_depth = candidate.commits_in_its_future;
+ best_candidates_at_same_depth = candidate.identity_bit;
+ }
+ Ordering::Equal => {
+ best_candidates_at_same_depth |= candidate.identity_bit;
+ }
+ Ordering::Greater => {}
+ }
+ }
+
+ if (flags & best_candidates_at_same_depth) == best_candidates_at_same_depth {
+ break;
+ }
+ }
+
+ parents_by_date_onto_queue_and_track_names(
+ &mut find,
+ &mut buf,
+ &mut parent_buf,
+ &mut queue,
+ &mut seen,
+ &commit,
+ flags,
+ first_parent,
+ )?;
+ }
+
+ if candidates.is_empty() {
+ return if fallback_to_oid {
+ Ok(Some(Outcome {
+ id: commit.to_owned(),
+ name: None,
+ name_by_oid,
+ depth: 0,
+ commits_seen,
+ }))
+ } else {
+ Ok(None)
+ };
+ }
+
+ candidates.sort_by(|a, b| {
+ a.commits_in_its_future
+ .cmp(&b.commits_in_its_future)
+ .then_with(|| a.order.cmp(&b.order))
+ });
+
+ if let Some(commit_id) = gave_up_on_commit {
+ queue.push_front((commit_id, u32::MAX));
+ commits_seen -= 1;
+ }
+
+ commits_seen += finish_depth_computation(
+ queue,
+ find,
+ candidates.first_mut().expect("at least one candidate"),
+ seen,
+ buf,
+ parent_buf,
+ first_parent,
+ )?;
+
+ Ok(candidates.into_iter().next().map(|c| Outcome {
+ name: c.name.into(),
+ id: commit.to_owned(),
+ depth: c.commits_in_its_future,
+ name_by_oid,
+ commits_seen,
+ }))
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ fn parents_by_date_onto_queue_and_track_names<Find, E>(
+ find: &mut Find,
+ buf: &mut Vec<u8>,
+ parent_buf: &mut Vec<u8>,
+ queue: &mut VecDeque<(gix_hash::ObjectId, u32)>,
+ seen: &mut HashMap<gix_hash::ObjectId, Flags>,
+ commit: &gix_hash::oid,
+ commit_flags: Flags,
+ first_parent: bool,
+ ) -> Result<(), Error<E>>
+ where
+ Find: for<'b> FnMut(&oid, &'b mut Vec<u8>) -> Result<Option<CommitRefIter<'b>>, E>,
+ E: std::error::Error + Send + Sync + 'static,
+ {
+ let commit_iter = find(commit, buf)
+ .map_err(|err| Error::Find {
+ err: Some(err),
+ oid: commit.to_owned(),
+ })?
+ .ok_or_else(|| Error::Find {
+ err: None,
+ oid: commit.to_owned(),
+ })?;
+ for token in commit_iter {
+ match token {
+ Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue,
+ Ok(gix_object::commit::ref_iter::Token::Parent { id: parent_id }) => match seen.entry(parent_id) {
+ hash_map::Entry::Vacant(entry) => {
+ let parent = match find(&parent_id, parent_buf).map_err(|err| Error::Find {
+ err: Some(err),
+ oid: commit.to_owned(),
+ })? {
+ Some(p) => p,
+ None => continue, // skip missing objects, they don't exist.
+ };
+
+ let parent_commit_date = parent
+ .committer()
+ .map(|committer| committer.time.seconds_since_unix_epoch)
+ .unwrap_or_default();
+
+ entry.insert(commit_flags);
+ match queue.binary_search_by(|c| c.1.cmp(&parent_commit_date).reverse()) {
+ Ok(_) => queue.push_back((parent_id, parent_commit_date)),
+ Err(pos) => queue.insert(pos, (parent_id, parent_commit_date)),
+ };
+ }
+ hash_map::Entry::Occupied(mut entry) => {
+ *entry.get_mut() |= commit_flags;
+ }
+ },
+ Ok(_unused_token) => break,
+ Err(err) => return Err(err.into()),
+ }
+ if first_parent {
+ break;
+ }
+ }
+
+ Ok(())
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ fn finish_depth_computation<'name, Find, E>(
+ mut queue: VecDeque<(gix_hash::ObjectId, u32)>,
+ mut find: Find,
+ best_candidate: &mut Candidate<'name>,
+ mut seen: HashMap<gix_hash::ObjectId, Flags>,
+ mut buf: Vec<u8>,
+ mut parent_buf: Vec<u8>,
+ first_parent: bool,
+ ) -> Result<u32, Error<E>>
+ where
+ Find: for<'b> FnMut(&oid, &'b mut Vec<u8>) -> Result<Option<CommitRefIter<'b>>, E>,
+ E: std::error::Error + Send + Sync + 'static,
+ {
+ let mut commits_seen = 0;
+ while let Some((commit, _commit_time)) = queue.pop_front() {
+ commits_seen += 1;
+ let flags = seen[&commit];
+ if (flags & best_candidate.identity_bit) == best_candidate.identity_bit {
+ if queue
+ .iter()
+ .all(|(id, _)| (seen[id] & best_candidate.identity_bit) == best_candidate.identity_bit)
+ {
+ break;
+ }
+ } else {
+ best_candidate.commits_in_its_future += 1;
+ }
+
+ parents_by_date_onto_queue_and_track_names(
+ &mut find,
+ &mut buf,
+ &mut parent_buf,
+ &mut queue,
+ &mut seen,
+ &commit,
+ flags,
+ first_parent,
+ )?;
+ }
+ Ok(commits_seen)
+ }
+
+ #[derive(Debug)]
+ struct Candidate<'a> {
+ name: Cow<'a, BStr>,
+ commits_in_its_future: Flags,
+ /// A single bit identifying this candidate uniquely in a bitset
+ identity_bit: Flags,
+ /// The order at which we found the candidate, first one has order = 0
+ order: usize,
+ }
+}
diff --git a/vendor/gix-revision/src/lib.rs b/vendor/gix-revision/src/lib.rs
new file mode 100644
index 000000000..ef273b5ae
--- /dev/null
+++ b/vendor/gix-revision/src/lib.rs
@@ -0,0 +1,19 @@
+//! Interact with git revisions by parsing them from rev-specs and describing them in terms of reference names.
+//!
+//! ## Feature Flags
+#![cfg_attr(
+ feature = "document-features",
+ cfg_attr(doc, doc = ::document_features::document_features!())
+)]
+#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
+#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
+
+///
+pub mod describe;
+pub use describe::function::describe;
+
+///
+pub mod spec;
+
+mod types;
+pub use types::Spec;
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;
diff --git a/vendor/gix-revision/src/types.rs b/vendor/gix-revision/src/types.rs
new file mode 100644
index 000000000..43b0caf97
--- /dev/null
+++ b/vendor/gix-revision/src/types.rs
@@ -0,0 +1,48 @@
+/// A revision specification without any bindings to a repository, useful for serialization or movement over thread boundaries.
+///
+/// Note that all [object ids][gix_hash::ObjectId] should be a committish, but don't have to be.
+/// Unless the field name contains `_exclusive`, the respective objects are included in the set.
+#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
+pub enum Spec {
+ /// Include commits reachable from this revision, i.e. `a` and its ancestors.
+ ///
+ /// The equivalent to [crate::spec::Kind::IncludeReachable], but with data.
+ Include(gix_hash::ObjectId),
+ /// Exclude commits reachable from this revision, i.e. `a` and its ancestors. Example: `^a`.
+ ///
+ /// The equivalent to [crate::spec::Kind::ExcludeReachable], but with data.
+ Exclude(gix_hash::ObjectId),
+ /// Every commit that is reachable from `from` to `to`, but not any ancestors of `from`. Example: `from..to`.
+ ///
+ /// The equivalent to [crate::spec::Kind::RangeBetween], but with data.
+ Range {
+ /// The starting point of the range, which is included in the set.
+ from: gix_hash::ObjectId,
+ /// The end point of the range, which is included in the set.
+ to: gix_hash::ObjectId,
+ },
+ /// Every commit reachable through either `theirs` or `ours`, but no commit that is reachable by both. Example: `theirs...ours`.
+ ///
+ /// The equivalent to [crate::spec::Kind::ReachableToMergeBase], but with data.
+ Merge {
+ /// Their side of the merge, which is included in the set.
+ theirs: gix_hash::ObjectId,
+ /// Our side of the merge, which is included in the set.
+ ours: gix_hash::ObjectId,
+ },
+ /// Include every commit of all parents of `a`, but not `a` itself. Example: `a^@`.
+ ///
+ /// The equivalent to [crate::spec::Kind::IncludeReachableFromParents], but with data.
+ IncludeOnlyParents(
+ /// Include only the parents of this object, but not the object itself.
+ gix_hash::ObjectId,
+ ),
+ /// Exclude every commit of all parents of `a`, but not `a` itself. Example: `a^!`.
+ ///
+ /// The equivalent to [crate::spec::Kind::ExcludeReachableFromParents], but with data.
+ ExcludeParents(
+ /// Exclude the parents of this object, but not the object itself.
+ gix_hash::ObjectId,
+ ),
+}