##// END OF EJS Templates
ancestor: add lazy membership testing to lazyancestors...
Siddharth Agarwal -
r18091:f7f8159c default
parent child Browse files
Show More
@@ -1,277 +1,277
1 # perf.py - performance test routines
1 # perf.py - performance test routines
2 '''helper extension to measure performance'''
2 '''helper extension to measure performance'''
3
3
4 from mercurial import cmdutil, scmutil, util, match, commands
4 from mercurial import cmdutil, scmutil, util, match, commands
5 import time, os, sys
5 import time, os, sys
6
6
7 def timer(func, title=None):
7 def timer(func, title=None):
8 results = []
8 results = []
9 begin = time.time()
9 begin = time.time()
10 count = 0
10 count = 0
11 while True:
11 while True:
12 ostart = os.times()
12 ostart = os.times()
13 cstart = time.time()
13 cstart = time.time()
14 r = func()
14 r = func()
15 cstop = time.time()
15 cstop = time.time()
16 ostop = os.times()
16 ostop = os.times()
17 count += 1
17 count += 1
18 a, b = ostart, ostop
18 a, b = ostart, ostop
19 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
19 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
20 if cstop - begin > 3 and count >= 100:
20 if cstop - begin > 3 and count >= 100:
21 break
21 break
22 if cstop - begin > 10 and count >= 3:
22 if cstop - begin > 10 and count >= 3:
23 break
23 break
24 if title:
24 if title:
25 sys.stderr.write("! %s\n" % title)
25 sys.stderr.write("! %s\n" % title)
26 if r:
26 if r:
27 sys.stderr.write("! result: %s\n" % r)
27 sys.stderr.write("! result: %s\n" % r)
28 m = min(results)
28 m = min(results)
29 sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
29 sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
30 % (m[0], m[1] + m[2], m[1], m[2], count))
30 % (m[0], m[1] + m[2], m[1], m[2], count))
31
31
32 def perfwalk(ui, repo, *pats):
32 def perfwalk(ui, repo, *pats):
33 try:
33 try:
34 m = scmutil.match(repo[None], pats, {})
34 m = scmutil.match(repo[None], pats, {})
35 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
35 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
36 except Exception:
36 except Exception:
37 try:
37 try:
38 m = scmutil.match(repo[None], pats, {})
38 m = scmutil.match(repo[None], pats, {})
39 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
39 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
40 except Exception:
40 except Exception:
41 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
41 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
42
42
43 def perfstatus(ui, repo, **opts):
43 def perfstatus(ui, repo, **opts):
44 #m = match.always(repo.root, repo.getcwd())
44 #m = match.always(repo.root, repo.getcwd())
45 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
45 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
46 # False))))
46 # False))))
47 timer(lambda: sum(map(len, repo.status(**opts))))
47 timer(lambda: sum(map(len, repo.status(**opts))))
48
48
49 def clearcaches(cl):
49 def clearcaches(cl):
50 # behave somewhat consistently across internal API changes
50 # behave somewhat consistently across internal API changes
51 if util.safehasattr(cl, 'clearcaches'):
51 if util.safehasattr(cl, 'clearcaches'):
52 cl.clearcaches()
52 cl.clearcaches()
53 elif util.safehasattr(cl, '_nodecache'):
53 elif util.safehasattr(cl, '_nodecache'):
54 from mercurial.node import nullid, nullrev
54 from mercurial.node import nullid, nullrev
55 cl._nodecache = {nullid: nullrev}
55 cl._nodecache = {nullid: nullrev}
56 cl._nodepos = None
56 cl._nodepos = None
57
57
58 def perfheads(ui, repo):
58 def perfheads(ui, repo):
59 cl = repo.changelog
59 cl = repo.changelog
60 def d():
60 def d():
61 len(cl.headrevs())
61 len(cl.headrevs())
62 clearcaches(cl)
62 clearcaches(cl)
63 timer(d)
63 timer(d)
64
64
65 def perftags(ui, repo):
65 def perftags(ui, repo):
66 import mercurial.changelog, mercurial.manifest
66 import mercurial.changelog, mercurial.manifest
67 def t():
67 def t():
68 repo.changelog = mercurial.changelog.changelog(repo.sopener)
68 repo.changelog = mercurial.changelog.changelog(repo.sopener)
69 repo.manifest = mercurial.manifest.manifest(repo.sopener)
69 repo.manifest = mercurial.manifest.manifest(repo.sopener)
70 repo._tags = None
70 repo._tags = None
71 return len(repo.tags())
71 return len(repo.tags())
72 timer(t)
72 timer(t)
73
73
74 def perfancestors(ui, repo):
74 def perfancestors(ui, repo):
75 heads = repo.changelog.headrevs()
75 heads = repo.changelog.headrevs()
76 def d():
76 def d():
77 for a in repo.changelog.ancestors(heads):
77 for a in repo.changelog.ancestors(heads):
78 pass
78 pass
79 timer(d)
79 timer(d)
80
80
81 def perfancestorset(ui, repo, revset):
81 def perfancestorset(ui, repo, revset):
82 revs = repo.revs(revset)
82 revs = repo.revs(revset)
83 heads = repo.changelog.headrevs()
83 heads = repo.changelog.headrevs()
84 def d():
84 def d():
85 s = set(repo.changelog.ancestors(heads))
85 s = repo.changelog.ancestors(heads)
86 for rev in revs:
86 for rev in revs:
87 rev in s
87 rev in s
88 timer(d)
88 timer(d)
89
89
90 def perfdirstate(ui, repo):
90 def perfdirstate(ui, repo):
91 "a" in repo.dirstate
91 "a" in repo.dirstate
92 def d():
92 def d():
93 repo.dirstate.invalidate()
93 repo.dirstate.invalidate()
94 "a" in repo.dirstate
94 "a" in repo.dirstate
95 timer(d)
95 timer(d)
96
96
97 def perfdirstatedirs(ui, repo):
97 def perfdirstatedirs(ui, repo):
98 "a" in repo.dirstate
98 "a" in repo.dirstate
99 def d():
99 def d():
100 "a" in repo.dirstate._dirs
100 "a" in repo.dirstate._dirs
101 del repo.dirstate._dirs
101 del repo.dirstate._dirs
102 timer(d)
102 timer(d)
103
103
104 def perfdirstatewrite(ui, repo):
104 def perfdirstatewrite(ui, repo):
105 ds = repo.dirstate
105 ds = repo.dirstate
106 "a" in ds
106 "a" in ds
107 def d():
107 def d():
108 ds._dirty = True
108 ds._dirty = True
109 ds.write()
109 ds.write()
110 timer(d)
110 timer(d)
111
111
112 def perfmanifest(ui, repo):
112 def perfmanifest(ui, repo):
113 def d():
113 def d():
114 t = repo.manifest.tip()
114 t = repo.manifest.tip()
115 m = repo.manifest.read(t)
115 m = repo.manifest.read(t)
116 repo.manifest.mapcache = None
116 repo.manifest.mapcache = None
117 repo.manifest._cache = None
117 repo.manifest._cache = None
118 timer(d)
118 timer(d)
119
119
120 def perfchangeset(ui, repo, rev):
120 def perfchangeset(ui, repo, rev):
121 n = repo[rev].node()
121 n = repo[rev].node()
122 def d():
122 def d():
123 c = repo.changelog.read(n)
123 c = repo.changelog.read(n)
124 #repo.changelog._cache = None
124 #repo.changelog._cache = None
125 timer(d)
125 timer(d)
126
126
127 def perfindex(ui, repo):
127 def perfindex(ui, repo):
128 import mercurial.revlog
128 import mercurial.revlog
129 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
129 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
130 n = repo["tip"].node()
130 n = repo["tip"].node()
131 def d():
131 def d():
132 cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
132 cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
133 cl.rev(n)
133 cl.rev(n)
134 timer(d)
134 timer(d)
135
135
136 def perfstartup(ui, repo):
136 def perfstartup(ui, repo):
137 cmd = sys.argv[0]
137 cmd = sys.argv[0]
138 def d():
138 def d():
139 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
139 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
140 timer(d)
140 timer(d)
141
141
142 def perfparents(ui, repo):
142 def perfparents(ui, repo):
143 nl = [repo.changelog.node(i) for i in xrange(1000)]
143 nl = [repo.changelog.node(i) for i in xrange(1000)]
144 def d():
144 def d():
145 for n in nl:
145 for n in nl:
146 repo.changelog.parents(n)
146 repo.changelog.parents(n)
147 timer(d)
147 timer(d)
148
148
149 def perflookup(ui, repo, rev):
149 def perflookup(ui, repo, rev):
150 timer(lambda: len(repo.lookup(rev)))
150 timer(lambda: len(repo.lookup(rev)))
151
151
152 def perfrevrange(ui, repo, *specs):
152 def perfrevrange(ui, repo, *specs):
153 revrange = scmutil.revrange
153 revrange = scmutil.revrange
154 timer(lambda: len(revrange(repo, specs)))
154 timer(lambda: len(revrange(repo, specs)))
155
155
156 def perfnodelookup(ui, repo, rev):
156 def perfnodelookup(ui, repo, rev):
157 import mercurial.revlog
157 import mercurial.revlog
158 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
158 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
159 n = repo[rev].node()
159 n = repo[rev].node()
160 def d():
160 def d():
161 cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
161 cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
162 cl.rev(n)
162 cl.rev(n)
163 timer(d)
163 timer(d)
164
164
165 def perfnodelookup(ui, repo, rev):
165 def perfnodelookup(ui, repo, rev):
166 import mercurial.revlog
166 import mercurial.revlog
167 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
167 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
168 n = repo[rev].node()
168 n = repo[rev].node()
169 cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
169 cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
170 def d():
170 def d():
171 cl.rev(n)
171 cl.rev(n)
172 clearcaches(cl)
172 clearcaches(cl)
173 timer(d)
173 timer(d)
174
174
175 def perflog(ui, repo, **opts):
175 def perflog(ui, repo, **opts):
176 ui.pushbuffer()
176 ui.pushbuffer()
177 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
177 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
178 copies=opts.get('rename')))
178 copies=opts.get('rename')))
179 ui.popbuffer()
179 ui.popbuffer()
180
180
181 def perftemplating(ui, repo):
181 def perftemplating(ui, repo):
182 ui.pushbuffer()
182 ui.pushbuffer()
183 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
183 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
184 template='{date|shortdate} [{rev}:{node|short}]'
184 template='{date|shortdate} [{rev}:{node|short}]'
185 ' {author|person}: {desc|firstline}\n'))
185 ' {author|person}: {desc|firstline}\n'))
186 ui.popbuffer()
186 ui.popbuffer()
187
187
188 def perfcca(ui, repo):
188 def perfcca(ui, repo):
189 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
189 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
190
190
191 def perffncacheload(ui, repo):
191 def perffncacheload(ui, repo):
192 s = repo.store
192 s = repo.store
193 def d():
193 def d():
194 s.fncache._load()
194 s.fncache._load()
195 timer(d)
195 timer(d)
196
196
197 def perffncachewrite(ui, repo):
197 def perffncachewrite(ui, repo):
198 s = repo.store
198 s = repo.store
199 s.fncache._load()
199 s.fncache._load()
200 def d():
200 def d():
201 s.fncache._dirty = True
201 s.fncache._dirty = True
202 s.fncache.write()
202 s.fncache.write()
203 timer(d)
203 timer(d)
204
204
205 def perffncacheencode(ui, repo):
205 def perffncacheencode(ui, repo):
206 s = repo.store
206 s = repo.store
207 s.fncache._load()
207 s.fncache._load()
208 def d():
208 def d():
209 for p in s.fncache.entries:
209 for p in s.fncache.entries:
210 s.encode(p)
210 s.encode(p)
211 timer(d)
211 timer(d)
212
212
213 def perfdiffwd(ui, repo):
213 def perfdiffwd(ui, repo):
214 """Profile diff of working directory changes"""
214 """Profile diff of working directory changes"""
215 options = {
215 options = {
216 'w': 'ignore_all_space',
216 'w': 'ignore_all_space',
217 'b': 'ignore_space_change',
217 'b': 'ignore_space_change',
218 'B': 'ignore_blank_lines',
218 'B': 'ignore_blank_lines',
219 }
219 }
220
220
221 for diffopt in ('', 'w', 'b', 'B', 'wB'):
221 for diffopt in ('', 'w', 'b', 'B', 'wB'):
222 opts = dict((options[c], '1') for c in diffopt)
222 opts = dict((options[c], '1') for c in diffopt)
223 def d():
223 def d():
224 ui.pushbuffer()
224 ui.pushbuffer()
225 commands.diff(ui, repo, **opts)
225 commands.diff(ui, repo, **opts)
226 ui.popbuffer()
226 ui.popbuffer()
227 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
227 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
228 timer(d, title)
228 timer(d, title)
229
229
230 def perfrevlog(ui, repo, file_, **opts):
230 def perfrevlog(ui, repo, file_, **opts):
231 from mercurial import revlog
231 from mercurial import revlog
232 dist = opts['dist']
232 dist = opts['dist']
233 def d():
233 def d():
234 r = revlog.revlog(lambda fn: open(fn, 'rb'), file_)
234 r = revlog.revlog(lambda fn: open(fn, 'rb'), file_)
235 for x in xrange(0, len(r), dist):
235 for x in xrange(0, len(r), dist):
236 r.revision(r.node(x))
236 r.revision(r.node(x))
237
237
238 timer(d)
238 timer(d)
239
239
240 def perfrevset(ui, repo, expr):
240 def perfrevset(ui, repo, expr):
241 def d():
241 def d():
242 repo.revs(expr)
242 repo.revs(expr)
243 timer(d)
243 timer(d)
244
244
245 cmdtable = {
245 cmdtable = {
246 'perfcca': (perfcca, []),
246 'perfcca': (perfcca, []),
247 'perffncacheload': (perffncacheload, []),
247 'perffncacheload': (perffncacheload, []),
248 'perffncachewrite': (perffncachewrite, []),
248 'perffncachewrite': (perffncachewrite, []),
249 'perffncacheencode': (perffncacheencode, []),
249 'perffncacheencode': (perffncacheencode, []),
250 'perflookup': (perflookup, []),
250 'perflookup': (perflookup, []),
251 'perfrevrange': (perfrevrange, []),
251 'perfrevrange': (perfrevrange, []),
252 'perfnodelookup': (perfnodelookup, []),
252 'perfnodelookup': (perfnodelookup, []),
253 'perfparents': (perfparents, []),
253 'perfparents': (perfparents, []),
254 'perfstartup': (perfstartup, []),
254 'perfstartup': (perfstartup, []),
255 'perfstatus': (perfstatus,
255 'perfstatus': (perfstatus,
256 [('u', 'unknown', False,
256 [('u', 'unknown', False,
257 'ask status to look for unknown files')]),
257 'ask status to look for unknown files')]),
258 'perfwalk': (perfwalk, []),
258 'perfwalk': (perfwalk, []),
259 'perfmanifest': (perfmanifest, []),
259 'perfmanifest': (perfmanifest, []),
260 'perfchangeset': (perfchangeset, []),
260 'perfchangeset': (perfchangeset, []),
261 'perfindex': (perfindex, []),
261 'perfindex': (perfindex, []),
262 'perfheads': (perfheads, []),
262 'perfheads': (perfheads, []),
263 'perftags': (perftags, []),
263 'perftags': (perftags, []),
264 'perfancestors': (perfancestors, []),
264 'perfancestors': (perfancestors, []),
265 'perfancestorset': (perfancestorset, [], "REVSET"),
265 'perfancestorset': (perfancestorset, [], "REVSET"),
266 'perfdirstate': (perfdirstate, []),
266 'perfdirstate': (perfdirstate, []),
267 'perfdirstatedirs': (perfdirstate, []),
267 'perfdirstatedirs': (perfdirstate, []),
268 'perfdirstatewrite': (perfdirstatewrite, []),
268 'perfdirstatewrite': (perfdirstatewrite, []),
269 'perflog': (perflog,
269 'perflog': (perflog,
270 [('', 'rename', False, 'ask log to follow renames')]),
270 [('', 'rename', False, 'ask log to follow renames')]),
271 'perftemplating': (perftemplating, []),
271 'perftemplating': (perftemplating, []),
272 'perfdiffwd': (perfdiffwd, []),
272 'perfdiffwd': (perfdiffwd, []),
273 'perfrevlog': (perfrevlog,
273 'perfrevlog': (perfrevlog,
274 [('d', 'dist', 100, 'distance between the revisions')],
274 [('d', 'dist', 100, 'distance between the revisions')],
275 "[INDEXFILE]"),
275 "[INDEXFILE]"),
276 'perfrevset': (perfrevset, [], "REVSET")
276 'perfrevset': (perfrevset, [], "REVSET")
277 }
277 }
@@ -1,221 +1,264
1 # ancestor.py - generic DAG ancestor algorithm for mercurial
1 # ancestor.py - generic DAG ancestor algorithm for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 import heapq, util
8 import heapq, util
9 from node import nullrev
9 from node import nullrev
10
10
11 def ancestor(a, b, pfunc):
11 def ancestor(a, b, pfunc):
12 """
12 """
13 Returns the common ancestor of a and b that is furthest from a
13 Returns the common ancestor of a and b that is furthest from a
14 root (as measured by longest path) or None if no ancestor is
14 root (as measured by longest path) or None if no ancestor is
15 found. If there are multiple common ancestors at the same
15 found. If there are multiple common ancestors at the same
16 distance, the first one found is returned.
16 distance, the first one found is returned.
17
17
18 pfunc must return a list of parent vertices for a given vertex
18 pfunc must return a list of parent vertices for a given vertex
19 """
19 """
20
20
21 if a == b:
21 if a == b:
22 return a
22 return a
23
23
24 a, b = sorted([a, b])
24 a, b = sorted([a, b])
25
25
26 # find depth from root of all ancestors
26 # find depth from root of all ancestors
27 # depth is stored as a negative for heapq
27 # depth is stored as a negative for heapq
28 parentcache = {}
28 parentcache = {}
29 visit = [a, b]
29 visit = [a, b]
30 depth = {}
30 depth = {}
31 while visit:
31 while visit:
32 vertex = visit[-1]
32 vertex = visit[-1]
33 pl = pfunc(vertex)
33 pl = pfunc(vertex)
34 parentcache[vertex] = pl
34 parentcache[vertex] = pl
35 if not pl:
35 if not pl:
36 depth[vertex] = 0
36 depth[vertex] = 0
37 visit.pop()
37 visit.pop()
38 else:
38 else:
39 for p in pl:
39 for p in pl:
40 if p == a or p == b: # did we find a or b as a parent?
40 if p == a or p == b: # did we find a or b as a parent?
41 return p # we're done
41 return p # we're done
42 if p not in depth:
42 if p not in depth:
43 visit.append(p)
43 visit.append(p)
44 if visit[-1] == vertex:
44 if visit[-1] == vertex:
45 # -(maximum distance of parents + 1)
45 # -(maximum distance of parents + 1)
46 depth[vertex] = min([depth[p] for p in pl]) - 1
46 depth[vertex] = min([depth[p] for p in pl]) - 1
47 visit.pop()
47 visit.pop()
48
48
49 # traverse ancestors in order of decreasing distance from root
49 # traverse ancestors in order of decreasing distance from root
50 def ancestors(vertex):
50 def ancestors(vertex):
51 h = [(depth[vertex], vertex)]
51 h = [(depth[vertex], vertex)]
52 seen = set()
52 seen = set()
53 while h:
53 while h:
54 d, n = heapq.heappop(h)
54 d, n = heapq.heappop(h)
55 if n not in seen:
55 if n not in seen:
56 seen.add(n)
56 seen.add(n)
57 yield (d, n)
57 yield (d, n)
58 for p in parentcache[n]:
58 for p in parentcache[n]:
59 heapq.heappush(h, (depth[p], p))
59 heapq.heappush(h, (depth[p], p))
60
60
61 def generations(vertex):
61 def generations(vertex):
62 sg, s = None, set()
62 sg, s = None, set()
63 for g, v in ancestors(vertex):
63 for g, v in ancestors(vertex):
64 if g != sg:
64 if g != sg:
65 if sg:
65 if sg:
66 yield sg, s
66 yield sg, s
67 sg, s = g, set((v,))
67 sg, s = g, set((v,))
68 else:
68 else:
69 s.add(v)
69 s.add(v)
70 yield sg, s
70 yield sg, s
71
71
72 x = generations(a)
72 x = generations(a)
73 y = generations(b)
73 y = generations(b)
74 gx = x.next()
74 gx = x.next()
75 gy = y.next()
75 gy = y.next()
76
76
77 # increment each ancestor list until it is closer to root than
77 # increment each ancestor list until it is closer to root than
78 # the other, or they match
78 # the other, or they match
79 try:
79 try:
80 while True:
80 while True:
81 if gx[0] == gy[0]:
81 if gx[0] == gy[0]:
82 for v in gx[1]:
82 for v in gx[1]:
83 if v in gy[1]:
83 if v in gy[1]:
84 return v
84 return v
85 gy = y.next()
85 gy = y.next()
86 gx = x.next()
86 gx = x.next()
87 elif gx[0] > gy[0]:
87 elif gx[0] > gy[0]:
88 gy = y.next()
88 gy = y.next()
89 else:
89 else:
90 gx = x.next()
90 gx = x.next()
91 except StopIteration:
91 except StopIteration:
92 return None
92 return None
93
93
94 def missingancestors(revs, bases, pfunc):
94 def missingancestors(revs, bases, pfunc):
95 """Return all the ancestors of revs that are not ancestors of bases.
95 """Return all the ancestors of revs that are not ancestors of bases.
96
96
97 This may include elements from revs.
97 This may include elements from revs.
98
98
99 Equivalent to the revset (::revs - ::bases). Revs are returned in
99 Equivalent to the revset (::revs - ::bases). Revs are returned in
100 revision number order, which is a topological order.
100 revision number order, which is a topological order.
101
101
102 revs and bases should both be iterables. pfunc must return a list of
102 revs and bases should both be iterables. pfunc must return a list of
103 parent revs for a given revs.
103 parent revs for a given revs.
104 """
104 """
105
105
106 revsvisit = set(revs)
106 revsvisit = set(revs)
107 basesvisit = set(bases)
107 basesvisit = set(bases)
108 if not revsvisit:
108 if not revsvisit:
109 return []
109 return []
110 if not basesvisit:
110 if not basesvisit:
111 basesvisit.add(nullrev)
111 basesvisit.add(nullrev)
112 start = max(max(revsvisit), max(basesvisit))
112 start = max(max(revsvisit), max(basesvisit))
113 bothvisit = revsvisit.intersection(basesvisit)
113 bothvisit = revsvisit.intersection(basesvisit)
114 revsvisit.difference_update(bothvisit)
114 revsvisit.difference_update(bothvisit)
115 basesvisit.difference_update(bothvisit)
115 basesvisit.difference_update(bothvisit)
116 # At this point, we hold the invariants that:
116 # At this point, we hold the invariants that:
117 # - revsvisit is the set of nodes we know are an ancestor of at least one
117 # - revsvisit is the set of nodes we know are an ancestor of at least one
118 # of the nodes in revs
118 # of the nodes in revs
119 # - basesvisit is the same for bases
119 # - basesvisit is the same for bases
120 # - bothvisit is the set of nodes we know are ancestors of at least one of
120 # - bothvisit is the set of nodes we know are ancestors of at least one of
121 # the nodes in revs and one of the nodes in bases
121 # the nodes in revs and one of the nodes in bases
122 # - a node may be in none or one, but not more, of revsvisit, basesvisit
122 # - a node may be in none or one, but not more, of revsvisit, basesvisit
123 # and bothvisit at any given time
123 # and bothvisit at any given time
124 # Now we walk down in reverse topo order, adding parents of nodes already
124 # Now we walk down in reverse topo order, adding parents of nodes already
125 # visited to the sets while maintaining the invariants. When a node is
125 # visited to the sets while maintaining the invariants. When a node is
126 # found in both revsvisit and basesvisit, it is removed from them and
126 # found in both revsvisit and basesvisit, it is removed from them and
127 # added to bothvisit instead. When revsvisit becomes empty, there are no
127 # added to bothvisit instead. When revsvisit becomes empty, there are no
128 # more ancestors of revs that aren't also ancestors of bases, so exit.
128 # more ancestors of revs that aren't also ancestors of bases, so exit.
129
129
130 missing = []
130 missing = []
131 for curr in xrange(start, nullrev, -1):
131 for curr in xrange(start, nullrev, -1):
132 if not revsvisit:
132 if not revsvisit:
133 break
133 break
134
134
135 if curr in bothvisit:
135 if curr in bothvisit:
136 bothvisit.remove(curr)
136 bothvisit.remove(curr)
137 # curr's parents might have made it into revsvisit or basesvisit
137 # curr's parents might have made it into revsvisit or basesvisit
138 # through another path
138 # through another path
139 for p in pfunc(curr):
139 for p in pfunc(curr):
140 revsvisit.discard(p)
140 revsvisit.discard(p)
141 basesvisit.discard(p)
141 basesvisit.discard(p)
142 bothvisit.add(p)
142 bothvisit.add(p)
143 continue
143 continue
144
144
145 # curr will never be in both revsvisit and basesvisit, since if it
145 # curr will never be in both revsvisit and basesvisit, since if it
146 # were it'd have been pushed to bothvisit
146 # were it'd have been pushed to bothvisit
147 if curr in revsvisit:
147 if curr in revsvisit:
148 missing.append(curr)
148 missing.append(curr)
149 thisvisit = revsvisit
149 thisvisit = revsvisit
150 othervisit = basesvisit
150 othervisit = basesvisit
151 elif curr in basesvisit:
151 elif curr in basesvisit:
152 thisvisit = basesvisit
152 thisvisit = basesvisit
153 othervisit = revsvisit
153 othervisit = revsvisit
154 else:
154 else:
155 # not an ancestor of revs or bases: ignore
155 # not an ancestor of revs or bases: ignore
156 continue
156 continue
157
157
158 thisvisit.remove(curr)
158 thisvisit.remove(curr)
159 for p in pfunc(curr):
159 for p in pfunc(curr):
160 if p == nullrev:
160 if p == nullrev:
161 pass
161 pass
162 elif p in othervisit or p in bothvisit:
162 elif p in othervisit or p in bothvisit:
163 # p is implicitly in thisvisit. This means p is or should be
163 # p is implicitly in thisvisit. This means p is or should be
164 # in bothvisit
164 # in bothvisit
165 revsvisit.discard(p)
165 revsvisit.discard(p)
166 basesvisit.discard(p)
166 basesvisit.discard(p)
167 bothvisit.add(p)
167 bothvisit.add(p)
168 else:
168 else:
169 # visit later
169 # visit later
170 thisvisit.add(p)
170 thisvisit.add(p)
171
171
172 missing.reverse()
172 missing.reverse()
173 return missing
173 return missing
174
174
175 class lazyancestors(object):
175 class lazyancestors(object):
176 def __init__(self, cl, revs, stoprev=0, inclusive=False):
176 def __init__(self, cl, revs, stoprev=0, inclusive=False):
177 """Create a new object generating ancestors for the given revs. Does
177 """Create a new object generating ancestors for the given revs. Does
178 not generate revs lower than stoprev.
178 not generate revs lower than stoprev.
179
179
180 This is computed lazily starting from revs. The object only supports
180 This is computed lazily starting from revs. The object supports
181 iteration.
181 iteration and membership.
182
182
183 cl should be a changelog and revs should be an iterable. inclusive is
183 cl should be a changelog and revs should be an iterable. inclusive is
184 a boolean that indicates whether revs should be included. Revs lower
184 a boolean that indicates whether revs should be included. Revs lower
185 than stoprev will not be generated.
185 than stoprev will not be generated.
186
186
187 Result does not include the null revision."""
187 Result does not include the null revision."""
188 self._parentrevs = cl.parentrevs
188 self._parentrevs = cl.parentrevs
189 self._initrevs = revs
189 self._initrevs = revs
190 self._stoprev = stoprev
190 self._stoprev = stoprev
191 self._inclusive = inclusive
191 self._inclusive = inclusive
192
192
193 # Initialize data structures for __contains__.
194 # For __contains__, we use a heap rather than a deque because
195 # (a) it minimizes the number of parentrevs calls made
196 # (b) it makes the loop termination condition obvious
197 # Python's heap is a min-heap. Multiply all values by -1 to convert it
198 # into a max-heap.
199 self._containsvisit = [-rev for rev in revs]
200 heapq.heapify(self._containsvisit)
201 if inclusive:
202 self._containsseen = set(revs)
203 else:
204 self._containsseen = set()
205
193 def __iter__(self):
206 def __iter__(self):
194 """Generate the ancestors of _initrevs in reverse topological order.
207 """Generate the ancestors of _initrevs in reverse topological order.
195
208
196 If inclusive is False, yield a sequence of revision numbers starting
209 If inclusive is False, yield a sequence of revision numbers starting
197 with the parents of each revision in revs, i.e., each revision is *not*
210 with the parents of each revision in revs, i.e., each revision is *not*
198 considered an ancestor of itself. Results are in breadth-first order:
211 considered an ancestor of itself. Results are in breadth-first order:
199 parents of each rev in revs, then parents of those, etc.
212 parents of each rev in revs, then parents of those, etc.
200
213
201 If inclusive is True, yield all the revs first (ignoring stoprev),
214 If inclusive is True, yield all the revs first (ignoring stoprev),
202 then yield all the ancestors of revs as when inclusive is False.
215 then yield all the ancestors of revs as when inclusive is False.
203 If an element in revs is an ancestor of a different rev it is not
216 If an element in revs is an ancestor of a different rev it is not
204 yielded again."""
217 yielded again."""
205 seen = set()
218 seen = set()
206 revs = self._initrevs
219 revs = self._initrevs
207 if self._inclusive:
220 if self._inclusive:
208 for rev in revs:
221 for rev in revs:
209 yield rev
222 yield rev
210 seen.update(revs)
223 seen.update(revs)
211
224
212 parentrevs = self._parentrevs
225 parentrevs = self._parentrevs
213 stoprev = self._stoprev
226 stoprev = self._stoprev
214 visit = util.deque(revs)
227 visit = util.deque(revs)
215
228
216 while visit:
229 while visit:
217 for parent in parentrevs(visit.popleft()):
230 for parent in parentrevs(visit.popleft()):
218 if parent >= stoprev and parent not in seen:
231 if parent >= stoprev and parent not in seen:
219 visit.append(parent)
232 visit.append(parent)
220 seen.add(parent)
233 seen.add(parent)
221 yield parent
234 yield parent
235
236 def __contains__(self, target):
237 """Test whether target is an ancestor of self._initrevs."""
238 # Trying to do both __iter__ and __contains__ using the same visit
239 # heap and seen set is complex enough that it slows down both. Keep
240 # them separate.
241 seen = self._containsseen
242 if target in seen:
243 return True
244
245 parentrevs = self._parentrevs
246 visit = self._containsvisit
247 stoprev = self._stoprev
248 heappop = heapq.heappop
249 heappush = heapq.heappush
250
251 targetseen = False
252
253 while visit and -visit[0] > target and not targetseen:
254 for parent in parentrevs(-heappop(visit)):
255 if parent < stoprev or parent in seen:
256 continue
257 # We need to make sure we push all parents into the heap so
258 # that we leave it in a consistent state for future calls.
259 heappush(visit, -parent)
260 seen.add(parent)
261 if parent == target:
262 targetseen = True
263
264 return targetseen
@@ -1,74 +1,106
1 from mercurial import ancestor
1 from mercurial import ancestor
2
2
3 # graph is a dict of child->parent adjacency lists for this graph:
3 # graph is a dict of child->parent adjacency lists for this graph:
4 # o 13
4 # o 13
5 # |
5 # |
6 # | o 12
6 # | o 12
7 # | |
7 # | |
8 # | | o 11
8 # | | o 11
9 # | | |\
9 # | | |\
10 # | | | | o 10
10 # | | | | o 10
11 # | | | | |
11 # | | | | |
12 # | o---+ | 9
12 # | o---+ | 9
13 # | | | | |
13 # | | | | |
14 # o | | | | 8
14 # o | | | | 8
15 # / / / /
15 # / / / /
16 # | | o | 7
16 # | | o | 7
17 # | | | |
17 # | | | |
18 # o---+ | 6
18 # o---+ | 6
19 # / / /
19 # / / /
20 # | | o 5
20 # | | o 5
21 # | |/
21 # | |/
22 # | o 4
22 # | o 4
23 # | |
23 # | |
24 # o | 3
24 # o | 3
25 # | |
25 # | |
26 # | o 2
26 # | o 2
27 # |/
27 # |/
28 # o 1
28 # o 1
29 # |
29 # |
30 # o 0
30 # o 0
31
31
32 graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4],
32 graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4],
33 7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9],
33 7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9],
34 13: [8]}
34 13: [8]}
35 pfunc = graph.get
35 pfunc = graph.get
36
36
37 class mockchangelog(object):
38 parentrevs = graph.get
39
37 def runmissingancestors(revs, bases):
40 def runmissingancestors(revs, bases):
38 print "%% ancestors of %s and not of %s" % (revs, bases)
41 print "%% ancestors of %s and not of %s" % (revs, bases)
39 print ancestor.missingancestors(revs, bases, pfunc)
42 print ancestor.missingancestors(revs, bases, pfunc)
40
43
41 def test_missingancestors():
44 def test_missingancestors():
42 # Empty revs
45 # Empty revs
43 runmissingancestors([], [1])
46 runmissingancestors([], [1])
44 runmissingancestors([], [])
47 runmissingancestors([], [])
45
48
46 # If bases is empty, it's the same as if it were [nullrev]
49 # If bases is empty, it's the same as if it were [nullrev]
47 runmissingancestors([12], [])
50 runmissingancestors([12], [])
48
51
49 # Trivial case: revs == bases
52 # Trivial case: revs == bases
50 runmissingancestors([0], [0])
53 runmissingancestors([0], [0])
51 runmissingancestors([4, 5, 6], [6, 5, 4])
54 runmissingancestors([4, 5, 6], [6, 5, 4])
52
55
53 # With nullrev
56 # With nullrev
54 runmissingancestors([-1], [12])
57 runmissingancestors([-1], [12])
55 runmissingancestors([12], [-1])
58 runmissingancestors([12], [-1])
56
59
57 # 9 is a parent of 12. 7 is a parent of 9, so an ancestor of 12. 6 is an
60 # 9 is a parent of 12. 7 is a parent of 9, so an ancestor of 12. 6 is an
58 # ancestor of 12 but not of 7.
61 # ancestor of 12 but not of 7.
59 runmissingancestors([12], [9])
62 runmissingancestors([12], [9])
60 runmissingancestors([9], [12])
63 runmissingancestors([9], [12])
61 runmissingancestors([12, 9], [7])
64 runmissingancestors([12, 9], [7])
62 runmissingancestors([7, 6], [12])
65 runmissingancestors([7, 6], [12])
63
66
64 # More complex cases
67 # More complex cases
65 runmissingancestors([10], [11, 12])
68 runmissingancestors([10], [11, 12])
66 runmissingancestors([11], [10])
69 runmissingancestors([11], [10])
67 runmissingancestors([11], [10, 12])
70 runmissingancestors([11], [10, 12])
68 runmissingancestors([12], [10])
71 runmissingancestors([12], [10])
69 runmissingancestors([12], [11])
72 runmissingancestors([12], [11])
70 runmissingancestors([10, 11, 12], [13])
73 runmissingancestors([10, 11, 12], [13])
71 runmissingancestors([13], [10, 11, 12])
74 runmissingancestors([13], [10, 11, 12])
72
75
76 def genlazyancestors(revs, stoprev=0, inclusive=False):
77 print ("%% lazy ancestor set for %s, stoprev = %s, inclusive = %s" %
78 (revs, stoprev, inclusive))
79 return ancestor.lazyancestors(mockchangelog, revs, stoprev=stoprev,
80 inclusive=inclusive)
81
82 def printlazyancestors(s, l):
83 print [n for n in l if n in s]
84
85 def test_lazyancestors():
86 # Empty revs
87 s = genlazyancestors([])
88 printlazyancestors(s, [3, 0, -1])
89
90 # Standard example
91 s = genlazyancestors([11, 13])
92 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
93
94 # Including revs
95 s = genlazyancestors([11, 13], inclusive=True)
96 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
97
98 # Test with stoprev
99 s = genlazyancestors([11, 13], stoprev=6)
100 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
101 s = genlazyancestors([11, 13], stoprev=6, inclusive=True)
102 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
103
73 if __name__ == '__main__':
104 if __name__ == '__main__':
74 test_missingancestors()
105 test_missingancestors()
106 test_lazyancestors()
@@ -1,36 +1,46
1 % ancestors of [] and not of [1]
1 % ancestors of [] and not of [1]
2 []
2 []
3 % ancestors of [] and not of []
3 % ancestors of [] and not of []
4 []
4 []
5 % ancestors of [12] and not of []
5 % ancestors of [12] and not of []
6 [0, 1, 2, 4, 6, 7, 9, 12]
6 [0, 1, 2, 4, 6, 7, 9, 12]
7 % ancestors of [0] and not of [0]
7 % ancestors of [0] and not of [0]
8 []
8 []
9 % ancestors of [4, 5, 6] and not of [6, 5, 4]
9 % ancestors of [4, 5, 6] and not of [6, 5, 4]
10 []
10 []
11 % ancestors of [-1] and not of [12]
11 % ancestors of [-1] and not of [12]
12 []
12 []
13 % ancestors of [12] and not of [-1]
13 % ancestors of [12] and not of [-1]
14 [0, 1, 2, 4, 6, 7, 9, 12]
14 [0, 1, 2, 4, 6, 7, 9, 12]
15 % ancestors of [12] and not of [9]
15 % ancestors of [12] and not of [9]
16 [12]
16 [12]
17 % ancestors of [9] and not of [12]
17 % ancestors of [9] and not of [12]
18 []
18 []
19 % ancestors of [12, 9] and not of [7]
19 % ancestors of [12, 9] and not of [7]
20 [6, 9, 12]
20 [6, 9, 12]
21 % ancestors of [7, 6] and not of [12]
21 % ancestors of [7, 6] and not of [12]
22 []
22 []
23 % ancestors of [10] and not of [11, 12]
23 % ancestors of [10] and not of [11, 12]
24 [5, 10]
24 [5, 10]
25 % ancestors of [11] and not of [10]
25 % ancestors of [11] and not of [10]
26 [3, 7, 11]
26 [3, 7, 11]
27 % ancestors of [11] and not of [10, 12]
27 % ancestors of [11] and not of [10, 12]
28 [3, 11]
28 [3, 11]
29 % ancestors of [12] and not of [10]
29 % ancestors of [12] and not of [10]
30 [6, 7, 9, 12]
30 [6, 7, 9, 12]
31 % ancestors of [12] and not of [11]
31 % ancestors of [12] and not of [11]
32 [6, 9, 12]
32 [6, 9, 12]
33 % ancestors of [10, 11, 12] and not of [13]
33 % ancestors of [10, 11, 12] and not of [13]
34 [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12]
34 [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12]
35 % ancestors of [13] and not of [10, 11, 12]
35 % ancestors of [13] and not of [10, 11, 12]
36 [8, 13]
36 [8, 13]
37 % lazy ancestor set for [], stoprev = 0, inclusive = False
38 []
39 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = False
40 [7, 8, 3, 4, 1, 0]
41 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = True
42 [11, 13, 7, 8, 3, 4, 1, 0]
43 % lazy ancestor set for [11, 13], stoprev = 6, inclusive = False
44 [7, 8]
45 % lazy ancestor set for [11, 13], stoprev = 6, inclusive = True
46 [11, 13, 7, 8]
General Comments 0
You need to be logged in to leave comments. Login now