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 | |
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')
-rw-r--r-- | vendor/gix-revision/src/describe.rs | 431 | ||||
-rw-r--r-- | vendor/gix-revision/src/lib.rs | 19 | ||||
-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 | ||||
-rw-r--r-- | vendor/gix-revision/src/types.rs | 48 |
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, + ), +} |