Show More
@@ -38,8 +38,15 b' pub struct DirstateEntry {' | |||
|
38 | 38 | /// merge. |
|
39 | 39 | pub const SIZE_FROM_OTHER_PARENT: i32 = -2; |
|
40 | 40 | |
|
41 | #[cfg(not(feature = "dirstate-tree"))] | |
|
41 | 42 | pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>; |
|
43 | #[cfg(not(feature = "dirstate-tree"))] | |
|
42 | 44 | pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>; |
|
45 | ||
|
46 | #[cfg(feature = "dirstate-tree")] | |
|
47 | pub type StateMap = dirstate_tree::tree::Tree; | |
|
48 | #[cfg(feature = "dirstate-tree")] | |
|
49 | pub type StateMapIter<'a> = dirstate_tree::iter::Iter<'a>; | |
|
43 | 50 | pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>; |
|
44 | 51 | pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>; |
|
45 | 52 |
@@ -14,7 +14,7 b' use crate::{' | |||
|
14 | 14 | files, |
|
15 | 15 | hg_path::{HgPath, HgPathBuf, HgPathError}, |
|
16 | 16 | }, |
|
17 | DirstateEntry, DirstateMapError, FastHashMap, | |
|
17 | DirstateEntry, DirstateMapError, FastHashMap, StateMap, | |
|
18 | 18 | }; |
|
19 | 19 | use std::collections::{hash_map, hash_map::Entry, HashMap, HashSet}; |
|
20 | 20 | |
@@ -30,15 +30,15 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"))] | |
|
33 | 34 | pub fn from_dirstate( |
|
34 | dirstate: &FastHashMap<HgPathBuf, DirstateEntry>, | |
|
35 | dirstate: &StateMap, | |
|
35 | 36 | skip_state: Option<EntryState>, |
|
36 | 37 | ) -> Result<Self, DirstateMapError> { |
|
37 | 38 | let mut multiset = DirsMultiset { |
|
38 | 39 | inner: FastHashMap::default(), |
|
39 | 40 | }; |
|
40 | ||
|
41 | for (filename, DirstateEntry { state, .. }) in dirstate { | |
|
41 | for (filename, DirstateEntry { state, .. }) in dirstate.iter() { | |
|
42 | 42 | // This `if` is optimized out of the loop |
|
43 | 43 | if let Some(skip) = skip_state { |
|
44 | 44 | if skip != *state { |
@@ -51,6 +51,30 b' impl DirsMultiset {' | |||
|
51 | 51 | |
|
52 | 52 | Ok(multiset) |
|
53 | 53 | } |
|
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 | } | |
|
54 | 78 | |
|
55 | 79 | /// Initializes the multiset from a manifest. |
|
56 | 80 | pub fn from_manifest( |
@@ -332,8 +356,8 b' mod tests {' | |||
|
332 | 356 | }; |
|
333 | 357 | assert_eq!(expected, new); |
|
334 | 358 | |
|
335 | let new = DirsMultiset::from_dirstate(&FastHashMap::default(), None) | |
|
336 | .unwrap(); | |
|
359 | let new = | |
|
360 | DirsMultiset::from_dirstate(&StateMap::default(), None).unwrap(); | |
|
337 | 361 | let expected = DirsMultiset { |
|
338 | 362 | inner: FastHashMap::default(), |
|
339 | 363 | }; |
@@ -357,7 +381,7 b' mod tests {' | |||
|
357 | 381 | }; |
|
358 | 382 | assert_eq!(expected, new); |
|
359 | 383 | |
|
360 |
let input_map = [" |
|
|
384 | let input_map = ["b/x", "a/c", "a/d/x"] | |
|
361 | 385 | .iter() |
|
362 | 386 | .map(|f| { |
|
363 | 387 | ( |
@@ -371,7 +395,7 b' mod tests {' | |||
|
371 | 395 | ) |
|
372 | 396 | }) |
|
373 | 397 | .collect(); |
|
374 |
let expected_inner = [("", 2), ("a", |
|
|
398 | let expected_inner = [("", 2), ("a", 2), ("b", 1), ("a/d", 1)] | |
|
375 | 399 | .iter() |
|
376 | 400 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) |
|
377 | 401 | .collect(); |
@@ -387,9 +411,9 b' mod tests {' | |||
|
387 | 411 | fn test_dirsmultiset_new_skip() { |
|
388 | 412 | let input_map = [ |
|
389 | 413 | ("a/", EntryState::Normal), |
|
390 |
("a/b |
|
|
414 | ("a/b", EntryState::Normal), | |
|
391 | 415 | ("a/c", EntryState::Removed), |
|
392 |
("a/d |
|
|
416 | ("a/d", EntryState::Merged), | |
|
393 | 417 | ] |
|
394 | 418 | .iter() |
|
395 | 419 | .map(|(f, state)| { |
@@ -406,7 +430,7 b' mod tests {' | |||
|
406 | 430 | .collect(); |
|
407 | 431 | |
|
408 | 432 | // "a" incremented with "a/c" and "a/d/" |
|
409 |
let expected_inner = [("", 1), ("a", 2) |
|
|
433 | let expected_inner = [("", 1), ("a", 2)] | |
|
410 | 434 | .iter() |
|
411 | 435 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) |
|
412 | 436 | .collect(); |
@@ -16,7 +16,6 b' use crate::{' | |||
|
16 | 16 | CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError, |
|
17 | 17 | DirstateParents, DirstateParseError, FastHashMap, StateMap, |
|
18 | 18 | }; |
|
19 | use core::borrow::Borrow; | |
|
20 | 19 | use micro_timer::timed; |
|
21 | 20 | use std::collections::HashSet; |
|
22 | 21 | use std::convert::TryInto; |
@@ -67,7 +66,7 b' impl DirstateMap {' | |||
|
67 | 66 | } |
|
68 | 67 | |
|
69 | 68 | pub fn clear(&mut self) { |
|
70 |
self.state_map |
|
|
69 | self.state_map = StateMap::default(); | |
|
71 | 70 | self.copy_map.clear(); |
|
72 | 71 | self.file_fold_map = None; |
|
73 | 72 | self.non_normal_set = None; |
@@ -189,18 +188,15 b' impl DirstateMap {' | |||
|
189 | 188 | ) { |
|
190 | 189 | for filename in filenames { |
|
191 | 190 | let mut changed = false; |
|
192 | self.state_map | |
|
193 | .entry(filename.to_owned()) | |
|
194 | .and_modify(|entry| { | |
|
195 | if entry.state == EntryState::Normal && entry.mtime == now | |
|
196 | { | |
|
191 | if let Some(entry) = self.state_map.get_mut(&filename) { | |
|
192 | if entry.state == EntryState::Normal && entry.mtime == now { | |
|
197 | 193 |
|
|
198 | 194 |
|
|
199 | 195 |
|
|
200 | 196 |
|
|
201 | 197 |
|
|
202 | 198 |
|
|
203 |
|
|
|
199 | } | |
|
204 | 200 | if changed { |
|
205 | 201 | self.get_non_normal_other_parent_entries() |
|
206 | 202 | .0 |
@@ -257,6 +253,7 b' impl DirstateMap {' | |||
|
257 | 253 | ) |
|
258 | 254 | } |
|
259 | 255 | |
|
256 | #[cfg(not(feature = "dirstate-tree"))] | |
|
260 | 257 | pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { |
|
261 | 258 | if !force |
|
262 | 259 | && self.non_normal_set.is_some() |
@@ -285,6 +282,34 b' impl DirstateMap {' | |||
|
285 | 282 | self.non_normal_set = Some(non_normal); |
|
286 | 283 | self.other_parent_set = Some(other_parent); |
|
287 | 284 | } |
|
285 | #[cfg(feature = "dirstate-tree")] | |
|
286 | pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { | |
|
287 | if !force | |
|
288 | && self.non_normal_set.is_some() | |
|
289 | && self.other_parent_set.is_some() | |
|
290 | { | |
|
291 | return; | |
|
292 | } | |
|
293 | let mut non_normal = HashSet::new(); | |
|
294 | let mut other_parent = HashSet::new(); | |
|
295 | ||
|
296 | for ( | |
|
297 | filename, | |
|
298 | DirstateEntry { | |
|
299 | state, size, mtime, .. | |
|
300 | }, | |
|
301 | ) in self.state_map.iter() | |
|
302 | { | |
|
303 | if state != EntryState::Normal || mtime == MTIME_UNSET { | |
|
304 | non_normal.insert(filename.to_owned()); | |
|
305 | } | |
|
306 | if state == EntryState::Normal && size == SIZE_FROM_OTHER_PARENT { | |
|
307 | other_parent.insert(filename.to_owned()); | |
|
308 | } | |
|
309 | } | |
|
310 | self.non_normal_set = Some(non_normal); | |
|
311 | self.other_parent_set = Some(other_parent); | |
|
312 | } | |
|
288 | 313 | |
|
289 | 314 | /// Both of these setters and their uses appear to be the simplest way to |
|
290 | 315 | /// emulate a Python lazy property, but it is ugly and unidiomatic. |
@@ -398,17 +423,33 b' impl DirstateMap {' | |||
|
398 | 423 | self.set_non_normal_other_parent_entries(true); |
|
399 | 424 | Ok(packed) |
|
400 | 425 | } |
|
401 | ||
|
426 | #[cfg(not(feature = "dirstate-tree"))] | |
|
402 | 427 | pub fn build_file_fold_map(&mut self) -> &FileFoldMap { |
|
403 | 428 | if let Some(ref file_fold_map) = self.file_fold_map { |
|
404 | 429 | return file_fold_map; |
|
405 | 430 | } |
|
406 | 431 | let mut new_file_fold_map = FileFoldMap::default(); |
|
407 | for (filename, DirstateEntry { state, .. }) in self.state_map.borrow() | |
|
408 | { | |
|
432 | ||
|
433 | for (filename, DirstateEntry { state, .. }) in self.state_map.iter() { | |
|
409 | 434 | if *state == EntryState::Removed { |
|
410 | 435 | new_file_fold_map |
|
411 | .insert(normalize_case(filename), filename.to_owned()); | |
|
436 | .insert(normalize_case(&filename), filename.to_owned()); | |
|
437 | } | |
|
438 | } | |
|
439 | self.file_fold_map = Some(new_file_fold_map); | |
|
440 | self.file_fold_map.as_ref().unwrap() | |
|
441 | } | |
|
442 | #[cfg(feature = "dirstate-tree")] | |
|
443 | pub fn build_file_fold_map(&mut self) -> &FileFoldMap { | |
|
444 | if let Some(ref file_fold_map) = self.file_fold_map { | |
|
445 | return file_fold_map; | |
|
446 | } | |
|
447 | let mut new_file_fold_map = FileFoldMap::default(); | |
|
448 | ||
|
449 | for (filename, DirstateEntry { state, .. }) in self.state_map.iter() { | |
|
450 | if state == EntryState::Removed { | |
|
451 | new_file_fold_map | |
|
452 | .insert(normalize_case(&filename), filename.to_owned()); | |
|
412 | 453 | } |
|
413 | 454 | } |
|
414 | 455 | self.file_fold_map = Some(new_file_fold_map); |
@@ -80,11 +80,11 b' pub fn parse_dirstate(' | |||
|
80 | 80 | )); |
|
81 | 81 | curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len); |
|
82 | 82 | } |
|
83 | ||
|
84 | 83 | Ok((parents, entries, copies)) |
|
85 | 84 | } |
|
86 | 85 | |
|
87 | 86 | /// `now` is the duration in seconds since the Unix epoch |
|
87 | #[cfg(not(feature = "dirstate-tree"))] | |
|
88 | 88 | pub fn pack_dirstate( |
|
89 | 89 | state_map: &mut StateMap, |
|
90 | 90 | copy_map: &CopyMap, |
@@ -156,15 +156,89 b' pub fn pack_dirstate(' | |||
|
156 | 156 | |
|
157 | 157 | Ok(packed) |
|
158 | 158 | } |
|
159 | /// `now` is the duration in seconds since the Unix epoch | |
|
160 | #[cfg(feature = "dirstate-tree")] | |
|
161 | pub fn pack_dirstate( | |
|
162 | state_map: &mut StateMap, | |
|
163 | copy_map: &CopyMap, | |
|
164 | parents: DirstateParents, | |
|
165 | now: Duration, | |
|
166 | ) -> Result<Vec<u8>, DirstatePackError> { | |
|
167 | // TODO move away from i32 before 2038. | |
|
168 | let now: i32 = now.as_secs().try_into().expect("time overflow"); | |
|
169 | ||
|
170 | let expected_size: usize = state_map | |
|
171 | .iter() | |
|
172 | .map(|(filename, _)| { | |
|
173 | let mut length = MIN_ENTRY_SIZE + filename.len(); | |
|
174 | if let Some(copy) = copy_map.get(&filename) { | |
|
175 | length += copy.len() + 1; | |
|
176 | } | |
|
177 | length | |
|
178 | }) | |
|
179 | .sum(); | |
|
180 | let expected_size = expected_size + PARENT_SIZE * 2; | |
|
181 | ||
|
182 | let mut packed = Vec::with_capacity(expected_size); | |
|
183 | let mut new_state_map = vec![]; | |
|
184 | ||
|
185 | packed.extend(&parents.p1); | |
|
186 | packed.extend(&parents.p2); | |
|
187 | ||
|
188 | for (filename, entry) in state_map.iter() { | |
|
189 | let new_filename = filename.to_owned(); | |
|
190 | let mut new_mtime: i32 = entry.mtime; | |
|
191 | if entry.state == EntryState::Normal && entry.mtime == now { | |
|
192 | // The file was last modified "simultaneously" with the current | |
|
193 | // write to dirstate (i.e. within the same second for file- | |
|
194 | // systems with a granularity of 1 sec). This commonly happens | |
|
195 | // for at least a couple of files on 'update'. | |
|
196 | // The user could change the file without changing its size | |
|
197 | // within the same second. Invalidate the file's mtime in | |
|
198 | // dirstate, forcing future 'status' calls to compare the | |
|
199 | // contents of the file if the size is the same. This prevents | |
|
200 | // mistakenly treating such files as clean. | |
|
201 | new_mtime = -1; | |
|
202 | new_state_map.push(( | |
|
203 | filename.to_owned(), | |
|
204 | DirstateEntry { | |
|
205 | mtime: new_mtime, | |
|
206 | ..entry | |
|
207 | }, | |
|
208 | )); | |
|
209 | } | |
|
210 | let mut new_filename = new_filename.into_vec(); | |
|
211 | if let Some(copy) = copy_map.get(&filename) { | |
|
212 | new_filename.push(b'\0'); | |
|
213 | new_filename.extend(copy.bytes()); | |
|
214 | } | |
|
215 | ||
|
216 | packed.write_u8(entry.state.into())?; | |
|
217 | packed.write_i32::<BigEndian>(entry.mode)?; | |
|
218 | packed.write_i32::<BigEndian>(entry.size)?; | |
|
219 | packed.write_i32::<BigEndian>(new_mtime)?; | |
|
220 | packed.write_i32::<BigEndian>(new_filename.len() as i32)?; | |
|
221 | packed.extend(new_filename) | |
|
222 | } | |
|
223 | ||
|
224 | if packed.len() != expected_size { | |
|
225 | return Err(DirstatePackError::BadSize(expected_size, packed.len())); | |
|
226 | } | |
|
227 | ||
|
228 | state_map.extend(new_state_map); | |
|
229 | ||
|
230 | Ok(packed) | |
|
231 | } | |
|
159 | 232 | |
|
160 | 233 | #[cfg(test)] |
|
161 | 234 | mod tests { |
|
162 | 235 | use super::*; |
|
163 | 236 | use crate::{utils::hg_path::HgPathBuf, FastHashMap}; |
|
237 | use pretty_assertions::assert_eq; | |
|
164 | 238 | |
|
165 | 239 | #[test] |
|
166 | 240 | fn test_pack_dirstate_empty() { |
|
167 |
let mut state_map |
|
|
241 | let mut state_map = StateMap::default(); | |
|
168 | 242 | let copymap = FastHashMap::default(); |
|
169 | 243 | let parents = DirstateParents { |
|
170 | 244 | p1: *b"12345678910111213141", |
@@ -9,6 +9,10 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 | use crate::utils::path_auditor::PathAuditor; | |
|
12 | 16 | use crate::{ |
|
13 | 17 | dirstate::SIZE_FROM_OTHER_PARENT, |
|
14 | 18 | filepatterns::PatternFileWarning, |
@@ -19,7 +23,6 b' use crate::{' | |||
|
19 | 23 | hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf, |
|
20 | 24 | HgPathError, |
|
21 | 25 | }, |
|
22 | path_auditor::PathAuditor, | |
|
23 | 26 | }, |
|
24 | 27 | CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap, |
|
25 | 28 | PatternError, |
@@ -701,12 +704,131 b' where' | |||
|
701 | 704 | }) |
|
702 | 705 | } |
|
703 | 706 | |
|
707 | /// Add the files in the dirstate to the results. | |
|
708 | /// | |
|
709 | /// This takes a mutable reference to the results to account for the | |
|
710 | /// `extend` in timings | |
|
711 | #[cfg(feature = "dirstate-tree")] | |
|
712 | #[timed] | |
|
713 | pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) { | |
|
714 | results.par_extend( | |
|
715 | self.dmap | |
|
716 | .fs_iter(self.root_dir.clone()) | |
|
717 | .par_bridge() | |
|
718 | .filter(|(path, _)| self.matcher.matches(path)) | |
|
719 | .flat_map(move |(filename, shortcut)| { | |
|
720 | let entry = match shortcut { | |
|
721 | StatusShortcut::Entry(e) => e, | |
|
722 | StatusShortcut::Dispatch(d) => { | |
|
723 | return Ok((Cow::Owned(filename), d)) | |
|
724 | } | |
|
725 | }; | |
|
726 | let filename_as_path = hg_path_to_path_buf(&filename)?; | |
|
727 | let meta = self | |
|
728 | .root_dir | |
|
729 | .join(filename_as_path) | |
|
730 | .symlink_metadata(); | |
|
731 | ||
|
732 | match meta { | |
|
733 | Ok(ref m) | |
|
734 | if !(m.file_type().is_file() | |
|
735 | || m.file_type().is_symlink()) => | |
|
736 | { | |
|
737 | Ok(( | |
|
738 | Cow::Owned(filename), | |
|
739 | dispatch_missing(entry.state), | |
|
740 | )) | |
|
741 | } | |
|
742 | Ok(m) => { | |
|
743 | let dispatch = dispatch_found( | |
|
744 | &filename, | |
|
745 | entry, | |
|
746 | HgMetadata::from_metadata(m), | |
|
747 | &self.dmap.copy_map, | |
|
748 | self.options, | |
|
749 | ); | |
|
750 | Ok((Cow::Owned(filename), dispatch)) | |
|
751 | } | |
|
752 | Err(ref e) | |
|
753 | if e.kind() == ErrorKind::NotFound | |
|
754 | || e.raw_os_error() == Some(20) => | |
|
755 | { | |
|
756 | // Rust does not yet have an `ErrorKind` for | |
|
757 | // `NotADirectory` (errno 20) | |
|
758 | // It happens if the dirstate contains `foo/bar` | |
|
759 | // and foo is not a | |
|
760 | // directory | |
|
761 | Ok(( | |
|
762 | Cow::Owned(filename), | |
|
763 | dispatch_missing(entry.state), | |
|
764 | )) | |
|
765 | } | |
|
766 | Err(e) => Err(e), | |
|
767 | } | |
|
768 | }), | |
|
769 | ); | |
|
770 | } | |
|
771 | ||
|
772 | /// Add the files in the dirstate to the results. | |
|
773 | /// | |
|
774 | /// This takes a mutable reference to the results to account for the | |
|
775 | /// `extend` in timings | |
|
776 | #[cfg(not(feature = "dirstate-tree"))] | |
|
777 | #[timed] | |
|
778 | pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) { | |
|
779 | results.par_extend(self.dmap.par_iter().flat_map( | |
|
780 | move |(filename, entry)| { | |
|
781 | let filename: &HgPath = filename; | |
|
782 | let filename_as_path = hg_path_to_path_buf(filename)?; | |
|
783 | let meta = | |
|
784 | self.root_dir.join(filename_as_path).symlink_metadata(); | |
|
785 | match meta { | |
|
786 | Ok(ref m) | |
|
787 | if !(m.file_type().is_file() | |
|
788 | || m.file_type().is_symlink()) => | |
|
789 | { | |
|
790 | Ok(( | |
|
791 | Cow::Borrowed(filename), | |
|
792 | dispatch_missing(entry.state), | |
|
793 | )) | |
|
794 | } | |
|
795 | Ok(m) => Ok(( | |
|
796 | Cow::Borrowed(filename), | |
|
797 | dispatch_found( | |
|
798 | filename, | |
|
799 | *entry, | |
|
800 | HgMetadata::from_metadata(m), | |
|
801 | &self.dmap.copy_map, | |
|
802 | self.options, | |
|
803 | ), | |
|
804 | )), | |
|
805 | Err(ref e) | |
|
806 | if e.kind() == ErrorKind::NotFound | |
|
807 | || e.raw_os_error() == Some(20) => | |
|
808 | { | |
|
809 | // Rust does not yet have an `ErrorKind` for | |
|
810 | // `NotADirectory` (errno 20) | |
|
811 | // It happens if the dirstate contains `foo/bar` | |
|
812 | // and foo is not a | |
|
813 | // directory | |
|
814 | Ok(( | |
|
815 | Cow::Borrowed(filename), | |
|
816 | dispatch_missing(entry.state), | |
|
817 | )) | |
|
818 | } | |
|
819 | Err(e) => Err(e), | |
|
820 | } | |
|
821 | }, | |
|
822 | )); | |
|
823 | } | |
|
824 | ||
|
704 | 825 | /// Checks all files that are in the dirstate but were not found during the |
|
705 | 826 | /// working directory traversal. This means that the rest must |
|
706 | 827 | /// be either ignored, under a symlink or under a new nested repo. |
|
707 | 828 | /// |
|
708 | 829 | /// This takes a mutable reference to the results to account for the |
|
709 | 830 | /// `extend` in timings |
|
831 | #[cfg(not(feature = "dirstate-tree"))] | |
|
710 | 832 | #[timed] |
|
711 | 833 | pub fn handle_unknowns( |
|
712 | 834 | &self, |
@@ -781,59 +903,6 b' where' | |||
|
781 | 903 | |
|
782 | 904 | Ok(()) |
|
783 | 905 | } |
|
784 | ||
|
785 | /// Add the files in the dirstate to the results. | |
|
786 | /// | |
|
787 | /// This takes a mutable reference to the results to account for the | |
|
788 | /// `extend` in timings | |
|
789 | #[timed] | |
|
790 | pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) { | |
|
791 | results.par_extend(self.dmap.par_iter().flat_map( | |
|
792 | move |(filename, entry)| { | |
|
793 | let filename: &HgPath = filename; | |
|
794 | let filename_as_path = hg_path_to_path_buf(filename)?; | |
|
795 | let meta = | |
|
796 | self.root_dir.join(filename_as_path).symlink_metadata(); | |
|
797 | ||
|
798 | match meta { | |
|
799 | Ok(ref m) | |
|
800 | if !(m.file_type().is_file() | |
|
801 | || m.file_type().is_symlink()) => | |
|
802 | { | |
|
803 | Ok(( | |
|
804 | Cow::Borrowed(filename), | |
|
805 | dispatch_missing(entry.state), | |
|
806 | )) | |
|
807 | } | |
|
808 | Ok(m) => Ok(( | |
|
809 | Cow::Borrowed(filename), | |
|
810 | dispatch_found( | |
|
811 | filename, | |
|
812 | *entry, | |
|
813 | HgMetadata::from_metadata(m), | |
|
814 | &self.dmap.copy_map, | |
|
815 | self.options, | |
|
816 | ), | |
|
817 | )), | |
|
818 | Err(ref e) | |
|
819 | if e.kind() == ErrorKind::NotFound | |
|
820 | || e.raw_os_error() == Some(20) => | |
|
821 | { | |
|
822 | // Rust does not yet have an `ErrorKind` for | |
|
823 | // `NotADirectory` (errno 20) | |
|
824 | // It happens if the dirstate contains `foo/bar` | |
|
825 | // and foo is not a | |
|
826 | // directory | |
|
827 | Ok(( | |
|
828 | Cow::Borrowed(filename), | |
|
829 | dispatch_missing(entry.state), | |
|
830 | )) | |
|
831 | } | |
|
832 | Err(e) => Err(e), | |
|
833 | } | |
|
834 | }, | |
|
835 | )); | |
|
836 | } | |
|
837 | 906 | } |
|
838 | 907 | |
|
839 | 908 | #[timed] |
@@ -14,6 +14,66 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"))] | |
|
17 | 77 | impl<'a, M: Matcher + Sync> Status<'a, M> { |
|
18 | 78 | pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> { |
|
19 | 79 | let (traversed_sender, traversed_receiver) = |
@@ -142,10 +142,10 b' py_class!(pub class DirstateMap |py| {' | |||
|
142 | 142 | })?, |
|
143 | 143 | ) |
|
144 | 144 | .and_then(|b| Ok(b.to_py_object(py))) |
|
145 |
.or_else(| |
|
|
145 | .or_else(|e| { | |
|
146 | 146 | Err(PyErr::new::<exc::OSError, _>( |
|
147 | 147 | py, |
|
148 | "Dirstate error".to_string(), | |
|
148 | format!("Dirstate error: {}", e.to_string()), | |
|
149 | 149 | )) |
|
150 | 150 | }) |
|
151 | 151 | } |
@@ -549,12 +549,14 b' impl DirstateMap {' | |||
|
549 | 549 | ) -> Ref<'a, RustDirstateMap> { |
|
550 | 550 | self.inner(py).borrow() |
|
551 | 551 | } |
|
552 | #[cfg(not(feature = "dirstate-tree"))] | |
|
552 | 553 | fn translate_key( |
|
553 | 554 | py: Python, |
|
554 | 555 | res: (&HgPathBuf, &DirstateEntry), |
|
555 | 556 | ) -> PyResult<Option<PyBytes>> { |
|
556 | 557 | Ok(Some(PyBytes::new(py, res.0.as_bytes()))) |
|
557 | 558 | } |
|
559 | #[cfg(not(feature = "dirstate-tree"))] | |
|
558 | 560 | fn translate_key_value( |
|
559 | 561 | py: Python, |
|
560 | 562 | res: (&HgPathBuf, &DirstateEntry), |
@@ -562,7 +564,25 b' impl DirstateMap {' | |||
|
562 | 564 | let (f, entry) = res; |
|
563 | 565 | Ok(Some(( |
|
564 | 566 | PyBytes::new(py, f.as_bytes()), |
|
565 | make_dirstate_tuple(py, entry)?, | |
|
567 | make_dirstate_tuple(py, &entry)?, | |
|
568 | ))) | |
|
569 | } | |
|
570 | #[cfg(feature = "dirstate-tree")] | |
|
571 | fn translate_key( | |
|
572 | py: Python, | |
|
573 | res: (HgPathBuf, DirstateEntry), | |
|
574 | ) -> PyResult<Option<PyBytes>> { | |
|
575 | Ok(Some(PyBytes::new(py, res.0.as_bytes()))) | |
|
576 | } | |
|
577 | #[cfg(feature = "dirstate-tree")] | |
|
578 | fn translate_key_value( | |
|
579 | py: Python, | |
|
580 | res: (HgPathBuf, DirstateEntry), | |
|
581 | ) -> PyResult<Option<(PyBytes, PyObject)>> { | |
|
582 | let (f, entry) = res; | |
|
583 | Ok(Some(( | |
|
584 | PyBytes::new(py, f.as_bytes()), | |
|
585 | make_dirstate_tuple(py, &entry)?, | |
|
566 | 586 | ))) |
|
567 | 587 | } |
|
568 | 588 | } |
@@ -159,7 +159,7 b' pub fn status_wrapper(' | |||
|
159 | 159 | .collect(); |
|
160 | 160 | |
|
161 | 161 | let files = files?; |
|
162 |
let matcher = FileMatcher::new( |
|
|
162 | let matcher = FileMatcher::new(files.as_ref()) | |
|
163 | 163 | .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?; |
|
164 | 164 | let ((lookup, status_res), warnings) = status( |
|
165 | 165 | &dmap, |
@@ -119,11 +119,11 b' fn pack_dirstate_wrapper(' | |||
|
119 | 119 | Duration::from_secs(now.as_object().extract::<u64>(py)?), |
|
120 | 120 | ) { |
|
121 | 121 | Ok(packed) => { |
|
122 |
for (filename, entry) in |
|
|
122 | for (filename, entry) in dirstate_map.iter() { | |
|
123 | 123 | dmap.set_item( |
|
124 | 124 | py, |
|
125 | 125 | PyBytes::new(py, filename.as_bytes()), |
|
126 | make_dirstate_tuple(py, entry)?, | |
|
126 | make_dirstate_tuple(py, &entry)?, | |
|
127 | 127 | )?; |
|
128 | 128 | } |
|
129 | 129 | Ok(PyBytes::new(py, &packed)) |
General Comments 0
You need to be logged in to leave comments.
Login now