gix_pathspec/search/
init.rs1use gix_error::Result;
2use std::path::Path;
3
4use crate::{MagicSignature, Pattern, Search, search::Spec};
5
6fn mapping_from_pattern(
8 mut pathspec: Pattern,
9 prefix: &Path,
10 root: &Path,
11 sequence_number: usize,
12) -> Result<gix_glob::search::pattern::Mapping<Spec>> {
13 pathspec.normalize(prefix, root)?;
14 let mut match_all = pathspec.is_nil();
15 let glob = {
16 let mut g = gix_glob::Pattern::from_bytes_without_negation(&pathspec.path).unwrap_or_else(|| {
17 match_all = true;
18 gix_glob::Pattern {
20 text: pathspec.path.clone(),
21 mode: gix_glob::pattern::Mode::empty(),
22 first_wildcard_pos: None,
23 }
24 });
25 g.mode |= gix_glob::pattern::Mode::ABSOLUTE;
26 if pathspec.signature.contains(MagicSignature::MUST_BE_DIR) {
27 g.mode |= gix_glob::pattern::Mode::MUST_BE_DIR;
28 }
29 g
30 };
31
32 Ok(gix_glob::search::pattern::Mapping {
33 pattern: glob,
34 value: Spec {
35 attrs_match: {
36 (!pathspec.attributes.is_empty()).then(|| {
37 let mut out = gix_attributes::search::Outcome::default();
38 out.initialize_with_selection(
39 &Default::default(),
40 pathspec.attributes.iter().map(|a| a.name.as_str()),
41 );
42 out
43 })
44 },
45 pattern: pathspec,
46 },
47 sequence_number,
48 })
49}
50
51fn common_prefix_len(patterns: &[gix_glob::search::pattern::Mapping<Spec>]) -> usize {
52 let mut count = 0;
53 let len = patterns
54 .iter()
55 .filter(|p| !p.value.pattern.is_excluded())
56 .map(|p| {
57 count += 1;
58 if p.value.pattern.signature.contains(MagicSignature::ICASE) {
59 p.value.pattern.prefix_len
60 } else {
61 p.pattern.first_wildcard_pos.unwrap_or(p.pattern.text.len())
62 }
63 })
64 .min()
65 .unwrap_or_default();
66
67 if len == 0 {
68 return 0;
69 }
70
71 let mut max_len = len;
72 if count < 2 {
73 return max_len;
74 }
75
76 let mut patterns = patterns
77 .iter()
78 .filter(|p| !p.value.pattern.is_excluded())
79 .map(|p| &p.value.pattern.path);
80 let base = &patterns.next().expect("at least two patterns");
81 for path in patterns {
82 for (idx, (a, b)) in base[..max_len].iter().zip(path[..max_len].iter()).enumerate() {
83 if *a != *b {
84 max_len = idx;
85 break;
86 }
87 }
88 }
89 max_len
90}
91
92impl Search {
94 pub fn from_specs(
99 pathspecs: impl IntoIterator<Item = Pattern>,
100 prefix: Option<&std::path::Path>,
101 root: &std::path::Path,
102 ) -> Result<Self> {
103 fn inner(
104 pathspecs: &mut dyn Iterator<Item = Pattern>,
105 prefix: Option<&std::path::Path>,
106 root: &std::path::Path,
107 ) -> Result<Search> {
108 let prefix = prefix.unwrap_or(std::path::Path::new(""));
109 let mut patterns = pathspecs
110 .enumerate()
111 .map(|(idx, pattern)| mapping_from_pattern(pattern, prefix, root, idx))
112 .collect::<std::result::Result<Vec<_>, _>>()?;
113
114 if patterns.is_empty() && !prefix.as_os_str().is_empty() {
115 patterns.push(mapping_from_pattern(
116 Pattern::from_literal(&[], MagicSignature::MUST_BE_DIR),
117 prefix,
118 root,
119 0,
120 )?);
121 }
122
123 patterns.sort_by(|a, b| {
125 a.value
126 .pattern
127 .is_excluded()
128 .cmp(&b.value.pattern.is_excluded())
129 .reverse()
130 });
131
132 let common_prefix_len = common_prefix_len(&patterns);
133 Ok(Search {
134 all_patterns_are_excluded: patterns.iter().all(|s| s.value.pattern.is_excluded()),
135 patterns,
136 match_all: Pattern {
137 nil: true,
138 ..Default::default()
139 },
140 source: None,
141 common_prefix_len,
142 })
143 }
144 inner(&mut pathspecs.into_iter(), prefix, root)
145 }
146
147 pub fn into_patterns(self) -> impl Iterator<Item = Pattern> {
149 self.patterns.into_iter().map(|p| p.value.pattern)
150 }
151}