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