D5440: rust: core implementation for lazyancestors

2018-12-23 Thread gracinet (Georges Racinet)
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

2018-12-22 Thread gracinet (Georges Racinet)
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

2018-12-22 Thread gracinet (Georges Racinet)
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

2018-12-22 Thread gracinet (Georges Racinet)
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

2018-12-15 Thread yuja (Yuya Nishihara)
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

2018-12-15 Thread Yuya Nishihara
> --- 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

2018-12-15 Thread kevincox (Kevin Cox)
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

2018-12-15 Thread gracinet (Georges Racinet)
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,
+})
+}
+
+