Show More
@@ -3,6 +3,12 b'' | |||
|
3 | 3 | version = 3 |
|
4 | 4 | |
|
5 | 5 | [[package]] |
|
6 | name = "Inflector" | |
|
7 | version = "0.11.4" | |
|
8 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
9 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" | |
|
10 | ||
|
11 | [[package]] | |
|
6 | 12 | name = "adler" |
|
7 | 13 | version = "0.2.3" |
|
8 | 14 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -18,6 +24,12 b' dependencies = [' | |||
|
18 | 24 | ] |
|
19 | 25 | |
|
20 | 26 | [[package]] |
|
27 | name = "aliasable" | |
|
28 | version = "0.1.3" | |
|
29 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
30 | checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" | |
|
31 | ||
|
32 | [[package]] | |
|
21 | 33 | name = "ansi_term" |
|
22 | 34 | version = "0.11.0" |
|
23 | 35 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -407,6 +419,7 b' dependencies = [' | |||
|
407 | 419 | "log", |
|
408 | 420 | "memmap2", |
|
409 | 421 | "micro-timer", |
|
422 | "ouroboros", | |
|
410 | 423 | "pretty_assertions", |
|
411 | 424 | "rand 0.8.4", |
|
412 | 425 | "rand_distr", |
@@ -415,7 +428,6 b' dependencies = [' | |||
|
415 | 428 | "regex", |
|
416 | 429 | "same-file", |
|
417 | 430 | "sha-1", |
|
418 | "stable_deref_trait", | |
|
419 | 431 | "tempfile", |
|
420 | 432 | "twox-hash", |
|
421 | 433 | "zstd", |
@@ -623,6 +635,30 b' source = "registry+https://github.com/ru' | |||
|
623 | 635 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" |
|
624 | 636 | |
|
625 | 637 | [[package]] |
|
638 | name = "ouroboros" | |
|
639 | version = "0.15.0" | |
|
640 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
641 | checksum = "9f31a3b678685b150cba82b702dcdc5e155893f63610cf388d30cd988d4ca2bf" | |
|
642 | dependencies = [ | |
|
643 | "aliasable", | |
|
644 | "ouroboros_macro", | |
|
645 | "stable_deref_trait", | |
|
646 | ] | |
|
647 | ||
|
648 | [[package]] | |
|
649 | name = "ouroboros_macro" | |
|
650 | version = "0.15.0" | |
|
651 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
652 | checksum = "084fd65d5dd8b3772edccb5ffd1e4b7eba43897ecd0f9401e330e8c542959408" | |
|
653 | dependencies = [ | |
|
654 | "Inflector", | |
|
655 | "proc-macro-error", | |
|
656 | "proc-macro2", | |
|
657 | "quote", | |
|
658 | "syn", | |
|
659 | ] | |
|
660 | ||
|
661 | [[package]] | |
|
626 | 662 | name = "output_vt100" |
|
627 | 663 | version = "0.1.2" |
|
628 | 664 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -662,6 +698,30 b' dependencies = [' | |||
|
662 | 698 | ] |
|
663 | 699 | |
|
664 | 700 | [[package]] |
|
701 | name = "proc-macro-error" | |
|
702 | version = "1.0.4" | |
|
703 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
704 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" | |
|
705 | dependencies = [ | |
|
706 | "proc-macro-error-attr", | |
|
707 | "proc-macro2", | |
|
708 | "quote", | |
|
709 | "syn", | |
|
710 | "version_check", | |
|
711 | ] | |
|
712 | ||
|
713 | [[package]] | |
|
714 | name = "proc-macro-error-attr" | |
|
715 | version = "1.0.4" | |
|
716 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
|
717 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" | |
|
718 | dependencies = [ | |
|
719 | "proc-macro2", | |
|
720 | "quote", | |
|
721 | "version_check", | |
|
722 | ] | |
|
723 | ||
|
724 | [[package]] | |
|
665 | 725 | name = "proc-macro2" |
|
666 | 726 | version = "1.0.24" |
|
667 | 727 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -18,6 +18,7 b' im-rc = "15.0.*"' | |||
|
18 | 18 | itertools = "0.9" |
|
19 | 19 | lazy_static = "1.4.0" |
|
20 | 20 | libc = "0.2" |
|
21 | ouroboros = "0.15.0" | |
|
21 | 22 | rand = "0.8.4" |
|
22 | 23 | rand_pcg = "0.3.1" |
|
23 | 24 | rand_distr = "0.4.2" |
@@ -26,7 +27,6 b' regex = "1.3.9"' | |||
|
26 | 27 | sha-1 = "0.9.6" |
|
27 | 28 | twox-hash = "1.5.0" |
|
28 | 29 | same-file = "1.0.6" |
|
29 | stable_deref_trait = "1.2.0" | |
|
30 | 30 | tempfile = "3.1.0" |
|
31 | 31 | crossbeam-channel = "0.4" |
|
32 | 32 | micro-timer = "0.3.0" |
@@ -136,8 +136,6 b' pub enum StatusError {' | |||
|
136 | 136 | DirstateV2ParseError(DirstateV2ParseError), |
|
137 | 137 | } |
|
138 | 138 | |
|
139 | pub type StatusResult<T> = Result<T, StatusError>; | |
|
140 | ||
|
141 | 139 | impl fmt::Display for StatusError { |
|
142 | 140 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
|
143 | 141 | match self { |
@@ -725,10 +725,11 b' where' | |||
|
725 | 725 | |
|
726 | 726 | impl OwningDirstateMap { |
|
727 | 727 | pub fn clear(&mut self) { |
|
728 |
|
|
|
729 | map.root = Default::default(); | |
|
730 | map.nodes_with_entry_count = 0; | |
|
731 | map.nodes_with_copy_source_count = 0; | |
|
728 | self.with_dmap_mut(|map| { | |
|
729 | map.root = Default::default(); | |
|
730 | map.nodes_with_entry_count = 0; | |
|
731 | map.nodes_with_copy_source_count = 0; | |
|
732 | }); | |
|
732 | 733 | } |
|
733 | 734 | |
|
734 | 735 | pub fn set_entry( |
@@ -736,9 +737,10 b' impl OwningDirstateMap {' | |||
|
736 | 737 | filename: &HgPath, |
|
737 | 738 | entry: DirstateEntry, |
|
738 | 739 | ) -> Result<(), DirstateV2ParseError> { |
|
739 |
|
|
|
740 | map.get_or_insert(&filename)?.data = NodeData::Entry(entry); | |
|
741 | Ok(()) | |
|
740 | self.with_dmap_mut(|map| { | |
|
741 | map.get_or_insert(&filename)?.data = NodeData::Entry(entry); | |
|
742 | Ok(()) | |
|
743 | }) | |
|
742 | 744 | } |
|
743 | 745 | |
|
744 | 746 | pub fn add_file( |
@@ -747,8 +749,9 b' impl OwningDirstateMap {' | |||
|
747 | 749 | entry: DirstateEntry, |
|
748 | 750 | ) -> Result<(), DirstateError> { |
|
749 | 751 | let old_state = self.get(filename)?.map(|e| e.state()); |
|
750 |
|
|
|
751 | Ok(map.add_or_remove_file(filename, old_state, entry)?) | |
|
752 | self.with_dmap_mut(|map| { | |
|
753 | Ok(map.add_or_remove_file(filename, old_state, entry)?) | |
|
754 | }) | |
|
752 | 755 | } |
|
753 | 756 | |
|
754 | 757 | pub fn remove_file( |
@@ -779,9 +782,10 b' impl OwningDirstateMap {' | |||
|
779 | 782 | if size == 0 { |
|
780 | 783 | self.copy_map_remove(filename)?; |
|
781 | 784 | } |
|
782 |
|
|
|
783 | let entry = DirstateEntry::new_removed(size); | |
|
784 | Ok(map.add_or_remove_file(filename, old_state, entry)?) | |
|
785 | self.with_dmap_mut(|map| { | |
|
786 | let entry = DirstateEntry::new_removed(size); | |
|
787 | Ok(map.add_or_remove_file(filename, old_state, entry)?) | |
|
788 | }) | |
|
785 | 789 | } |
|
786 | 790 | |
|
787 | 791 | pub fn drop_entry_and_copy_source( |
@@ -791,7 +795,6 b' impl OwningDirstateMap {' | |||
|
791 | 795 | let was_tracked = self |
|
792 | 796 | .get(filename)? |
|
793 | 797 | .map_or(false, |e| e.state().is_tracked()); |
|
794 | let map = self.get_map_mut(); | |
|
795 | 798 | struct Dropped { |
|
796 | 799 | was_tracked: bool, |
|
797 | 800 | had_entry: bool, |
@@ -878,52 +881,56 b' impl OwningDirstateMap {' | |||
|
878 | 881 | Ok(Some((dropped, remove))) |
|
879 | 882 | } |
|
880 | 883 | |
|
881 | if let Some((dropped, _removed)) = recur( | |
|
882 | map.on_disk, | |
|
883 |
|
|
|
884 |
&mut map. |
|
|
885 | filename, | |
|
886 | )? { | |
|
887 | if dropped.had_entry { | |
|
888 | map.nodes_with_entry_count -= 1 | |
|
884 | self.with_dmap_mut(|map| { | |
|
885 | if let Some((dropped, _removed)) = recur( | |
|
886 | map.on_disk, | |
|
887 | &mut map.unreachable_bytes, | |
|
888 | &mut map.root, | |
|
889 | filename, | |
|
890 | )? { | |
|
891 | if dropped.had_entry { | |
|
892 | map.nodes_with_entry_count -= 1 | |
|
893 | } | |
|
894 | if dropped.had_copy_source { | |
|
895 | map.nodes_with_copy_source_count -= 1 | |
|
896 | } | |
|
897 | } else { | |
|
898 | debug_assert!(!was_tracked); | |
|
889 | 899 | } |
|
890 | if dropped.had_copy_source { | |
|
891 | map.nodes_with_copy_source_count -= 1 | |
|
892 | } | |
|
893 | } else { | |
|
894 | debug_assert!(!was_tracked); | |
|
895 | } | |
|
896 | Ok(()) | |
|
900 | Ok(()) | |
|
901 | }) | |
|
897 | 902 | } |
|
898 | 903 | |
|
899 | 904 | pub fn has_tracked_dir( |
|
900 | 905 | &mut self, |
|
901 | 906 | directory: &HgPath, |
|
902 | 907 | ) -> Result<bool, DirstateError> { |
|
903 |
|
|
|
904 | if let Some(node) = map.get_node(directory)? { | |
|
905 | // A node without a `DirstateEntry` was created to hold child | |
|
906 | // nodes, and is therefore a directory. | |
|
907 | let state = node.state()?; | |
|
908 | Ok(state.is_none() && node.tracked_descendants_count() > 0) | |
|
909 | } else { | |
|
910 | Ok(false) | |
|
911 | } | |
|
908 | self.with_dmap_mut(|map| { | |
|
909 | if let Some(node) = map.get_node(directory)? { | |
|
910 | // A node without a `DirstateEntry` was created to hold child | |
|
911 | // nodes, and is therefore a directory. | |
|
912 | let state = node.state()?; | |
|
913 | Ok(state.is_none() && node.tracked_descendants_count() > 0) | |
|
914 | } else { | |
|
915 | Ok(false) | |
|
916 | } | |
|
917 | }) | |
|
912 | 918 | } |
|
913 | 919 | |
|
914 | 920 | pub fn has_dir( |
|
915 | 921 | &mut self, |
|
916 | 922 | directory: &HgPath, |
|
917 | 923 | ) -> Result<bool, DirstateError> { |
|
918 |
|
|
|
919 | if let Some(node) = map.get_node(directory)? { | |
|
920 | // A node without a `DirstateEntry` was created to hold child | |
|
921 | // nodes, and is therefore a directory. | |
|
922 | let state = node.state()?; | |
|
923 | Ok(state.is_none() && node.descendants_with_entry_count() > 0) | |
|
924 | } else { | |
|
925 | Ok(false) | |
|
926 | } | |
|
924 | self.with_dmap_mut(|map| { | |
|
925 | if let Some(node) = map.get_node(directory)? { | |
|
926 | // A node without a `DirstateEntry` was created to hold child | |
|
927 | // nodes, and is therefore a directory. | |
|
928 | let state = node.state()?; | |
|
929 | Ok(state.is_none() && node.descendants_with_entry_count() > 0) | |
|
930 | } else { | |
|
931 | Ok(false) | |
|
932 | } | |
|
933 | }) | |
|
927 | 934 | } |
|
928 | 935 | |
|
929 | 936 | #[timed] |
@@ -975,16 +982,29 b' impl OwningDirstateMap {' | |||
|
975 | 982 | on_disk::write(map, can_append) |
|
976 | 983 | } |
|
977 | 984 | |
|
978 | pub fn status<'a>( | |
|
979 | &'a mut self, | |
|
980 | matcher: &'a (dyn Matcher + Sync), | |
|
985 | /// `callback` allows the caller to process and do something with the | |
|
986 | /// results of the status. This is needed to do so efficiently (i.e. | |
|
987 | /// without cloning the `DirstateStatus` object with its paths) because | |
|
988 | /// we need to borrow from `Self`. | |
|
989 | pub fn with_status<R>( | |
|
990 | &mut self, | |
|
991 | matcher: &(dyn Matcher + Sync), | |
|
981 | 992 | root_dir: PathBuf, |
|
982 | 993 | ignore_files: Vec<PathBuf>, |
|
983 | 994 | options: StatusOptions, |
|
984 | ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError> | |
|
985 | { | |
|
986 | let map = self.get_map_mut(); | |
|
987 | super::status::status(map, matcher, root_dir, ignore_files, options) | |
|
995 | callback: impl for<'r> FnOnce( | |
|
996 | Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>, | |
|
997 | ) -> R, | |
|
998 | ) -> R { | |
|
999 | self.with_dmap_mut(|map| { | |
|
1000 | callback(super::status::status( | |
|
1001 | map, | |
|
1002 | matcher, | |
|
1003 | root_dir, | |
|
1004 | ignore_files, | |
|
1005 | options, | |
|
1006 | )) | |
|
1007 | }) | |
|
988 | 1008 | } |
|
989 | 1009 | |
|
990 | 1010 | pub fn copy_map_len(&self) -> usize { |
@@ -1032,22 +1052,23 b' impl OwningDirstateMap {' | |||
|
1032 | 1052 | &mut self, |
|
1033 | 1053 | key: &HgPath, |
|
1034 | 1054 | ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> { |
|
1035 |
|
|
|
1036 | let count = &mut map.nodes_with_copy_source_count; | |
|
1037 | let unreachable_bytes = &mut map.unreachable_bytes; | |
|
1038 | Ok(DirstateMap::get_node_mut( | |
|
1039 | map.on_disk, | |
|
1040 | unreachable_bytes, | |
|
1041 | &mut map.root, | |
|
1042 | key, | |
|
1043 | )? | |
|
1044 | .and_then(|node| { | |
|
1045 | if let Some(source) = &node.copy_source { | |
|
1046 | *count -= 1; | |
|
1047 | DirstateMap::count_dropped_path(unreachable_bytes, source); | |
|
1048 | } | |
|
1049 | node.copy_source.take().map(Cow::into_owned) | |
|
1050 | })) | |
|
1055 | self.with_dmap_mut(|map| { | |
|
1056 | let count = &mut map.nodes_with_copy_source_count; | |
|
1057 | let unreachable_bytes = &mut map.unreachable_bytes; | |
|
1058 | Ok(DirstateMap::get_node_mut( | |
|
1059 | map.on_disk, | |
|
1060 | unreachable_bytes, | |
|
1061 | &mut map.root, | |
|
1062 | key, | |
|
1063 | )? | |
|
1064 | .and_then(|node| { | |
|
1065 | if let Some(source) = &node.copy_source { | |
|
1066 | *count -= 1; | |
|
1067 | DirstateMap::count_dropped_path(unreachable_bytes, source); | |
|
1068 | } | |
|
1069 | node.copy_source.take().map(Cow::into_owned) | |
|
1070 | })) | |
|
1071 | }) | |
|
1051 | 1072 | } |
|
1052 | 1073 | |
|
1053 | 1074 | pub fn copy_map_insert( |
@@ -1055,19 +1076,20 b' impl OwningDirstateMap {' | |||
|
1055 | 1076 | key: HgPathBuf, |
|
1056 | 1077 | value: HgPathBuf, |
|
1057 | 1078 | ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> { |
|
1058 |
|
|
|
1059 | let node = DirstateMap::get_or_insert_node( | |
|
1060 | map.on_disk, | |
|
1061 | &mut map.unreachable_bytes, | |
|
1062 | &mut map.root, | |
|
1063 | &key, | |
|
1064 | WithBasename::to_cow_owned, | |
|
1065 | |_ancestor| {}, | |
|
1066 | )?; | |
|
1067 | if node.copy_source.is_none() { | |
|
1068 | map.nodes_with_copy_source_count += 1 | |
|
1069 | } | |
|
1070 | Ok(node.copy_source.replace(value.into()).map(Cow::into_owned)) | |
|
1079 | self.with_dmap_mut(|map| { | |
|
1080 | let node = DirstateMap::get_or_insert_node( | |
|
1081 | map.on_disk, | |
|
1082 | &mut map.unreachable_bytes, | |
|
1083 | &mut map.root, | |
|
1084 | &key, | |
|
1085 | WithBasename::to_cow_owned, | |
|
1086 | |_ancestor| {}, | |
|
1087 | )?; | |
|
1088 | if node.copy_source.is_none() { | |
|
1089 | map.nodes_with_copy_source_count += 1 | |
|
1090 | } | |
|
1091 | Ok(node.copy_source.replace(value.into()).map(Cow::into_owned)) | |
|
1092 | }) | |
|
1071 | 1093 | } |
|
1072 | 1094 | |
|
1073 | 1095 | pub fn len(&self) -> usize { |
@@ -1115,7 +1137,7 b' impl OwningDirstateMap {' | |||
|
1115 | 1137 | >, |
|
1116 | 1138 | DirstateError, |
|
1117 | 1139 | > { |
|
1118 |
let map = self.get_map |
|
|
1140 | let map = self.get_map(); | |
|
1119 | 1141 | let on_disk = map.on_disk; |
|
1120 | 1142 | Ok(Box::new(filter_map_results( |
|
1121 | 1143 | map.iter_nodes(), |
@@ -1,142 +1,89 b'' | |||
|
1 | use crate::{DirstateError, DirstateParents}; | |
|
2 | ||
|
1 | 3 | use super::dirstate_map::DirstateMap; |
|
2 | use stable_deref_trait::StableDeref; | |
|
3 | 4 | use std::ops::Deref; |
|
4 | 5 | |
|
5 | /* | |
|
6 | // /!\ This is unsound and can cause use after free. It will be fixed in the | |
|
7 | // next patch | |
|
8 | ||
|
9 | // If we change `value` from its current use of `HgPathBuf` to `&HgPath`, | |
|
10 | // nothing here tells that `value` will outlive `OwningDirstateMap` | |
|
11 | pub fn copy_map_insert<'a,'owned>( | |
|
12 | &'owned mut self, | |
|
13 | key: &HgPath, | |
|
14 | value: &'a HgPath, | |
|
15 | ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> { | |
|
16 | // `'local` is smaller than `'a` here | |
|
17 | let map: &'local mut DirstateMap<'local> = self.get_map_mut(); | |
|
18 | let node: &'local mut Node<'local> = DirstateMap::get_or_insert_node( | |
|
19 | map.on_disk, | |
|
20 | &mut map.unreachable_bytes, | |
|
21 | &mut map.root, | |
|
22 | &key, | |
|
23 | WithBasename::to_cow_owned, | |
|
24 | |_ancestor| {}, | |
|
25 | )?; | |
|
26 | if node.copy_source.is_none() { | |
|
27 | map.nodes_with_copy_source_count += 1 | |
|
28 | } | |
|
29 | Ok(node.copy_source.replace(value.into()).map(Cow::into_owned)) | |
|
30 | // and right here ----------^^^^^^^^^^^^ | |
|
31 | // we are storing `&'a HgPath` in `Node<'local>` which is possible | |
|
32 | // because to the compiler, `'a` is longer than ``local`. | |
|
33 | // It is wrong because nothing proves that `&'a HgPath` will outlive `self`. | |
|
34 | } | |
|
35 | ||
|
36 | // All of this is caused by the wrong cast of the DirstateMap pointer that | |
|
37 | // fakes the lifetime of `DirstateMap` and ensures the compiler that it lives | |
|
38 | // as long as `on_disk`, which is only true for its immutable data. | |
|
39 | // This will be fixed in the next commit. | |
|
40 | */ | |
|
6 | use ouroboros::self_referencing; | |
|
41 | 7 | |
|
42 | 8 | /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it |
|
43 | 9 | /// borrows. |
|
44 | /// | |
|
45 | /// This is similar to [`OwningRef`] which is more limited because it | |
|
46 | /// represents exactly one `&T` reference next to the value it borrows, as | |
|
47 | /// opposed to a struct that may contain an arbitrary number of references in | |
|
48 | /// arbitrarily-nested data structures. | |
|
49 | /// | |
|
50 | /// [`OwningRef`]: https://docs.rs/owning_ref/0.4.1/owning_ref/struct.OwningRef.html | |
|
10 | #[self_referencing] | |
|
51 | 11 | pub struct OwningDirstateMap { |
|
52 | /// Owned handle to a bytes buffer with a stable address. | |
|
53 | /// | |
|
54 | /// See <https://docs.rs/owning_ref/0.4.1/owning_ref/trait.StableAddress.html>. | |
|
55 | 12 | on_disk: Box<dyn Deref<Target = [u8]> + Send>, |
|
56 | ||
|
57 | /// Pointer for `Box<DirstateMap<'on_disk>>`, typed-erased because the | |
|
58 | /// language cannot represent a lifetime referencing a sibling field. | |
|
59 | /// This is not quite a self-referencial struct (moving this struct is not | |
|
60 | /// a problem as it doesn’t change the address of the bytes buffer owned | |
|
61 | /// by `on_disk`) but touches similar borrow-checker limitations. | |
|
62 | ptr: *mut (), | |
|
13 | #[borrows(on_disk)] | |
|
14 | #[covariant] | |
|
15 | map: DirstateMap<'this>, | |
|
63 | 16 | } |
|
64 | 17 | |
|
65 | 18 | impl OwningDirstateMap { |
|
66 | 19 | pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self |
|
67 | 20 | where |
|
68 |
OnDisk: Deref<Target = [u8]> + |
|
|
21 | OnDisk: Deref<Target = [u8]> + Send + 'static, | |
|
69 | 22 | { |
|
70 | 23 | let on_disk = Box::new(on_disk); |
|
71 | let bytes: &'_ [u8] = &on_disk; | |
|
72 | let map = DirstateMap::empty(bytes); | |
|
73 | 24 | |
|
74 | // Like in `bytes` above, this `'_` lifetime parameter borrows from | |
|
75 | // the bytes buffer owned by `on_disk`. | |
|
76 | let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map)); | |
|
77 | ||
|
78 | // Erase the pointed type entirely in order to erase the lifetime. | |
|
79 | let ptr: *mut () = ptr.cast(); | |
|
80 | ||
|
81 | Self { on_disk, ptr } | |
|
25 | OwningDirstateMapBuilder { | |
|
26 | on_disk, | |
|
27 | map_builder: |bytes| DirstateMap::empty(&bytes), | |
|
28 | } | |
|
29 | .build() | |
|
82 | 30 | } |
|
83 | 31 | |
|
84 | pub fn get_pair_mut<'a>( | |
|
85 | &'a mut self, | |
|
86 | ) -> (&'a [u8], &'a mut DirstateMap<'a>) { | |
|
87 | // SAFETY: We cast the type-erased pointer back to the same type it had | |
|
88 | // in `new`, except with a different lifetime parameter. This time we | |
|
89 | // connect the lifetime to that of `self`. This cast is valid because | |
|
90 | // `self` owns the same `on_disk` whose buffer `DirstateMap` | |
|
91 | // references. That buffer has a stable memory address because our | |
|
92 | // `Self::new_empty` counstructor requires `StableDeref`. | |
|
93 | let ptr: *mut DirstateMap<'a> = self.ptr.cast(); | |
|
94 | // SAFETY: we dereference that pointer, connecting the lifetime of the | |
|
95 | // new `&mut` to that of `self`. This is valid because the | |
|
96 | // raw pointer is to a boxed value, and `self` owns that box. | |
|
97 | (&self.on_disk, unsafe { &mut *ptr }) | |
|
98 | } | |
|
32 | pub fn new_v1<OnDisk>( | |
|
33 | on_disk: OnDisk, | |
|
34 | ) -> Result<(Self, DirstateParents), DirstateError> | |
|
35 | where | |
|
36 | OnDisk: Deref<Target = [u8]> + Send + 'static, | |
|
37 | { | |
|
38 | let on_disk = Box::new(on_disk); | |
|
39 | let mut parents = DirstateParents::NULL; | |
|
99 | 40 | |
|
100 | pub fn get_map_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> { | |
|
101 | self.get_pair_mut().1 | |
|
41 | Ok(( | |
|
42 | OwningDirstateMapTryBuilder { | |
|
43 | on_disk, | |
|
44 | map_builder: |bytes| { | |
|
45 | DirstateMap::new_v1(&bytes).map(|(dmap, p)| { | |
|
46 | parents = p.unwrap_or(DirstateParents::NULL); | |
|
47 | dmap | |
|
48 | }) | |
|
49 | }, | |
|
50 | } | |
|
51 | .try_build()?, | |
|
52 | parents, | |
|
53 | )) | |
|
102 | 54 | } |
|
103 | 55 | |
|
104 | pub fn get_map<'a>(&'a self) -> &'a DirstateMap<'a> { | |
|
105 | // SAFETY: same reasoning as in `get_pair_mut` above. | |
|
106 | let ptr: *mut DirstateMap<'a> = self.ptr.cast(); | |
|
107 | unsafe { &*ptr } | |
|
56 | pub fn new_v2<OnDisk>( | |
|
57 | on_disk: OnDisk, | |
|
58 | data_size: usize, | |
|
59 | metadata: &[u8], | |
|
60 | ) -> Result<Self, DirstateError> | |
|
61 | where | |
|
62 | OnDisk: Deref<Target = [u8]> + Send + 'static, | |
|
63 | { | |
|
64 | let on_disk = Box::new(on_disk); | |
|
65 | ||
|
66 | OwningDirstateMapTryBuilder { | |
|
67 | on_disk, | |
|
68 | map_builder: |bytes| { | |
|
69 | DirstateMap::new_v2(&bytes, data_size, metadata) | |
|
70 | }, | |
|
71 | } | |
|
72 | .try_build() | |
|
108 | 73 | } |
|
109 | 74 | |
|
110 | pub fn on_disk<'a>(&'a self) -> &'a [u8] { | |
|
111 |
&self |
|
|
75 | pub fn with_dmap_mut<R>( | |
|
76 | &mut self, | |
|
77 | f: impl FnOnce(&mut DirstateMap) -> R, | |
|
78 | ) -> R { | |
|
79 | self.with_map_mut(f) | |
|
80 | } | |
|
81 | ||
|
82 | pub fn get_map(&self) -> &DirstateMap { | |
|
83 | self.borrow_map() | |
|
84 | } | |
|
85 | ||
|
86 | pub fn on_disk(&self) -> &[u8] { | |
|
87 | self.borrow_on_disk() | |
|
112 | 88 | } |
|
113 | 89 | } |
|
114 | ||
|
115 | impl Drop for OwningDirstateMap { | |
|
116 | fn drop(&mut self) { | |
|
117 | // Silence a "field is never read" warning, and demonstrate that this | |
|
118 | // value is still alive. | |
|
119 | let _: &Box<dyn Deref<Target = [u8]> + Send> = &self.on_disk; | |
|
120 | // SAFETY: this cast is the same as in `get_mut`, and is valid for the | |
|
121 | // same reason. `self.on_disk` still exists at this point, drop glue | |
|
122 | // will drop it implicitly after this `drop` method returns. | |
|
123 | let ptr: *mut DirstateMap<'_> = self.ptr.cast(); | |
|
124 | // SAFETY: `Box::from_raw` takes ownership of the box away from `self`. | |
|
125 | // This is fine because drop glue does nothing for `*mut ()` and we’re | |
|
126 | // in `drop`, so `get` and `get_mut` cannot be called again. | |
|
127 | unsafe { drop(Box::from_raw(ptr)) } | |
|
128 | } | |
|
129 | } | |
|
130 | ||
|
131 | fn _static_assert_is_send<T: Send>() {} | |
|
132 | ||
|
133 | fn _static_assert_fields_are_send() { | |
|
134 | _static_assert_is_send::<Box<DirstateMap<'_>>>(); | |
|
135 | } | |
|
136 | ||
|
137 | // SAFETY: we don’t get this impl implicitly because `*mut (): !Send` because | |
|
138 | // thread-safety of raw pointers is unknown in the general case. However this | |
|
139 | // particular raw pointer represents a `Box<DirstateMap<'on_disk>>` that we | |
|
140 | // own. Since that `Box` is `Send` as shown in above, it is sound to mark | |
|
141 | // this struct as `Send` too. | |
|
142 | unsafe impl Send for OwningDirstateMap {} |
@@ -40,13 +40,14 b' use std::time::SystemTime;' | |||
|
40 | 40 | /// exists in one of the two trees, depending on information requested by |
|
41 | 41 | /// `options` we may need to traverse the remaining subtree. |
|
42 | 42 | #[timed] |
|
43 |
pub fn status<' |
|
|
44 |
dmap: &' |
|
|
43 | pub fn status<'dirstate>( | |
|
44 | dmap: &'dirstate mut DirstateMap, | |
|
45 | 45 | matcher: &(dyn Matcher + Sync), |
|
46 | 46 | root_dir: PathBuf, |
|
47 | 47 | ignore_files: Vec<PathBuf>, |
|
48 | 48 | options: StatusOptions, |
|
49 |
) -> Result<(DirstateStatus<' |
|
|
49 | ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError> | |
|
50 | { | |
|
50 | 51 | // Force the global rayon threadpool to not exceed 16 concurrent threads. |
|
51 | 52 | // This is a stop-gap measure until we figure out why using more than 16 |
|
52 | 53 | // threads makes `status` slower for each additional thread. |
@@ -1,7 +1,6 b'' | |||
|
1 | 1 | use crate::changelog::Changelog; |
|
2 | 2 | use crate::config::{Config, ConfigError, ConfigParseError}; |
|
3 | 3 | use crate::dirstate::DirstateParents; |
|
4 | use crate::dirstate_tree::dirstate_map::DirstateMap; | |
|
5 | 4 | use crate::dirstate_tree::on_disk::Docket as DirstateDocket; |
|
6 | 5 | use crate::dirstate_tree::owning::OwningDirstateMap; |
|
7 | 6 | use crate::errors::HgResultExt; |
@@ -340,25 +339,19 b' impl Repo {' | |||
|
340 | 339 | .set(Some(docket.uuid.to_owned())); |
|
341 | 340 | let data_size = docket.data_size(); |
|
342 | 341 | let metadata = docket.tree_metadata(); |
|
343 |
|
|
|
342 | if let Some(data_mmap) = self | |
|
344 | 343 | .hg_vfs() |
|
345 | 344 | .mmap_open(docket.data_filename()) |
|
346 | 345 | .io_not_found_as_none()? |
|
347 | 346 | { |
|
348 |
OwningDirstateMap::new_ |
|
|
347 | OwningDirstateMap::new_v2(data_mmap, data_size, metadata) | |
|
349 | 348 | } else { |
|
350 |
OwningDirstateMap::new_ |
|
|
351 |
} |
|
|
352 | let (on_disk, placeholder) = map.get_pair_mut(); | |
|
353 | *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?; | |
|
354 | Ok(map) | |
|
349 | OwningDirstateMap::new_v2(Vec::new(), data_size, metadata) | |
|
350 | } | |
|
355 | 351 | } else { |
|
356 | let mut map = OwningDirstateMap::new_empty(dirstate_file_contents); | |
|
357 | let (on_disk, placeholder) = map.get_pair_mut(); | |
|
358 | let (inner, parents) = DirstateMap::new_v1(on_disk)?; | |
|
359 | self.dirstate_parents | |
|
360 | .set(parents.unwrap_or(DirstateParents::NULL)); | |
|
361 | *placeholder = inner; | |
|
352 | let (map, parents) = | |
|
353 | OwningDirstateMap::new_v1(dirstate_file_contents)?; | |
|
354 | self.dirstate_parents.set(parents); | |
|
362 | 355 | Ok(map) |
|
363 | 356 | } |
|
364 | 357 | } |
@@ -23,7 +23,6 b' use crate::{' | |||
|
23 | 23 | }; |
|
24 | 24 | use hg::{ |
|
25 | 25 | dirstate::StateMapIter, |
|
26 | dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap, | |
|
27 | 26 | dirstate_tree::on_disk::DirstateV2ParseError, |
|
28 | 27 | dirstate_tree::owning::OwningDirstateMap, |
|
29 | 28 | revlog::Node, |
@@ -53,18 +52,12 b' py_class!(pub class DirstateMap |py| {' | |||
|
53 | 52 | on_disk: PyBytes, |
|
54 | 53 | ) -> PyResult<PyObject> { |
|
55 | 54 | let on_disk = PyBytesDeref::new(py, on_disk); |
|
56 |
let |
|
|
57 | let (on_disk, map_placeholder) = map.get_pair_mut(); | |
|
58 | ||
|
59 | let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk) | |
|
55 | let (map, parents) = OwningDirstateMap::new_v1(on_disk) | |
|
60 | 56 | .map_err(|e| dirstate_error(py, e))?; |
|
61 | *map_placeholder = actual_map; | |
|
62 | 57 | let map = Self::create_instance(py, map)?; |
|
63 | let parents = parents.map(|p| { | |
|
64 |
|
|
|
65 | let p2 = PyBytes::new(py, p.p2.as_bytes()); | |
|
66 | (p1, p2) | |
|
67 | }); | |
|
58 | let p1 = PyBytes::new(py, parents.p1.as_bytes()); | |
|
59 | let p2 = PyBytes::new(py, parents.p2.as_bytes()); | |
|
60 | let parents = (p1, p2); | |
|
68 | 61 | Ok((map, parents).to_py_object(py).into_object()) |
|
69 | 62 | } |
|
70 | 63 | |
@@ -79,9 +72,7 b' py_class!(pub class DirstateMap |py| {' | |||
|
79 | 72 | PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e)) |
|
80 | 73 | }; |
|
81 | 74 | let on_disk = PyBytesDeref::new(py, on_disk); |
|
82 |
let |
|
|
83 | let (on_disk, map_placeholder) = map.get_pair_mut(); | |
|
84 | *map_placeholder = TreeDirstateMap::new_v2( | |
|
75 | let map = OwningDirstateMap::new_v2( | |
|
85 | 76 | on_disk, data_size, tree_metadata.data(py), |
|
86 | 77 | ).map_err(dirstate_error)?; |
|
87 | 78 | let map = Self::create_instance(py, map)?; |
@@ -127,25 +127,29 b' pub fn status_wrapper(' | |||
|
127 | 127 | // The caller may call `copymap.items()` separately |
|
128 | 128 | let list_copies = false; |
|
129 | 129 | |
|
130 | let after_status = |res: Result<(DirstateStatus<'_>, _), StatusError>| { | |
|
131 | let (status_res, warnings) = | |
|
132 | res.map_err(|e| handle_fallback(py, e))?; | |
|
133 | build_response(py, status_res, warnings) | |
|
134 | }; | |
|
135 | ||
|
130 | 136 | match matcher.get_type(py).name(py).borrow() { |
|
131 | 137 | "alwaysmatcher" => { |
|
132 | 138 | let matcher = AlwaysMatcher; |
|
133 | let (status_res, warnings) = dmap | |
|
134 |
|
|
|
135 | &matcher, | |
|
136 | root_dir.to_path_buf(), | |
|
137 | ignore_files, | |
|
138 |
|
|
|
139 |
|
|
|
140 |
|
|
|
141 |
|
|
|
142 |
|
|
|
143 |
|
|
|
144 | collect_traversed_dirs, | |
|
145 |
|
|
|
146 |
|
|
|
147 | .map_err(|e| handle_fallback(py, e))?; | |
|
148 | build_response(py, status_res, warnings) | |
|
139 | dmap.with_status( | |
|
140 | &matcher, | |
|
141 | root_dir.to_path_buf(), | |
|
142 | ignore_files, | |
|
143 | StatusOptions { | |
|
144 | check_exec, | |
|
145 | list_clean, | |
|
146 | list_ignored, | |
|
147 | list_unknown, | |
|
148 | list_copies, | |
|
149 | collect_traversed_dirs, | |
|
150 | }, | |
|
151 | after_status, | |
|
152 | ) | |
|
149 | 153 | } |
|
150 | 154 | "exactmatcher" => { |
|
151 | 155 | let files = matcher.call_method( |
@@ -167,22 +171,20 b' pub fn status_wrapper(' | |||
|
167 | 171 | let files = files?; |
|
168 | 172 | let matcher = FileMatcher::new(files.as_ref()) |
|
169 | 173 | .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?; |
|
170 | let (status_res, warnings) = dmap | |
|
171 |
|
|
|
172 | &matcher, | |
|
173 | root_dir.to_path_buf(), | |
|
174 | ignore_files, | |
|
175 |
|
|
|
176 |
|
|
|
177 |
|
|
|
178 |
|
|
|
179 |
|
|
|
180 |
|
|
|
181 | collect_traversed_dirs, | |
|
182 |
|
|
|
183 |
|
|
|
184 | .map_err(|e| handle_fallback(py, e))?; | |
|
185 | build_response(py, status_res, warnings) | |
|
174 | dmap.with_status( | |
|
175 | &matcher, | |
|
176 | root_dir.to_path_buf(), | |
|
177 | ignore_files, | |
|
178 | StatusOptions { | |
|
179 | check_exec, | |
|
180 | list_clean, | |
|
181 | list_ignored, | |
|
182 | list_unknown, | |
|
183 | list_copies, | |
|
184 | collect_traversed_dirs, | |
|
185 | }, | |
|
186 | after_status, | |
|
187 | ) | |
|
186 | 188 | } |
|
187 | 189 | "includematcher" => { |
|
188 | 190 | // Get the patterns from Python even though most of them are |
@@ -219,23 +221,20 b' pub fn status_wrapper(' | |||
|
219 | 221 | let matcher = IncludeMatcher::new(ignore_patterns) |
|
220 | 222 | .map_err(|e| handle_fallback(py, e.into()))?; |
|
221 | 223 | |
|
222 | let (status_res, warnings) = dmap | |
|
223 |
|
|
|
224 | &matcher, | |
|
225 | root_dir.to_path_buf(), | |
|
226 | ignore_files, | |
|
227 |
|
|
|
228 |
|
|
|
229 |
|
|
|
230 |
|
|
|
231 |
|
|
|
232 |
|
|
|
233 | collect_traversed_dirs, | |
|
234 |
|
|
|
235 |
|
|
|
236 | .map_err(|e| handle_fallback(py, e))?; | |
|
237 | ||
|
238 | build_response(py, status_res, warnings) | |
|
224 | dmap.with_status( | |
|
225 | &matcher, | |
|
226 | root_dir.to_path_buf(), | |
|
227 | ignore_files, | |
|
228 | StatusOptions { | |
|
229 | check_exec, | |
|
230 | list_clean, | |
|
231 | list_ignored, | |
|
232 | list_unknown, | |
|
233 | list_copies, | |
|
234 | collect_traversed_dirs, | |
|
235 | }, | |
|
236 | after_status, | |
|
237 | ) | |
|
239 | 238 | } |
|
240 | 239 | e => Err(PyErr::new::<ValueError, _>( |
|
241 | 240 | py, |
@@ -25,6 +25,9 b' use hg::utils::files::get_bytes_from_os_' | |||
|
25 | 25 | use hg::utils::files::get_bytes_from_path; |
|
26 | 26 | use hg::utils::files::get_path_from_bytes; |
|
27 | 27 | use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; |
|
28 | use hg::DirstateStatus; | |
|
29 | use hg::PatternFileWarning; | |
|
30 | use hg::StatusError; | |
|
28 | 31 | use hg::StatusOptions; |
|
29 | 32 | use log::info; |
|
30 | 33 | use std::io; |
@@ -230,117 +233,132 b' pub fn run(invocation: &crate::CliInvoca' | |||
|
230 | 233 | list_copies, |
|
231 | 234 | collect_traversed_dirs: false, |
|
232 | 235 | }; |
|
233 | let (mut ds_status, pattern_warnings) = dmap.status( | |
|
234 | &AlwaysMatcher, | |
|
235 | repo.working_directory_path().to_owned(), | |
|
236 | ignore_files(repo, config), | |
|
237 | options, | |
|
238 | )?; | |
|
239 | for warning in pattern_warnings { | |
|
240 | match warning { | |
|
241 | hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui | |
|
242 | .write_stderr(&format_bytes!( | |
|
243 | b"{}: ignoring invalid syntax '{}'\n", | |
|
244 | get_bytes_from_path(path), | |
|
245 | &*syntax | |
|
246 | ))?, | |
|
247 | hg::PatternFileWarning::NoSuchFile(path) => { | |
|
248 | let path = if let Ok(relative) = | |
|
249 | path.strip_prefix(repo.working_directory_path()) | |
|
250 | { | |
|
251 | relative | |
|
252 | } else { | |
|
253 | &*path | |
|
254 | }; | |
|
255 | ui.write_stderr(&format_bytes!( | |
|
256 | b"skipping unreadable pattern file '{}': \ | |
|
257 | No such file or directory\n", | |
|
258 | get_bytes_from_path(path), | |
|
259 | ))? | |
|
236 | ||
|
237 | type StatusResult<'a> = | |
|
238 | Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>; | |
|
239 | ||
|
240 | let after_status = |res: StatusResult| -> Result<_, CommandError> { | |
|
241 | let (mut ds_status, pattern_warnings) = res?; | |
|
242 | for warning in pattern_warnings { | |
|
243 | match warning { | |
|
244 | hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui | |
|
245 | .write_stderr(&format_bytes!( | |
|
246 | b"{}: ignoring invalid syntax '{}'\n", | |
|
247 | get_bytes_from_path(path), | |
|
248 | &*syntax | |
|
249 | ))?, | |
|
250 | hg::PatternFileWarning::NoSuchFile(path) => { | |
|
251 | let path = if let Ok(relative) = | |
|
252 | path.strip_prefix(repo.working_directory_path()) | |
|
253 | { | |
|
254 | relative | |
|
255 | } else { | |
|
256 | &*path | |
|
257 | }; | |
|
258 | ui.write_stderr(&format_bytes!( | |
|
259 | b"skipping unreadable pattern file '{}': \ | |
|
260 | No such file or directory\n", | |
|
261 | get_bytes_from_path(path), | |
|
262 | ))? | |
|
263 | } | |
|
260 | 264 | } |
|
261 | 265 | } |
|
262 | } | |
|
263 | 266 | |
|
264 | for (path, error) in ds_status.bad { | |
|
265 | let error = match error { | |
|
266 | hg::BadMatch::OsError(code) => { | |
|
267 | std::io::Error::from_raw_os_error(code).to_string() | |
|
268 | } | |
|
269 | hg::BadMatch::BadType(ty) => { | |
|
270 | format!("unsupported file type (type is {})", ty) | |
|
271 | } | |
|
272 | }; | |
|
273 | ui.write_stderr(&format_bytes!( | |
|
274 | b"{}: {}\n", | |
|
275 | path.as_bytes(), | |
|
276 | error.as_bytes() | |
|
277 | ))? | |
|
278 | } | |
|
279 | if !ds_status.unsure.is_empty() { | |
|
280 | info!( | |
|
281 | "Files to be rechecked by retrieval from filelog: {:?}", | |
|
282 | ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>() | |
|
283 | ); | |
|
284 | } | |
|
285 | let mut fixup = Vec::new(); | |
|
286 | if !ds_status.unsure.is_empty() | |
|
287 | && (display_states.modified || display_states.clean) | |
|
288 | { | |
|
289 | let p1 = repo.dirstate_parents()?.p1; | |
|
290 | let manifest = repo.manifest_for_node(p1).map_err(|e| { | |
|
291 | CommandError::from((e, &*format!("{:x}", p1.short()))) | |
|
292 | })?; | |
|
293 | for to_check in ds_status.unsure { | |
|
294 | if unsure_is_modified(repo, &manifest, &to_check.path)? { | |
|
295 | if display_states.modified { | |
|
296 | ds_status.modified.push(to_check); | |
|
267 | for (path, error) in ds_status.bad { | |
|
268 | let error = match error { | |
|
269 | hg::BadMatch::OsError(code) => { | |
|
270 | std::io::Error::from_raw_os_error(code).to_string() | |
|
271 | } | |
|
272 | hg::BadMatch::BadType(ty) => { | |
|
273 | format!("unsupported file type (type is {})", ty) | |
|
297 | 274 | } |
|
298 |
} |
|
|
299 | if display_states.clean { | |
|
300 | ds_status.clean.push(to_check.clone()); | |
|
275 | }; | |
|
276 | ui.write_stderr(&format_bytes!( | |
|
277 | b"{}: {}\n", | |
|
278 | path.as_bytes(), | |
|
279 | error.as_bytes() | |
|
280 | ))? | |
|
281 | } | |
|
282 | if !ds_status.unsure.is_empty() { | |
|
283 | info!( | |
|
284 | "Files to be rechecked by retrieval from filelog: {:?}", | |
|
285 | ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>() | |
|
286 | ); | |
|
287 | } | |
|
288 | let mut fixup = Vec::new(); | |
|
289 | if !ds_status.unsure.is_empty() | |
|
290 | && (display_states.modified || display_states.clean) | |
|
291 | { | |
|
292 | let p1 = repo.dirstate_parents()?.p1; | |
|
293 | let manifest = repo.manifest_for_node(p1).map_err(|e| { | |
|
294 | CommandError::from((e, &*format!("{:x}", p1.short()))) | |
|
295 | })?; | |
|
296 | for to_check in ds_status.unsure { | |
|
297 | if unsure_is_modified(repo, &manifest, &to_check.path)? { | |
|
298 | if display_states.modified { | |
|
299 | ds_status.modified.push(to_check); | |
|
300 | } | |
|
301 | } else { | |
|
302 | if display_states.clean { | |
|
303 | ds_status.clean.push(to_check.clone()); | |
|
304 | } | |
|
305 | fixup.push(to_check.path.into_owned()) | |
|
301 | 306 | } |
|
302 | fixup.push(to_check.path.into_owned()) | |
|
303 | 307 | } |
|
304 | 308 | } |
|
305 | } | |
|
306 | let relative_paths = (!ui.plain(None)) | |
|
307 | && config | |
|
308 | .get_option(b"commands", b"status.relative")? | |
|
309 | .unwrap_or(config.get_bool(b"ui", b"relative-paths")?); | |
|
310 | let output = DisplayStatusPaths { | |
|
311 |
|
|
|
312 | no_status, | |
|
313 | relativize: if relative_paths { | |
|
314 | Some(RelativizePaths::new(repo)?) | |
|
315 | } else { | |
|
316 |
|
|
|
317 |
} |
|
|
309 | let relative_paths = (!ui.plain(None)) | |
|
310 | && config | |
|
311 | .get_option(b"commands", b"status.relative")? | |
|
312 | .unwrap_or(config.get_bool(b"ui", b"relative-paths")?); | |
|
313 | let output = DisplayStatusPaths { | |
|
314 | ui, | |
|
315 | no_status, | |
|
316 | relativize: if relative_paths { | |
|
317 | Some(RelativizePaths::new(repo)?) | |
|
318 | } else { | |
|
319 | None | |
|
320 | }, | |
|
321 | }; | |
|
322 | if display_states.modified { | |
|
323 | output.display(b"M ", "status.modified", ds_status.modified)?; | |
|
324 | } | |
|
325 | if display_states.added { | |
|
326 | output.display(b"A ", "status.added", ds_status.added)?; | |
|
327 | } | |
|
328 | if display_states.removed { | |
|
329 | output.display(b"R ", "status.removed", ds_status.removed)?; | |
|
330 | } | |
|
331 | if display_states.deleted { | |
|
332 | output.display(b"! ", "status.deleted", ds_status.deleted)?; | |
|
333 | } | |
|
334 | if display_states.unknown { | |
|
335 | output.display(b"? ", "status.unknown", ds_status.unknown)?; | |
|
336 | } | |
|
337 | if display_states.ignored { | |
|
338 | output.display(b"I ", "status.ignored", ds_status.ignored)?; | |
|
339 | } | |
|
340 | if display_states.clean { | |
|
341 | output.display(b"C ", "status.clean", ds_status.clean)?; | |
|
342 | } | |
|
343 | ||
|
344 | let dirstate_write_needed = ds_status.dirty; | |
|
345 | let filesystem_time_at_status_start = | |
|
346 | ds_status.filesystem_time_at_status_start; | |
|
347 | ||
|
348 | Ok(( | |
|
349 | fixup, | |
|
350 | dirstate_write_needed, | |
|
351 | filesystem_time_at_status_start, | |
|
352 | )) | |
|
318 | 353 | }; |
|
319 | if display_states.modified { | |
|
320 | output.display(b"M ", "status.modified", ds_status.modified)?; | |
|
321 | } | |
|
322 | if display_states.added { | |
|
323 | output.display(b"A ", "status.added", ds_status.added)?; | |
|
324 | } | |
|
325 | if display_states.removed { | |
|
326 | output.display(b"R ", "status.removed", ds_status.removed)?; | |
|
327 | } | |
|
328 | if display_states.deleted { | |
|
329 | output.display(b"! ", "status.deleted", ds_status.deleted)?; | |
|
330 | } | |
|
331 | if display_states.unknown { | |
|
332 | output.display(b"? ", "status.unknown", ds_status.unknown)?; | |
|
333 | } | |
|
334 | if display_states.ignored { | |
|
335 | output.display(b"I ", "status.ignored", ds_status.ignored)?; | |
|
336 | } | |
|
337 | if display_states.clean { | |
|
338 | output.display(b"C ", "status.clean", ds_status.clean)?; | |
|
339 | } | |
|
340 | ||
|
341 | let mut dirstate_write_needed = ds_status.dirty; | |
|
342 | let filesystem_time_at_status_start = | |
|
343 | ds_status.filesystem_time_at_status_start; | |
|
354 | let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) = | |
|
355 | dmap.with_status( | |
|
356 | &AlwaysMatcher, | |
|
357 | repo.working_directory_path().to_owned(), | |
|
358 | ignore_files(repo, config), | |
|
359 | options, | |
|
360 | after_status, | |
|
361 | )?; | |
|
344 | 362 | |
|
345 | 363 | if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) |
|
346 | 364 | && !dirstate_write_needed |
General Comments 0
You need to be logged in to leave comments.
Login now