1use std::path::{Path, PathBuf};
2
3use bstr::{BStr, ByteSlice};
4use gix_glob::search::{pattern, Pattern};
5
6use super::Attributes;
7use crate::{
8 search::{Assignments, MetadataCollection, Outcome, TrackedAssignment, Value},
9 Search,
10};
11
12impl Search {
14 pub fn new_globals(
23 files: impl IntoIterator<Item = impl Into<PathBuf>>,
24 buf: &mut Vec<u8>,
25 collection: &mut MetadataCollection,
26 ) -> std::io::Result<Self> {
27 let mut group = Self::default();
28 group.add_patterns_buffer(
29 b"[attr]binary -diff -merge -text",
30 "[builtin]".into(),
31 None,
32 collection,
33 true, );
35
36 for path in files.into_iter() {
37 group.add_patterns_file(path.into(), true, None, buf, collection, true )?;
38 }
39 Ok(group)
40 }
41}
42
43impl Search {
45 pub fn add_patterns_file(
51 &mut self,
52 source: PathBuf,
53 follow_symlinks: bool,
54 root: Option<&Path>,
55 buf: &mut Vec<u8>,
56 collection: &mut MetadataCollection,
57 allow_macros: bool,
58 ) -> std::io::Result<bool> {
59 let was_added =
62 gix_glob::search::add_patterns_file(&mut self.patterns, source, follow_symlinks, root, buf, Attributes)?;
63 if was_added {
64 let last = self.patterns.last_mut().expect("just added");
65 if !allow_macros {
66 last.patterns
67 .retain(|p| !matches!(p.value, Value::MacroAssignments { .. }));
68 }
69 collection.update_from_list(last);
70 }
71 Ok(was_added)
72 }
73 pub fn add_patterns_buffer(
77 &mut self,
78 bytes: &[u8],
79 source: PathBuf,
80 root: Option<&Path>,
81 collection: &mut MetadataCollection,
82 allow_macros: bool,
83 ) {
84 self.patterns
85 .push(pattern::List::from_bytes(bytes, source, root, Attributes));
86 let last = self.patterns.last_mut().expect("just added");
87 if !allow_macros {
88 last.patterns
89 .retain(|p| !matches!(p.value, Value::MacroAssignments { .. }));
90 }
91 collection.update_from_list(last);
92 }
93
94 pub fn pop_pattern_list(&mut self) -> Option<gix_glob::search::pattern::List<Attributes>> {
96 self.patterns.pop()
97 }
98}
99
100impl Search {
102 pub fn pattern_matching_relative_path(
105 &self,
106 relative_path: &BStr,
107 case: gix_glob::pattern::Case,
108 is_dir: Option<bool>,
109 out: &mut Outcome,
110 ) -> bool {
111 let basename_pos = relative_path.rfind(b"/").map(|p| p + 1);
112 let mut has_match = false;
113 self.patterns.iter().rev().any(|pl| {
114 has_match |= pattern_matching_relative_path(pl, relative_path, basename_pos, case, is_dir, out);
115 out.is_done()
116 });
117 has_match
118 }
119
120 pub fn num_pattern_lists(&self) -> usize {
122 self.patterns.len()
123 }
124}
125
126impl Pattern for Attributes {
127 type Value = Value;
128
129 fn bytes_to_patterns(&self, bytes: &[u8], _source: &std::path::Path) -> Vec<pattern::Mapping<Self::Value>> {
130 fn into_owned_assignments<'a>(
131 attrs: impl Iterator<Item = Result<crate::AssignmentRef<'a>, crate::name::Error>>,
132 ) -> Option<Assignments> {
133 let res = attrs
134 .map(|res| {
135 res.map(|a| TrackedAssignment {
136 id: Default::default(),
137 inner: a.to_owned(),
138 })
139 })
140 .collect::<Result<Assignments, _>>();
141 match res {
142 Ok(res) => Some(res),
143 Err(_err) => {
144 gix_trace::warn!("{}", _err);
145 None
146 }
147 }
148 }
149
150 crate::parse(bytes)
151 .filter_map(|res| match res {
152 Ok(pattern) => Some(pattern),
153 Err(_err) => {
154 gix_trace::warn!("{}: {}", _source.display(), _err);
155 None
156 }
157 })
158 .filter_map(|(pattern_kind, assignments, line_number)| {
159 let (pattern, value) = match pattern_kind {
160 crate::parse::Kind::Macro(macro_name) => (
161 gix_glob::Pattern {
162 text: macro_name.as_str().into(),
163 mode: macro_mode(),
164 first_wildcard_pos: None,
165 },
166 Value::MacroAssignments {
167 id: Default::default(),
168 assignments: into_owned_assignments(assignments)?,
169 },
170 ),
171 crate::parse::Kind::Pattern(p) => (
172 (!p.is_negative()).then_some(p)?,
173 Value::Assignments(into_owned_assignments(assignments)?),
174 ),
175 };
176 pattern::Mapping {
177 pattern,
178 value,
179 sequence_number: line_number,
180 }
181 .into()
182 })
183 .collect()
184 }
185}
186
187impl Attributes {
188 fn may_use_glob_pattern(pattern: &gix_glob::Pattern) -> bool {
189 pattern.mode != macro_mode()
190 }
191}
192
193fn macro_mode() -> gix_glob::pattern::Mode {
194 gix_glob::pattern::Mode::all()
195}
196
197#[allow(unused_variables)]
203fn pattern_matching_relative_path(
204 list: &gix_glob::search::pattern::List<Attributes>,
205 relative_path: &BStr,
206 basename_pos: Option<usize>,
207 case: gix_glob::pattern::Case,
208 is_dir: Option<bool>,
209 out: &mut Outcome,
210) -> bool {
211 let (relative_path, basename_start_pos) =
212 match list.strip_base_handle_recompute_basename_pos(relative_path, basename_pos, case) {
213 Some(r) => r,
214 None => return false,
215 };
216 let cur_len = out.remaining();
217 'outer: for pattern::Mapping {
218 pattern,
219 value,
220 sequence_number,
221 } in list
222 .patterns
223 .iter()
224 .rev()
225 .filter(|pm| Attributes::may_use_glob_pattern(&pm.pattern))
226 {
227 let value: &Value = value;
228 let attrs = match value {
229 Value::MacroAssignments { .. } => {
230 unreachable!("we can't match on macros as they have no pattern")
231 }
232 Value::Assignments(attrs) => attrs,
233 };
234 if out.has_unspecified_attributes(attrs.iter().map(|attr| attr.id))
235 && pattern.matches_repo_relative_path(
236 relative_path,
237 basename_start_pos,
238 is_dir,
239 case,
240 gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL,
241 )
242 {
243 let all_filled = out.fill_attributes(attrs.iter(), pattern, list.source.as_ref(), *sequence_number);
244 if all_filled {
245 break 'outer;
246 }
247 }
248 }
249 cur_len != out.remaining()
250}