##// END OF EJS Templates
rust-filepatterns: add support for `include` and `subinclude` patterns...
Raphaël Gomès -
r44785:2fe89bec default
parent child Browse files
Show More
@@ -1,534 +1,656
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::{utils::SliceExt, FastHashMap, PatternError};
10 use crate::{
11 utils::{
12 files::{canonical_path, get_bytes_from_path, get_path_from_bytes},
13 hg_path::{path_to_hg_path_buf, HgPathBuf, HgPathError},
14 SliceExt,
15 },
16 FastHashMap, PatternError,
17 };
11 use lazy_static::lazy_static;
18 use lazy_static::lazy_static;
12 use regex::bytes::{NoExpand, Regex};
19 use regex::bytes::{NoExpand, Regex};
13 use std::fs::File;
20 use std::fs::File;
14 use std::io::Read;
21 use std::io::Read;
22 use std::ops::Deref;
15 use std::path::{Path, PathBuf};
23 use std::path::{Path, PathBuf};
16 use std::vec::Vec;
24 use std::vec::Vec;
17
25
18 lazy_static! {
26 lazy_static! {
19 static ref RE_ESCAPE: Vec<Vec<u8>> = {
27 static ref RE_ESCAPE: Vec<Vec<u8>> = {
20 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
28 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
21 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
29 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
22 for byte in to_escape {
30 for byte in to_escape {
23 v[*byte as usize].insert(0, b'\\');
31 v[*byte as usize].insert(0, b'\\');
24 }
32 }
25 v
33 v
26 };
34 };
27 }
35 }
28
36
29 /// These are matched in order
37 /// These are matched in order
30 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
38 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
31 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
39 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
32
40
33 /// Appended to the regexp of globs
41 /// Appended to the regexp of globs
34 const GLOB_SUFFIX: &[u8; 7] = b"(?:/|$)";
42 const GLOB_SUFFIX: &[u8; 7] = b"(?:/|$)";
35
43
36 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
44 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
37 pub enum PatternSyntax {
45 pub enum PatternSyntax {
38 /// A regular expression
46 /// A regular expression
39 Regexp,
47 Regexp,
40 /// Glob that matches at the front of the path
48 /// Glob that matches at the front of the path
41 RootGlob,
49 RootGlob,
42 /// Glob that matches at any suffix of the path (still anchored at
50 /// Glob that matches at any suffix of the path (still anchored at
43 /// slashes)
51 /// slashes)
44 Glob,
52 Glob,
45 /// a path relative to repository root, which is matched recursively
53 /// a path relative to repository root, which is matched recursively
46 Path,
54 Path,
47 /// A path relative to cwd
55 /// A path relative to cwd
48 RelPath,
56 RelPath,
49 /// an unrooted glob (*.rs matches Rust files in all dirs)
57 /// an unrooted glob (*.rs matches Rust files in all dirs)
50 RelGlob,
58 RelGlob,
51 /// A regexp that needn't match the start of a name
59 /// A regexp that needn't match the start of a name
52 RelRegexp,
60 RelRegexp,
53 /// A path relative to repository root, which is matched non-recursively
61 /// A path relative to repository root, which is matched non-recursively
54 /// (will not match subdirectories)
62 /// (will not match subdirectories)
55 RootFiles,
63 RootFiles,
64 /// A file of patterns to read and include
65 Include,
66 /// A file of patterns to match against files under the same directory
67 SubInclude,
56 }
68 }
57
69
58 /// Transforms a glob pattern into a regex
70 /// Transforms a glob pattern into a regex
59 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
71 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
60 let mut input = pat;
72 let mut input = pat;
61 let mut res: Vec<u8> = vec![];
73 let mut res: Vec<u8> = vec![];
62 let mut group_depth = 0;
74 let mut group_depth = 0;
63
75
64 while let Some((c, rest)) = input.split_first() {
76 while let Some((c, rest)) = input.split_first() {
65 input = rest;
77 input = rest;
66
78
67 match c {
79 match c {
68 b'*' => {
80 b'*' => {
69 for (source, repl) in GLOB_REPLACEMENTS {
81 for (source, repl) in GLOB_REPLACEMENTS {
70 if let Some(rest) = input.drop_prefix(source) {
82 if let Some(rest) = input.drop_prefix(source) {
71 input = rest;
83 input = rest;
72 res.extend(*repl);
84 res.extend(*repl);
73 break;
85 break;
74 }
86 }
75 }
87 }
76 }
88 }
77 b'?' => res.extend(b"."),
89 b'?' => res.extend(b"."),
78 b'[' => {
90 b'[' => {
79 match input.iter().skip(1).position(|b| *b == b']') {
91 match input.iter().skip(1).position(|b| *b == b']') {
80 None => res.extend(b"\\["),
92 None => res.extend(b"\\["),
81 Some(end) => {
93 Some(end) => {
82 // Account for the one we skipped
94 // Account for the one we skipped
83 let end = end + 1;
95 let end = end + 1;
84
96
85 res.extend(b"[");
97 res.extend(b"[");
86
98
87 for (i, b) in input[..end].iter().enumerate() {
99 for (i, b) in input[..end].iter().enumerate() {
88 if *b == b'!' && i == 0 {
100 if *b == b'!' && i == 0 {
89 res.extend(b"^")
101 res.extend(b"^")
90 } else if *b == b'^' && i == 0 {
102 } else if *b == b'^' && i == 0 {
91 res.extend(b"\\^")
103 res.extend(b"\\^")
92 } else if *b == b'\\' {
104 } else if *b == b'\\' {
93 res.extend(b"\\\\")
105 res.extend(b"\\\\")
94 } else {
106 } else {
95 res.push(*b)
107 res.push(*b)
96 }
108 }
97 }
109 }
98 res.extend(b"]");
110 res.extend(b"]");
99 input = &input[end + 1..];
111 input = &input[end + 1..];
100 }
112 }
101 }
113 }
102 }
114 }
103 b'{' => {
115 b'{' => {
104 group_depth += 1;
116 group_depth += 1;
105 res.extend(b"(?:")
117 res.extend(b"(?:")
106 }
118 }
107 b'}' if group_depth > 0 => {
119 b'}' if group_depth > 0 => {
108 group_depth -= 1;
120 group_depth -= 1;
109 res.extend(b")");
121 res.extend(b")");
110 }
122 }
111 b',' if group_depth > 0 => res.extend(b"|"),
123 b',' if group_depth > 0 => res.extend(b"|"),
112 b'\\' => {
124 b'\\' => {
113 let c = {
125 let c = {
114 if let Some((c, rest)) = input.split_first() {
126 if let Some((c, rest)) = input.split_first() {
115 input = rest;
127 input = rest;
116 c
128 c
117 } else {
129 } else {
118 c
130 c
119 }
131 }
120 };
132 };
121 res.extend(&RE_ESCAPE[*c as usize])
133 res.extend(&RE_ESCAPE[*c as usize])
122 }
134 }
123 _ => res.extend(&RE_ESCAPE[*c as usize]),
135 _ => res.extend(&RE_ESCAPE[*c as usize]),
124 }
136 }
125 }
137 }
126 res
138 res
127 }
139 }
128
140
129 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
141 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
130 pattern
142 pattern
131 .iter()
143 .iter()
132 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
144 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
133 .collect()
145 .collect()
134 }
146 }
135
147
136 pub fn parse_pattern_syntax(
148 pub fn parse_pattern_syntax(
137 kind: &[u8],
149 kind: &[u8],
138 ) -> Result<PatternSyntax, PatternError> {
150 ) -> Result<PatternSyntax, PatternError> {
139 match kind {
151 match kind {
140 b"re:" => Ok(PatternSyntax::Regexp),
152 b"re:" => Ok(PatternSyntax::Regexp),
141 b"path:" => Ok(PatternSyntax::Path),
153 b"path:" => Ok(PatternSyntax::Path),
142 b"relpath:" => Ok(PatternSyntax::RelPath),
154 b"relpath:" => Ok(PatternSyntax::RelPath),
143 b"rootfilesin:" => Ok(PatternSyntax::RootFiles),
155 b"rootfilesin:" => Ok(PatternSyntax::RootFiles),
144 b"relglob:" => Ok(PatternSyntax::RelGlob),
156 b"relglob:" => Ok(PatternSyntax::RelGlob),
145 b"relre:" => Ok(PatternSyntax::RelRegexp),
157 b"relre:" => Ok(PatternSyntax::RelRegexp),
146 b"glob:" => Ok(PatternSyntax::Glob),
158 b"glob:" => Ok(PatternSyntax::Glob),
147 b"rootglob:" => Ok(PatternSyntax::RootGlob),
159 b"rootglob:" => Ok(PatternSyntax::RootGlob),
160 b"include:" => Ok(PatternSyntax::Include),
161 b"subinclude:" => Ok(PatternSyntax::SubInclude),
148 _ => Err(PatternError::UnsupportedSyntax(
162 _ => Err(PatternError::UnsupportedSyntax(
149 String::from_utf8_lossy(kind).to_string(),
163 String::from_utf8_lossy(kind).to_string(),
150 )),
164 )),
151 }
165 }
152 }
166 }
153
167
154 /// Builds the regex that corresponds to the given pattern.
168 /// Builds the regex that corresponds to the given pattern.
155 /// If within a `syntax: regexp` context, returns the pattern,
169 /// If within a `syntax: regexp` context, returns the pattern,
156 /// otherwise, returns the corresponding regex.
170 /// otherwise, returns the corresponding regex.
157 fn _build_single_regex(entry: &IgnorePattern) -> Vec<u8> {
171 fn _build_single_regex(entry: &IgnorePattern) -> Vec<u8> {
158 let IgnorePattern {
172 let IgnorePattern {
159 syntax, pattern, ..
173 syntax, pattern, ..
160 } = entry;
174 } = entry;
161 if pattern.is_empty() {
175 if pattern.is_empty() {
162 return vec![];
176 return vec![];
163 }
177 }
164 match syntax {
178 match syntax {
165 PatternSyntax::Regexp => pattern.to_owned(),
179 PatternSyntax::Regexp => pattern.to_owned(),
166 PatternSyntax::RelRegexp => {
180 PatternSyntax::RelRegexp => {
167 if pattern[0] == b'^' {
181 if pattern[0] == b'^' {
168 return pattern.to_owned();
182 return pattern.to_owned();
169 }
183 }
170 [&b".*"[..], pattern].concat()
184 [&b".*"[..], pattern].concat()
171 }
185 }
172 PatternSyntax::Path | PatternSyntax::RelPath => {
186 PatternSyntax::Path | PatternSyntax::RelPath => {
173 if pattern == b"." {
187 if pattern == b"." {
174 return vec![];
188 return vec![];
175 }
189 }
176 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
190 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
177 }
191 }
178 PatternSyntax::RootFiles => {
192 PatternSyntax::RootFiles => {
179 let mut res = if pattern == b"." {
193 let mut res = if pattern == b"." {
180 vec![]
194 vec![]
181 } else {
195 } else {
182 // Pattern is a directory name.
196 // Pattern is a directory name.
183 [escape_pattern(pattern).as_slice(), b"/"].concat()
197 [escape_pattern(pattern).as_slice(), b"/"].concat()
184 };
198 };
185
199
186 // Anything after the pattern must be a non-directory.
200 // Anything after the pattern must be a non-directory.
187 res.extend(b"[^/]+$");
201 res.extend(b"[^/]+$");
188 res
202 res
189 }
203 }
190 PatternSyntax::RelGlob => {
204 PatternSyntax::RelGlob => {
191 let glob_re = glob_to_re(pattern);
205 let glob_re = glob_to_re(pattern);
192 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
206 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
193 [b".*", rest, GLOB_SUFFIX].concat()
207 [b".*", rest, GLOB_SUFFIX].concat()
194 } else {
208 } else {
195 [b"(?:|.*/)", glob_re.as_slice(), GLOB_SUFFIX].concat()
209 [b"(?:|.*/)", glob_re.as_slice(), GLOB_SUFFIX].concat()
196 }
210 }
197 }
211 }
198 PatternSyntax::Glob | PatternSyntax::RootGlob => {
212 PatternSyntax::Glob | PatternSyntax::RootGlob => {
199 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
213 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
200 }
214 }
215 PatternSyntax::Include | PatternSyntax::SubInclude => unreachable!(),
201 }
216 }
202 }
217 }
203
218
204 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
219 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
205 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
220 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
206
221
207 /// TODO support other platforms
222 /// TODO support other platforms
208 #[cfg(unix)]
223 #[cfg(unix)]
209 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
224 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
210 if bytes.is_empty() {
225 if bytes.is_empty() {
211 return b".".to_vec();
226 return b".".to_vec();
212 }
227 }
213 let sep = b'/';
228 let sep = b'/';
214
229
215 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
230 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
216 if initial_slashes > 2 {
231 if initial_slashes > 2 {
217 // POSIX allows one or two initial slashes, but treats three or more
232 // POSIX allows one or two initial slashes, but treats three or more
218 // as single slash.
233 // as single slash.
219 initial_slashes = 1;
234 initial_slashes = 1;
220 }
235 }
221 let components = bytes
236 let components = bytes
222 .split(|b| *b == sep)
237 .split(|b| *b == sep)
223 .filter(|c| !(c.is_empty() || c == b"."))
238 .filter(|c| !(c.is_empty() || c == b"."))
224 .fold(vec![], |mut acc, component| {
239 .fold(vec![], |mut acc, component| {
225 if component != b".."
240 if component != b".."
226 || (initial_slashes == 0 && acc.is_empty())
241 || (initial_slashes == 0 && acc.is_empty())
227 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
242 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
228 {
243 {
229 acc.push(component)
244 acc.push(component)
230 } else if !acc.is_empty() {
245 } else if !acc.is_empty() {
231 acc.pop();
246 acc.pop();
232 }
247 }
233 acc
248 acc
234 });
249 });
235 let mut new_bytes = components.join(&sep);
250 let mut new_bytes = components.join(&sep);
236
251
237 if initial_slashes > 0 {
252 if initial_slashes > 0 {
238 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
253 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
239 buf.extend(new_bytes);
254 buf.extend(new_bytes);
240 new_bytes = buf;
255 new_bytes = buf;
241 }
256 }
242 if new_bytes.is_empty() {
257 if new_bytes.is_empty() {
243 b".".to_vec()
258 b".".to_vec()
244 } else {
259 } else {
245 new_bytes
260 new_bytes
246 }
261 }
247 }
262 }
248
263
249 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
264 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
250 /// that don't need to be transformed into a regex.
265 /// that don't need to be transformed into a regex.
251 pub fn build_single_regex(
266 pub fn build_single_regex(
252 entry: &IgnorePattern,
267 entry: &IgnorePattern,
253 ) -> Result<Vec<u8>, PatternError> {
268 ) -> Result<Vec<u8>, PatternError> {
254 let IgnorePattern {
269 let IgnorePattern {
255 pattern, syntax, ..
270 pattern, syntax, ..
256 } = entry;
271 } = entry;
257 let pattern = match syntax {
272 let pattern = match syntax {
258 PatternSyntax::RootGlob
273 PatternSyntax::RootGlob
259 | PatternSyntax::Path
274 | PatternSyntax::Path
260 | PatternSyntax::RelGlob
275 | PatternSyntax::RelGlob
261 | PatternSyntax::RootFiles => normalize_path_bytes(&pattern),
276 | PatternSyntax::RootFiles => normalize_path_bytes(&pattern),
277 PatternSyntax::Include | PatternSyntax::SubInclude => {
278 return Err(PatternError::NonRegexPattern(entry.clone()))
279 }
262 _ => pattern.to_owned(),
280 _ => pattern.to_owned(),
263 };
281 };
264 if *syntax == PatternSyntax::RootGlob
282 if *syntax == PatternSyntax::RootGlob
265 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b))
283 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b))
266 {
284 {
267 let mut escaped = escape_pattern(&pattern);
285 let mut escaped = escape_pattern(&pattern);
268 escaped.extend(GLOB_SUFFIX);
286 escaped.extend(GLOB_SUFFIX);
269 Ok(escaped)
287 Ok(escaped)
270 } else {
288 } else {
271 let mut entry = entry.clone();
289 let mut entry = entry.clone();
272 entry.pattern = pattern;
290 entry.pattern = pattern;
273 Ok(_build_single_regex(&entry))
291 Ok(_build_single_regex(&entry))
274 }
292 }
275 }
293 }
276
294
277 lazy_static! {
295 lazy_static! {
278 static ref SYNTAXES: FastHashMap<&'static [u8], &'static [u8]> = {
296 static ref SYNTAXES: FastHashMap<&'static [u8], &'static [u8]> = {
279 let mut m = FastHashMap::default();
297 let mut m = FastHashMap::default();
280
298
281 m.insert(b"re".as_ref(), b"relre:".as_ref());
299 m.insert(b"re".as_ref(), b"relre:".as_ref());
282 m.insert(b"regexp".as_ref(), b"relre:".as_ref());
300 m.insert(b"regexp".as_ref(), b"relre:".as_ref());
283 m.insert(b"glob".as_ref(), b"relglob:".as_ref());
301 m.insert(b"glob".as_ref(), b"relglob:".as_ref());
284 m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref());
302 m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref());
285 m.insert(b"include".as_ref(), b"include:".as_ref());
303 m.insert(b"include".as_ref(), b"include:".as_ref());
286 m.insert(b"subinclude".as_ref(), b"subinclude:".as_ref());
304 m.insert(b"subinclude".as_ref(), b"subinclude:".as_ref());
287 m
305 m
288 };
306 };
289 }
307 }
290
308
291 #[derive(Debug)]
309 #[derive(Debug)]
292 pub enum PatternFileWarning {
310 pub enum PatternFileWarning {
293 /// (file path, syntax bytes)
311 /// (file path, syntax bytes)
294 InvalidSyntax(PathBuf, Vec<u8>),
312 InvalidSyntax(PathBuf, Vec<u8>),
295 /// File path
313 /// File path
296 NoSuchFile(PathBuf),
314 NoSuchFile(PathBuf),
297 }
315 }
298
316
299 pub fn parse_pattern_file_contents<P: AsRef<Path>>(
317 pub fn parse_pattern_file_contents<P: AsRef<Path>>(
300 lines: &[u8],
318 lines: &[u8],
301 file_path: P,
319 file_path: P,
302 warn: bool,
320 warn: bool,
303 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
321 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
304 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
322 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
305 let comment_escape_regex = Regex::new(r"\\#").unwrap();
323 let comment_escape_regex = Regex::new(r"\\#").unwrap();
306 let mut inputs: Vec<IgnorePattern> = vec![];
324 let mut inputs: Vec<IgnorePattern> = vec![];
307 let mut warnings: Vec<PatternFileWarning> = vec![];
325 let mut warnings: Vec<PatternFileWarning> = vec![];
308
326
309 let mut current_syntax = b"relre:".as_ref();
327 let mut current_syntax = b"relre:".as_ref();
310
328
311 for (line_number, mut line) in lines.split(|c| *c == b'\n').enumerate() {
329 for (line_number, mut line) in lines.split(|c| *c == b'\n').enumerate() {
312 let line_number = line_number + 1;
330 let line_number = line_number + 1;
313
331
314 let line_buf;
332 let line_buf;
315 if line.contains(&b'#') {
333 if line.contains(&b'#') {
316 if let Some(cap) = comment_regex.captures(line) {
334 if let Some(cap) = comment_regex.captures(line) {
317 line = &line[..cap.get(1).unwrap().end()]
335 line = &line[..cap.get(1).unwrap().end()]
318 }
336 }
319 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
337 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
320 line = &line_buf;
338 line = &line_buf;
321 }
339 }
322
340
323 let mut line = line.trim_end();
341 let mut line = line.trim_end();
324
342
325 if line.is_empty() {
343 if line.is_empty() {
326 continue;
344 continue;
327 }
345 }
328
346
329 if let Some(syntax) = line.drop_prefix(b"syntax:") {
347 if let Some(syntax) = line.drop_prefix(b"syntax:") {
330 let syntax = syntax.trim();
348 let syntax = syntax.trim();
331
349
332 if let Some(rel_syntax) = SYNTAXES.get(syntax) {
350 if let Some(rel_syntax) = SYNTAXES.get(syntax) {
333 current_syntax = rel_syntax;
351 current_syntax = rel_syntax;
334 } else if warn {
352 } else if warn {
335 warnings.push(PatternFileWarning::InvalidSyntax(
353 warnings.push(PatternFileWarning::InvalidSyntax(
336 file_path.as_ref().to_owned(),
354 file_path.as_ref().to_owned(),
337 syntax.to_owned(),
355 syntax.to_owned(),
338 ));
356 ));
339 }
357 }
340 continue;
358 continue;
341 }
359 }
342
360
343 let mut line_syntax: &[u8] = &current_syntax;
361 let mut line_syntax: &[u8] = &current_syntax;
344
362
345 for (s, rels) in SYNTAXES.iter() {
363 for (s, rels) in SYNTAXES.iter() {
346 if let Some(rest) = line.drop_prefix(rels) {
364 if let Some(rest) = line.drop_prefix(rels) {
347 line_syntax = rels;
365 line_syntax = rels;
348 line = rest;
366 line = rest;
349 break;
367 break;
350 }
368 }
351 if let Some(rest) = line.drop_prefix(&[s, &b":"[..]].concat()) {
369 if let Some(rest) = line.drop_prefix(&[s, &b":"[..]].concat()) {
352 line_syntax = rels;
370 line_syntax = rels;
353 line = rest;
371 line = rest;
354 break;
372 break;
355 }
373 }
356 }
374 }
357
375
358 inputs.push(IgnorePattern::new(
376 inputs.push(IgnorePattern::new(
359 parse_pattern_syntax(&line_syntax).map_err(|e| match e {
377 parse_pattern_syntax(&line_syntax).map_err(|e| match e {
360 PatternError::UnsupportedSyntax(syntax) => {
378 PatternError::UnsupportedSyntax(syntax) => {
361 PatternError::UnsupportedSyntaxInFile(
379 PatternError::UnsupportedSyntaxInFile(
362 syntax,
380 syntax,
363 file_path.as_ref().to_string_lossy().into(),
381 file_path.as_ref().to_string_lossy().into(),
364 line_number,
382 line_number,
365 )
383 )
366 }
384 }
367 _ => e,
385 _ => e,
368 })?,
386 })?,
369 &line,
387 &line,
370 &file_path,
388 &file_path,
371 ));
389 ));
372 }
390 }
373 Ok((inputs, warnings))
391 Ok((inputs, warnings))
374 }
392 }
375
393
376 pub fn read_pattern_file<P: AsRef<Path>>(
394 pub fn read_pattern_file<P: AsRef<Path>>(
377 file_path: P,
395 file_path: P,
378 warn: bool,
396 warn: bool,
379 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
397 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
380 let mut f = match File::open(file_path.as_ref()) {
398 let mut f = match File::open(file_path.as_ref()) {
381 Ok(f) => Ok(f),
399 Ok(f) => Ok(f),
382 Err(e) => match e.kind() {
400 Err(e) => match e.kind() {
383 std::io::ErrorKind::NotFound => {
401 std::io::ErrorKind::NotFound => {
384 return Ok((
402 return Ok((
385 vec![],
403 vec![],
386 vec![PatternFileWarning::NoSuchFile(
404 vec![PatternFileWarning::NoSuchFile(
387 file_path.as_ref().to_owned(),
405 file_path.as_ref().to_owned(),
388 )],
406 )],
389 ))
407 ))
390 }
408 }
391 _ => Err(e),
409 _ => Err(e),
392 },
410 },
393 }?;
411 }?;
394 let mut contents = Vec::new();
412 let mut contents = Vec::new();
395
413
396 f.read_to_end(&mut contents)?;
414 f.read_to_end(&mut contents)?;
397
415
398 Ok(parse_pattern_file_contents(&contents, file_path, warn)?)
416 Ok(parse_pattern_file_contents(&contents, file_path, warn)?)
399 }
417 }
400
418
401 /// Represents an entry in an "ignore" file.
419 /// Represents an entry in an "ignore" file.
402 #[derive(Debug, Eq, PartialEq, Clone)]
420 #[derive(Debug, Eq, PartialEq, Clone)]
403 pub struct IgnorePattern {
421 pub struct IgnorePattern {
404 pub syntax: PatternSyntax,
422 pub syntax: PatternSyntax,
405 pub pattern: Vec<u8>,
423 pub pattern: Vec<u8>,
406 pub source: PathBuf,
424 pub source: PathBuf,
407 }
425 }
408
426
409 impl IgnorePattern {
427 impl IgnorePattern {
410 pub fn new(
428 pub fn new(
411 syntax: PatternSyntax,
429 syntax: PatternSyntax,
412 pattern: &[u8],
430 pattern: &[u8],
413 source: impl AsRef<Path>,
431 source: impl AsRef<Path>,
414 ) -> Self {
432 ) -> Self {
415 Self {
433 Self {
416 syntax,
434 syntax,
417 pattern: pattern.to_owned(),
435 pattern: pattern.to_owned(),
418 source: source.as_ref().to_owned(),
436 source: source.as_ref().to_owned(),
419 }
437 }
420 }
438 }
421 }
439 }
422
440
423 pub type PatternResult<T> = Result<T, PatternError>;
441 pub type PatternResult<T> = Result<T, PatternError>;
424
442
443 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
444 /// patterns.
445 ///
446 /// `subinclude:` is not treated as a special pattern here: unraveling them
447 /// needs to occur in the "ignore" phase.
448 pub fn get_patterns_from_file(
449 pattern_file: impl AsRef<Path>,
450 root_dir: impl AsRef<Path>,
451 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
452 let (patterns, mut warnings) = read_pattern_file(&pattern_file, true)?;
453 let patterns = patterns
454 .into_iter()
455 .flat_map(|entry| -> PatternResult<_> {
456 let IgnorePattern {
457 syntax,
458 pattern,
459 source: _,
460 } = &entry;
461 Ok(match syntax {
462 PatternSyntax::Include => {
463 let inner_include =
464 root_dir.as_ref().join(get_path_from_bytes(&pattern));
465 let (inner_pats, inner_warnings) = get_patterns_from_file(
466 &inner_include,
467 root_dir.as_ref(),
468 )?;
469 warnings.extend(inner_warnings);
470 inner_pats
471 }
472 _ => vec![entry],
473 })
474 })
475 .flatten()
476 .collect();
477
478 Ok((patterns, warnings))
479 }
480
481 /// Holds all the information needed to handle a `subinclude:` pattern.
482 pub struct SubInclude {
483 /// Will be used for repository (hg) paths that start with this prefix.
484 /// It is relative to the current working directory, so comparing against
485 /// repository paths is painless.
486 pub prefix: HgPathBuf,
487 /// The file itself, containing the patterns
488 pub path: PathBuf,
489 /// Folder in the filesystem where this it applies
490 pub root: PathBuf,
491 }
492
493 impl SubInclude {
494 pub fn new(
495 root_dir: impl AsRef<Path>,
496 pattern: &[u8],
497 source: impl AsRef<Path>,
498 ) -> Result<SubInclude, HgPathError> {
499 let normalized_source =
500 normalize_path_bytes(&get_bytes_from_path(source));
501
502 let source_root = get_path_from_bytes(&normalized_source);
503 let source_root = source_root.parent().unwrap_or(source_root.deref());
504
505 let path = source_root.join(get_path_from_bytes(pattern));
506 let new_root = path.parent().unwrap_or(path.deref());
507
508 let prefix = canonical_path(&root_dir, &root_dir, new_root)?;
509
510 Ok(Self {
511 prefix: path_to_hg_path_buf(prefix).and_then(|mut p| {
512 if !p.is_empty() {
513 p.push(b'/');
514 }
515 Ok(p)
516 })?,
517 path: path.to_owned(),
518 root: new_root.to_owned(),
519 })
520 }
521 }
522
523 /// Separate and pre-process subincludes from other patterns for the "ignore"
524 /// phase.
525 pub fn filter_subincludes(
526 ignore_patterns: &[IgnorePattern],
527 root_dir: impl AsRef<Path>,
528 ) -> Result<(Vec<SubInclude>, Vec<&IgnorePattern>), HgPathError> {
529 let mut subincludes = vec![];
530 let mut others = vec![];
531
532 for ignore_pattern in ignore_patterns.iter() {
533 let IgnorePattern {
534 syntax,
535 pattern,
536 source,
537 } = ignore_pattern;
538 if *syntax == PatternSyntax::SubInclude {
539 subincludes.push(SubInclude::new(&root_dir, pattern, &source)?);
540 } else {
541 others.push(ignore_pattern)
542 }
543 }
544 Ok((subincludes, others))
545 }
546
425 #[cfg(test)]
547 #[cfg(test)]
426 mod tests {
548 mod tests {
427 use super::*;
549 use super::*;
428 use pretty_assertions::assert_eq;
550 use pretty_assertions::assert_eq;
429
551
430 #[test]
552 #[test]
431 fn escape_pattern_test() {
553 fn escape_pattern_test() {
432 let untouched =
554 let untouched =
433 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
555 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
434 assert_eq!(escape_pattern(untouched), untouched.to_vec());
556 assert_eq!(escape_pattern(untouched), untouched.to_vec());
435 // All escape codes
557 // All escape codes
436 assert_eq!(
558 assert_eq!(
437 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
559 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
438 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
560 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
439 .to_vec()
561 .to_vec()
440 );
562 );
441 }
563 }
442
564
443 #[test]
565 #[test]
444 fn glob_test() {
566 fn glob_test() {
445 assert_eq!(glob_to_re(br#"?"#), br#"."#);
567 assert_eq!(glob_to_re(br#"?"#), br#"."#);
446 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
568 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
447 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
569 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
448 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
570 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
449 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
571 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
450 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
572 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
451 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
573 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
452 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
574 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
453 }
575 }
454
576
455 #[test]
577 #[test]
456 fn test_parse_pattern_file_contents() {
578 fn test_parse_pattern_file_contents() {
457 let lines = b"syntax: glob\n*.elc";
579 let lines = b"syntax: glob\n*.elc";
458
580
459 assert_eq!(
581 assert_eq!(
460 parse_pattern_file_contents(lines, Path::new("file_path"), false)
582 parse_pattern_file_contents(lines, Path::new("file_path"), false)
461 .unwrap()
583 .unwrap()
462 .0,
584 .0,
463 vec![IgnorePattern::new(
585 vec![IgnorePattern::new(
464 PatternSyntax::RelGlob,
586 PatternSyntax::RelGlob,
465 b"*.elc",
587 b"*.elc",
466 Path::new("file_path")
588 Path::new("file_path")
467 )],
589 )],
468 );
590 );
469
591
470 let lines = b"syntax: include\nsyntax: glob";
592 let lines = b"syntax: include\nsyntax: glob";
471
593
472 assert_eq!(
594 assert_eq!(
473 parse_pattern_file_contents(lines, Path::new("file_path"), false)
595 parse_pattern_file_contents(lines, Path::new("file_path"), false)
474 .unwrap()
596 .unwrap()
475 .0,
597 .0,
476 vec![]
598 vec![]
477 );
599 );
478 let lines = b"glob:**.o";
600 let lines = b"glob:**.o";
479 assert_eq!(
601 assert_eq!(
480 parse_pattern_file_contents(lines, Path::new("file_path"), false)
602 parse_pattern_file_contents(lines, Path::new("file_path"), false)
481 .unwrap()
603 .unwrap()
482 .0,
604 .0,
483 vec![IgnorePattern::new(
605 vec![IgnorePattern::new(
484 PatternSyntax::RelGlob,
606 PatternSyntax::RelGlob,
485 b"**.o",
607 b"**.o",
486 Path::new("file_path")
608 Path::new("file_path")
487 )]
609 )]
488 );
610 );
489 }
611 }
490
612
491 #[test]
613 #[test]
492 fn test_build_single_regex() {
614 fn test_build_single_regex() {
493 assert_eq!(
615 assert_eq!(
494 build_single_regex(&IgnorePattern::new(
616 build_single_regex(&IgnorePattern::new(
495 PatternSyntax::RelGlob,
617 PatternSyntax::RelGlob,
496 b"rust/target/",
618 b"rust/target/",
497 Path::new("")
619 Path::new("")
498 ))
620 ))
499 .unwrap(),
621 .unwrap(),
500 br"(?:|.*/)rust/target(?:/|$)".to_vec(),
622 br"(?:|.*/)rust/target(?:/|$)".to_vec(),
501 );
623 );
502 }
624 }
503
625
504 #[test]
626 #[test]
505 fn test_build_single_regex_shortcut() {
627 fn test_build_single_regex_shortcut() {
506 assert_eq!(
628 assert_eq!(
507 build_single_regex(&IgnorePattern::new(
629 build_single_regex(&IgnorePattern::new(
508 PatternSyntax::RootGlob,
630 PatternSyntax::RootGlob,
509 b"",
631 b"",
510 Path::new("")
632 Path::new("")
511 ))
633 ))
512 .unwrap(),
634 .unwrap(),
513 br"\.(?:/|$)".to_vec(),
635 br"\.(?:/|$)".to_vec(),
514 );
636 );
515 assert_eq!(
637 assert_eq!(
516 build_single_regex(&IgnorePattern::new(
638 build_single_regex(&IgnorePattern::new(
517 PatternSyntax::RootGlob,
639 PatternSyntax::RootGlob,
518 b"whatever",
640 b"whatever",
519 Path::new("")
641 Path::new("")
520 ))
642 ))
521 .unwrap(),
643 .unwrap(),
522 br"whatever(?:/|$)".to_vec(),
644 br"whatever(?:/|$)".to_vec(),
523 );
645 );
524 assert_eq!(
646 assert_eq!(
525 build_single_regex(&IgnorePattern::new(
647 build_single_regex(&IgnorePattern::new(
526 PatternSyntax::RootGlob,
648 PatternSyntax::RootGlob,
527 b"*.o",
649 b"*.o",
528 Path::new("")
650 Path::new("")
529 ))
651 ))
530 .unwrap(),
652 .unwrap(),
531 br"[^/]*\.o(?:/|$)".to_vec(),
653 br"[^/]*\.o(?:/|$)".to_vec(),
532 );
654 );
533 }
655 }
534 }
656 }
@@ -1,169 +1,175
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
2 // and Mercurial contributors
2 // and Mercurial contributors
3 //
3 //
4 // This software may be used and distributed according to the terms of the
4 // This software may be used and distributed according to the terms of the
5 // GNU General Public License version 2 or any later version.
5 // GNU General Public License version 2 or any later version.
6 mod ancestors;
6 mod ancestors;
7 pub mod dagops;
7 pub mod dagops;
8 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
8 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
9 mod dirstate;
9 mod dirstate;
10 pub mod discovery;
10 pub mod discovery;
11 pub mod testing; // unconditionally built, for use from integration tests
11 pub mod testing; // unconditionally built, for use from integration tests
12 pub use dirstate::{
12 pub use dirstate::{
13 dirs_multiset::{DirsMultiset, DirsMultisetIter},
13 dirs_multiset::{DirsMultiset, DirsMultisetIter},
14 dirstate_map::DirstateMap,
14 dirstate_map::DirstateMap,
15 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
15 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
16 status::{status, StatusResult},
16 status::{status, StatusResult},
17 CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState,
17 CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState,
18 StateMap, StateMapIter,
18 StateMap, StateMapIter,
19 };
19 };
20 mod filepatterns;
20 mod filepatterns;
21 pub mod matchers;
21 pub mod matchers;
22 pub mod revlog;
22 pub mod revlog;
23 pub use revlog::*;
23 pub use revlog::*;
24 pub mod utils;
24 pub mod utils;
25
25
26 use crate::utils::hg_path::{HgPathBuf, HgPathError};
26 use crate::utils::hg_path::{HgPathBuf, HgPathError};
27 pub use filepatterns::{
27 pub use filepatterns::{
28 parse_pattern_syntax, read_pattern_file, IgnorePattern,
28 parse_pattern_syntax, read_pattern_file, IgnorePattern,
29 PatternFileWarning, PatternSyntax,
29 PatternFileWarning, PatternSyntax,
30 };
30 };
31 use std::collections::HashMap;
31 use std::collections::HashMap;
32 use twox_hash::RandomXxHashBuilder64;
32 use twox_hash::RandomXxHashBuilder64;
33
33
34 pub type LineNumber = usize;
34 pub type LineNumber = usize;
35
35
36 /// Rust's default hasher is too slow because it tries to prevent collision
36 /// Rust's default hasher is too slow because it tries to prevent collision
37 /// attacks. We are not concerned about those: if an ill-minded person has
37 /// attacks. We are not concerned about those: if an ill-minded person has
38 /// write access to your repository, you have other issues.
38 /// write access to your repository, you have other issues.
39 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
39 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
40
40
41 #[derive(Clone, Debug, PartialEq)]
41 #[derive(Clone, Debug, PartialEq)]
42 pub enum DirstateParseError {
42 pub enum DirstateParseError {
43 TooLittleData,
43 TooLittleData,
44 Overflow,
44 Overflow,
45 CorruptedEntry(String),
45 CorruptedEntry(String),
46 Damaged,
46 Damaged,
47 }
47 }
48
48
49 impl From<std::io::Error> for DirstateParseError {
49 impl From<std::io::Error> for DirstateParseError {
50 fn from(e: std::io::Error) -> Self {
50 fn from(e: std::io::Error) -> Self {
51 DirstateParseError::CorruptedEntry(e.to_string())
51 DirstateParseError::CorruptedEntry(e.to_string())
52 }
52 }
53 }
53 }
54
54
55 impl ToString for DirstateParseError {
55 impl ToString for DirstateParseError {
56 fn to_string(&self) -> String {
56 fn to_string(&self) -> String {
57 use crate::DirstateParseError::*;
57 use crate::DirstateParseError::*;
58 match self {
58 match self {
59 TooLittleData => "Too little data for dirstate.".to_string(),
59 TooLittleData => "Too little data for dirstate.".to_string(),
60 Overflow => "Overflow in dirstate.".to_string(),
60 Overflow => "Overflow in dirstate.".to_string(),
61 CorruptedEntry(e) => format!("Corrupted entry: {:?}.", e),
61 CorruptedEntry(e) => format!("Corrupted entry: {:?}.", e),
62 Damaged => "Dirstate appears to be damaged.".to_string(),
62 Damaged => "Dirstate appears to be damaged.".to_string(),
63 }
63 }
64 }
64 }
65 }
65 }
66
66
67 #[derive(Debug, PartialEq)]
67 #[derive(Debug, PartialEq)]
68 pub enum DirstatePackError {
68 pub enum DirstatePackError {
69 CorruptedEntry(String),
69 CorruptedEntry(String),
70 CorruptedParent,
70 CorruptedParent,
71 BadSize(usize, usize),
71 BadSize(usize, usize),
72 }
72 }
73
73
74 impl From<std::io::Error> for DirstatePackError {
74 impl From<std::io::Error> for DirstatePackError {
75 fn from(e: std::io::Error) -> Self {
75 fn from(e: std::io::Error) -> Self {
76 DirstatePackError::CorruptedEntry(e.to_string())
76 DirstatePackError::CorruptedEntry(e.to_string())
77 }
77 }
78 }
78 }
79 #[derive(Debug, PartialEq)]
79 #[derive(Debug, PartialEq)]
80 pub enum DirstateMapError {
80 pub enum DirstateMapError {
81 PathNotFound(HgPathBuf),
81 PathNotFound(HgPathBuf),
82 EmptyPath,
82 EmptyPath,
83 InvalidPath(HgPathError),
83 InvalidPath(HgPathError),
84 }
84 }
85
85
86 impl ToString for DirstateMapError {
86 impl ToString for DirstateMapError {
87 fn to_string(&self) -> String {
87 fn to_string(&self) -> String {
88 match self {
88 match self {
89 DirstateMapError::PathNotFound(_) => {
89 DirstateMapError::PathNotFound(_) => {
90 "expected a value, found none".to_string()
90 "expected a value, found none".to_string()
91 }
91 }
92 DirstateMapError::EmptyPath => "Overflow in dirstate.".to_string(),
92 DirstateMapError::EmptyPath => "Overflow in dirstate.".to_string(),
93 DirstateMapError::InvalidPath(e) => e.to_string(),
93 DirstateMapError::InvalidPath(e) => e.to_string(),
94 }
94 }
95 }
95 }
96 }
96 }
97
97
98 pub enum DirstateError {
98 pub enum DirstateError {
99 Parse(DirstateParseError),
99 Parse(DirstateParseError),
100 Pack(DirstatePackError),
100 Pack(DirstatePackError),
101 Map(DirstateMapError),
101 Map(DirstateMapError),
102 IO(std::io::Error),
102 IO(std::io::Error),
103 }
103 }
104
104
105 impl From<DirstateParseError> for DirstateError {
105 impl From<DirstateParseError> for DirstateError {
106 fn from(e: DirstateParseError) -> Self {
106 fn from(e: DirstateParseError) -> Self {
107 DirstateError::Parse(e)
107 DirstateError::Parse(e)
108 }
108 }
109 }
109 }
110
110
111 impl From<DirstatePackError> for DirstateError {
111 impl From<DirstatePackError> for DirstateError {
112 fn from(e: DirstatePackError) -> Self {
112 fn from(e: DirstatePackError) -> Self {
113 DirstateError::Pack(e)
113 DirstateError::Pack(e)
114 }
114 }
115 }
115 }
116
116
117 #[derive(Debug)]
117 #[derive(Debug)]
118 pub enum PatternError {
118 pub enum PatternError {
119 Path(HgPathError),
119 Path(HgPathError),
120 UnsupportedSyntax(String),
120 UnsupportedSyntax(String),
121 UnsupportedSyntaxInFile(String, String, usize),
121 UnsupportedSyntaxInFile(String, String, usize),
122 TooLong(usize),
122 TooLong(usize),
123 IO(std::io::Error),
123 IO(std::io::Error),
124 /// Needed a pattern that can be turned into a regex but got one that
125 /// can't. This should only happen through programmer error.
126 NonRegexPattern(IgnorePattern),
124 }
127 }
125
128
126 impl ToString for PatternError {
129 impl ToString for PatternError {
127 fn to_string(&self) -> String {
130 fn to_string(&self) -> String {
128 match self {
131 match self {
129 PatternError::UnsupportedSyntax(syntax) => {
132 PatternError::UnsupportedSyntax(syntax) => {
130 format!("Unsupported syntax {}", syntax)
133 format!("Unsupported syntax {}", syntax)
131 }
134 }
132 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
135 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
133 format!(
136 format!(
134 "{}:{}: unsupported syntax {}",
137 "{}:{}: unsupported syntax {}",
135 file_path, line, syntax
138 file_path, line, syntax
136 )
139 )
137 }
140 }
138 PatternError::TooLong(size) => {
141 PatternError::TooLong(size) => {
139 format!("matcher pattern is too long ({} bytes)", size)
142 format!("matcher pattern is too long ({} bytes)", size)
140 }
143 }
141 PatternError::IO(e) => e.to_string(),
144 PatternError::IO(e) => e.to_string(),
142 PatternError::Path(e) => e.to_string(),
145 PatternError::Path(e) => e.to_string(),
146 PatternError::NonRegexPattern(pattern) => {
147 format!("'{:?}' cannot be turned into a regex", pattern)
148 }
143 }
149 }
144 }
150 }
145 }
151 }
146
152
147 impl From<DirstateMapError> for DirstateError {
153 impl From<DirstateMapError> for DirstateError {
148 fn from(e: DirstateMapError) -> Self {
154 fn from(e: DirstateMapError) -> Self {
149 DirstateError::Map(e)
155 DirstateError::Map(e)
150 }
156 }
151 }
157 }
152
158
153 impl From<std::io::Error> for DirstateError {
159 impl From<std::io::Error> for DirstateError {
154 fn from(e: std::io::Error) -> Self {
160 fn from(e: std::io::Error) -> Self {
155 DirstateError::IO(e)
161 DirstateError::IO(e)
156 }
162 }
157 }
163 }
158
164
159 impl From<std::io::Error> for PatternError {
165 impl From<std::io::Error> for PatternError {
160 fn from(e: std::io::Error) -> Self {
166 fn from(e: std::io::Error) -> Self {
161 PatternError::IO(e)
167 PatternError::IO(e)
162 }
168 }
163 }
169 }
164
170
165 impl From<HgPathError> for PatternError {
171 impl From<HgPathError> for PatternError {
166 fn from(e: HgPathError) -> Self {
172 fn from(e: HgPathError) -> Self {
167 PatternError::Path(e)
173 PatternError::Path(e)
168 }
174 }
169 }
175 }
General Comments 0
You need to be logged in to leave comments. Login now