##// 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 use lazy_static::lazy_static;
27 use lazy_static::lazy_static;
28 use rayon::prelude::*;
28 use rayon::prelude::*;
29 use std::collections::VecDeque;
30 use std::{
29 use std::{
31 borrow::Cow,
30 borrow::Cow,
32 collections::HashSet,
31 collections::HashSet,
@@ -38,7 +37,7 b' use std::{'
38
37
39 /// Wrong type of file from a `BadMatch`
38 /// Wrong type of file from a `BadMatch`
40 /// Note: a lot of those don't exist on all platforms.
39 /// Note: a lot of those don't exist on all platforms.
41 #[derive(Debug)]
40 #[derive(Debug, Copy, Clone)]
42 pub enum BadType {
41 pub enum BadType {
43 CharacterDevice,
42 CharacterDevice,
44 BlockDevice,
43 BlockDevice,
@@ -63,7 +62,7 b' impl ToString for BadType {'
63 }
62 }
64
63
65 /// Was explicitly matched but cannot be found/accessed
64 /// Was explicitly matched but cannot be found/accessed
66 #[derive(Debug)]
65 #[derive(Debug, Copy, Clone)]
67 pub enum BadMatch {
66 pub enum BadMatch {
68 OsError(i32),
67 OsError(i32),
69 BadType(BadType),
68 BadType(BadType),
@@ -72,7 +71,7 b' pub enum BadMatch {'
72 /// Marker enum used to dispatch new status entries into the right collections.
71 /// Marker enum used to dispatch new status entries into the right collections.
73 /// Is similar to `crate::EntryState`, but represents the transient state of
72 /// Is similar to `crate::EntryState`, but represents the transient state of
74 /// entries during the lifetime of a command.
73 /// entries during the lifetime of a command.
75 #[derive(Debug)]
74 #[derive(Debug, Copy, Clone)]
76 enum Dispatch {
75 enum Dispatch {
77 Unsure,
76 Unsure,
78 Modified,
77 Modified,
@@ -300,49 +299,53 b' pub struct StatusOptions {'
300 pub list_ignored: bool,
299 pub list_ignored: bool,
301 }
300 }
302
301
303 /// Dispatch a single file found during `traverse`.
302 /// Dispatch a single entry (file, folder, symlink...) found during `traverse`.
304 /// If `file` is a folder that needs to be traversed, it will be pushed into
303 /// If the entry is a folder that needs to be traversed, it will be pushed into
305 /// `work`.
304 /// `work`.
306 fn traverse_worker<'a>(
305 fn handle_traversed_entry<'a>(
307 work: &mut VecDeque<HgPathBuf>,
306 dir_entry: &DirEntry,
308 matcher: &impl Matcher,
307 matcher: &(impl Matcher + Sync),
308 root_dir: impl AsRef<Path>,
309 dmap: &DirstateMap,
309 dmap: &DirstateMap,
310 filename: impl AsRef<HgPath>,
310 filename: impl AsRef<HgPath>,
311 dir_entry: &DirEntry,
311 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
312 ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool,
312 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
313 dir_ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool,
313 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
314 options: StatusOptions,
314 options: StatusOptions,
315 ) -> Option<IoResult<(Cow<'a, HgPath>, Dispatch)>> {
315 ) -> IoResult<Vec<(Cow<'a, HgPath>, Dispatch)>> {
316 let file_type = match dir_entry.file_type() {
316 let file_type = dir_entry.file_type()?;
317 Ok(x) => x,
318 Err(e) => return Some(Err(e.into())),
319 };
320 let filename = filename.as_ref();
317 let filename = filename.as_ref();
321 let entry_option = dmap.get(filename);
318 let entry_option = dmap.get(filename);
322
319
323 if file_type.is_dir() {
320 if file_type.is_dir() {
324 // Do we need to traverse it?
321 // Do we need to traverse it?
325 if !ignore_fn(&filename) || options.list_ignored {
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 // Nested `if` until `rust-lang/rust#53668` is stable
334 // Nested `if` until `rust-lang/rust#53668` is stable
329 if let Some(entry) = entry_option {
335 if let Some(entry) = entry_option {
330 // Used to be a file, is now a folder
336 // Used to be a file, is now a folder
331 if matcher.matches_everything() || matcher.matches(&filename) {
337 if matcher.matches_everything() || matcher.matches(&filename) {
332 return Some(Ok((
338 return Ok(vec![(
333 Cow::Owned(filename.to_owned()),
339 Cow::Owned(filename.to_owned()),
334 dispatch_missing(entry.state),
340 dispatch_missing(entry.state),
335 )));
341 )]);
336 }
342 }
337 }
343 }
338 } else if file_type.is_file() || file_type.is_symlink() {
344 } else if file_type.is_file() || file_type.is_symlink() {
339 if let Some(entry) = entry_option {
345 if let Some(entry) = entry_option {
340 if matcher.matches_everything() || matcher.matches(&filename) {
346 if matcher.matches_everything() || matcher.matches(&filename) {
341 let metadata = match dir_entry.metadata() {
347 let metadata = dir_entry.metadata()?;
342 Ok(x) => x,
348 return Ok(vec![(
343 Err(e) => return Some(Err(e.into())),
344 };
345 return Some(Ok((
346 Cow::Owned(filename.to_owned()),
349 Cow::Owned(filename.to_owned()),
347 dispatch_found(
350 dispatch_found(
348 &filename,
351 &filename,
@@ -351,7 +354,7 b" fn traverse_worker<'a>("
351 &dmap.copy_map,
354 &dmap.copy_map,
352 options,
355 options,
353 ),
356 ),
354 )));
357 )]);
355 }
358 }
356 } else if (matcher.matches_everything() || matcher.matches(&filename))
359 } else if (matcher.matches_everything() || matcher.matches(&filename))
357 && !ignore_fn(&filename)
360 && !ignore_fn(&filename)
@@ -360,113 +363,105 b" fn traverse_worker<'a>("
360 && dir_ignore_fn(&filename)
363 && dir_ignore_fn(&filename)
361 {
364 {
362 if options.list_ignored {
365 if options.list_ignored {
363 return Some(Ok((
366 return Ok(vec![(
364 Cow::Owned(filename.to_owned()),
367 Cow::Owned(filename.to_owned()),
365 Dispatch::Ignored,
368 Dispatch::Ignored,
366 )));
369 )]);
367 }
370 }
368 } else {
371 } else {
369 return Some(Ok((
372 return Ok(vec![(
370 Cow::Owned(filename.to_owned()),
373 Cow::Owned(filename.to_owned()),
371 Dispatch::Unknown,
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 } else if let Some(entry) = entry_option {
383 } else if let Some(entry) = entry_option {
376 // Used to be a file or a folder, now something else.
384 // Used to be a file or a folder, now something else.
377 if matcher.matches_everything() || matcher.matches(&filename) {
385 if matcher.matches_everything() || matcher.matches(&filename) {
378 return Some(Ok((
386 return Ok(vec![(
379 Cow::Owned(filename.to_owned()),
387 Cow::Owned(filename.to_owned()),
380 dispatch_missing(entry.state),
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
395 /// Decides whether the directory needs to be listed, and if so dispatches its
388 /// current `DirstateMap`.
396 /// entries
389 fn traverse<'a>(
397 fn traverse_dir<'a>(
390 matcher: &(impl Matcher + Sync),
398 matcher: &(impl Matcher + Sync),
391 root_dir: impl AsRef<Path>,
399 root_dir: impl AsRef<Path>,
392 dmap: &DirstateMap,
400 dmap: &DirstateMap,
393 path: impl AsRef<HgPath>,
401 path: impl AsRef<HgPath>,
394 old_results: FastHashMap<Cow<'a, HgPath>, Dispatch>,
402 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
395 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
403 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
396 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
404 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
397 options: StatusOptions,
405 options: StatusOptions,
398 ) -> IoResult<FastHashMap<Cow<'a, HgPath>, Dispatch>> {
406 ) -> IoResult<Vec<(Cow<'a, HgPath>, Dispatch)>> {
399 let root_dir = root_dir.as_ref();
407 let directory = path.as_ref();
400 let mut new_results = FastHashMap::default();
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();
419 let skip_dot_hg = !directory.as_bytes().is_empty();
403 work.push_front(path.as_ref().to_owned());
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() {
437 let mut new_results = vec![];
406 if directory.as_bytes() == b".hg" {
438 for (filename, dir_entry) in entries {
407 continue;
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) {
444 // TODO normalize
410 VisitChildrenSet::Empty => continue,
445 let filename = if directory.is_empty() {
411 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
446 filename.to_owned()
412 VisitChildrenSet::Set(set) => Some(set),
447 } else {
413 };
448 directory.join(&filename)
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,
434 };
449 };
435
450
436 for (filename, dir_entry) in entries {
451 if !old_results.contains_key(filename.deref()) {
437 if let Some(ref set) = visit_entries {
452 new_results.extend(handle_traversed_entry(
438 if !set.contains(filename.deref()) {
453 &dir_entry,
439 continue;
454 matcher,
440 }
455 root_dir.as_ref(),
441 }
456 &dmap,
442 // TODO normalize
457 &filename,
443 let filename = if directory.is_empty() {
458 old_results,
444 filename.to_owned()
459 ignore_fn,
445 } else {
460 dir_ignore_fn,
446 directory.join(&filename)
461 options,
447 };
462 )?);
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 }
465 }
463 }
466 }
464 }
467
468 new_results.extend(old_results.into_iter());
469
470 Ok(new_results)
465 Ok(new_results)
471 }
466 }
472
467
@@ -637,7 +632,10 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
637
632
638 // Step 1: check the files explicitly mentioned by the user
633 // Step 1: check the files explicitly mentioned by the user
639 let explicit = walk_explicit(files, &dmap, root_dir, options);
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 .filter_map(Result::ok)
639 .filter_map(Result::ok)
642 .map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch))
640 .map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch))
643 .partition(|(_, dispatch)| match dispatch {
641 .partition(|(_, dispatch)| match dispatch {
@@ -645,29 +643,36 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
645 _ => false,
643 _ => false,
646 });
644 });
647
645
648 // Step 2: recursively check the working directory for changes if needed
646 if !work.is_empty() {
649 for (dir, dispatch) in work {
647 // Hashmaps are quite a bit slower to build than vecs, so only build it
650 match dispatch {
648 // if needed.
651 Dispatch::Directory { was_file } => {
649 let old_results = results.iter().cloned().collect();
652 if was_file {
650
653 results.insert(dir.to_owned(), Dispatch::Removed);
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
674 _ => unreachable!("There can only be directories in `work`"),
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 }
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 if results.is_empty() && matcher.matches_everything() {
687 if results.is_empty() && matcher.matches_everything() {
683 Box::new(dmap.iter().map(|(f, e)| (f.deref(), e)))
688 Box::new(dmap.iter().map(|(f, e)| (f.deref(), e)))
684 } else {
689 } else {
685 Box::new(dmap.iter().filter_map(|(f, e)| {
690 // Only convert to a hashmap if needed.
686 if !results.contains_key(f.deref())
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 && matcher.matches(f)
695 && matcher.matches(f)
688 {
696 {
689 Some((f.deref(), e))
697 Some((f.deref(), e))
@@ -706,7 +714,7 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
706 if path_auditor.check(filename) {
714 if path_auditor.check(filename) {
707 // TODO normalize for case-insensitive filesystems
715 // TODO normalize for case-insensitive filesystems
708 let buf = hg_path_to_path_buf(filename)?;
716 let buf = hg_path_to_path_buf(filename)?;
709 results.insert(
717 results.push((
710 Cow::Borrowed(filename),
718 Cow::Borrowed(filename),
711 match root_dir.as_ref().join(&buf).symlink_metadata() {
719 match root_dir.as_ref().join(&buf).symlink_metadata() {
712 // File was just ignored, no links, and exists
720 // File was just ignored, no links, and exists
@@ -723,14 +731,14 b" pub fn status<'a: 'c, 'b: 'c, 'c>("
723 // File doesn't exist
731 // File doesn't exist
724 Err(_) => dispatch_missing(entry.state),
732 Err(_) => dispatch_missing(entry.state),
725 },
733 },
726 );
734 ));
727 } else {
735 } else {
728 // It's either missing or under a symlink directory which
736 // It's either missing or under a symlink directory which
729 // we, in this case, report as missing.
737 // we, in this case, report as missing.
730 results.insert(
738 results.push((
731 Cow::Borrowed(filename),
739 Cow::Borrowed(filename),
732 dispatch_missing(entry.state),
740 dispatch_missing(entry.state),
733 );
741 ));
734 }
742 }
735 }
743 }
736 } else {
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 Ok((build_response(results), warnings))
754 Ok((build_response(results), warnings))
770 }
755 }
General Comments 0
You need to be logged in to leave comments. Login now