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