From c23a457e72abe608715ac76f076f47dc42af07a5 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 20:31:44 +0200 Subject: Merging upstream version 1.74.1+dfsg1. Signed-off-by: Daniel Baumann --- vendor/gix/src/pathspec.rs | 207 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 vendor/gix/src/pathspec.rs (limited to 'vendor/gix/src/pathspec.rs') diff --git a/vendor/gix/src/pathspec.rs b/vendor/gix/src/pathspec.rs new file mode 100644 index 000000000..235a91d76 --- /dev/null +++ b/vendor/gix/src/pathspec.rs @@ -0,0 +1,207 @@ +//! Pathspec plumbing and abstractions +use gix_macros::momo; +use gix_odb::FindExt; +pub use gix_pathspec::*; + +use crate::{bstr::BStr, AttributeStack, Pathspec, PathspecDetached, Repository}; + +/// +pub mod init { + /// The error returned by [`Pathspec::new()`](super::Pathspec::new()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + MakeAttributes(#[from] Box), + #[error(transparent)] + Defaults(#[from] crate::repository::pathspec_defaults_ignore_case::Error), + #[error(transparent)] + ParseSpec(#[from] gix_pathspec::parse::Error), + #[error( + "Could not obtain the repository prefix as the relative path of the CWD as seen from the working tree" + )] + NormalizeSpec(#[from] gix_pathspec::normalize::Error), + #[error(transparent)] + RepoPrefix(#[from] gix_path::realpath::Error), + } +} + +/// Lifecycle +impl<'repo> Pathspec<'repo> { + /// Create a new instance by parsing `patterns` into [`Pathspecs`](Pattern) to make them usable for searches. + /// `make_attribute` may be called if one of the patterns has a `(attr:a)` element which requires attribute matching. It should + /// be used to control where attributes are coming from. + /// If `inherit_ignore_case` is `true`, the pathspecs may have their ignore-case default overridden to be case-insensitive by default. + /// This only works towards turning ignore-case for pathspecs on, but won't ever turn that setting off if. + /// + /// ### Deviation + /// + /// Pathspecs can declare to be case-insensitive as part of their elements, which is a setting that is now respected for attribute + /// queries as well. + pub fn new( + repo: &'repo Repository, + patterns: impl IntoIterator>, + inherit_ignore_case: bool, + make_attributes: impl FnOnce() -> Result>, + ) -> Result { + let defaults = repo.pathspec_defaults_inherit_ignore_case(inherit_ignore_case)?; + let patterns = patterns + .into_iter() + .map(move |p| parse(p.as_ref(), defaults)) + .collect::, _>>()?; + let needs_cache = patterns.iter().any(|p| !p.attributes.is_empty()); + let search = Search::from_specs( + patterns, + repo.prefix()?, + &gix_path::realpath_opts( + repo.work_dir().unwrap_or_else(|| repo.git_dir()), + repo.options.current_dir_or_empty(), + gix_path::realpath::MAX_SYMLINKS, + )?, + )?; + let cache = needs_cache.then(make_attributes).transpose()?; + Ok(Self { + repo, + search, + stack: cache, + }) + } + /// Turn ourselves into the functional parts for direct usage. + /// Note that the [`cache`](AttributeStack) is only set if one of the [`search` patterns](Search) + /// is specifying attributes to match for. + pub fn into_parts(self) -> (Search, Option>) { + ( + self.search, + self.stack.map(|stack| AttributeStack::new(stack, self.repo)), + ) + } + + /// Turn ourselves into an implementation that works without a repository instance and that is rather minimal. + pub fn detach(self) -> std::io::Result { + Ok(PathspecDetached { + search: self.search, + stack: self.stack, + odb: self.repo.objects.clone().into_arc()?, + }) + } +} + +/// Access +impl<'repo> Pathspec<'repo> { + /// Return the attributes cache which is used when matching attributes in pathspecs, or `None` if none of the pathspecs require that. + pub fn attributes(&self) -> Option<&gix_worktree::Stack> { + self.stack.as_ref() + } + + /// Return the search itself which can be used for matching paths or accessing the actual patterns that will be used. + pub fn search(&self) -> &gix_pathspec::Search { + &self.search + } + + /// Return the first [`Match`](search::Match) of `relative_path`, or `None`. + /// Note that the match might [be excluded](search::Match::is_excluded()). + /// `is_dir` is true if `relative_path` is a directory. + #[doc( + alias = "match_diff", + alias = "match_tree", + alias = "match_index", + alias = "match_workdir", + alias = "matches_path", + alias = "git2" + )] + #[momo] + pub fn pattern_matching_relative_path<'a>( + &mut self, + relative_path: impl Into<&'a BStr>, + is_dir: Option, + ) -> Option> { + self.search.pattern_matching_relative_path( + relative_path.into(), + is_dir, + &mut |relative_path, case, is_dir, out| { + let stack = self.stack.as_mut().expect("initialized in advance"); + stack + .set_case(case) + .at_entry(relative_path, Some(is_dir), |id, buf| { + self.repo.objects.find_blob(id, buf) + }) + .map_or(false, |platform| platform.matching_attributes(out)) + }, + ) + } + + /// The simplified version of [`pattern_matching_relative_path()`](Self::pattern_matching_relative_path()) which returns + /// `true` if `relative_path` is included in the set of positive pathspecs, while not being excluded. + #[momo] + pub fn is_included<'a>(&mut self, relative_path: impl Into<&'a BStr>, is_dir: Option) -> bool { + self.pattern_matching_relative_path(relative_path, is_dir) + .map_or(false, |m| !m.is_excluded()) + } + + /// Return an iterator over all entries along with their path if the path matches the pathspec, or `None` if the pathspec is + /// known to match no entry. + // TODO: tests + pub fn index_entries_with_paths<'s: 'repo, 'a: 'repo>( + &'s mut self, + index: &'a gix_index::State, + ) -> Option + 'repo + 's> { + index.prefixed_entries(self.search.common_prefix()).map(|entries| { + entries.iter().filter_map(move |entry| { + let path = entry.path(index); + self.is_included(path, Some(false)).then_some((path, entry)) + }) + }) + } +} + +/// Access +impl PathspecDetached { + /// Return the first [`Match`](search::Match) of `relative_path`, or `None`. + /// Note that the match might [be excluded](search::Match::is_excluded()). + /// `is_dir` is true if `relative_path` is a directory. + #[doc( + alias = "match_diff", + alias = "match_tree", + alias = "match_index", + alias = "match_workdir", + alias = "matches_path", + alias = "git2" + )] + #[momo] + pub fn pattern_matching_relative_path<'a>( + &mut self, + relative_path: impl Into<&'a BStr>, + is_dir: Option, + ) -> Option> { + self.search.pattern_matching_relative_path( + relative_path.into(), + is_dir, + &mut |relative_path, case, is_dir, out| { + let stack = self.stack.as_mut().expect("initialized in advance"); + stack + .set_case(case) + .at_entry(relative_path, Some(is_dir), |id, buf| self.odb.find_blob(id, buf)) + .map_or(false, |platform| platform.matching_attributes(out)) + }, + ) + } + + /// The simplified version of [`pattern_matching_relative_path()`](Self::pattern_matching_relative_path()) which returns + /// `true` if `relative_path` is included in the set of positive pathspecs, while not being excluded. + #[momo] + pub fn is_included<'a>(&mut self, relative_path: impl Into<&'a BStr>, is_dir: Option) -> bool { + self.pattern_matching_relative_path(relative_path, is_dir) + .map_or(false, |m| !m.is_excluded()) + } +} + +#[cfg(feature = "status")] +impl gix_status::Pathspec for PathspecDetached { + fn common_prefix(&self) -> &BStr { + self.search.common_prefix() + } + + fn is_included(&mut self, relative_path: &BStr, is_dir: Option) -> bool { + self.is_included(relative_path, is_dir) + } +} -- cgit v1.2.3