comparison rust/hg-core/src/dirstate_tree/status.rs @ 48501:4afb9627dc77

dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too This would only be relevant in contrived scenarios such as a dirstate file being written with a libc that supports sub-second precision in mtimes, then transfered (at the filesystem level, not `hg clone`) to another system where libc *doesn?t* have sub-second precision and truncates the stored mtime of a directory to integer seconds. Differential Revision: https://phab.mercurial-scm.org/D11939
author Simon Sapin <simon.sapin@octobus.net>
date Fri, 17 Dec 2021 14:15:08 +0100
parents 473af5cbc209
children 94e36b230990
comparison
equal deleted inserted replaced
48500:c5d6c874766a 48501:4afb9627dc77
61 (ignore_fn, warnings, Some(changed)) 61 (ignore_fn, warnings, Some(changed))
62 } else { 62 } else {
63 (Box::new(|&_| true), vec![], None) 63 (Box::new(|&_| true), vec![], None)
64 }; 64 };
65 65
66 let filesystem_time_at_status_start = filesystem_now(&root_dir).ok(); 66 let filesystem_time_at_status_start =
67 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
67 let outcome = DirstateStatus { 68 let outcome = DirstateStatus {
68 filesystem_time_at_status_start, 69 filesystem_time_at_status_start,
69 ..Default::default() 70 ..Default::default()
70 }; 71 };
71 let common = StatusCommon { 72 let common = StatusCommon {
143 /// and therefore isn’t reading `.hgignore`. 144 /// and therefore isn’t reading `.hgignore`.
144 ignore_patterns_have_changed: Option<bool>, 145 ignore_patterns_have_changed: Option<bool>,
145 146
146 /// The current time at the start of the `status()` algorithm, as measured 147 /// The current time at the start of the `status()` algorithm, as measured
147 /// and possibly truncated by the filesystem. 148 /// and possibly truncated by the filesystem.
148 filesystem_time_at_status_start: Option<SystemTime>, 149 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
149 } 150 }
150 151
151 enum Outcome { 152 enum Outcome {
152 Modified, 153 Modified,
153 Added, 154 Added,
470 &self, 471 &self,
471 children_all_have_dirstate_node_or_are_ignored: bool, 472 children_all_have_dirstate_node_or_are_ignored: bool,
472 directory_metadata: &std::fs::Metadata, 473 directory_metadata: &std::fs::Metadata,
473 dirstate_node: NodeRef<'tree, 'on_disk>, 474 dirstate_node: NodeRef<'tree, 'on_disk>,
474 ) -> Result<(), DirstateV2ParseError> { 475 ) -> Result<(), DirstateV2ParseError> {
475 if children_all_have_dirstate_node_or_are_ignored { 476 if !children_all_have_dirstate_node_or_are_ignored {
476 // All filesystem directory entries from `read_dir` have a 477 return Ok(());
477 // corresponding node in the dirstate, so we can reconstitute the 478 }
478 // names of those entries without calling `read_dir` again. 479 // All filesystem directory entries from `read_dir` have a
479 if let (Some(status_start), Ok(directory_mtime)) = ( 480 // corresponding node in the dirstate, so we can reconstitute the
480 &self.filesystem_time_at_status_start, 481 // names of those entries without calling `read_dir` again.
481 directory_metadata.modified(), 482
483 // TODO: use let-else here and below when available:
484 // https://github.com/rust-lang/rust/issues/87335
485 let status_start = if let Some(status_start) =
486 &self.filesystem_time_at_status_start
487 {
488 status_start
489 } else {
490 return Ok(());
491 };
492
493 // Although the Rust standard library’s `SystemTime` type
494 // has nanosecond precision, the times reported for a
495 // directory’s (or file’s) modified time may have lower
496 // resolution based on the filesystem (for example ext3
497 // only stores integer seconds), kernel (see
498 // https://stackoverflow.com/a/14393315/1162888), etc.
499 let directory_mtime = if let Ok(option) =
500 TruncatedTimestamp::for_reliable_mtime_of(
501 directory_metadata,
502 status_start,
482 ) { 503 ) {
483 // Although the Rust standard library’s `SystemTime` type 504 if let Some(directory_mtime) = option {
484 // has nanosecond precision, the times reported for a 505 directory_mtime
485 // directory’s (or file’s) modified time may have lower 506 } else {
486 // resolution based on the filesystem (for example ext3 507 // The directory was modified too recently,
487 // only stores integer seconds), kernel (see 508 // don’t cache its `read_dir` results.
488 // https://stackoverflow.com/a/14393315/1162888), etc. 509 //
489 if &directory_mtime >= status_start { 510 // 1. A change to this directory (direct child was
490 // The directory was modified too recently, don’t cache its 511 // added or removed) cause its mtime to be set
491 // `read_dir` results. 512 // (possibly truncated) to `directory_mtime`
492 // 513 // 2. This `status` algorithm calls `read_dir`
493 // A timeline like this is possible: 514 // 3. An other change is made to the same directory is
494 // 515 // made so that calling `read_dir` agin would give
495 // 1. A change to this directory (direct child was 516 // different results, but soon enough after 1. that
496 // added or removed) cause its mtime to be set 517 // the mtime stays the same
497 // (possibly truncated) to `directory_mtime` 518 //
498 // 2. This `status` algorithm calls `read_dir` 519 // On a system where the time resolution poor, this
499 // 3. An other change is made to the same directory is 520 // scenario is not unlikely if all three steps are caused
500 // made so that calling `read_dir` agin would give 521 // by the same script.
501 // different results, but soon enough after 1. that 522 return Ok(());
502 // the mtime stays the same 523 }
503 // 524 } else {
504 // On a system where the time resolution poor, this 525 // OS/libc does not support mtime?
505 // scenario is not unlikely if all three steps are caused 526 return Ok(());
506 // by the same script. 527 };
507 } else { 528 // We’ve observed (through `status_start`) that time has
508 // We’ve observed (through `status_start`) that time has 529 // “progressed” since `directory_mtime`, so any further
509 // “progressed” since `directory_mtime`, so any further 530 // change to this directory is extremely likely to cause a
510 // change to this directory is extremely likely to cause a 531 // different mtime.
511 // different mtime. 532 //
512 // 533 // Having the same mtime again is not entirely impossible
513 // Having the same mtime again is not entirely impossible 534 // since the system clock is not monotonous. It could jump
514 // since the system clock is not monotonous. It could jump 535 // backward to some point before `directory_mtime`, then a
515 // backward to some point before `directory_mtime`, then a 536 // directory change could potentially happen during exactly
516 // directory change could potentially happen during exactly 537 // the wrong tick.
517 // the wrong tick. 538 //
518 // 539 // We deem this scenario (unlike the previous one) to be
519 // We deem this scenario (unlike the previous one) to be 540 // unlikely enough in practice.
520 // unlikely enough in practice. 541
521 let truncated = TruncatedTimestamp::from(directory_mtime); 542 let is_up_to_date =
522 let is_up_to_date = if let Some(cached) = 543 if let Some(cached) = dirstate_node.cached_directory_mtime()? {
523 dirstate_node.cached_directory_mtime()? 544 cached.likely_equal(directory_mtime)
524 { 545 } else {
525 cached.likely_equal(truncated) 546 false
526 } else { 547 };
527 false 548 if !is_up_to_date {
528 }; 549 let hg_path = dirstate_node
529 if !is_up_to_date { 550 .full_path_borrowed(self.dmap.on_disk)?
530 let hg_path = dirstate_node 551 .detach_from_tree();
531 .full_path_borrowed(self.dmap.on_disk)? 552 self.new_cachable_directories
532 .detach_from_tree(); 553 .lock()
533 self.new_cachable_directories 554 .unwrap()
534 .lock() 555 .push((hg_path, directory_mtime))
535 .unwrap()
536 .push((hg_path, truncated))
537 }
538 }
539 }
540 } 556 }
541 Ok(()) 557 Ok(())
542 } 558 }
543 559
544 /// A file with `EntryState::Normal` in the dirstate was found in the 560 /// A file with `EntryState::Normal` in the dirstate was found in the