Mercurial > public > mercurial-scm > hg-stable
diff rust/hg-core/src/dirstate_tree/status.rs @ 48538: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 |
line wrap: on
line diff
--- a/rust/hg-core/src/dirstate_tree/status.rs Wed Dec 15 15:28:41 2021 +0100 +++ b/rust/hg-core/src/dirstate_tree/status.rs Fri Dec 17 14:15:08 2021 +0100 @@ -63,7 +63,8 @@ (Box::new(|&_| true), vec![], None) }; - let filesystem_time_at_status_start = filesystem_now(&root_dir).ok(); + let filesystem_time_at_status_start = + filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from); let outcome = DirstateStatus { filesystem_time_at_status_start, ..Default::default() @@ -145,7 +146,7 @@ /// The current time at the start of the `status()` algorithm, as measured /// and possibly truncated by the filesystem. - filesystem_time_at_status_start: Option<SystemTime>, + filesystem_time_at_status_start: Option<TruncatedTimestamp>, } enum Outcome { @@ -472,71 +473,86 @@ directory_metadata: &std::fs::Metadata, dirstate_node: NodeRef<'tree, 'on_disk>, ) -> Result<(), DirstateV2ParseError> { - if children_all_have_dirstate_node_or_are_ignored { - // All filesystem directory entries from `read_dir` have a - // corresponding node in the dirstate, so we can reconstitute the - // names of those entries without calling `read_dir` again. - if let (Some(status_start), Ok(directory_mtime)) = ( - &self.filesystem_time_at_status_start, - directory_metadata.modified(), + if !children_all_have_dirstate_node_or_are_ignored { + return Ok(()); + } + // All filesystem directory entries from `read_dir` have a + // corresponding node in the dirstate, so we can reconstitute the + // names of those entries without calling `read_dir` again. + + // TODO: use let-else here and below when available: + // https://github.com/rust-lang/rust/issues/87335 + let status_start = if let Some(status_start) = + &self.filesystem_time_at_status_start + { + status_start + } else { + return Ok(()); + }; + + // Although the Rust standard library’s `SystemTime` type + // has nanosecond precision, the times reported for a + // directory’s (or file’s) modified time may have lower + // resolution based on the filesystem (for example ext3 + // only stores integer seconds), kernel (see + // https://stackoverflow.com/a/14393315/1162888), etc. + let directory_mtime = if let Ok(option) = + TruncatedTimestamp::for_reliable_mtime_of( + directory_metadata, + status_start, ) { - // Although the Rust standard library’s `SystemTime` type - // has nanosecond precision, the times reported for a - // directory’s (or file’s) modified time may have lower - // resolution based on the filesystem (for example ext3 - // only stores integer seconds), kernel (see - // https://stackoverflow.com/a/14393315/1162888), etc. - if &directory_mtime >= status_start { - // The directory was modified too recently, don’t cache its - // `read_dir` results. - // - // A timeline like this is possible: - // - // 1. A change to this directory (direct child was - // added or removed) cause its mtime to be set - // (possibly truncated) to `directory_mtime` - // 2. This `status` algorithm calls `read_dir` - // 3. An other change is made to the same directory is - // made so that calling `read_dir` agin would give - // different results, but soon enough after 1. that - // the mtime stays the same - // - // On a system where the time resolution poor, this - // scenario is not unlikely if all three steps are caused - // by the same script. - } else { - // We’ve observed (through `status_start`) that time has - // “progressed” since `directory_mtime`, so any further - // change to this directory is extremely likely to cause a - // different mtime. - // - // Having the same mtime again is not entirely impossible - // since the system clock is not monotonous. It could jump - // backward to some point before `directory_mtime`, then a - // directory change could potentially happen during exactly - // the wrong tick. - // - // We deem this scenario (unlike the previous one) to be - // unlikely enough in practice. - let truncated = TruncatedTimestamp::from(directory_mtime); - let is_up_to_date = if let Some(cached) = - dirstate_node.cached_directory_mtime()? - { - cached.likely_equal(truncated) - } else { - false - }; - if !is_up_to_date { - let hg_path = dirstate_node - .full_path_borrowed(self.dmap.on_disk)? - .detach_from_tree(); - self.new_cachable_directories - .lock() - .unwrap() - .push((hg_path, truncated)) - } - } + if let Some(directory_mtime) = option { + directory_mtime + } else { + // The directory was modified too recently, + // don’t cache its `read_dir` results. + // + // 1. A change to this directory (direct child was + // added or removed) cause its mtime to be set + // (possibly truncated) to `directory_mtime` + // 2. This `status` algorithm calls `read_dir` + // 3. An other change is made to the same directory is + // made so that calling `read_dir` agin would give + // different results, but soon enough after 1. that + // the mtime stays the same + // + // On a system where the time resolution poor, this + // scenario is not unlikely if all three steps are caused + // by the same script. + return Ok(()); } + } else { + // OS/libc does not support mtime? + return Ok(()); + }; + // We’ve observed (through `status_start`) that time has + // “progressed” since `directory_mtime`, so any further + // change to this directory is extremely likely to cause a + // different mtime. + // + // Having the same mtime again is not entirely impossible + // since the system clock is not monotonous. It could jump + // backward to some point before `directory_mtime`, then a + // directory change could potentially happen during exactly + // the wrong tick. + // + // We deem this scenario (unlike the previous one) to be + // unlikely enough in practice. + + let is_up_to_date = + if let Some(cached) = dirstate_node.cached_directory_mtime()? { + cached.likely_equal(directory_mtime) + } else { + false + }; + if !is_up_to_date { + let hg_path = dirstate_node + .full_path_borrowed(self.dmap.on_disk)? + .detach_from_tree(); + self.new_cachable_directories + .lock() + .unwrap() + .push((hg_path, directory_mtime)) } Ok(()) }