##// END OF EJS Templates
lazyancestors: extract __iter__ to free function...
Martin von Zweigbergk -
r39517:b6a0e06b default
parent child Browse files
Show More
@@ -1,378 +1,382 b''
1 1 # ancestor.py - generic DAG ancestor algorithm for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
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 from __future__ import absolute_import
9 9
10 10 import heapq
11 11
12 12 from .node import nullrev
13 13 from . import (
14 14 pycompat,
15 15 )
16 16
17 17 def commonancestorsheads(pfunc, *nodes):
18 18 """Returns a set with the heads of all common ancestors of all nodes,
19 19 heads(::nodes[0] and ::nodes[1] and ...) .
20 20
21 21 pfunc must return a list of parent vertices for a given vertex.
22 22 """
23 23 if not isinstance(nodes, set):
24 24 nodes = set(nodes)
25 25 if nullrev in nodes:
26 26 return set()
27 27 if len(nodes) <= 1:
28 28 return nodes
29 29
30 30 allseen = (1 << len(nodes)) - 1
31 31 seen = [0] * (max(nodes) + 1)
32 32 for i, n in enumerate(nodes):
33 33 seen[n] = 1 << i
34 34 poison = 1 << (i + 1)
35 35
36 36 gca = set()
37 37 interesting = len(nodes)
38 38 nv = len(seen) - 1
39 39 while nv >= 0 and interesting:
40 40 v = nv
41 41 nv -= 1
42 42 if not seen[v]:
43 43 continue
44 44 sv = seen[v]
45 45 if sv < poison:
46 46 interesting -= 1
47 47 if sv == allseen:
48 48 gca.add(v)
49 49 sv |= poison
50 50 if v in nodes:
51 51 # history is linear
52 52 return {v}
53 53 if sv < poison:
54 54 for p in pfunc(v):
55 55 sp = seen[p]
56 56 if p == nullrev:
57 57 continue
58 58 if sp == 0:
59 59 seen[p] = sv
60 60 interesting += 1
61 61 elif sp != sv:
62 62 seen[p] |= sv
63 63 else:
64 64 for p in pfunc(v):
65 65 if p == nullrev:
66 66 continue
67 67 sp = seen[p]
68 68 if sp and sp < poison:
69 69 interesting -= 1
70 70 seen[p] = sv
71 71 return gca
72 72
73 73 def ancestors(pfunc, *orignodes):
74 74 """
75 75 Returns the common ancestors of a and b that are furthest from a
76 76 root (as measured by longest path).
77 77
78 78 pfunc must return a list of parent vertices for a given vertex.
79 79 """
80 80 def deepest(nodes):
81 81 interesting = {}
82 82 count = max(nodes) + 1
83 83 depth = [0] * count
84 84 seen = [0] * count
85 85 mapping = []
86 86 for (i, n) in enumerate(sorted(nodes)):
87 87 depth[n] = 1
88 88 b = 1 << i
89 89 seen[n] = b
90 90 interesting[b] = 1
91 91 mapping.append((b, n))
92 92 nv = count - 1
93 93 while nv >= 0 and len(interesting) > 1:
94 94 v = nv
95 95 nv -= 1
96 96 dv = depth[v]
97 97 if dv == 0:
98 98 continue
99 99 sv = seen[v]
100 100 for p in pfunc(v):
101 101 if p == nullrev:
102 102 continue
103 103 dp = depth[p]
104 104 nsp = sp = seen[p]
105 105 if dp <= dv:
106 106 depth[p] = dv + 1
107 107 if sp != sv:
108 108 interesting[sv] += 1
109 109 nsp = seen[p] = sv
110 110 if sp:
111 111 interesting[sp] -= 1
112 112 if interesting[sp] == 0:
113 113 del interesting[sp]
114 114 elif dv == dp - 1:
115 115 nsp = sp | sv
116 116 if nsp == sp:
117 117 continue
118 118 seen[p] = nsp
119 119 interesting.setdefault(nsp, 0)
120 120 interesting[nsp] += 1
121 121 interesting[sp] -= 1
122 122 if interesting[sp] == 0:
123 123 del interesting[sp]
124 124 interesting[sv] -= 1
125 125 if interesting[sv] == 0:
126 126 del interesting[sv]
127 127
128 128 if len(interesting) != 1:
129 129 return []
130 130
131 131 k = 0
132 132 for i in interesting:
133 133 k |= i
134 134 return set(n for (i, n) in mapping if k & i)
135 135
136 136 gca = commonancestorsheads(pfunc, *orignodes)
137 137
138 138 if len(gca) <= 1:
139 139 return gca
140 140 return deepest(gca)
141 141
142 142 class incrementalmissingancestors(object):
143 143 '''persistent state used to calculate missing ancestors incrementally
144 144
145 145 Although similar in spirit to lazyancestors below, this is a separate class
146 146 because trying to support contains and missingancestors operations with the
147 147 same internal data structures adds needless complexity.'''
148 148 def __init__(self, pfunc, bases):
149 149 self.bases = set(bases)
150 150 if not self.bases:
151 151 self.bases.add(nullrev)
152 152 self.pfunc = pfunc
153 153
154 154 def hasbases(self):
155 155 '''whether the common set has any non-trivial bases'''
156 156 return self.bases and self.bases != {nullrev}
157 157
158 158 def addbases(self, newbases):
159 159 '''grow the ancestor set by adding new bases'''
160 160 self.bases.update(newbases)
161 161
162 162 def removeancestorsfrom(self, revs):
163 163 '''remove all ancestors of bases from the set revs (in place)'''
164 164 bases = self.bases
165 165 pfunc = self.pfunc
166 166 revs.difference_update(bases)
167 167 # nullrev is always an ancestor
168 168 revs.discard(nullrev)
169 169 if not revs:
170 170 return
171 171 # anything in revs > start is definitely not an ancestor of bases
172 172 # revs <= start needs to be investigated
173 173 start = max(bases)
174 174 keepcount = sum(1 for r in revs if r > start)
175 175 if len(revs) == keepcount:
176 176 # no revs to consider
177 177 return
178 178
179 179 for curr in pycompat.xrange(start, min(revs) - 1, -1):
180 180 if curr not in bases:
181 181 continue
182 182 revs.discard(curr)
183 183 bases.update(pfunc(curr))
184 184 if len(revs) == keepcount:
185 185 # no more potential revs to discard
186 186 break
187 187
188 188 def missingancestors(self, revs):
189 189 '''return all the ancestors of revs that are not ancestors of self.bases
190 190
191 191 This may include elements from revs.
192 192
193 193 Equivalent to the revset (::revs - ::self.bases). Revs are returned in
194 194 revision number order, which is a topological order.'''
195 195 revsvisit = set(revs)
196 196 basesvisit = self.bases
197 197 pfunc = self.pfunc
198 198 bothvisit = revsvisit.intersection(basesvisit)
199 199 revsvisit.difference_update(bothvisit)
200 200 if not revsvisit:
201 201 return []
202 202
203 203 start = max(max(revsvisit), max(basesvisit))
204 204 # At this point, we hold the invariants that:
205 205 # - revsvisit is the set of nodes we know are an ancestor of at least
206 206 # one of the nodes in revs
207 207 # - basesvisit is the same for bases
208 208 # - bothvisit is the set of nodes we know are ancestors of at least one
209 209 # of the nodes in revs and one of the nodes in bases. bothvisit and
210 210 # revsvisit are mutually exclusive, but bothvisit is a subset of
211 211 # basesvisit.
212 212 # Now we walk down in reverse topo order, adding parents of nodes
213 213 # already visited to the sets while maintaining the invariants. When a
214 214 # node is found in both revsvisit and basesvisit, it is removed from
215 215 # revsvisit and added to bothvisit. When revsvisit becomes empty, there
216 216 # are no more ancestors of revs that aren't also ancestors of bases, so
217 217 # exit.
218 218
219 219 missing = []
220 220 for curr in pycompat.xrange(start, nullrev, -1):
221 221 if not revsvisit:
222 222 break
223 223
224 224 if curr in bothvisit:
225 225 bothvisit.remove(curr)
226 226 # curr's parents might have made it into revsvisit through
227 227 # another path
228 228 for p in pfunc(curr):
229 229 revsvisit.discard(p)
230 230 basesvisit.add(p)
231 231 bothvisit.add(p)
232 232 continue
233 233
234 234 if curr in revsvisit:
235 235 missing.append(curr)
236 236 revsvisit.remove(curr)
237 237 thisvisit = revsvisit
238 238 othervisit = basesvisit
239 239 elif curr in basesvisit:
240 240 thisvisit = basesvisit
241 241 othervisit = revsvisit
242 242 else:
243 243 # not an ancestor of revs or bases: ignore
244 244 continue
245 245
246 246 for p in pfunc(curr):
247 247 if p == nullrev:
248 248 pass
249 249 elif p in othervisit or p in bothvisit:
250 250 # p is implicitly in thisvisit. This means p is or should be
251 251 # in bothvisit
252 252 revsvisit.discard(p)
253 253 basesvisit.add(p)
254 254 bothvisit.add(p)
255 255 else:
256 256 # visit later
257 257 thisvisit.add(p)
258 258
259 259 missing.reverse()
260 260 return missing
261 261
262 # Extracted from lazyancestors.__iter__ to avoid a reference cycle
263 def _lazyancestorsiter(parentrevs, initrevs, stoprev, inclusive):
264 seen = {nullrev}
265 revs = initrevs
266
267 schedule = heapq.heappush
268 nextitem = heapq.heappop
269 see = seen.add
270
271 if inclusive:
272 visit = [-r for r in revs]
273 seen.update(revs)
274 heapq.heapify(visit)
275 else:
276 visit = []
277 heapq.heapify(visit)
278 for r in revs:
279 for parent in parentrevs(r):
280 if parent not in seen:
281 schedule(visit, -parent)
282 see(parent)
283
284 while visit:
285 current = -nextitem(visit)
286 if current >= stoprev:
287 yield current
288 for parent in parentrevs(current):
289 if parent not in seen:
290 schedule(visit, -parent)
291 see(parent)
292
262 293 class lazyancestors(object):
263 294 def __init__(self, pfunc, revs, stoprev=0, inclusive=False):
264 295 """Create a new object generating ancestors for the given revs. Does
265 296 not generate revs lower than stoprev.
266 297
267 298 This is computed lazily starting from revs. The object supports
268 299 iteration and membership.
269 300
270 301 cl should be a changelog and revs should be an iterable. inclusive is
271 302 a boolean that indicates whether revs should be included. Revs lower
272 303 than stoprev will not be generated.
273 304
274 305 Result does not include the null revision."""
275 306 self._parentrevs = pfunc
276 307 self._initrevs = revs = [r for r in revs if r >= stoprev]
277 308 self._stoprev = stoprev
278 309 self._inclusive = inclusive
279 310
280 311 # Initialize data structures for __contains__.
281 312 # For __contains__, we use a heap rather than a deque because
282 313 # (a) it minimizes the number of parentrevs calls made
283 314 # (b) it makes the loop termination condition obvious
284 315 # Python's heap is a min-heap. Multiply all values by -1 to convert it
285 316 # into a max-heap.
286 317 self._containsvisit = [-rev for rev in revs]
287 318 heapq.heapify(self._containsvisit)
288 319 if inclusive:
289 320 self._containsseen = set(revs)
290 321 else:
291 322 self._containsseen = set()
292 323
293 324 def __nonzero__(self):
294 325 """False if the set is empty, True otherwise."""
295 326 try:
296 327 next(iter(self))
297 328 return True
298 329 except StopIteration:
299 330 return False
300 331
301 332 __bool__ = __nonzero__
302 333
303 334 def __iter__(self):
304 335 """Generate the ancestors of _initrevs in reverse topological order.
305 336
306 337 If inclusive is False, yield a sequence of revision numbers starting
307 338 with the parents of each revision in revs, i.e., each revision is
308 339 *not* considered an ancestor of itself. Results are emitted in reverse
309 340 revision number order. That order is also topological: a child is
310 341 always emitted before its parent.
311 342
312 343 If inclusive is True, the source revisions are also yielded. The
313 344 reverse revision number order is still enforced."""
314 seen = {nullrev}
315 revs = self._initrevs
316
317 parentrevs = self._parentrevs
318 stoprev = self._stoprev
319 schedule = heapq.heappush
320 nextitem = heapq.heappop
321 see = seen.add
322
323 if self._inclusive:
324 visit = [-r for r in revs]
325 seen.update(revs)
326 heapq.heapify(visit)
327 else:
328 visit = []
329 heapq.heapify(visit)
330 for r in revs:
331 for parent in parentrevs(r):
332 if parent not in seen:
333 schedule(visit, -parent)
334 see(parent)
335
336 while visit:
337 current = -nextitem(visit)
338 if current >= stoprev:
339 yield current
340 for parent in parentrevs(current):
341 if parent not in seen:
342 schedule(visit, -parent)
343 see(parent)
345 for rev in _lazyancestorsiter(self._parentrevs, self._initrevs,
346 self._stoprev, self._inclusive):
347 yield rev
344 348
345 349 def __contains__(self, target):
346 350 """Test whether target is an ancestor of self._initrevs."""
347 351 # Trying to do both __iter__ and __contains__ using the same visit
348 352 # heap and seen set is complex enough that it slows down both. Keep
349 353 # them separate.
350 354 seen = self._containsseen
351 355 if target in seen:
352 356 return True
353 357 # Only integer target is valid, but some callers expect 'None in self'
354 358 # to be False. So we explicitly allow it.
355 359 if target is None:
356 360 return False
357 361
358 362 parentrevs = self._parentrevs
359 363 visit = self._containsvisit
360 364 stoprev = self._stoprev
361 365 heappop = heapq.heappop
362 366 heappush = heapq.heappush
363 367 see = seen.add
364 368
365 369 targetseen = False
366 370
367 371 while visit and -visit[0] > target and not targetseen:
368 372 for parent in parentrevs(-heappop(visit)):
369 373 if parent < stoprev or parent in seen:
370 374 continue
371 375 # We need to make sure we push all parents into the heap so
372 376 # that we leave it in a consistent state for future calls.
373 377 heappush(visit, -parent)
374 378 see(parent)
375 379 if parent == target:
376 380 targetseen = True
377 381
378 382 return targetseen
General Comments 0
You need to be logged in to leave comments. Login now