diff --git a/rust/hg-core/src/revlog/index.rs b/rust/hg-core/src/revlog/index.rs index 43241f31cf5b432b3ec1509ee9caa28ca6961ad3_cnVzdC9oZy1jb3JlL3NyYy9yZXZsb2cvaW5kZXgucnM=..89ce6a49bfeb991570001542bef34b256701bb19_cnVzdC9oZy1jb3JlL3NyYy9yZXZsb2cvaW5kZXgucnM= 100644 --- a/rust/hg-core/src/revlog/index.rs +++ b/rust/hg-core/src/revlog/index.rs @@ -1036,10 +1036,37 @@ /// Given a (possibly overlapping) set of revs, return all the /// common ancestors heads: `heads(::args[0] and ::a[1] and ...)` - pub fn common_ancestor_heads(&self, _revisions: &[Revision]) { - todo!() + pub fn common_ancestor_heads( + &self, + revisions: &[Revision], + ) -> Result<Vec<Revision>, GraphError> { + // given that revisions is expected to be small, we find this shortcut + // potentially acceptable, especially given that `hg-cpython` could + // very much bypass this, constructing a vector of unique values from + // the onset. + let as_set: HashSet<Revision> = revisions.iter().copied().collect(); + // Besides deduplicating, the C version also implements the shortcut + // for `NULL_REVISION`: + if as_set.contains(&NULL_REVISION) { + return Ok(vec![]); + } + + let revisions: Vec<Revision> = as_set.into_iter().collect(); + + if revisions.len() <= 63 { + self.find_gca_candidates::<u64>(&revisions) + } else { + self.find_gca_candidates::<NonStaticPoisonableBitSet>(&revisions) + } + } + + pub fn ancestors( + &self, + revisions: &[Revision], + ) -> Result<Vec<Revision>, GraphError> { + self.find_deepest_revs(&self.common_ancestor_heads(revisions)?) } /// Given a disjoint set of revs, return all candidates for the /// greatest common ancestor. In revset notation, this is the set /// `heads(::a and ::b and ...)` @@ -1041,9 +1068,8 @@ } /// Given a disjoint set of revs, return all candidates for the /// greatest common ancestor. In revset notation, this is the set /// `heads(::a and ::b and ...)` - #[allow(dead_code)] fn find_gca_candidates<BS: PoisonableBitSet + Clone>( &self, revs: &[Revision], @@ -1147,7 +1173,6 @@ /// Given a disjoint set of revs, return the subset with the longest path /// to the root. - #[allow(dead_code)] fn find_deepest_revs( &self, revs: &[Revision], diff --git a/rust/hg-cpython/src/revlog.rs b/rust/hg-cpython/src/revlog.rs index 43241f31cf5b432b3ec1509ee9caa28ca6961ad3_cnVzdC9oZy1jcHl0aG9uL3NyYy9yZXZsb2cucnM=..89ce6a49bfeb991570001542bef34b256701bb19_cnVzdC9oZy1jcHl0aG9uL3NyYy9yZXZsb2cucnM= 100644 --- a/rust/hg-cpython/src/revlog.rs +++ b/rust/hg-cpython/src/revlog.rs @@ -196,8 +196,14 @@ /// return the gca set of the given revs def ancestors(&self, *args, **kw) -> PyResult<PyObject> { - self.call_cindex(py, "ancestors", args, kw) + let rust_res = self.inner_ancestors(py, args)?; + + let c_res = self.call_cindex(py, "ancestors", args, kw)?; + // the algorithm should always provide the results in reverse ordering + assert_py_eq(py, "ancestors", &rust_res, &c_res)?; + + Ok(rust_res) } /// return the heads of the common ancestors of the given revs def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> { @@ -200,8 +206,14 @@ } /// return the heads of the common ancestors of the given revs def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> { - self.call_cindex(py, "commonancestorsheads", args, kw) + let rust_res = self.inner_commonancestorsheads(py, args)?; + + let c_res = self.call_cindex(py, "commonancestorsheads", args, kw)?; + // the algorithm should always provide the results in reverse ordering + assert_py_eq(py, "commonancestorsheads", &rust_res, &c_res)?; + + Ok(rust_res) } /// Clear the index caches and inner py_class data. @@ -850,6 +862,38 @@ Ok(PyList::new(py, &as_vec).into_object()) } + fn inner_ancestors( + &self, + py: Python, + py_revs: &PyTuple, + ) -> PyResult<PyObject> { + let index = &mut *self.index(py).borrow_mut(); + let revs: Vec<_> = rev_pyiter_collect(py, py_revs.as_object(), index)?; + let as_vec: Vec<_> = index + .ancestors(&revs) + .map_err(|e| graph_error(py, e))? + .iter() + .map(|r| PyRevision::from(*r).into_py_object(py).into_object()) + .collect(); + Ok(PyList::new(py, &as_vec).into_object()) + } + + fn inner_commonancestorsheads( + &self, + py: Python, + py_revs: &PyTuple, + ) -> PyResult<PyObject> { + let index = &mut *self.index(py).borrow_mut(); + let revs: Vec<_> = rev_pyiter_collect(py, py_revs.as_object(), index)?; + let as_vec: Vec<_> = index + .common_ancestor_heads(&revs) + .map_err(|e| graph_error(py, e))? + .iter() + .map(|r| PyRevision::from(*r).into_py_object(py).into_object()) + .collect(); + Ok(PyList::new(py, &as_vec).into_object()) + } + fn inner_computephasesmapsets( &self, py: Python,