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