Show More
@@ -41,9 +41,3 b' default-features = false' | |||
|
41 | 41 | clap = "*" |
|
42 | 42 | pretty_assertions = "0.6.1" |
|
43 | 43 | tempfile = "3.1.0" |
|
44 | ||
|
45 | [features] | |
|
46 | # Use a (still unoptimized) tree for the dirstate instead of the current flat | |
|
47 | # dirstate. This is not yet recommended for performance reasons. A future | |
|
48 | # version might make it the default, or make it a runtime option. | |
|
49 | dirstate-tree = [] |
@@ -14,8 +14,6 b' use std::convert::TryFrom;' | |||
|
14 | 14 | |
|
15 | 15 | pub mod dirs_multiset; |
|
16 | 16 | pub mod dirstate_map; |
|
17 | #[cfg(feature = "dirstate-tree")] | |
|
18 | pub mod dirstate_tree; | |
|
19 | 17 | pub mod parsers; |
|
20 | 18 | pub mod status; |
|
21 | 19 | |
@@ -52,15 +50,9 b' struct RawEntry {' | |||
|
52 | 50 | /// merge. |
|
53 | 51 | pub const SIZE_FROM_OTHER_PARENT: i32 = -2; |
|
54 | 52 | |
|
55 | #[cfg(not(feature = "dirstate-tree"))] | |
|
56 | 53 | pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>; |
|
57 | #[cfg(not(feature = "dirstate-tree"))] | |
|
58 | 54 | pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>; |
|
59 | 55 | |
|
60 | #[cfg(feature = "dirstate-tree")] | |
|
61 | pub type StateMap = dirstate_tree::tree::Tree; | |
|
62 | #[cfg(feature = "dirstate-tree")] | |
|
63 | pub type StateMapIter<'a> = dirstate_tree::iter::Iter<'a>; | |
|
64 | 56 | pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>; |
|
65 | 57 | pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>; |
|
66 | 58 |
@@ -30,7 +30,6 b' impl DirsMultiset {' | |||
|
30 | 30 | /// Initializes the multiset from a dirstate. |
|
31 | 31 | /// |
|
32 | 32 | /// If `skip_state` is provided, skips dirstate entries with equal state. |
|
33 | #[cfg(not(feature = "dirstate-tree"))] | |
|
34 | 33 | pub fn from_dirstate( |
|
35 | 34 | dirstate: &StateMap, |
|
36 | 35 | skip_state: Option<EntryState>, |
@@ -51,30 +50,6 b' impl DirsMultiset {' | |||
|
51 | 50 | |
|
52 | 51 | Ok(multiset) |
|
53 | 52 | } |
|
54 | /// Initializes the multiset from a dirstate. | |
|
55 | /// | |
|
56 | /// If `skip_state` is provided, skips dirstate entries with equal state. | |
|
57 | #[cfg(feature = "dirstate-tree")] | |
|
58 | pub fn from_dirstate( | |
|
59 | dirstate: &StateMap, | |
|
60 | skip_state: Option<EntryState>, | |
|
61 | ) -> Result<Self, DirstateMapError> { | |
|
62 | let mut multiset = DirsMultiset { | |
|
63 | inner: FastHashMap::default(), | |
|
64 | }; | |
|
65 | for (filename, DirstateEntry { state, .. }) in dirstate.iter() { | |
|
66 | // This `if` is optimized out of the loop | |
|
67 | if let Some(skip) = skip_state { | |
|
68 | if skip != state { | |
|
69 | multiset.add_path(filename)?; | |
|
70 | } | |
|
71 | } else { | |
|
72 | multiset.add_path(filename)?; | |
|
73 | } | |
|
74 | } | |
|
75 | ||
|
76 | Ok(multiset) | |
|
77 | } | |
|
78 | 53 | |
|
79 | 54 | /// Initializes the multiset from a manifest. |
|
80 | 55 | pub fn from_manifest( |
@@ -254,7 +254,6 b' impl DirstateMap {' | |||
|
254 | 254 | ) |
|
255 | 255 | } |
|
256 | 256 | |
|
257 | #[cfg(not(feature = "dirstate-tree"))] | |
|
258 | 257 | pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { |
|
259 | 258 | if !force |
|
260 | 259 | && self.non_normal_set.is_some() |
@@ -283,34 +282,6 b' impl DirstateMap {' | |||
|
283 | 282 | self.non_normal_set = Some(non_normal); |
|
284 | 283 | self.other_parent_set = Some(other_parent); |
|
285 | 284 | } |
|
286 | #[cfg(feature = "dirstate-tree")] | |
|
287 | pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { | |
|
288 | if !force | |
|
289 | && self.non_normal_set.is_some() | |
|
290 | && self.other_parent_set.is_some() | |
|
291 | { | |
|
292 | return; | |
|
293 | } | |
|
294 | let mut non_normal = HashSet::new(); | |
|
295 | let mut other_parent = HashSet::new(); | |
|
296 | ||
|
297 | for ( | |
|
298 | filename, | |
|
299 | DirstateEntry { | |
|
300 | state, size, mtime, .. | |
|
301 | }, | |
|
302 | ) in self.state_map.iter() | |
|
303 | { | |
|
304 | if state != EntryState::Normal || mtime == MTIME_UNSET { | |
|
305 | non_normal.insert(filename.to_owned()); | |
|
306 | } | |
|
307 | if state == EntryState::Normal && size == SIZE_FROM_OTHER_PARENT { | |
|
308 | other_parent.insert(filename.to_owned()); | |
|
309 | } | |
|
310 | } | |
|
311 | self.non_normal_set = Some(non_normal); | |
|
312 | self.other_parent_set = Some(other_parent); | |
|
313 | } | |
|
314 | 285 | |
|
315 | 286 | /// Both of these setters and their uses appear to be the simplest way to |
|
316 | 287 | /// emulate a Python lazy property, but it is ugly and unidiomatic. |
@@ -426,7 +397,6 b' impl DirstateMap {' | |||
|
426 | 397 | self.set_non_normal_other_parent_entries(true); |
|
427 | 398 | Ok(packed) |
|
428 | 399 | } |
|
429 | #[cfg(not(feature = "dirstate-tree"))] | |
|
430 | 400 | pub fn build_file_fold_map(&mut self) -> &FileFoldMap { |
|
431 | 401 | if let Some(ref file_fold_map) = self.file_fold_map { |
|
432 | 402 | return file_fold_map; |
@@ -442,22 +412,6 b' impl DirstateMap {' | |||
|
442 | 412 | self.file_fold_map = Some(new_file_fold_map); |
|
443 | 413 | self.file_fold_map.as_ref().unwrap() |
|
444 | 414 | } |
|
445 | #[cfg(feature = "dirstate-tree")] | |
|
446 | pub fn build_file_fold_map(&mut self) -> &FileFoldMap { | |
|
447 | if let Some(ref file_fold_map) = self.file_fold_map { | |
|
448 | return file_fold_map; | |
|
449 | } | |
|
450 | let mut new_file_fold_map = FileFoldMap::default(); | |
|
451 | ||
|
452 | for (filename, DirstateEntry { state, .. }) in self.state_map.iter() { | |
|
453 | if state != EntryState::Removed { | |
|
454 | new_file_fold_map | |
|
455 | .insert(normalize_case(&filename), filename.to_owned()); | |
|
456 | } | |
|
457 | } | |
|
458 | self.file_fold_map = Some(new_file_fold_map); | |
|
459 | self.file_fold_map.as_ref().unwrap() | |
|
460 | } | |
|
461 | 415 | } |
|
462 | 416 | |
|
463 | 417 | #[cfg(test)] |
@@ -73,7 +73,6 b' pub fn parse_dirstate(mut contents: &[u8' | |||
|
73 | 73 | } |
|
74 | 74 | |
|
75 | 75 | /// `now` is the duration in seconds since the Unix epoch |
|
76 | #[cfg(not(feature = "dirstate-tree"))] | |
|
77 | 76 | pub fn pack_dirstate( |
|
78 | 77 | state_map: &mut StateMap, |
|
79 | 78 | copy_map: &CopyMap, |
@@ -146,79 +145,6 b' pub fn pack_dirstate(' | |||
|
146 | 145 | |
|
147 | 146 | Ok(packed) |
|
148 | 147 | } |
|
149 | /// `now` is the duration in seconds since the Unix epoch | |
|
150 | #[cfg(feature = "dirstate-tree")] | |
|
151 | pub fn pack_dirstate( | |
|
152 | state_map: &mut StateMap, | |
|
153 | copy_map: &CopyMap, | |
|
154 | parents: DirstateParents, | |
|
155 | now: Duration, | |
|
156 | ) -> Result<Vec<u8>, DirstatePackError> { | |
|
157 | // TODO move away from i32 before 2038. | |
|
158 | let now: i32 = now.as_secs().try_into().expect("time overflow"); | |
|
159 | ||
|
160 | let expected_size: usize = state_map | |
|
161 | .iter() | |
|
162 | .map(|(filename, _)| { | |
|
163 | let mut length = MIN_ENTRY_SIZE + filename.len(); | |
|
164 | if let Some(copy) = copy_map.get(&filename) { | |
|
165 | length += copy.len() + 1; | |
|
166 | } | |
|
167 | length | |
|
168 | }) | |
|
169 | .sum(); | |
|
170 | let expected_size = expected_size + PARENT_SIZE * 2; | |
|
171 | ||
|
172 | let mut packed = Vec::with_capacity(expected_size); | |
|
173 | let mut new_state_map = vec![]; | |
|
174 | ||
|
175 | packed.extend(&parents.p1); | |
|
176 | packed.extend(&parents.p2); | |
|
177 | ||
|
178 | for (filename, entry) in state_map.iter() { | |
|
179 | let new_filename = filename.to_owned(); | |
|
180 | let mut new_mtime: i32 = entry.mtime; | |
|
181 | if entry.state == EntryState::Normal && entry.mtime == now { | |
|
182 | // The file was last modified "simultaneously" with the current | |
|
183 | // write to dirstate (i.e. within the same second for file- | |
|
184 | // systems with a granularity of 1 sec). This commonly happens | |
|
185 | // for at least a couple of files on 'update'. | |
|
186 | // The user could change the file without changing its size | |
|
187 | // within the same second. Invalidate the file's mtime in | |
|
188 | // dirstate, forcing future 'status' calls to compare the | |
|
189 | // contents of the file if the size is the same. This prevents | |
|
190 | // mistakenly treating such files as clean. | |
|
191 | new_mtime = -1; | |
|
192 | new_state_map.push(( | |
|
193 | filename.to_owned(), | |
|
194 | DirstateEntry { | |
|
195 | mtime: new_mtime, | |
|
196 | ..entry | |
|
197 | }, | |
|
198 | )); | |
|
199 | } | |
|
200 | let mut new_filename = new_filename.into_vec(); | |
|
201 | if let Some(copy) = copy_map.get(&filename) { | |
|
202 | new_filename.push(b'\0'); | |
|
203 | new_filename.extend(copy.bytes()); | |
|
204 | } | |
|
205 | ||
|
206 | packed.write_u8(entry.state.into())?; | |
|
207 | packed.write_i32::<BigEndian>(entry.mode)?; | |
|
208 | packed.write_i32::<BigEndian>(entry.size)?; | |
|
209 | packed.write_i32::<BigEndian>(new_mtime)?; | |
|
210 | packed.write_i32::<BigEndian>(new_filename.len() as i32)?; | |
|
211 | packed.extend(new_filename) | |
|
212 | } | |
|
213 | ||
|
214 | if packed.len() != expected_size { | |
|
215 | return Err(DirstatePackError::BadSize(expected_size, packed.len())); | |
|
216 | } | |
|
217 | ||
|
218 | state_map.extend(new_state_map); | |
|
219 | ||
|
220 | Ok(packed) | |
|
221 | } | |
|
222 | 148 | |
|
223 | 149 | #[cfg(test)] |
|
224 | 150 | mod tests { |
@@ -9,9 +9,6 b'' | |||
|
9 | 9 | //! It is currently missing a lot of functionality compared to the Python one |
|
10 | 10 | //! and will only be triggered in narrow cases. |
|
11 | 11 | |
|
12 | #[cfg(feature = "dirstate-tree")] | |
|
13 | use crate::dirstate::dirstate_tree::iter::StatusShortcut; | |
|
14 | #[cfg(not(feature = "dirstate-tree"))] | |
|
15 | 12 | use crate::utils::path_auditor::PathAuditor; |
|
16 | 13 | use crate::{ |
|
17 | 14 | dirstate::SIZE_FROM_OTHER_PARENT, |
@@ -703,83 +700,6 b' where' | |||
|
703 | 700 | /// |
|
704 | 701 | /// This takes a mutable reference to the results to account for the |
|
705 | 702 | /// `extend` in timings |
|
706 | #[cfg(feature = "dirstate-tree")] | |
|
707 | #[timed] | |
|
708 | pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) { | |
|
709 | results.par_extend( | |
|
710 | self.dmap | |
|
711 | .fs_iter(self.root_dir.clone()) | |
|
712 | .par_bridge() | |
|
713 | .filter(|(path, _)| self.matcher.matches(path)) | |
|
714 | .map(move |(filename, shortcut)| { | |
|
715 | let entry = match shortcut { | |
|
716 | StatusShortcut::Entry(e) => e, | |
|
717 | StatusShortcut::Dispatch(d) => { | |
|
718 | return (Cow::Owned(filename), d) | |
|
719 | } | |
|
720 | }; | |
|
721 | let filename_as_path = match hg_path_to_path_buf(&filename) | |
|
722 | { | |
|
723 | Ok(f) => f, | |
|
724 | Err(_) => { | |
|
725 | return ( | |
|
726 | Cow::Owned(filename), | |
|
727 | INVALID_PATH_DISPATCH, | |
|
728 | ) | |
|
729 | } | |
|
730 | }; | |
|
731 | let meta = self | |
|
732 | .root_dir | |
|
733 | .join(filename_as_path) | |
|
734 | .symlink_metadata(); | |
|
735 | ||
|
736 | match meta { | |
|
737 | Ok(m) | |
|
738 | if !(m.file_type().is_file() | |
|
739 | || m.file_type().is_symlink()) => | |
|
740 | { | |
|
741 | ( | |
|
742 | Cow::Owned(filename), | |
|
743 | dispatch_missing(entry.state), | |
|
744 | ) | |
|
745 | } | |
|
746 | Ok(m) => { | |
|
747 | let dispatch = dispatch_found( | |
|
748 | &filename, | |
|
749 | entry, | |
|
750 | HgMetadata::from_metadata(m), | |
|
751 | &self.dmap.copy_map, | |
|
752 | self.options, | |
|
753 | ); | |
|
754 | (Cow::Owned(filename), dispatch) | |
|
755 | } | |
|
756 | Err(e) | |
|
757 | if e.kind() == ErrorKind::NotFound | |
|
758 | || e.raw_os_error() == Some(20) => | |
|
759 | { | |
|
760 | // Rust does not yet have an `ErrorKind` for | |
|
761 | // `NotADirectory` (errno 20) | |
|
762 | // It happens if the dirstate contains `foo/bar` | |
|
763 | // and foo is not a | |
|
764 | // directory | |
|
765 | ( | |
|
766 | Cow::Owned(filename), | |
|
767 | dispatch_missing(entry.state), | |
|
768 | ) | |
|
769 | } | |
|
770 | Err(e) => { | |
|
771 | (Cow::Owned(filename), dispatch_os_error(&e)) | |
|
772 | } | |
|
773 | } | |
|
774 | }), | |
|
775 | ); | |
|
776 | } | |
|
777 | ||
|
778 | /// Add the files in the dirstate to the results. | |
|
779 | /// | |
|
780 | /// This takes a mutable reference to the results to account for the | |
|
781 | /// `extend` in timings | |
|
782 | #[cfg(not(feature = "dirstate-tree"))] | |
|
783 | 703 | #[timed] |
|
784 | 704 | pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) { |
|
785 | 705 | results.par_extend( |
@@ -850,7 +770,6 b' where' | |||
|
850 | 770 | /// |
|
851 | 771 | /// This takes a mutable reference to the results to account for the |
|
852 | 772 | /// `extend` in timings |
|
853 | #[cfg(not(feature = "dirstate-tree"))] | |
|
854 | 773 | #[timed] |
|
855 | 774 | pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) { |
|
856 | 775 | let to_visit: Vec<(&HgPath, &DirstateEntry)> = |
@@ -14,66 +14,6 b' use crate::{DirstateStatus, StatusError}' | |||
|
14 | 14 | /// files. |
|
15 | 15 | pub type LookupAndStatus<'a> = (Vec<HgPathCow<'a>>, DirstateStatus<'a>); |
|
16 | 16 | |
|
17 | #[cfg(feature = "dirstate-tree")] | |
|
18 | impl<'a, M: Matcher + Sync> Status<'a, M> { | |
|
19 | pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> { | |
|
20 | let (traversed_sender, traversed_receiver) = | |
|
21 | crossbeam_channel::unbounded(); | |
|
22 | ||
|
23 | // Step 1: check the files explicitly mentioned by the user | |
|
24 | let (work, mut results) = self.walk_explicit(traversed_sender.clone()); | |
|
25 | ||
|
26 | // Step 2: Check files in the dirstate | |
|
27 | if !self.matcher.is_exact() { | |
|
28 | self.extend_from_dmap(&mut results); | |
|
29 | } | |
|
30 | // Step 3: Check the working directory if listing unknowns | |
|
31 | if !work.is_empty() { | |
|
32 | // Hashmaps are quite a bit slower to build than vecs, so only | |
|
33 | // build it if needed. | |
|
34 | let mut old_results = None; | |
|
35 | ||
|
36 | // Step 2: recursively check the working directory for changes if | |
|
37 | // needed | |
|
38 | for (dir, dispatch) in work { | |
|
39 | match dispatch { | |
|
40 | Dispatch::Directory { was_file } => { | |
|
41 | if was_file { | |
|
42 | results.push((dir.to_owned(), Dispatch::Removed)); | |
|
43 | } | |
|
44 | if self.options.list_ignored | |
|
45 | || self.options.list_unknown | |
|
46 | && !self.dir_ignore(&dir) | |
|
47 | { | |
|
48 | if old_results.is_none() { | |
|
49 | old_results = | |
|
50 | Some(results.iter().cloned().collect()); | |
|
51 | } | |
|
52 | self.traverse( | |
|
53 | &dir, | |
|
54 | old_results | |
|
55 | .as_ref() | |
|
56 | .expect("old results should exist"), | |
|
57 | &mut results, | |
|
58 | traversed_sender.clone(), | |
|
59 | ); | |
|
60 | } | |
|
61 | } | |
|
62 | _ => { | |
|
63 | unreachable!("There can only be directories in `work`") | |
|
64 | } | |
|
65 | } | |
|
66 | } | |
|
67 | } | |
|
68 | ||
|
69 | drop(traversed_sender); | |
|
70 | let traversed = traversed_receiver.into_iter().collect(); | |
|
71 | ||
|
72 | Ok(build_response(results, traversed)) | |
|
73 | } | |
|
74 | } | |
|
75 | ||
|
76 | #[cfg(not(feature = "dirstate-tree"))] | |
|
77 | 17 | impl<'a, M: Matcher + Sync> Status<'a, M> { |
|
78 | 18 | pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> { |
|
79 | 19 | let (traversed_sender, traversed_receiver) = |
@@ -10,7 +10,6 b' crate-type = ["cdylib"]' | |||
|
10 | 10 | |
|
11 | 11 | [features] |
|
12 | 12 | default = ["python27"] |
|
13 | dirstate-tree = ["hg-core/dirstate-tree"] | |
|
14 | 13 | |
|
15 | 14 | # Features to build an extension module: |
|
16 | 15 | python27 = ["cpython/python27-sys", "cpython/extension-module-2-7"] |
@@ -547,14 +547,12 b' impl DirstateMap {' | |||
|
547 | 547 | ) -> Ref<'a, RustDirstateMap> { |
|
548 | 548 | self.inner(py).borrow() |
|
549 | 549 | } |
|
550 | #[cfg(not(feature = "dirstate-tree"))] | |
|
551 | 550 | fn translate_key( |
|
552 | 551 | py: Python, |
|
553 | 552 | res: (&HgPathBuf, &DirstateEntry), |
|
554 | 553 | ) -> PyResult<Option<PyBytes>> { |
|
555 | 554 | Ok(Some(PyBytes::new(py, res.0.as_bytes()))) |
|
556 | 555 | } |
|
557 | #[cfg(not(feature = "dirstate-tree"))] | |
|
558 | 556 | fn translate_key_value( |
|
559 | 557 | py: Python, |
|
560 | 558 | res: (&HgPathBuf, &DirstateEntry), |
@@ -565,24 +563,6 b' impl DirstateMap {' | |||
|
565 | 563 | make_dirstate_tuple(py, &entry)?, |
|
566 | 564 | ))) |
|
567 | 565 | } |
|
568 | #[cfg(feature = "dirstate-tree")] | |
|
569 | fn translate_key( | |
|
570 | py: Python, | |
|
571 | res: (HgPathBuf, DirstateEntry), | |
|
572 | ) -> PyResult<Option<PyBytes>> { | |
|
573 | Ok(Some(PyBytes::new(py, res.0.as_bytes()))) | |
|
574 | } | |
|
575 | #[cfg(feature = "dirstate-tree")] | |
|
576 | fn translate_key_value( | |
|
577 | py: Python, | |
|
578 | res: (HgPathBuf, DirstateEntry), | |
|
579 | ) -> PyResult<Option<(PyBytes, PyObject)>> { | |
|
580 | let (f, entry) = res; | |
|
581 | Ok(Some(( | |
|
582 | PyBytes::new(py, f.as_bytes()), | |
|
583 | make_dirstate_tuple(py, &entry)?, | |
|
584 | ))) | |
|
585 | } | |
|
586 | 566 | } |
|
587 | 567 | |
|
588 | 568 | py_shared_iterator!( |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now