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