changeset 52864:d961e09d3d8c

rust-pyo3-dirstate: DirstateMap mutating methods except CopyMap related Here we noticed a discrepancy in the truncated timestamps with what `DirstateItem` uses. More domain knowledge would be needed to decide whether it is normal or it would require some cleanup.
author Georges Racinet <georges.racinet@cloudcrane.io>
date Thu, 30 Jan 2025 12:24:41 +0100
parents ab6198160960
children 6b38ff460f2a
files rust/hg-pyo3/src/dirstate/dirstate_map.rs rust/hg-pyo3/src/exceptions.rs
diffstat 2 files changed, 228 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/rust/hg-pyo3/src/dirstate/dirstate_map.rs	Wed Feb 05 11:04:19 2025 +0100
+++ b/rust/hg-pyo3/src/dirstate/dirstate_map.rs	Thu Jan 30 12:24:41 2025 +0100
@@ -8,35 +8,47 @@
 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
 //! `hg-core` package.
 
-use pyo3::exceptions::PyKeyError;
+use pyo3::exceptions::{PyKeyError, PyOSError};
 use pyo3::prelude::*;
-use pyo3::types::{PyBytes, PyBytesMethods, PyTuple};
+use pyo3::types::{
+    PyBytes, PyBytesMethods, PyDict, PyDictMethods, PyList, PyTuple,
+};
 use pyo3_sharedref::{py_shared_iterator, PyShareable};
 
-use std::sync::RwLockReadGuard;
+use std::sync::{RwLockReadGuard, RwLockWriteGuard};
 
 use hg::{
     dirstate::{
         dirstate_map::{
-            DirstateIdentity as CoreDirstateIdentity, DirstateMapWriteMode,
+            DirstateEntryReset, DirstateIdentity as CoreDirstateIdentity,
+            DirstateMapWriteMode,
         },
-        entry::DirstateEntry,
+        entry::{DirstateEntry, ParentFileData, TruncatedTimestamp},
         on_disk::DirstateV2ParseError,
         owning::OwningDirstateMap,
         StateMapIter,
     },
-    utils::hg_path::HgPath,
+    utils::{files::normalize_case, hg_path::HgPath},
     DirstateParents,
 };
 
 use super::item::DirstateItem;
 use crate::{
-    exceptions::{dirstate_error, dirstate_v2_error, map_try_lock_error},
+    exceptions::{
+        dirstate_error, dirstate_v2_error, map_try_lock_error,
+        to_string_value_error,
+    },
     node::{node_from_py_bytes, PyNode},
-    path::PyHgPathRef,
+    path::{PyHgPathBuf, PyHgPathDirstateV2Result, PyHgPathRef},
     utils::PyBytesDeref,
 };
 
+/// Type alias to satisfy Clippy in `DirstateMap::reset_state)`
+///
+/// It is *not* the same as [`super::item::UncheckedTruncatedTimeStamp`] and
+/// this is worth reviewing.
+type UncheckedTruncatedTimeStamp = Option<(i64, u32, bool)>;
+
 #[pyclass(mapping)]
 pub struct DirstateMap {
     inner: PyShareable<OwningDirstateMap>,
@@ -97,6 +109,13 @@
         })
     }
 
+    fn clear(slf: &Bound<'_, Self>) -> PyResult<()> {
+        Self::with_inner_write(slf, |_self_ref, mut inner| {
+            inner.clear();
+            Ok(())
+        })
+    }
+
     #[pyo3(signature = (key, default=None))]
     fn get(
         slf: &Bound<'_, Self>,
@@ -115,6 +134,141 @@
         })
     }
 
