diff --git a/rust/hg-core/src/revlog/index.rs b/rust/hg-core/src/revlog/index.rs --- a/rust/hg-core/src/revlog/index.rs +++ b/rust/hg-core/src/revlog/index.rs @@ -1036,14 +1036,40 @@ impl Index { /// 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, 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 = 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 = as_set.into_iter().collect(); + + if revisions.len() <= 63 { + self.find_gca_candidates::(&revisions) + } else { + self.find_gca_candidates::(&revisions) + } + } + + pub fn ancestors( + &self, + revisions: &[Revision], + ) -> Result, 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 ...)` - #[allow(dead_code)] fn find_gca_candidates( &self, revs: &[Revision], @@ -1147,7 +1173,6 @@ impl Index { /// 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 --- a/rust/hg-cpython/src/revlog.rs +++ b/rust/hg-cpython/src/revlog.rs @@ -196,12 +196,24 @@ py_class!(pub class MixedIndex |py| { /// return the gca set of the given revs def ancestors(&self, *args, **kw) -> PyResult { - 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 { - 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 @@ impl MixedIndex { Ok(PyList::new(py, &as_vec).into_object()) } + fn inner_ancestors( + &self, + py: Python, + py_revs: &PyTuple, + ) -> PyResult { + 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 { + 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,