##// END OF EJS Templates
rhg: Update the dirstate on disk after status...
Simon Sapin -
r49250:000130cf default
parent child Browse files
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: Default::default(),
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: filesystem_now(&root_dir).ok(),
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_os_string, HgPath};
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_os_string(hg_path).expect("HgPath conversion");
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 return Ok(contents_in_p1 != &*fs_contents);
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 (no-rhg !)
175 n 644 12 set c
176 n 0 -1 unset c (rhg known-bad-output !)
177 $ hg backout -d '6 0' -m 'to be rollback-ed soon' -r .
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.
@@ -9,10 +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
16 ------ Test dirstate._dirs refcounting
12 ------ Test dirstate._dirs refcounting
17
13
18 $ hg init t
14 $ hg init t
General Comments 0
You need to be logged in to leave comments. Login now