Mercurial > public > mercurial-scm > hg
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 |