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