##// END OF EJS Templates
dirstate-v2: Apply SECOND_AMBIGUOUS to directory mtimes too...
Simon Sapin -
r49332:4afb9627 default
parent child Browse files
Show More
@@ -9,8 +9,8 b''
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 use crate::dirstate::entry::TruncatedTimestamp;
12 13 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
13
14 14 use crate::{
15 15 utils::hg_path::{HgPath, HgPathError},
16 16 PatternError,
@@ -77,7 +77,7 b' pub struct StatusOptions {'
77 77 pub struct DirstateStatus<'a> {
78 78 /// The current time at the start of the `status()` algorithm, as measured
79 79 /// and possibly truncated by the filesystem.
80 pub filesystem_time_at_status_start: Option<std::time::SystemTime>,
80 pub filesystem_time_at_status_start: Option<TruncatedTimestamp>,
81 81
82 82 /// Tracked files whose contents have changed since the parent revision
83 83 pub modified: Vec<StatusPath<'a>>,
@@ -382,7 +382,7 b' impl Node {'
382 382 && self.flags().contains(Flags::HAS_MTIME)
383 383 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
384 384 {
385 Ok(Some(self.mtime.try_into()?))
385 Ok(Some(self.mtime()?))
386 386 } else {
387 387 Ok(None)
388 388 }
@@ -402,6 +402,14 b' impl Node {'
402 402 file_type | permisions
403 403 }
404 404
405 fn mtime(&self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
406 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
407 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
408 m.second_ambiguous = true;
409 }
410 Ok(m)
411 }
412
405 413 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
406 414 // TODO: convert through raw bits instead?
407 415 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
@@ -418,11 +426,7 b' impl Node {'
418 426 && !self.flags().contains(Flags::DIRECTORY)
419 427 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
420 428 {
421 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
422 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
423 m.second_ambiguous = true;
424 }
425 Some(m)
429 Some(self.mtime()?)
426 430 } else {
427 431 None
428 432 };
@@ -681,7 +685,7 b" impl Writer<'_, '_> {"
681 685 dirstate_map::NodeData::Entry(entry) => {
682 686 Node::from_dirstate_entry(entry)
683 687 }
684 dirstate_map::NodeData::CachedDirectory { mtime } => (
688 dirstate_map::NodeData::CachedDirectory { mtime } => {
685 689 // we currently never set a mtime if unknown file
686 690 // are present.
687 691 // So if we have a mtime for a directory, we know
@@ -692,12 +696,14 b" impl Writer<'_, '_> {"
692 696 // We never set ALL_IGNORED_RECORDED since we
693 697 // don't track that case
694 698 // currently.
695 Flags::DIRECTORY
699 let mut flags = Flags::DIRECTORY
696 700 | Flags::HAS_MTIME
697 | Flags::ALL_UNKNOWN_RECORDED,
698 0.into(),
699 (*mtime).into(),
700 ),
701 | Flags::ALL_UNKNOWN_RECORDED;
702 if mtime.second_ambiguous {
703 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS)
704 }
705 (flags, 0.into(), (*mtime).into())
706 }
701 707 dirstate_map::NodeData::None => (
702 708 Flags::DIRECTORY,
703 709 0.into(),
@@ -63,7 +63,8 b" pub fn status<'tree, 'on_disk: 'tree>("
63 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 68 let outcome = DirstateStatus {
68 69 filesystem_time_at_status_start,
69 70 ..Default::default()
@@ -145,7 +146,7 b" struct StatusCommon<'a, 'tree, 'on_disk:"
145 146
146 147 /// The current time at the start of the `status()` algorithm, as measured
147 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 152 enum Outcome {
@@ -472,71 +473,86 b" impl<'a, 'tree, 'on_disk> StatusCommon<'"
472 473 directory_metadata: &std::fs::Metadata,
473 474 dirstate_node: NodeRef<'tree, 'on_disk>,
474 475 ) -> Result<(), DirstateV2ParseError> {
475 if children_all_have_dirstate_node_or_are_ignored {
476 // All filesystem directory entries from `read_dir` have a
477 // corresponding node in the dirstate, so we can reconstitute the
478 // names of those entries without calling `read_dir` again.
479 if let (Some(status_start), Ok(directory_mtime)) = (
480 &self.filesystem_time_at_status_start,
481 directory_metadata.modified(),
476 if !children_all_have_dirstate_node_or_are_ignored {
477 return Ok(());
478 }
479 // All filesystem directory entries from `read_dir` have a
480 // corresponding node in the dirstate, so we can reconstitute the
481 // names of those entries without calling `read_dir` again.
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
484 // has nanosecond precision, the times reported for a
485 // directory’s (or file’s) modified time may have lower
486 // resolution based on the filesystem (for example ext3
487 // only stores integer seconds), kernel (see
488 // https://stackoverflow.com/a/14393315/1162888), etc.
489 if &directory_mtime >= status_start {
490 // The directory was modified too recently, don’t cache its
491 // `read_dir` results.
492 //
493 // A timeline like this is possible:
494 //
495 // 1. A change to this directory (direct child was
496 // added or removed) cause its mtime to be set
497 // (possibly truncated) to `directory_mtime`
498 // 2. This `status` algorithm calls `read_dir`
499 // 3. An other change is made to the same directory is
500 // made so that calling `read_dir` agin would give
501 // different results, but soon enough after 1. that
502 // the mtime stays the same
503 //
504 // On a system where the time resolution poor, this
505 // scenario is not unlikely if all three steps are caused
506 // by the same script.
507 } else {
508 // We’ve observed (through `status_start`) that time has
509 // “progressed” since `directory_mtime`, so any further
510 // change to this directory is extremely likely to cause a
511 // different mtime.
512 //
513 // Having the same mtime again is not entirely impossible
514 // since the system clock is not monotonous. It could jump
515 // backward to some point before `directory_mtime`, then a
516 // directory change could potentially happen during exactly
517 // the wrong tick.
518 //
519 // We deem this scenario (unlike the previous one) to be
520 // unlikely enough in practice.
521 let truncated = TruncatedTimestamp::from(directory_mtime);
522 let is_up_to_date = if let Some(cached) =
523 dirstate_node.cached_directory_mtime()?
524 {
525 cached.likely_equal(truncated)
526 } else {
527 false
528 };
529 if !is_up_to_date {
530 let hg_path = dirstate_node
531 .full_path_borrowed(self.dmap.on_disk)?
532 .detach_from_tree();
533 self.new_cachable_directories
534 .lock()
535 .unwrap()
536 .push((hg_path, truncated))
537 }
538 }
504 if let Some(directory_mtime) = option {
505 directory_mtime
506 } else {
507 // The directory was modified too recently,
508 // don’t cache its `read_dir` results.
509 //
510 // 1. A change to this directory (direct child was
511 // added or removed) cause its mtime to be set
512 // (possibly truncated) to `directory_mtime`
513 // 2. This `status` algorithm calls `read_dir`
514 // 3. An other change is made to the same directory is
515 // made so that calling `read_dir` agin would give
516 // different results, but soon enough after 1. that
517 // the mtime stays the same
518 //
519 // On a system where the time resolution poor, this
520 // scenario is not unlikely if all three steps are caused
521 // by the same script.
522 return Ok(());
539 523 }
524 } else {
525 // OS/libc does not support mtime?
526 return Ok(());
527 };
528 // We’ve observed (through `status_start`) that time has
529 // “progressed” since `directory_mtime`, so any further
530 // change to this directory is extremely likely to cause a
531 // different mtime.
532 //
533 // Having the same mtime again is not entirely impossible
534 // since the system clock is not monotonous. It could jump
535 // backward to some point before `directory_mtime`, then a
536 // directory change could potentially happen during exactly
537 // the wrong tick.
538 //
539 // We deem this scenario (unlike the previous one) to be
540 // unlikely enough in practice.
541
542 let is_up_to_date =
543 if let Some(cached) = dirstate_node.cached_directory_mtime()? {
544 cached.likely_equal(directory_mtime)
545 } else {
546 false
547 };
548 if !is_up_to_date {
549 let hg_path = dirstate_node
550 .full_path_borrowed(self.dmap.on_disk)?
551 .detach_from_tree();
552 self.new_cachable_directories
553 .lock()
554 .unwrap()
555 .push((hg_path, directory_mtime))
540 556 }
541 557 Ok(())
542 558 }
@@ -315,9 +315,8 b' pub fn run(invocation: &crate::CliInvoca'
315 315 }
316 316
317 317 let mut dirstate_write_needed = ds_status.dirty;
318 let filesystem_time_at_status_start = ds_status
319 .filesystem_time_at_status_start
320 .map(TruncatedTimestamp::from);
318 let filesystem_time_at_status_start =
319 ds_status.filesystem_time_at_status_start;
321 320
322 321 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
323 322 && !dirstate_write_needed
General Comments 0
You need to be logged in to leave comments. Login now