rust: Add Python bindings for DirstateEntry as rustext.dirstate.DirstateItem
authorSimon Sapin <simon.sapin@octobus.net>
Wed, 22 Sep 2021 11:28:52 +0200
changeset 48043 3e69bef2031a
parent 48042 008959fcbfb2
child 48044 d5528ac9b4f2
rust: Add Python bindings for DirstateEntry as rustext.dirstate.DirstateItem Differential Revision: https://phab.mercurial-scm.org/D11485
rust/hg-core/src/dirstate/entry.rs
rust/hg-cpython/src/dirstate.rs
rust/hg-cpython/src/dirstate/item.rs
--- a/rust/hg-core/src/dirstate/entry.rs	Mon Sep 20 19:18:21 2021 +0200
+++ b/rust/hg-core/src/dirstate/entry.rs	Wed Sep 22 11:28:52 2021 +0200
@@ -22,7 +22,7 @@
 }
 
 bitflags! {
-    struct Flags: u8 {
+    pub struct Flags: u8 {
         const WDIR_TRACKED = 1 << 0;
         const P1_TRACKED = 1 << 1;
         const P2_TRACKED = 1 << 2;
@@ -47,6 +47,20 @@
 pub const SIZE_NON_NORMAL: i32 = -1;
 
 impl DirstateEntry {
+    pub fn new(
+        flags: Flags,
+        mode_size_mtime: Option<(i32, i32, i32)>,
+    ) -> Self {
+        let (mode, size, mtime) =
+            mode_size_mtime.unwrap_or((0, SIZE_NON_NORMAL, MTIME_UNSET));
+        Self {
+            flags,
+            mode,
+            size,
+            mtime,
+        }
+    }
+
     pub fn from_v1_data(
         state: EntryState,
         mode: i32,
@@ -155,33 +169,37 @@
         Self::from_v1_data(state, mode, size, mtime)
     }
 
+    pub fn tracked(&self) -> bool {
+        self.flags.contains(Flags::WDIR_TRACKED)
+    }
+
     fn tracked_in_any_parent(&self) -> bool {
         self.flags.intersects(Flags::P1_TRACKED | Flags::P2_TRACKED)
     }
 
-    fn removed(&self) -> bool {
+    pub fn removed(&self) -> bool {
         self.tracked_in_any_parent()
             && !self.flags.contains(Flags::WDIR_TRACKED)
     }
 
-    fn merged_removed(&self) -> bool {
+    pub fn merged_removed(&self) -> bool {
         self.removed() && self.flags.contains(Flags::MERGED)
     }
 
-    fn from_p2_removed(&self) -> bool {
+    pub fn from_p2_removed(&self) -> bool {
         self.removed() && self.flags.contains(Flags::CLEAN_P2)
     }
 
-    fn merged(&self) -> bool {
+    pub fn merged(&self) -> bool {
         self.flags.contains(Flags::WDIR_TRACKED | Flags::MERGED)
     }
 
-    fn added(&self) -> bool {
+    pub fn added(&self) -> bool {
         self.flags.contains(Flags::WDIR_TRACKED)
             && !self.tracked_in_any_parent()
     }
 
-    fn from_p2(&self) -> bool {
+    pub fn from_p2(&self) -> bool {
         self.flags.contains(Flags::WDIR_TRACKED | Flags::CLEAN_P2)
     }
 
@@ -237,6 +255,39 @@
         }
     }
 
+    pub fn set_possibly_dirty(&mut self) {
+        self.flags.insert(Flags::POSSIBLY_DIRTY)
+    }
+
+    pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) {
+        self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
+        self.flags.remove(
+            Flags::P2_TRACKED // This might be wrong
+                | Flags::MERGED
+                | Flags::CLEAN_P2
+                | Flags::POSSIBLY_DIRTY,
+        );
+        self.mode = mode;
+        self.size = size;
+        self.mtime = mtime;
+    }
+
+    pub fn set_tracked(&mut self) {
+        self.flags
+            .insert(Flags::WDIR_TRACKED | Flags::POSSIBLY_DIRTY);
+        // size = None on the python size turn into size = NON_NORMAL when
+        // accessed. So the next line is currently required, but a some future
+        // clean up would be welcome.
+        self.size = SIZE_NON_NORMAL;
+    }
+
+    pub fn set_untracked(&mut self) {
+        self.flags.remove(Flags::WDIR_TRACKED);
+        self.mode = 0;
+        self.size = 0;
+        self.mtime = 0;
+    }
+
     /// Returns `(state, mode, size, mtime)` for the puprose of serialization
     /// in the dirstate-v1 format.
     ///
--- a/rust/hg-cpython/src/dirstate.rs	Mon Sep 20 19:18:21 2021 +0200
+++ b/rust/hg-cpython/src/dirstate.rs	Wed Sep 22 11:28:52 2021 +0200
@@ -12,8 +12,10 @@
 mod copymap;
 mod dirs_multiset;
 mod dirstate_map;
+mod item;
 mod non_normal_entries;
 mod status;
+use self::item::DirstateItem;
 use crate::{
     dirstate::{
         dirs_multiset::Dirs, dirstate_map::DirstateMap, status::status_wrapper,
@@ -83,6 +85,7 @@
     )?;
     m.add_class::<Dirs>(py)?;
     m.add_class::<DirstateMap>(py)?;
+    m.add_class::<DirstateItem>(py)?;
     m.add(py, "V2_FORMAT_MARKER", PyBytes::new(py, V2_FORMAT_MARKER))?;
     m.add(
         py,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hg-cpython/src/dirstate/item.rs	Wed Sep 22 11:28:52 2021 +0200
@@ -0,0 +1,189 @@
+use cpython::exc;
+use cpython::PyBytes;
+use cpython::PyErr;
+use cpython::PyNone;
+use cpython::PyObject;
+use cpython::PyResult;
+use cpython::Python;
+use cpython::PythonObject;
+use hg::dirstate::entry::Flags;
+use hg::dirstate::DirstateEntry;
+use hg::dirstate::EntryState;
+use std::cell::Cell;
+use std::convert::TryFrom;
+
+py_class!(pub class DirstateItem |py| {
+    data entry: Cell<DirstateEntry>;
+
+    def __new__(
+        _cls,
+        wc_tracked: bool = false,
+        p1_tracked: bool = false,
+        p2_tracked: bool = false,
+        merged: bool = false,
+        clean_p1: bool = false,
+        clean_p2: bool = false,
+        possibly_dirty: bool = false,
+        parentfiledata: Option<(i32, i32, i32)> = None,
+
+    ) -> PyResult<DirstateItem> {
+        let mut flags = Flags::empty();
+        flags.set(Flags::WDIR_TRACKED, wc_tracked);
+        flags.set(Flags::P1_TRACKED, p1_tracked);
+        flags.set(Flags::P2_TRACKED, p2_tracked);
+        flags.set(Flags::MERGED, merged);
+        flags.set(Flags::CLEAN_P1, clean_p1);
+        flags.set(Flags::CLEAN_P2, clean_p2);
+        flags.set(Flags::POSSIBLY_DIRTY, possibly_dirty);
+        let entry = DirstateEntry::new(flags, parentfiledata);
+        DirstateItem::create_instance(py, Cell::new(entry))
+    }
+
+    @property
+    def state(&self) -> PyResult<PyBytes> {
+        let state_byte: u8 = self.entry(py).get().state().into();
+        Ok(PyBytes::new(py, &[state_byte]))
+    }
+
+    @property
+    def mode(&self) -> PyResult<i32> {
+        Ok(self.entry(py).get().mode())
+    }
+
+    @property
+    def size(&self) -> PyResult<i32> {
+        Ok(self.entry(py).get().size())
+    }
+
+    @property
+    def mtime(&self) -> PyResult<i32> {
+        Ok(self.entry(py).get().mtime())
+    }
+
+    @property
+    def tracked(&self) -> PyResult<bool> {
+        Ok(self.entry(py).get().tracked())
+    }
+
+    @property
+    def added(&self) -> PyResult<bool> {
+        Ok(self.entry(py).get().added())
+    }
+
+    @property
+    def merged(&self) -> PyResult<bool> {
+        Ok(self.entry(py).get().merged())
+    }
+
+    @property
+    def removed(&self) -> PyResult<bool> {
+        Ok(self.entry(py).get().removed())
+    }
+
+    @property
+    def from_p2(&self) -> PyResult<bool> {
+        Ok(self.entry(py).get().from_p2())
+    }
+
+    @property
+    def merged_removed(&self) -> PyResult<bool> {
+        Ok(self.entry(py).get().merged_removed())
+    }
+
+    @property
+    def from_p2_removed(&self) -> PyResult<bool> {
+        Ok(self.entry(py).get().from_p2_removed())
+    }
+
+    @property
+    def dm_nonnormal(&self) -> PyResult<bool> {
+        Ok(self.entry(py).get().is_non_normal())
+    }
+
+    @property
+    def dm_otherparent(&self) -> PyResult<bool> {
+        Ok(self.entry(py).get().is_from_other_parent())
+    }
+
+    def v1_state(&self) -> PyResult<PyBytes> {
+        let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
+        let state_byte: u8 = state.into();
+        Ok(PyBytes::new(py, &[state_byte]))
+    }
+
+    def v1_mode(&self) -> PyResult<i32> {
+        let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
+        Ok(mode)
+    }
+
+    def v1_size(&self) -> PyResult<i32> {
+        let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
+        Ok(size)
+    }
+
+    def v1_mtime(&self) -> PyResult<i32> {
+        let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
+        Ok(mtime)
+    }
+
+    def need_delay(&self, now: i32) -> PyResult<bool> {
+        Ok(self.entry(py).get().mtime_is_ambiguous(now))
+    }
+
+    @classmethod
+    def from_v1_data(
+        _cls,
+        state: PyBytes,
+        mode: i32,
+        size: i32,
+        mtime: i32,
+    ) -> PyResult<Self> {
+        let state = <[u8; 1]>::try_from(state.data(py))
+            .ok()
+            .and_then(|state| EntryState::try_from(state[0]).ok())
+            .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
+        let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
+        DirstateItem::create_instance(py, Cell::new(entry))
+    }
+
+    def set_clean(
+        &self,
+        mode: i32,
+        size: i32,
+        mtime: i32,
+    ) -> PyResult<PyNone> {
+        self.update(py, |entry| entry.set_clean(mode, size, mtime));
+        Ok(PyNone)
+    }
+
+    def set_possibly_dirty(&self) -> PyResult<PyNone> {
+        self.update(py, |entry| entry.set_possibly_dirty());
+        Ok(PyNone)
+    }
+
+    def set_tracked(&self) -> PyResult<PyNone> {
+        self.update(py, |entry| entry.set_tracked());
+        Ok(PyNone)
+    }
+
+    def set_untracked(&self) -> PyResult<PyNone> {
+        self.update(py, |entry| entry.set_untracked());
+        Ok(PyNone)
+    }
+});
+
+impl DirstateItem {
+    pub fn new_as_pyobject(
+        py: Python<'_>,
+        entry: DirstateEntry,
+    ) -> PyResult<PyObject> {
+        Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
+    }
+
+    // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
+    pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
+        let mut entry = self.entry(py).get();
+        f(&mut entry);
+        self.entry(py).set(entry)
+    }
+}