@@ -16,16 +16,25 @@ pub trait Graph: Debug {
16
16
/// An error type.
17
17
type Error : std:: error:: Error ;
18
18
19
- /// Return whether or not `node` is an ancestor of `descendant`.
19
+ /// Return whether or not `node` is an ancestor of `descendant`. A node `X``
20
+ /// is said to be an "ancestor" of node `Y` if one of the following is true:
21
+ ///
22
+ /// - `X == Y`
23
+ /// - `X` is an immediate parent of `Y`.
24
+ /// - `X` is an ancestor of an immediate parent of `Y` (defined
25
+ /// recursively).
20
26
fn is_ancestor (
21
27
& self ,
22
28
ancestor : Self :: Node ,
23
29
descendant : Self :: Node ,
24
30
) -> Result < bool , Self :: Error > ;
25
31
26
32
/// Filter `nodes` to only include nodes that are not ancestors of any other
27
- /// node in `nodes`. This is not strictly necessary, but it makes the
28
- /// intermediate results more sensible.
33
+ /// node in `nodes`. This is not strictly necessary, but it improves
34
+ /// performance as some operations are linear in the size of the success
35
+ /// bounds, and it can make the intermediate results more sensible.
36
+ ///
37
+ /// This operation is called `heads` in e.g. Mercurial.
29
38
#[ instrument]
30
39
fn simplify_success_bounds (
31
40
& self ,
@@ -35,8 +44,11 @@ pub trait Graph: Debug {
35
44
}
36
45
37
46
/// Filter `nodes` to only include nodes that are not descendants of any
38
- /// other node in `nodes`. This is not strictly necessary, but it makes the
39
- /// intermediate results more sensible.
47
+ /// other node in `nodes`. This is not strictly necessary, but it improves
48
+ /// performance as some operations are linear in the size of the failure
49
+ /// bounds, and it can make the intermediate results more sensible.
50
+ ///
51
+ /// This operation is called `roots` in e.g. Mercurial.
40
52
#[ instrument]
41
53
fn simplify_failure_bounds (
42
54
& self ,
@@ -102,6 +114,10 @@ pub trait Strategy<G: Graph>: Debug {
102
114
/// For example, linear search would return a node immediately "after"
103
115
/// the node(s) in `success_bounds`, while binary search would return the
104
116
/// node in the middle of `success_bounds` and `failure_bounds`.`
117
+ ///
118
+ /// NOTE: This must not return a value that has already been included in the
119
+ /// success or failure bounds, since then you would search it again in a
120
+ /// loop indefinitely. In that case, you must return `None` instead.
105
121
fn midpoint (
106
122
& self ,
107
123
graph : & G ,
@@ -150,7 +166,10 @@ pub struct EagerSolution<Node: Debug + Hash + Eq> {
150
166
151
167
#[ allow( missing_docs) ]
152
168
#[ derive( Debug , thiserror:: Error ) ]
153
- pub enum SearchError < TGraphError , TStrategyError > {
169
+ pub enum SearchError < TNode , TGraphError , TStrategyError > {
170
+ #[ error( "node {node:?} has already been classified as a {status:?} node, but was returned as a new midpoint to search; this would loop indefinitely" ) ]
171
+ AlreadySearchedMidpoint { node : TNode , status : Status } ,
172
+
154
173
#[ error( transparent) ]
155
174
Graph ( TGraphError ) ,
156
175
@@ -247,8 +266,8 @@ impl<G: Graph> Search<G> {
247
266
& ' a self ,
248
267
strategy : & ' a S ,
249
268
) -> Result <
250
- LazySolution < G :: Node , SearchError < G :: Error , S :: Error > > ,
251
- SearchError < G :: Error , S :: Error > ,
269
+ LazySolution < G :: Node , SearchError < G :: Node , G :: Error , S :: Error > > ,
270
+ SearchError < G :: Node , G :: Error , S :: Error > ,
252
271
> {
253
272
let success_bounds = self . success_bounds ( ) . map_err ( SearchError :: Graph ) ?;
254
273
let failure_bounds = self . failure_bounds ( ) . map_err ( SearchError :: Graph ) ?;
@@ -268,7 +287,7 @@ impl<G: Graph> Search<G> {
268
287
}
269
288
270
289
impl < ' a , G : Graph , S : Strategy < G > > Iterator for Iter < ' a , G , S > {
271
- type Item = Result < G :: Node , SearchError < G :: Error , S :: Error > > ;
290
+ type Item = Result < G :: Node , SearchError < G :: Node , G :: Error , S :: Error > > ;
272
291
273
292
fn next ( & mut self ) -> Option < Self :: Item > {
274
293
while let Some ( state) = self . states . pop_front ( ) {
@@ -290,6 +309,31 @@ impl<G: Graph> Search<G> {
290
309
Err ( err) => return Some ( Err ( SearchError :: Strategy ( err) ) ) ,
291
310
} ;
292
311
312
+ for success_node in success_bounds. iter ( ) {
313
+ match self . graph . is_ancestor ( node. clone ( ) , success_node. clone ( ) ) {
314
+ Ok ( true ) => {
315
+ return Some ( Err ( SearchError :: AlreadySearchedMidpoint {
316
+ node,
317
+ status : Status :: Success ,
318
+ } ) ) ;
319
+ }
320
+ Ok ( false ) => ( ) ,
321
+ Err ( err) => return Some ( Err ( SearchError :: Graph ( err) ) ) ,
322
+ }
323
+ }
324
+ for failure_node in failure_bounds. iter ( ) {
325
+ match self . graph . is_ancestor ( failure_node. clone ( ) , node. clone ( ) ) {
326
+ Ok ( true ) => {
327
+ return Some ( Err ( SearchError :: AlreadySearchedMidpoint {
328
+ node,
329
+ status : Status :: Failure ,
330
+ } ) ) ;
331
+ }
332
+ Ok ( false ) => ( ) ,
333
+ Err ( err) => return Some ( Err ( SearchError :: Graph ( err) ) ) ,
334
+ }
335
+ }
336
+
293
337
// Speculate failure:
294
338
self . states . push_back ( State {
295
339
success_bounds : success_bounds. clone ( ) ,
0 commit comments