diff --git a/mercurial/dirstatemap.py b/mercurial/dirstatemap.py --- a/mercurial/dirstatemap.py +++ b/mercurial/dirstatemap.py @@ -573,11 +573,15 @@ if rustmod is not None: testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file') if not self.docket.uuid: data = b'' + self._map = rustmod.DirstateMap.new_empty() else: data = self._read_v2_data() - self._map = rustmod.DirstateMap.new_v2( - data, self.docket.data_size, self.docket.tree_metadata - ) + self._map = rustmod.DirstateMap.new_v2( + data, + self.docket.data_size, + self.docket.tree_metadata, + self.docket.uuid, + ) parents = self.docket.parents else: self._set_identity() diff --git a/rust/hg-core/src/dirstate_tree/dirstate_map.rs b/rust/hg-core/src/dirstate_tree/dirstate_map.rs --- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs +++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs @@ -66,9 +66,16 @@ pub struct DirstateMap<'on_disk> { pub(super) unreachable_bytes: u32, /// Size of the data used to first load this `DirstateMap`. Used in case - /// we need to write some new metadata, but no new data on disk. + /// we need to write some new metadata, but no new data on disk, + /// as well as to detect writes that have happened in another process + /// since first read. pub(super) old_data_size: usize, + /// UUID used when first loading this `DirstateMap`. Used to check if + /// the UUID has been changed by another process since first read. + /// Can be `None` if using dirstate v1 or if it's a brand new dirstate. + pub(super) old_uuid: Option>, + pub(super) dirstate_version: DirstateVersion, /// Controlled by config option `devel.dirstate.v2.data_update_mode` @@ -460,6 +467,7 @@ impl<'on_disk> DirstateMap<'on_disk> { ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN], unreachable_bytes: 0, old_data_size: 0, + old_uuid: None, dirstate_version: DirstateVersion::V1, write_mode: DirstateMapWriteMode::Auto, } @@ -470,9 +478,10 @@ impl<'on_disk> DirstateMap<'on_disk> { on_disk: &'on_disk [u8], data_size: usize, metadata: &[u8], + uuid: Vec, ) -> Result { if let Some(data) = on_disk.get(..data_size) { - Ok(on_disk::read(data, metadata)?) + Ok(on_disk::read(data, metadata, uuid)?) } else { Err(DirstateV2ParseError::new("not enough bytes on disk").into()) } @@ -1843,6 +1852,7 @@ mod tests { packed, packed_len, metadata.as_bytes(), + vec![], )?; // Check that everything is accounted for diff --git a/rust/hg-core/src/dirstate_tree/on_disk.rs b/rust/hg-core/src/dirstate_tree/on_disk.rs --- a/rust/hg-core/src/dirstate_tree/on_disk.rs +++ b/rust/hg-core/src/dirstate_tree/on_disk.rs @@ -290,6 +290,7 @@ pub fn read_docket( pub(super) fn read<'on_disk>( on_disk: &'on_disk [u8], metadata: &[u8], + uuid: Vec, ) -> Result, DirstateV2ParseError> { if on_disk.is_empty() { let mut map = DirstateMap::empty(on_disk); @@ -312,6 +313,7 @@ pub(super) fn read<'on_disk>( ignore_patterns_hash: meta.ignore_patterns_hash, unreachable_bytes: meta.unreachable_bytes.get(), old_data_size: on_disk.len(), + old_uuid: Some(uuid), dirstate_version: DirstateVersion::V2, write_mode: DirstateMapWriteMode::Auto, }; diff --git a/rust/hg-core/src/dirstate_tree/owning.rs b/rust/hg-core/src/dirstate_tree/owning.rs --- a/rust/hg-core/src/dirstate_tree/owning.rs +++ b/rust/hg-core/src/dirstate_tree/owning.rs @@ -57,6 +57,7 @@ impl OwningDirstateMap { on_disk: OnDisk, data_size: usize, metadata: &[u8], + uuid: Vec, ) -> Result where OnDisk: Deref + Send + 'static, @@ -66,7 +67,7 @@ impl OwningDirstateMap { OwningDirstateMapTryBuilder { on_disk, map_builder: |bytes| { - DirstateMap::new_v2(&bytes, data_size, metadata) + DirstateMap::new_v2(&bytes, data_size, metadata, uuid) }, } .try_build() @@ -86,4 +87,12 @@ impl OwningDirstateMap { pub fn on_disk(&self) -> &[u8] { self.borrow_on_disk() } + + pub fn old_uuid(&self) -> Option<&[u8]> { + self.get_map().old_uuid.as_deref() + } + + pub fn old_data_size(&self) -> usize { + self.get_map().old_data_size + } } diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -383,6 +383,7 @@ impl Repo { self.dirstate_parents.set(docket.parents()); self.dirstate_data_file_uuid .set(Some(docket.uuid.to_owned())); + let uuid = docket.uuid.to_owned(); let data_size = docket.data_size(); let context = "between reading dirstate docket and data file"; @@ -415,16 +416,16 @@ impl Repo { } Err(e) => return Err(e.into()), }; - OwningDirstateMap::new_v2(contents, data_size, metadata) + OwningDirstateMap::new_v2(contents, data_size, metadata, uuid) } else { match self .hg_vfs() .mmap_open(docket.data_filename()) .io_not_found_as_none() { - Ok(Some(data_mmap)) => { - OwningDirstateMap::new_v2(data_mmap, data_size, metadata) - } + Ok(Some(data_mmap)) => OwningDirstateMap::new_v2( + data_mmap, data_size, metadata, uuid, + ), Ok(None) => { // Race where the data file was deleted right after we // read the docket, try again diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs --- a/rust/hg-cpython/src/dirstate/dirstate_map.rs +++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs @@ -66,18 +66,28 @@ py_class!(pub class DirstateMap |py| { on_disk: PyBytes, data_size: usize, tree_metadata: PyBytes, + uuid: PyBytes, ) -> PyResult { let dirstate_error = |e: DirstateError| { PyErr::new::(py, format!("Dirstate error: {:?}", e)) }; let on_disk = PyBytesDeref::new(py, on_disk); + let uuid = uuid.data(py); let map = OwningDirstateMap::new_v2( - on_disk, data_size, tree_metadata.data(py), + on_disk, data_size, tree_metadata.data(py), uuid.to_owned(), ).map_err(dirstate_error)?; let map = Self::create_instance(py, map)?; Ok(map.into_object()) } + /// Returns an empty DirstateMap. Only used for a new dirstate. + @staticmethod + def new_empty() -> PyResult { + let map = OwningDirstateMap::new_empty(vec![]); + let map = Self::create_instance(py, map)?; + Ok(map.into_object()) + } + def clear(&self) -> PyResult { self.inner(py).borrow_mut().clear(); Ok(py.None())