##// END OF EJS Templates
ancestor: filter out initial revisions lower than stoprev
Yuya Nishihara -
r39512:7eadc940 default
parent child Browse files
Show More
@@ -1,379 +1,379
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 self._initrevs = revs
276 self._initrevs = revs = [r for r in revs if r >= stoprev]
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 312 If inclusive is True, the source revisions are also yielded. The
313 313 reverse revision number order is still enforced."""
314 314 seen = set()
315 315 revs = self._initrevs
316 316
317 317 parentrevs = self._parentrevs
318 318 stoprev = self._stoprev
319 319 schedule = heapq.heappush
320 320 nextitem = heapq.heappop
321 321 see = seen.add
322 322 see(nullrev)
323 323
324 324 if self._inclusive:
325 325 visit = [-r for r in revs]
326 326 seen.update(revs)
327 327 heapq.heapify(visit)
328 328 else:
329 329 visit = []
330 330 heapq.heapify(visit)
331 331 for r in revs:
332 332 for parent in parentrevs(r):
333 333 if parent not in seen:
334 334 schedule(visit, -parent)
335 335 see(parent)
336 336
337 337 while visit:
338 338 current = -nextitem(visit)
339 339 if current >= stoprev:
340 340 yield current
341 341 for parent in parentrevs(current):
342 342 if parent not in seen:
343 343 schedule(visit, -parent)
344 344 see(parent)
345 345
346 346 def __contains__(self, target):
347 347 """Test whether target is an ancestor of self._initrevs."""
348 348 # Trying to do both __iter__ and __contains__ using the same visit
349 349 # heap and seen set is complex enough that it slows down both. Keep
350 350 # them separate.
351 351 seen = self._containsseen
352 352 if target in seen:
353 353 return True
354 354 # Only integer target is valid, but some callers expect 'None in self'
355 355 # to be False. So we explicitly allow it.
356 356 if target is None:
357 357 return False
358 358
359 359 parentrevs = self._parentrevs
360 360 visit = self._containsvisit
361 361 stoprev = self._stoprev
362 362 heappop = heapq.heappop
363 363 heappush = heapq.heappush
364 364 see = seen.add
365 365
366 366 targetseen = False
367 367
368 368 while visit and -visit[0] > target and not targetseen:
369 369 for parent in parentrevs(-heappop(visit)):
370 370 if parent < stoprev or parent in seen:
371 371 continue
372 372 # We need to make sure we push all parents into the heap so
373 373 # that we leave it in a consistent state for future calls.
374 374 heappush(visit, -parent)
375 375 see(parent)
376 376 if parent == target:
377 377 targetseen = True
378 378
379 379 return targetseen
@@ -1,24 +1,24
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 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 18 iteration: [13, 11, 8, 7]
19 19 % lazy ancestor set for [11, 13], stoprev = 11, inclusive = True
20 20 membership: [11, 13]
21 21 iteration: [13, 11]
22 22 % lazy ancestor set for [11, 13], stoprev = 12, inclusive = True
23 membership: [11, 13]
23 membership: [13]
24 24 iteration: [13]
General Comments 0
You need to be logged in to leave comments. Login now