##// 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 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
570 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
571 if self._use_dirstate_v2:
571 if self._use_dirstate_v2:
572 self.docket # load the data if needed
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 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
579 testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
574 if not self.docket.uuid:
580 if not self.docket.uuid:
575 data = b''
581 data = b''
@@ -581,12 +587,19 b' if rustmod is not None:'
581 self.docket.data_size,
587 self.docket.data_size,
582 self.docket.tree_metadata,
588 self.docket.tree_metadata,
583 self.docket.uuid,
589 self.docket.uuid,
590 inode,
584 )
591 )
585 parents = self.docket.parents
592 parents = self.docket.parents
586 else:
593 else:
587 self._set_identity()
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 self._map, parents = rustmod.DirstateMap.new_v1(
601 self._map, parents = rustmod.DirstateMap.new_v1(
589 self._readdirstatefile()
602 self._readdirstatefile(), inode
590 )
603 )
591
604
592 if parents and not self._dirtyparents:
605 if parents and not self._dirtyparents:
@@ -76,6 +76,14 b" pub struct DirstateMap<'on_disk> {"
76 /// Can be `None` if using dirstate v1 or if it's a brand new dirstate.
76 /// Can be `None` if using dirstate v1 or if it's a brand new dirstate.
77 pub(super) old_uuid: Option<Vec<u8>>,
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 pub(super) dirstate_version: DirstateVersion,
87 pub(super) dirstate_version: DirstateVersion,
80
88
81 /// Controlled by config option `devel.dirstate.v2.data_update_mode`
89 /// Controlled by config option `devel.dirstate.v2.data_update_mode`
@@ -468,6 +476,7 b" impl<'on_disk> DirstateMap<'on_disk> {"
468 unreachable_bytes: 0,
476 unreachable_bytes: 0,
469 old_data_size: 0,
477 old_data_size: 0,
470 old_uuid: None,
478 old_uuid: None,
479 identity: None,
471 dirstate_version: DirstateVersion::V1,
480 dirstate_version: DirstateVersion::V1,
472 write_mode: DirstateMapWriteMode::Auto,
481 write_mode: DirstateMapWriteMode::Auto,
473 }
482 }
@@ -479,9 +488,10 b" impl<'on_disk> DirstateMap<'on_disk> {"
479 data_size: usize,
488 data_size: usize,
480 metadata: &[u8],
489 metadata: &[u8],
481 uuid: Vec<u8>,
490 uuid: Vec<u8>,
491 identity: Option<u64>,
482 ) -> Result<Self, DirstateError> {
492 ) -> Result<Self, DirstateError> {
483 if let Some(data) = on_disk.get(..data_size) {
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 } else {
495 } else {
486 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
496 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
487 }
497 }
@@ -490,6 +500,7 b" impl<'on_disk> DirstateMap<'on_disk> {"
490 #[timed]
500 #[timed]
491 pub fn new_v1(
501 pub fn new_v1(
492 on_disk: &'on_disk [u8],
502 on_disk: &'on_disk [u8],
503 identity: Option<u64>,
493 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
504 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
494 let mut map = Self::empty(on_disk);
505 let mut map = Self::empty(on_disk);
495 if map.on_disk.is_empty() {
506 if map.on_disk.is_empty() {
@@ -531,6 +542,7 b" impl<'on_disk> DirstateMap<'on_disk> {"
531 },
542 },
532 )?;
543 )?;
533 let parents = Some(parents.clone());
544 let parents = Some(parents.clone());
545 map.identity = identity;
534
546
535 Ok((map, parents))
547 Ok((map, parents))
536 }
548 }
@@ -1853,6 +1865,7 b' mod tests {'
1853 packed_len,
1865 packed_len,
1854 metadata.as_bytes(),
1866 metadata.as_bytes(),
1855 vec![],
1867 vec![],
1868 None,
1856 )?;
1869 )?;
1857
1870
1858 // Check that everything is accounted for
1871 // Check that everything is accounted for
@@ -291,6 +291,7 b" pub(super) fn read<'on_disk>("
291 on_disk: &'on_disk [u8],
291 on_disk: &'on_disk [u8],
292 metadata: &[u8],
292 metadata: &[u8],
293 uuid: Vec<u8>,
293 uuid: Vec<u8>,
294 identity: Option<u64>,
294 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
295 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
295 if on_disk.is_empty() {
296 if on_disk.is_empty() {
296 let mut map = DirstateMap::empty(on_disk);
297 let mut map = DirstateMap::empty(on_disk);
@@ -314,6 +315,7 b" pub(super) fn read<'on_disk>("
314 unreachable_bytes: meta.unreachable_bytes.get(),
315 unreachable_bytes: meta.unreachable_bytes.get(),
315 old_data_size: on_disk.len(),
316 old_data_size: on_disk.len(),
316 old_uuid: Some(uuid),
317 old_uuid: Some(uuid),
318 identity,
317 dirstate_version: DirstateVersion::V2,
319 dirstate_version: DirstateVersion::V2,
318 write_mode: DirstateMapWriteMode::Auto,
320 write_mode: DirstateMapWriteMode::Auto,
319 };
321 };
@@ -31,6 +31,7 b' impl OwningDirstateMap {'
31
31
32 pub fn new_v1<OnDisk>(
32 pub fn new_v1<OnDisk>(
33 on_disk: OnDisk,
33 on_disk: OnDisk,
34 identity: Option<u64>,
34 ) -> Result<(Self, DirstateParents), DirstateError>
35 ) -> Result<(Self, DirstateParents), DirstateError>
35 where
36 where
36 OnDisk: Deref<Target = [u8]> + Send + 'static,
37 OnDisk: Deref<Target = [u8]> + Send + 'static,
@@ -42,7 +43,7 b' impl OwningDirstateMap {'
42 OwningDirstateMapTryBuilder {
43 OwningDirstateMapTryBuilder {
43 on_disk,
44 on_disk,
44 map_builder: |bytes| {
45 map_builder: |bytes| {
45 DirstateMap::new_v1(&bytes).map(|(dmap, p)| {
46 DirstateMap::new_v1(&bytes, identity).map(|(dmap, p)| {
46 parents = p.unwrap_or(DirstateParents::NULL);
47 parents = p.unwrap_or(DirstateParents::NULL);
47 dmap
48 dmap
48 })
49 })
@@ -58,6 +59,7 b' impl OwningDirstateMap {'
58 data_size: usize,
59 data_size: usize,
59 metadata: &[u8],
60 metadata: &[u8],
60 uuid: Vec<u8>,
61 uuid: Vec<u8>,
62 identity: Option<u64>,
61 ) -> Result<Self, DirstateError>
63 ) -> Result<Self, DirstateError>
62 where
64 where
63 OnDisk: Deref<Target = [u8]> + Send + 'static,
65 OnDisk: Deref<Target = [u8]> + Send + 'static,
@@ -67,7 +69,9 b' impl OwningDirstateMap {'
67 OwningDirstateMapTryBuilder {
69 OwningDirstateMapTryBuilder {
68 on_disk,
70 on_disk,
69 map_builder: |bytes| {
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 .try_build()
77 .try_build()
@@ -92,6 +96,10 b' impl OwningDirstateMap {'
92 self.get_map().old_uuid.as_deref()
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 pub fn old_data_size(&self) -> usize {
103 pub fn old_data_size(&self) -> usize {
96 self.get_map().old_data_size
104 self.get_map().old_data_size
97 }
105 }
@@ -259,6 +259,15 b' impl Repo {'
259 .unwrap_or(Vec::new()))
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 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
271 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
263 Ok(*self
272 Ok(*self
264 .dirstate_parents
273 .dirstate_parents
@@ -284,23 +293,27 b' impl Repo {'
284 /// Returns the information read from the dirstate docket necessary to
293 /// Returns the information read from the dirstate docket necessary to
285 /// check if the data file has been updated/deleted by another process
294 /// check if the data file has been updated/deleted by another process
286 /// since we last read the dirstate.
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 fn get_dirstate_data_file_integrity(
297 fn get_dirstate_data_file_integrity(
289 &self,
298 &self,
290 ) -> Result<(Option<Vec<u8>>, usize), HgError> {
299 ) -> Result<(Option<u64>, Option<Vec<u8>>, usize), HgError> {
291 assert!(
300 assert!(
292 self.has_dirstate_v2(),
301 self.has_dirstate_v2(),
293 "accessing dirstate data file ID without dirstate-v2"
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 let dirstate = self.dirstate_file_contents()?;
308 let dirstate = self.dirstate_file_contents()?;
296 if dirstate.is_empty() {
309 if dirstate.is_empty() {
297 self.dirstate_parents.set(DirstateParents::NULL);
310 self.dirstate_parents.set(DirstateParents::NULL);
298 Ok((None, 0))
311 Ok((identity, None, 0))
299 } else {
312 } else {
300 let docket =
313 let docket =
301 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
314 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
302 self.dirstate_parents.set(docket.parents());
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 self.config(),
360 self.config(),
348 "dirstate.pre-read-file",
361 "dirstate.pre-read-file",
349 );
362 );
363 let identity = self.dirstate_identity()?;
350 let dirstate_file_contents = self.dirstate_file_contents()?;
364 let dirstate_file_contents = self.dirstate_file_contents()?;
351 return if dirstate_file_contents.is_empty() {
365 return if dirstate_file_contents.is_empty() {
352 self.dirstate_parents.set(DirstateParents::NULL);
366 self.dirstate_parents.set(DirstateParents::NULL);
353 Ok(OwningDirstateMap::new_empty(Vec::new()))
367 Ok(OwningDirstateMap::new_empty(Vec::new()))
354 } else {
368 } else {
355 let (map, parents) =
369 let (map, parents) = OwningDirstateMap::new_v1(
356 OwningDirstateMap::new_v1(dirstate_file_contents)?;
370 dirstate_file_contents,
371 identity,
372 )?;
357 self.dirstate_parents.set(parents);
373 self.dirstate_parents.set(parents);
358 Ok(map)
374 Ok(map)
359 };
375 };
@@ -365,6 +381,7 b' impl Repo {'
365 ) -> Result<OwningDirstateMap, DirstateError> {
381 ) -> Result<OwningDirstateMap, DirstateError> {
366 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
382 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
367 let dirstate_file_contents = self.dirstate_file_contents()?;
383 let dirstate_file_contents = self.dirstate_file_contents()?;
384 let identity = self.dirstate_identity()?;
368 if dirstate_file_contents.is_empty() {
385 if dirstate_file_contents.is_empty() {
369 self.dirstate_parents.set(DirstateParents::NULL);
386 self.dirstate_parents.set(DirstateParents::NULL);
370 return Ok(OwningDirstateMap::new_empty(Vec::new()));
387 return Ok(OwningDirstateMap::new_empty(Vec::new()));
@@ -410,7 +427,9 b' impl Repo {'
410 }
427 }
411 Err(e) => return Err(e.into()),
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 } else {
433 } else {
415 match self
434 match self
416 .hg_vfs()
435 .hg_vfs()
@@ -418,7 +437,7 b' impl Repo {'
418 .io_not_found_as_none()
437 .io_not_found_as_none()
419 {
438 {
420 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
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 Ok(None) => {
442 Ok(None) => {
424 // Race where the data file was deleted right after we
443 // Race where the data file was deleted right after we
@@ -534,12 +553,15 b' impl Repo {'
534 // it’s unset
553 // it’s unset
535 let parents = self.dirstate_parents()?;
554 let parents = self.dirstate_parents()?;
536 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
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 let uuid_changed = uuid.as_deref() != map.old_uuid();
559 let uuid_changed = uuid.as_deref() != map.old_uuid();
539 let data_length_changed = data_size != map.old_data_size();
560 let data_length_changed = data_size != map.old_data_size();
540
561
541 if uuid_changed || data_length_changed {
562 if identity_changed || uuid_changed || data_length_changed {
542 // If uuid or length changed since last disk read, don't write.
563 // If any of identity, uuid or length have changed since
564 // last disk read, don't write.
543 // This is fine because either we're in a command that doesn't
565 // This is fine because either we're in a command that doesn't
544 // write anything too important (like `hg status`), or we're in
566 // write anything too important (like `hg status`), or we're in
545 // `hg add` and we're supposed to have taken the lock before
567 // `hg add` and we're supposed to have taken the lock before
@@ -636,6 +658,22 b' impl Repo {'
636
658
637 (packed_dirstate, old_uuid)
659 (packed_dirstate, old_uuid)
638 } else {
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 (map.pack_v1(parents)?, None)
677 (map.pack_v1(parents)?, None)
640 };
678 };
641
679
@@ -49,9 +49,10 b' py_class!(pub class DirstateMap |py| {'
49 @staticmethod
49 @staticmethod
50 def new_v1(
50 def new_v1(
51 on_disk: PyBytes,
51 on_disk: PyBytes,
52 identity: Option<u64>,
52 ) -> PyResult<PyObject> {
53 ) -> PyResult<PyObject> {
53 let on_disk = PyBytesDeref::new(py, on_disk);
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 .map_err(|e| dirstate_error(py, e))?;
56 .map_err(|e| dirstate_error(py, e))?;
56 let map = Self::create_instance(py, map)?;
57 let map = Self::create_instance(py, map)?;
57 let p1 = PyBytes::new(py, parents.p1.as_bytes());
58 let p1 = PyBytes::new(py, parents.p1.as_bytes());
@@ -67,6 +68,7 b' py_class!(pub class DirstateMap |py| {'
67 data_size: usize,
68 data_size: usize,
68 tree_metadata: PyBytes,
69 tree_metadata: PyBytes,
69 uuid: PyBytes,
70 uuid: PyBytes,
71 identity: Option<u64>,
70 ) -> PyResult<PyObject> {
72 ) -> PyResult<PyObject> {
71 let dirstate_error = |e: DirstateError| {
73 let dirstate_error = |e: DirstateError| {
72 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
74 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
@@ -74,7 +76,11 b' py_class!(pub class DirstateMap |py| {'
74 let on_disk = PyBytesDeref::new(py, on_disk);
76 let on_disk = PyBytesDeref::new(py, on_disk);
75 let uuid = uuid.data(py);
77 let uuid = uuid.data(py);
76 let map = OwningDirstateMap::new_v2(
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 ).map_err(dirstate_error)?;
84 ).map_err(dirstate_error)?;
79 let map = Self::create_instance(py, map)?;
85 let map = Self::create_instance(py, map)?;
80 Ok(map.into_object())
86 Ok(map.into_object())
@@ -242,12 +242,9 b' Add a file'
242 The file should in a "added" state
242 The file should in a "added" state
243
243
244 $ hg status
244 $ hg status
245 A dir/n (no-rhg dirstate-v1 !)
245 A dir/n
246 A dir/n (no-dirstate-v1 !)
247 A dir/n (missing-correct-output rhg dirstate-v1 !)
248 A dir/o
246 A dir/o
249 R dir/nested/m
247 R dir/nested/m
250 ? dir/n (known-bad-output rhg dirstate-v1 !)
251 ? p
248 ? p
252 ? q
249 ? q
253
250
@@ -289,22 +286,6 b' Add a file and force the data file rewri'
289
286
290 The parent must change and the status should be clean
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 $ hg summary
289 $ hg summary
309 parent: 2:2e3b442a2fd4 tip
290 parent: 2:2e3b442a2fd4 tip
310 created-during-status
291 created-during-status
@@ -317,7 +298,6 b' The parent must change and the status sh'
317 ? dir/n
298 ? dir/n
318 ? p
299 ? p
319 ? q
300 ? q
320 #endif
321
301
322 The status process should return a consistent result and not crash.
302 The status process should return a consistent result and not crash.
323
303
@@ -416,9 +396,7 b' touch g'
416 the first update should be on disk
396 the first update should be on disk
417
397
418 $ hg debugstate --all | grep "g"
398 $ hg debugstate --all | grep "g"
419 n 644 0 2000-01-01 00:10:00 g (known-bad-output rhg dirstate-v1 !)
399 n 644 0 2000-01-01 00:25:00 g
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 !)
422
400
423 The status process should return a consistent result and not crash.
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