rust-index: add support for `computephasesmapsets`
authorRaphaël Gomès <rgomes@octobus.net>
Mon, 30 Oct 2023 11:54:42 +0100
changeset 51241 c817d9f626d3
parent 51240 8cb31833b486
child 51242 5a7d5fd6808c
rust-index: add support for `computephasesmapsets` Exposition in `hg-cpython` done in the regular `impl` block to enjoy rustfmt and clearer compilartion errors.
rust/hg-core/src/revlog/index.rs
rust/hg-cpython/src/revlog.rs
--- a/rust/hg-core/src/revlog/index.rs	Sat Sep 30 15:59:03 2023 +0200
+++ b/rust/hg-core/src/revlog/index.rs	Mon Oct 30 11:54:42 2023 +0100
@@ -1,5 +1,5 @@
 use std::collections::hash_map::RandomState;
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
 use std::fmt::Debug;
 use std::ops::Deref;
 use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
@@ -910,7 +910,128 @@
         }
         &revs[start..end]
     }
+
+    /// Computes the set of revisions for each non-public phase from `roots`,
+    /// which are the last known roots for each non-public phase.
+    pub fn compute_phases_map_sets(
+        &self,
+        roots: HashMap<Phase, Vec<Revision>>,
+    ) -> Result<(usize, RootsPerPhase), GraphError> {
+        let mut phases = HashMap::new();
+        let mut min_phase_rev = NULL_REVISION;
+
+        for phase in Phase::non_public_phases() {
+            if let Some(phase_roots) = roots.get(phase) {
+                let min_rev =
+                    self.add_roots_get_min(phase_roots, &mut phases, *phase);
+                if min_rev != NULL_REVISION
+                    && (min_phase_rev == NULL_REVISION
+                        || min_rev < min_phase_rev)
+                {
+                    min_phase_rev = min_rev;
+                }
+            } else {
+                continue;
+            };
+        }
+        let mut phase_sets: RootsPerPhase = Default::default();
+
+        if min_phase_rev == NULL_REVISION {
+            min_phase_rev = Revision(self.len() as BaseRevision);
+        }
+
+        for rev in min_phase_rev.0..self.len() as BaseRevision {
+            let rev = Revision(rev);
+            let [p1, p2] = self.parents(rev)?;
+
+            const DEFAULT_PHASE: &Phase = &Phase::Public;
+            if p1.0 >= 0
+                && phases.get(&p1).unwrap_or(DEFAULT_PHASE)
+                    > phases.get(&rev).unwrap_or(DEFAULT_PHASE)
+            {
+                phases.insert(rev, phases[&p1]);
+            }
+            if p2.0 >= 0
+                && phases.get(&p2).unwrap_or(DEFAULT_PHASE)
+                    > phases.get(&rev).unwrap_or(DEFAULT_PHASE)
+            {
+                phases.insert(rev, phases[&p2]);
+            }
+            let set = match phases.get(&rev).unwrap_or(DEFAULT_PHASE) {
+                Phase::Public => continue,
+                phase => &mut phase_sets[*phase as usize - 1],
+            };
+            set.insert(rev);
+        }
+
+        Ok((self.len(), phase_sets))
+    }
+
+    fn add_roots_get_min(
+        &self,
+        phase_roots: &[Revision],
+        phases: &mut HashMap<Revision, Phase>,
+        phase: Phase,
+    ) -> Revision {
+        let mut min_rev = NULL_REVISION;
+
+        for root in phase_roots {
+            phases.insert(*root, phase);
+            if min_rev == NULL_REVISION || min_rev > *root {
+                min_rev = *root;
+            }
+        }
+        min_rev
+    }
 }