+    fn set_tracked(
+        slf: &Bound<'_, Self>,
+        f: &Bound<'_, PyBytes>,
+    ) -> PyResult<bool> {
+        Self::with_inner_write(slf, |_self_ref, mut inner| {
+            inner
+                .set_tracked(HgPath::new(f.as_bytes()))
+                .map_err(dirstate_v2_error)
+        })
+    }
+
+    fn set_untracked(
+        slf: &Bound<'_, Self>,
+        f: &Bound<'_, PyBytes>,
+    ) -> PyResult<bool> {
+        Self::with_inner_write(slf, |_self_ref, mut inner| {
+            // here it would be more straightforward to use dirstate_v2_error,
+            // but that raises ValueError instead of OSError
+            inner
+                .set_untracked(HgPath::new(f.as_bytes()))
+                .map_err(|_| PyOSError::new_err("Dirstate error"))
+        })
+    }
+
+    fn set_clean(
+        slf: &Bound<'_, Self>,
+        f: &Bound<'_, PyBytes>,
+        mode: u32,
+        size: u32,
+        mtime: (i64, u32, bool),
+    ) -> PyResult<()> {
+        let (mtime_s, mtime_ns, second_ambiguous) = mtime;
+        let timestamp = TruncatedTimestamp::new_truncate(
+            mtime_s,
+            mtime_ns,
+            second_ambiguous,
+        );
+
+        Self::with_inner_write(slf, |_self_ref, mut inner| {
+            inner
+                .set_clean(HgPath::new(f.as_bytes()), mode, size, timestamp)
+                .map_err(dirstate_error)
+        })
+    }
+
+    fn set_possibly_dirty(
+        slf: &Bound<'_, Self>,
+        f: &Bound<'_, PyBytes>,
+    ) -> PyResult<()> {
+        Self::with_inner_write(slf, |_self_ref, mut inner| {
+            inner
+                .set_possibly_dirty(HgPath::new(f.as_bytes()))
+                .map_err(dirstate_error)
+        })
+    }
+
+    #[pyo3(signature = (f,
+                        wc_tracked=false,
+                        p1_tracked=false,
+                        p2_info=false,
+                        has_meaningful_mtime=true,
+                        parentfiledata=None))]
+    fn reset_state(
+        slf: &Bound<'_, Self>,
+        f: &Bound<'_, PyBytes>,
+        wc_tracked: bool,
+        p1_tracked: bool,
+        p2_info: bool,
+        has_meaningful_mtime: bool,
+        parentfiledata: Option<(u32, u32, UncheckedTruncatedTimeStamp)>,
+    ) -> PyResult<()> {
+        let mut has_meaningful_mtime = has_meaningful_mtime;
+        let parent_file_data = match parentfiledata {
+            None => {
+                has_meaningful_mtime = false;
+                None
+            }
+            Some(data) => {
+                let (mode, size, mtime_info) = data;
+                let mtime = if let Some(mtime_info) = mtime_info {
+                    let (mtime_s, mtime_ns, second_ambiguous) = mtime_info;
+                    let timestamp = TruncatedTimestamp::new_truncate(
+                        mtime_s,
+                        mtime_ns,
+                        second_ambiguous,
+                    );
+                    Some(timestamp)
+                } else {
+                    has_meaningful_mtime = false;
+                    None
+                };
+                Some(ParentFileData {
+                    mode_size: Some((mode, size)),
+                    mtime,
+                })
+            }
+        };
+
+        let reset = DirstateEntryReset {
+            filename: HgPath::new(f.as_bytes()),
+            wc_tracked,
+            p1_tracked,
+            p2_info,
+            has_meaningful_mtime,
+            parent_file_data_opt: parent_file_data,
+            from_empty: false,
+        };
+
+        Self::with_inner_write(slf, |_self_ref, mut inner| {
+            inner.reset_state(reset).map_err(dirstate_error)
+        })
+    }
+
+    fn hastrackeddir(
+        slf: &Bound<'_, Self>,
+        d: &Bound<'_, PyBytes>,
+    ) -> PyResult<bool> {
+        Self::with_inner_write(slf, |_self_ref, mut inner| {
+            inner
+                .has_tracked_dir(HgPath::new(d.as_bytes()))
+                .map_err(to_string_value_error)
+        })
+    }
+
+    fn hasdir(
+        slf: &Bound<'_, Self>,
+        d: &Bound<'_, PyBytes>,
+    ) -> PyResult<bool> {
+        Self::with_inner_write(slf, |_self_ref, mut inner| {
+            inner
+                .has_dir(HgPath::new(d.as_bytes()))
+                .map_err(to_string_value_error)
+        })
+    }
+
     /// Returns suitable data for writing on disk in v1 format
     ///
     /// Despite the name, this is not a mutation of the object.
