diff --git a/rust/hg-core/src/dirstate/entry.rs b/rust/hg-core/src/dirstate/entry.rs --- a/rust/hg-core/src/dirstate/entry.rs +++ b/rust/hg-core/src/dirstate/entry.rs @@ -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. /// diff --git a/rust/hg-cpython/src/dirstate.rs b/rust/hg-cpython/src/dirstate.rs --- a/rust/hg-cpython/src/dirstate.rs +++ b/rust/hg-cpython/src/dirstate.rs @@ -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::(py)?; m.add_class::(py)?; + m.add_class::(py)?; m.add(py, "V2_FORMAT_MARKER", PyBytes::new(py, V2_FORMAT_MARKER))?; m.add( py, diff --git a/rust/hg-cpython/src/dirstate/item.rs b/rust/hg-cpython/src/dirstate/item.rs new file mode 100644 --- /dev/null +++ b/rust/hg-cpython/src/dirstate/item.rs @@ -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; + + 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 { + 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 { + let state_byte: u8 = self.entry(py).get().state().into(); + Ok(PyBytes::new(py, &[state_byte])) + } + + @property + def mode(&self) -> PyResult { + Ok(self.entry(py).get().mode()) + } + + @property + def size(&self) -> PyResult { + Ok(self.entry(py).get().size()) + } + + @property + def mtime(&self) -> PyResult { + Ok(self.entry(py).get().mtime()) + } + + @property + def tracked(&self) -> PyResult { + Ok(self.entry(py).get().tracked()) + } + + @property + def added(&self) -> PyResult { + Ok(self.entry(py).get().added()) + } + + @property + def merged(&self) -> PyResult { + Ok(self.entry(py).get().merged()) + } + + @property + def removed(&self) -> PyResult { + Ok(self.entry(py).get().removed()) + } + + @property + def from_p2(&self) -> PyResult { + Ok(self.entry(py).get().from_p2()) + } + + @property + def merged_removed(&self) -> PyResult { + Ok(self.entry(py).get().merged_removed()) + } + + @property + def from_p2_removed(&self) -> PyResult { + Ok(self.entry(py).get().from_p2_removed()) + } + + @property + def dm_nonnormal(&self) -> PyResult { + Ok(self.entry(py).get().is_non_normal()) + } + + @property + def dm_otherparent(&self) -> PyResult { + Ok(self.entry(py).get().is_from_other_parent()) + } + + def v1_state(&self) -> PyResult { + 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 { + let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data(); + Ok(mode) + } + + def v1_size(&self) -> PyResult { + let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data(); + Ok(size) + } + + def v1_mtime(&self) -> PyResult { + let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data(); + Ok(mtime) + } + + def need_delay(&self, now: i32) -> PyResult { + Ok(self.entry(py).get().mtime_is_ambiguous(now)) + } + + @classmethod + def from_v1_data( + _cls, + state: PyBytes, + mode: i32, + size: i32, + mtime: i32, + ) -> PyResult { + let state = <[u8; 1]>::try_from(state.data(py)) + .ok() + .and_then(|state| EntryState::try_from(state[0]).ok()) + .ok_or_else(|| PyErr::new::(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 { + self.update(py, |entry| entry.set_clean(mode, size, mtime)); + Ok(PyNone) + } + + def set_possibly_dirty(&self) -> PyResult { + self.update(py, |entry| entry.set_possibly_dirty()); + Ok(PyNone) + } + + def set_tracked(&self) -> PyResult { + self.update(py, |entry| entry.set_tracked()); + Ok(PyNone) + } + + def set_untracked(&self) -> PyResult { + self.update(py, |entry| entry.set_untracked()); + Ok(PyNone) + } +}); + +impl DirstateItem { + pub fn new_as_pyobject( + py: Python<'_>, + entry: DirstateEntry, + ) -> PyResult { + 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) + } +}