##// END OF EJS Templates
rust: simplify pattern file parsing...
Spencer Baugh -
r51750:796b5d66 default
parent child Browse files
Show More
@@ -1,776 +1,807 b''
1 // filepatterns.rs
1 // filepatterns.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Handling of Mercurial-specific patterns.
8 //! Handling of Mercurial-specific patterns.
9
9
10 use crate::{
10 use crate::{
11 utils::{
11 utils::{
12 files::{canonical_path, get_bytes_from_path, get_path_from_bytes},
12 files::{canonical_path, get_bytes_from_path, get_path_from_bytes},
13 hg_path::{path_to_hg_path_buf, HgPathBuf, HgPathError},
13 hg_path::{path_to_hg_path_buf, HgPathBuf, HgPathError},
14 SliceExt,
14 SliceExt,
15 },
15 },
16 FastHashMap, PatternError,
16 FastHashMap, PatternError,
17 };
17 };
18 use lazy_static::lazy_static;
18 use lazy_static::lazy_static;
19 use regex::bytes::{NoExpand, Regex};
19 use regex::bytes::{NoExpand, Regex};
20 use std::ops::Deref;
20 use std::ops::Deref;
21 use std::path::{Path, PathBuf};
21 use std::path::{Path, PathBuf};
22 use std::vec::Vec;
22 use std::vec::Vec;
23
23
24 lazy_static! {
24 lazy_static! {
25 static ref RE_ESCAPE: Vec<Vec<u8>> = {
25 static ref RE_ESCAPE: Vec<Vec<u8>> = {
26 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
26 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
27 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
27 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
28 for byte in to_escape {
28 for byte in to_escape {
29 v[*byte as usize].insert(0, b'\\');
29 v[*byte as usize].insert(0, b'\\');
30 }
30 }
31 v
31 v
32 };
32 };
33 }
33 }
34
34
35 /// These are matched in order
35 /// These are matched in order
36 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
36 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
37 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
37 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
38
38
39 /// Appended to the regexp of globs
39 /// Appended to the regexp of globs
40 const GLOB_SUFFIX: &[u8; 7] = b"(?:/|$)";
40 const GLOB_SUFFIX: &[u8; 7] = b"(?:/|$)";
41
41
42 #[derive(Debug, Clone, PartialEq, Eq)]
42 #[derive(Debug, Clone, PartialEq, Eq)]
43 pub enum PatternSyntax {
43 pub enum PatternSyntax {
44 /// A regular expression
44 /// A regular expression
45 Regexp,
45 Regexp,
46 /// Glob that matches at the front of the path
46 /// Glob that matches at the front of the path
47 RootGlob,
47 RootGlob,
48 /// Glob that matches at any suffix of the path (still anchored at
48 /// Glob that matches at any suffix of the path (still anchored at
49 /// slashes)
49 /// slashes)
50 Glob,
50 Glob,
51 /// a path relative to repository root, which is matched recursively
51 /// a path relative to repository root, which is matched recursively
52 Path,
52 Path,
53 /// a single exact path relative to repository root
53 /// a single exact path relative to repository root
54 FilePath,
54 FilePath,
55 /// A path relative to cwd
55 /// A path relative to cwd
56 RelPath,
56 RelPath,
57 /// an unrooted glob (*.rs matches Rust files in all dirs)
57 /// an unrooted glob (*.rs matches Rust files in all dirs)
58 RelGlob,
58 RelGlob,
59 /// A regexp that needn't match the start of a name
59 /// A regexp that needn't match the start of a name
60 RelRegexp,
60 RelRegexp,
61 /// A path relative to repository root, which is matched non-recursively
61 /// A path relative to repository root, which is matched non-recursively
62 /// (will not match subdirectories)
62 /// (will not match subdirectories)
63 RootFiles,
63 RootFiles,
64 /// A file of patterns to read and include
64 /// A file of patterns to read and include
65 Include,
65 Include,
66 /// A file of patterns to match against files under the same directory
66 /// A file of patterns to match against files under the same directory
67 SubInclude,
67 SubInclude,
68 /// SubInclude with the result of parsing the included file
68 /// SubInclude with the result of parsing the included file
69 ///
69 ///
70 /// Note: there is no ExpandedInclude because that expansion can be done
70 /// Note: there is no ExpandedInclude because that expansion can be done
71 /// in place by replacing the Include pattern by the included patterns.
71 /// in place by replacing the Include pattern by the included patterns.
72 /// SubInclude requires more handling.
72 /// SubInclude requires more handling.
73 ///
73 ///
74 /// Note: `Box` is used to minimize size impact on other enum variants
74 /// Note: `Box` is used to minimize size impact on other enum variants
75 ExpandedSubInclude(Box<SubInclude>),
75 ExpandedSubInclude(Box<SubInclude>),
76 }
76 }
77
77
78 /// Transforms a glob pattern into a regex
78 /// Transforms a glob pattern into a regex
79 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
79 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
80 let mut input = pat;
80 let mut input = pat;
81 let mut res: Vec<u8> = vec![];
81 let mut res: Vec<u8> = vec![];
82 let mut group_depth = 0;
82 let mut group_depth = 0;
83
83
84 while let Some((c, rest)) = input.split_first() {
84 while let Some((c, rest)) = input.split_first() {
85 input = rest;
85 input = rest;
86
86
87 match c {
87 match c {
88 b'*' => {
88 b'*' => {
89 for (source, repl) in GLOB_REPLACEMENTS {
89 for (source, repl) in GLOB_REPLACEMENTS {
90 if let Some(rest) = input.drop_prefix(source) {
90 if let Some(rest) = input.drop_prefix(source) {
91 input = rest;
91 input = rest;
92 res.extend(*repl);
92 res.extend(*repl);
93 break;
93 break;
94 }
94 }
95 }
95 }
96 }
96 }
97 b'?' => res.extend(b"."),
97 b'?' => res.extend(b"."),
98 b'[' => {
98 b'[' => {
99 match input.iter().skip(1).position(|b| *b == b']') {
99 match input.iter().skip(1).position(|b| *b == b']') {
100 None => res.extend(b"\\["),
100 None => res.extend(b"\\["),
101 Some(end) => {
101 Some(end) => {
102 // Account for the one we skipped
102 // Account for the one we skipped
103 let end = end + 1;
103 let end = end + 1;
104
104
105 res.extend(b"[");
105 res.extend(b"[");
106
106
107 for (i, b) in input[..end].iter().enumerate() {
107 for (i, b) in input[..end].iter().enumerate() {
108 if *b == b'!' && i == 0 {
108 if *b == b'!' && i == 0 {
109 res.extend(b"^")
109 res.extend(b"^")
110 } else if *b == b'^' && i == 0 {
110 } else if *b == b'^' && i == 0 {
111 res.extend(b"\\^")
111 res.extend(b"\\^")
112 } else if *b == b'\\' {
112 } else if *b == b'\\' {
113 res.extend(b"\\\\")
113 res.extend(b"\\\\")
114 } else {
114 } else {
115 res.push(*b)
115 res.push(*b)
116 }
116 }
117 }
117 }
118 res.extend(b"]");
118 res.extend(b"]");
119 input = &input[end + 1..];
119 input = &input[end + 1..];
120 }
120 }
121 }
121 }
122 }
122 }
123 b'{' => {
123 b'{' => {
124 group_depth += 1;
124 group_depth += 1;
125 res.extend(b"(?:")
125 res.extend(b"(?:")
126 }
126 }
127 b'}' if group_depth > 0 => {
127 b'}' if group_depth > 0 => {
128 group_depth -= 1;
128 group_depth -= 1;
129 res.extend(b")");
129 res.extend(b")");
130 }
130 }
131 b',' if group_depth > 0 => res.extend(b"|"),
131 b',' if group_depth > 0 => res.extend(b"|"),
132 b'\\' => {
132 b'\\' => {
133 let c = {
133 let c = {
134 if let Some((c, rest)) = input.split_first() {
134 if let Some((c, rest)) = input.split_first() {
135 input = rest;
135 input = rest;
136 c
136 c
137 } else {
137 } else {
138 c
138 c
139 }
139 }
140 };
140 };
141 res.extend(&RE_ESCAPE[*c as usize])
141 res.extend(&RE_ESCAPE[*c as usize])
142 }
142 }
143 _ => res.extend(&RE_ESCAPE[*c as usize]),
143 _ => res.extend(&RE_ESCAPE[*c as usize]),
144 }
144 }
145 }
145 }
146 res
146 res
147 }
147 }
148
148
149 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
149 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
150 pattern
150 pattern
151 .iter()
151 .iter()
152 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
152 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
153 .collect()
153 .collect()
154 }
154 }
155
155
156 pub fn parse_pattern_syntax(
156 pub fn parse_pattern_syntax(
157 kind: &[u8],
157 kind: &[u8],
158 ) -> Result<PatternSyntax, PatternError> {
158 ) -> Result<PatternSyntax, PatternError> {
159 match kind {
159 match kind {
160 b"re:" => Ok(PatternSyntax::Regexp),
160 b"re:" => Ok(PatternSyntax::Regexp),
161 b"path:" => Ok(PatternSyntax::Path),
161 b"path:" => Ok(PatternSyntax::Path),
162 b"filepath:" => Ok(PatternSyntax::FilePath),
162 b"filepath:" => Ok(PatternSyntax::FilePath),
163 b"relpath:" => Ok(PatternSyntax::RelPath),
163 b"relpath:" => Ok(PatternSyntax::RelPath),
164 b"rootfilesin:" => Ok(PatternSyntax::RootFiles),
164 b"rootfilesin:" => Ok(PatternSyntax::RootFiles),
165 b"relglob:" => Ok(PatternSyntax::RelGlob),
165 b"relglob:" => Ok(PatternSyntax::RelGlob),
166 b"relre:" => Ok(PatternSyntax::RelRegexp),
166 b"relre:" => Ok(PatternSyntax::RelRegexp),
167 b"glob:" => Ok(PatternSyntax::Glob),
167 b"glob:" => Ok(PatternSyntax::Glob),
168 b"rootglob:" => Ok(PatternSyntax::RootGlob),
168 b"rootglob:" => Ok(PatternSyntax::RootGlob),
169 b"include:" => Ok(PatternSyntax::Include),
169 b"include:" => Ok(PatternSyntax::Include),
170 b"subinclude:" => Ok(PatternSyntax::SubInclude),
170 b"subinclude:" => Ok(PatternSyntax::SubInclude),
171 _ => Err(PatternError::UnsupportedSyntax(
171 _ => Err(PatternError::UnsupportedSyntax(
172 String::from_utf8_lossy(kind).to_string(),
172 String::from_utf8_lossy(kind).to_string(),
173 )),
173 )),
174 }
174 }
175 }
175 }
176
176
177 lazy_static! {
177 lazy_static! {
178 static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap();
178 static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap();
179 }
179 }
180
180
181 /// Builds the regex that corresponds to the given pattern.
181 /// Builds the regex that corresponds to the given pattern.
182 /// If within a `syntax: regexp` context, returns the pattern,
182 /// If within a `syntax: regexp` context, returns the pattern,
183 /// otherwise, returns the corresponding regex.
183 /// otherwise, returns the corresponding regex.
184 fn _build_single_regex(entry: &IgnorePattern) -> Vec<u8> {
184 fn _build_single_regex(entry: &IgnorePattern) -> Vec<u8> {
185 let IgnorePattern {
185 let IgnorePattern {
186 syntax, pattern, ..
186 syntax, pattern, ..
187 } = entry;
187 } = entry;
188 if pattern.is_empty() {
188 if pattern.is_empty() {
189 return vec![];
189 return vec![];
190 }
190 }
191 match syntax {
191 match syntax {
192 PatternSyntax::Regexp => pattern.to_owned(),
192 PatternSyntax::Regexp => pattern.to_owned(),
193 PatternSyntax::RelRegexp => {
193 PatternSyntax::RelRegexp => {
194 // The `regex` crate accepts `**` while `re2` and Python's `re`
194 // The `regex` crate accepts `**` while `re2` and Python's `re`
195 // do not. Checking for `*` correctly triggers the same error all
195 // do not. Checking for `*` correctly triggers the same error all
196 // engines.
196 // engines.
197 if pattern[0] == b'^'
197 if pattern[0] == b'^'
198 || pattern[0] == b'*'
198 || pattern[0] == b'*'
199 || pattern.starts_with(b".*")
199 || pattern.starts_with(b".*")
200 {
200 {
201 return pattern.to_owned();
201 return pattern.to_owned();
202 }
202 }
203 match FLAG_RE.find(pattern) {
203 match FLAG_RE.find(pattern) {
204 Some(mat) => {
204 Some(mat) => {
205 let s = mat.start();
205 let s = mat.start();
206 let e = mat.end();
206 let e = mat.end();
207 [
207 [
208 &b"(?"[..],
208 &b"(?"[..],
209 &pattern[s + 2..e - 1],
209 &pattern[s + 2..e - 1],
210 &b":"[..],
210 &b":"[..],
211 if pattern[e] == b'^'
211 if pattern[e] == b'^'
212 || pattern[e] == b'*'
212 || pattern[e] == b'*'
213 || pattern[e..].starts_with(b".*")
213 || pattern[e..].starts_with(b".*")
214 {
214 {
215 &b""[..]
215 &b""[..]
216 } else {
216 } else {
217 &b".*"[..]
217 &b".*"[..]
218 },
218 },
219 &pattern[e..],
219 &pattern[e..],
220 &b")"[..],
220 &b")"[..],
221 ]
221 ]
222 .concat()
222 .concat()
223 }
223 }
224 None => [&b".*"[..], pattern].concat(),
224 None => [&b".*"[..], pattern].concat(),
225 }
225 }
226 }
226 }
227 PatternSyntax::Path | PatternSyntax::RelPath => {
227 PatternSyntax::Path | PatternSyntax::RelPath => {
228 if pattern == b"." {
228 if pattern == b"." {
229 return vec![];
229 return vec![];
230 }
230 }
231 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
231 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
232 }
232 }
233 PatternSyntax::RootFiles => {
233 PatternSyntax::RootFiles => {
234 let mut res = if pattern == b"." {
234 let mut res = if pattern == b"." {
235 vec![]
235 vec![]
236 } else {
236 } else {
237 // Pattern is a directory name.
237 // Pattern is a directory name.
238 [escape_pattern(pattern).as_slice(), b"/"].concat()
238 [escape_pattern(pattern).as_slice(), b"/"].concat()
239 };
239 };
240
240
241 // Anything after the pattern must be a non-directory.
241 // Anything after the pattern must be a non-directory.
242 res.extend(b"[^/]+$");
242 res.extend(b"[^/]+$");
243 res
243 res
244 }
244 }
245 PatternSyntax::RelGlob => {
245 PatternSyntax::RelGlob => {
246 let glob_re = glob_to_re(pattern);
246 let glob_re = glob_to_re(pattern);
247 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
247 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
248 [b".*", rest, GLOB_SUFFIX].concat()
248 [b".*", rest, GLOB_SUFFIX].concat()
249 } else {
249 } else {
250 [b"(?:.*/)?", glob_re.as_slice(), GLOB_SUFFIX].concat()
250 [b"(?:.*/)?", glob_re.as_slice(), GLOB_SUFFIX].concat()
251 }
251 }
252 }
252 }
253 PatternSyntax::Glob | PatternSyntax::RootGlob => {
253 PatternSyntax::Glob | PatternSyntax::RootGlob => {
254 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
254 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
255 }
255 }
256 PatternSyntax::Include
256 PatternSyntax::Include
257 | PatternSyntax::SubInclude
257 | PatternSyntax::SubInclude
258 | PatternSyntax::ExpandedSubInclude(_)
258 | PatternSyntax::ExpandedSubInclude(_)
259 | PatternSyntax::FilePath => unreachable!(),
259 | PatternSyntax::FilePath => unreachable!(),
260 }
260 }
261 }
261 }
262
262
263 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
263 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
264 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
264 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
265
265
266 /// TODO support other platforms
266 /// TODO support other platforms
267 #[cfg(unix)]
267 #[cfg(unix)]
268 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
268 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
269 if bytes.is_empty() {
269 if bytes.is_empty() {
270 return b".".to_vec();
270 return b".".to_vec();
271 }
271 }
272 let sep = b'/';
272 let sep = b'/';
273
273
274 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
274 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
275 if initial_slashes > 2 {
275 if initial_slashes > 2 {
276 // POSIX allows one or two initial slashes, but treats three or more
276 // POSIX allows one or two initial slashes, but treats three or more
277 // as single slash.
277 // as single slash.
278 initial_slashes = 1;
278 initial_slashes = 1;
279 }
279 }
280 let components = bytes
280 let components = bytes
281 .split(|b| *b == sep)
281 .split(|b| *b == sep)
282 .filter(|c| !(c.is_empty() || c == b"."))
282 .filter(|c| !(c.is_empty() || c == b"."))
283 .fold(vec![], |mut acc, component| {
283 .fold(vec![], |mut acc, component| {
284 if component != b".."
284 if component != b".."
285 || (initial_slashes == 0 && acc.is_empty())
285 || (initial_slashes == 0 && acc.is_empty())
286 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
286 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
287 {
287 {
288 acc.push(component)
288 acc.push(component)
289 } else if !acc.is_empty() {
289 } else if !acc.is_empty() {
290 acc.pop();
290 acc.pop();
291 }
291 }
292 acc
292 acc
293 });
293 });
294 let mut new_bytes = components.join(&sep);
294 let mut new_bytes = components.join(&sep);
295
295
296 if initial_slashes > 0 {
296 if initial_slashes > 0 {
297 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
297 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
298 buf.extend(new_bytes);
298 buf.extend(new_bytes);
299 new_bytes = buf;
299 new_bytes = buf;
300 }
300 }
301 if new_bytes.is_empty() {
301 if new_bytes.is_empty() {
302 b".".to_vec()
302 b".".to_vec()
303 } else {
303 } else {
304 new_bytes
304 new_bytes
305 }
305 }
306 }
306 }
307
307
308 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
308 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
309 /// that don't need to be transformed into a regex.
309 /// that don't need to be transformed into a regex.
310 pub fn build_single_regex(
310 pub fn build_single_regex(
311 entry: &IgnorePattern,
311 entry: &IgnorePattern,
312 ) -> Result<Option<Vec<u8>>, PatternError> {
312 ) -> Result<Option<Vec<u8>>, PatternError> {
313 let IgnorePattern {
313 let IgnorePattern {
314 pattern, syntax, ..
314 pattern, syntax, ..
315 } = entry;
315 } = entry;
316 let pattern = match syntax {
316 let pattern = match syntax {
317 PatternSyntax::RootGlob
317 PatternSyntax::RootGlob
318 | PatternSyntax::Path
318 | PatternSyntax::Path
319 | PatternSyntax::RelGlob
319 | PatternSyntax::RelGlob
320 | PatternSyntax::RootFiles => normalize_path_bytes(pattern),
320 | PatternSyntax::RootFiles => normalize_path_bytes(pattern),
321 PatternSyntax::Include | PatternSyntax::SubInclude => {
321 PatternSyntax::Include | PatternSyntax::SubInclude => {
322 return Err(PatternError::NonRegexPattern(entry.clone()))
322 return Err(PatternError::NonRegexPattern(entry.clone()))
323 }
323 }
324 _ => pattern.to_owned(),
324 _ => pattern.to_owned(),
325 };
325 };
326 let is_simple_rootglob = *syntax == PatternSyntax::RootGlob
326 let is_simple_rootglob = *syntax == PatternSyntax::RootGlob
327 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b));
327 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b));
328 if is_simple_rootglob || syntax == &PatternSyntax::FilePath {
328 if is_simple_rootglob || syntax == &PatternSyntax::FilePath {
329 Ok(None)
329 Ok(None)
330 } else {
330 } else {
331 let mut entry = entry.clone();
331 let mut entry = entry.clone();
332 entry.pattern = pattern;
332 entry.pattern = pattern;
333 Ok(Some(_build_single_regex(&entry)))
333 Ok(Some(_build_single_regex(&entry)))
334 }
334 }
335 }
335 }
336
336
337 lazy_static! {
337 lazy_static! {
338 static ref SYNTAXES: FastHashMap<&'static [u8], &'static [u8]> = {
338 static ref SYNTAXES: FastHashMap<&'static [u8], PatternSyntax> = {
339 let mut m = FastHashMap::default();
339 let mut m = FastHashMap::default();
340
340
341 m.insert(b"re".as_ref(), b"relre:".as_ref());
341 m.insert(b"re:".as_ref(), PatternSyntax::Regexp);
342 m.insert(b"regexp".as_ref(), b"relre:".as_ref());
342 m.insert(b"regexp:".as_ref(), PatternSyntax::Regexp);
343 m.insert(b"glob".as_ref(), b"relglob:".as_ref());
343 m.insert(b"path:".as_ref(), PatternSyntax::Path);
344 m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref());
344 m.insert(b"filepath:".as_ref(), PatternSyntax::FilePath);
345 m.insert(b"include".as_ref(), b"include:".as_ref());
345 m.insert(b"relpath:".as_ref(), PatternSyntax::RelPath);
346 m.insert(b"subinclude".as_ref(), b"subinclude:".as_ref());
346 m.insert(b"rootfilesin:".as_ref(), PatternSyntax::RootFiles);
347 m.insert(b"path".as_ref(), b"path:".as_ref());
347 m.insert(b"relglob:".as_ref(), PatternSyntax::RelGlob);
348 m.insert(b"rootfilesin".as_ref(), b"rootfilesin:".as_ref());
348 m.insert(b"relre:".as_ref(), PatternSyntax::RelRegexp);
349 m.insert(b"glob:".as_ref(), PatternSyntax::Glob);
350 m.insert(b"rootglob:".as_ref(), PatternSyntax::RootGlob);
351 m.insert(b"include:".as_ref(), PatternSyntax::Include);
352 m.insert(b"subinclude:".as_ref(), PatternSyntax::SubInclude);
353
349 m
354 m
350 };
355 };
351 }
356 }
352
357
353 #[derive(Debug)]
358 #[derive(Debug)]
354 pub enum PatternFileWarning {
359 pub enum PatternFileWarning {
355 /// (file path, syntax bytes)
360 /// (file path, syntax bytes)
356 InvalidSyntax(PathBuf, Vec<u8>),
361 InvalidSyntax(PathBuf, Vec<u8>),
357 /// File path
362 /// File path
358 NoSuchFile(PathBuf),
363 NoSuchFile(PathBuf),
359 }
364 }
360
365
366 pub fn parse_one_pattern(
367 pattern: &[u8],
368 source: &Path,
369 default: PatternSyntax,
370 ) -> IgnorePattern {
371 let mut pattern_bytes: &[u8] = pattern;
372 let mut syntax = default;
373
374 for (s, val) in SYNTAXES.iter() {
375 if let Some(rest) = pattern_bytes.drop_prefix(s) {
376 syntax = val.clone();
377 pattern_bytes = rest;
378 break;
379 }
380 }
381
382 let pattern = pattern_bytes.to_vec();
383
384 IgnorePattern {
385 syntax,
386 pattern,
387 source: source.to_owned(),
388 }
389 }
390
361 pub fn parse_pattern_file_contents(
391 pub fn parse_pattern_file_contents(
362 lines: &[u8],
392 lines: &[u8],
363 file_path: &Path,
393 file_path: &Path,
364 default_syntax_override: Option<&[u8]>,
394 default_syntax_override: Option<PatternSyntax>,
365 warn: bool,
395 warn: bool,
396 relativize: bool,
366 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
397 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
367 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
398 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
368
399
369 #[allow(clippy::trivial_regex)]
400 #[allow(clippy::trivial_regex)]
370 let comment_escape_regex = Regex::new(r"\\#").unwrap();
401 let comment_escape_regex = Regex::new(r"\\#").unwrap();
371 let mut inputs: Vec<IgnorePattern> = vec![];
402 let mut inputs: Vec<IgnorePattern> = vec![];
372 let mut warnings: Vec<PatternFileWarning> = vec![];
403 let mut warnings: Vec<PatternFileWarning> = vec![];
373
404
374 let mut current_syntax =
405 let mut current_syntax =
375 default_syntax_override.unwrap_or_else(|| b"relre:".as_ref());
406 default_syntax_override.unwrap_or(PatternSyntax::RelRegexp);
376
407
377 for (line_number, mut line) in lines.split(|c| *c == b'\n').enumerate() {
408 for mut line in lines.split(|c| *c == b'\n') {
378 let line_number = line_number + 1;
379
380 let line_buf;
409 let line_buf;
381 if line.contains(&b'#') {
410 if line.contains(&b'#') {
382 if let Some(cap) = comment_regex.captures(line) {
411 if let Some(cap) = comment_regex.captures(line) {
383 line = &line[..cap.get(1).unwrap().end()]
412 line = &line[..cap.get(1).unwrap().end()]
384 }
413 }
385 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
414 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
386 line = &line_buf;
415 line = &line_buf;
387 }
416 }
388
417
389 let mut line = line.trim_end();
418 let line = line.trim_end();
390
419
391 if line.is_empty() {
420 if line.is_empty() {
392 continue;
421 continue;
393 }
422 }
394
423
395 if let Some(syntax) = line.drop_prefix(b"syntax:") {
424 if let Some(syntax) = line.drop_prefix(b"syntax:") {
396 let syntax = syntax.trim();
425 let syntax = syntax.trim();
397
426
398 if let Some(rel_syntax) = SYNTAXES.get(syntax) {
427 if let Some(parsed) =
399 current_syntax = rel_syntax;
428 SYNTAXES.get([syntax, &b":"[..]].concat().as_slice())
429 {
430 current_syntax = parsed.clone();
400 } else if warn {
431 } else if warn {
401 warnings.push(PatternFileWarning::InvalidSyntax(
432 warnings.push(PatternFileWarning::InvalidSyntax(
402 file_path.to_owned(),
433 file_path.to_owned(),
403 syntax.to_owned(),
434 syntax.to_owned(),
404 ));
435 ));
405 }
436 }
406 continue;
437 } else {
438 let pattern = parse_one_pattern(
439 line,
440 file_path,
441 current_syntax.clone(),
442 );
443 inputs.push(if relativize {
444 pattern.to_relative()
445 } else {
446 pattern
447 })
407 }
448 }
408
409 let mut line_syntax: &[u8] = current_syntax;
410
411 for (s, rels) in SYNTAXES.iter() {
412 if let Some(rest) = line.drop_prefix(rels) {
413 line_syntax = rels;
414 line = rest;
415 break;
416 }
417 if let Some(rest) = line.drop_prefix(&[s, &b":"[..]].concat()) {
418 line_syntax = rels;
419 line = rest;
420 break;
421 }
422 }
423
424 inputs.push(IgnorePattern::new(
425 parse_pattern_syntax(line_syntax).map_err(|e| match e {
426 PatternError::UnsupportedSyntax(syntax) => {
427 PatternError::UnsupportedSyntaxInFile(
428 syntax,
429 file_path.to_string_lossy().into(),
430 line_number,
431 )
432 }
433 _ => e,
434 })?,
435 line,
436 file_path,
437 ));
438 }
449 }
439 Ok((inputs, warnings))
450 Ok((inputs, warnings))
440 }
451 }
441
452
442 pub fn read_pattern_file(
453 pub fn read_pattern_file(
443 file_path: &Path,
454 file_path: &Path,
444 warn: bool,
455 warn: bool,
445 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
456 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
446 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
457 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
447 match std::fs::read(file_path) {
458 match std::fs::read(file_path) {
448 Ok(contents) => {
459 Ok(contents) => {
449 inspect_pattern_bytes(file_path, &contents);
460 inspect_pattern_bytes(file_path, &contents);
450 parse_pattern_file_contents(&contents, file_path, None, warn)
461 parse_pattern_file_contents(&contents, file_path, None, warn, true)
451 }
462 }
452 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
463 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
453 vec![],
464 vec![],
454 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
465 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
455 )),
466 )),
456 Err(e) => Err(e.into()),
467 Err(e) => Err(e.into()),
457 }
468 }
458 }
469 }
459
470
460 /// Represents an entry in an "ignore" file.
471 /// Represents an entry in an "ignore" file.
461 #[derive(Debug, Eq, PartialEq, Clone)]
472 #[derive(Debug, Eq, PartialEq, Clone)]
462 pub struct IgnorePattern {
473 pub struct IgnorePattern {
463 pub syntax: PatternSyntax,
474 pub syntax: PatternSyntax,
464 pub pattern: Vec<u8>,
475 pub pattern: Vec<u8>,
465 pub source: PathBuf,
476 pub source: PathBuf,
466 }
477 }
467
478
468 impl IgnorePattern {
479 impl IgnorePattern {
469 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
480 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
470 Self {
481 Self {
471 syntax,
482 syntax,
472 pattern: pattern.to_owned(),
483 pattern: pattern.to_owned(),
473 source: source.to_owned(),
484 source: source.to_owned(),
474 }
485 }
475 }
486 }
487
488 pub fn to_relative(self) -> Self {
489 let Self {
490 syntax,
491 pattern,
492 source,
493 } = self;
494 Self {
495 syntax: match syntax {
496 PatternSyntax::Regexp => PatternSyntax::RelRegexp,
497 PatternSyntax::Glob => PatternSyntax::RelGlob,
498 x => x,
499 },
500 pattern,
501 source,
502 }
503 }
476 }
504 }
477
505
478 pub type PatternResult<T> = Result<T, PatternError>;
506 pub type PatternResult<T> = Result<T, PatternError>;
479
507
480 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
508 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
481 /// and `subinclude:` patterns.
509 /// and `subinclude:` patterns.
482 ///
510 ///
483 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
511 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
484 /// is used for the latter to form a tree of patterns.
512 /// is used for the latter to form a tree of patterns.
485 pub fn get_patterns_from_file(
513 pub fn get_patterns_from_file(
486 pattern_file: &Path,
514 pattern_file: &Path,
487 root_dir: &Path,
515 root_dir: &Path,
488 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
516 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
489 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
517 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
490 let (patterns, mut warnings) =
518 let (patterns, mut warnings) =
491 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
519 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
492 let patterns = patterns
520 let patterns = patterns
493 .into_iter()
521 .into_iter()
494 .flat_map(|entry| -> PatternResult<_> {
522 .flat_map(|entry| -> PatternResult<_> {
495 Ok(match &entry.syntax {
523 Ok(match &entry.syntax {
496 PatternSyntax::Include => {
524 PatternSyntax::Include => {
497 let inner_include =
525 let inner_include =
498 root_dir.join(get_path_from_bytes(&entry.pattern));
526 root_dir.join(get_path_from_bytes(&entry.pattern));
499 let (inner_pats, inner_warnings) = get_patterns_from_file(
527 let (inner_pats, inner_warnings) = get_patterns_from_file(
500 &inner_include,
528 &inner_include,
501 root_dir,
529 root_dir,
502 inspect_pattern_bytes,
530 inspect_pattern_bytes,
503 )?;
531 )?;
504 warnings.extend(inner_warnings);
532 warnings.extend(inner_warnings);
505 inner_pats
533 inner_pats
506 }
534 }
507 PatternSyntax::SubInclude => {
535 PatternSyntax::SubInclude => {
508 let mut sub_include = SubInclude::new(
536 let mut sub_include = SubInclude::new(
509 root_dir,
537 root_dir,
510 &entry.pattern,
538 &entry.pattern,
511 &entry.source,
539 &entry.source,
512 )?;
540 )?;
513 let (inner_patterns, inner_warnings) =
541 let (inner_patterns, inner_warnings) =
514 get_patterns_from_file(
542 get_patterns_from_file(
515 &sub_include.path,
543 &sub_include.path,
516 &sub_include.root,
544 &sub_include.root,
517 inspect_pattern_bytes,
545 inspect_pattern_bytes,
518 )?;
546 )?;
519 sub_include.included_patterns = inner_patterns;
547 sub_include.included_patterns = inner_patterns;
520 warnings.extend(inner_warnings);
548 warnings.extend(inner_warnings);
521 vec![IgnorePattern {
549 vec![IgnorePattern {
522 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
550 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
523 sub_include,
551 sub_include,
524 )),
552 )),
525 ..entry
553 ..entry
526 }]
554 }]
527 }
555 }
528 _ => vec![entry],
556 _ => vec![entry],
529 })
557 })
530 })
558 })
531 .flatten()
559 .flatten()
532 .collect();
560 .collect();
533
561
534 Ok((patterns, warnings))
562 Ok((patterns, warnings))
535 }
563 }
536
564
537 /// Holds all the information needed to handle a `subinclude:` pattern.
565 /// Holds all the information needed to handle a `subinclude:` pattern.
538 #[derive(Debug, PartialEq, Eq, Clone)]
566 #[derive(Debug, PartialEq, Eq, Clone)]
539 pub struct SubInclude {
567 pub struct SubInclude {
540 /// Will be used for repository (hg) paths that start with this prefix.
568 /// Will be used for repository (hg) paths that start with this prefix.
541 /// It is relative to the current working directory, so comparing against
569 /// It is relative to the current working directory, so comparing against
542 /// repository paths is painless.
570 /// repository paths is painless.
543 pub prefix: HgPathBuf,
571 pub prefix: HgPathBuf,
544 /// The file itself, containing the patterns
572 /// The file itself, containing the patterns
545 pub path: PathBuf,
573 pub path: PathBuf,
546 /// Folder in the filesystem where this it applies
574 /// Folder in the filesystem where this it applies
547 pub root: PathBuf,
575 pub root: PathBuf,
548
576
549 pub included_patterns: Vec<IgnorePattern>,
577 pub included_patterns: Vec<IgnorePattern>,
550 }
578 }
551
579
552 impl SubInclude {
580 impl SubInclude {
553 pub fn new(
581 pub fn new(
554 root_dir: &Path,
582 root_dir: &Path,
555 pattern: &[u8],
583 pattern: &[u8],
556 source: &Path,
584 source: &Path,
557 ) -> Result<SubInclude, HgPathError> {
585 ) -> Result<SubInclude, HgPathError> {
558 let normalized_source =
586 let normalized_source =
559 normalize_path_bytes(&get_bytes_from_path(source));
587 normalize_path_bytes(&get_bytes_from_path(source));
560
588
561 let source_root = get_path_from_bytes(&normalized_source);
589 let source_root = get_path_from_bytes(&normalized_source);
562 let source_root =
590 let source_root =
563 source_root.parent().unwrap_or_else(|| source_root.deref());
591 source_root.parent().unwrap_or_else(|| source_root.deref());
564
592
565 let path = source_root.join(get_path_from_bytes(pattern));
593 let path = source_root.join(get_path_from_bytes(pattern));
566 let new_root = path.parent().unwrap_or_else(|| path.deref());
594 let new_root = path.parent().unwrap_or_else(|| path.deref());
567
595
568 let prefix = canonical_path(root_dir, root_dir, new_root)?;
596 let prefix = canonical_path(root_dir, root_dir, new_root)?;
569
597
570 Ok(Self {
598 Ok(Self {
571 prefix: path_to_hg_path_buf(prefix).map(|mut p| {
599 prefix: path_to_hg_path_buf(prefix).map(|mut p| {
572 if !p.is_empty() {
600 if !p.is_empty() {
573 p.push_byte(b'/');
601 p.push_byte(b'/');
574 }
602 }
575 p
603 p
576 })?,
604 })?,
577 path: path.to_owned(),
605 path: path.to_owned(),
578 root: new_root.to_owned(),
606 root: new_root.to_owned(),
579 included_patterns: Vec::new(),
607 included_patterns: Vec::new(),
580 })
608 })
581 }
609 }
582 }
610 }
583
611
584 /// Separate and pre-process subincludes from other patterns for the "ignore"
612 /// Separate and pre-process subincludes from other patterns for the "ignore"
585 /// phase.
613 /// phase.
586 pub fn filter_subincludes(
614 pub fn filter_subincludes(
587 ignore_patterns: Vec<IgnorePattern>,
615 ignore_patterns: Vec<IgnorePattern>,
588 ) -> Result<(Vec<SubInclude>, Vec<IgnorePattern>), HgPathError> {
616 ) -> Result<(Vec<SubInclude>, Vec<IgnorePattern>), HgPathError> {
589 let mut subincludes = vec![];
617 let mut subincludes = vec![];
590 let mut others = vec![];
618 let mut others = vec![];
591
619
592 for pattern in ignore_patterns {
620 for pattern in ignore_patterns {
593 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
621 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
594 {
622 {
595 subincludes.push(*sub_include);
623 subincludes.push(*sub_include);
596 } else {
624 } else {
597 others.push(pattern)
625 others.push(pattern)
598 }
626 }
599 }
627 }
600 Ok((subincludes, others))
628 Ok((subincludes, others))
601 }
629 }
602
630
603 #[cfg(test)]
631 #[cfg(test)]
604 mod tests {
632 mod tests {
605 use super::*;
633 use super::*;
606 use pretty_assertions::assert_eq;
634 use pretty_assertions::assert_eq;
607
635
608 #[test]
636 #[test]
609 fn escape_pattern_test() {
637 fn escape_pattern_test() {
610 let untouched =
638 let untouched =
611 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
639 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
612 assert_eq!(escape_pattern(untouched), untouched.to_vec());
640 assert_eq!(escape_pattern(untouched), untouched.to_vec());
613 // All escape codes
641 // All escape codes
614 assert_eq!(
642 assert_eq!(
615 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
643 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
616 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
644 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
617 .to_vec()
645 .to_vec()
618 );
646 );
619 }
647 }
620
648
621 #[test]
649 #[test]
622 fn glob_test() {
650 fn glob_test() {
623 assert_eq!(glob_to_re(br#"?"#), br#"."#);
651 assert_eq!(glob_to_re(br#"?"#), br#"."#);
624 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
652 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
625 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
653 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
626 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
654 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
627 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
655 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
628 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
656 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
629 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
657 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
630 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
658 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
631 }
659 }
632
660
633 #[test]
661 #[test]
634 fn test_parse_pattern_file_contents() {
662 fn test_parse_pattern_file_contents() {
635 let lines = b"syntax: glob\n*.elc";
663 let lines = b"syntax: glob\n*.elc";
636
664
637 assert_eq!(
665 assert_eq!(
638 parse_pattern_file_contents(
666 parse_pattern_file_contents(
639 lines,
667 lines,
640 Path::new("file_path"),
668 Path::new("file_path"),
641 None,
669 None,
642 false
670 false,
671 true,
643 )
672 )
644 .unwrap()
673 .unwrap()
645 .0,
674 .0,
646 vec![IgnorePattern::new(
675 vec![IgnorePattern::new(
647 PatternSyntax::RelGlob,
676 PatternSyntax::RelGlob,
648 b"*.elc",
677 b"*.elc",
649 Path::new("file_path")
678 Path::new("file_path")
650 )],
679 )],
651 );
680 );
652
681
653 let lines = b"syntax: include\nsyntax: glob";
682 let lines = b"syntax: include\nsyntax: glob";
654
683
655 assert_eq!(
684 assert_eq!(
656 parse_pattern_file_contents(
685 parse_pattern_file_contents(
657 lines,
686 lines,
658 Path::new("file_path"),
687 Path::new("file_path"),
659 None,
688 None,
660 false
689 false,
690 true,
661 )
691 )
662 .unwrap()
692 .unwrap()
663 .0,
693 .0,
664 vec![]
694 vec![]
665 );
695 );
666 let lines = b"glob:**.o";
696 let lines = b"glob:**.o";
667 assert_eq!(
697 assert_eq!(
668 parse_pattern_file_contents(
698 parse_pattern_file_contents(
669 lines,
699 lines,
670 Path::new("file_path"),
700 Path::new("file_path"),
671 None,
701 None,
672 false
702 false,
703 true,
673 )
704 )
674 .unwrap()
705 .unwrap()
675 .0,
706 .0,
676 vec![IgnorePattern::new(
707 vec![IgnorePattern::new(
677 PatternSyntax::RelGlob,
708 PatternSyntax::RelGlob,
678 b"**.o",
709 b"**.o",
679 Path::new("file_path")
710 Path::new("file_path")
680 )]
711 )]
681 );
712 );
682 }
713 }
683
714
684 #[test]
715 #[test]
685 fn test_build_single_regex() {
716 fn test_build_single_regex() {
686 assert_eq!(
717 assert_eq!(
687 build_single_regex(&IgnorePattern::new(
718 build_single_regex(&IgnorePattern::new(
688 PatternSyntax::RelGlob,
719 PatternSyntax::RelGlob,
689 b"rust/target/",
720 b"rust/target/",
690 Path::new("")
721 Path::new("")
691 ))
722 ))
692 .unwrap(),
723 .unwrap(),
693 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
724 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
694 );
725 );
695 assert_eq!(
726 assert_eq!(
696 build_single_regex(&IgnorePattern::new(
727 build_single_regex(&IgnorePattern::new(
697 PatternSyntax::Regexp,
728 PatternSyntax::Regexp,
698 br"rust/target/\d+",
729 br"rust/target/\d+",
699 Path::new("")
730 Path::new("")
700 ))
731 ))
701 .unwrap(),
732 .unwrap(),
702 Some(br"rust/target/\d+".to_vec()),
733 Some(br"rust/target/\d+".to_vec()),
703 );
734 );
704 }
735 }
705
736
706 #[test]
737 #[test]
707 fn test_build_single_regex_shortcut() {
738 fn test_build_single_regex_shortcut() {
708 assert_eq!(
739 assert_eq!(
709 build_single_regex(&IgnorePattern::new(
740 build_single_regex(&IgnorePattern::new(
710 PatternSyntax::RootGlob,
741 PatternSyntax::RootGlob,
711 b"",
742 b"",
712 Path::new("")
743 Path::new("")
713 ))
744 ))
714 .unwrap(),
745 .unwrap(),
715 None,
746 None,
716 );
747 );
717 assert_eq!(
748 assert_eq!(
718 build_single_regex(&IgnorePattern::new(
749 build_single_regex(&IgnorePattern::new(
719 PatternSyntax::RootGlob,
750 PatternSyntax::RootGlob,
720 b"whatever",
751 b"whatever",
721 Path::new("")
752 Path::new("")
722 ))
753 ))
723 .unwrap(),
754 .unwrap(),
724 None,
755 None,
725 );
756 );
726 assert_eq!(
757 assert_eq!(
727 build_single_regex(&IgnorePattern::new(
758 build_single_regex(&IgnorePattern::new(
728 PatternSyntax::RootGlob,
759 PatternSyntax::RootGlob,
729 b"*.o",
760 b"*.o",
730 Path::new("")
761 Path::new("")
731 ))
762 ))
732 .unwrap(),
763 .unwrap(),
733 Some(br"[^/]*\.o(?:/|$)".to_vec()),
764 Some(br"[^/]*\.o(?:/|$)".to_vec()),
734 );
765 );
735 }
766 }
736
767
737 #[test]
768 #[test]
738 fn test_build_single_relregex() {
769 fn test_build_single_relregex() {
739 assert_eq!(
770 assert_eq!(
740 build_single_regex(&IgnorePattern::new(
771 build_single_regex(&IgnorePattern::new(
741 PatternSyntax::RelRegexp,
772 PatternSyntax::RelRegexp,
742 b"^ba{2}r",
773 b"^ba{2}r",
743 Path::new("")
774 Path::new("")
744 ))
775 ))
745 .unwrap(),
776 .unwrap(),
746 Some(b"^ba{2}r".to_vec()),
777 Some(b"^ba{2}r".to_vec()),
747 );
778 );
748 assert_eq!(
779 assert_eq!(
749 build_single_regex(&IgnorePattern::new(
780 build_single_regex(&IgnorePattern::new(
750 PatternSyntax::RelRegexp,
781 PatternSyntax::RelRegexp,
751 b"ba{2}r",
782 b"ba{2}r",
752 Path::new("")
783 Path::new("")
753 ))
784 ))
754 .unwrap(),
785 .unwrap(),
755 Some(b".*ba{2}r".to_vec()),
786 Some(b".*ba{2}r".to_vec()),
756 );
787 );
757 assert_eq!(
788 assert_eq!(
758 build_single_regex(&IgnorePattern::new(
789 build_single_regex(&IgnorePattern::new(
759 PatternSyntax::RelRegexp,
790 PatternSyntax::RelRegexp,
760 b"(?ia)ba{2}r",
791 b"(?ia)ba{2}r",
761 Path::new("")
792 Path::new("")
762 ))
793 ))
763 .unwrap(),
794 .unwrap(),
764 Some(b"(?ia:.*ba{2}r)".to_vec()),
795 Some(b"(?ia:.*ba{2}r)".to_vec()),
765 );
796 );
766 assert_eq!(
797 assert_eq!(
767 build_single_regex(&IgnorePattern::new(
798 build_single_regex(&IgnorePattern::new(
768 PatternSyntax::RelRegexp,
799 PatternSyntax::RelRegexp,
769 b"(?ia)^ba{2}r",
800 b"(?ia)^ba{2}r",
770 Path::new("")
801 Path::new("")
771 ))
802 ))
772 .unwrap(),
803 .unwrap(),
773 Some(b"(?ia:^ba{2}r)".to_vec()),
804 Some(b"(?ia:^ba{2}r)".to_vec()),
774 );
805 );
775 }
806 }
776 }
807 }
@@ -1,113 +1,115 b''
1 use std::path::Path;
1 use std::path::Path;
2
2
3 use crate::{
3 use crate::{
4 errors::HgError,
4 errors::HgError,
5 exit_codes,
5 exit_codes,
6 filepatterns::parse_pattern_file_contents,
6 filepatterns::parse_pattern_file_contents,
7 matchers::{
7 matchers::{
8 AlwaysMatcher, DifferenceMatcher, IncludeMatcher, Matcher,
8 AlwaysMatcher, DifferenceMatcher, IncludeMatcher, Matcher,
9 NeverMatcher,
9 NeverMatcher,
10 },
10 },
11 repo::Repo,
11 repo::Repo,
12 requirements::NARROW_REQUIREMENT,
12 requirements::NARROW_REQUIREMENT,
13 sparse::{self, SparseConfigError, SparseWarning},
13 sparse::{self, SparseConfigError, SparseWarning},
14 };
14 };
15
15
16 /// The file in .hg/store/ that indicates which paths exit in the store
16 /// The file in .hg/store/ that indicates which paths exit in the store
17 const FILENAME: &str = "narrowspec";
17 const FILENAME: &str = "narrowspec";
18 /// The file in .hg/ that indicates which paths exit in the dirstate
18 /// The file in .hg/ that indicates which paths exit in the dirstate
19 const DIRSTATE_FILENAME: &str = "narrowspec.dirstate";
19 const DIRSTATE_FILENAME: &str = "narrowspec.dirstate";
20
20
21 /// Pattern prefixes that are allowed in narrow patterns. This list MUST
21 /// Pattern prefixes that are allowed in narrow patterns. This list MUST
22 /// only contain patterns that are fast and safe to evaluate. Keep in mind
22 /// only contain patterns that are fast and safe to evaluate. Keep in mind
23 /// that patterns are supplied by clients and executed on remote servers
23 /// that patterns are supplied by clients and executed on remote servers
24 /// as part of wire protocol commands. That means that changes to this
24 /// as part of wire protocol commands. That means that changes to this
25 /// data structure influence the wire protocol and should not be taken
25 /// data structure influence the wire protocol and should not be taken
26 /// lightly - especially removals.
26 /// lightly - especially removals.
27 const VALID_PREFIXES: [&str; 2] = ["path:", "rootfilesin:"];
27 const VALID_PREFIXES: [&str; 2] = ["path:", "rootfilesin:"];
28
28
29 /// Return the matcher for the current narrow spec, and all configuration
29 /// Return the matcher for the current narrow spec, and all configuration
30 /// warnings to display.
30 /// warnings to display.
31 pub fn matcher(
31 pub fn matcher(
32 repo: &Repo,
32 repo: &Repo,
33 ) -> Result<(Box<dyn Matcher + Sync>, Vec<SparseWarning>), SparseConfigError> {
33 ) -> Result<(Box<dyn Matcher + Sync>, Vec<SparseWarning>), SparseConfigError> {
34 let mut warnings = vec![];
34 let mut warnings = vec![];
35 if !repo.requirements().contains(NARROW_REQUIREMENT) {
35 if !repo.requirements().contains(NARROW_REQUIREMENT) {
36 return Ok((Box::new(AlwaysMatcher), warnings));
36 return Ok((Box::new(AlwaysMatcher), warnings));
37 }
37 }
38 // Treat "narrowspec does not exist" the same as "narrowspec file exists
38 // Treat "narrowspec does not exist" the same as "narrowspec file exists
39 // and is empty".
39 // and is empty".
40 let store_spec = repo.store_vfs().try_read(FILENAME)?.unwrap_or_default();
40 let store_spec = repo.store_vfs().try_read(FILENAME)?.unwrap_or_default();
41 let working_copy_spec = repo
41 let working_copy_spec = repo
42 .hg_vfs()
42 .hg_vfs()
43 .try_read(DIRSTATE_FILENAME)?
43 .try_read(DIRSTATE_FILENAME)?
44 .unwrap_or_default();
44 .unwrap_or_default();
45 if store_spec != working_copy_spec {
45 if store_spec != working_copy_spec {
46 return Err(HgError::abort(
46 return Err(HgError::abort(
47 "abort: working copy's narrowspec is stale",
47 "abort: working copy's narrowspec is stale",
48 exit_codes::STATE_ERROR,
48 exit_codes::STATE_ERROR,
49 Some("run 'hg tracked --update-working-copy'".into()),
49 Some("run 'hg tracked --update-working-copy'".into()),
50 )
50 )
51 .into());
51 .into());
52 }
52 }
53
53
54 let config = sparse::parse_config(
54 let config = sparse::parse_config(
55 &store_spec,
55 &store_spec,
56 sparse::SparseConfigContext::Narrow,
56 sparse::SparseConfigContext::Narrow,
57 )?;
57 )?;
58
58
59 warnings.extend(config.warnings);
59 warnings.extend(config.warnings);
60
60
61 if !config.profiles.is_empty() {
61 if !config.profiles.is_empty() {
62 // TODO (from Python impl) maybe do something with profiles?
62 // TODO (from Python impl) maybe do something with profiles?
63 return Err(SparseConfigError::IncludesInNarrow);
63 return Err(SparseConfigError::IncludesInNarrow);
64 }
64 }
65 validate_patterns(&config.includes)?;
65 validate_patterns(&config.includes)?;
66 validate_patterns(&config.excludes)?;
66 validate_patterns(&config.excludes)?;
67
67
68 if config.includes.is_empty() {
68 if config.includes.is_empty() {
69 return Ok((Box::new(NeverMatcher), warnings));
69 return Ok((Box::new(NeverMatcher), warnings));
70 }
70 }
71
71
72 let (patterns, subwarnings) = parse_pattern_file_contents(
72 let (patterns, subwarnings) = parse_pattern_file_contents(
73 &config.includes,
73 &config.includes,
74 Path::new(""),
74 Path::new(""),
75 None,
75 None,
76 false,
76 false,
77 true,
77 )?;
78 )?;
78 warnings.extend(subwarnings.into_iter().map(From::from));
79 warnings.extend(subwarnings.into_iter().map(From::from));
79
80
80 let mut m: Box<dyn Matcher + Sync> =
81 let mut m: Box<dyn Matcher + Sync> =
81 Box::new(IncludeMatcher::new(patterns)?);
82 Box::new(IncludeMatcher::new(patterns)?);
82
83
83 let (patterns, subwarnings) = parse_pattern_file_contents(
84 let (patterns, subwarnings) = parse_pattern_file_contents(
84 &config.excludes,
85 &config.excludes,
85 Path::new(""),
86 Path::new(""),
86 None,
87 None,
87 false,
88 false,
89 true,
88 )?;
90 )?;
89 if !patterns.is_empty() {
91 if !patterns.is_empty() {
90 warnings.extend(subwarnings.into_iter().map(From::from));
92 warnings.extend(subwarnings.into_iter().map(From::from));
91 let exclude_matcher = Box::new(IncludeMatcher::new(patterns)?);
93 let exclude_matcher = Box::new(IncludeMatcher::new(patterns)?);
92 m = Box::new(DifferenceMatcher::new(m, exclude_matcher));
94 m = Box::new(DifferenceMatcher::new(m, exclude_matcher));
93 }
95 }
94
96
95 Ok((m, warnings))
97 Ok((m, warnings))
96 }
98 }
97
99
98 fn validate_patterns(patterns: &[u8]) -> Result<(), SparseConfigError> {
100 fn validate_patterns(patterns: &[u8]) -> Result<(), SparseConfigError> {
99 for pattern in patterns.split(|c| *c == b'\n') {
101 for pattern in patterns.split(|c| *c == b'\n') {
100 if pattern.is_empty() {
102 if pattern.is_empty() {
101 continue;
103 continue;
102 }
104 }
103 for prefix in VALID_PREFIXES.iter() {
105 for prefix in VALID_PREFIXES.iter() {
104 if pattern.starts_with(prefix.as_bytes()) {
106 if pattern.starts_with(prefix.as_bytes()) {
105 return Ok(());
107 return Ok(());
106 }
108 }
107 }
109 }
108 return Err(SparseConfigError::InvalidNarrowPrefix(
110 return Err(SparseConfigError::InvalidNarrowPrefix(
109 pattern.to_owned(),
111 pattern.to_owned(),
110 ));
112 ));
111 }
113 }
112 Ok(())
114 Ok(())
113 }
115 }
@@ -1,339 +1,341 b''
1 use std::{collections::HashSet, path::Path};
1 use std::{collections::HashSet, path::Path};
2
2
3 use format_bytes::{write_bytes, DisplayBytes};
3 use format_bytes::{write_bytes, DisplayBytes};
4
4
5 use crate::{
5 use crate::{
6 errors::HgError,
6 errors::HgError,
7 filepatterns::parse_pattern_file_contents,
7 filepatterns::parse_pattern_file_contents,
8 matchers::{
8 matchers::{
9 AlwaysMatcher, DifferenceMatcher, IncludeMatcher, Matcher,
9 AlwaysMatcher, DifferenceMatcher, IncludeMatcher, Matcher,
10 UnionMatcher,
10 UnionMatcher,
11 },
11 },
12 operations::cat,
12 operations::cat,
13 repo::Repo,
13 repo::Repo,
14 requirements::SPARSE_REQUIREMENT,
14 requirements::SPARSE_REQUIREMENT,
15 utils::{hg_path::HgPath, SliceExt},
15 utils::{hg_path::HgPath, SliceExt},
16 IgnorePattern, PatternError, PatternFileWarning, PatternSyntax, Revision,
16 IgnorePattern, PatternError, PatternFileWarning, PatternSyntax, Revision,
17 NULL_REVISION,
17 NULL_REVISION,
18 };
18 };
19
19
20 /// Command which is triggering the config read
20 /// Command which is triggering the config read
21 #[derive(Copy, Clone, Debug)]
21 #[derive(Copy, Clone, Debug)]
22 pub enum SparseConfigContext {
22 pub enum SparseConfigContext {
23 Sparse,
23 Sparse,
24 Narrow,
24 Narrow,
25 }
25 }
26
26
27 impl DisplayBytes for SparseConfigContext {
27 impl DisplayBytes for SparseConfigContext {
28 fn display_bytes(
28 fn display_bytes(
29 &self,
29 &self,
30 output: &mut dyn std::io::Write,
30 output: &mut dyn std::io::Write,
31 ) -> std::io::Result<()> {
31 ) -> std::io::Result<()> {
32 match self {
32 match self {
33 SparseConfigContext::Sparse => write_bytes!(output, b"sparse"),
33 SparseConfigContext::Sparse => write_bytes!(output, b"sparse"),
34 SparseConfigContext::Narrow => write_bytes!(output, b"narrow"),
34 SparseConfigContext::Narrow => write_bytes!(output, b"narrow"),
35 }
35 }
36 }
36 }
37 }
37 }
38
38
39 /// Possible warnings when reading sparse configuration
39 /// Possible warnings when reading sparse configuration
40 #[derive(Debug, derive_more::From)]
40 #[derive(Debug, derive_more::From)]
41 pub enum SparseWarning {
41 pub enum SparseWarning {
42 /// Warns about improper paths that start with "/"
42 /// Warns about improper paths that start with "/"
43 RootWarning {
43 RootWarning {
44 context: SparseConfigContext,
44 context: SparseConfigContext,
45 line: Vec<u8>,
45 line: Vec<u8>,
46 },
46 },
47 /// Warns about a profile missing from the given changelog revision
47 /// Warns about a profile missing from the given changelog revision
48 ProfileNotFound { profile: Vec<u8>, rev: Revision },
48 ProfileNotFound { profile: Vec<u8>, rev: Revision },
49 #[from]
49 #[from]
50 Pattern(PatternFileWarning),
50 Pattern(PatternFileWarning),
51 }
51 }
52
52
53 /// Parsed sparse config
53 /// Parsed sparse config
54 #[derive(Debug, Default)]
54 #[derive(Debug, Default)]
55 pub struct SparseConfig {
55 pub struct SparseConfig {
56 // Line-separated
56 // Line-separated
57 pub(crate) includes: Vec<u8>,
57 pub(crate) includes: Vec<u8>,
58 // Line-separated
58 // Line-separated
59 pub(crate) excludes: Vec<u8>,
59 pub(crate) excludes: Vec<u8>,
60 pub(crate) profiles: HashSet<Vec<u8>>,
60 pub(crate) profiles: HashSet<Vec<u8>>,
61 pub(crate) warnings: Vec<SparseWarning>,
61 pub(crate) warnings: Vec<SparseWarning>,
62 }
62 }
63
63
64 /// All possible errors when reading sparse/narrow config
64 /// All possible errors when reading sparse/narrow config
65 #[derive(Debug, derive_more::From)]
65 #[derive(Debug, derive_more::From)]
66 pub enum SparseConfigError {
66 pub enum SparseConfigError {
67 IncludesAfterExcludes {
67 IncludesAfterExcludes {
68 context: SparseConfigContext,
68 context: SparseConfigContext,
69 },
69 },
70 EntryOutsideSection {
70 EntryOutsideSection {
71 context: SparseConfigContext,
71 context: SparseConfigContext,
72 line: Vec<u8>,
72 line: Vec<u8>,
73 },
73 },
74 /// Narrow config does not support '%include' directives
74 /// Narrow config does not support '%include' directives
75 IncludesInNarrow,
75 IncludesInNarrow,
76 /// An invalid pattern prefix was given to the narrow spec. Includes the
76 /// An invalid pattern prefix was given to the narrow spec. Includes the
77 /// entire pattern for context.
77 /// entire pattern for context.
78 InvalidNarrowPrefix(Vec<u8>),
78 InvalidNarrowPrefix(Vec<u8>),
79 #[from]
79 #[from]
80 HgError(HgError),
80 HgError(HgError),
81 #[from]
81 #[from]
82 PatternError(PatternError),
82 PatternError(PatternError),
83 }
83 }
84
84
85 /// Parse sparse config file content.
85 /// Parse sparse config file content.
86 pub(crate) fn parse_config(
86 pub(crate) fn parse_config(
87 raw: &[u8],
87 raw: &[u8],
88 context: SparseConfigContext,
88 context: SparseConfigContext,
89 ) -> Result<SparseConfig, SparseConfigError> {
89 ) -> Result<SparseConfig, SparseConfigError> {
90 let mut includes = vec![];
90 let mut includes = vec![];
91 let mut excludes = vec![];
91 let mut excludes = vec![];
92 let mut profiles = HashSet::new();
92 let mut profiles = HashSet::new();
93 let mut warnings = vec![];
93 let mut warnings = vec![];
94
94
95 #[derive(PartialEq, Eq)]
95 #[derive(PartialEq, Eq)]
96 enum Current {
96 enum Current {
97 Includes,
97 Includes,
98 Excludes,
98 Excludes,
99 None,
99 None,
100 }
100 }
101
101
102 let mut current = Current::None;
102 let mut current = Current::None;
103 let mut in_section = false;
103 let mut in_section = false;
104
104
105 for line in raw.split(|c| *c == b'\n') {
105 for line in raw.split(|c| *c == b'\n') {
106 let line = line.trim();
106 let line = line.trim();
107 if line.is_empty() || line[0] == b'#' {
107 if line.is_empty() || line[0] == b'#' {
108 // empty or comment line, skip
108 // empty or comment line, skip
109 continue;
109 continue;
110 }
110 }
111 if line.starts_with(b"%include ") {
111 if line.starts_with(b"%include ") {
112 let profile = line[b"%include ".len()..].trim();
112 let profile = line[b"%include ".len()..].trim();
113 if !profile.is_empty() {
113 if !profile.is_empty() {
114 profiles.insert(profile.into());
114 profiles.insert(profile.into());
115 }
115 }
116 } else if line == b"[include]" {
116 } else if line == b"[include]" {
117 if in_section && current == Current::Includes {
117 if in_section && current == Current::Includes {
118 return Err(SparseConfigError::IncludesAfterExcludes {
118 return Err(SparseConfigError::IncludesAfterExcludes {
119 context,
119 context,
120 });
120 });
121 }
121 }
122 in_section = true;
122 in_section = true;
123 current = Current::Includes;
123 current = Current::Includes;
124 continue;
124 continue;
125 } else if line == b"[exclude]" {
125 } else if line == b"[exclude]" {
126 in_section = true;
126 in_section = true;
127 current = Current::Excludes;
127 current = Current::Excludes;
128 } else {
128 } else {
129 if current == Current::None {
129 if current == Current::None {
130 return Err(SparseConfigError::EntryOutsideSection {
130 return Err(SparseConfigError::EntryOutsideSection {
131 context,
131 context,
132 line: line.into(),
132 line: line.into(),
133 });
133 });
134 }
134 }
135 if line.trim().starts_with(b"/") {
135 if line.trim().starts_with(b"/") {
136 warnings.push(SparseWarning::RootWarning {
136 warnings.push(SparseWarning::RootWarning {
137 context,
137 context,
138 line: line.into(),
138 line: line.into(),
139 });
139 });
140 continue;
140 continue;
141 }
141 }
142 match current {
142 match current {
143 Current::Includes => {
143 Current::Includes => {
144 includes.push(b'\n');
144 includes.push(b'\n');
145 includes.extend(line.iter());
145 includes.extend(line.iter());
146 }
146 }
147 Current::Excludes => {
147 Current::Excludes => {
148 excludes.push(b'\n');
148 excludes.push(b'\n');
149 excludes.extend(line.iter());
149 excludes.extend(line.iter());
150 }
150 }
151 Current::None => unreachable!(),
151 Current::None => unreachable!(),
152 }
152 }
153 }
153 }
154 }
154 }
155
155
156 Ok(SparseConfig {
156 Ok(SparseConfig {
157 includes,
157 includes,
158 excludes,
158 excludes,
159 profiles,
159 profiles,
160 warnings,
160 warnings,
161 })
161 })
162 }
162 }
163
163
164 fn read_temporary_includes(
164 fn read_temporary_includes(
165 repo: &Repo,
165 repo: &Repo,
166 ) -> Result<Vec<Vec<u8>>, SparseConfigError> {
166 ) -> Result<Vec<Vec<u8>>, SparseConfigError> {
167 let raw = repo.hg_vfs().try_read("tempsparse")?.unwrap_or_default();
167 let raw = repo.hg_vfs().try_read("tempsparse")?.unwrap_or_default();
168 if raw.is_empty() {
168 if raw.is_empty() {
169 return Ok(vec![]);
169 return Ok(vec![]);
170 }
170 }
171 Ok(raw.split(|c| *c == b'\n').map(ToOwned::to_owned).collect())
171 Ok(raw.split(|c| *c == b'\n').map(ToOwned::to_owned).collect())
172 }
172 }
173
173
174 /// Obtain sparse checkout patterns for the given revision
174 /// Obtain sparse checkout patterns for the given revision
175 fn patterns_for_rev(
175 fn patterns_for_rev(
176 repo: &Repo,
176 repo: &Repo,
177 rev: Revision,
177 rev: Revision,
178 ) -> Result<Option<SparseConfig>, SparseConfigError> {
178 ) -> Result<Option<SparseConfig>, SparseConfigError> {
179 if !repo.has_sparse() {
179 if !repo.has_sparse() {
180 return Ok(None);
180 return Ok(None);
181 }
181 }
182 let raw = repo.hg_vfs().try_read("sparse")?.unwrap_or_default();
182 let raw = repo.hg_vfs().try_read("sparse")?.unwrap_or_default();
183
183
184 if raw.is_empty() {
184 if raw.is_empty() {
185 return Ok(None);
185 return Ok(None);
186 }
186 }
187
187
188 let mut config = parse_config(&raw, SparseConfigContext::Sparse)?;
188 let mut config = parse_config(&raw, SparseConfigContext::Sparse)?;
189
189
190 if !config.profiles.is_empty() {
190 if !config.profiles.is_empty() {
191 let mut profiles: Vec<Vec<u8>> = config.profiles.into_iter().collect();
191 let mut profiles: Vec<Vec<u8>> = config.profiles.into_iter().collect();
192 let mut visited = HashSet::new();
192 let mut visited = HashSet::new();
193
193
194 while let Some(profile) = profiles.pop() {
194 while let Some(profile) = profiles.pop() {
195 if visited.contains(&profile) {
195 if visited.contains(&profile) {
196 continue;
196 continue;
197 }
197 }
198 visited.insert(profile.to_owned());
198 visited.insert(profile.to_owned());
199
199
200 let output =
200 let output =
201 cat(repo, &rev.to_string(), vec![HgPath::new(&profile)])
201 cat(repo, &rev.to_string(), vec![HgPath::new(&profile)])
202 .map_err(|_| {
202 .map_err(|_| {
203 HgError::corrupted(
203 HgError::corrupted(
204 "dirstate points to non-existent parent node"
204 "dirstate points to non-existent parent node"
205 .to_string(),
205 .to_string(),
206 )
206 )
207 })?;
207 })?;
208 if output.results.is_empty() {
208 if output.results.is_empty() {
209 config.warnings.push(SparseWarning::ProfileNotFound {
209 config.warnings.push(SparseWarning::ProfileNotFound {
210 profile: profile.to_owned(),
210 profile: profile.to_owned(),
211 rev,
211 rev,
212 })
212 })
213 }
213 }
214
214
215 let subconfig = parse_config(
215 let subconfig = parse_config(
216 &output.results[0].1,
216 &output.results[0].1,
217 SparseConfigContext::Sparse,
217 SparseConfigContext::Sparse,
218 )?;
218 )?;
219 if !subconfig.includes.is_empty() {
219 if !subconfig.includes.is_empty() {
220 config.includes.push(b'\n');
220 config.includes.push(b'\n');
221 config.includes.extend(&subconfig.includes);
221 config.includes.extend(&subconfig.includes);
222 }
222 }
223 if !subconfig.includes.is_empty() {
223 if !subconfig.includes.is_empty() {
224 config.includes.push(b'\n');
224 config.includes.push(b'\n');
225 config.excludes.extend(&subconfig.excludes);
225 config.excludes.extend(&subconfig.excludes);
226 }
226 }
227 config.warnings.extend(subconfig.warnings.into_iter());
227 config.warnings.extend(subconfig.warnings.into_iter());
228 profiles.extend(subconfig.profiles.into_iter());
228 profiles.extend(subconfig.profiles.into_iter());
229 }
229 }
230
230
231 config.profiles = visited;
231 config.profiles = visited;
232 }
232 }
233
233
234 if !config.includes.is_empty() {
234 if !config.includes.is_empty() {
235 config.includes.extend(b"\n.hg*");
235 config.includes.extend(b"\n.hg*");
236 }
236 }
237
237
238 Ok(Some(config))
238 Ok(Some(config))
239 }
239 }
240
240
241 /// Obtain a matcher for sparse working directories.
241 /// Obtain a matcher for sparse working directories.
242 pub fn matcher(
242 pub fn matcher(
243 repo: &Repo,
243 repo: &Repo,
244 ) -> Result<(Box<dyn Matcher + Sync>, Vec<SparseWarning>), SparseConfigError> {
244 ) -> Result<(Box<dyn Matcher + Sync>, Vec<SparseWarning>), SparseConfigError> {
245 let mut warnings = vec![];
245 let mut warnings = vec![];
246 if !repo.requirements().contains(SPARSE_REQUIREMENT) {
246 if !repo.requirements().contains(SPARSE_REQUIREMENT) {
247 return Ok((Box::new(AlwaysMatcher), warnings));
247 return Ok((Box::new(AlwaysMatcher), warnings));
248 }
248 }
249
249
250 let parents = repo.dirstate_parents()?;
250 let parents = repo.dirstate_parents()?;
251 let mut revs = vec![];
251 let mut revs = vec![];
252 let p1_rev =
252 let p1_rev =
253 repo.changelog()?
253 repo.changelog()?
254 .rev_from_node(parents.p1.into())
254 .rev_from_node(parents.p1.into())
255 .map_err(|_| {
255 .map_err(|_| {
256 HgError::corrupted(
256 HgError::corrupted(
257 "dirstate points to non-existent parent node".to_string(),
257 "dirstate points to non-existent parent node".to_string(),
258 )
258 )
259 })?;
259 })?;
260 if p1_rev != NULL_REVISION {
260 if p1_rev != NULL_REVISION {
261 revs.push(p1_rev)
261 revs.push(p1_rev)
262 }
262 }
263 let p2_rev =
263 let p2_rev =
264 repo.changelog()?
264 repo.changelog()?
265 .rev_from_node(parents.p2.into())
265 .rev_from_node(parents.p2.into())
266 .map_err(|_| {
266 .map_err(|_| {
267 HgError::corrupted(
267 HgError::corrupted(
268 "dirstate points to non-existent parent node".to_string(),
268 "dirstate points to non-existent parent node".to_string(),
269 )
269 )
270 })?;
270 })?;
271 if p2_rev != NULL_REVISION {
271 if p2_rev != NULL_REVISION {
272 revs.push(p2_rev)
272 revs.push(p2_rev)
273 }
273 }
274 let mut matchers = vec![];
274 let mut matchers = vec![];
275
275
276 for rev in revs.iter() {
276 for rev in revs.iter() {
277 let config = patterns_for_rev(repo, *rev);
277 let config = patterns_for_rev(repo, *rev);
278 if let Ok(Some(config)) = config {
278 if let Ok(Some(config)) = config {
279 warnings.extend(config.warnings);
279 warnings.extend(config.warnings);
280 let mut m: Box<dyn Matcher + Sync> = Box::new(AlwaysMatcher);
280 let mut m: Box<dyn Matcher + Sync> = Box::new(AlwaysMatcher);
281 if !config.includes.is_empty() {
281 if !config.includes.is_empty() {
282 let (patterns, subwarnings) = parse_pattern_file_contents(
282 let (patterns, subwarnings) = parse_pattern_file_contents(
283 &config.includes,
283 &config.includes,
284 Path::new(""),
284 Path::new(""),
285 Some(b"glob:".as_ref()),
285 Some(PatternSyntax::Glob),
286 false,
286 false,
287 false,
287 )?;
288 )?;
288 warnings.extend(subwarnings.into_iter().map(From::from));
289 warnings.extend(subwarnings.into_iter().map(From::from));
289 m = Box::new(IncludeMatcher::new(patterns)?);
290 m = Box::new(IncludeMatcher::new(patterns)?);
290 }
291 }
291 if !config.excludes.is_empty() {
292 if !config.excludes.is_empty() {
292 let (patterns, subwarnings) = parse_pattern_file_contents(
293 let (patterns, subwarnings) = parse_pattern_file_contents(
293 &config.excludes,
294 &config.excludes,
294 Path::new(""),
295 Path::new(""),
295 Some(b"glob:".as_ref()),
296 Some(PatternSyntax::Glob),
297 false,
296 false,
298 false,
297 )?;
299 )?;
298 warnings.extend(subwarnings.into_iter().map(From::from));
300 warnings.extend(subwarnings.into_iter().map(From::from));
299 m = Box::new(DifferenceMatcher::new(
301 m = Box::new(DifferenceMatcher::new(
300 m,
302 m,
301 Box::new(IncludeMatcher::new(patterns)?),
303 Box::new(IncludeMatcher::new(patterns)?),
302 ));
304 ));
303 }
305 }
304 matchers.push(m);
306 matchers.push(m);
305 }
307 }
306 }
308 }
307 let result: Box<dyn Matcher + Sync> = match matchers.len() {
309 let result: Box<dyn Matcher + Sync> = match matchers.len() {
308 0 => Box::new(AlwaysMatcher),
310 0 => Box::new(AlwaysMatcher),
309 1 => matchers.pop().expect("1 is equal to 0"),
311 1 => matchers.pop().expect("1 is equal to 0"),
310 _ => Box::new(UnionMatcher::new(matchers)),
312 _ => Box::new(UnionMatcher::new(matchers)),
311 };
313 };
312
314
313 let matcher =
315 let matcher =
314 force_include_matcher(result, &read_temporary_includes(repo)?)?;
316 force_include_matcher(result, &read_temporary_includes(repo)?)?;
315 Ok((matcher, warnings))
317 Ok((matcher, warnings))
316 }
318 }
317
319
318 /// Returns a matcher that returns true for any of the forced includes before
320 /// Returns a matcher that returns true for any of the forced includes before
319 /// testing against the actual matcher
321 /// testing against the actual matcher
320 fn force_include_matcher(
322 fn force_include_matcher(
321 result: Box<dyn Matcher + Sync>,
323 result: Box<dyn Matcher + Sync>,
322 temp_includes: &[Vec<u8>],
324 temp_includes: &[Vec<u8>],
323 ) -> Result<Box<dyn Matcher + Sync>, PatternError> {
325 ) -> Result<Box<dyn Matcher + Sync>, PatternError> {
324 if temp_includes.is_empty() {
326 if temp_includes.is_empty() {
325 return Ok(result);
327 return Ok(result);
326 }
328 }
327 let forced_include_matcher = IncludeMatcher::new(
329 let forced_include_matcher = IncludeMatcher::new(
328 temp_includes
330 temp_includes
329 .iter()
331 .iter()
330 .map(|include| {
332 .map(|include| {
331 IgnorePattern::new(PatternSyntax::Path, include, Path::new(""))
333 IgnorePattern::new(PatternSyntax::Path, include, Path::new(""))
332 })
334 })
333 .collect(),
335 .collect(),
334 )?;
336 )?;
335 Ok(Box::new(UnionMatcher::new(vec![
337 Ok(Box::new(UnionMatcher::new(vec![
336 Box::new(forced_include_matcher),
338 Box::new(forced_include_matcher),
337 result,
339 result,
338 ])))
340 ])))
339 }
341 }
General Comments 0
You need to be logged in to leave comments. Login now