@@ -168,6 +322,24 @@
         })
     }
 
+    fn filefoldmapasdict(
+        slf: &Bound<'_, Self>,
+        py: Python,
+    ) -> PyResult<Py<PyDict>> {
+        let dict = PyDict::new(py);
+        Self::with_inner_read(slf, |_self_ref, inner| {
+            for item in inner.iter() {
+                let (path, entry) = item.map_err(dirstate_v2_error)?;
+                if !entry.removed() {
+                    let key = normalize_case(path);
+                    dict.set_item(PyHgPathBuf(key), PyHgPathRef(path))?;
+                }
+            }
+            Ok(())
+        })?;
+        Ok(dict.unbind())
+    }
+
     fn __len__(slf: &Bound<'_, Self>) -> PyResult<usize> {
         Self::with_inner_read(slf, |_self_ref, inner| Ok(inner.len()))
     }
@@ -211,6 +383,39 @@
         Self::keys(slf)
     }
 
+    fn tracked_dirs(
+        slf: &Bound<'_, Self>,
+        py: Python,
+    ) -> PyResult<Py<PyList>> {
+        // core iterator is not exact sized, we cannot use `PyList::new`
+        let dirs = PyList::empty(py);
+        Self::with_inner_write(slf, |_self_ref, mut inner| {
+            for path in inner.iter_tracked_dirs().map_err(dirstate_error)? {
+                dirs.append(PyHgPathDirstateV2Result(path))?;
+            }
+            Ok(())
+        })?;
+        Ok(dirs.unbind())
+    }
+
+    fn setparents_fixup(
+        slf: &Bound<'_, Self>,
+        py: Python,
+    ) -> PyResult<Py<PyDict>> {
+        let dict = PyDict::new(py);
+        let copies = Self::with_inner_write(slf, |_self_ref, mut inner| {
+            inner.setparents_fixup().map_err(dirstate_v2_error)
+        })?;
+
+        // it might be interesting to try and use the `IntoPyDict` trait,
+        // but it does about the same thing
+        // but that would require performing the inner `as_bytes()` as well
+        for (key, value) in copies {
+            dict.set_item(PyHgPathBuf(key), PyHgPathBuf(value))?;
+        }
+        Ok(dict.unbind())
+    }
+
     fn debug_iter(
         slf: &Bound<'_, Self>,
         py: Python,
@@ -287,6 +492,21 @@
         let guard = shareable_ref.try_read().map_err(map_try_lock_error)?;
         f(&self_ref, guard)
     }
+
+    fn with_inner_write<'py, T>(
+        slf: &Bound<'py, Self>,
+        f: impl FnOnce(
+            &PyRef<'py, Self>,
+            RwLockWriteGuard<OwningDirstateMap>,
+        ) -> PyResult<T>,
+    ) -> PyResult<T> {
+        let self_ref = slf.borrow();
+        // Safety: the owner is the right one. We will anyway
+        // not actually `share` it.
+        let shareable_ref = unsafe { self_ref.inner.borrow_with_owner(slf) };
+        let guard = shareable_ref.try_write().map_err(map_try_lock_error)?;
+        f(&self_ref, guard)
+    }
 }
 
 #[pyclass]
--- a/rust/hg-pyo3/src/exceptions.rs	Wed Feb 05 11:04:19 2025 +0100
+++ b/rust/hg-pyo3/src/exceptions.rs	Thu Jan 30 12:24:41 2025 +0100
@@ -52,7 +52,6 @@
     PyRuntimeError::new_err(format!("In Rust PyO3 bindings: {e}"))
 }
 
-#[allow(unused)]
 pub fn to_string_value_error<T: Display>(e: T) -> PyErr {
     PyValueError::new_err(e.to_string())
 }