Show More
@@ -9,8 +9,8 b'' | |||||
9 | //! It is currently missing a lot of functionality compared to the Python one |
|
9 | //! It is currently missing a lot of functionality compared to the Python one | |
10 | //! and will only be triggered in narrow cases. |
|
10 | //! and will only be triggered in narrow cases. | |
11 |
|
11 | |||
|
12 | use crate::dirstate::entry::TruncatedTimestamp; | |||
12 | use crate::dirstate_tree::on_disk::DirstateV2ParseError; |
|
13 | use crate::dirstate_tree::on_disk::DirstateV2ParseError; | |
13 |
|
||||
14 | use crate::{ |
|
14 | use crate::{ | |
15 | utils::hg_path::{HgPath, HgPathError}, |
|
15 | utils::hg_path::{HgPath, HgPathError}, | |
16 | PatternError, |
|
16 | PatternError, | |
@@ -77,7 +77,7 b' pub struct StatusOptions {' | |||||
77 | pub struct DirstateStatus<'a> { |
|
77 | pub struct DirstateStatus<'a> { | |
78 | /// The current time at the start of the `status()` algorithm, as measured |
|
78 | /// The current time at the start of the `status()` algorithm, as measured | |
79 | /// and possibly truncated by the filesystem. |
|
79 | /// and possibly truncated by the filesystem. | |
80 |
pub filesystem_time_at_status_start: Option< |
|
80 | pub filesystem_time_at_status_start: Option<TruncatedTimestamp>, | |
81 |
|
81 | |||
82 | /// Tracked files whose contents have changed since the parent revision |
|
82 | /// Tracked files whose contents have changed since the parent revision | |
83 | pub modified: Vec<StatusPath<'a>>, |
|
83 | pub modified: Vec<StatusPath<'a>>, |
@@ -382,7 +382,7 b' impl Node {' | |||||
382 | && self.flags().contains(Flags::HAS_MTIME) |
|
382 | && self.flags().contains(Flags::HAS_MTIME) | |
383 | && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED) |
|
383 | && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED) | |
384 | { |
|
384 | { | |
385 |
Ok(Some(self.mtime |
|
385 | Ok(Some(self.mtime()?)) | |
386 | } else { |
|
386 | } else { | |
387 | Ok(None) |
|
387 | Ok(None) | |
388 | } |
|
388 | } | |
@@ -402,6 +402,14 b' impl Node {' | |||||
402 | file_type | permisions |
|
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 | fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> { |
|
413 | fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> { | |
406 | // TODO: convert through raw bits instead? |
|
414 | // TODO: convert through raw bits instead? | |
407 | let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED); |
|
415 | let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED); | |
@@ -418,11 +426,7 b' impl Node {' | |||||
418 | && !self.flags().contains(Flags::DIRECTORY) |
|
426 | && !self.flags().contains(Flags::DIRECTORY) | |
419 | && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED) |
|
427 | && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED) | |
420 | { |
|
428 | { | |
421 | let mut m: TruncatedTimestamp = self.mtime.try_into()?; |
|
429 | Some(self.mtime()?) | |
422 | if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) { |
|
|||
423 | m.second_ambiguous = true; |
|
|||
424 | } |
|
|||
425 | Some(m) |
|
|||
426 | } else { |
|
430 | } else { | |
427 | None |
|
431 | None | |
428 | }; |
|
432 | }; | |
@@ -681,7 +685,7 b" impl Writer<'_, '_> {" | |||||
681 | dirstate_map::NodeData::Entry(entry) => { |
|
685 | dirstate_map::NodeData::Entry(entry) => { | |
682 | Node::from_dirstate_entry(entry) |
|
686 | Node::from_dirstate_entry(entry) | |
683 | } |
|
687 | } | |
684 |
dirstate_map::NodeData::CachedDirectory { mtime } => |
|
688 | dirstate_map::NodeData::CachedDirectory { mtime } => { | |
685 | // we currently never set a mtime if unknown file |
|
689 | // we currently never set a mtime if unknown file | |
686 | // are present. |
|
690 | // are present. | |
687 | // So if we have a mtime for a directory, we know |
|
691 | // So if we have a mtime for a directory, we know | |
@@ -692,12 +696,14 b" impl Writer<'_, '_> {" | |||||
692 | // We never set ALL_IGNORED_RECORDED since we |
|
696 | // We never set ALL_IGNORED_RECORDED since we | |
693 | // don't track that case |
|
697 | // don't track that case | |
694 | // currently. |
|
698 | // currently. | |
695 | Flags::DIRECTORY |
|
699 | let mut flags = Flags::DIRECTORY | |
696 | | Flags::HAS_MTIME |
|
700 | | Flags::HAS_MTIME | |
697 |
| Flags::ALL_UNKNOWN_RECORDED |
|
701 | | Flags::ALL_UNKNOWN_RECORDED; | |
698 |
|
|
702 | if mtime.second_ambiguous { | |
699 | (*mtime).into(), |
|
703 | flags.insert(Flags::MTIME_SECOND_AMBIGUOUS) | |
700 |
|
|
704 | } | |
|
705 | (flags, 0.into(), (*mtime).into()) | |||
|
706 | } | |||
701 | dirstate_map::NodeData::None => ( |
|
707 | dirstate_map::NodeData::None => ( | |
702 | Flags::DIRECTORY, |
|
708 | Flags::DIRECTORY, | |
703 | 0.into(), |
|
709 | 0.into(), |
@@ -63,7 +63,8 b" pub fn status<'tree, 'on_disk: 'tree>(" | |||||
63 | (Box::new(|&_| true), vec![], None) |
|
63 | (Box::new(|&_| true), vec![], None) | |
64 | }; |
|
64 | }; | |
65 |
|
65 | |||
66 |
let filesystem_time_at_status_start = |
|
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() | |
@@ -145,7 +146,7 b" struct StatusCommon<'a, 'tree, 'on_disk:" | |||||
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< |
|
149 | filesystem_time_at_status_start: Option<TruncatedTimestamp>, | |
149 | } |
|
150 | } | |
150 |
|
151 | |||
151 | enum Outcome { |
|
152 | enum Outcome { | |
@@ -472,71 +473,86 b" impl<'a, 'tree, 'on_disk> StatusCommon<'" | |||||
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 |
|
|
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 |
|
|||
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 | } |
|
|||
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 | Ok(()) |
|
557 | Ok(()) | |
542 | } |
|
558 | } |
@@ -315,9 +315,8 b' pub fn run(invocation: &crate::CliInvoca' | |||||
315 | } |
|
315 | } | |
316 |
|
316 | |||
317 | let mut dirstate_write_needed = ds_status.dirty; |
|
317 | let mut dirstate_write_needed = ds_status.dirty; | |
318 |
let filesystem_time_at_status_start = |
|
318 | let filesystem_time_at_status_start = | |
319 | .filesystem_time_at_status_start |
|
319 | ds_status.filesystem_time_at_status_start; | |
320 | .map(TruncatedTimestamp::from); |
|
|||
321 |
|
320 | |||
322 | if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) |
|
321 | if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) | |
323 | && !dirstate_write_needed |
|
322 | && !dirstate_write_needed |
General Comments 0
You need to be logged in to leave comments.
Login now