##// END OF EJS Templates
rhg: remember the inode of .hg/dirstate...
Raphaël Gomès -
r51140:dbe09fb0 stable
parent child Browse files
Show More
@@ -570,6 +570,12 b' if rustmod is not None:'
570 570 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
571 571 if self._use_dirstate_v2:
572 572 self.docket # load the data if needed
573 inode = (
574 self.identity.stat.st_ino
575 if self.identity is not None
576 and self.identity.stat is not None
577 else None
578 )
573 579 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
574 580 if not self.docket.uuid:
575 581 data = b''
@@ -581,12 +587,19 b' if rustmod is not None:'
581 587 self.docket.data_size,
582 588 self.docket.tree_metadata,
583 589 self.docket.uuid,
590 inode,
584 591 )
585 592 parents = self.docket.parents
586 593 else:
587 594 self._set_identity()
595 inode = (
596 self.identity.stat.st_ino
597 if self.identity is not None
598 and self.identity.stat is not None
599 else None
600 )
588 601 self._map, parents = rustmod.DirstateMap.new_v1(
589 self._readdirstatefile()
602 self._readdirstatefile(), inode
590 603 )
591 604
592 605 if parents and not self._dirtyparents:
@@ -76,6 +76,14 b" pub struct DirstateMap<'on_disk> {"
76 76 /// Can be `None` if using dirstate v1 or if it's a brand new dirstate.
77 77 pub(super) old_uuid: Option<Vec<u8>>,
78 78
79 /// Identity of the dirstate file (for dirstate-v1) or the docket file
80 /// (v2). Used to detect if the file has changed from another process.
81 /// Since it's always written atomically, we can compare the inode to
82 /// check the file identity.
83 ///
84 /// TODO On non-Unix systems, something like hashing is a possibility?
85 pub(super) identity: Option<u64>,
86
79 87 pub(super) dirstate_version: DirstateVersion,
80 88
81 89 /// Controlled by config option `devel.dirstate.v2.data_update_mode`
@@ -468,6 +476,7 b" impl<'on_disk> DirstateMap<'on_disk> {"
468 476 unreachable_bytes: 0,
469 477 old_data_size: 0,
470 478 old_uuid: None,
479 identity: None,
471 480 dirstate_version: DirstateVersion::V1,
472 481 write_mode: DirstateMapWriteMode::Auto,
473 482 }
@@ -479,9 +488,10 b" impl<'on_disk> DirstateMap<'on_disk> {"
479 488 data_size: usize,
480 489 metadata: &[u8],
481 490 uuid: Vec<u8>,
491 identity: Option<u64>,
482 492 ) -> Result<Self, DirstateError> {
483 493 if let Some(data) = on_disk.get(..data_size) {
484 Ok(on_disk::read(data, metadata, uuid)?)
494 Ok(on_disk::read(data, metadata, uuid, identity)?)
485 495 } else {
486 496 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
487 497 }
@@ -490,6 +500,7 b" impl<'on_disk> DirstateMap<'on_disk> {"
490 500 #[timed]
491 501 pub fn new_v1(
492 502 on_disk: &'on_disk [u8],
503 identity: Option<u64>,
493 504 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
494 505 let mut map = Self::empty(on_disk);
495 506 if map.on_disk.is_empty() {
@@ -531,6 +542,7 b" impl<'on_disk> DirstateMap<'on_disk> {"
531 542 },
532 543 )?;
533 544 let parents = Some(parents.clone());
545 map.identity = identity;
534 546
535 547 Ok((map, parents))
536 548 }
@@ -1853,6 +1865,7 b' mod tests {'
1853 1865 packed_len,
1854 1866 metadata.as_bytes(),
1855 1867 vec![],
1868 None,
1856 1869 )?;
1857 1870
1858 1871 // Check that everything is accounted for
@@ -291,6 +291,7 b" pub(super) fn read<'on_disk>("
291 291 on_disk: &'on_disk [u8],
292 292 metadata: &[u8],
293 293 uuid: Vec<u8>,
294 identity: Option<u64>,
294 295 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
295 296 if on_disk.is_empty() {
296 297 let mut map = DirstateMap::empty(on_disk);
@@ -314,6 +315,7 b" pub(super) fn read<'on_disk>("
314 315 unreachable_bytes: meta.unreachable_bytes.get(),
315 316 old_data_size: on_disk.len(),
316 317 old_uuid: Some(uuid),
318 identity,
317 319 dirstate_version: DirstateVersion::V2,
318 320 write_mode: DirstateMapWriteMode::Auto,
319 321 };
@@ -31,6 +31,7 b' impl OwningDirstateMap {'
31 31
32 32 pub fn new_v1<OnDisk>(
33 33 on_disk: OnDisk,
34 identity: Option<u64>,
34 35 ) -> Result<(Self, DirstateParents), DirstateError>
35 36 where
36 37 OnDisk: Deref<Target = [u8]> + Send + 'static,
@@ -42,7 +43,7 b' impl OwningDirstateMap {'
42 43 OwningDirstateMapTryBuilder {
43 44 on_disk,
44 45 map_builder: |bytes| {
45 DirstateMap::new_v1(&bytes).map(|(dmap, p)| {
46 DirstateMap::new_v1(&bytes, identity).map(|(dmap, p)| {
46 47 parents = p.unwrap_or(DirstateParents::NULL);
47 48 dmap
48 49 })
@@ -58,6 +59,7 b' impl OwningDirstateMap {'
58 59 data_size: usize,
59 60 metadata: &[u8],
60 61 uuid: Vec<u8>,
62 identity: Option<u64>,
61 63 ) -> Result<Self, DirstateError>
62 64 where
63 65 OnDisk: Deref<Target = [u8]> + Send + 'static,
@@ -67,7 +69,9 b' impl OwningDirstateMap {'
67 69 OwningDirstateMapTryBuilder {
68 70 on_disk,
69 71 map_builder: |bytes| {
70 DirstateMap::new_v2(&bytes, data_size, metadata, uuid)
72 DirstateMap::new_v2(
73 &bytes, data_size, metadata, uuid, identity,
74 )
71 75 },
72 76 }
73 77 .try_build()
@@ -92,6 +96,10 b' impl OwningDirstateMap {'
92 96 self.get_map().old_uuid.as_deref()
93 97 }
94 98
99 pub fn old_identity(&self) -> Option<u64> {
100 self.get_map().identity
101 }
102
95 103 pub fn old_data_size(&self) -> usize {
96 104 self.get_map().old_data_size
97 105 }
@@ -259,6 +259,15 b' impl Repo {'
259 259 .unwrap_or(Vec::new()))
260 260 }
261 261
262 fn dirstate_identity(&self) -> Result<Option<u64>, HgError> {
263 use std::os::unix::fs::MetadataExt;
264 Ok(self
265 .hg_vfs()
266 .symlink_metadata("dirstate")
267 .io_not_found_as_none()?
268 .map(|meta| meta.ino()))
269 }
270
262 271 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
263 272 Ok(*self
264 273 .dirstate_parents
@@ -284,23 +293,27 b' impl Repo {'
284 293 /// Returns the information read from the dirstate docket necessary to
285 294 /// check if the data file has been updated/deleted by another process
286 295 /// since we last read the dirstate.
287 /// Namely, the data file uuid and the data size.
296 /// Namely, the inode, data file uuid and the data size.
288 297 fn get_dirstate_data_file_integrity(
289 298 &self,
290 ) -> Result<(Option<Vec<u8>>, usize), HgError> {
299 ) -> Result<(Option<u64>, Option<Vec<u8>>, usize), HgError> {
291 300 assert!(
292 301 self.has_dirstate_v2(),
293 302 "accessing dirstate data file ID without dirstate-v2"
294 303 );
304 // Get the identity before the contents since we could have a race
305 // between the two. Having an identity that is too old is fine, but
306 // one that is younger than the content change is bad.
307 let identity = self.dirstate_identity()?;
295 308 let dirstate = self.dirstate_file_contents()?;
296 309 if dirstate.is_empty() {
297 310 self.dirstate_parents.set(DirstateParents::NULL);
298 Ok((None, 0))
311 Ok((identity, None, 0))
299 312 } else {
300 313 let docket =
301 314 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
302 315 self.dirstate_parents.set(docket.parents());
303 Ok((Some(docket.uuid.to_owned()), docket.data_size()))
316 Ok((identity, Some(docket.uuid.to_owned()), docket.data_size()))
304 317 }
305 318 }
306 319
@@ -347,13 +360,16 b' impl Repo {'
347 360 self.config(),
348 361 "dirstate.pre-read-file",
349 362 );
363 let identity = self.dirstate_identity()?;
350 364 let dirstate_file_contents = self.dirstate_file_contents()?;
351 365 return if dirstate_file_contents.is_empty() {
352 366 self.dirstate_parents.set(DirstateParents::NULL);
353 367 Ok(OwningDirstateMap::new_empty(Vec::new()))
354 368 } else {
355 let (map, parents) =
356 OwningDirstateMap::new_v1(dirstate_file_contents)?;
369 let (map, parents) = OwningDirstateMap::new_v1(
370 dirstate_file_contents,
371 identity,
372 )?;
357 373 self.dirstate_parents.set(parents);
358 374 Ok(map)
359 375 };
@@ -365,6 +381,7 b' impl Repo {'
365 381 ) -> Result<OwningDirstateMap, DirstateError> {
366 382 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
367 383 let dirstate_file_contents = self.dirstate_file_contents()?;
384 let identity = self.dirstate_identity()?;
368 385 if dirstate_file_contents.is_empty() {
369 386 self.dirstate_parents.set(DirstateParents::NULL);
370 387 return Ok(OwningDirstateMap::new_empty(Vec::new()));
@@ -410,7 +427,9 b' impl Repo {'
410 427 }
411 428 Err(e) => return Err(e.into()),
412 429 };
413 OwningDirstateMap::new_v2(contents, data_size, metadata, uuid)
430 OwningDirstateMap::new_v2(
431 contents, data_size, metadata, uuid, identity,
432 )
414 433 } else {
415 434 match self
416 435 .hg_vfs()
@@ -418,7 +437,7 b' impl Repo {'
418 437 .io_not_found_as_none()
419 438 {
420 439 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
421 data_mmap, data_size, metadata, uuid,
440 data_mmap, data_size, metadata, uuid, identity,
422 441 ),
423 442 Ok(None) => {
424 443 // Race where the data file was deleted right after we
@@ -534,12 +553,15 b' impl Repo {'
534 553 // it’s unset
535 554 let parents = self.dirstate_parents()?;
536 555 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
537 let (uuid, data_size) = self.get_dirstate_data_file_integrity()?;
556 let (identity, uuid, data_size) =
557 self.get_dirstate_data_file_integrity()?;
558 let identity_changed = identity != map.old_identity();
538 559 let uuid_changed = uuid.as_deref() != map.old_uuid();
539 560 let data_length_changed = data_size != map.old_data_size();
540 561
541 if uuid_changed || data_length_changed {
542 // If uuid or length changed since last disk read, don't write.
562 if identity_changed || uuid_changed || data_length_changed {
563 // If any of identity, uuid or length have changed since
564 // last disk read, don't write.
543 565 // This is fine because either we're in a command that doesn't
544 566 // write anything too important (like `hg status`), or we're in
545 567 // `hg add` and we're supposed to have taken the lock before
@@ -636,6 +658,22 b' impl Repo {'
636 658
637 659 (packed_dirstate, old_uuid)
638 660 } else {
661 let identity = self.dirstate_identity()?;
662 if identity != map.old_identity() {
663 // If identity changed since last disk read, don't write.
664 // This is fine because either we're in a command that doesn't
665 // write anything too important (like `hg status`), or we're in
666 // `hg add` and we're supposed to have taken the lock before
667 // reading anyway.
668 //
669 // TODO complain loudly if we've changed anything important
670 // without taking the lock.
671 // (see `hg help config.format.use-dirstate-tracked-hint`)
672 log::debug!(
673 "dirstate has changed since last read, not updating."
674 );
675 return Ok(());
676 }
639 677 (map.pack_v1(parents)?, None)
640 678 };
641 679
@@ -49,9 +49,10 b' py_class!(pub class DirstateMap |py| {'
49 49 @staticmethod
50 50 def new_v1(
51 51 on_disk: PyBytes,
52 identity: Option<u64>,
52 53 ) -> PyResult<PyObject> {
53 54 let on_disk = PyBytesDeref::new(py, on_disk);
54 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
55 let (map, parents) = OwningDirstateMap::new_v1(on_disk, identity)
55 56 .map_err(|e| dirstate_error(py, e))?;
56 57 let map = Self::create_instance(py, map)?;
57 58 let p1 = PyBytes::new(py, parents.p1.as_bytes());
@@ -67,6 +68,7 b' py_class!(pub class DirstateMap |py| {'
67 68 data_size: usize,
68 69 tree_metadata: PyBytes,
69 70 uuid: PyBytes,
71 identity: Option<u64>,
70 72 ) -> PyResult<PyObject> {
71 73 let dirstate_error = |e: DirstateError| {
72 74 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
@@ -74,7 +76,11 b' py_class!(pub class DirstateMap |py| {'
74 76 let on_disk = PyBytesDeref::new(py, on_disk);
75 77 let uuid = uuid.data(py);
76 78 let map = OwningDirstateMap::new_v2(
77 on_disk, data_size, tree_metadata.data(py), uuid.to_owned(),
79 on_disk,
80 data_size,
81 tree_metadata.data(py),
82 uuid.to_owned(),
83 identity,
78 84 ).map_err(dirstate_error)?;
79 85 let map = Self::create_instance(py, map)?;
80 86 Ok(map.into_object())
@@ -242,12 +242,9 b' Add a file'
242 242 The file should in a "added" state
243 243
244 244 $ hg status
245 A dir/n (no-rhg dirstate-v1 !)
246 A dir/n (no-dirstate-v1 !)
247 A dir/n (missing-correct-output rhg dirstate-v1 !)
245 A dir/n
248 246 A dir/o
249 247 R dir/nested/m
250 ? dir/n (known-bad-output rhg dirstate-v1 !)
251 248 ? p
252 249 ? q
253 250
@@ -289,22 +286,6 b' Add a file and force the data file rewri'
289 286
290 287 The parent must change and the status should be clean
291 288
292 # XXX rhg misbehaves here
293 #if rhg dirstate-v1
294 $ hg summary
295 parent: 1:c349430a1631
296 more files to have two commits
297 branch: default
298 commit: 1 added, 1 removed, 3 unknown (new branch head)
299 update: 1 new changesets (update)
300 phases: 3 draft
301 $ hg status
302 A dir/o
303 R dir/nested/m
304 ? dir/n
305 ? p
306 ? q
307 #else
308 289 $ hg summary
309 290 parent: 2:2e3b442a2fd4 tip
310 291 created-during-status
@@ -317,7 +298,6 b' The parent must change and the status sh'
317 298 ? dir/n
318 299 ? p
319 300 ? q
320 #endif
321 301
322 302 The status process should return a consistent result and not crash.
323 303
@@ -416,9 +396,7 b' touch g'
416 396 the first update should be on disk
417 397
418 398 $ hg debugstate --all | grep "g"
419 n 644 0 2000-01-01 00:10:00 g (known-bad-output rhg dirstate-v1 !)
420 n 644 0 2000-01-01 00:25:00 g (rhg no-dirstate-v1 !)
421 n 644 0 2000-01-01 00:25:00 g (no-rhg !)
399 n 644 0 2000-01-01 00:25:00 g
422 400
423 401 The status process should return a consistent result and not crash.
424 402
General Comments 0
You need to be logged in to leave comments. Login now