##// END OF EJS Templates
rust: rename local variables in AncestorsIterator::next...
Georges Racinet -
r40867:70976974 default
parent child Browse files
Show More
@@ -1,273 +1,272
1 // ancestors.rs
1 // ancestors.rs
2 //
2 //
3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Rust versions of generic DAG ancestors algorithms for Mercurial
8 //! Rust versions of generic DAG ancestors algorithms for Mercurial
9
9
10 use super::{Graph, GraphError, Revision, NULL_REVISION};
10 use super::{Graph, GraphError, Revision, NULL_REVISION};
11 use std::collections::{BinaryHeap, HashSet};
11 use std::collections::{BinaryHeap, HashSet};
12
12
13 /// Iterator over the ancestors of a given list of revisions
13 /// Iterator over the ancestors of a given list of revisions
14 /// This is a generic type, defined and implemented for any Graph, so that
14 /// This is a generic type, defined and implemented for any Graph, so that
15 /// it's easy to
15 /// it's easy to
16 ///
16 ///
17 /// - unit test in pure Rust
17 /// - unit test in pure Rust
18 /// - bind to main Mercurial code, potentially in several ways and have these
18 /// - bind to main Mercurial code, potentially in several ways and have these
19 /// bindings evolve over time
19 /// bindings evolve over time
20 pub struct AncestorsIterator<G: Graph> {
20 pub struct AncestorsIterator<G: Graph> {
21 graph: G,
21 graph: G,
22 visit: BinaryHeap<Revision>,
22 visit: BinaryHeap<Revision>,
23 seen: HashSet<Revision>,
23 seen: HashSet<Revision>,
24 stoprev: Revision,
24 stoprev: Revision,
25 }
25 }
26
26
27 impl<G: Graph> AncestorsIterator<G> {
27 impl<G: Graph> AncestorsIterator<G> {
28 /// Constructor.
28 /// Constructor.
29 ///
29 ///
30 /// if `inclusive` is true, then the init revisions are emitted in
30 /// if `inclusive` is true, then the init revisions are emitted in
31 /// particular, otherwise iteration starts from their parents.
31 /// particular, otherwise iteration starts from their parents.
32 pub fn new<I>(
32 pub fn new<I>(
33 graph: G,
33 graph: G,
34 initrevs: I,
34 initrevs: I,
35 stoprev: Revision,
35 stoprev: Revision,
36 inclusive: bool,
36 inclusive: bool,
37 ) -> Result<Self, GraphError>
37 ) -> Result<Self, GraphError>
38 where
38 where
39 I: IntoIterator<Item = Revision>,
39 I: IntoIterator<Item = Revision>,
40 {
40 {
41 let filtered_initrevs = initrevs.into_iter().filter(|&r| r >= stoprev);
41 let filtered_initrevs = initrevs.into_iter().filter(|&r| r >= stoprev);
42 if inclusive {
42 if inclusive {
43 let visit: BinaryHeap<Revision> = filtered_initrevs.collect();
43 let visit: BinaryHeap<Revision> = filtered_initrevs.collect();
44 let seen = visit.iter().map(|&x| x).collect();
44 let seen = visit.iter().map(|&x| x).collect();
45 return Ok(AncestorsIterator {
45 return Ok(AncestorsIterator {
46 visit: visit,
46 visit: visit,
47 seen: seen,
47 seen: seen,
48 stoprev: stoprev,
48 stoprev: stoprev,
49 graph: graph,
49 graph: graph,
50 });
50 });
51 }
51 }
52 let mut this = AncestorsIterator {
52 let mut this = AncestorsIterator {
53 visit: BinaryHeap::new(),
53 visit: BinaryHeap::new(),
54 seen: HashSet::new(),
54 seen: HashSet::new(),
55 stoprev: stoprev,
55 stoprev: stoprev,
56 graph: graph,
56 graph: graph,
57 };
57 };
58 this.seen.insert(NULL_REVISION);
58 this.seen.insert(NULL_REVISION);
59 for rev in filtered_initrevs {
59 for rev in filtered_initrevs {
60 let parents = this.graph.parents(rev)?;
60 let parents = this.graph.parents(rev)?;
61 this.conditionally_push_rev(parents.0);
61 this.conditionally_push_rev(parents.0);
62 this.conditionally_push_rev(parents.1);
62 this.conditionally_push_rev(parents.1);
63 }
63 }
64 Ok(this)
64 Ok(this)
65 }
65 }
66
66
67 #[inline]
67 #[inline]
68 fn conditionally_push_rev(&mut self, rev: Revision) {
68 fn conditionally_push_rev(&mut self, rev: Revision) {
69 if self.stoprev <= rev && !self.seen.contains(&rev) {
69 if self.stoprev <= rev && !self.seen.contains(&rev) {
70 self.seen.insert(rev);
70 self.seen.insert(rev);
71 self.visit.push(rev);
71 self.visit.push(rev);
72 }
72 }
73 }
73 }
74
74
75 /// Consumes partially the iterator to tell if the given target
75 /// Consumes partially the iterator to tell if the given target
76 /// revision
76 /// revision
77 /// is in the ancestors it emits.
77 /// is in the ancestors it emits.
78 /// This is meant for iterators actually dedicated to that kind of
78 /// This is meant for iterators actually dedicated to that kind of
79 /// purpose
79 /// purpose
80 pub fn contains(&mut self, target: Revision) -> bool {
80 pub fn contains(&mut self, target: Revision) -> bool {
81 if self.seen.contains(&target) && target != NULL_REVISION {
81 if self.seen.contains(&target) && target != NULL_REVISION {
82 return true;
82 return true;
83 }
83 }
84 for rev in self {
84 for rev in self {
85 if rev == target {
85 if rev == target {
86 return true;
86 return true;
87 }
87 }
88 if rev < target {
88 if rev < target {
89 return false;
89 return false;
90 }
90 }
91 }
91 }
92 false
92 false
93 }
93 }
94 }
94 }
95
95
96 /// Main implementation.
96 /// Main implementation.
97 ///
97 ///
98 /// The algorithm is the same as in `_lazyancestorsiter()` from `ancestors.py`
98 /// The algorithm is the same as in `_lazyancestorsiter()` from `ancestors.py`
99 /// with a few non crucial differences:
99 /// with a few non crucial differences:
100 ///
100 ///
101 /// - there's no filtering of invalid parent revisions. Actually, it should be
101 /// - there's no filtering of invalid parent revisions. Actually, it should be
102 /// consistent and more efficient to filter them from the end caller.
102 /// consistent and more efficient to filter them from the end caller.
103 /// - we don't have the optimization for adjacent revs
103 /// - we don't have the optimization for adjacent revs
104 /// (case where p1 == rev-1), because it amounts to update the first element
104 /// (case where p1 == rev-1), because it amounts to update the first element
105 /// of the heap without sifting, which Rust's BinaryHeap doesn't let us do.
105 /// of the heap without sifting, which Rust's BinaryHeap doesn't let us do.
106 /// - we save a few pushes by comparing with `stoprev` before pushing
106 /// - we save a few pushes by comparing with `stoprev` before pushing
107 ///
107 ///
108 /// Error treatment:
108 /// Error treatment:
109 /// We swallow the possible GraphError of conditionally_push_parents() to
109 /// We swallow the possible GraphError of conditionally_push_parents() to
110 /// respect the Iterator trait in a simple manner: never emitting parents
110 /// respect the Iterator trait in a simple manner: never emitting parents
111 /// for the returned revision. We finds this good enough for now, because:
111 /// for the returned revision. We finds this good enough for now, because:
112 ///
112 ///
113 /// - there's a good chance that invalid revisionss are fed from the start,
113 /// - there's a good chance that invalid revisionss are fed from the start,
114 /// and `new()` doesn't swallow the error result.
114 /// and `new()` doesn't swallow the error result.
115 /// - this is probably what the Python implementation produces anyway, due
115 /// - this is probably what the Python implementation produces anyway, due
116 /// to filtering at each step, and Python code is currently the only
116 /// to filtering at each step, and Python code is currently the only
117 /// concrete caller we target, so we shouldn't need a finer error treatment
117 /// concrete caller we target, so we shouldn't need a finer error treatment
118 /// for the time being.
118 /// for the time being.
119 impl<G: Graph> Iterator for AncestorsIterator<G> {
119 impl<G: Graph> Iterator for AncestorsIterator<G> {
120 type Item = Revision;
120 type Item = Revision;
121
121
122 fn next(&mut self) -> Option<Revision> {
122 fn next(&mut self) -> Option<Revision> {
123 let current = match self.visit.peek() {
123 let current = match self.visit.peek() {
124 None => {
124 None => {
125 return None;
125 return None;
126 }
126 }
127 Some(c) => *c,
127 Some(c) => *c,
128 };
128 };
129 let parents = self
129 let (p1, p2) = self
130 .graph
130 .graph
131 .parents(current)
131 .parents(current)
132 .unwrap_or((NULL_REVISION, NULL_REVISION));
132 .unwrap_or((NULL_REVISION, NULL_REVISION));
133 let p1 = parents.0;
134 if p1 < self.stoprev || self.seen.contains(&p1) {
133 if p1 < self.stoprev || self.seen.contains(&p1) {
135 self.visit.pop();
134 self.visit.pop();
136 } else {
135 } else {
137 *(self.visit.peek_mut().unwrap()) = p1;
136 *(self.visit.peek_mut().unwrap()) = p1;
138 self.seen.insert(p1);
137 self.seen.insert(p1);
139 };
138 };
140
139
141 self.conditionally_push_rev(parents.1);
140 self.conditionally_push_rev(p2);
142 Some(current)
141 Some(current)
143 }
142 }
144 }
143 }
145
144
146 #[cfg(test)]
145 #[cfg(test)]
147 mod tests {
146 mod tests {
148
147
149 use super::*;
148 use super::*;
150
149
151 #[derive(Clone, Debug)]
150 #[derive(Clone, Debug)]
152 struct Stub;
151 struct Stub;
153
152
154 /// This is the same as the dict from test-ancestors.py
153 /// This is the same as the dict from test-ancestors.py
155 impl Graph for Stub {
154 impl Graph for Stub {
156 fn parents(
155 fn parents(
157 &self,
156 &self,
158 rev: Revision,
157 rev: Revision,
159 ) -> Result<(Revision, Revision), GraphError> {
158 ) -> Result<(Revision, Revision), GraphError> {
160 match rev {
159 match rev {
161 0 => Ok((-1, -1)),
160 0 => Ok((-1, -1)),
162 1 => Ok((0, -1)),
161 1 => Ok((0, -1)),
163 2 => Ok((1, -1)),
162 2 => Ok((1, -1)),
164 3 => Ok((1, -1)),
163 3 => Ok((1, -1)),
165 4 => Ok((2, -1)),
164 4 => Ok((2, -1)),
166 5 => Ok((4, -1)),
165 5 => Ok((4, -1)),
167 6 => Ok((4, -1)),
166 6 => Ok((4, -1)),
168 7 => Ok((4, -1)),
167 7 => Ok((4, -1)),
169 8 => Ok((-1, -1)),
168 8 => Ok((-1, -1)),
170 9 => Ok((6, 7)),
169 9 => Ok((6, 7)),
171 10 => Ok((5, -1)),
170 10 => Ok((5, -1)),
172 11 => Ok((3, 7)),
171 11 => Ok((3, 7)),
173 12 => Ok((9, -1)),
172 12 => Ok((9, -1)),
174 13 => Ok((8, -1)),
173 13 => Ok((8, -1)),
175 r => Err(GraphError::ParentOutOfRange(r)),
174 r => Err(GraphError::ParentOutOfRange(r)),
176 }
175 }
177 }
176 }
178 }
177 }
179
178
180 fn list_ancestors<G: Graph>(
179 fn list_ancestors<G: Graph>(
181 graph: G,
180 graph: G,
182 initrevs: Vec<Revision>,
181 initrevs: Vec<Revision>,
183 stoprev: Revision,
182 stoprev: Revision,
184 inclusive: bool,
183 inclusive: bool,
185 ) -> Vec<Revision> {
184 ) -> Vec<Revision> {
186 AncestorsIterator::new(graph, initrevs, stoprev, inclusive)
185 AncestorsIterator::new(graph, initrevs, stoprev, inclusive)
187 .unwrap()
186 .unwrap()
188 .collect()
187 .collect()
189 }
188 }
190
189
191 #[test]
190 #[test]
192 /// Same tests as test-ancestor.py, without membership
191 /// Same tests as test-ancestor.py, without membership
193 /// (see also test-ancestor.py.out)
192 /// (see also test-ancestor.py.out)
194 fn test_list_ancestor() {
193 fn test_list_ancestor() {
195 assert_eq!(list_ancestors(Stub, vec![], 0, false), vec![]);
194 assert_eq!(list_ancestors(Stub, vec![], 0, false), vec![]);
196 assert_eq!(
195 assert_eq!(
197 list_ancestors(Stub, vec![11, 13], 0, false),
196 list_ancestors(Stub, vec![11, 13], 0, false),
198 vec![8, 7, 4, 3, 2, 1, 0]
197 vec![8, 7, 4, 3, 2, 1, 0]
199 );
198 );
200 assert_eq!(list_ancestors(Stub, vec![1, 3], 0, false), vec![1, 0]);
199 assert_eq!(list_ancestors(Stub, vec![1, 3], 0, false), vec![1, 0]);
201 assert_eq!(
200 assert_eq!(
202 list_ancestors(Stub, vec![11, 13], 0, true),
201 list_ancestors(Stub, vec![11, 13], 0, true),
203 vec![13, 11, 8, 7, 4, 3, 2, 1, 0]
202 vec![13, 11, 8, 7, 4, 3, 2, 1, 0]
204 );
203 );
205 assert_eq!(list_ancestors(Stub, vec![11, 13], 6, false), vec![8, 7]);
204 assert_eq!(list_ancestors(Stub, vec![11, 13], 6, false), vec![8, 7]);
206 assert_eq!(
205 assert_eq!(
207 list_ancestors(Stub, vec![11, 13], 6, true),
206 list_ancestors(Stub, vec![11, 13], 6, true),
208 vec![13, 11, 8, 7]
207 vec![13, 11, 8, 7]
209 );
208 );
210 assert_eq!(list_ancestors(Stub, vec![11, 13], 11, true), vec![13, 11]);
209 assert_eq!(list_ancestors(Stub, vec![11, 13], 11, true), vec![13, 11]);
211 assert_eq!(list_ancestors(Stub, vec![11, 13], 12, true), vec![13]);
210 assert_eq!(list_ancestors(Stub, vec![11, 13], 12, true), vec![13]);
212 assert_eq!(
211 assert_eq!(
213 list_ancestors(Stub, vec![10, 1], 0, true),
212 list_ancestors(Stub, vec![10, 1], 0, true),
214 vec![10, 5, 4, 2, 1, 0]
213 vec![10, 5, 4, 2, 1, 0]
215 );
214 );
216 }
215 }
217
216
218 #[test]
217 #[test]
219 /// Corner case that's not directly in test-ancestors.py, but
218 /// Corner case that's not directly in test-ancestors.py, but
220 /// that happens quite often, as demonstrated by running the whole
219 /// that happens quite often, as demonstrated by running the whole
221 /// suite.
220 /// suite.
222 /// For instance, run tests/test-obsolete-checkheads.t
221 /// For instance, run tests/test-obsolete-checkheads.t
223 fn test_nullrev_input() {
222 fn test_nullrev_input() {
224 let mut iter =
223 let mut iter =
225 AncestorsIterator::new(Stub, vec![-1], 0, false).unwrap();
224 AncestorsIterator::new(Stub, vec![-1], 0, false).unwrap();
226 assert_eq!(iter.next(), None)
225 assert_eq!(iter.next(), None)
227 }
226 }
228
227
229 #[test]
228 #[test]
230 fn test_contains() {
229 fn test_contains() {
231 let mut lazy =
230 let mut lazy =
232 AncestorsIterator::new(Stub, vec![10, 1], 0, true).unwrap();
231 AncestorsIterator::new(Stub, vec![10, 1], 0, true).unwrap();
233 assert!(lazy.contains(1));
232 assert!(lazy.contains(1));
234 assert!(!lazy.contains(3));
233 assert!(!lazy.contains(3));
235
234
236 let mut lazy =
235 let mut lazy =
237 AncestorsIterator::new(Stub, vec![0], 0, false).unwrap();
236 AncestorsIterator::new(Stub, vec![0], 0, false).unwrap();
238 assert!(!lazy.contains(NULL_REVISION));
237 assert!(!lazy.contains(NULL_REVISION));
239 }
238 }
240
239
241 /// A corrupted Graph, supporting error handling tests
240 /// A corrupted Graph, supporting error handling tests
242 struct Corrupted;
241 struct Corrupted;
243
242
244 impl Graph for Corrupted {
243 impl Graph for Corrupted {
245 fn parents(
244 fn parents(
246 &self,
245 &self,
247 rev: Revision,
246 rev: Revision,
248 ) -> Result<(Revision, Revision), GraphError> {
247 ) -> Result<(Revision, Revision), GraphError> {
249 match rev {
248 match rev {
250 1 => Ok((0, -1)),
249 1 => Ok((0, -1)),
251 r => Err(GraphError::ParentOutOfRange(r)),
250 r => Err(GraphError::ParentOutOfRange(r)),
252 }
251 }
253 }
252 }
254 }
253 }
255
254
256 #[test]
255 #[test]
257 fn test_initrev_out_of_range() {
256 fn test_initrev_out_of_range() {
258 // inclusive=false looks up initrev's parents right away
257 // inclusive=false looks up initrev's parents right away
259 match AncestorsIterator::new(Stub, vec![25], 0, false) {
258 match AncestorsIterator::new(Stub, vec![25], 0, false) {
260 Ok(_) => panic!("Should have been ParentOutOfRange"),
259 Ok(_) => panic!("Should have been ParentOutOfRange"),
261 Err(e) => assert_eq!(e, GraphError::ParentOutOfRange(25)),
260 Err(e) => assert_eq!(e, GraphError::ParentOutOfRange(25)),
262 }
261 }
263 }
262 }
264
263
265 #[test]
264 #[test]
266 fn test_next_out_of_range() {
265 fn test_next_out_of_range() {
267 // inclusive=false looks up initrev's parents right away
266 // inclusive=false looks up initrev's parents right away
268 let mut iter =
267 let mut iter =
269 AncestorsIterator::new(Corrupted, vec![1], 0, false).unwrap();
268 AncestorsIterator::new(Corrupted, vec![1], 0, false).unwrap();
270 assert_eq!(iter.next(), Some(0));
269 assert_eq!(iter.next(), Some(0));
271 assert_eq!(iter.next(), None);
270 assert_eq!(iter.next(), None);
272 }
271 }
273 }
272 }
General Comments 0
You need to be logged in to leave comments. Login now