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