+
+/// Set of roots of all non-public phases
+pub type RootsPerPhase = [HashSet<Revision>; Phase::non_public_phases().len()];
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
+pub enum Phase {
+    Public = 0,
+    Draft = 1,
+    Secret = 2,
+    Archived = 3,
+    Internal = 4,
+}
+
+impl TryFrom<usize> for Phase {
+    type Error = RevlogError;
+
+    fn try_from(value: usize) -> Result<Self, Self::Error> {
+        Ok(match value {
+            0 => Self::Public,
+            1 => Self::Draft,
+            2 => Self::Secret,
+            32 => Self::Archived,
+            96 => Self::Internal,
+            v => {
+                return Err(RevlogError::corrupted(format!(
+                    "invalid phase value {}",
+                    v
+                )))
+            }
+        })
+    }
+}
+
+impl Phase {
+    pub const fn all_phases() -> &'static [Self] {
+        &[
+            Self::Public,
+            Self::Draft,
+            Self::Secret,
+            Self::Archived,
+            Self::Internal,
+        ]
+    }
+    pub const fn non_public_phases() -> &'static [Self] {
+        &[Self::Draft, Self::Secret, Self::Archived, Self::Internal]
+    }
+}
+
 fn inline_scan(bytes: &[u8]) -> (usize, Vec<usize>) {
     let mut offset: usize = 0;
     let mut offsets = Vec::new();
--- a/rust/hg-cpython/src/revlog.rs	Sat Sep 30 15:59:03 2023 +0200
+++ b/rust/hg-cpython/src/revlog.rs	Mon Oct 30 11:54:42 2023 +0100
@@ -20,12 +20,12 @@
 };
 use hg::{
     errors::HgError,
-    index::{IndexHeader, RevisionDataParams, SnapshotsCache},
+    index::{IndexHeader, Phase, RevisionDataParams, SnapshotsCache},
     nodemap::{Block, NodeMapError, NodeTree},
     revlog::{nodemap::NodeMap, NodePrefix, RevlogError, RevlogIndex},
     BaseRevision, Revision, UncheckedRevision, NULL_REVISION,
 };
-use std::cell::RefCell;
+use std::{cell::RefCell, collections::HashMap};
 
 /// Return a Struct implementing the Graph trait
 pub(crate) fn pyindex_to_graph(
@@ -241,7 +241,12 @@
 
     /// compute phases
     def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
-        self.call_cindex(py, "computephasesmapsets", args, kw)
+        let py_roots = args.get_item(py, 0).extract::<PyDict>(py)?;
+        let rust_res = self.inner_computephasesmapsets(py, py_roots)?;
+
+        let c_res = self.call_cindex(py, "computephasesmapsets", args, kw)?;
+        assert_py_eq(py, "computephasesmapsets", &rust_res, &c_res)?;
+        Ok(rust_res)
     }
 
     /// reachableroots
@@ -829,6 +834,71 @@
         Ok(PyList::new(py, &as_vec).into_object())
     }
 
+    fn inner_computephasesmapsets(
+        &self,
+        py: Python,
+        py_roots: PyDict,
+    ) -> PyResult<PyObject> {
+        let index = &*self.index(py).borrow();
+        let opt = self.get_nodetree(py)?.borrow();
+        let nt = opt.as_ref().unwrap();
+        let roots: Result<HashMap<Phase, Vec<Revision>>, PyErr> = py_roots
+            .items_list(py)
+            .iter(py)
+            .map(|r| {
+                let phase = r.get_item(py, 0)?;
+                let nodes = r.get_item(py, 1)?;
+                // Transform the nodes from Python to revs here since we
+                // have access to the nodemap
+                let revs: Result<_, _> = nodes
+                    .iter(py)?
+                    .map(|node| match node?.extract::<PyBytes>(py) {
+                        Ok(py_bytes) => {
+                            let node = node_from_py_bytes(py, &py_bytes)?;
+                            nt.find_bin(index, node.into())
+                                .map_err(|e| nodemap_error(py, e))?
+                                .ok_or_else(|| revlog_error(py))
+                        }
+                        Err(e) => Err(e),
+                    })
+                    .collect();
+                let phase = Phase::try_from(phase.extract::<usize>(py)?)
+                    .map_err(|_| revlog_error(py));
+                Ok((phase?, revs?))
+            })
+            .collect();
+        let (len, phase_maps) = index
+            .compute_phases_map_sets(roots?)
+            .map_err(|e| graph_error(py, e))?;
+
+        // Ugly hack, but temporary
+        const IDX_TO_PHASE_NUM: [usize; 4] = [1, 2, 32, 96];
+        let py_phase_maps = PyDict::new(py);
+        for (idx, roots) in phase_maps.iter().enumerate() {
+            let phase_num = IDX_TO_PHASE_NUM[idx].into_py_object(py);
+            // OPTIM too bad we have to collect here. At least, we could
+            // reuse the same Vec and allocate it with capacity at
+            // max(len(phase_maps)
+            let roots_vec: Vec<PyInt> = roots
+                .iter()
+                .map(|r| PyRevision::from(*r).into_py_object(py))
+                .collect();
+            py_phase_maps.set_item(
+                py,
+                phase_num,
+                PySet::new(py, roots_vec)?,
+            )?;
+        }
+        Ok(PyTuple::new(
+            py,
+            &[
+                len.into_py_object(py).into_object(),
+                py_phase_maps.into_object(),
+            ],
+        )
+        .into_object())
+    }
+
     fn inner_slicechunktodensity(
         &self,
         py: Python,