##// END OF EJS Templates
rust-status: move to recursive traversal to prepare for parallel traversal...
Raphaël Gomès -
r45023:f8a9922a default
parent child Browse files
Show More
@@ -26,7 +26,6 b' use crate::{'
26 26 };
27 27 use lazy_static::lazy_static;
28 28 use rayon::prelude::*;
29 use std::collections::VecDeque;
30 29 use std::{
31 30 borrow::Cow,
32 31 collections::HashSet,
@@ -38,7 +37,7 b' use std::{'
38 37
39 38 /// Wrong type of file from a `BadMatch`
40 39 /// Note: a lot of those don't exist on all platforms.
41 #[derive(Debug)]
40 #[derive(Debug, Copy, Clone)]
42 41 pub enum BadType {
43 42 CharacterDevice,
44 43 BlockDevice,
@@ -63,7 +62,7 b' impl ToString for BadType {'
63 62 }
64 63
65 64 /// Was explicitly matched but cannot be found/accessed
66 #[derive(Debug)]
65 #[derive(Debug, Copy, Clone)]
67 66 pub enum BadMatch {
68 67 OsError(i32),
69 68 BadType(BadType),
@@ -72,7 +71,7 b' pub enum BadMatch {'
72 71 /// Marker enum used to dispatch new status entries into the right collections.
73 72 /// Is similar to `crate::EntryState`, but represents the transient state of
74 73 /// entries during the lifetime of a command.
75 #[derive(Debug)]
74 #[derive(Debug, Copy, Clone)]
76 75 enum Dispatch {
77 76 Unsure,
78 77 Modified,
@@ -300,49 +299,53 b' pub struct StatusOptions {'
300 299 pub list_ignored: bool,
301 300 }
302 301
303 /// Dispatch a single file found during `traverse`.
304 /// If `file` is a folder that needs to be traversed, it will be pushed into
302 /// Dispatch a single entry (file, folder, symlink...) found during `traverse`.
303 /// If the entry is a folder that needs to be traversed, it will be pushed into
305 304 /// `work`.
306 fn traverse_worker<'a>(
307 work: &mut VecDeque<HgPathBuf>,
308 matcher: &impl Matcher,
305 fn handle_traversed_entry<'a>(
306 dir_entry: &DirEntry,
307 matcher: &(impl Matcher + Sync),
308 root_dir: impl AsRef<Path>,
309 309 dmap: &DirstateMap,
310 310 filename: impl AsRef<HgPath>,
311 dir_entry: &DirEntry,
312 ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool,
313 dir_ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool,
311 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
312 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
313 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
314 314 options: StatusOptions,
315 ) -> Option<IoResult<(Cow<'a, HgPath>, Dispatch)>> {
316 let file_type = match dir_entry.file_type() {
317 Ok(x) => x,
318 Err(e) => return Some(Err(e.into())),
319 };
315 ) -> IoResult<Vec<(Cow<'a, HgPath>, Dispatch)>> {
316 let file_type = dir_entry.file_type()?;
320 317 let filename = filename.as_ref();
321 318 let entry_option = dmap.get(filename);
322 319
323 320 if file_type.is_dir() {
324 321 // Do we need to traverse it?
325 322 if !ignore_fn(&filename) || options.list_ignored {
326 work.push_front(filename.to_owned());
323 return traverse_dir(
324 matcher,
325 root_dir,
326 dmap,
327 filename.to_owned(),
328 &old_results,
329 ignore_fn,
330 dir_ignore_fn,
331 options,
332 );
327 333 }
328 334 // Nested `if` until `rust-lang/rust#53668` is stable
329 335 if let Some(entry) = entry_option {
330 336 // Used to be a file, is now a folder
331 337 if matcher.matches_everything() || matcher.matches(&filename) {
332 return Some(Ok((
338 return Ok(vec![(
333 339 Cow::Owned(filename.to_owned()),
334 340 dispatch_missing(entry.state),
335 )));
341 )]);
336 342 }
337 343 }
338 344 } else if file_type.is_file() || file_type.is_symlink() {
339 345 if let Some(entry) = entry_option {
340 346 if matcher.matches_everything() || matcher.matches(&filename) {
341 let metadata = match dir_entry.metadata() {
342 Ok(x) => x,
343 Err(e) => return Some(Err(e.into())),
344 };
345 return Some(Ok((
347 let metadata = dir_entry.metadata()?;
348 return Ok(vec![(
346 349 Cow::Owned(filename.to_owned()),
347 350 dispatch_found(
348 351 &filename,
@@ -351,7 +354,7 b" fn traverse_worker<'a>("
351 354 &dmap.copy_map,
352 355 options,
353 356 ),
354 )));
357 )]);
355 358 }
356 359 } else if (matcher.matches_everything() || matcher.matches(&filename))
357 360 && !ignore_fn(&filename)
@@ -360,113 +363,105 b" fn traverse_worker<'a>("
360 363 && dir_ignore_fn(&filename)
361 364 {
362 365 if options.list_ignored {
363 return Some(Ok((
366 return Ok(vec![(
364 367 Cow::Owned(filename.to_owned()),
365 368 Dispatch::Ignored,
366 )));
369 )]);
367 370 }
368 371 } else {
369 return Some(Ok((
372 return Ok(vec![(
370 373 Cow::Owned(filename.to_owned()),
371 374 Dispatch::Unknown,
372 )));
375 )]);
373 376 }
377 } else if ignore_fn(&filename) && options.list_ignored {
378 return Ok(vec![(
379 Cow::Owned(filename.to_owned()),
380 Dispatch::Ignored,
381 )]);
374 382 }
375 383 } else if let Some(entry) = entry_option {
376 384 // Used to be a file or a folder, now something else.
377 385 if matcher.matches_everything() || matcher.matches(&filename) {
378 return Some(Ok((
386 return Ok(vec![(
379 387 Cow::Owned(filename.to_owned()),
380 388 dispatch_missing(entry.state),
381 )));
389 )]);
382 390 }
383 391 }
384 None
392 return Ok(vec![]);
385 393 }
386 394
387 /// Walk the working directory recursively to look for changes compared to the
388 /// current `DirstateMap`.
389 fn traverse<'a>(
395 /// Decides whether the directory needs to be listed, and if so dispatches its
396 /// entries
397 fn traverse_dir<'a>(
390 398 matcher: &(impl Matcher + Sync),
391 399 root_dir: impl AsRef<Path>,
392 400 dmap: &DirstateMap,
393 401 path: impl AsRef<HgPath>,
394 old_results: FastHashMap<Cow<'a, HgPath>, Dispatch>,
402 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
395 403 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
396 404 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
397 405 options: StatusOptions,
398 ) -> IoResult<FastHashMap<Cow<'a, HgPath>, Dispatch>> {
399 let root_dir = root_dir.as_ref();
400 let mut new_results = FastHashMap::default();
406 ) -> IoResult<Vec<(Cow<'a, HgPath>, Dispatch)>> {
407 let directory = path.as_ref();
408 if directory.as_bytes() == b".hg" {
409 return Ok(vec![]);
410 }
411 let visit_entries = match matcher.visit_children_set(directory) {
412 VisitChildrenSet::Empty => return Ok(vec![]),
413 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
414 VisitChildrenSet::Set(set) => Some(set),
415 };
416 let buf = hg_path_to_path_buf(directory)?;
417 let dir_path = root_dir.as_ref().join(buf);
401 418
402 let mut work = VecDeque::new();
403 work.push_front(path.as_ref().to_owned());
419 let skip_dot_hg = !directory.as_bytes().is_empty();
420 let entries = match list_directory(dir_path, skip_dot_hg) {
421 Err(e) => match e.kind() {
422 ErrorKind::NotFound | ErrorKind::PermissionDenied => {
423 return Ok(vec![(
424 Cow::Owned(directory.to_owned()),
425 Dispatch::Bad(BadMatch::OsError(
426 // Unwrapping here is OK because the error always
427 // is a real os error
428 e.raw_os_error().unwrap(),
429 )),
430 )]);
431 }
432 _ => return Err(e),
433 },
434 Ok(entries) => entries,
435 };
404 436
405 while let Some(ref directory) = work.pop_front() {
406 if directory.as_bytes() == b".hg" {
407 continue;
437 let mut new_results = vec![];
438 for (filename, dir_entry) in entries {
439 if let Some(ref set) = visit_entries {
440 if !set.contains(filename.deref()) {
441 continue;
442 }
408 443 }
409 let visit_entries = match matcher.visit_children_set(directory) {
410 VisitChildrenSet::Empty => continue,
411 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
412 VisitChildrenSet::Set(set) => Some(set),
413 };
414 let buf = hg_path_to_path_buf(directory)?;
415 let dir_path = root_dir.join(buf);
416
417 let skip_dot_hg = !directory.as_bytes().is_empty();
418 let entries = match list_directory(dir_path, skip_dot_hg) {
419 Err(e) => match e.kind() {
420 ErrorKind::NotFound | ErrorKind::PermissionDenied => {
421 new_results.insert(
422 Cow::Owned(directory.to_owned()),
423 Dispatch::Bad(BadMatch::OsError(
424 // Unwrapping here is OK because the error always
425 // is a real os error
426 e.raw_os_error().unwrap(),
427 )),
428 );
429 continue;
430 }
431 _ => return Err(e),
432 },
433 Ok(entries) => entries,
444 // TODO normalize
445 let filename = if directory.is_empty() {
446 filename.to_owned()
447 } else {
448 directory.join(&filename)
434 449 };
435 450
436 for (filename, dir_entry) in entries {
437 if let Some(ref set) = visit_entries {
438 if !set.contains(filename.deref()) {
439 continue;
440 }
441 }
442 // TODO normalize
443 let filename = if directory.is_empty() {
444 filename.to_owned()
445 } else {
446 directory.join(&filename)
447 };
448
449 if !old_results.contains_key(filename.deref()) {
450 if let Some((res, dispatch)) = traverse_worker(
451 &mut work,
452 matcher,
453 &dmap,
454 &filename,
455 &dir_entry,
456 &ignore_fn,
457 &dir_ignore_fn,
458 options,
459 )
460 .transpose()?
461 {
462 new_results.insert(res, dispatch);
463 }
464 }
451 if !old_results.contains_key(filename.deref()) {
452 new_results.extend(handle_traversed_entry(
453 &dir_entry,
454 matcher,
455 root_dir.as_ref(),
456 &dmap,
457 &filename,
458 old_results,
459 ignore_fn,
460 dir_ignore_fn,
461 options,
462 )?);
465 463 }
466 464 }
467
468 new_results.extend(old_results.into_iter());
469
470 465 Ok(new_results)
471 466 }
472 467
@@ -637,7 +632,10 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
637 632
638 633 // Step 1: check the files explicitly mentioned by the user
639 634 let explicit = walk_explicit(files, &dmap, root_dir, options);
640 let (work, mut results): (Vec<_>, FastHashMap<_, _>) = explicit
635
636 // Collect results into a `Vec` because we do very few lookups in most
637 // cases.
638 let (work, mut results): (Vec<_>, Vec<_>) = explicit
641 639 .filter_map(Result::ok)
642 640 .map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch))
643 641 .partition(|(_, dispatch)| match dispatch {
@@ -645,29 +643,36 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
645 643 _ => false,
646 644 });
647 645
648 // Step 2: recursively check the working directory for changes if needed
649 for (dir, dispatch) in work {
650 match dispatch {
651 Dispatch::Directory { was_file } => {
652 if was_file {
653 results.insert(dir.to_owned(), Dispatch::Removed);
646 if !work.is_empty() {
647 // Hashmaps are quite a bit slower to build than vecs, so only build it
648 // if needed.
649 let old_results = results.iter().cloned().collect();
650
651 // Step 2: recursively check the working directory for changes if
652 // needed
653 for (dir, dispatch) in work {
654 match dispatch {
655 Dispatch::Directory { was_file } => {
656 if was_file {
657 results.push((dir.to_owned(), Dispatch::Removed));
658 }
659 if options.list_ignored
660 || options.list_unknown && !dir_ignore_fn(&dir)
661 {
662 results.par_extend(traverse_dir(
663 matcher,
664 root_dir,
665 &dmap,
666 &dir,
667 &old_results,
668 &ignore_fn,
669 &dir_ignore_fn,
670 options,
671 )?);
672 }
654 673 }
655 if options.list_ignored
656 || options.list_unknown && !dir_ignore_fn(&dir)
657 {
658 results = traverse(
659 matcher,
660 root_dir,
661 &dmap,
662 &dir,
663 results,
664 &ignore_fn,
665 &dir_ignore_fn,
666 options,
667 )?;
668 }
674 _ => unreachable!("There can only be directories in `work`"),
669 675 }
670 _ => unreachable!("There can only be directories in `work`"),
671 676 }
672 677 }
673 678
@@ -682,8 +687,11 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
682 687 if results.is_empty() && matcher.matches_everything() {
683 688 Box::new(dmap.iter().map(|(f, e)| (f.deref(), e)))
684 689 } else {
685 Box::new(dmap.iter().filter_map(|(f, e)| {
686 if !results.contains_key(f.deref())
690 // Only convert to a hashmap if needed.
691 let old_results: FastHashMap<_, _> =
692 results.iter().cloned().collect();
693 Box::new(dmap.iter().filter_map(move |(f, e)| {
694 if !old_results.contains_key(f.deref())
687 695 && matcher.matches(f)
688 696 {
689 697 Some((f.deref(), e))
@@ -706,7 +714,7 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
706 714 if path_auditor.check(filename) {
707 715 // TODO normalize for case-insensitive filesystems
708 716 let buf = hg_path_to_path_buf(filename)?;
709 results.insert(
717 results.push((
710 718 Cow::Borrowed(filename),
711 719 match root_dir.as_ref().join(&buf).symlink_metadata() {
712 720 // File was just ignored, no links, and exists
@@ -723,14 +731,14 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
723 731 // File doesn't exist
724 732 Err(_) => dispatch_missing(entry.state),
725 733 },
726 );
734 ));
727 735 } else {
728 736 // It's either missing or under a symlink directory which
729 737 // we, in this case, report as missing.
730 results.insert(
738 results.push((
731 739 Cow::Borrowed(filename),
732 740 dispatch_missing(entry.state),
733 );
741 ));
734 742 }
735 743 }
736 744 } else {
@@ -743,28 +751,5 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
743 751 }
744 752 }
745 753
746 let results = results.into_iter().filter_map(|(filename, dispatch)| {
747 match dispatch {
748 Dispatch::Bad(_) => return Some((filename, dispatch)),
749 _ => {}
750 };
751 // TODO do this in //, not at the end
752 if !dmap.contains_key(filename.deref()) {
753 if (options.list_ignored || matcher.exact_match(&filename))
754 && dir_ignore_fn(&filename)
755 {
756 if options.list_ignored {
757 return Some((filename.to_owned(), Dispatch::Ignored));
758 }
759 } else {
760 if !ignore_fn(&filename) {
761 return Some((filename.to_owned(), Dispatch::Unknown));
762 }
763 }
764 return None;
765 }
766 Some((filename, dispatch))
767 });
768
769 754 Ok((build_response(results), warnings))
770 755 }
General Comments 0
You need to be logged in to leave comments. Login now