Show More
@@ -96,7 +96,7 b' def reliable_mtime_of(stat_result, prese' | |||||
96 | """same as `mtime_of`, but return None if the date might be ambiguous |
|
96 | """same as `mtime_of`, but return None if the date might be ambiguous | |
97 |
|
97 | |||
98 | A modification time is reliable if it is older than "present_time" (or |
|
98 | A modification time is reliable if it is older than "present_time" (or | |
99 | sufficiently in the futur). |
|
99 | sufficiently in the future). | |
100 |
|
100 | |||
101 | Otherwise a concurrent modification might happens with the same mtime. |
|
101 | Otherwise a concurrent modification might happens with the same mtime. | |
102 | """ |
|
102 | """ |
@@ -102,6 +102,35 b' impl TruncatedTimestamp {' | |||||
102 | } |
|
102 | } | |
103 | } |
|
103 | } | |
104 |
|
104 | |||
|
105 | /// Returns whether this timestamp is reliable as the "mtime" of a file. | |||
|
106 | /// | |||
|
107 | /// A modification time is reliable if it is older than `boundary` (or | |||
|
108 | /// sufficiently in the future). | |||
|
109 | /// | |||
|
110 | /// Otherwise a concurrent modification might happens with the same mtime. | |||
|
111 | pub fn is_reliable_mtime(&self, boundary: &Self) -> bool { | |||
|
112 | // If the mtime of the ambiguous file is younger (or equal) to the | |||
|
113 | // starting point of the `status` walk, we cannot garantee that | |||
|
114 | // another, racy, write will not happen right after with the same mtime | |||
|
115 | // and we cannot cache the information. | |||
|
116 | // | |||
|
117 | // However if the mtime is far away in the future, this is likely some | |||
|
118 | // mismatch between the current clock and previous file system | |||
|
119 | // operation. So mtime more than one days in the future are considered | |||
|
120 | // fine. | |||
|
121 | if self.truncated_seconds == boundary.truncated_seconds { | |||
|
122 | self.nanoseconds != 0 | |||
|
123 | && boundary.nanoseconds != 0 | |||
|
124 | && self.nanoseconds < boundary.nanoseconds | |||
|
125 | } else { | |||
|
126 | // `truncated_seconds` is less than 2**31, | |||
|
127 | // so this does not overflow `u32`: | |||
|
128 | let one_day_later = boundary.truncated_seconds + 24 * 3600; | |||
|
129 | self.truncated_seconds < boundary.truncated_seconds | |||
|
130 | || self.truncated_seconds > one_day_later | |||
|
131 | } | |||
|
132 | } | |||
|
133 | ||||
105 | /// The lower 31 bits of the number of seconds since the epoch. |
|
134 | /// The lower 31 bits of the number of seconds since the epoch. | |
106 | pub fn truncated_seconds(&self) -> u32 { |
|
135 | pub fn truncated_seconds(&self) -> u32 { | |
107 | self.truncated_seconds |
|
136 | self.truncated_seconds | |
@@ -191,7 +220,7 b' impl From<SystemTime> for TruncatedTimes' | |||||
191 | } |
|
220 | } | |
192 |
|
221 | |||
193 | const NSEC_PER_SEC: u32 = 1_000_000_000; |
|
222 | const NSEC_PER_SEC: u32 = 1_000_000_000; | |
194 | const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF; |
|
223 | pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF; | |
195 |
|
224 | |||
196 | pub const MTIME_UNSET: i32 = -1; |
|
225 | pub const MTIME_UNSET: i32 = -1; | |
197 |
|
226 |
@@ -73,6 +73,10 b' pub struct StatusOptions {' | |||||
73 |
|
73 | |||
74 | #[derive(Debug, Default)] |
|
74 | #[derive(Debug, Default)] | |
75 | pub struct DirstateStatus<'a> { |
|
75 | pub struct DirstateStatus<'a> { | |
|
76 | /// The current time at the start of the `status()` algorithm, as measured | |||
|
77 | /// and possibly truncated by the filesystem. | |||
|
78 | pub filesystem_time_at_status_start: Option<std::time::SystemTime>, | |||
|
79 | ||||
76 | /// Tracked files whose contents have changed since the parent revision |
|
80 | /// Tracked files whose contents have changed since the parent revision | |
77 | pub modified: Vec<HgPathCow<'a>>, |
|
81 | pub modified: Vec<HgPathCow<'a>>, | |
78 |
|
82 |
@@ -61,16 +61,21 b" pub fn status<'tree, 'on_disk: 'tree>(" | |||||
61 | (Box::new(|&_| true), vec![], None) |
|
61 | (Box::new(|&_| true), vec![], None) | |
62 | }; |
|
62 | }; | |
63 |
|
63 | |||
|
64 | let filesystem_time_at_status_start = filesystem_now(&root_dir).ok(); | |||
|
65 | let outcome = DirstateStatus { | |||
|
66 | filesystem_time_at_status_start, | |||
|
67 | ..Default::default() | |||
|
68 | }; | |||
64 | let common = StatusCommon { |
|
69 | let common = StatusCommon { | |
65 | dmap, |
|
70 | dmap, | |
66 | options, |
|
71 | options, | |
67 | matcher, |
|
72 | matcher, | |
68 | ignore_fn, |
|
73 | ignore_fn, | |
69 |
outcome: |
|
74 | outcome: Mutex::new(outcome), | |
70 | ignore_patterns_have_changed: patterns_changed, |
|
75 | ignore_patterns_have_changed: patterns_changed, | |
71 | new_cachable_directories: Default::default(), |
|
76 | new_cachable_directories: Default::default(), | |
72 | outated_cached_directories: Default::default(), |
|
77 | outated_cached_directories: Default::default(), | |
73 |
filesystem_time_at_status_start |
|
78 | filesystem_time_at_status_start, | |
74 | }; |
|
79 | }; | |
75 | let is_at_repo_root = true; |
|
80 | let is_at_repo_root = true; | |
76 | let hg_path = &BorrowedPath::OnDisk(HgPath::new("")); |
|
81 | let hg_path = &BorrowedPath::OnDisk(HgPath::new("")); |
@@ -13,14 +13,18 b' use format_bytes::format_bytes;' | |||||
13 | use hg; |
|
13 | use hg; | |
14 | use hg::config::Config; |
|
14 | use hg::config::Config; | |
15 | use hg::dirstate::has_exec_bit; |
|
15 | use hg::dirstate::has_exec_bit; | |
16 | use hg::errors::HgError; |
|
16 | use hg::dirstate::TruncatedTimestamp; | |
|
17 | use hg::dirstate::RANGE_MASK_31BIT; | |||
|
18 | use hg::errors::{HgError, IoResultExt}; | |||
|
19 | use hg::lock::LockError; | |||
17 | use hg::manifest::Manifest; |
|
20 | use hg::manifest::Manifest; | |
18 | use hg::matchers::AlwaysMatcher; |
|
21 | use hg::matchers::AlwaysMatcher; | |
19 | use hg::repo::Repo; |
|
22 | use hg::repo::Repo; | |
20 | use hg::utils::files::get_bytes_from_os_string; |
|
23 | use hg::utils::files::get_bytes_from_os_string; | |
21 |
use hg::utils::hg_path::{hg_path_to_ |
|
24 | use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; | |
22 | use hg::{HgPathCow, StatusOptions}; |
|
25 | use hg::{HgPathCow, StatusOptions}; | |
23 | use log::{info, warn}; |
|
26 | use log::{info, warn}; | |
|
27 | use std::io; | |||
24 |
|
28 | |||
25 | pub const HELP_TEXT: &str = " |
|
29 | pub const HELP_TEXT: &str = " | |
26 | Show changed files in the working directory |
|
30 | Show changed files in the working directory | |
@@ -229,6 +233,7 b' pub fn run(invocation: &crate::CliInvoca' | |||||
229 | &ds_status.unsure |
|
233 | &ds_status.unsure | |
230 | ); |
|
234 | ); | |
231 | } |
|
235 | } | |
|
236 | let mut fixup = Vec::new(); | |||
232 | if !ds_status.unsure.is_empty() |
|
237 | if !ds_status.unsure.is_empty() | |
233 | && (display_states.modified || display_states.clean) |
|
238 | && (display_states.modified || display_states.clean) | |
234 | { |
|
239 | { | |
@@ -243,8 +248,9 b' pub fn run(invocation: &crate::CliInvoca' | |||||
243 | } |
|
248 | } | |
244 | } else { |
|
249 | } else { | |
245 | if display_states.clean { |
|
250 | if display_states.clean { | |
246 | ds_status.clean.push(to_check); |
|
251 | ds_status.clean.push(to_check.clone()); | |
247 | } |
|
252 | } | |
|
253 | fixup.push(to_check.into_owned()) | |||
248 | } |
|
254 | } | |
249 | } |
|
255 | } | |
250 | } |
|
256 | } | |
@@ -318,6 +324,71 b' pub fn run(invocation: &crate::CliInvoca' | |||||
318 | b"C", |
|
324 | b"C", | |
319 | )?; |
|
325 | )?; | |
320 | } |
|
326 | } | |
|
327 | ||||
|
328 | let mut dirstate_write_needed = ds_status.dirty; | |||
|
329 | let filesystem_time_at_status_start = ds_status | |||
|
330 | .filesystem_time_at_status_start | |||
|
331 | .map(TruncatedTimestamp::from); | |||
|
332 | ||||
|
333 | if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) | |||
|
334 | && !dirstate_write_needed | |||
|
335 | { | |||
|
336 | // Nothing to update | |||
|
337 | return Ok(()); | |||
|
338 | } | |||
|
339 | ||||
|
340 | // Update the dirstate on disk if we can | |||
|
341 | let with_lock_result = | |||
|
342 | repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> { | |||
|
343 | if let Some(mtime_boundary) = filesystem_time_at_status_start { | |||
|
344 | for hg_path in fixup { | |||
|
345 | use std::os::unix::fs::MetadataExt; | |||
|
346 | let fs_path = hg_path_to_path_buf(&hg_path) | |||
|
347 | .expect("HgPath conversion"); | |||
|
348 | // Specifically do not reuse `fs_metadata` from | |||
|
349 | // `unsure_is_clean` which was needed before reading | |||
|
350 | // contents. Here we access metadata again after reading | |||
|
351 | // content, in case it changed in the meantime. | |||
|
352 | let fs_metadata = repo | |||
|
353 | .working_directory_vfs() | |||
|
354 | .symlink_metadata(&fs_path)?; | |||
|
355 | let mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata) | |||
|
356 | .when_reading_file(&fs_path)?; | |||
|
357 | if mtime.is_reliable_mtime(&mtime_boundary) { | |||
|
358 | let mode = fs_metadata.mode(); | |||
|
359 | let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT; | |||
|
360 | let mut entry = dmap | |||
|
361 | .get(&hg_path)? | |||
|
362 | .expect("ambiguous file not in dirstate"); | |||
|
363 | entry.set_clean(mode, size, mtime); | |||
|
364 | dmap.add_file(&hg_path, entry)?; | |||
|
365 | dirstate_write_needed = true | |||
|
366 | } | |||
|
367 | } | |||
|
368 | } | |||
|
369 | drop(dmap); // Avoid "already mutably borrowed" RefCell panics | |||
|
370 | if dirstate_write_needed { | |||
|
371 | repo.write_dirstate()? | |||
|
372 | } | |||
|
373 | Ok(()) | |||
|
374 | }); | |||
|
375 | match with_lock_result { | |||
|
376 | Ok(closure_result) => closure_result?, | |||
|
377 | Err(LockError::AlreadyHeld) => { | |||
|
378 | // Not updating the dirstate is not ideal but not critical: | |||
|
379 | // don’t keep our caller waiting until some other Mercurial | |||
|
380 | // process releases the lock. | |||
|
381 | } | |||
|
382 | Err(LockError::Other(HgError::IoError { error, .. })) | |||
|
383 | if error.kind() == io::ErrorKind::PermissionDenied => | |||
|
384 | { | |||
|
385 | // `hg status` on a read-only repository is fine | |||
|
386 | } | |||
|
387 | Err(LockError::Other(error)) => { | |||
|
388 | // Report other I/O errors | |||
|
389 | Err(error)? | |||
|
390 | } | |||
|
391 | } | |||
321 | Ok(()) |
|
392 | Ok(()) | |
322 | } |
|
393 | } | |
323 |
|
394 | |||
@@ -368,7 +439,7 b' fn unsure_is_modified(' | |||||
368 | hg_path: &HgPath, |
|
439 | hg_path: &HgPath, | |
369 | ) -> Result<bool, HgError> { |
|
440 | ) -> Result<bool, HgError> { | |
370 | let vfs = repo.working_directory_vfs(); |
|
441 | let vfs = repo.working_directory_vfs(); | |
371 |
let fs_path = hg_path_to_ |
|
442 | let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion"); | |
372 | let fs_metadata = vfs.symlink_metadata(&fs_path)?; |
|
443 | let fs_metadata = vfs.symlink_metadata(&fs_path)?; | |
373 | let is_symlink = fs_metadata.file_type().is_symlink(); |
|
444 | let is_symlink = fs_metadata.file_type().is_symlink(); | |
374 | // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the |
|
445 | // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the | |
@@ -399,5 +470,5 b' fn unsure_is_modified(' | |||||
399 | } else { |
|
470 | } else { | |
400 | vfs.read(fs_path)? |
|
471 | vfs.read(fs_path)? | |
401 | }; |
|
472 | }; | |
402 |
|
|
473 | Ok(contents_in_p1 != &*fs_contents) | |
403 | } |
|
474 | } |
@@ -172,8 +172,7 b' transaction: in-memory dirstate changes ' | |||||
172 | $ hg status -A |
|
172 | $ hg status -A | |
173 | C c |
|
173 | C c | |
174 | $ hg debugstate --no-dates |
|
174 | $ hg debugstate --no-dates | |
175 |
n 644 12 set c |
|
175 | n 644 12 set c | |
176 | n 0 -1 unset c (rhg known-bad-output !) |
|
|||
177 |
$ |
|
176 | $ hg backout -d '6 0' -m 'to be rollback-ed soon' -r . | |
178 | removing c |
|
177 | removing c | |
179 | adding b |
|
178 | adding b |
@@ -9,9 +9,6 b'' | |||||
9 | > EOF |
|
9 | > EOF | |
10 | #endif |
|
10 | #endif | |
11 |
|
11 | |||
12 | TODO: fix rhg bugs that make this test fail when status is enabled |
|
|||
13 | $ unset RHG_STATUS |
|
|||
14 |
|
||||
15 | Checking the size/permissions/file-type of files stored in the |
|
12 | Checking the size/permissions/file-type of files stored in the | |
16 | dirstate after an update where the files are changed concurrently |
|
13 | dirstate after an update where the files are changed concurrently | |
17 | outside of hg's control. |
|
14 | outside of hg's control. |
General Comments 0
You need to be logged in to leave comments.
Login now