Show More
@@ -38,8 +38,15 b' pub struct DirstateEntry {' | |||||
38 | /// merge. |
|
38 | /// merge. | |
39 | pub const SIZE_FROM_OTHER_PARENT: i32 = -2; |
|
39 | pub const SIZE_FROM_OTHER_PARENT: i32 = -2; | |
40 |
|
40 | |||
|
41 | #[cfg(not(feature = "dirstate-tree"))] | |||
41 | pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>; |
|
42 | pub type StateMap = FastHashMap<HgPathBuf, DirstateEntry>; | |
|
43 | #[cfg(not(feature = "dirstate-tree"))] | |||
42 | pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>; |
|
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 | pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>; |
|
50 | pub type CopyMap = FastHashMap<HgPathBuf, HgPathBuf>; | |
44 | pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>; |
|
51 | pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>; | |
45 |
|
52 |
@@ -14,7 +14,7 b' use crate::{' | |||||
14 | files, |
|
14 | files, | |
15 | hg_path::{HgPath, HgPathBuf, HgPathError}, |
|
15 | hg_path::{HgPath, HgPathBuf, HgPathError}, | |
16 | }, |
|
16 | }, | |
17 | DirstateEntry, DirstateMapError, FastHashMap, |
|
17 | DirstateEntry, DirstateMapError, FastHashMap, StateMap, | |
18 | }; |
|
18 | }; | |
19 | use std::collections::{hash_map, hash_map::Entry, HashMap, HashSet}; |
|
19 | use std::collections::{hash_map, hash_map::Entry, HashMap, HashSet}; | |
20 |
|
20 | |||
@@ -30,15 +30,15 b' impl DirsMultiset {' | |||||
30 | /// Initializes the multiset from a dirstate. |
|
30 | /// Initializes the multiset from a dirstate. | |
31 | /// |
|
31 | /// | |
32 | /// If `skip_state` is provided, skips dirstate entries with equal state. |
|
32 | /// If `skip_state` is provided, skips dirstate entries with equal state. | |
|
33 | #[cfg(not(feature = "dirstate-tree"))] | |||
33 | pub fn from_dirstate( |
|
34 | pub fn from_dirstate( | |
34 | dirstate: &FastHashMap<HgPathBuf, DirstateEntry>, |
|
35 | dirstate: &StateMap, | |
35 | skip_state: Option<EntryState>, |
|
36 | skip_state: Option<EntryState>, | |
36 | ) -> Result<Self, DirstateMapError> { |
|
37 | ) -> Result<Self, DirstateMapError> { | |
37 | let mut multiset = DirsMultiset { |
|
38 | let mut multiset = DirsMultiset { | |
38 | inner: FastHashMap::default(), |
|
39 | inner: FastHashMap::default(), | |
39 | }; |
|
40 | }; | |
40 |
|
41 | for (filename, DirstateEntry { state, .. }) in dirstate.iter() { | ||
41 | for (filename, DirstateEntry { state, .. }) in dirstate { |
|
|||
42 | // This `if` is optimized out of the loop |
|
42 | // This `if` is optimized out of the loop | |
43 | if let Some(skip) = skip_state { |
|
43 | if let Some(skip) = skip_state { | |
44 | if skip != *state { |
|
44 | if skip != *state { | |
@@ -51,6 +51,30 b' impl DirsMultiset {' | |||||
51 |
|
51 | |||
52 | Ok(multiset) |
|
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 | /// Initializes the multiset from a manifest. |
|
79 | /// Initializes the multiset from a manifest. | |
56 | pub fn from_manifest( |
|
80 | pub fn from_manifest( | |
@@ -332,8 +356,8 b' mod tests {' | |||||
332 | }; |
|
356 | }; | |
333 | assert_eq!(expected, new); |
|
357 | assert_eq!(expected, new); | |
334 |
|
358 | |||
335 | let new = DirsMultiset::from_dirstate(&FastHashMap::default(), None) |
|
359 | let new = | |
336 | .unwrap(); |
|
360 | DirsMultiset::from_dirstate(&StateMap::default(), None).unwrap(); | |
337 | let expected = DirsMultiset { |
|
361 | let expected = DirsMultiset { | |
338 | inner: FastHashMap::default(), |
|
362 | inner: FastHashMap::default(), | |
339 | }; |
|
363 | }; | |
@@ -357,7 +381,7 b' mod tests {' | |||||
357 | }; |
|
381 | }; | |
358 | assert_eq!(expected, new); |
|
382 | assert_eq!(expected, new); | |
359 |
|
383 | |||
360 |
let input_map = [" |
|
384 | let input_map = ["b/x", "a/c", "a/d/x"] | |
361 | .iter() |
|
385 | .iter() | |
362 | .map(|f| { |
|
386 | .map(|f| { | |
363 | ( |
|
387 | ( | |
@@ -371,7 +395,7 b' mod tests {' | |||||
371 | ) |
|
395 | ) | |
372 | }) |
|
396 | }) | |
373 | .collect(); |
|
397 | .collect(); | |
374 |
let expected_inner = [("", 2), ("a", |
|
398 | let expected_inner = [("", 2), ("a", 2), ("b", 1), ("a/d", 1)] | |
375 | .iter() |
|
399 | .iter() | |
376 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) |
|
400 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | |
377 | .collect(); |
|
401 | .collect(); | |
@@ -387,9 +411,9 b' mod tests {' | |||||
387 | fn test_dirsmultiset_new_skip() { |
|
411 | fn test_dirsmultiset_new_skip() { | |
388 | let input_map = [ |
|
412 | let input_map = [ | |
389 | ("a/", EntryState::Normal), |
|
413 | ("a/", EntryState::Normal), | |
390 |
("a/b |
|
414 | ("a/b", EntryState::Normal), | |
391 | ("a/c", EntryState::Removed), |
|
415 | ("a/c", EntryState::Removed), | |
392 |
("a/d |
|
416 | ("a/d", EntryState::Merged), | |
393 | ] |
|
417 | ] | |
394 | .iter() |
|
418 | .iter() | |
395 | .map(|(f, state)| { |
|
419 | .map(|(f, state)| { | |
@@ -406,7 +430,7 b' mod tests {' | |||||
406 | .collect(); |
|
430 | .collect(); | |
407 |
|
431 | |||
408 | // "a" incremented with "a/c" and "a/d/" |
|
432 | // "a" incremented with "a/c" and "a/d/" | |
409 |
let expected_inner = [("", 1), ("a", 2) |
|
433 | let expected_inner = [("", 1), ("a", 2)] | |
410 | .iter() |
|
434 | .iter() | |
411 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) |
|
435 | .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v)) | |
412 | .collect(); |
|
436 | .collect(); |
@@ -16,7 +16,6 b' use crate::{' | |||||
16 | CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError, |
|
16 | CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError, | |
17 | DirstateParents, DirstateParseError, FastHashMap, StateMap, |
|
17 | DirstateParents, DirstateParseError, FastHashMap, StateMap, | |
18 | }; |
|
18 | }; | |
19 | use core::borrow::Borrow; |
|
|||
20 | use micro_timer::timed; |
|
19 | use micro_timer::timed; | |
21 | use std::collections::HashSet; |
|
20 | use std::collections::HashSet; | |
22 | use std::convert::TryInto; |
|
21 | use std::convert::TryInto; | |
@@ -67,7 +66,7 b' impl DirstateMap {' | |||||
67 | } |
|
66 | } | |
68 |
|
67 | |||
69 | pub fn clear(&mut self) { |
|
68 | pub fn clear(&mut self) { | |
70 |
self.state_map |
|
69 | self.state_map = StateMap::default(); | |
71 | self.copy_map.clear(); |
|
70 | self.copy_map.clear(); | |
72 | self.file_fold_map = None; |
|
71 | self.file_fold_map = None; | |
73 | self.non_normal_set = None; |
|
72 | self.non_normal_set = None; | |
@@ -189,18 +188,15 b' impl DirstateMap {' | |||||
189 | ) { |
|
188 | ) { | |
190 | for filename in filenames { |
|
189 | for filename in filenames { | |
191 | let mut changed = false; |
|
190 | let mut changed = false; | |
192 | self.state_map |
|
191 | if let Some(entry) = self.state_map.get_mut(&filename) { | |
193 | .entry(filename.to_owned()) |
|
192 | if entry.state == EntryState::Normal && entry.mtime == now { | |
194 | .and_modify(|entry| { |
|
193 | changed = true; | |
195 | if entry.state == EntryState::Normal && entry.mtime == now |
|
194 | *entry = DirstateEntry { | |
196 |
|
|
195 | mtime: MTIME_UNSET, | |
197 |
|
|
196 | ..*entry | |
198 | *entry = DirstateEntry { |
|
197 | }; | |
199 | mtime: MTIME_UNSET, |
|
198 | } | |
200 | ..*entry |
|
199 | } | |
201 | }; |
|
|||
202 | } |
|
|||
203 | }); |
|
|||
204 | if changed { |
|
200 | if changed { | |
205 | self.get_non_normal_other_parent_entries() |
|
201 | self.get_non_normal_other_parent_entries() | |
206 | .0 |
|
202 | .0 | |
@@ -257,6 +253,7 b' impl DirstateMap {' | |||||
257 | ) |
|
253 | ) | |
258 | } |
|
254 | } | |
259 |
|
255 | |||
|
256 | #[cfg(not(feature = "dirstate-tree"))] | |||
260 | pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { |
|
257 | pub fn set_non_normal_other_parent_entries(&mut self, force: bool) { | |
261 | if !force |
|
258 | if !force | |
262 | && self.non_normal_set.is_some() |
|
259 | && self.non_normal_set.is_some() | |
@@ -285,6 +282,34 b' impl DirstateMap {' | |||||
285 | self.non_normal_set = Some(non_normal); |
|
282 | self.non_normal_set = Some(non_normal); | |
286 | self.other_parent_set = Some(other_parent); |
|
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 | /// Both of these setters and their uses appear to be the simplest way to |
|
314 | /// Both of these setters and their uses appear to be the simplest way to | |
290 | /// emulate a Python lazy property, but it is ugly and unidiomatic. |
|
315 | /// emulate a Python lazy property, but it is ugly and unidiomatic. | |
@@ -398,17 +423,33 b' impl DirstateMap {' | |||||
398 | self.set_non_normal_other_parent_entries(true); |
|
423 | self.set_non_normal_other_parent_entries(true); | |
399 | Ok(packed) |
|
424 | Ok(packed) | |
400 | } |
|
425 | } | |
401 |
|
426 | #[cfg(not(feature = "dirstate-tree"))] | ||
402 | pub fn build_file_fold_map(&mut self) -> &FileFoldMap { |
|
427 | pub fn build_file_fold_map(&mut self) -> &FileFoldMap { | |
403 | if let Some(ref file_fold_map) = self.file_fold_map { |
|
428 | if let Some(ref file_fold_map) = self.file_fold_map { | |
404 | return file_fold_map; |
|
429 | return file_fold_map; | |
405 | } |
|
430 | } | |
406 | let mut new_file_fold_map = FileFoldMap::default(); |
|
431 | let mut new_file_fold_map = FileFoldMap::default(); | |
407 | for (filename, DirstateEntry { state, .. }) in self.state_map.borrow() |
|
432 | ||
408 | { |
|
433 | for (filename, DirstateEntry { state, .. }) in self.state_map.iter() { | |
409 | if *state == EntryState::Removed { |
|
434 | if *state == EntryState::Removed { | |
410 | new_file_fold_map |
|
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 | self.file_fold_map = Some(new_file_fold_map); |
|
455 | self.file_fold_map = Some(new_file_fold_map); |
@@ -80,11 +80,11 b' pub fn parse_dirstate(' | |||||
80 | )); |
|
80 | )); | |
81 | curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len); |
|
81 | curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len); | |
82 | } |
|
82 | } | |
83 |
|
||||
84 | Ok((parents, entries, copies)) |
|
83 | Ok((parents, entries, copies)) | |
85 | } |
|
84 | } | |
86 |
|
85 | |||
87 | /// `now` is the duration in seconds since the Unix epoch |
|
86 | /// `now` is the duration in seconds since the Unix epoch | |
|
87 | #[cfg(not(feature = "dirstate-tree"))] | |||
88 | pub fn pack_dirstate( |
|
88 | pub fn pack_dirstate( | |
89 | state_map: &mut StateMap, |
|
89 | state_map: &mut StateMap, | |
90 | copy_map: &CopyMap, |
|
90 | copy_map: &CopyMap, | |
@@ -156,15 +156,89 b' pub fn pack_dirstate(' | |||||
156 |
|
156 | |||
157 | Ok(packed) |
|
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 | #[cfg(test)] |
|
233 | #[cfg(test)] | |
161 | mod tests { |
|
234 | mod tests { | |
162 | use super::*; |
|
235 | use super::*; | |
163 | use crate::{utils::hg_path::HgPathBuf, FastHashMap}; |
|
236 | use crate::{utils::hg_path::HgPathBuf, FastHashMap}; | |
|
237 | use pretty_assertions::assert_eq; | |||
164 |
|
238 | |||
165 | #[test] |
|
239 | #[test] | |
166 | fn test_pack_dirstate_empty() { |
|
240 | fn test_pack_dirstate_empty() { | |
167 |
let mut state_map |
|
241 | let mut state_map = StateMap::default(); | |
168 | let copymap = FastHashMap::default(); |
|
242 | let copymap = FastHashMap::default(); | |
169 | let parents = DirstateParents { |
|
243 | let parents = DirstateParents { | |
170 | p1: *b"12345678910111213141", |
|
244 | p1: *b"12345678910111213141", |
@@ -9,6 +9,10 b'' | |||||
9 | //! It is currently missing a lot of functionality compared to the Python one |
|
9 | //! It is currently missing a lot of functionality compared to the Python one | |
10 | //! and will only be triggered in narrow cases. |
|
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 | use crate::{ |
|
16 | use crate::{ | |
13 | dirstate::SIZE_FROM_OTHER_PARENT, |
|
17 | dirstate::SIZE_FROM_OTHER_PARENT, | |
14 | filepatterns::PatternFileWarning, |
|
18 | filepatterns::PatternFileWarning, | |
@@ -19,7 +23,6 b' use crate::{' | |||||
19 | hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf, |
|
23 | hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf, | |
20 | HgPathError, |
|
24 | HgPathError, | |
21 | }, |
|
25 | }, | |
22 | path_auditor::PathAuditor, |
|
|||
23 | }, |
|
26 | }, | |
24 | CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap, |
|
27 | CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap, | |
25 | PatternError, |
|
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 | /// Checks all files that are in the dirstate but were not found during the |
|
825 | /// Checks all files that are in the dirstate but were not found during the | |
705 | /// working directory traversal. This means that the rest must |
|
826 | /// working directory traversal. This means that the rest must | |
706 | /// be either ignored, under a symlink or under a new nested repo. |
|
827 | /// be either ignored, under a symlink or under a new nested repo. | |
707 | /// |
|
828 | /// | |
708 | /// This takes a mutable reference to the results to account for the |
|
829 | /// This takes a mutable reference to the results to account for the | |
709 | /// `extend` in timings |
|
830 | /// `extend` in timings | |
|
831 | #[cfg(not(feature = "dirstate-tree"))] | |||
710 | #[timed] |
|
832 | #[timed] | |
711 | pub fn handle_unknowns( |
|
833 | pub fn handle_unknowns( | |
712 | &self, |
|
834 | &self, | |
@@ -781,59 +903,6 b' where' | |||||
781 |
|
903 | |||
782 | Ok(()) |
|
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 | #[timed] |
|
908 | #[timed] |
@@ -14,6 +14,66 b' use crate::{DirstateStatus, StatusError}' | |||||
14 | /// files. |
|
14 | /// files. | |
15 | pub type LookupAndStatus<'a> = (Vec<HgPathCow<'a>>, DirstateStatus<'a>); |
|
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 | impl<'a, M: Matcher + Sync> Status<'a, M> { |
|
77 | impl<'a, M: Matcher + Sync> Status<'a, M> { | |
18 | pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> { |
|
78 | pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> { | |
19 | let (traversed_sender, traversed_receiver) = |
|
79 | let (traversed_sender, traversed_receiver) = |
@@ -142,10 +142,10 b' py_class!(pub class DirstateMap |py| {' | |||||
142 | })?, |
|
142 | })?, | |
143 | ) |
|
143 | ) | |
144 | .and_then(|b| Ok(b.to_py_object(py))) |
|
144 | .and_then(|b| Ok(b.to_py_object(py))) | |
145 |
.or_else(| |
|
145 | .or_else(|e| { | |
146 | Err(PyErr::new::<exc::OSError, _>( |
|
146 | Err(PyErr::new::<exc::OSError, _>( | |
147 | py, |
|
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 | ) -> Ref<'a, RustDirstateMap> { |
|
549 | ) -> Ref<'a, RustDirstateMap> { | |
550 | self.inner(py).borrow() |
|
550 | self.inner(py).borrow() | |
551 | } |
|
551 | } | |
|
552 | #[cfg(not(feature = "dirstate-tree"))] | |||
552 | fn translate_key( |
|
553 | fn translate_key( | |
553 | py: Python, |
|
554 | py: Python, | |
554 | res: (&HgPathBuf, &DirstateEntry), |
|
555 | res: (&HgPathBuf, &DirstateEntry), | |
555 | ) -> PyResult<Option<PyBytes>> { |
|
556 | ) -> PyResult<Option<PyBytes>> { | |
556 | Ok(Some(PyBytes::new(py, res.0.as_bytes()))) |
|
557 | Ok(Some(PyBytes::new(py, res.0.as_bytes()))) | |
557 | } |
|
558 | } | |
|
559 | #[cfg(not(feature = "dirstate-tree"))] | |||
558 | fn translate_key_value( |
|
560 | fn translate_key_value( | |
559 | py: Python, |
|
561 | py: Python, | |
560 | res: (&HgPathBuf, &DirstateEntry), |
|
562 | res: (&HgPathBuf, &DirstateEntry), | |
@@ -562,7 +564,25 b' impl DirstateMap {' | |||||
562 | let (f, entry) = res; |
|
564 | let (f, entry) = res; | |
563 | Ok(Some(( |
|
565 | Ok(Some(( | |
564 | PyBytes::new(py, f.as_bytes()), |
|
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 | .collect(); |
|
159 | .collect(); | |
160 |
|
160 | |||
161 | let files = files?; |
|
161 | let files = files?; | |
162 |
let matcher = FileMatcher::new( |
|
162 | let matcher = FileMatcher::new(files.as_ref()) | |
163 | .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?; |
|
163 | .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?; | |
164 | let ((lookup, status_res), warnings) = status( |
|
164 | let ((lookup, status_res), warnings) = status( | |
165 | &dmap, |
|
165 | &dmap, |
@@ -119,11 +119,11 b' fn pack_dirstate_wrapper(' | |||||
119 | Duration::from_secs(now.as_object().extract::<u64>(py)?), |
|
119 | Duration::from_secs(now.as_object().extract::<u64>(py)?), | |
120 | ) { |
|
120 | ) { | |
121 | Ok(packed) => { |
|
121 | Ok(packed) => { | |
122 |
for (filename, entry) in |
|
122 | for (filename, entry) in dirstate_map.iter() { | |
123 | dmap.set_item( |
|
123 | dmap.set_item( | |
124 | py, |
|
124 | py, | |
125 | PyBytes::new(py, filename.as_bytes()), |
|
125 | PyBytes::new(py, filename.as_bytes()), | |
126 | make_dirstate_tuple(py, entry)?, |
|
126 | make_dirstate_tuple(py, &entry)?, | |
127 | )?; |
|
127 | )?; | |
128 | } |
|
128 | } | |
129 | Ok(PyBytes::new(py, &packed)) |
|
129 | Ok(PyBytes::new(py, &packed)) |
General Comments 0
You need to be logged in to leave comments.
Login now