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