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