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