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