| use std::path::Path; |
| |
| use crate::{search::Spec, MagicSignature, Pattern, Search}; |
| |
| /// Create a new specification to support matches from `pathspec`, [normalizing](Pattern::normalize()) it with `prefix` and `root`. |
| fn mapping_from_pattern( |
| mut pathspec: Pattern, |
| prefix: &Path, |
| root: &Path, |
| sequence_number: usize, |
| ) -> Result<gix_glob::search::pattern::Mapping<Spec>, crate::normalize::Error> { |
| pathspec.normalize(prefix, root)?; |
| let mut match_all = pathspec.is_nil(); |
| let glob = { |
| let mut g = gix_glob::Pattern::from_bytes_without_negation(&pathspec.path).unwrap_or_else(|| { |
| match_all = true; |
| // This pattern is setup to match literally as all-whitespace. |
| gix_glob::Pattern { |
| text: pathspec.path.clone(), |
| mode: gix_glob::pattern::Mode::empty(), |
| first_wildcard_pos: None, |
| } |
| }); |
| g.mode |= gix_glob::pattern::Mode::ABSOLUTE; |
| if pathspec.signature.contains(MagicSignature::MUST_BE_DIR) { |
| g.mode |= gix_glob::pattern::Mode::MUST_BE_DIR; |
| } |
| g |
| }; |
| |
| Ok(gix_glob::search::pattern::Mapping { |
| pattern: glob, |
| value: Spec { |
| attrs_match: { |
| (!pathspec.attributes.is_empty()).then(|| { |
| let mut out = gix_attributes::search::Outcome::default(); |
| out.initialize_with_selection( |
| &Default::default(), |
| pathspec.attributes.iter().map(|a| a.name.as_str()), |
| ); |
| out |
| }) |
| }, |
| pattern: pathspec, |
| }, |
| sequence_number, |
| }) |
| } |
| |
| fn common_prefix_len(patterns: &[gix_glob::search::pattern::Mapping<Spec>]) -> usize { |
| let mut count = 0; |
| let len = patterns |
| .iter() |
| .filter(|p| !p.value.pattern.is_excluded()) |
| .map(|p| { |
| count += 1; |
| if p.value.pattern.signature.contains(MagicSignature::ICASE) { |
| p.value.pattern.prefix_len |
| } else { |
| p.pattern.first_wildcard_pos.unwrap_or(p.pattern.text.len()) |
| } |
| }) |
| .min() |
| .unwrap_or_default(); |
| |
| if len == 0 { |
| return 0; |
| } |
| |
| let mut max_len = len; |
| if count < 2 { |
| return max_len; |
| } |
| |
| let mut patterns = patterns |
| .iter() |
| .filter(|p| !p.value.pattern.is_excluded()) |
| .map(|p| &p.value.pattern.path); |
| let base = &patterns.next().expect("at least two patterns"); |
| for path in patterns { |
| for (idx, (a, b)) in base[..max_len].iter().zip(path[..max_len].iter()).enumerate() { |
| if *a != *b { |
| max_len = idx; |
| break; |
| } |
| } |
| } |
| max_len |
| } |
| |
| /// Lifecycle |
| impl Search { |
| /// Create a search from ready-made `pathspecs`, and [normalize](Pattern::normalize()) them with `prefix` and `root`. |
| /// `root` is the absolute path to the worktree root, if available, or the `git_dir` in case of bare repositories. |
| /// If `pathspecs` doesn't yield any pattern, we will match everything automatically. If `prefix` is also provided and not empty, |
| /// an artificial pattern will be added to yield all. |
| pub fn from_specs( |
| pathspecs: impl IntoIterator<Item = Pattern>, |
| prefix: Option<&std::path::Path>, |
| root: &std::path::Path, |
| ) -> Result<Self, crate::normalize::Error> { |
| let prefix = prefix.unwrap_or(std::path::Path::new("")); |
| let mut patterns = pathspecs |
| .into_iter() |
| .enumerate() |
| .map(|(idx, pattern)| mapping_from_pattern(pattern, prefix, root, idx)) |
| .collect::<Result<Vec<_>, _>>()?; |
| |
| if patterns.is_empty() && !prefix.as_os_str().is_empty() { |
| patterns.push(mapping_from_pattern( |
| Pattern::from_literal(&[], MagicSignature::MUST_BE_DIR), |
| prefix, |
| root, |
| 0, |
| )?); |
| } |
| |
| // Excludes should always happen first so we know a match is authoritative (otherwise we could find a non-excluding match first). |
| patterns.sort_by(|a, b| { |
| a.value |
| .pattern |
| .is_excluded() |
| .cmp(&b.value.pattern.is_excluded()) |
| .reverse() |
| }); |
| |
| let common_prefix_len = common_prefix_len(&patterns); |
| Ok(Search { |
| all_patterns_are_excluded: patterns.iter().all(|s| s.value.pattern.is_excluded()), |
| patterns, |
| source: None, |
| common_prefix_len, |
| }) |
| } |
| |
| /// Obtain ownership of the normalized pathspec patterns that were used for the search. |
| pub fn into_patterns(self) -> impl Iterator<Item = Pattern> { |
| self.patterns.into_iter().map(|p| p.value.pattern) |
| } |
| } |