D5440: rust: core implementation for lazyancestors
This revision was automatically updated to reflect the committed changes. Closed by commit rHGef54bd33b476: rust: core implementation for lazyancestors (authored by gracinet, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D5440?vs=12954&id=12968 REVISION DETAIL https://phab.mercurial-scm.org/D5440 AFFECTED FILES rust/hg-core/src/ancestors.rs rust/hg-core/src/lib.rs rust/hg-cpython/src/cindex.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/cindex.rs b/rust/hg-cpython/src/cindex.rs --- a/rust/hg-cpython/src/cindex.rs +++ b/rust/hg-cpython/src/cindex.rs @@ -15,7 +15,7 @@ extern crate python3_sys as python_sys; use self::python_sys::PyCapsule_Import; -use cpython::{PyErr, PyObject, PyResult, Python}; +use cpython::{PyClone, PyErr, PyObject, PyResult, Python}; use hg::{Graph, GraphError, Revision}; use libc::c_int; use std::ffi::CStr; @@ -59,7 +59,6 @@ /// the core API, that would be tied to `GILGuard` / `Python<'p>` /// in the case of the `cpython` crate bindings yet could leave room for other /// mechanisms in other contexts. - pub struct Index { index: PyObject, parents: IndexParentsFn, @@ -74,6 +73,16 @@ } } +impl Clone for Index { +fn clone(&self) -> Self { +let guard = Python::acquire_gil(); +Index { +index: self.index.clone_ref(guard.python()), +parents: self.parents.clone(), +} +} +} + impl Graph for Index { /// wrap a call to the C extern parents function fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError> { diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -3,7 +3,7 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. mod ancestors; -pub use ancestors::{AncestorsIterator, MissingAncestors}; +pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors}; /// Mercurial revision numbers /// diff --git a/rust/hg-core/src/ancestors.rs b/rust/hg-core/src/ancestors.rs --- a/rust/hg-core/src/ancestors.rs +++ b/rust/hg-core/src/ancestors.rs @@ -25,6 +25,15 @@ stoprev: Revision, } +/// Lazy ancestors set, backed by AncestorsIterator +pub struct LazyAncestors { +graph: G, +containsiter: AncestorsIterator, +initrevs: Vec, +stoprev: Revision, +inclusive: bool, +} + pub struct MissingAncestors { graph: G, bases: HashSet, @@ -98,9 +107,31 @@ } Ok(false) } + +pub fn peek(&self) -> Option { +self.visit.peek().map(|&r| r) +} + +/// Tell if the iterator is about an empty set +/// +/// The result does not depend whether the iterator has been consumed +/// or not. +/// This is mostly meant for iterators backing a lazy ancestors set +pub fn is_empty(&self) -> bool { +if self.visit.len() > 0 { +return false; +} +if self.seen.len() > 1 { +return false; +} +// at this point, the seen set is at most a singleton. +// If not `self.inclusive`, it's still possible that it has only +// the null revision +self.seen.is_empty() || self.seen.contains(&NULL_REVISION) +} } -/// Main implementation. +/// Main implementation for the iterator /// /// The algorithm is the same as in `_lazyancestorsiter()` from `ancestors.py` /// with a few non crucial differences: @@ -137,6 +168,49 @@ } } +impl LazyAncestors { +pub fn new( +graph: G, +initrevs: impl IntoIterator, +stoprev: Revision, +inclusive: bool, +) -> Result { +let v: Vec = initrevs.into_iter().collect(); +Ok(LazyAncestors { +graph: graph.clone(), +containsiter: AncestorsIterator::new( +graph, +v.iter().cloned(), +stoprev, +inclusive, +)?, +initrevs: v, +stoprev: stoprev, +inclusive: inclusive, +}) +} + +pub fn contains(&mut self, rev: Revision) -> Result { +self.containsiter.contains(rev) +} + +pub fn is_empty(&self) -> bool { +self.containsiter.is_empty() +} + +pub fn iter(&self) -> AncestorsIterator { +// the arguments being the same as for self.containsiter, we know +// for sure that AncestorsIterator constructor can't fail +AncestorsIterator::new( +self.graph.clone(), +self.initrevs.iter().cloned(), +self.stoprev, +self.inclusive, +) +.unwrap() +} +} + impl MissingAncestors { pub fn new(graph: G, bases: impl IntoIterator) -> Self { let mut bases: HashSet = bases.into_iter().collect(); @@ -407,7 +481,40 @@ assert!(!lazy.contains(NULL_REVISION).unwrap()); } +#
D5440: rust: core implementation for lazyancestors
gracinet updated this revision to Diff 12954. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D5440?vs=12951&id=12954 REVISION DETAIL https://phab.mercurial-scm.org/D5440 AFFECTED FILES rust/hg-core/src/ancestors.rs rust/hg-core/src/lib.rs rust/hg-cpython/src/cindex.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/cindex.rs b/rust/hg-cpython/src/cindex.rs --- a/rust/hg-cpython/src/cindex.rs +++ b/rust/hg-cpython/src/cindex.rs @@ -15,7 +15,7 @@ extern crate python3_sys as python_sys; use self::python_sys::PyCapsule_Import; -use cpython::{PyErr, PyObject, PyResult, Python}; +use cpython::{PyClone, PyErr, PyObject, PyResult, Python}; use hg::{Graph, GraphError, Revision}; use libc::c_int; use std::ffi::CStr; @@ -59,7 +59,6 @@ /// the core API, that would be tied to `GILGuard` / `Python<'p>` /// in the case of the `cpython` crate bindings yet could leave room for other /// mechanisms in other contexts. - pub struct Index { index: PyObject, parents: IndexParentsFn, @@ -74,6 +73,16 @@ } } +impl Clone for Index { +fn clone(&self) -> Self { +let guard = Python::acquire_gil(); +Index { +index: self.index.clone_ref(guard.python()), +parents: self.parents.clone(), +} +} +} + impl Graph for Index { /// wrap a call to the C extern parents function fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError> { diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -3,7 +3,7 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. mod ancestors; -pub use ancestors::{AncestorsIterator, MissingAncestors}; +pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors}; /// Mercurial revision numbers /// diff --git a/rust/hg-core/src/ancestors.rs b/rust/hg-core/src/ancestors.rs --- a/rust/hg-core/src/ancestors.rs +++ b/rust/hg-core/src/ancestors.rs @@ -25,6 +25,15 @@ stoprev: Revision, } +/// Lazy ancestors set, backed by AncestorsIterator +pub struct LazyAncestors { +graph: G, +containsiter: AncestorsIterator, +initrevs: Vec, +stoprev: Revision, +inclusive: bool, +} + pub struct MissingAncestors { graph: G, bases: HashSet, @@ -98,9 +107,31 @@ } Ok(false) } + +pub fn peek(&self) -> Option { +self.visit.peek().map(|&r| r) +} + +/// Tell if the iterator is about an empty set +/// +/// The result does not depend whether the iterator has been consumed +/// or not. +/// This is mostly meant for iterators backing a lazy ancestors set +pub fn is_empty(&self) -> bool { +if self.visit.len() > 0 { +return false; +} +if self.seen.len() > 1 { +return false; +} +// at this point, the seen set is at most a singleton. +// If not `self.inclusive`, it's still possible that it has only +// the null revision +self.seen.is_empty() || self.seen.contains(&NULL_REVISION) +} } -/// Main implementation. +/// Main implementation for the iterator /// /// The algorithm is the same as in `_lazyancestorsiter()` from `ancestors.py` /// with a few non crucial differences: @@ -137,6 +168,49 @@ } } +impl LazyAncestors { +pub fn new( +graph: G, +initrevs: impl IntoIterator, +stoprev: Revision, +inclusive: bool, +) -> Result { +let v: Vec = initrevs.into_iter().collect(); +Ok(LazyAncestors { +graph: graph.clone(), +containsiter: AncestorsIterator::new( +graph, +v.iter().cloned(), +stoprev, +inclusive, +)?, +initrevs: v, +stoprev: stoprev, +inclusive: inclusive, +}) +} + +pub fn contains(&mut self, rev: Revision) -> Result { +self.containsiter.contains(rev) +} + +pub fn is_empty(&self) -> bool { +self.containsiter.is_empty() +} + +pub fn iter(&self) -> AncestorsIterator { +// the arguments being the same as for self.containsiter, we know +// for sure that AncestorsIterator constructor can't fail +AncestorsIterator::new( +self.graph.clone(), +self.initrevs.iter().cloned(), +self.stoprev, +self.inclusive, +) +.unwrap() +} +} + impl MissingAncestors { pub fn new(graph: G, bases: impl IntoIterator) -> Self { let mut bases: HashSet = bases.into_iter().collect(); @@ -407,7 +481,40 @@ assert!(!lazy.contains(NULL_REVISION).unwrap()); } +#[test] +fn test_peek() { +let mut iter = +AncestorsIterator::new(Stub, vec![10], 0, true).unwrap(); +// peek() giv
D5440: rust: core implementation for lazyancestors
gracinet updated this revision to Diff 12951. gracinet marked 4 inline comments as done. REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D5440?vs=12877&id=12951 REVISION DETAIL https://phab.mercurial-scm.org/D5440 AFFECTED FILES rust/hg-core/src/ancestors.rs rust/hg-core/src/lib.rs rust/hg-cpython/src/cindex.rs CHANGE DETAILS diff --git a/rust/hg-cpython/src/cindex.rs b/rust/hg-cpython/src/cindex.rs --- a/rust/hg-cpython/src/cindex.rs +++ b/rust/hg-cpython/src/cindex.rs @@ -15,7 +15,7 @@ extern crate python3_sys as python_sys; use self::python_sys::PyCapsule_Import; -use cpython::{PyErr, PyObject, PyResult, Python}; +use cpython::{PyClone, PyErr, PyObject, PyResult, Python}; use hg::{Graph, GraphError, Revision}; use libc::c_int; use std::ffi::CStr; @@ -59,7 +59,6 @@ /// the core API, that would be tied to `GILGuard` / `Python<'p>` /// in the case of the `cpython` crate bindings yet could leave room for other /// mechanisms in other contexts. - pub struct Index { index: PyObject, parents: IndexParentsFn, @@ -74,6 +73,16 @@ } } +impl Clone for Index { +fn clone(&self) -> Self { +let guard = Python::acquire_gil(); +Index { +index: self.index.clone_ref(guard.python()), +parents: self.parents.clone(), +} +} +} + impl Graph for Index { /// wrap a call to the C extern parents function fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError> { diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -3,7 +3,7 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. mod ancestors; -pub use ancestors::{AncestorsIterator, MissingAncestors}; +pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors}; /// Mercurial revision numbers /// diff --git a/rust/hg-core/src/ancestors.rs b/rust/hg-core/src/ancestors.rs --- a/rust/hg-core/src/ancestors.rs +++ b/rust/hg-core/src/ancestors.rs @@ -25,6 +25,15 @@ stoprev: Revision, } +/// Lazy ancestors set, backed by AncestorsIterator +pub struct LazyAncestors { +graph: G, +containsiter: AncestorsIterator, +initrevs: Vec, +stoprev: Revision, +inclusive: bool, +} + pub struct MissingAncestors { graph: G, bases: HashSet, @@ -98,9 +107,31 @@ } Ok(false) } + +pub fn peek(&self) -> Option { +self.visit.peek().map(|&r| r) +} + +/// Tell if the iterator is about an empty set +/// +/// The result does not depend whether the iterator has been consumed +/// or not. +/// This is mostly meant for iterators backing a lazy ancestors set +pub fn is_empty(&self) -> bool { +if self.visit.len() > 0 { +return false; +} +if self.seen.len() > 1 { +return false; +} +// at this point, the seen set is at most a singleton. +// If not `self.inclusive`, it's still possible that it has only +// the null revision +self.seen.is_empty() || self.seen.contains(&NULL_REVISION) +} } -/// Main implementation. +/// Main implementation for the iterator /// /// The algorithm is the same as in `_lazyancestorsiter()` from `ancestors.py` /// with a few non crucial differences: @@ -137,6 +168,49 @@ } } +impl LazyAncestors { +pub fn new( +graph: G, +initrevs: impl IntoIterator, +stoprev: Revision, +inclusive: bool, +) -> Result { +let v: Vec = initrevs.into_iter().collect(); +Ok(LazyAncestors { +graph: graph.clone(), +containsiter: AncestorsIterator::new( +graph, +v.iter().cloned(), +stoprev, +inclusive, +)?, +initrevs: v, +stoprev: stoprev, +inclusive: inclusive, +}) +} + +pub fn contains(&mut self, rev: Revision) -> Result { +self.containsiter.contains(rev) +} + +pub fn is_empty(&self) -> bool { +self.containsiter.is_empty() +} + +pub fn iter(&self) -> AncestorsIterator { +// the arguments being the same as for self.containsiter, we know +// for sure that AncestorsIterator constructor can't fail +AncestorsIterator::new( +self.graph.clone(), +self.initrevs.iter().cloned(), +self.stoprev, +self.inclusive, +) +.unwrap() +} +} + impl MissingAncestors { pub fn new(graph: G, bases: impl IntoIterator) -> Self { let mut bases: HashSet = bases.into_iter().collect(); @@ -407,7 +481,40 @@ assert!(!lazy.contains(NULL_REVISION).unwrap()); } +#[test] +fn test_peek() { +let mut iter = +AncestorsIterator::new(Stub, vec![10]
D5440: rust: core implementation for lazyancestors
gracinet added a comment. @yuja, yes a `Graph` not implementing `Clone` is already a good thing, as it avoids to implement `Clone` in `hg-direct-ffi` prematurely. I think the whole `hg::LazyAncestors` can end up being useful from core Rust code, too, that's why I prefer that to a `hg-cpython` only implementation. INLINE COMMENTS > kevincox wrote in ancestors.rs:124 > I think this variable makes the code harder to read. I would just repeat the > calls to `.len()`. agreed, and made a clearer version of the last lines as an OR statement using `self.seen.is_empty()` REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D5440 To: gracinet, #hg-reviewers, kevincox Cc: yuja, durin42, kevincox, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D5440: rust: core implementation for lazyancestors
yuja added a comment. > - a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -2,8 +2,10 @@ // // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. +use std::clone::Clone; Nit: it's in prelude. > /// The simplest expression of what we need of Mercurial DAGs. > > -pub trait Graph { > +pub trait Graph: Clone { I think it's the requirement of the caller, i.e. `G: Graph + Clone`. Not all graphs have to be clone-able. Alternatively, maybe the LazyAncestor can be moved to the cpython crate, where PyClone is available. > +/// Tell if the iterator is about an empty set > +/// > +/// The result does not depend whether the iterator has been consumed > +/// or not. > +/// This is mostly meant for iterators backing a lazy ancestors set > +pub fn empty(&self) -> bool { Nit: s/empty/is_empty/ because `empty()` can be a verb. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D5440 To: gracinet, #hg-reviewers, kevincox Cc: yuja, durin42, kevincox, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: D5440: rust: core implementation for lazyancestors
> --- a/rust/hg-core/src/lib.rs > +++ b/rust/hg-core/src/lib.rs > @@ -2,8 +2,10 @@ > // > // This software may be used and distributed according to the terms of the > // GNU General Public License version 2 or any later version. > +use std::clone::Clone; Nit: it's in prelude. > /// The simplest expression of what we need of Mercurial DAGs. > -pub trait Graph { > +pub trait Graph: Clone { I think it's the requirement of the caller, i.e. `G: Graph + Clone`. Not all graphs have to be clone-able. Alternatively, maybe the LazyAncestor can be moved to the cpython crate, where PyClone is available. > +/// Tell if the iterator is about an empty set > +/// > +/// The result does not depend whether the iterator has been consumed > +/// or not. > +/// This is mostly meant for iterators backing a lazy ancestors set > +pub fn empty(&self) -> bool { Nit: s/empty/is_empty/ because `empty()` can be a verb. ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D5440: rust: core implementation for lazyancestors
kevincox accepted this revision. kevincox added inline comments. INLINE COMMENTS > ancestors.rs:120 > +/// This is mostly meant for iterators backing a lazy ancestors set > +pub fn empty(&self) -> bool { > +if self.visit.len() > 0 { Can you clarify this `is_empty` to match rust convention? > ancestors.rs:121 > +pub fn empty(&self) -> bool { > +if self.visit.len() > 0 { > +return false; `if !self.visit.is_empty()` > ancestors.rs:124 > +} > +let seen = self.seen.len(); > +if seen > 1 { I think this variable makes the code harder to read. I would just repeat the calls to `.len()`. > ancestors.rs:196 > + > +#[inline] > +pub fn contains(&mut self, rev: Revision) -> Result { In general I wouldn't add these without careful benchmarking. In this case the compiler can trivially notice that this method is a good inlining candidate. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D5440 To: gracinet, #hg-reviewers, kevincox Cc: durin42, kevincox, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D5440: rust: core implementation for lazyancestors
gracinet created this revision. Herald added subscribers: mercurial-devel, kevincox, durin42. Herald added a reviewer: hg-reviewers. REVISION SUMMARY Once exposed through appropriate bindings, this should be able to replace ancestor.lazyancestors entirely. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D5440 AFFECTED FILES rust/hg-core/src/ancestors.rs rust/hg-core/src/lib.rs rust/hg-cpython/src/cindex.rs rust/hg-direct-ffi/src/ancestors.rs CHANGE DETAILS diff --git a/rust/hg-direct-ffi/src/ancestors.rs b/rust/hg-direct-ffi/src/ancestors.rs --- a/rust/hg-direct-ffi/src/ancestors.rs +++ b/rust/hg-direct-ffi/src/ancestors.rs @@ -30,6 +30,12 @@ /// This implementation of the Graph trait, relies on (pointers to) /// - the C index object (`index` member) /// - the `index_get_parents()` function (`parents` member) +/// +/// In case of `.clone()` (would be from a full binding for `LazyAncestors` +/// that doesn't exist at the time of this writing), reference increasing +/// and decreasing would be the responsibility of the caller, same as it +/// is for `AncestorsIterator` +#[derive(Clone, Debug)] pub struct Index { index: IndexPtr, } diff --git a/rust/hg-cpython/src/cindex.rs b/rust/hg-cpython/src/cindex.rs --- a/rust/hg-cpython/src/cindex.rs +++ b/rust/hg-cpython/src/cindex.rs @@ -15,7 +15,7 @@ extern crate python3_sys as python_sys; use self::python_sys::PyCapsule_Import; -use cpython::{PyErr, PyObject, PyResult, Python}; +use cpython::{PyClone, PyErr, PyObject, PyResult, Python}; use hg::{Graph, GraphError, Revision}; use libc::{c_int, ssize_t}; use std::ffi::CStr; @@ -47,6 +47,16 @@ } } +impl Clone for Index { +fn clone(&self) -> Self { +let guard = Python::acquire_gil(); +Index { +index: self.index.clone_ref(guard.python()), +parents: self.parents.clone(), +} +} +} + impl Graph for Index { /// wrap a call to the C extern parents function fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError> { diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -2,8 +2,10 @@ // // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. +use std::clone::Clone; + mod ancestors; -pub use ancestors::{AncestorsIterator, MissingAncestors}; +pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors}; /// Mercurial revision numbers /// @@ -14,7 +16,7 @@ pub const NULL_REVISION: Revision = -1; /// The simplest expression of what we need of Mercurial DAGs. -pub trait Graph { +pub trait Graph: Clone { /// Return the two parents of the given `Revision`. /// /// Each of the parents can be independently `NULL_REVISION` diff --git a/rust/hg-core/src/ancestors.rs b/rust/hg-core/src/ancestors.rs --- a/rust/hg-core/src/ancestors.rs +++ b/rust/hg-core/src/ancestors.rs @@ -25,6 +25,15 @@ stoprev: Revision, } +/// Lazy ancestors set, backed by AncestorsIterator +pub struct LazyAncestors { +graph: G, +containsiter: AncestorsIterator, +initrevs: Vec, +stoprev: Revision, +inclusive: bool, +} + pub struct MissingAncestors { graph: G, bases: HashSet, @@ -98,9 +107,34 @@ } Ok(false) } + +pub fn peek(&self) -> Option { +self.visit.peek().map(|&r| r) +} + +/// Tell if the iterator is about an empty set +/// +/// The result does not depend whether the iterator has been consumed +/// or not. +/// This is mostly meant for iterators backing a lazy ancestors set +pub fn empty(&self) -> bool { +if self.visit.len() > 0 { +return false; +} +let seen = self.seen.len(); +if seen > 1 { +return false; +} +// at this point, the seen set is a singleton. If not self.inclusive, +// then its only element can be the null revision +if seen == 0 || self.seen.contains(&NULL_REVISION) { +return true; +} +false +} } -/// Main implementation. +/// Main implementation for the iterator /// /// The algorithm is the same as in `_lazyancestorsiter()` from `ancestors.py` /// with a few non crucial differences: @@ -137,6 +171,51 @@ } } +impl LazyAncestors { +pub fn new( +graph: G, +initrevs: impl IntoIterator, +stoprev: Revision, +inclusive: bool, +) -> Result { +let v: Vec = initrevs.into_iter().collect(); +Ok(LazyAncestors { +graph: graph.clone(), +containsiter: AncestorsIterator::new( +graph, +v.iter().cloned(), +stoprev, +inclusive, +)?, +initrevs: v, +stoprev: stoprev, +inclusive: inclusive, +}) +} + +