##// END OF EJS Templates
rust-status: don't bubble up os errors, translate them to bad matches...
Raphaël Gomès -
r46466:5c736ba5 default
parent child Browse files
Show More
@@ -109,6 +109,10 b" pub type HgPathCow<'a> = Cow<'a, HgPath>"
109 109 /// A path with its computed ``Dispatch`` information
110 110 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
111 111
112 /// The conversion from `HgPath` to a real fs path failed.
113 /// `22` is the error code for "Invalid argument"
114 const INVALID_PATH_DISPATCH: Dispatch = Dispatch::Bad(BadMatch::OsError(22));
115
112 116 /// Dates and times that are outside the 31-bit signed range are compared
113 117 /// modulo 2^31. This should prevent hg from behaving badly with very large
114 118 /// files or corrupt dates while still having a high probability of detecting
@@ -217,6 +221,12 b' fn dispatch_missing(state: EntryState) -'
217 221 }
218 222 }
219 223
224 fn dispatch_os_error(e: &std::io::Error) -> Dispatch {
225 Dispatch::Bad(BadMatch::OsError(
226 e.raw_os_error().expect("expected real OS error"),
227 ))
228 }
229
220 230 lazy_static! {
221 231 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
222 232 let mut h = HashSet::new();
@@ -372,13 +382,18 b' where'
372 382 .file_set()
373 383 .unwrap_or(&DEFAULT_WORK)
374 384 .par_iter()
375 .map(|&filename| -> Option<IoResult<_>> {
385 .flat_map(|&filename| -> Option<_> {
376 386 // TODO normalization
377 387 let normalized = filename;
378 388
379 389 let buf = match hg_path_to_path_buf(normalized) {
380 390 Ok(x) => x,
381 Err(e) => return Some(Err(e.into())),
391 Err(_) => {
392 return Some((
393 Cow::Borrowed(normalized),
394 INVALID_PATH_DISPATCH,
395 ))
396 }
382 397 };
383 398 let target = self.root_dir.join(buf);
384 399 let st = target.symlink_metadata();
@@ -389,7 +404,7 b' where'
389 404 return if file_type.is_file() || file_type.is_symlink()
390 405 {
391 406 if let Some(entry) = in_dmap {
392 return Some(Ok((
407 return Some((
393 408 Cow::Borrowed(normalized),
394 409 dispatch_found(
395 410 &normalized,
@@ -398,26 +413,26 b' where'
398 413 &self.dmap.copy_map,
399 414 self.options,
400 415 ),
401 )));
416 ));
402 417 }
403 Some(Ok((
418 Some((
404 419 Cow::Borrowed(normalized),
405 420 Dispatch::Unknown,
406 )))
421 ))
407 422 } else if file_type.is_dir() {
408 423 if self.options.collect_traversed_dirs {
409 424 traversed_sender
410 425 .send(normalized.to_owned())
411 426 .expect("receiver should outlive sender");
412 427 }
413 Some(Ok((
428 Some((
414 429 Cow::Borrowed(normalized),
415 430 Dispatch::Directory {
416 431 was_file: in_dmap.is_some(),
417 432 },
418 )))
433 ))
419 434 } else {
420 Some(Ok((
435 Some((
421 436 Cow::Borrowed(normalized),
422 437 Dispatch::Bad(BadMatch::BadType(
423 438 // TODO do more than unknown
@@ -428,22 +443,20 b' where'
428 443 // users.
429 444 BadType::Unknown,
430 445 )),
431 )))
446 ))
432 447 };
433 448 }
434 449 Err(_) => {
435 450 if let Some(entry) = in_dmap {
436 return Some(Ok((
451 return Some((
437 452 Cow::Borrowed(normalized),
438 453 dispatch_missing(entry.state),
439 )));
454 ));
440 455 }
441 456 }
442 457 };
443 458 None
444 459 })
445 .flatten()
446 .filter_map(Result::ok)
447 460 .partition(|(_, dispatch)| match dispatch {
448 461 Dispatch::Directory { .. } => true,
449 462 _ => false,
@@ -462,7 +475,7 b' where'
462 475 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
463 476 results: &mut Vec<DispatchedPath<'a>>,
464 477 traversed_sender: crossbeam::Sender<HgPathBuf>,
465 ) -> IoResult<()> {
478 ) {
466 479 // The traversal is done in parallel, so use a channel to gather
467 480 // entries. `crossbeam::Sender` is `Sync`, while `mpsc::Sender`
468 481 // is not.
@@ -474,25 +487,17 b' where'
474 487 path,
475 488 &old_results,
476 489 traversed_sender,
477 )?;
490 );
478 491
479 492 // Disconnect the channel so the receiver stops waiting
480 493 drop(files_transmitter);
481 494
482 // TODO don't collect. Find a way of replicating the behavior of
483 // `itertools::process_results`, but for `rayon::ParallelIterator`
484 let new_results: IoResult<Vec<(Cow<HgPath>, Dispatch)>> =
485 files_receiver
486 .into_iter()
487 .map(|item| {
488 let (f, d) = item?;
489 Ok((Cow::Owned(f), d))
490 })
491 .collect();
495 let new_results = files_receiver
496 .into_iter()
497 .par_bridge()
498 .map(|(f, d)| (Cow::Owned(f), d));
492 499
493 results.par_extend(new_results?);
494
495 Ok(())
500 results.par_extend(new_results);
496 501 }
497 502
498 503 /// Dispatch a single entry (file, folder, symlink...) found during
@@ -501,7 +506,7 b' where'
501 506 fn handle_traversed_entry<'b>(
502 507 &'a self,
503 508 scope: &rayon::Scope<'b>,
504 files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
509 files_sender: &'b crossbeam::Sender<(HgPathBuf, Dispatch)>,
505 510 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
506 511 filename: HgPathBuf,
507 512 dir_entry: DirEntry,
@@ -534,7 +539,7 b' where'
534 539 {
535 540 let metadata = dir_entry.metadata()?;
536 541 files_sender
537 .send(Ok((
542 .send((
538 543 filename.to_owned(),
539 544 dispatch_found(
540 545 &filename,
@@ -543,7 +548,7 b' where'
543 548 &self.dmap.copy_map,
544 549 self.options,
545 550 ),
546 )))
551 ))
547 552 .unwrap();
548 553 }
549 554 } else if (self.matcher.matches_everything()
@@ -556,17 +561,17 b' where'
556 561 {
557 562 if self.options.list_ignored {
558 563 files_sender
559 .send(Ok((filename.to_owned(), Dispatch::Ignored)))
564 .send((filename.to_owned(), Dispatch::Ignored))
560 565 .unwrap();
561 566 }
562 567 } else if self.options.list_unknown {
563 568 files_sender
564 .send(Ok((filename.to_owned(), Dispatch::Unknown)))
569 .send((filename.to_owned(), Dispatch::Unknown))
565 570 .unwrap();
566 571 }
567 572 } else if self.is_ignored(&filename) && self.options.list_ignored {
568 573 files_sender
569 .send(Ok((filename.to_owned(), Dispatch::Ignored)))
574 .send((filename.to_owned(), Dispatch::Ignored))
570 575 .unwrap();
571 576 }
572 577 } else if let Some(entry) = entry_option {
@@ -575,10 +580,7 b' where'
575 580 || self.matcher.matches(&filename)
576 581 {
577 582 files_sender
578 .send(Ok((
579 filename.to_owned(),
580 dispatch_missing(entry.state),
581 )))
583 .send((filename.to_owned(), dispatch_missing(entry.state)))
582 584 .unwrap();
583 585 }
584 586 }
@@ -590,7 +592,7 b' where'
590 592 fn handle_traversed_dir<'b>(
591 593 &'a self,
592 594 scope: &rayon::Scope<'b>,
593 files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
595 files_sender: &'b crossbeam::Sender<(HgPathBuf, Dispatch)>,
594 596 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
595 597 entry_option: Option<&'a DirstateEntry>,
596 598 directory: HgPathBuf,
@@ -606,10 +608,10 b' where'
606 608 || self.matcher.matches(&directory)
607 609 {
608 610 files_sender
609 .send(Ok((
611 .send((
610 612 directory.to_owned(),
611 613 dispatch_missing(entry.state),
612 )))
614 ))
613 615 .unwrap();
614 616 }
615 617 }
@@ -621,7 +623,6 b' where'
621 623 &old_results,
622 624 traversed_sender,
623 625 )
624 .unwrap_or_else(|e| files_sender.send(Err(e)).unwrap())
625 626 }
626 627 });
627 628 }
@@ -630,11 +631,11 b' where'
630 631 /// entries in a separate thread.
631 632 fn traverse_dir(
632 633 &self,
633 files_sender: &crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
634 files_sender: &crossbeam::Sender<(HgPathBuf, Dispatch)>,
634 635 directory: impl AsRef<HgPath>,
635 636 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
636 637 traversed_sender: crossbeam::Sender<HgPathBuf>,
637 ) -> IoResult<()> {
638 ) {
638 639 let directory = directory.as_ref();
639 640
640 641 if self.options.collect_traversed_dirs {
@@ -644,38 +645,33 b' where'
644 645 }
645 646
646 647 let visit_entries = match self.matcher.visit_children_set(directory) {
647 VisitChildrenSet::Empty => return Ok(()),
648 VisitChildrenSet::Empty => return,
648 649 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
649 650 VisitChildrenSet::Set(set) => Some(set),
650 651 };
651 let buf = hg_path_to_path_buf(directory)?;
652 let buf = match hg_path_to_path_buf(directory) {
653 Ok(b) => b,
654 Err(_) => {
655 files_sender
656 .send((directory.to_owned(), INVALID_PATH_DISPATCH))
657 .expect("receiver should outlive sender");
658 return;
659 }
660 };
652 661 let dir_path = self.root_dir.join(buf);
653 662
654 663 let skip_dot_hg = !directory.as_bytes().is_empty();
655 664 let entries = match list_directory(dir_path, skip_dot_hg) {
656 665 Err(e) => {
657 return match e.kind() {
658 ErrorKind::NotFound | ErrorKind::PermissionDenied => {
659 files_sender
660 .send(Ok((
661 directory.to_owned(),
662 Dispatch::Bad(BadMatch::OsError(
663 // Unwrapping here is OK because the error
664 // always is a
665 // real os error
666 e.raw_os_error().unwrap(),
667 )),
668 )))
669 .expect("receiver should outlive sender");
670 Ok(())
671 }
672 _ => Err(e),
673 };
666 files_sender
667 .send((directory.to_owned(), dispatch_os_error(&e)))
668 .expect("receiver should outlive sender");
669 return;
674 670 }
675 671 Ok(entries) => entries,
676 672 };
677 673
678 rayon::scope(|scope| -> IoResult<()> {
674 rayon::scope(|scope| {
679 675 for (filename, dir_entry) in entries {
680 676 if let Some(ref set) = visit_entries {
681 677 if !set.contains(filename.deref()) {
@@ -690,17 +686,26 b' where'
690 686 };
691 687
692 688 if !old_results.contains_key(filename.deref()) {
693 self.handle_traversed_entry(
689 match self.handle_traversed_entry(
694 690 scope,
695 691 files_sender,
696 692 old_results,
697 693 filename,
698 694 dir_entry,
699 695 traversed_sender.clone(),
700 )?;
696 ) {
697 Err(e) => {
698 files_sender
699 .send((
700 directory.to_owned(),
701 dispatch_os_error(&e),
702 ))
703 .expect("receiver should outlive sender");
704 }
705 Ok(_) => {}
706 }
701 707 }
702 708 }
703 Ok(())
704 709 })
705 710 }
706 711
@@ -716,14 +721,23 b' where'
716 721 .fs_iter(self.root_dir.clone())
717 722 .par_bridge()
718 723 .filter(|(path, _)| self.matcher.matches(path))
719 .flat_map(move |(filename, shortcut)| {
724 .map(move |(filename, shortcut)| {
720 725 let entry = match shortcut {
721 726 StatusShortcut::Entry(e) => e,
722 727 StatusShortcut::Dispatch(d) => {
723 return Ok((Cow::Owned(filename), d))
728 return (Cow::Owned(filename), d)
724 729 }
725 730 };
726 let filename_as_path = hg_path_to_path_buf(&filename)?;
731 let filename_as_path = match hg_path_to_path_buf(&filename)
732 {
733 Ok(f) => f,
734 Err(_) => {
735 return (
736 Cow::Owned(filename),
737 INVALID_PATH_DISPATCH,
738 )
739 }
740 };
727 741 let meta = self
728 742 .root_dir
729 743 .join(filename_as_path)
@@ -734,10 +748,10 b' where'
734 748 if !(m.file_type().is_file()
735 749 || m.file_type().is_symlink()) =>
736 750 {
737 Ok((
751 (
738 752 Cow::Owned(filename),
739 753 dispatch_missing(entry.state),
740 ))
754 )
741 755 }
742 756 Ok(m) => {
743 757 let dispatch = dispatch_found(
@@ -747,7 +761,7 b' where'
747 761 &self.dmap.copy_map,
748 762 self.options,
749 763 );
750 Ok((Cow::Owned(filename), dispatch))
764 (Cow::Owned(filename), dispatch)
751 765 }
752 766 Err(e)
753 767 if e.kind() == ErrorKind::NotFound
@@ -758,12 +772,14 b' where'
758 772 // It happens if the dirstate contains `foo/bar`
759 773 // and foo is not a
760 774 // directory
761 Ok((
775 (
762 776 Cow::Owned(filename),
763 777 dispatch_missing(entry.state),
764 ))
778 )
765 779 }
766 Err(e) => Err(e),
780 Err(e) => {
781 (Cow::Owned(filename), dispatch_os_error(&e))
782 }
767 783 }
768 784 }),
769 785 );
@@ -776,10 +792,18 b' where'
776 792 #[cfg(not(feature = "dirstate-tree"))]
777 793 #[timed]
778 794 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
779 results.par_extend(self.dmap.par_iter().flat_map(
795 results.par_extend(self.dmap.par_iter().map(
780 796 move |(filename, entry)| {
781 797 let filename: &HgPath = filename;
782 let filename_as_path = hg_path_to_path_buf(filename)?;
798 let filename_as_path = match hg_path_to_path_buf(filename) {
799 Ok(f) => f,
800 Err(_) => {
801 return (
802 Cow::Borrowed(filename),
803 INVALID_PATH_DISPATCH,
804 )
805 }
806 };
783 807 let meta =
784 808 self.root_dir.join(filename_as_path).symlink_metadata();
785 809 match meta {
@@ -787,12 +811,12 b' where'
787 811 if !(m.file_type().is_file()
788 812 || m.file_type().is_symlink()) =>
789 813 {
790 Ok((
814 (
791 815 Cow::Borrowed(filename),
792 816 dispatch_missing(entry.state),
793 ))
817 )
794 818 }
795 Ok(m) => Ok((
819 Ok(m) => (
796 820 Cow::Borrowed(filename),
797 821 dispatch_found(
798 822 filename,
@@ -801,7 +825,7 b' where'
801 825 &self.dmap.copy_map,
802 826 self.options,
803 827 ),
804 )),
828 ),
805 829 Err(e)
806 830 if e.kind() == ErrorKind::NotFound
807 831 || e.raw_os_error() == Some(20) =>
@@ -811,12 +835,12 b' where'
811 835 // It happens if the dirstate contains `foo/bar`
812 836 // and foo is not a
813 837 // directory
814 Ok((
838 (
815 839 Cow::Borrowed(filename),
816 840 dispatch_missing(entry.state),
817 ))
841 )
818 842 }
819 Err(e) => Err(e),
843 Err(e) => (Cow::Borrowed(filename), dispatch_os_error(&e)),
820 844 }
821 845 },
822 846 ));
@@ -830,10 +854,7 b' where'
830 854 /// `extend` in timings
831 855 #[cfg(not(feature = "dirstate-tree"))]
832 856 #[timed]
833 pub fn handle_unknowns(
834 &self,
835 results: &mut Vec<DispatchedPath<'a>>,
836 ) -> IoResult<()> {
857 pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
837 858 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
838 859 if results.is_empty() && self.matcher.matches_everything() {
839 860 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
@@ -857,21 +878,23 b' where'
857 878
858 879 let path_auditor = PathAuditor::new(&self.root_dir);
859 880
860 // TODO don't collect. Find a way of replicating the behavior of
861 // `itertools::process_results`, but for `rayon::ParallelIterator`
862 let new_results: IoResult<Vec<_>> = to_visit
863 .into_par_iter()
864 .filter_map(|(filename, entry)| -> Option<IoResult<_>> {
881 let new_results = to_visit.into_par_iter().filter_map(
882 |(filename, entry)| -> Option<_> {
865 883 // Report ignored items in the dmap as long as they are not
866 884 // under a symlink directory.
867 885 if path_auditor.check(filename) {
868 886 // TODO normalize for case-insensitive filesystems
869 887 let buf = match hg_path_to_path_buf(filename) {
870 888 Ok(x) => x,
871 Err(e) => return Some(Err(e.into())),
889 Err(_) => {
890 return Some((
891 Cow::Owned(filename.to_owned()),
892 INVALID_PATH_DISPATCH,
893 ));
894 }
872 895 };
873 Some(Ok((
874 Cow::Borrowed(filename),
896 Some((
897 Cow::Owned(filename.to_owned()),
875 898 match self.root_dir.join(&buf).symlink_metadata() {
876 899 // File was just ignored, no links, and exists
877 900 Ok(meta) => {
@@ -887,21 +910,19 b' where'
887 910 // File doesn't exist
888 911 Err(_) => dispatch_missing(entry.state),
889 912 },
890 )))
913 ))
891 914 } else {
892 915 // It's either missing or under a symlink directory which
893 916 // we, in this case, report as missing.
894 Some(Ok((
895 Cow::Borrowed(filename),
917 Some((
918 Cow::Owned(filename.to_owned()),
896 919 dispatch_missing(entry.state),
897 )))
920 ))
898 921 }
899 })
900 .collect();
922 },
923 );
901 924
902 results.par_extend(new_results?);
903
904 Ok(())
925 results.par_extend(new_results);
905 926 }
906 927 }
907 928
@@ -56,7 +56,7 b" impl<'a, M: Matcher + Sync> Status<'a, M"
56 56 .expect("old results should exist"),
57 57 &mut results,
58 58 traversed_sender.clone(),
59 )?;
59 );
60 60 }
61 61 }
62 62 _ => {
@@ -104,7 +104,7 b" impl<'a, M: Matcher + Sync> Status<'a, M"
104 104 &old_results,
105 105 &mut results,
106 106 traversed_sender.clone(),
107 )?;
107 );
108 108 }
109 109 }
110 110 _ => {
@@ -116,7 +116,7 b" impl<'a, M: Matcher + Sync> Status<'a, M"
116 116
117 117 if !self.matcher.is_exact() {
118 118 if self.options.list_unknown {
119 self.handle_unknowns(&mut results)?;
119 self.handle_unknowns(&mut results);
120 120 } else {
121 121 // TODO this is incorrect, see issue6335
122 122 // This requires a fix in both Python and Rust that can happen
General Comments 0
You need to be logged in to leave comments. Login now