##// END OF EJS Templates
dagop: change visit dict of filectxancestors() indexed solely by rev...
Yuya Nishihara -
r35275:2b348dc3 default
parent child Browse files
Show More
@@ -1,535 +1,544 b''
1 1 # dagop.py - graph ancestry and topology algorithm for revset
2 2 #
3 3 # Copyright 2010 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 . import (
13 13 error,
14 14 mdiff,
15 15 node,
16 16 patch,
17 17 smartset,
18 18 )
19 19
20 20 baseset = smartset.baseset
21 21 generatorset = smartset.generatorset
22 22
23 23 # possible maximum depth between null and wdir()
24 24 _maxlogdepth = 0x80000000
25 25
26 26 def _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse):
27 27 """Walk DAG using 'pfunc' from the given 'revs' nodes
28 28
29 29 'pfunc(rev)' should return the parent/child revisions of the given 'rev'
30 30 if 'reverse' is True/False respectively.
31 31
32 32 Scan ends at the stopdepth (exlusive) if specified. Revisions found
33 33 earlier than the startdepth are omitted.
34 34 """
35 35 if startdepth is None:
36 36 startdepth = 0
37 37 if stopdepth is None:
38 38 stopdepth = _maxlogdepth
39 39 if stopdepth == 0:
40 40 return
41 41 if stopdepth < 0:
42 42 raise error.ProgrammingError('negative stopdepth')
43 43 if reverse:
44 44 heapsign = -1 # max heap
45 45 else:
46 46 heapsign = +1 # min heap
47 47
48 48 # load input revs lazily to heap so earlier revisions can be yielded
49 49 # without fully computing the input revs
50 50 revs.sort(reverse)
51 51 irevs = iter(revs)
52 52 pendingheap = [] # [(heapsign * rev, depth), ...] (i.e. lower depth first)
53 53
54 54 inputrev = next(irevs, None)
55 55 if inputrev is not None:
56 56 heapq.heappush(pendingheap, (heapsign * inputrev, 0))
57 57
58 58 lastrev = None
59 59 while pendingheap:
60 60 currev, curdepth = heapq.heappop(pendingheap)
61 61 currev = heapsign * currev
62 62 if currev == inputrev:
63 63 inputrev = next(irevs, None)
64 64 if inputrev is not None:
65 65 heapq.heappush(pendingheap, (heapsign * inputrev, 0))
66 66 # rescan parents until curdepth >= startdepth because queued entries
67 67 # of the same revision are iterated from the lowest depth
68 68 foundnew = (currev != lastrev)
69 69 if foundnew and curdepth >= startdepth:
70 70 lastrev = currev
71 71 yield currev
72 72 pdepth = curdepth + 1
73 73 if foundnew and pdepth < stopdepth:
74 74 for prev in pfunc(currev):
75 75 if prev != node.nullrev:
76 76 heapq.heappush(pendingheap, (heapsign * prev, pdepth))
77 77
78 78 def filectxancestors(fctx, followfirst=False):
79 79 """Like filectx.ancestors(), but includes the given fctx itself"""
80 80 visit = {}
81 def addvisit(fctx):
82 rev = fctx.rev()
83 if rev not in visit:
84 visit[rev] = set()
85 visit[rev].add(fctx)
86
81 87 c = fctx
82 88 if followfirst:
83 89 cut = 1
84 90 else:
85 91 cut = None
86 92
87 93 yield c
88 94 while True:
89 95 for parent in c.parents()[:cut]:
90 visit[(parent.rev(), parent.filenode())] = parent
96 addvisit(parent)
91 97 if not visit:
92 98 break
93 c = visit.pop(max(visit))
99 rev = max(visit)
100 c = visit[rev].pop()
101 if not visit[rev]:
102 del visit[rev]
94 103 yield c
95 104
96 105 def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth, cutfunc):
97 106 if followfirst:
98 107 cut = 1
99 108 else:
100 109 cut = None
101 110 cl = repo.changelog
102 111 def plainpfunc(rev):
103 112 try:
104 113 return cl.parentrevs(rev)[:cut]
105 114 except error.WdirUnsupported:
106 115 return (pctx.rev() for pctx in repo[rev].parents()[:cut])
107 116 if cutfunc is None:
108 117 pfunc = plainpfunc
109 118 else:
110 119 pfunc = lambda rev: [r for r in plainpfunc(rev) if not cutfunc(r)]
111 120 revs = revs.filter(lambda rev: not cutfunc(rev))
112 121 return _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse=True)
113 122
114 123 def revancestors(repo, revs, followfirst=False, startdepth=None,
115 124 stopdepth=None, cutfunc=None):
116 125 """Like revlog.ancestors(), but supports additional options, includes
117 126 the given revs themselves, and returns a smartset
118 127
119 128 Scan ends at the stopdepth (exlusive) if specified. Revisions found
120 129 earlier than the startdepth are omitted.
121 130
122 131 If cutfunc is provided, it will be used to cut the traversal of the DAG.
123 132 When cutfunc(X) returns True, the DAG traversal stops - revision X and
124 133 X's ancestors in the traversal path will be skipped. This could be an
125 134 optimization sometimes.
126 135
127 136 Note: if Y is an ancestor of X, cutfunc(X) returning True does not
128 137 necessarily mean Y will also be cut. Usually cutfunc(Y) also wants to
129 138 return True in this case. For example,
130 139
131 140 D # revancestors(repo, D, cutfunc=lambda rev: rev == B)
132 141 |\ # will include "A", because the path D -> C -> A was not cut.
133 142 B C # If "B" gets cut, "A" might want to be cut too.
134 143 |/
135 144 A
136 145 """
137 146 gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth,
138 147 cutfunc)
139 148 return generatorset(gen, iterasc=False)
140 149
141 150 def _genrevdescendants(repo, revs, followfirst):
142 151 if followfirst:
143 152 cut = 1
144 153 else:
145 154 cut = None
146 155
147 156 cl = repo.changelog
148 157 first = revs.min()
149 158 nullrev = node.nullrev
150 159 if first == nullrev:
151 160 # Are there nodes with a null first parent and a non-null
152 161 # second one? Maybe. Do we care? Probably not.
153 162 yield first
154 163 for i in cl:
155 164 yield i
156 165 else:
157 166 seen = set(revs)
158 167 for i in cl.revs(first):
159 168 if i in seen:
160 169 yield i
161 170 continue
162 171 for x in cl.parentrevs(i)[:cut]:
163 172 if x != nullrev and x in seen:
164 173 seen.add(i)
165 174 yield i
166 175 break
167 176
168 177 def _builddescendantsmap(repo, startrev, followfirst):
169 178 """Build map of 'rev -> child revs', offset from startrev"""
170 179 cl = repo.changelog
171 180 nullrev = node.nullrev
172 181 descmap = [[] for _rev in xrange(startrev, len(cl))]
173 182 for currev in cl.revs(startrev + 1):
174 183 p1rev, p2rev = cl.parentrevs(currev)
175 184 if p1rev >= startrev:
176 185 descmap[p1rev - startrev].append(currev)
177 186 if not followfirst and p2rev != nullrev and p2rev >= startrev:
178 187 descmap[p2rev - startrev].append(currev)
179 188 return descmap
180 189
181 190 def _genrevdescendantsofdepth(repo, revs, followfirst, startdepth, stopdepth):
182 191 startrev = revs.min()
183 192 descmap = _builddescendantsmap(repo, startrev, followfirst)
184 193 def pfunc(rev):
185 194 return descmap[rev - startrev]
186 195 return _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse=False)
187 196
188 197 def revdescendants(repo, revs, followfirst, startdepth=None, stopdepth=None):
189 198 """Like revlog.descendants() but supports additional options, includes
190 199 the given revs themselves, and returns a smartset
191 200
192 201 Scan ends at the stopdepth (exlusive) if specified. Revisions found
193 202 earlier than the startdepth are omitted.
194 203 """
195 204 if startdepth is None and stopdepth is None:
196 205 gen = _genrevdescendants(repo, revs, followfirst)
197 206 else:
198 207 gen = _genrevdescendantsofdepth(repo, revs, followfirst,
199 208 startdepth, stopdepth)
200 209 return generatorset(gen, iterasc=True)
201 210
202 211 def _reachablerootspure(repo, minroot, roots, heads, includepath):
203 212 """return (heads(::<roots> and ::<heads>))
204 213
205 214 If includepath is True, return (<roots>::<heads>)."""
206 215 if not roots:
207 216 return []
208 217 parentrevs = repo.changelog.parentrevs
209 218 roots = set(roots)
210 219 visit = list(heads)
211 220 reachable = set()
212 221 seen = {}
213 222 # prefetch all the things! (because python is slow)
214 223 reached = reachable.add
215 224 dovisit = visit.append
216 225 nextvisit = visit.pop
217 226 # open-code the post-order traversal due to the tiny size of
218 227 # sys.getrecursionlimit()
219 228 while visit:
220 229 rev = nextvisit()
221 230 if rev in roots:
222 231 reached(rev)
223 232 if not includepath:
224 233 continue
225 234 parents = parentrevs(rev)
226 235 seen[rev] = parents
227 236 for parent in parents:
228 237 if parent >= minroot and parent not in seen:
229 238 dovisit(parent)
230 239 if not reachable:
231 240 return baseset()
232 241 if not includepath:
233 242 return reachable
234 243 for rev in sorted(seen):
235 244 for parent in seen[rev]:
236 245 if parent in reachable:
237 246 reached(rev)
238 247 return reachable
239 248
240 249 def reachableroots(repo, roots, heads, includepath=False):
241 250 """return (heads(::<roots> and ::<heads>))
242 251
243 252 If includepath is True, return (<roots>::<heads>)."""
244 253 if not roots:
245 254 return baseset()
246 255 minroot = roots.min()
247 256 roots = list(roots)
248 257 heads = list(heads)
249 258 try:
250 259 revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)
251 260 except AttributeError:
252 261 revs = _reachablerootspure(repo, minroot, roots, heads, includepath)
253 262 revs = baseset(revs)
254 263 revs.sort()
255 264 return revs
256 265
257 266 def _changesrange(fctx1, fctx2, linerange2, diffopts):
258 267 """Return `(diffinrange, linerange1)` where `diffinrange` is True
259 268 if diff from fctx2 to fctx1 has changes in linerange2 and
260 269 `linerange1` is the new line range for fctx1.
261 270 """
262 271 blocks = mdiff.allblocks(fctx1.data(), fctx2.data(), diffopts)
263 272 filteredblocks, linerange1 = mdiff.blocksinrange(blocks, linerange2)
264 273 diffinrange = any(stype == '!' for _, stype in filteredblocks)
265 274 return diffinrange, linerange1
266 275
267 276 def blockancestors(fctx, fromline, toline, followfirst=False):
268 277 """Yield ancestors of `fctx` with respect to the block of lines within
269 278 `fromline`-`toline` range.
270 279 """
271 280 diffopts = patch.diffopts(fctx._repo.ui)
272 281 fctx = fctx.introfilectx()
273 282 visit = {(fctx.linkrev(), fctx.filenode()): (fctx, (fromline, toline))}
274 283 while visit:
275 284 c, linerange2 = visit.pop(max(visit))
276 285 pl = c.parents()
277 286 if followfirst:
278 287 pl = pl[:1]
279 288 if not pl:
280 289 # The block originates from the initial revision.
281 290 yield c, linerange2
282 291 continue
283 292 inrange = False
284 293 for p in pl:
285 294 inrangep, linerange1 = _changesrange(p, c, linerange2, diffopts)
286 295 inrange = inrange or inrangep
287 296 if linerange1[0] == linerange1[1]:
288 297 # Parent's linerange is empty, meaning that the block got
289 298 # introduced in this revision; no need to go futher in this
290 299 # branch.
291 300 continue
292 301 # Set _descendantrev with 'c' (a known descendant) so that, when
293 302 # _adjustlinkrev is called for 'p', it receives this descendant
294 303 # (as srcrev) instead possibly topmost introrev.
295 304 p._descendantrev = c.rev()
296 305 visit[p.linkrev(), p.filenode()] = p, linerange1
297 306 if inrange:
298 307 yield c, linerange2
299 308
300 309 def blockdescendants(fctx, fromline, toline):
301 310 """Yield descendants of `fctx` with respect to the block of lines within
302 311 `fromline`-`toline` range.
303 312 """
304 313 # First possibly yield 'fctx' if it has changes in range with respect to
305 314 # its parents.
306 315 try:
307 316 c, linerange1 = next(blockancestors(fctx, fromline, toline))
308 317 except StopIteration:
309 318 pass
310 319 else:
311 320 if c == fctx:
312 321 yield c, linerange1
313 322
314 323 diffopts = patch.diffopts(fctx._repo.ui)
315 324 fl = fctx.filelog()
316 325 seen = {fctx.filerev(): (fctx, (fromline, toline))}
317 326 for i in fl.descendants([fctx.filerev()]):
318 327 c = fctx.filectx(i)
319 328 inrange = False
320 329 for x in fl.parentrevs(i):
321 330 try:
322 331 p, linerange2 = seen[x]
323 332 except KeyError:
324 333 # nullrev or other branch
325 334 continue
326 335 inrangep, linerange1 = _changesrange(c, p, linerange2, diffopts)
327 336 inrange = inrange or inrangep
328 337 # If revision 'i' has been seen (it's a merge) and the line range
329 338 # previously computed differs from the one we just got, we take the
330 339 # surrounding interval. This is conservative but avoids loosing
331 340 # information.
332 341 if i in seen and seen[i][1] != linerange1:
333 342 lbs, ubs = zip(linerange1, seen[i][1])
334 343 linerange1 = min(lbs), max(ubs)
335 344 seen[i] = c, linerange1
336 345 if inrange:
337 346 yield c, linerange1
338 347
339 348 def toposort(revs, parentsfunc, firstbranch=()):
340 349 """Yield revisions from heads to roots one (topo) branch at a time.
341 350
342 351 This function aims to be used by a graph generator that wishes to minimize
343 352 the number of parallel branches and their interleaving.
344 353
345 354 Example iteration order (numbers show the "true" order in a changelog):
346 355
347 356 o 4
348 357 |
349 358 o 1
350 359 |
351 360 | o 3
352 361 | |
353 362 | o 2
354 363 |/
355 364 o 0
356 365
357 366 Note that the ancestors of merges are understood by the current
358 367 algorithm to be on the same branch. This means no reordering will
359 368 occur behind a merge.
360 369 """
361 370
362 371 ### Quick summary of the algorithm
363 372 #
364 373 # This function is based around a "retention" principle. We keep revisions
365 374 # in memory until we are ready to emit a whole branch that immediately
366 375 # "merges" into an existing one. This reduces the number of parallel
367 376 # branches with interleaved revisions.
368 377 #
369 378 # During iteration revs are split into two groups:
370 379 # A) revision already emitted
371 380 # B) revision in "retention". They are stored as different subgroups.
372 381 #
373 382 # for each REV, we do the following logic:
374 383 #
375 384 # 1) if REV is a parent of (A), we will emit it. If there is a
376 385 # retention group ((B) above) that is blocked on REV being
377 386 # available, we emit all the revisions out of that retention
378 387 # group first.
379 388 #
380 389 # 2) else, we'll search for a subgroup in (B) awaiting for REV to be
381 390 # available, if such subgroup exist, we add REV to it and the subgroup is
382 391 # now awaiting for REV.parents() to be available.
383 392 #
384 393 # 3) finally if no such group existed in (B), we create a new subgroup.
385 394 #
386 395 #
387 396 # To bootstrap the algorithm, we emit the tipmost revision (which
388 397 # puts it in group (A) from above).
389 398
390 399 revs.sort(reverse=True)
391 400
392 401 # Set of parents of revision that have been emitted. They can be considered
393 402 # unblocked as the graph generator is already aware of them so there is no
394 403 # need to delay the revisions that reference them.
395 404 #
396 405 # If someone wants to prioritize a branch over the others, pre-filling this
397 406 # set will force all other branches to wait until this branch is ready to be
398 407 # emitted.
399 408 unblocked = set(firstbranch)
400 409
401 410 # list of groups waiting to be displayed, each group is defined by:
402 411 #
403 412 # (revs: lists of revs waiting to be displayed,
404 413 # blocked: set of that cannot be displayed before those in 'revs')
405 414 #
406 415 # The second value ('blocked') correspond to parents of any revision in the
407 416 # group ('revs') that is not itself contained in the group. The main idea
408 417 # of this algorithm is to delay as much as possible the emission of any
409 418 # revision. This means waiting for the moment we are about to display
410 419 # these parents to display the revs in a group.
411 420 #
412 421 # This first implementation is smart until it encounters a merge: it will
413 422 # emit revs as soon as any parent is about to be emitted and can grow an
414 423 # arbitrary number of revs in 'blocked'. In practice this mean we properly
415 424 # retains new branches but gives up on any special ordering for ancestors
416 425 # of merges. The implementation can be improved to handle this better.
417 426 #
418 427 # The first subgroup is special. It corresponds to all the revision that
419 428 # were already emitted. The 'revs' lists is expected to be empty and the
420 429 # 'blocked' set contains the parents revisions of already emitted revision.
421 430 #
422 431 # You could pre-seed the <parents> set of groups[0] to a specific
423 432 # changesets to select what the first emitted branch should be.
424 433 groups = [([], unblocked)]
425 434 pendingheap = []
426 435 pendingset = set()
427 436
428 437 heapq.heapify(pendingheap)
429 438 heappop = heapq.heappop
430 439 heappush = heapq.heappush
431 440 for currentrev in revs:
432 441 # Heap works with smallest element, we want highest so we invert
433 442 if currentrev not in pendingset:
434 443 heappush(pendingheap, -currentrev)
435 444 pendingset.add(currentrev)
436 445 # iterates on pending rev until after the current rev have been
437 446 # processed.
438 447 rev = None
439 448 while rev != currentrev:
440 449 rev = -heappop(pendingheap)
441 450 pendingset.remove(rev)
442 451
443 452 # Seek for a subgroup blocked, waiting for the current revision.
444 453 matching = [i for i, g in enumerate(groups) if rev in g[1]]
445 454
446 455 if matching:
447 456 # The main idea is to gather together all sets that are blocked
448 457 # on the same revision.
449 458 #
450 459 # Groups are merged when a common blocking ancestor is
451 460 # observed. For example, given two groups:
452 461 #
453 462 # revs [5, 4] waiting for 1
454 463 # revs [3, 2] waiting for 1
455 464 #
456 465 # These two groups will be merged when we process
457 466 # 1. In theory, we could have merged the groups when
458 467 # we added 2 to the group it is now in (we could have
459 468 # noticed the groups were both blocked on 1 then), but
460 469 # the way it works now makes the algorithm simpler.
461 470 #
462 471 # We also always keep the oldest subgroup first. We can
463 472 # probably improve the behavior by having the longest set
464 473 # first. That way, graph algorithms could minimise the length
465 474 # of parallel lines their drawing. This is currently not done.
466 475 targetidx = matching.pop(0)
467 476 trevs, tparents = groups[targetidx]
468 477 for i in matching:
469 478 gr = groups[i]
470 479 trevs.extend(gr[0])
471 480 tparents |= gr[1]
472 481 # delete all merged subgroups (except the one we kept)
473 482 # (starting from the last subgroup for performance and
474 483 # sanity reasons)
475 484 for i in reversed(matching):
476 485 del groups[i]
477 486 else:
478 487 # This is a new head. We create a new subgroup for it.
479 488 targetidx = len(groups)
480 489 groups.append(([], {rev}))
481 490
482 491 gr = groups[targetidx]
483 492
484 493 # We now add the current nodes to this subgroups. This is done
485 494 # after the subgroup merging because all elements from a subgroup
486 495 # that relied on this rev must precede it.
487 496 #
488 497 # we also update the <parents> set to include the parents of the
489 498 # new nodes.
490 499 if rev == currentrev: # only display stuff in rev
491 500 gr[0].append(rev)
492 501 gr[1].remove(rev)
493 502 parents = [p for p in parentsfunc(rev) if p > node.nullrev]
494 503 gr[1].update(parents)
495 504 for p in parents:
496 505 if p not in pendingset:
497 506 pendingset.add(p)
498 507 heappush(pendingheap, -p)
499 508
500 509 # Look for a subgroup to display
501 510 #
502 511 # When unblocked is empty (if clause), we were not waiting for any
503 512 # revisions during the first iteration (if no priority was given) or
504 513 # if we emitted a whole disconnected set of the graph (reached a
505 514 # root). In that case we arbitrarily take the oldest known
506 515 # subgroup. The heuristic could probably be better.
507 516 #
508 517 # Otherwise (elif clause) if the subgroup is blocked on
509 518 # a revision we just emitted, we can safely emit it as
510 519 # well.
511 520 if not unblocked:
512 521 if len(groups) > 1: # display other subset
513 522 targetidx = 1
514 523 gr = groups[1]
515 524 elif not gr[1] & unblocked:
516 525 gr = None
517 526
518 527 if gr is not None:
519 528 # update the set of awaited revisions with the one from the
520 529 # subgroup
521 530 unblocked |= gr[1]
522 531 # output all revisions in the subgroup
523 532 for r in gr[0]:
524 533 yield r
525 534 # delete the subgroup that you just output
526 535 # unless it is groups[0] in which case you just empty it.
527 536 if targetidx:
528 537 del groups[targetidx]
529 538 else:
530 539 gr[0][:] = []
531 540 # Check if we have some subgroup waiting for revisions we are not going to
532 541 # iterate over
533 542 for g in groups:
534 543 for r in g[0]:
535 544 yield r
General Comments 0
You need to be logged in to leave comments. Login now