D6231: rust-discovery: starting core implementation

2019-04-18 Thread gracinet (Georges Racinet)
This revision was automatically updated to reflect the committed changes.
Closed by commit rHG10b465d61556: rust-discovery: starting core implementation 
(authored by gracinet, committed by ).

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D6231?vs=14794&id=14852

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

AFFECTED FILES
  rust/hg-core/src/discovery.rs
  rust/hg-core/src/lib.rs

CHANGE DETAILS

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
@@ -6,6 +6,7 @@
 pub mod dagops;
 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
 pub mod testing;  // unconditionally built, for use from integration tests
+pub mod discovery;
 
 /// Mercurial revision numbers
 ///
diff --git a/rust/hg-core/src/discovery.rs b/rust/hg-core/src/discovery.rs
new file mode 100644
--- /dev/null
+++ b/rust/hg-core/src/discovery.rs
@@ -0,0 +1,196 @@
+// discovery.rs
+//
+// Copyright 2019 Georges Racinet 
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+
+//! Discovery operations
+//!
+//! This is a Rust counterpart to the `partialdiscovery` class of
+//! `mercurial.setdiscovery`
+
+use super::{Graph, GraphError, Revision};
+use crate::ancestors::MissingAncestors;
+use crate::dagops;
+use std::collections::HashSet;
+
+pub struct PartialDiscovery {
+target_heads: Option>,
+graph: G, // plays the role of self._repo
+common: MissingAncestors,
+undecided: Option>,
+missing: HashSet,
+}
+
+impl PartialDiscovery {
+/// Create a PartialDiscovery object, with the intent
+/// of comparing our `::` revset to the contents of another
+/// repo.
+///
+/// For now `target_heads` is passed as a vector, and will be used
+/// at the first call to `ensure_undecided()`.
+///
+/// If we want to make the signature more flexible,
+/// we'll have to make it a type argument of `PartialDiscovery` or a trait
+/// object since we'll keep it in the meanwhile
+pub fn new(graph: G, target_heads: Vec) -> Self {
+PartialDiscovery {
+undecided: None,
+target_heads: Some(target_heads),
+graph: graph.clone(),
+common: MissingAncestors::new(graph, vec![]),
+missing: HashSet::new(),
+}
+}
+
+/// Register revisions known as being common
+pub fn add_common_revisions(
+&mut self,
+common: impl IntoIterator,
+) -> Result<(), GraphError> {
+self.common.add_bases(common);
+if let Some(ref mut undecided) = self.undecided {
+self.common.remove_ancestors_from(undecided)?;
+}
+Ok(())
+}
+
+/// Register revisions known as being missing
+pub fn add_missing_revisions(
+&mut self,
+missing: impl IntoIterator,
+) -> Result<(), GraphError> {
+self.ensure_undecided()?;
+let range = dagops::range(
+&self.graph,
+missing,
+self.undecided.as_ref().unwrap().iter().cloned(),
+)?;
+let undecided_mut = self.undecided.as_mut().unwrap();
+for missrev in range {
+self.missing.insert(missrev);
+undecided_mut.remove(&missrev);
+}
+Ok(())
+}
+
+/// Do we have any information about the peer?
+pub fn has_info(&self) -> bool {
+self.common.has_bases()
+}
+
+/// Did we acquire full knowledge of our Revisions that the peer has?
+pub fn is_complete(&self) -> bool {
+self.undecided.as_ref().map_or(false, |s| s.is_empty())
+}
+
+/// Return the heads of the currently known common set of revisions.
+///
+/// If the discovery process is not complete (see `is_complete()`), the
+/// caller must be aware that this is an intermediate state.
+///
+/// On the other hand, if it is complete, then this is currently
+/// the only way to retrieve the end results of the discovery process.
+///
+/// We may introduce in the future an `into_common_heads` call that
+/// would be more appropriate for normal Rust callers, dropping `self`
+/// if it is complete.
+pub fn common_heads(&self) -> Result, GraphError> {
+self.common.bases_heads()
+}
+
+/// Force first computation of `self.undecided`
+///
+/// After this, `self.undecided.as_ref()` and `.as_mut()` can be
+/// unwrapped to get workable immutable or mutable references without
+/// any panic.
+///
+/// This is an imperative call instead of an access with added lazyness
+/// to reduce easily the scope of mutable borrow for the caller,
+/// compared to undecided(&'a mut self) -> &'a… that would keep it
+/// as long as the resulting immutable one.
+fn ensure_undecided(&mut self) -> Result<(), GraphError> {
+if self.undecided.is_

D6231: rust-discovery: starting core implementation

2019-04-17 Thread kevincox (Kevin Cox)
kevincox added a comment.


  In https://phab.mercurial-scm.org/D6231#91102, @gracinet wrote:
  
  > I believe to have addressed your immediate concerns.
  >
  > I played with  splitting in different `struct`s for the various stages of 
the process. It is indeed cleaner and clearer Rust code.
  >  However mutating in place of `Enum`s exposed to Python is a bit of a 
headache, because of the wrapping in `RefCell>`, and I don't have a 
solution for that, so I'm gonna think about that in a follow-up.
  
  
  Thanks, it looks good for now :)

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

To: gracinet, #hg-reviewers, kevincox
Cc: durin42, kevincox, mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D6231: rust-discovery: starting core implementation

2019-04-17 Thread gracinet (Georges Racinet)
gracinet marked an inline comment as done.
gracinet added a comment.


  I believe to have addressed your immediate concerns.
  
  I played with  splitting in different `struct`s for the various stages of the 
process. It is indeed cleaner and clearer Rust code.
  However mutating in place of `Enum`s exposed to Python is a bit of a 
headache, because of the wrapping in `RefCell>`, and I don't have a 
solution for that, so I'm gonna think about that in a follow-up.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

To: gracinet, #hg-reviewers, kevincox
Cc: durin42, kevincox, mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D6231: rust-discovery: starting core implementation

2019-04-17 Thread gracinet (Georges Racinet)
gracinet updated this revision to Diff 14794.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D6231?vs=14744&id=14794

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

AFFECTED FILES
  rust/hg-core/src/discovery.rs
  rust/hg-core/src/lib.rs

CHANGE DETAILS

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
@@ -6,6 +6,7 @@
 pub mod dagops;
 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
 pub mod testing;  // unconditionally built, for use from integration tests
+pub mod discovery;
 
 /// Mercurial revision numbers
 ///
diff --git a/rust/hg-core/src/discovery.rs b/rust/hg-core/src/discovery.rs
new file mode 100644
--- /dev/null
+++ b/rust/hg-core/src/discovery.rs
@@ -0,0 +1,196 @@
+// discovery.rs
+//
+// Copyright 2019 Georges Racinet 
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+
+//! Discovery operations
+//!
+//! This is a Rust counterpart to the `partialdiscovery` class of
+//! `mercurial.setdiscovery`
+
+use super::{Graph, GraphError, Revision};
+use crate::ancestors::MissingAncestors;
+use crate::dagops;
+use std::collections::HashSet;
+
+pub struct PartialDiscovery {
+target_heads: Option>,
+graph: G, // plays the role of self._repo
+common: MissingAncestors,
+undecided: Option>,
+missing: HashSet,
+}
+
+impl PartialDiscovery {
+/// Create a PartialDiscovery object, with the intent
+/// of comparing our `::` revset to the contents of another
+/// repo.
+///
+/// For now `target_heads` is passed as a vector, and will be used
+/// at the first call to `ensure_undecided()`.
+///
+/// If we want to make the signature more flexible,
+/// we'll have to make it a type argument of `PartialDiscovery` or a trait
+/// object since we'll keep it in the meanwhile
+pub fn new(graph: G, target_heads: Vec) -> Self {
+PartialDiscovery {
+undecided: None,
+target_heads: Some(target_heads),
+graph: graph.clone(),
+common: MissingAncestors::new(graph, vec![]),
+missing: HashSet::new(),
+}
+}
+
+/// Register revisions known as being common
+pub fn add_common_revisions(
+&mut self,
+common: impl IntoIterator,
+) -> Result<(), GraphError> {
+self.common.add_bases(common);
+if let Some(ref mut undecided) = self.undecided {
+self.common.remove_ancestors_from(undecided)?;
+}
+Ok(())
+}
+
+/// Register revisions known as being missing
+pub fn add_missing_revisions(
+&mut self,
+missing: impl IntoIterator,
+) -> Result<(), GraphError> {
+self.ensure_undecided()?;
+let range = dagops::range(
+&self.graph,
+missing,
+self.undecided.as_ref().unwrap().iter().cloned(),
+)?;
+let undecided_mut = self.undecided.as_mut().unwrap();
+for missrev in range {
+self.missing.insert(missrev);
+undecided_mut.remove(&missrev);
+}
+Ok(())
+}
+
+/// Do we have any information about the peer?
+pub fn has_info(&self) -> bool {
+self.common.has_bases()
+}
+
+/// Did we acquire full knowledge of our Revisions that the peer has?
+pub fn is_complete(&self) -> bool {
+self.undecided.as_ref().map_or(false, |s| s.is_empty())
+}
+
+/// Return the heads of the currently known common set of revisions.
+///
+/// If the discovery process is not complete (see `is_complete()`), the
+/// caller must be aware that this is an intermediate state.
+///
+/// On the other hand, if it is complete, then this is currently
+/// the only way to retrieve the end results of the discovery process.
+///
+/// We may introduce in the future an `into_common_heads` call that
+/// would be more appropriate for normal Rust callers, dropping `self`
+/// if it is complete.
+pub fn common_heads(&self) -> Result, GraphError> {
+self.common.bases_heads()
+}
+
+/// Force first computation of `self.undecided`
+///
+/// After this, `self.undecided.as_ref()` and `.as_mut()` can be
+/// unwrapped to get workable immutable or mutable references without
+/// any panic.
+///
+/// This is an imperative call instead of an access with added lazyness
+/// to reduce easily the scope of mutable borrow for the caller,
+/// compared to undecided(&'a mut self) -> &'a… that would keep it
+/// as long as the resulting immutable one.
+fn ensure_undecided(&mut self) -> Result<(), GraphError> {
+if self.undecided.is_some() {
+return Ok(());
+}
+let tgt = self.target_heads.take().unwrap();
+self.undecided =
+Some(s

D6231: rust-discovery: starting core implementation

2019-04-15 Thread kevincox (Kevin Cox)
kevincox added inline comments.

INLINE COMMENTS

> gracinet wrote in discovery.rs:93
> I'm not sure. It wouldn't be a straight error to call this early, as long as 
> the caller knows what this means, but I don't have any use-case before the 
> discovery is complete.
> 
> On the other hand, I could go as far as consume the discovery object and use 
> `self.common.into_bases_heads()`, but maybe that could make things more 
> complicated for the Python bindings.

If it is allowed can you document what happens what happens if you call it 
before then?

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

To: gracinet, #hg-reviewers, kevincox
Cc: durin42, kevincox, mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D6231: rust-discovery: starting core implementation

2019-04-15 Thread gracinet (Georges Racinet)
gracinet added inline comments.

INLINE COMMENTS

> kevincox wrote in discovery.rs:57
> You can replace the early return by an `if let`. This also removed the 
> `.unwrap()` which is always nice.
> 
>   if let Some(ref mut undecided) = self.undecided {
>   self.common.remove_ancestors_from(undecided)?;
>   }

ah, yes the trick is the `ref mut` inside the `if let`, I see

> kevincox wrote in discovery.rs:93
> This this imply that this method shouldn't be called until then? If so you 
> should probably add a `debug_assert!(self.is_complete())`.

I'm not sure. It wouldn't be a straight error to call this early, as long as 
the caller knows what this means, but I don't have any use-case before the 
discovery is complete.

On the other hand, I could go as far as consume the discovery object and use 
`self.common.into_bases_heads()`, but maybe that could make things more 
complicated for the Python bindings.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

To: gracinet, #hg-reviewers, kevincox
Cc: durin42, kevincox, mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D6231: rust-discovery: starting core implementation

2019-04-15 Thread gracinet (Georges Racinet)
gracinet updated this revision to Diff 14744.
gracinet marked 2 inline comments as done.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D6231?vs=14720&id=14744

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

AFFECTED FILES
  rust/hg-core/src/discovery.rs
  rust/hg-core/src/lib.rs

CHANGE DETAILS

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
@@ -6,6 +6,7 @@
 pub mod dagops;
 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
 pub mod testing;  // unconditionally built, for use from integration tests
+pub mod discovery;
 
 /// Mercurial revision numbers
 ///
diff --git a/rust/hg-core/src/discovery.rs b/rust/hg-core/src/discovery.rs
new file mode 100644
--- /dev/null
+++ b/rust/hg-core/src/discovery.rs
@@ -0,0 +1,189 @@
+// discovery.rs
+//
+// Copyright 2019 Georges Racinet 
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+
+//! Discovery operations
+//!
+//! This is a Rust counterpart to the `partialdiscovery` class of
+//! `mercurial.setdiscovery`
+
+use super::{Graph, GraphError, Revision};
+use crate::ancestors::MissingAncestors;
+use crate::dagops;
+use std::collections::HashSet;
+
+pub struct PartialDiscovery {
+target_heads: Option>,
+graph: G, // plays the role of self._repo
+common: MissingAncestors,
+undecided: Option>,
+missing: HashSet,
+}
+
+impl PartialDiscovery {
+/// Create a PartialDiscovery object, with the intent
+/// of comparing our `::` revset to the contents of another
+/// repo.
+///
+/// For now `target_heads` is passed as a vector, and will be used
+/// at the first call to `ensure_undecided()`.
+///
+/// If we want to make the signature more flexible,
+/// we'll have to make it a type argument of `PartialDiscovery` or a trait
+/// object since we'll keep it in the meanwhile
+pub fn new(graph: G, target_heads: Vec) -> Self {
+PartialDiscovery {
+undecided: None,
+target_heads: Some(target_heads),
+graph: graph.clone(),
+common: MissingAncestors::new(graph, vec![]),
+missing: HashSet::new(),
+}
+}
+
+/// Register revisions known as being common
+pub fn add_common_revisions(
+&mut self,
+common: impl IntoIterator,
+) -> Result<(), GraphError> {
+self.common.add_bases(common);
+if let Some(ref mut undecided) = self.undecided {
+self.common.remove_ancestors_from(undecided)?;
+}
+Ok(())
+}
+
+/// Register revisions known as being missing
+pub fn add_missing_revisions(
+&mut self,
+missing: impl IntoIterator,
+) -> Result<(), GraphError> {
+self.ensure_undecided()?;
+let range = dagops::range(
+&self.graph,
+missing,
+self.undecided.as_ref().unwrap().iter().cloned(),
+)?;
+let undecided_mut = self.undecided.as_mut().unwrap();
+for missrev in range {
+self.missing.insert(missrev);
+undecided_mut.remove(&missrev);
+}
+Ok(())
+}
+
+/// Do we have any information about the peer?
+pub fn has_info(&self) -> bool {
+self.common.has_bases()
+}
+
+/// Did we acquire full knowledge of our Revisions that the peer has?
+pub fn is_complete(&self) -> bool {
+self.undecided.as_ref().map_or(false, |s| s.is_empty())
+}
+
+/// Return the heads of the common set of revisions
+///
+/// This is the end goal of the discovery process seeks,
+/// once `self.is_complete()` returns `true`.
+pub fn common_heads(&self) -> Result, GraphError> {
+self.common.bases_heads()
+}
+
+/// Force first computation of `self.undecided`
+///
+/// After this, `self.undecided.as_ref()` and `.as_mut()` can be
+/// unwrapped to get workable immutable or mutable references without
+/// any panic.
+///
+/// This is an imperative call instead of an access with added lazyness
+/// to reduce easily the scope of mutable borrow for the caller,
+/// compared to undecided(&'a mut self) -> &'a… that would keep it
+/// as long as the resulting immutable one.
+fn ensure_undecided(&mut self) -> Result<(), GraphError> {
+if self.undecided.is_some() {
+return Ok(());
+}
+let tgt = self.target_heads.take().unwrap();
+self.undecided =
+Some(self.common.missing_ancestors(tgt)?.into_iter().collect());
+Ok(())
+}
+}
+
+#[cfg(test)]
+mod tests {
+use super::*;
+use crate::testing::SampleGraph;
+
+/// A PartialDiscovery as for pushing all the heads of `SampleGraph`
+fn full_disco() -> PartialDiscovery {
+PartialDiscovery::new(SampleGraph, vec![10, 11

D6231: rust-discovery: starting core implementation

2019-04-15 Thread kevincox (Kevin Cox)
kevincox added a comment.


  In https://phab.mercurial-scm.org/D6231#90756, @gracinet wrote:
  
  > Yes, that's what I had in mind. Not sure where that enum would be defined 
(hg-cpython or hg-core) but it doesn't matter so much.
  
  
  I would probably stick it in hg-cpython because it is a compromise for the 
python binding. For anyone using hg-core from rust I would hope that they 
directly use the structs.
  
  But yes, not to important either way.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

To: gracinet, #hg-reviewers, kevincox
Cc: durin42, kevincox, mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D6231: rust-discovery: starting core implementation

2019-04-15 Thread gracinet (Georges Racinet)
gracinet added a comment.


  > What I would probably do is still implement the Rust part with multiple 
structs but then wrap it in an enum for the python API.
  
  Yes, that's what I had in mind. Not sure where that enum would be defined 
(hg-cpython or hg-core) but it doesn't matter so much.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

To: gracinet, #hg-reviewers, kevincox
Cc: durin42, kevincox, mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D6231: rust-discovery: starting core implementation

2019-04-15 Thread kevincox (Kevin Cox)
kevincox added a comment.


  In https://phab.mercurial-scm.org/D6231#90744, @gracinet wrote:
  
  > That's an interesting idea. I expect it to solve my gripes with 
`ensure_undecided()` neatly. I'm not sure how it'd fare on the Python side, and 
probably wouldn't do it in the bindings to maintain compatibility, but I'll 
play with it a bit.
  
  
  Well the point is to force the caller to acknowledge the state change, but in 
python that isn't really possible because it is dynamically typed. What I would 
probably do is still implement the Rust part with multiple structs but then 
wrap it in an enum for the python API. Then each python method call turns into.
  
fn do_thing() {
  match self {
HasInfo(state) => state.do_thing()
other => unreachable!("Invalid state for do_thing(). Expected HasInfo 
got {:?}.", other);
  }
}
  
  And yes, this should allow you to avoid all of the `unwrap`ing since it would 
all be done at the API boundary.
  
  I think you could also propagate this to python by having the "state" 
transition methods return a new object. However because you can't ensure that 
the code drops the access to the previous object you would have to copy 
everything into the new object or ensure that you don't mutate shared state. 
The value is still lower here though because of the dynamic nature of python.
  
  > Maybe this could be a good topic for a follow-up ?
  
  Yeah, sounds reasonable.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

To: gracinet, #hg-reviewers, kevincox
Cc: durin42, kevincox, mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D6231: rust-discovery: starting core implementation

2019-04-15 Thread gracinet (Georges Racinet)
gracinet added a comment.


  @kevincox Thanks for the reviews !
  
  > It would probably be better to have 3 classes and when you apply new 
information each class transitions into the next one. This allows the type 
system to ensure that you are using the class correctly.
  
  That's an interesting idea. I expect it to solve my gripes with 
`ensure_undecided()` neatly. I'm not sure how it'd fare on the Python side, and 
probably wouldn't do it in the bindings to maintain compatibility, but I'll 
play with it a bit.
  Maybe this could be a good topic for a follow-up ?
  Note: I still have the various sampling methods to submit today or tomorrow, 
but I think they would play well with that idea.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

To: gracinet, #hg-reviewers, kevincox
Cc: durin42, kevincox, mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D6231: rust-discovery: starting core implementation

2019-04-14 Thread kevincox (Kevin Cox)
kevincox accepted this revision.
kevincox added a comment.


  I'm not a huge fan of the design of this class because it has a strong 
protocol to use correctly. It would probably be better to have 3 classes and 
when you apply new information each class transitions into the next one. This 
allows the type system to ensure that you are using the class correctly. 
However it appears that this is just re-implementing an API so improving that 
API is probably out of scope for the time being.
  
  Otherwise the implementation looks good.

INLINE COMMENTS

> discovery.rs:57
> +self.common
> +.remove_ancestors_from(self.undecided.as_mut().unwrap())?;
> +Ok(())

You can replace the early return by an `if let`. This also removed the 
`.unwrap()` which is always nice.

  if let Some(ref mut undecided) = self.undecided {
  self.common.remove_ancestors_from(undecided)?;
  }

> discovery.rs:73
> +let undecided_mut = self.undecided.as_mut().unwrap();
> +for missrev in range.into_iter() {
> +self.missing.insert(missrev);

I believe this `.into_iter()` is redundant because the `for` loop calls it for 
you.

> discovery.rs:93
> +/// This is the end goal of the discovery process seeks,
> +/// once `self.is_complete()` returns `true`.
> +pub fn common_heads(&self) -> Result, GraphError> {

This this imply that this method shouldn't be called until then? If so you 
should probably add a `debug_assert!(self.is_complete())`.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

To: gracinet, #hg-reviewers, kevincox
Cc: durin42, kevincox, mjpieters, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D6231: rust-discovery: starting core implementation

2019-04-12 Thread gracinet (Georges Racinet)
gracinet created this revision.
Herald added subscribers: mercurial-devel, mjpieters, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Once exposed to the Python side, this core object will avoid
  costly roundtrips with potentially big sets of revisions.
  
  This changeset implements the core logic of the object only, i.e.,
  manipulation of the missing, common and undefined set-like revision
  attributes.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6231

AFFECTED FILES
  rust/hg-core/src/discovery.rs
  rust/hg-core/src/lib.rs

CHANGE DETAILS

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
@@ -6,6 +6,7 @@
 pub mod dagops;
 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
 pub mod testing;  // unconditionally built, for use from integration tests
+pub mod discovery;
 
 /// Mercurial revision numbers
 ///
diff --git a/rust/hg-core/src/discovery.rs b/rust/hg-core/src/discovery.rs
new file mode 100644
--- /dev/null
+++ b/rust/hg-core/src/discovery.rs
@@ -0,0 +1,191 @@
+// discovery.rs
+//
+// Copyright 2019 Georges Racinet 
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+
+//! Discovery operations
+//!
+//! This is a Rust counterpart to the `partialdiscovery` class of
+//! `mercurial.setdiscovery`
+
+use super::{Graph, GraphError, Revision};
+use crate::ancestors::MissingAncestors;
+use crate::dagops;
+use std::collections::HashSet;
+
+pub struct PartialDiscovery {
+target_heads: Option>,
+graph: G, // plays the role of self._repo
+common: MissingAncestors,
+undecided: Option>,
+missing: HashSet,
+}
+
+impl PartialDiscovery {
+/// Create a PartialDiscovery object, with the intent
+/// of comparing our `::` revset to the contents of another
+/// repo.
+///
+/// For now `target_heads` is passed as a vector, and will be used
+/// at the first call to `ensure_undecided()`.
+///
+/// If we want to make the signature more flexible,
+/// we'll have to make it a type argument of `PartialDiscovery` or a trait
+/// object since we'll keep it in the meanwhile
+pub fn new(graph: G, target_heads: Vec) -> Self {
+PartialDiscovery {
+undecided: None,
+target_heads: Some(target_heads),
+graph: graph.clone(),
+common: MissingAncestors::new(graph, vec![]),
+missing: HashSet::new(),
+}
+}
+
+/// Register revisions known as being common
+pub fn add_common_revisions(
+&mut self,
+common: impl IntoIterator,
+) -> Result<(), GraphError> {
+self.common.add_bases(common);
+if self.undecided.is_none() {
+return Ok(());
+}
+self.common
+.remove_ancestors_from(self.undecided.as_mut().unwrap())?;
+Ok(())
+}
+
+/// Register revisions known as being missing
+pub fn add_missing_revisions(
+&mut self,
+missing: impl IntoIterator,
+) -> Result<(), GraphError> {
+self.ensure_undecided()?;
+let range = dagops::range(
+&self.graph,
+missing,
+self.undecided.as_ref().unwrap().iter().cloned(),
+)?;
+let undecided_mut = self.undecided.as_mut().unwrap();
+for missrev in range.into_iter() {
+self.missing.insert(missrev);
+undecided_mut.remove(&missrev);
+}
+Ok(())
+}
+
+/// Do we have any information about the peer?
+pub fn has_info(&self) -> bool {
+self.common.has_bases()
+}
+
+/// Did we acquire full knowledge of our Revisions that the peer has?
+pub fn is_complete(&self) -> bool {
+self.undecided.as_ref().map_or(false, |s| s.is_empty())
+}
+
+/// Return the heads of the common set of revisions
+///
+/// This is the end goal of the discovery process seeks,
+/// once `self.is_complete()` returns `true`.
+pub fn common_heads(&self) -> Result, GraphError> {
+self.common.bases_heads()
+}
+
+/// Force first computation of `self.undecided`
+///
+/// After this, `self.undecided.as_ref()` and `.as_mut()` can be
+/// unwrapped to get workable immutable or mutable references without
+/// any panic.
+///
+/// This is an imperative call instead of an access with added lazyness
+/// to reduce easily the scope of mutable borrow for the caller,
+/// compared to undecided(&'a mut self) -> &'a… that would keep it
+/// as long as the resulting immutable one.
+fn ensure_undecided(&mut self) -> Result<(), GraphError> {
+if self.undecided.is_some() {
+return Ok(());
+}
+let tgt = self.target_heads.take().unwrap();
+self.undecided =
+Some(self.common.missing_ances