| use std::path::{Component, Path, PathBuf}; |
| |
| use bstr::{BStr, BString, ByteSlice, ByteVec}; |
| |
| use crate::{normalize, MagicSignature, Pattern, SearchMode}; |
| |
| /// Access |
| impl Pattern { |
| /// Returns `true` if this seems to be a pathspec that indicates that 'there is no pathspec'. |
| /// |
| /// Note that such a spec is `:`. |
| pub fn is_nil(&self) -> bool { |
| self.nil |
| } |
| |
| /// Return the prefix-portion of the `path` of this spec, which is a *directory*. |
| /// It can be empty if there is no prefix. |
| /// |
| /// A prefix is effectively the CWD seen as relative to the working tree, and it's assumed to |
| /// match case-sensitively. This makes it useful for skipping over large portions of input by |
| /// directly comparing them. |
| pub fn prefix_directory(&self) -> &BStr { |
| self.path[..self.prefix_len].as_bstr() |
| } |
| |
| /// Return the path of this spec, typically used for matching. |
| pub fn path(&self) -> &BStr { |
| self.path.as_ref() |
| } |
| } |
| |
| /// Mutation |
| impl Pattern { |
| /// Normalize the pattern's path by assuring it's relative to the root of the working tree, and contains |
| /// no relative path components. Further, it assures that `/` are used as path separator. |
| /// |
| /// If `self.path` is a relative path, it will be put in front of the pattern path if `self.signature` isn't indicating `TOP` already. |
| /// If `self.path` is an absolute path, we will use `root` to make it worktree relative if possible. |
| /// |
| /// `prefix` can be empty, we will still normalize this pathspec to resolve relative path components, and |
| /// it is assumed not to contain any relative path components, e.g. '', 'a', 'a/b' are valid. |
| /// `root` is the absolute path to the root of either the worktree or the repository's `git_dir`. |
| pub fn normalize(&mut self, prefix: &Path, root: &Path) -> Result<&mut Self, normalize::Error> { |
| fn prefix_components_to_subtract(path: &Path) -> usize { |
| let parent_component_end_bound = path.components().enumerate().fold(None::<usize>, |acc, (idx, c)| { |
| matches!(c, Component::ParentDir).then_some(idx + 1).or(acc) |
| }); |
| let count = path |
| .components() |
| .take(parent_component_end_bound.unwrap_or(0)) |
| .map(|c| match c { |
| Component::ParentDir => 1_isize, |
| Component::Normal(_) => -1, |
| _ => 0, |
| }) |
| .sum::<isize>(); |
| (count > 0).then_some(count as usize).unwrap_or_default() |
| } |
| |
| let mut path = gix_path::from_bstr(self.path.as_bstr()); |
| let mut num_prefix_components = 0; |
| let mut was_absolute = false; |
| if gix_path::is_absolute(path.as_ref()) { |
| was_absolute = true; |
| let rela_path = match path.strip_prefix(root) { |
| Ok(path) => path, |
| Err(_) => { |
| return Err(normalize::Error::AbsolutePathOutsideOfWorktree { |
| path: path.into_owned(), |
| worktree_path: root.into(), |
| }) |
| } |
| }; |
| path = rela_path.to_owned().into(); |
| } else if !prefix.as_os_str().is_empty() && !self.signature.contains(MagicSignature::TOP) { |
| debug_assert_eq!( |
| prefix |
| .components() |
| .filter(|c| matches!(c, Component::Normal(_))) |
| .count(), |
| prefix.components().count(), |
| "BUG: prefixes must not have relative path components, or calculations here will be wrong so pattern won't match" |
| ); |
| num_prefix_components = prefix |
| .components() |
| .count() |
| .saturating_sub(prefix_components_to_subtract(path.as_ref())); |
| path = prefix.join(path).into(); |
| } |
| |
| let assure_path_cannot_break_out_upwards = Path::new(""); |
| let path = match gix_path::normalize(path.as_ref().into(), assure_path_cannot_break_out_upwards) { |
| Some(path) => { |
| if was_absolute { |
| num_prefix_components = path.components().count().saturating_sub( |
| if self.signature.contains(MagicSignature::MUST_BE_DIR) { |
| 0 |
| } else { |
| 1 |
| }, |
| ); |
| } |
| path |
| } |
| None => { |
| return Err(normalize::Error::OutsideOfWorktree { |
| path: path.into_owned(), |
| }) |
| } |
| }; |
| |
| self.path = if path == Path::new(".") { |
| self.nil = true; |
| BString::from(".") |
| } else { |
| let cleaned = PathBuf::from_iter(path.components().filter(|c| !matches!(c, Component::CurDir))); |
| let mut out = gix_path::to_unix_separators_on_windows(gix_path::into_bstr(cleaned)).into_owned(); |
| self.prefix_len = { |
| if self.signature.contains(MagicSignature::MUST_BE_DIR) { |
| out.push(b'/'); |
| } |
| let len = out |
| .find_iter(b"/") |
| .take(num_prefix_components) |
| .last() |
| .unwrap_or_default(); |
| if self.signature.contains(MagicSignature::MUST_BE_DIR) { |
| out.pop(); |
| } |
| len |
| }; |
| out |
| }; |
| |
| Ok(self) |
| } |
| } |
| |
| /// Access |
| impl Pattern { |
| /// Return `true` if this pathspec is negated, which means it will exclude an item from the result set instead of including it. |
| pub fn is_excluded(&self) -> bool { |
| self.signature.contains(MagicSignature::EXCLUDE) |
| } |
| |
| /// Returns `true` is this pattern is supposed to always match, as it's either empty or designated `nil`. |
| /// Note that technically the pattern might still be excluded. |
| pub fn always_matches(&self) -> bool { |
| self.is_nil() || self.path.is_empty() |
| } |
| |
| /// Translate ourselves to a long display format, that when parsed back will yield the same pattern. |
| /// |
| /// Note that the |
| pub fn to_bstring(&self) -> BString { |
| if self.is_nil() { |
| ":".into() |
| } else { |
| let mut buf: BString = ":(".into(); |
| if self.signature.contains(MagicSignature::TOP) { |
| buf.push_str("top,"); |
| } |
| if self.signature.contains(MagicSignature::EXCLUDE) { |
| buf.push_str("exclude,"); |
| } |
| if self.signature.contains(MagicSignature::ICASE) { |
| buf.push_str("icase,"); |
| } |
| match self.search_mode { |
| SearchMode::ShellGlob => {} |
| SearchMode::Literal => buf.push_str("literal,"), |
| SearchMode::PathAwareGlob => buf.push_str("glob,"), |
| } |
| if self.attributes.is_empty() { |
| if buf.last() == Some(&b',') { |
| buf.pop(); |
| } |
| } else { |
| buf.push_str("attr:"); |
| for attr in &self.attributes { |
| let attr = attr.as_ref().to_string().replace(',', "\\,"); |
| buf.push_str(&attr); |
| buf.push(b' '); |
| } |
| buf.pop(); // trailing ' ' |
| } |
| buf.push(b')'); |
| buf.extend_from_slice(&self.path); |
| if self.signature.contains(MagicSignature::MUST_BE_DIR) { |
| buf.push(b'/'); |
| } |
| buf |
| } |
| } |
| } |
| |
| impl std::fmt::Display for Pattern { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| self.to_bstring().fmt(f) |
| } |
| } |