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( |
|
|
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 |
|
|
|
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( |
|
|
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 |
|
|
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 |
? |
|
252 | 249 |
? |
|
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: |
|
|
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