##// END OF EJS Templates
ancestors: ensure a consistent order even in the "inclusive" case...
Boris Feld -
r39510:a60dae06 default
parent child Browse files
Show More
@@ -1,380 +1,379 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 262 class lazyancestors(object):
263 263 def __init__(self, pfunc, revs, stoprev=0, inclusive=False):
264 264 """Create a new object generating ancestors for the given revs. Does
265 265 not generate revs lower than stoprev.
266 266
267 267 This is computed lazily starting from revs. The object supports
268 268 iteration and membership.
269 269
270 270 cl should be a changelog and revs should be an iterable. inclusive is
271 271 a boolean that indicates whether revs should be included. Revs lower
272 272 than stoprev will not be generated.
273 273
274 274 Result does not include the null revision."""
275 275 self._parentrevs = pfunc
276 276 self._initrevs = revs
277 277 self._stoprev = stoprev
278 278 self._inclusive = inclusive
279 279
280 280 # Initialize data structures for __contains__.
281 281 # For __contains__, we use a heap rather than a deque because
282 282 # (a) it minimizes the number of parentrevs calls made
283 283 # (b) it makes the loop termination condition obvious
284 284 # Python's heap is a min-heap. Multiply all values by -1 to convert it
285 285 # into a max-heap.
286 286 self._containsvisit = [-rev for rev in revs]
287 287 heapq.heapify(self._containsvisit)
288 288 if inclusive:
289 289 self._containsseen = set(revs)
290 290 else:
291 291 self._containsseen = set()
292 292
293 293 def __nonzero__(self):
294 294 """False if the set is empty, True otherwise."""
295 295 try:
296 296 next(iter(self))
297 297 return True
298 298 except StopIteration:
299 299 return False
300 300
301 301 __bool__ = __nonzero__
302 302
303 303 def __iter__(self):
304 304 """Generate the ancestors of _initrevs in reverse topological order.
305 305
306 306 If inclusive is False, yield a sequence of revision numbers starting
307 307 with the parents of each revision in revs, i.e., each revision is
308 308 *not* considered an ancestor of itself. Results are emitted in reverse
309 309 revision number order. That order is also topological: a child is
310 310 always emitted before its parent.
311 311
312 If inclusive is True, yield all the revs first (ignoring stoprev),
313 then yield all the ancestors of revs as when inclusive is False. If an
314 element in revs is an ancestor of a different rev it is not yielded
315 again."""
312 If inclusive is True, the source revisions are also yielded. The
313 reverse revision number order is still enforced."""
316 314 seen = set()
317 315 revs = self._initrevs
318 if self._inclusive:
319 for rev in revs:
320 yield rev
321 seen.update(revs)
322 316
323 317 parentrevs = self._parentrevs
324 318 stoprev = self._stoprev
325 visit = []
326 heapq.heapify(visit)
327 319 schedule = heapq.heappush
328 320 nextitem = heapq.heappop
329 321 see = seen.add
330 322 see(nullrev)
331 323
324 if self._inclusive:
325 visit = [-r for r in revs]
326 seen.update(revs)
327 heapq.heapify(visit)
328 else:
329 visit = []
330 heapq.heapify(visit)
332 331 for r in revs:
333 332 for parent in parentrevs(r):
334 333 if parent not in seen:
335 334 schedule(visit, -parent)
336 335 see(parent)
337 336
338 337 while visit:
339 338 current = -nextitem(visit)
340 339 if current >= stoprev:
341 340 yield current
342 341 for parent in parentrevs(current):
343 342 if parent not in seen:
344 343 schedule(visit, -parent)
345 344 see(parent)
346 345
347 346 def __contains__(self, target):
348 347 """Test whether target is an ancestor of self._initrevs."""
349 348 # Trying to do both __iter__ and __contains__ using the same visit
350 349 # heap and seen set is complex enough that it slows down both. Keep
351 350 # them separate.
352 351 seen = self._containsseen
353 352 if target in seen:
354 353 return True
355 354 # Only integer target is valid, but some callers expect 'None in self'
356 355 # to be False. So we explicitly allow it.
357 356 if target is None:
358 357 return False
359 358
360 359 parentrevs = self._parentrevs
361 360 visit = self._containsvisit
362 361 stoprev = self._stoprev
363 362 heappop = heapq.heappop
364 363 heappush = heapq.heappush
365 364 see = seen.add
366 365
367 366 targetseen = False
368 367
369 368 while visit and -visit[0] > target and not targetseen:
370 369 for parent in parentrevs(-heappop(visit)):
371 370 if parent < stoprev or parent in seen:
372 371 continue
373 372 # We need to make sure we push all parents into the heap so
374 373 # that we leave it in a consistent state for future calls.
375 374 heappush(visit, -parent)
376 375 see(parent)
377 376 if parent == target:
378 377 targetseen = True
379 378
380 379 return targetseen
@@ -1,18 +1,18 b''
1 1 % lazy ancestor set for [], stoprev = 0, inclusive = False
2 2 membership: []
3 3 iteration: []
4 4 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = False
5 5 membership: [7, 8, 3, 4, 1, 0]
6 6 iteration: [8, 7, 4, 3, 2, 1, 0]
7 7 % lazy ancestor set for [1, 3], stoprev = 0, inclusive = False
8 8 membership: [1, 0]
9 9 iteration: [1, 0]
10 10 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = True
11 11 membership: [11, 13, 7, 8, 3, 4, 1, 0]
12 iteration: [11, 13, 8, 7, 4, 3, 2, 1, 0]
12 iteration: [13, 11, 8, 7, 4, 3, 2, 1, 0]
13 13 % lazy ancestor set for [11, 13], stoprev = 6, inclusive = False
14 14 membership: [7, 8]
15 15 iteration: [8, 7]
16 16 % lazy ancestor set for [11, 13], stoprev = 6, inclusive = True
17 17 membership: [11, 13, 7, 8]
18 iteration: [11, 13, 8, 7]
18 iteration: [13, 11, 8, 7]
@@ -1,19 +1,19 b''
1 1 Ancestors of 5
2 2 4 2 0
3 3 Ancestors of 6 and 5
4 4 4 3 2 1 0
5 5 Ancestors of 5 and 4
6 6 4 2 0
7 7 Ancestors of 7, stop at 6
8 8 6
9 9 Ancestors of 7, including revs
10 10 7 6 5 4 3 2 1 0
11 11 Ancestors of 7, 5 and 3, including revs
12 7 5 3 6 4 2 1 0
12 7 6 5 4 3 2 1 0
13 13
14 14 Descendants of 5
15 15 7 8
16 16 Descendants of 5 and 3
17 17 6 7 8
18 18 Descendants of 5 and 4
19 19 5 7 8
General Comments 0
You need to be logged in to leave comments. Login now