##// END OF EJS Templates
rust-status: wrap `stat_dmap_entries` to ease profiling...
Raphaël Gomès -
r45025:b8ba46c9 default
parent child Browse files
Show More
@@ -1,777 +1,789 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Rust implementation of dirstate.status (dirstate.py).
8 //! Rust implementation of dirstate.status (dirstate.py).
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 use crate::{
12 use crate::{
13 dirstate::SIZE_FROM_OTHER_PARENT,
13 dirstate::SIZE_FROM_OTHER_PARENT,
14 filepatterns::PatternFileWarning,
14 filepatterns::PatternFileWarning,
15 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
15 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
16 utils::{
16 utils::{
17 files::{find_dirs, HgMetadata},
17 files::{find_dirs, HgMetadata},
18 hg_path::{
18 hg_path::{
19 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
19 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
20 HgPathError,
20 HgPathError,
21 },
21 },
22 path_auditor::PathAuditor,
22 path_auditor::PathAuditor,
23 },
23 },
24 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
24 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
25 PatternError,
25 PatternError,
26 };
26 };
27 use lazy_static::lazy_static;
27 use lazy_static::lazy_static;
28 use rayon::prelude::*;
28 use rayon::prelude::*;
29 use std::{
29 use std::{
30 borrow::Cow,
30 borrow::Cow,
31 collections::HashSet,
31 collections::HashSet,
32 fs::{read_dir, DirEntry},
32 fs::{read_dir, DirEntry},
33 io::ErrorKind,
33 io::ErrorKind,
34 ops::Deref,
34 ops::Deref,
35 path::Path,
35 path::Path,
36 };
36 };
37
37
38 /// Wrong type of file from a `BadMatch`
38 /// Wrong type of file from a `BadMatch`
39 /// Note: a lot of those don't exist on all platforms.
39 /// Note: a lot of those don't exist on all platforms.
40 #[derive(Debug, Copy, Clone)]
40 #[derive(Debug, Copy, Clone)]
41 pub enum BadType {
41 pub enum BadType {
42 CharacterDevice,
42 CharacterDevice,
43 BlockDevice,
43 BlockDevice,
44 FIFO,
44 FIFO,
45 Socket,
45 Socket,
46 Directory,
46 Directory,
47 Unknown,
47 Unknown,
48 }
48 }
49
49
50 impl ToString for BadType {
50 impl ToString for BadType {
51 fn to_string(&self) -> String {
51 fn to_string(&self) -> String {
52 match self {
52 match self {
53 BadType::CharacterDevice => "character device",
53 BadType::CharacterDevice => "character device",
54 BadType::BlockDevice => "block device",
54 BadType::BlockDevice => "block device",
55 BadType::FIFO => "fifo",
55 BadType::FIFO => "fifo",
56 BadType::Socket => "socket",
56 BadType::Socket => "socket",
57 BadType::Directory => "directory",
57 BadType::Directory => "directory",
58 BadType::Unknown => "unknown",
58 BadType::Unknown => "unknown",
59 }
59 }
60 .to_string()
60 .to_string()
61 }
61 }
62 }
62 }
63
63
64 /// Was explicitly matched but cannot be found/accessed
64 /// Was explicitly matched but cannot be found/accessed
65 #[derive(Debug, Copy, Clone)]
65 #[derive(Debug, Copy, Clone)]
66 pub enum BadMatch {
66 pub enum BadMatch {
67 OsError(i32),
67 OsError(i32),
68 BadType(BadType),
68 BadType(BadType),
69 }
69 }
70
70
71 /// Marker enum used to dispatch new status entries into the right collections.
71 /// Marker enum used to dispatch new status entries into the right collections.
72 /// Is similar to `crate::EntryState`, but represents the transient state of
72 /// Is similar to `crate::EntryState`, but represents the transient state of
73 /// entries during the lifetime of a command.
73 /// entries during the lifetime of a command.
74 #[derive(Debug, Copy, Clone)]
74 #[derive(Debug, Copy, Clone)]
75 enum Dispatch {
75 enum Dispatch {
76 Unsure,
76 Unsure,
77 Modified,
77 Modified,
78 Added,
78 Added,
79 Removed,
79 Removed,
80 Deleted,
80 Deleted,
81 Clean,
81 Clean,
82 Unknown,
82 Unknown,
83 Ignored,
83 Ignored,
84 /// Empty dispatch, the file is not worth listing
84 /// Empty dispatch, the file is not worth listing
85 None,
85 None,
86 /// Was explicitly matched but cannot be found/accessed
86 /// Was explicitly matched but cannot be found/accessed
87 Bad(BadMatch),
87 Bad(BadMatch),
88 Directory {
88 Directory {
89 /// True if the directory used to be a file in the dmap so we can say
89 /// True if the directory used to be a file in the dmap so we can say
90 /// that it's been removed.
90 /// that it's been removed.
91 was_file: bool,
91 was_file: bool,
92 },
92 },
93 }
93 }
94
94
95 type IoResult<T> = std::io::Result<T>;
95 type IoResult<T> = std::io::Result<T>;
96
96
97 /// Dates and times that are outside the 31-bit signed range are compared
97 /// Dates and times that are outside the 31-bit signed range are compared
98 /// modulo 2^31. This should prevent hg from behaving badly with very large
98 /// modulo 2^31. This should prevent hg from behaving badly with very large
99 /// files or corrupt dates while still having a high probability of detecting
99 /// files or corrupt dates while still having a high probability of detecting
100 /// changes. (issue2608)
100 /// changes. (issue2608)
101 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
101 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
102 /// is not defined for `i32`, and there is no `As` trait. This forces the
102 /// is not defined for `i32`, and there is no `As` trait. This forces the
103 /// caller to cast `b` as `i32`.
103 /// caller to cast `b` as `i32`.
104 fn mod_compare(a: i32, b: i32) -> bool {
104 fn mod_compare(a: i32, b: i32) -> bool {
105 a & i32::max_value() != b & i32::max_value()
105 a & i32::max_value() != b & i32::max_value()
106 }
106 }
107
107
108 /// Return a sorted list containing information about the entries
108 /// Return a sorted list containing information about the entries
109 /// in the directory.
109 /// in the directory.
110 ///
110 ///
111 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
111 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
112 fn list_directory(
112 fn list_directory(
113 path: impl AsRef<Path>,
113 path: impl AsRef<Path>,
114 skip_dot_hg: bool,
114 skip_dot_hg: bool,
115 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
115 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
116 let mut results = vec![];
116 let mut results = vec![];
117 let entries = read_dir(path.as_ref())?;
117 let entries = read_dir(path.as_ref())?;
118
118
119 for entry in entries {
119 for entry in entries {
120 let entry = entry?;
120 let entry = entry?;
121 let filename = os_string_to_hg_path_buf(entry.file_name())?;
121 let filename = os_string_to_hg_path_buf(entry.file_name())?;
122 let file_type = entry.file_type()?;
122 let file_type = entry.file_type()?;
123 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
123 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
124 return Ok(vec![]);
124 return Ok(vec![]);
125 } else {
125 } else {
126 results.push((HgPathBuf::from(filename), entry))
126 results.push((HgPathBuf::from(filename), entry))
127 }
127 }
128 }
128 }
129
129
130 results.sort_unstable_by_key(|e| e.0.clone());
130 results.sort_unstable_by_key(|e| e.0.clone());
131 Ok(results)
131 Ok(results)
132 }
132 }
133
133
134 /// The file corresponding to the dirstate entry was found on the filesystem.
134 /// The file corresponding to the dirstate entry was found on the filesystem.
135 fn dispatch_found(
135 fn dispatch_found(
136 filename: impl AsRef<HgPath>,
136 filename: impl AsRef<HgPath>,
137 entry: DirstateEntry,
137 entry: DirstateEntry,
138 metadata: HgMetadata,
138 metadata: HgMetadata,
139 copy_map: &CopyMap,
139 copy_map: &CopyMap,
140 options: StatusOptions,
140 options: StatusOptions,
141 ) -> Dispatch {
141 ) -> Dispatch {
142 let DirstateEntry {
142 let DirstateEntry {
143 state,
143 state,
144 mode,
144 mode,
145 mtime,
145 mtime,
146 size,
146 size,
147 } = entry;
147 } = entry;
148
148
149 let HgMetadata {
149 let HgMetadata {
150 st_mode,
150 st_mode,
151 st_size,
151 st_size,
152 st_mtime,
152 st_mtime,
153 ..
153 ..
154 } = metadata;
154 } = metadata;
155
155
156 match state {
156 match state {
157 EntryState::Normal => {
157 EntryState::Normal => {
158 let size_changed = mod_compare(size, st_size as i32);
158 let size_changed = mod_compare(size, st_size as i32);
159 let mode_changed =
159 let mode_changed =
160 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
160 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
161 let metadata_changed = size >= 0 && (size_changed || mode_changed);
161 let metadata_changed = size >= 0 && (size_changed || mode_changed);
162 let other_parent = size == SIZE_FROM_OTHER_PARENT;
162 let other_parent = size == SIZE_FROM_OTHER_PARENT;
163 if metadata_changed
163 if metadata_changed
164 || other_parent
164 || other_parent
165 || copy_map.contains_key(filename.as_ref())
165 || copy_map.contains_key(filename.as_ref())
166 {
166 {
167 Dispatch::Modified
167 Dispatch::Modified
168 } else if mod_compare(mtime, st_mtime as i32) {
168 } else if mod_compare(mtime, st_mtime as i32) {
169 Dispatch::Unsure
169 Dispatch::Unsure
170 } else if st_mtime == options.last_normal_time {
170 } else if st_mtime == options.last_normal_time {
171 // the file may have just been marked as normal and
171 // the file may have just been marked as normal and
172 // it may have changed in the same second without
172 // it may have changed in the same second without
173 // changing its size. This can happen if we quickly
173 // changing its size. This can happen if we quickly
174 // do multiple commits. Force lookup, so we don't
174 // do multiple commits. Force lookup, so we don't
175 // miss such a racy file change.
175 // miss such a racy file change.
176 Dispatch::Unsure
176 Dispatch::Unsure
177 } else if options.list_clean {
177 } else if options.list_clean {
178 Dispatch::Clean
178 Dispatch::Clean
179 } else {
179 } else {
180 Dispatch::None
180 Dispatch::None
181 }
181 }
182 }
182 }
183 EntryState::Merged => Dispatch::Modified,
183 EntryState::Merged => Dispatch::Modified,
184 EntryState::Added => Dispatch::Added,
184 EntryState::Added => Dispatch::Added,
185 EntryState::Removed => Dispatch::Removed,
185 EntryState::Removed => Dispatch::Removed,
186 EntryState::Unknown => Dispatch::Unknown,
186 EntryState::Unknown => Dispatch::Unknown,
187 }
187 }
188 }
188 }
189
189
190 /// The file corresponding to this Dirstate entry is missing.
190 /// The file corresponding to this Dirstate entry is missing.
191 fn dispatch_missing(state: EntryState) -> Dispatch {
191 fn dispatch_missing(state: EntryState) -> Dispatch {
192 match state {
192 match state {
193 // File was removed from the filesystem during commands
193 // File was removed from the filesystem during commands
194 EntryState::Normal | EntryState::Merged | EntryState::Added => {
194 EntryState::Normal | EntryState::Merged | EntryState::Added => {
195 Dispatch::Deleted
195 Dispatch::Deleted
196 }
196 }
197 // File was removed, everything is normal
197 // File was removed, everything is normal
198 EntryState::Removed => Dispatch::Removed,
198 EntryState::Removed => Dispatch::Removed,
199 // File is unknown to Mercurial, everything is normal
199 // File is unknown to Mercurial, everything is normal
200 EntryState::Unknown => Dispatch::Unknown,
200 EntryState::Unknown => Dispatch::Unknown,
201 }
201 }
202 }
202 }
203
203
204 lazy_static! {
204 lazy_static! {
205 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
205 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
206 let mut h = HashSet::new();
206 let mut h = HashSet::new();
207 h.insert(HgPath::new(b""));
207 h.insert(HgPath::new(b""));
208 h
208 h
209 };
209 };
210 }
210 }
211
211
212 /// Get stat data about the files explicitly specified by match.
212 /// Get stat data about the files explicitly specified by match.
213 /// TODO subrepos
213 /// TODO subrepos
214 fn walk_explicit<'a>(
214 fn walk_explicit<'a>(
215 files: Option<&'a HashSet<&HgPath>>,
215 files: Option<&'a HashSet<&HgPath>>,
216 dmap: &'a DirstateMap,
216 dmap: &'a DirstateMap,
217 root_dir: impl AsRef<Path> + Sync + Send + 'a,
217 root_dir: impl AsRef<Path> + Sync + Send + 'a,
218 options: StatusOptions,
218 options: StatusOptions,
219 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
219 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
220 files
220 files
221 .unwrap_or(&DEFAULT_WORK)
221 .unwrap_or(&DEFAULT_WORK)
222 .par_iter()
222 .par_iter()
223 .map(move |filename| {
223 .map(move |filename| {
224 // TODO normalization
224 // TODO normalization
225 let normalized = filename.as_ref();
225 let normalized = filename.as_ref();
226
226
227 let buf = match hg_path_to_path_buf(normalized) {
227 let buf = match hg_path_to_path_buf(normalized) {
228 Ok(x) => x,
228 Ok(x) => x,
229 Err(e) => return Some(Err(e.into())),
229 Err(e) => return Some(Err(e.into())),
230 };
230 };
231 let target = root_dir.as_ref().join(buf);
231 let target = root_dir.as_ref().join(buf);
232 let st = target.symlink_metadata();
232 let st = target.symlink_metadata();
233 let in_dmap = dmap.get(normalized);
233 let in_dmap = dmap.get(normalized);
234 match st {
234 match st {
235 Ok(meta) => {
235 Ok(meta) => {
236 let file_type = meta.file_type();
236 let file_type = meta.file_type();
237 return if file_type.is_file() || file_type.is_symlink() {
237 return if file_type.is_file() || file_type.is_symlink() {
238 if let Some(entry) = in_dmap {
238 if let Some(entry) = in_dmap {
239 return Some(Ok((
239 return Some(Ok((
240 normalized,
240 normalized,
241 dispatch_found(
241 dispatch_found(
242 &normalized,
242 &normalized,
243 *entry,
243 *entry,
244 HgMetadata::from_metadata(meta),
244 HgMetadata::from_metadata(meta),
245 &dmap.copy_map,
245 &dmap.copy_map,
246 options,
246 options,
247 ),
247 ),
248 )));
248 )));
249 }
249 }
250 Some(Ok((normalized, Dispatch::Unknown)))
250 Some(Ok((normalized, Dispatch::Unknown)))
251 } else {
251 } else {
252 if file_type.is_dir() {
252 if file_type.is_dir() {
253 Some(Ok((
253 Some(Ok((
254 normalized,
254 normalized,
255 Dispatch::Directory {
255 Dispatch::Directory {
256 was_file: in_dmap.is_some(),
256 was_file: in_dmap.is_some(),
257 },
257 },
258 )))
258 )))
259 } else {
259 } else {
260 Some(Ok((
260 Some(Ok((
261 normalized,
261 normalized,
262 Dispatch::Bad(BadMatch::BadType(
262 Dispatch::Bad(BadMatch::BadType(
263 // TODO do more than unknown
263 // TODO do more than unknown
264 // Support for all `BadType` variant
264 // Support for all `BadType` variant
265 // varies greatly between platforms.
265 // varies greatly between platforms.
266 // So far, no tests check the type and
266 // So far, no tests check the type and
267 // this should be good enough for most
267 // this should be good enough for most
268 // users.
268 // users.
269 BadType::Unknown,
269 BadType::Unknown,
270 )),
270 )),
271 )))
271 )))
272 }
272 }
273 };
273 };
274 }
274 }
275 Err(_) => {
275 Err(_) => {
276 if let Some(entry) = in_dmap {
276 if let Some(entry) = in_dmap {
277 return Some(Ok((
277 return Some(Ok((
278 normalized,
278 normalized,
279 dispatch_missing(entry.state),
279 dispatch_missing(entry.state),
280 )));
280 )));
281 }
281 }
282 }
282 }
283 };
283 };
284 None
284 None
285 })
285 })
286 .flatten()
286 .flatten()
287 }
287 }
288
288
289 #[derive(Debug, Copy, Clone)]
289 #[derive(Debug, Copy, Clone)]
290 pub struct StatusOptions {
290 pub struct StatusOptions {
291 /// Remember the most recent modification timeslot for status, to make
291 /// Remember the most recent modification timeslot for status, to make
292 /// sure we won't miss future size-preserving file content modifications
292 /// sure we won't miss future size-preserving file content modifications
293 /// that happen within the same timeslot.
293 /// that happen within the same timeslot.
294 pub last_normal_time: i64,
294 pub last_normal_time: i64,
295 /// Whether we are on a filesystem with UNIX-like exec flags
295 /// Whether we are on a filesystem with UNIX-like exec flags
296 pub check_exec: bool,
296 pub check_exec: bool,
297 pub list_clean: bool,
297 pub list_clean: bool,
298 pub list_unknown: bool,
298 pub list_unknown: bool,
299 pub list_ignored: bool,
299 pub list_ignored: bool,
300 }
300 }
301
301
302 /// Dispatch a single entry (file, folder, symlink...) found during `traverse`.
302 /// Dispatch a single entry (file, folder, symlink...) found during `traverse`.
303 /// If the entry is a folder that needs to be traversed, it will be pushed into
303 /// If the entry is a folder that needs to be traversed, it will be pushed into
304 /// `work`.
304 /// `work`.
305 fn handle_traversed_entry<'a>(
305 fn handle_traversed_entry<'a>(
306 dir_entry: &DirEntry,
306 dir_entry: &DirEntry,
307 matcher: &(impl Matcher + Sync),
307 matcher: &(impl Matcher + Sync),
308 root_dir: impl AsRef<Path>,
308 root_dir: impl AsRef<Path>,
309 dmap: &DirstateMap,
309 dmap: &DirstateMap,
310 filename: impl AsRef<HgPath>,
310 filename: impl AsRef<HgPath>,
311 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
311 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
312 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
312 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
313 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
313 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
314 options: StatusOptions,
314 options: StatusOptions,
315 ) -> IoResult<Vec<(Cow<'a, HgPath>, Dispatch)>> {
315 ) -> IoResult<Vec<(Cow<'a, HgPath>, Dispatch)>> {
316 let file_type = dir_entry.file_type()?;
316 let file_type = dir_entry.file_type()?;
317 let filename = filename.as_ref();
317 let filename = filename.as_ref();
318 let entry_option = dmap.get(filename);
318 let entry_option = dmap.get(filename);
319
319
320 if file_type.is_dir() {
320 if file_type.is_dir() {
321 // Do we need to traverse it?
321 // Do we need to traverse it?
322 if !ignore_fn(&filename) || options.list_ignored {
322 if !ignore_fn(&filename) || options.list_ignored {
323 return traverse_dir(
323 return traverse_dir(
324 matcher,
324 matcher,
325 root_dir,
325 root_dir,
326 dmap,
326 dmap,
327 filename.to_owned(),
327 filename.to_owned(),
328 &old_results,
328 &old_results,
329 ignore_fn,
329 ignore_fn,
330 dir_ignore_fn,
330 dir_ignore_fn,
331 options,
331 options,
332 );
332 );
333 }
333 }
334 // Nested `if` until `rust-lang/rust#53668` is stable
334 // Nested `if` until `rust-lang/rust#53668` is stable
335 if let Some(entry) = entry_option {
335 if let Some(entry) = entry_option {
336 // Used to be a file, is now a folder
336 // Used to be a file, is now a folder
337 if matcher.matches_everything() || matcher.matches(&filename) {
337 if matcher.matches_everything() || matcher.matches(&filename) {
338 return Ok(vec![(
338 return Ok(vec![(
339 Cow::Owned(filename.to_owned()),
339 Cow::Owned(filename.to_owned()),
340 dispatch_missing(entry.state),
340 dispatch_missing(entry.state),
341 )]);
341 )]);
342 }
342 }
343 }
343 }
344 } else if file_type.is_file() || file_type.is_symlink() {
344 } else if file_type.is_file() || file_type.is_symlink() {
345 if let Some(entry) = entry_option {
345 if let Some(entry) = entry_option {
346 if matcher.matches_everything() || matcher.matches(&filename) {
346 if matcher.matches_everything() || matcher.matches(&filename) {
347 let metadata = dir_entry.metadata()?;
347 let metadata = dir_entry.metadata()?;
348 return Ok(vec![(
348 return Ok(vec![(
349 Cow::Owned(filename.to_owned()),
349 Cow::Owned(filename.to_owned()),
350 dispatch_found(
350 dispatch_found(
351 &filename,
351 &filename,
352 *entry,
352 *entry,
353 HgMetadata::from_metadata(metadata),
353 HgMetadata::from_metadata(metadata),
354 &dmap.copy_map,
354 &dmap.copy_map,
355 options,
355 options,
356 ),
356 ),
357 )]);
357 )]);
358 }
358 }
359 } else if (matcher.matches_everything() || matcher.matches(&filename))
359 } else if (matcher.matches_everything() || matcher.matches(&filename))
360 && !ignore_fn(&filename)
360 && !ignore_fn(&filename)
361 {
361 {
362 if (options.list_ignored || matcher.exact_match(&filename))
362 if (options.list_ignored || matcher.exact_match(&filename))
363 && dir_ignore_fn(&filename)
363 && dir_ignore_fn(&filename)
364 {
364 {
365 if options.list_ignored {
365 if options.list_ignored {
366 return Ok(vec![(
366 return Ok(vec![(
367 Cow::Owned(filename.to_owned()),
367 Cow::Owned(filename.to_owned()),
368 Dispatch::Ignored,
368 Dispatch::Ignored,
369 )]);
369 )]);
370 }
370 }
371 } else {
371 } else {
372 return Ok(vec![(
372 return Ok(vec![(
373 Cow::Owned(filename.to_owned()),
373 Cow::Owned(filename.to_owned()),
374 Dispatch::Unknown,
374 Dispatch::Unknown,
375 )]);
375 )]);
376 }
376 }
377 } else if ignore_fn(&filename) && options.list_ignored {
377 } else if ignore_fn(&filename) && options.list_ignored {
378 return Ok(vec![(
378 return Ok(vec![(
379 Cow::Owned(filename.to_owned()),
379 Cow::Owned(filename.to_owned()),
380 Dispatch::Ignored,
380 Dispatch::Ignored,
381 )]);
381 )]);
382 }
382 }
383 } else if let Some(entry) = entry_option {
383 } else if let Some(entry) = entry_option {
384 // Used to be a file or a folder, now something else.
384 // Used to be a file or a folder, now something else.
385 if matcher.matches_everything() || matcher.matches(&filename) {
385 if matcher.matches_everything() || matcher.matches(&filename) {
386 return Ok(vec![(
386 return Ok(vec![(
387 Cow::Owned(filename.to_owned()),
387 Cow::Owned(filename.to_owned()),
388 dispatch_missing(entry.state),
388 dispatch_missing(entry.state),
389 )]);
389 )]);
390 }
390 }
391 }
391 }
392 return Ok(vec![]);
392 return Ok(vec![]);
393 }
393 }
394
394
395 /// Decides whether the directory needs to be listed, and if so dispatches its
395 /// Decides whether the directory needs to be listed, and if so dispatches its
396 /// entries
396 /// entries
397 fn traverse_dir<'a>(
397 fn traverse_dir<'a>(
398 matcher: &(impl Matcher + Sync),
398 matcher: &(impl Matcher + Sync),
399 root_dir: impl AsRef<Path>,
399 root_dir: impl AsRef<Path>,
400 dmap: &DirstateMap,
400 dmap: &DirstateMap,
401 path: impl AsRef<HgPath>,
401 path: impl AsRef<HgPath>,
402 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
402 old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
403 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
403 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
404 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
404 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
405 options: StatusOptions,
405 options: StatusOptions,
406 ) -> IoResult<Vec<(Cow<'a, HgPath>, Dispatch)>> {
406 ) -> IoResult<Vec<(Cow<'a, HgPath>, Dispatch)>> {
407 let directory = path.as_ref();
407 let directory = path.as_ref();
408 if directory.as_bytes() == b".hg" {
408 if directory.as_bytes() == b".hg" {
409 return Ok(vec![]);
409 return Ok(vec![]);
410 }
410 }
411 let visit_entries = match matcher.visit_children_set(directory) {
411 let visit_entries = match matcher.visit_children_set(directory) {
412 VisitChildrenSet::Empty => return Ok(vec![]),
412 VisitChildrenSet::Empty => return Ok(vec![]),
413 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
413 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
414 VisitChildrenSet::Set(set) => Some(set),
414 VisitChildrenSet::Set(set) => Some(set),
415 };
415 };
416 let buf = hg_path_to_path_buf(directory)?;
416 let buf = hg_path_to_path_buf(directory)?;
417 let dir_path = root_dir.as_ref().join(buf);
417 let dir_path = root_dir.as_ref().join(buf);
418
418
419 let skip_dot_hg = !directory.as_bytes().is_empty();
419 let skip_dot_hg = !directory.as_bytes().is_empty();
420 let entries = match list_directory(dir_path, skip_dot_hg) {
420 let entries = match list_directory(dir_path, skip_dot_hg) {
421 Err(e) => match e.kind() {
421 Err(e) => match e.kind() {
422 ErrorKind::NotFound | ErrorKind::PermissionDenied => {
422 ErrorKind::NotFound | ErrorKind::PermissionDenied => {
423 return Ok(vec![(
423 return Ok(vec![(
424 Cow::Owned(directory.to_owned()),
424 Cow::Owned(directory.to_owned()),
425 Dispatch::Bad(BadMatch::OsError(
425 Dispatch::Bad(BadMatch::OsError(
426 // Unwrapping here is OK because the error always
426 // Unwrapping here is OK because the error always
427 // is a real os error
427 // is a real os error
428 e.raw_os_error().unwrap(),
428 e.raw_os_error().unwrap(),
429 )),
429 )),
430 )]);
430 )]);
431 }
431 }
432 _ => return Err(e),
432 _ => return Err(e),
433 },
433 },
434 Ok(entries) => entries,
434 Ok(entries) => entries,
435 };
435 };
436
436
437 let mut new_results = vec![];
437 let mut new_results = vec![];
438 for (filename, dir_entry) in entries {
438 for (filename, dir_entry) in entries {
439 if let Some(ref set) = visit_entries {
439 if let Some(ref set) = visit_entries {
440 if !set.contains(filename.deref()) {
440 if !set.contains(filename.deref()) {
441 continue;
441 continue;
442 }
442 }
443 }
443 }
444 // TODO normalize
444 // TODO normalize
445 let filename = if directory.is_empty() {
445 let filename = if directory.is_empty() {
446 filename.to_owned()
446 filename.to_owned()
447 } else {
447 } else {
448 directory.join(&filename)
448 directory.join(&filename)
449 };
449 };
450
450
451 if !old_results.contains_key(filename.deref()) {
451 if !old_results.contains_key(filename.deref()) {
452 new_results.extend(handle_traversed_entry(
452 new_results.extend(handle_traversed_entry(
453 &dir_entry,
453 &dir_entry,
454 matcher,
454 matcher,
455 root_dir.as_ref(),
455 root_dir.as_ref(),
456 &dmap,
456 &dmap,
457 &filename,
457 &filename,
458 old_results,
458 old_results,
459 ignore_fn,
459 ignore_fn,
460 dir_ignore_fn,
460 dir_ignore_fn,
461 options,
461 options,
462 )?);
462 )?);
463 }
463 }
464 }
464 }
465 Ok(new_results)
465 Ok(new_results)
466 }
466 }
467
467
468 /// Stat all entries in the `DirstateMap` and mark them for dispatch.
468 /// Stat all entries in the `DirstateMap` and mark them for dispatch.
469 fn stat_dmap_entries(
469 fn stat_dmap_entries(
470 dmap: &DirstateMap,
470 dmap: &DirstateMap,
471 root_dir: impl AsRef<Path> + Sync + Send,
471 root_dir: impl AsRef<Path> + Sync + Send,
472 options: StatusOptions,
472 options: StatusOptions,
473 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
473 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
474 dmap.par_iter().map(move |(filename, entry)| {
474 dmap.par_iter().map(move |(filename, entry)| {
475 let filename: &HgPath = filename;
475 let filename: &HgPath = filename;
476 let filename_as_path = hg_path_to_path_buf(filename)?;
476 let filename_as_path = hg_path_to_path_buf(filename)?;
477 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
477 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
478
478
479 match meta {
479 match meta {
480 Ok(ref m)
480 Ok(ref m)
481 if !(m.file_type().is_file()
481 if !(m.file_type().is_file()
482 || m.file_type().is_symlink()) =>
482 || m.file_type().is_symlink()) =>
483 {
483 {
484 Ok((filename, dispatch_missing(entry.state)))
484 Ok((filename, dispatch_missing(entry.state)))
485 }
485 }
486 Ok(m) => Ok((
486 Ok(m) => Ok((
487 filename,
487 filename,
488 dispatch_found(
488 dispatch_found(
489 filename,
489 filename,
490 *entry,
490 *entry,
491 HgMetadata::from_metadata(m),
491 HgMetadata::from_metadata(m),
492 &dmap.copy_map,
492 &dmap.copy_map,
493 options,
493 options,
494 ),
494 ),
495 )),
495 )),
496 Err(ref e)
496 Err(ref e)
497 if e.kind() == ErrorKind::NotFound
497 if e.kind() == ErrorKind::NotFound
498 || e.raw_os_error() == Some(20) =>
498 || e.raw_os_error() == Some(20) =>
499 {
499 {
500 // Rust does not yet have an `ErrorKind` for
500 // Rust does not yet have an `ErrorKind` for
501 // `NotADirectory` (errno 20)
501 // `NotADirectory` (errno 20)
502 // It happens if the dirstate contains `foo/bar` and
502 // It happens if the dirstate contains `foo/bar` and
503 // foo is not a directory
503 // foo is not a directory
504 Ok((filename, dispatch_missing(entry.state)))
504 Ok((filename, dispatch_missing(entry.state)))
505 }
505 }
506 Err(e) => Err(e),
506 Err(e) => Err(e),
507 }
507 }
508 })
508 })
509 }
509 }
510
510
511 /// This takes a mutable reference to the results to account for the `extend`
512 /// in timings
513 fn extend_from_dmap<'a>(
514 dmap: &'a DirstateMap,
515 root_dir: impl AsRef<Path> + Sync + Send,
516 options: StatusOptions,
517 results: &mut Vec<(Cow<'a, HgPath>, Dispatch)>,
518 ) {
519 results.par_extend(
520 stat_dmap_entries(dmap, root_dir, options)
521 .flatten()
522 .map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch)),
523 );
524 }
525
511 pub struct DirstateStatus<'a> {
526 pub struct DirstateStatus<'a> {
512 pub modified: Vec<Cow<'a, HgPath>>,
527 pub modified: Vec<Cow<'a, HgPath>>,
513 pub added: Vec<Cow<'a, HgPath>>,
528 pub added: Vec<Cow<'a, HgPath>>,
514 pub removed: Vec<Cow<'a, HgPath>>,
529 pub removed: Vec<Cow<'a, HgPath>>,
515 pub deleted: Vec<Cow<'a, HgPath>>,
530 pub deleted: Vec<Cow<'a, HgPath>>,
516 pub clean: Vec<Cow<'a, HgPath>>,
531 pub clean: Vec<Cow<'a, HgPath>>,
517 pub ignored: Vec<Cow<'a, HgPath>>,
532 pub ignored: Vec<Cow<'a, HgPath>>,
518 pub unknown: Vec<Cow<'a, HgPath>>,
533 pub unknown: Vec<Cow<'a, HgPath>>,
519 pub bad: Vec<(Cow<'a, HgPath>, BadMatch)>,
534 pub bad: Vec<(Cow<'a, HgPath>, BadMatch)>,
520 }
535 }
521
536
522 fn build_response<'a>(
537 fn build_response<'a>(
523 results: impl IntoIterator<Item = (Cow<'a, HgPath>, Dispatch)>,
538 results: impl IntoIterator<Item = (Cow<'a, HgPath>, Dispatch)>,
524 ) -> (Vec<Cow<'a, HgPath>>, DirstateStatus<'a>) {
539 ) -> (Vec<Cow<'a, HgPath>>, DirstateStatus<'a>) {
525 let mut lookup = vec![];
540 let mut lookup = vec![];
526 let mut modified = vec![];
541 let mut modified = vec![];
527 let mut added = vec![];
542 let mut added = vec![];
528 let mut removed = vec![];
543 let mut removed = vec![];
529 let mut deleted = vec![];
544 let mut deleted = vec![];
530 let mut clean = vec![];
545 let mut clean = vec![];
531 let mut ignored = vec![];
546 let mut ignored = vec![];
532 let mut unknown = vec![];
547 let mut unknown = vec![];
533 let mut bad = vec![];
548 let mut bad = vec![];
534
549
535 for (filename, dispatch) in results.into_iter() {
550 for (filename, dispatch) in results.into_iter() {
536 match dispatch {
551 match dispatch {
537 Dispatch::Unknown => unknown.push(filename),
552 Dispatch::Unknown => unknown.push(filename),
538 Dispatch::Unsure => lookup.push(filename),
553 Dispatch::Unsure => lookup.push(filename),
539 Dispatch::Modified => modified.push(filename),
554 Dispatch::Modified => modified.push(filename),
540 Dispatch::Added => added.push(filename),
555 Dispatch::Added => added.push(filename),
541 Dispatch::Removed => removed.push(filename),
556 Dispatch::Removed => removed.push(filename),
542 Dispatch::Deleted => deleted.push(filename),
557 Dispatch::Deleted => deleted.push(filename),
543 Dispatch::Clean => clean.push(filename),
558 Dispatch::Clean => clean.push(filename),
544 Dispatch::Ignored => ignored.push(filename),
559 Dispatch::Ignored => ignored.push(filename),
545 Dispatch::None => {}
560 Dispatch::None => {}
546 Dispatch::Bad(reason) => bad.push((filename, reason)),
561 Dispatch::Bad(reason) => bad.push((filename, reason)),
547 Dispatch::Directory { .. } => {}
562 Dispatch::Directory { .. } => {}
548 }
563 }
549 }
564 }
550
565
551 (
566 (
552 lookup,
567 lookup,
553 DirstateStatus {
568 DirstateStatus {
554 modified,
569 modified,
555 added,
570 added,
556 removed,
571 removed,
557 deleted,
572 deleted,
558 clean,
573 clean,
559 ignored,
574 ignored,
560 unknown,
575 unknown,
561 bad,
576 bad,
562 },
577 },
563 )
578 )
564 }
579 }
565
580
566 pub enum StatusError {
581 pub enum StatusError {
567 IO(std::io::Error),
582 IO(std::io::Error),
568 Path(HgPathError),
583 Path(HgPathError),
569 Pattern(PatternError),
584 Pattern(PatternError),
570 }
585 }
571
586
572 pub type StatusResult<T> = Result<T, StatusError>;
587 pub type StatusResult<T> = Result<T, StatusError>;
573
588
574 impl From<PatternError> for StatusError {
589 impl From<PatternError> for StatusError {
575 fn from(e: PatternError) -> Self {
590 fn from(e: PatternError) -> Self {
576 StatusError::Pattern(e)
591 StatusError::Pattern(e)
577 }
592 }
578 }
593 }
579 impl From<HgPathError> for StatusError {
594 impl From<HgPathError> for StatusError {
580 fn from(e: HgPathError) -> Self {
595 fn from(e: HgPathError) -> Self {
581 StatusError::Path(e)
596 StatusError::Path(e)
582 }
597 }
583 }
598 }
584 impl From<std::io::Error> for StatusError {
599 impl From<std::io::Error> for StatusError {
585 fn from(e: std::io::Error) -> Self {
600 fn from(e: std::io::Error) -> Self {
586 StatusError::IO(e)
601 StatusError::IO(e)
587 }
602 }
588 }
603 }
589
604
590 impl ToString for StatusError {
605 impl ToString for StatusError {
591 fn to_string(&self) -> String {
606 fn to_string(&self) -> String {
592 match self {
607 match self {
593 StatusError::IO(e) => e.to_string(),
608 StatusError::IO(e) => e.to_string(),
594 StatusError::Path(e) => e.to_string(),
609 StatusError::Path(e) => e.to_string(),
595 StatusError::Pattern(e) => e.to_string(),
610 StatusError::Pattern(e) => e.to_string(),
596 }
611 }
597 }
612 }
598 }
613 }
599
614
600 /// This takes a mutable reference to the results to account for the `extend`
615 /// This takes a mutable reference to the results to account for the `extend`
601 /// in timings
616 /// in timings
602 fn handle_unknowns<'a>(
617 fn handle_unknowns<'a>(
603 dmap: &'a DirstateMap,
618 dmap: &'a DirstateMap,
604 matcher: &(impl Matcher + Sync),
619 matcher: &(impl Matcher + Sync),
605 root_dir: impl AsRef<Path> + Sync + Send + Copy,
620 root_dir: impl AsRef<Path> + Sync + Send + Copy,
606 options: StatusOptions,
621 options: StatusOptions,
607 results: &mut Vec<(Cow<'a, HgPath>, Dispatch)>,
622 results: &mut Vec<(Cow<'a, HgPath>, Dispatch)>,
608 ) -> IoResult<()> {
623 ) -> IoResult<()> {
609 let to_visit: Vec<(&HgPath, &DirstateEntry)> = if results.is_empty()
624 let to_visit: Vec<(&HgPath, &DirstateEntry)> = if results.is_empty()
610 && matcher.matches_everything()
625 && matcher.matches_everything()
611 {
626 {
612 dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
627 dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
613 } else {
628 } else {
614 // Only convert to a hashmap if needed.
629 // Only convert to a hashmap if needed.
615 let old_results: FastHashMap<_, _> = results.iter().cloned().collect();
630 let old_results: FastHashMap<_, _> = results.iter().cloned().collect();
616 dmap.iter()
631 dmap.iter()
617 .filter_map(move |(f, e)| {
632 .filter_map(move |(f, e)| {
618 if !old_results.contains_key(f.deref()) && matcher.matches(f) {
633 if !old_results.contains_key(f.deref()) && matcher.matches(f) {
619 Some((f.deref(), e))
634 Some((f.deref(), e))
620 } else {
635 } else {
621 None
636 None
622 }
637 }
623 })
638 })
624 .collect()
639 .collect()
625 };
640 };
626
641
627 // We walked all dirs under the roots that weren't ignored, and
642 // We walked all dirs under the roots that weren't ignored, and
628 // everything that matched was stat'ed and is already in results.
643 // everything that matched was stat'ed and is already in results.
629 // The rest must thus be ignored or under a symlink.
644 // The rest must thus be ignored or under a symlink.
630 let path_auditor = PathAuditor::new(root_dir);
645 let path_auditor = PathAuditor::new(root_dir);
631
646
632 // TODO don't collect. Find a way of replicating the behavior of
647 // TODO don't collect. Find a way of replicating the behavior of
633 // `itertools::process_results`, but for `rayon::ParallelIterator`
648 // `itertools::process_results`, but for `rayon::ParallelIterator`
634 let new_results: IoResult<Vec<_>> = to_visit
649 let new_results: IoResult<Vec<_>> = to_visit
635 .into_par_iter()
650 .into_par_iter()
636 .filter_map(|(filename, entry)| -> Option<IoResult<_>> {
651 .filter_map(|(filename, entry)| -> Option<IoResult<_>> {
637 // Report ignored items in the dmap as long as they are not
652 // Report ignored items in the dmap as long as they are not
638 // under a symlink directory.
653 // under a symlink directory.
639 if path_auditor.check(filename) {
654 if path_auditor.check(filename) {
640 // TODO normalize for case-insensitive filesystems
655 // TODO normalize for case-insensitive filesystems
641 let buf = match hg_path_to_path_buf(filename) {
656 let buf = match hg_path_to_path_buf(filename) {
642 Ok(x) => x,
657 Ok(x) => x,
643 Err(e) => return Some(Err(e.into())),
658 Err(e) => return Some(Err(e.into())),
644 };
659 };
645 Some(Ok((
660 Some(Ok((
646 Cow::Borrowed(filename),
661 Cow::Borrowed(filename),
647 match root_dir.as_ref().join(&buf).symlink_metadata() {
662 match root_dir.as_ref().join(&buf).symlink_metadata() {
648 // File was just ignored, no links, and exists
663 // File was just ignored, no links, and exists
649 Ok(meta) => {
664 Ok(meta) => {
650 let metadata = HgMetadata::from_metadata(meta);
665 let metadata = HgMetadata::from_metadata(meta);
651 dispatch_found(
666 dispatch_found(
652 filename,
667 filename,
653 *entry,
668 *entry,
654 metadata,
669 metadata,
655 &dmap.copy_map,
670 &dmap.copy_map,
656 options,
671 options,
657 )
672 )
658 }
673 }
659 // File doesn't exist
674 // File doesn't exist
660 Err(_) => dispatch_missing(entry.state),
675 Err(_) => dispatch_missing(entry.state),
661 },
676 },
662 )))
677 )))
663 } else {
678 } else {
664 // It's either missing or under a symlink directory which
679 // It's either missing or under a symlink directory which
665 // we, in this case, report as missing.
680 // we, in this case, report as missing.
666 Some(Ok((
681 Some(Ok((
667 Cow::Borrowed(filename),
682 Cow::Borrowed(filename),
668 dispatch_missing(entry.state),
683 dispatch_missing(entry.state),
669 )))
684 )))
670 }
685 }
671 })
686 })
672 .collect();
687 .collect();
673
688
674 results.par_extend(new_results?);
689 results.par_extend(new_results?);
675
690
676 Ok(())
691 Ok(())
677 }
692 }
678
693
679 /// Get the status of files in the working directory.
694 /// Get the status of files in the working directory.
680 ///
695 ///
681 /// This is the current entry-point for `hg-core` and is realistically unusable
696 /// This is the current entry-point for `hg-core` and is realistically unusable
682 /// outside of a Python context because its arguments need to provide a lot of
697 /// outside of a Python context because its arguments need to provide a lot of
683 /// information that will not be necessary in the future.
698 /// information that will not be necessary in the future.
684 pub fn status<'a: 'c, 'b: 'c, 'c>(
699 pub fn status<'a: 'c, 'b: 'c, 'c>(
685 dmap: &'a DirstateMap,
700 dmap: &'a DirstateMap,
686 matcher: &'b (impl Matcher + Sync),
701 matcher: &'b (impl Matcher + Sync),
687 root_dir: impl AsRef<Path> + Sync + Send + Copy + 'c,
702 root_dir: impl AsRef<Path> + Sync + Send + Copy + 'c,
688 ignore_files: &[impl AsRef<Path> + 'c],
703 ignore_files: &[impl AsRef<Path> + 'c],
689 options: StatusOptions,
704 options: StatusOptions,
690 ) -> StatusResult<(
705 ) -> StatusResult<(
691 (Vec<Cow<'c, HgPath>>, DirstateStatus<'c>),
706 (Vec<Cow<'c, HgPath>>, DirstateStatus<'c>),
692 Vec<PatternFileWarning>,
707 Vec<PatternFileWarning>,
693 )> {
708 )> {
694 let (ignore_fn, warnings) = get_ignore_function(&ignore_files, root_dir)?;
709 let (ignore_fn, warnings) = get_ignore_function(&ignore_files, root_dir)?;
695
710
696 // Is the path or one of its ancestors ignored?
711 // Is the path or one of its ancestors ignored?
697 let dir_ignore_fn = |dir: &_| {
712 let dir_ignore_fn = |dir: &_| {
698 if ignore_fn(dir) {
713 if ignore_fn(dir) {
699 true
714 true
700 } else {
715 } else {
701 for p in find_dirs(dir) {
716 for p in find_dirs(dir) {
702 if ignore_fn(p) {
717 if ignore_fn(p) {
703 return true;
718 return true;
704 }
719 }
705 }
720 }
706 false
721 false
707 }
722 }
708 };
723 };
709
724
710 let files = matcher.file_set();
725 let files = matcher.file_set();
711
726
712 // Step 1: check the files explicitly mentioned by the user
727 // Step 1: check the files explicitly mentioned by the user
713 let explicit = walk_explicit(files, &dmap, root_dir, options);
728 let explicit = walk_explicit(files, &dmap, root_dir, options);
714
729
715 // Collect results into a `Vec` because we do very few lookups in most
730 // Collect results into a `Vec` because we do very few lookups in most
716 // cases.
731 // cases.
717 let (work, mut results): (Vec<_>, Vec<_>) = explicit
732 let (work, mut results): (Vec<_>, Vec<_>) = explicit
718 .filter_map(Result::ok)
733 .filter_map(Result::ok)
719 .map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch))
734 .map(|(filename, dispatch)| (Cow::Borrowed(filename), dispatch))
720 .partition(|(_, dispatch)| match dispatch {
735 .partition(|(_, dispatch)| match dispatch {
721 Dispatch::Directory { .. } => true,
736 Dispatch::Directory { .. } => true,
722 _ => false,
737 _ => false,
723 });
738 });
724
739
725 if !work.is_empty() {
740 if !work.is_empty() {
726 // Hashmaps are quite a bit slower to build than vecs, so only build it
741 // Hashmaps are quite a bit slower to build than vecs, so only build it
727 // if needed.
742 // if needed.
728 let old_results = results.iter().cloned().collect();
743 let old_results = results.iter().cloned().collect();
729
744
730 // Step 2: recursively check the working directory for changes if
745 // Step 2: recursively check the working directory for changes if
731 // needed
746 // needed
732 for (dir, dispatch) in work {
747 for (dir, dispatch) in work {
733 match dispatch {
748 match dispatch {
734 Dispatch::Directory { was_file } => {
749 Dispatch::Directory { was_file } => {
735 if was_file {
750 if was_file {
736 results.push((dir.to_owned(), Dispatch::Removed));
751 results.push((dir.to_owned(), Dispatch::Removed));
737 }
752 }
738 if options.list_ignored
753 if options.list_ignored
739 || options.list_unknown && !dir_ignore_fn(&dir)
754 || options.list_unknown && !dir_ignore_fn(&dir)
740 {
755 {
741 results.par_extend(traverse_dir(
756 results.par_extend(traverse_dir(
742 matcher,
757 matcher,
743 root_dir,
758 root_dir,
744 &dmap,
759 &dmap,
745 &dir,
760 &dir,
746 &old_results,
761 &old_results,
747 &ignore_fn,
762 &ignore_fn,
748 &dir_ignore_fn,
763 &dir_ignore_fn,
749 options,
764 options,
750 )?);
765 )?);
751 }
766 }
752 }
767 }
753 _ => unreachable!("There can only be directories in `work`"),
768 _ => unreachable!("There can only be directories in `work`"),
754 }
769 }
755 }
770 }
756 }
771 }
757
772
758 if !matcher.is_exact() {
773 if !matcher.is_exact() {
759 // Step 3: Check the remaining files from the dmap.
774 // Step 3: Check the remaining files from the dmap.
760 // If a dmap file is not in results yet, it was either
775 // If a dmap file is not in results yet, it was either
761 // a) not matched b) ignored, c) missing, or d) under a
776 // a) not matched b) ignored, c) missing, or d) under a
762 // symlink directory.
777 // symlink directory.
763
778
764 if options.list_unknown {
779 if options.list_unknown {
765 handle_unknowns(dmap, matcher, root_dir, options, &mut results)?;
780 handle_unknowns(dmap, matcher, root_dir, options, &mut results)?;
766 } else {
781 } else {
767 // We may not have walked the full directory tree above, so stat
782 // We may not have walked the full directory tree above, so stat
768 // and check everything we missed.
783 // and check everything we missed.
769 let stat_results = stat_dmap_entries(&dmap, root_dir, options);
784 extend_from_dmap(&dmap, root_dir, options, &mut results);
770 results.par_extend(stat_results.flatten().map(
771 |(filename, dispatch)| (Cow::Borrowed(filename), dispatch),
772 ));
773 }
785 }
774 }
786 }
775
787
776 Ok((build_response(results), warnings))
788 Ok((build_response(results), warnings))
777 }
789 }
General Comments 0
You need to be logged in to leave comments. Login now