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