##// END OF EJS Templates
manifest: move clearcaches to manifestlog...
Durham Goode -
r30370:10c92459 default
parent child Browse files
Show More
@@ -1,1162 +1,1162
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 # "historical portability" policy of perf.py:
4 # "historical portability" policy of perf.py:
5 #
5 #
6 # We have to do:
6 # We have to do:
7 # - make perf.py "loadable" with as wide Mercurial version as possible
7 # - make perf.py "loadable" with as wide Mercurial version as possible
8 # This doesn't mean that perf commands work correctly with that Mercurial.
8 # This doesn't mean that perf commands work correctly with that Mercurial.
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
10 # - make historical perf command work correctly with as wide Mercurial
10 # - make historical perf command work correctly with as wide Mercurial
11 # version as possible
11 # version as possible
12 #
12 #
13 # We have to do, if possible with reasonable cost:
13 # We have to do, if possible with reasonable cost:
14 # - make recent perf command for historical feature work correctly
14 # - make recent perf command for historical feature work correctly
15 # with early Mercurial
15 # with early Mercurial
16 #
16 #
17 # We don't have to do:
17 # We don't have to do:
18 # - make perf command for recent feature work correctly with early
18 # - make perf command for recent feature work correctly with early
19 # Mercurial
19 # Mercurial
20
20
21 from __future__ import absolute_import
21 from __future__ import absolute_import
22 import functools
22 import functools
23 import os
23 import os
24 import random
24 import random
25 import sys
25 import sys
26 import time
26 import time
27 from mercurial import (
27 from mercurial import (
28 bdiff,
28 bdiff,
29 changegroup,
29 changegroup,
30 cmdutil,
30 cmdutil,
31 commands,
31 commands,
32 copies,
32 copies,
33 error,
33 error,
34 extensions,
34 extensions,
35 mdiff,
35 mdiff,
36 merge,
36 merge,
37 revlog,
37 revlog,
38 util,
38 util,
39 )
39 )
40
40
41 # for "historical portability":
41 # for "historical portability":
42 # try to import modules separately (in dict order), and ignore
42 # try to import modules separately (in dict order), and ignore
43 # failure, because these aren't available with early Mercurial
43 # failure, because these aren't available with early Mercurial
44 try:
44 try:
45 from mercurial import branchmap # since 2.5 (or bcee63733aad)
45 from mercurial import branchmap # since 2.5 (or bcee63733aad)
46 except ImportError:
46 except ImportError:
47 pass
47 pass
48 try:
48 try:
49 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
49 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
50 except ImportError:
50 except ImportError:
51 pass
51 pass
52 try:
52 try:
53 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
53 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
54 except ImportError:
54 except ImportError:
55 pass
55 pass
56 try:
56 try:
57 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
57 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
58 except ImportError:
58 except ImportError:
59 pass
59 pass
60
60
61 # for "historical portability":
61 # for "historical portability":
62 # define util.safehasattr forcibly, because util.safehasattr has been
62 # define util.safehasattr forcibly, because util.safehasattr has been
63 # available since 1.9.3 (or 94b200a11cf7)
63 # available since 1.9.3 (or 94b200a11cf7)
64 _undefined = object()
64 _undefined = object()
65 def safehasattr(thing, attr):
65 def safehasattr(thing, attr):
66 return getattr(thing, attr, _undefined) is not _undefined
66 return getattr(thing, attr, _undefined) is not _undefined
67 setattr(util, 'safehasattr', safehasattr)
67 setattr(util, 'safehasattr', safehasattr)
68
68
69 # for "historical portability":
69 # for "historical portability":
70 # use locally defined empty option list, if formatteropts isn't
70 # use locally defined empty option list, if formatteropts isn't
71 # available, because commands.formatteropts has been available since
71 # available, because commands.formatteropts has been available since
72 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
72 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
73 # available since 2.2 (or ae5f92e154d3)
73 # available since 2.2 (or ae5f92e154d3)
74 formatteropts = getattr(commands, "formatteropts", [])
74 formatteropts = getattr(commands, "formatteropts", [])
75
75
76 # for "historical portability":
76 # for "historical portability":
77 # use locally defined option list, if debugrevlogopts isn't available,
77 # use locally defined option list, if debugrevlogopts isn't available,
78 # because commands.debugrevlogopts has been available since 3.7 (or
78 # because commands.debugrevlogopts has been available since 3.7 (or
79 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
79 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
80 # since 1.9 (or a79fea6b3e77).
80 # since 1.9 (or a79fea6b3e77).
81 revlogopts = getattr(commands, "debugrevlogopts", [
81 revlogopts = getattr(commands, "debugrevlogopts", [
82 ('c', 'changelog', False, ('open changelog')),
82 ('c', 'changelog', False, ('open changelog')),
83 ('m', 'manifest', False, ('open manifest')),
83 ('m', 'manifest', False, ('open manifest')),
84 ('', 'dir', False, ('open directory manifest')),
84 ('', 'dir', False, ('open directory manifest')),
85 ])
85 ])
86
86
87 cmdtable = {}
87 cmdtable = {}
88
88
89 # for "historical portability":
89 # for "historical portability":
90 # define parsealiases locally, because cmdutil.parsealiases has been
90 # define parsealiases locally, because cmdutil.parsealiases has been
91 # available since 1.5 (or 6252852b4332)
91 # available since 1.5 (or 6252852b4332)
92 def parsealiases(cmd):
92 def parsealiases(cmd):
93 return cmd.lstrip("^").split("|")
93 return cmd.lstrip("^").split("|")
94
94
95 if safehasattr(cmdutil, 'command'):
95 if safehasattr(cmdutil, 'command'):
96 import inspect
96 import inspect
97 command = cmdutil.command(cmdtable)
97 command = cmdutil.command(cmdtable)
98 if 'norepo' not in inspect.getargspec(command)[0]:
98 if 'norepo' not in inspect.getargspec(command)[0]:
99 # for "historical portability":
99 # for "historical portability":
100 # wrap original cmdutil.command, because "norepo" option has
100 # wrap original cmdutil.command, because "norepo" option has
101 # been available since 3.1 (or 75a96326cecb)
101 # been available since 3.1 (or 75a96326cecb)
102 _command = command
102 _command = command
103 def command(name, options=(), synopsis=None, norepo=False):
103 def command(name, options=(), synopsis=None, norepo=False):
104 if norepo:
104 if norepo:
105 commands.norepo += ' %s' % ' '.join(parsealiases(name))
105 commands.norepo += ' %s' % ' '.join(parsealiases(name))
106 return _command(name, list(options), synopsis)
106 return _command(name, list(options), synopsis)
107 else:
107 else:
108 # for "historical portability":
108 # for "historical portability":
109 # define "@command" annotation locally, because cmdutil.command
109 # define "@command" annotation locally, because cmdutil.command
110 # has been available since 1.9 (or 2daa5179e73f)
110 # has been available since 1.9 (or 2daa5179e73f)
111 def command(name, options=(), synopsis=None, norepo=False):
111 def command(name, options=(), synopsis=None, norepo=False):
112 def decorator(func):
112 def decorator(func):
113 if synopsis:
113 if synopsis:
114 cmdtable[name] = func, list(options), synopsis
114 cmdtable[name] = func, list(options), synopsis
115 else:
115 else:
116 cmdtable[name] = func, list(options)
116 cmdtable[name] = func, list(options)
117 if norepo:
117 if norepo:
118 commands.norepo += ' %s' % ' '.join(parsealiases(name))
118 commands.norepo += ' %s' % ' '.join(parsealiases(name))
119 return func
119 return func
120 return decorator
120 return decorator
121
121
122 def getlen(ui):
122 def getlen(ui):
123 if ui.configbool("perf", "stub"):
123 if ui.configbool("perf", "stub"):
124 return lambda x: 1
124 return lambda x: 1
125 return len
125 return len
126
126
127 def gettimer(ui, opts=None):
127 def gettimer(ui, opts=None):
128 """return a timer function and formatter: (timer, formatter)
128 """return a timer function and formatter: (timer, formatter)
129
129
130 This function exists to gather the creation of formatter in a single
130 This function exists to gather the creation of formatter in a single
131 place instead of duplicating it in all performance commands."""
131 place instead of duplicating it in all performance commands."""
132
132
133 # enforce an idle period before execution to counteract power management
133 # enforce an idle period before execution to counteract power management
134 # experimental config: perf.presleep
134 # experimental config: perf.presleep
135 time.sleep(getint(ui, "perf", "presleep", 1))
135 time.sleep(getint(ui, "perf", "presleep", 1))
136
136
137 if opts is None:
137 if opts is None:
138 opts = {}
138 opts = {}
139 # redirect all to stderr
139 # redirect all to stderr
140 ui = ui.copy()
140 ui = ui.copy()
141 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
141 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
142 if uifout:
142 if uifout:
143 # for "historical portability":
143 # for "historical portability":
144 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
144 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
145 uifout.set(ui.ferr)
145 uifout.set(ui.ferr)
146
146
147 # get a formatter
147 # get a formatter
148 uiformatter = getattr(ui, 'formatter', None)
148 uiformatter = getattr(ui, 'formatter', None)
149 if uiformatter:
149 if uiformatter:
150 fm = uiformatter('perf', opts)
150 fm = uiformatter('perf', opts)
151 else:
151 else:
152 # for "historical portability":
152 # for "historical portability":
153 # define formatter locally, because ui.formatter has been
153 # define formatter locally, because ui.formatter has been
154 # available since 2.2 (or ae5f92e154d3)
154 # available since 2.2 (or ae5f92e154d3)
155 from mercurial import node
155 from mercurial import node
156 class defaultformatter(object):
156 class defaultformatter(object):
157 """Minimized composition of baseformatter and plainformatter
157 """Minimized composition of baseformatter and plainformatter
158 """
158 """
159 def __init__(self, ui, topic, opts):
159 def __init__(self, ui, topic, opts):
160 self._ui = ui
160 self._ui = ui
161 if ui.debugflag:
161 if ui.debugflag:
162 self.hexfunc = node.hex
162 self.hexfunc = node.hex
163 else:
163 else:
164 self.hexfunc = node.short
164 self.hexfunc = node.short
165 def __nonzero__(self):
165 def __nonzero__(self):
166 return False
166 return False
167 def startitem(self):
167 def startitem(self):
168 pass
168 pass
169 def data(self, **data):
169 def data(self, **data):
170 pass
170 pass
171 def write(self, fields, deftext, *fielddata, **opts):
171 def write(self, fields, deftext, *fielddata, **opts):
172 self._ui.write(deftext % fielddata, **opts)
172 self._ui.write(deftext % fielddata, **opts)
173 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
173 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
174 if cond:
174 if cond:
175 self._ui.write(deftext % fielddata, **opts)
175 self._ui.write(deftext % fielddata, **opts)
176 def plain(self, text, **opts):
176 def plain(self, text, **opts):
177 self._ui.write(text, **opts)
177 self._ui.write(text, **opts)
178 def end(self):
178 def end(self):
179 pass
179 pass
180 fm = defaultformatter(ui, 'perf', opts)
180 fm = defaultformatter(ui, 'perf', opts)
181
181
182 # stub function, runs code only once instead of in a loop
182 # stub function, runs code only once instead of in a loop
183 # experimental config: perf.stub
183 # experimental config: perf.stub
184 if ui.configbool("perf", "stub"):
184 if ui.configbool("perf", "stub"):
185 return functools.partial(stub_timer, fm), fm
185 return functools.partial(stub_timer, fm), fm
186 return functools.partial(_timer, fm), fm
186 return functools.partial(_timer, fm), fm
187
187
188 def stub_timer(fm, func, title=None):
188 def stub_timer(fm, func, title=None):
189 func()
189 func()
190
190
191 def _timer(fm, func, title=None):
191 def _timer(fm, func, title=None):
192 results = []
192 results = []
193 begin = time.time()
193 begin = time.time()
194 count = 0
194 count = 0
195 while True:
195 while True:
196 ostart = os.times()
196 ostart = os.times()
197 cstart = time.time()
197 cstart = time.time()
198 r = func()
198 r = func()
199 cstop = time.time()
199 cstop = time.time()
200 ostop = os.times()
200 ostop = os.times()
201 count += 1
201 count += 1
202 a, b = ostart, ostop
202 a, b = ostart, ostop
203 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
203 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
204 if cstop - begin > 3 and count >= 100:
204 if cstop - begin > 3 and count >= 100:
205 break
205 break
206 if cstop - begin > 10 and count >= 3:
206 if cstop - begin > 10 and count >= 3:
207 break
207 break
208
208
209 fm.startitem()
209 fm.startitem()
210
210
211 if title:
211 if title:
212 fm.write('title', '! %s\n', title)
212 fm.write('title', '! %s\n', title)
213 if r:
213 if r:
214 fm.write('result', '! result: %s\n', r)
214 fm.write('result', '! result: %s\n', r)
215 m = min(results)
215 m = min(results)
216 fm.plain('!')
216 fm.plain('!')
217 fm.write('wall', ' wall %f', m[0])
217 fm.write('wall', ' wall %f', m[0])
218 fm.write('comb', ' comb %f', m[1] + m[2])
218 fm.write('comb', ' comb %f', m[1] + m[2])
219 fm.write('user', ' user %f', m[1])
219 fm.write('user', ' user %f', m[1])
220 fm.write('sys', ' sys %f', m[2])
220 fm.write('sys', ' sys %f', m[2])
221 fm.write('count', ' (best of %d)', count)
221 fm.write('count', ' (best of %d)', count)
222 fm.plain('\n')
222 fm.plain('\n')
223
223
224 # utilities for historical portability
224 # utilities for historical portability
225
225
226 def getint(ui, section, name, default):
226 def getint(ui, section, name, default):
227 # for "historical portability":
227 # for "historical portability":
228 # ui.configint has been available since 1.9 (or fa2b596db182)
228 # ui.configint has been available since 1.9 (or fa2b596db182)
229 v = ui.config(section, name, None)
229 v = ui.config(section, name, None)
230 if v is None:
230 if v is None:
231 return default
231 return default
232 try:
232 try:
233 return int(v)
233 return int(v)
234 except ValueError:
234 except ValueError:
235 raise error.ConfigError(("%s.%s is not an integer ('%s')")
235 raise error.ConfigError(("%s.%s is not an integer ('%s')")
236 % (section, name, v))
236 % (section, name, v))
237
237
238 def safeattrsetter(obj, name, ignoremissing=False):
238 def safeattrsetter(obj, name, ignoremissing=False):
239 """Ensure that 'obj' has 'name' attribute before subsequent setattr
239 """Ensure that 'obj' has 'name' attribute before subsequent setattr
240
240
241 This function is aborted, if 'obj' doesn't have 'name' attribute
241 This function is aborted, if 'obj' doesn't have 'name' attribute
242 at runtime. This avoids overlooking removal of an attribute, which
242 at runtime. This avoids overlooking removal of an attribute, which
243 breaks assumption of performance measurement, in the future.
243 breaks assumption of performance measurement, in the future.
244
244
245 This function returns the object to (1) assign a new value, and
245 This function returns the object to (1) assign a new value, and
246 (2) restore an original value to the attribute.
246 (2) restore an original value to the attribute.
247
247
248 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
248 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
249 abortion, and this function returns None. This is useful to
249 abortion, and this function returns None. This is useful to
250 examine an attribute, which isn't ensured in all Mercurial
250 examine an attribute, which isn't ensured in all Mercurial
251 versions.
251 versions.
252 """
252 """
253 if not util.safehasattr(obj, name):
253 if not util.safehasattr(obj, name):
254 if ignoremissing:
254 if ignoremissing:
255 return None
255 return None
256 raise error.Abort(("missing attribute %s of %s might break assumption"
256 raise error.Abort(("missing attribute %s of %s might break assumption"
257 " of performance measurement") % (name, obj))
257 " of performance measurement") % (name, obj))
258
258
259 origvalue = getattr(obj, name)
259 origvalue = getattr(obj, name)
260 class attrutil(object):
260 class attrutil(object):
261 def set(self, newvalue):
261 def set(self, newvalue):
262 setattr(obj, name, newvalue)
262 setattr(obj, name, newvalue)
263 def restore(self):
263 def restore(self):
264 setattr(obj, name, origvalue)
264 setattr(obj, name, origvalue)
265
265
266 return attrutil()
266 return attrutil()
267
267
268 # utilities to examine each internal API changes
268 # utilities to examine each internal API changes
269
269
270 def getbranchmapsubsettable():
270 def getbranchmapsubsettable():
271 # for "historical portability":
271 # for "historical portability":
272 # subsettable is defined in:
272 # subsettable is defined in:
273 # - branchmap since 2.9 (or 175c6fd8cacc)
273 # - branchmap since 2.9 (or 175c6fd8cacc)
274 # - repoview since 2.5 (or 59a9f18d4587)
274 # - repoview since 2.5 (or 59a9f18d4587)
275 for mod in (branchmap, repoview):
275 for mod in (branchmap, repoview):
276 subsettable = getattr(mod, 'subsettable', None)
276 subsettable = getattr(mod, 'subsettable', None)
277 if subsettable:
277 if subsettable:
278 return subsettable
278 return subsettable
279
279
280 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
280 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
281 # branchmap and repoview modules exist, but subsettable attribute
281 # branchmap and repoview modules exist, but subsettable attribute
282 # doesn't)
282 # doesn't)
283 raise error.Abort(("perfbranchmap not available with this Mercurial"),
283 raise error.Abort(("perfbranchmap not available with this Mercurial"),
284 hint="use 2.5 or later")
284 hint="use 2.5 or later")
285
285
286 def getsvfs(repo):
286 def getsvfs(repo):
287 """Return appropriate object to access files under .hg/store
287 """Return appropriate object to access files under .hg/store
288 """
288 """
289 # for "historical portability":
289 # for "historical portability":
290 # repo.svfs has been available since 2.3 (or 7034365089bf)
290 # repo.svfs has been available since 2.3 (or 7034365089bf)
291 svfs = getattr(repo, 'svfs', None)
291 svfs = getattr(repo, 'svfs', None)
292 if svfs:
292 if svfs:
293 return svfs
293 return svfs
294 else:
294 else:
295 return getattr(repo, 'sopener')
295 return getattr(repo, 'sopener')
296
296
297 def getvfs(repo):
297 def getvfs(repo):
298 """Return appropriate object to access files under .hg
298 """Return appropriate object to access files under .hg
299 """
299 """
300 # for "historical portability":
300 # for "historical portability":
301 # repo.vfs has been available since 2.3 (or 7034365089bf)
301 # repo.vfs has been available since 2.3 (or 7034365089bf)
302 vfs = getattr(repo, 'vfs', None)
302 vfs = getattr(repo, 'vfs', None)
303 if vfs:
303 if vfs:
304 return vfs
304 return vfs
305 else:
305 else:
306 return getattr(repo, 'opener')
306 return getattr(repo, 'opener')
307
307
308 def repocleartagscachefunc(repo):
308 def repocleartagscachefunc(repo):
309 """Return the function to clear tags cache according to repo internal API
309 """Return the function to clear tags cache according to repo internal API
310 """
310 """
311 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
311 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
312 # in this case, setattr(repo, '_tagscache', None) or so isn't
312 # in this case, setattr(repo, '_tagscache', None) or so isn't
313 # correct way to clear tags cache, because existing code paths
313 # correct way to clear tags cache, because existing code paths
314 # expect _tagscache to be a structured object.
314 # expect _tagscache to be a structured object.
315 def clearcache():
315 def clearcache():
316 # _tagscache has been filteredpropertycache since 2.5 (or
316 # _tagscache has been filteredpropertycache since 2.5 (or
317 # 98c867ac1330), and delattr() can't work in such case
317 # 98c867ac1330), and delattr() can't work in such case
318 if '_tagscache' in vars(repo):
318 if '_tagscache' in vars(repo):
319 del repo.__dict__['_tagscache']
319 del repo.__dict__['_tagscache']
320 return clearcache
320 return clearcache
321
321
322 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
322 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
323 if repotags: # since 1.4 (or 5614a628d173)
323 if repotags: # since 1.4 (or 5614a628d173)
324 return lambda : repotags.set(None)
324 return lambda : repotags.set(None)
325
325
326 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
326 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
327 if repotagscache: # since 0.6 (or d7df759d0e97)
327 if repotagscache: # since 0.6 (or d7df759d0e97)
328 return lambda : repotagscache.set(None)
328 return lambda : repotagscache.set(None)
329
329
330 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
330 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
331 # this point, but it isn't so problematic, because:
331 # this point, but it isn't so problematic, because:
332 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
332 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
333 # in perftags() causes failure soon
333 # in perftags() causes failure soon
334 # - perf.py itself has been available since 1.1 (or eb240755386d)
334 # - perf.py itself has been available since 1.1 (or eb240755386d)
335 raise error.Abort(("tags API of this hg command is unknown"))
335 raise error.Abort(("tags API of this hg command is unknown"))
336
336
337 # perf commands
337 # perf commands
338
338
339 @command('perfwalk', formatteropts)
339 @command('perfwalk', formatteropts)
340 def perfwalk(ui, repo, *pats, **opts):
340 def perfwalk(ui, repo, *pats, **opts):
341 timer, fm = gettimer(ui, opts)
341 timer, fm = gettimer(ui, opts)
342 try:
342 try:
343 m = scmutil.match(repo[None], pats, {})
343 m = scmutil.match(repo[None], pats, {})
344 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
344 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
345 except Exception:
345 except Exception:
346 try:
346 try:
347 m = scmutil.match(repo[None], pats, {})
347 m = scmutil.match(repo[None], pats, {})
348 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
348 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
349 except Exception:
349 except Exception:
350 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
350 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
351 fm.end()
351 fm.end()
352
352
353 @command('perfannotate', formatteropts)
353 @command('perfannotate', formatteropts)
354 def perfannotate(ui, repo, f, **opts):
354 def perfannotate(ui, repo, f, **opts):
355 timer, fm = gettimer(ui, opts)
355 timer, fm = gettimer(ui, opts)
356 fc = repo['.'][f]
356 fc = repo['.'][f]
357 timer(lambda: len(fc.annotate(True)))
357 timer(lambda: len(fc.annotate(True)))
358 fm.end()
358 fm.end()
359
359
360 @command('perfstatus',
360 @command('perfstatus',
361 [('u', 'unknown', False,
361 [('u', 'unknown', False,
362 'ask status to look for unknown files')] + formatteropts)
362 'ask status to look for unknown files')] + formatteropts)
363 def perfstatus(ui, repo, **opts):
363 def perfstatus(ui, repo, **opts):
364 #m = match.always(repo.root, repo.getcwd())
364 #m = match.always(repo.root, repo.getcwd())
365 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
365 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
366 # False))))
366 # False))))
367 timer, fm = gettimer(ui, opts)
367 timer, fm = gettimer(ui, opts)
368 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
368 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
369 fm.end()
369 fm.end()
370
370
371 @command('perfaddremove', formatteropts)
371 @command('perfaddremove', formatteropts)
372 def perfaddremove(ui, repo, **opts):
372 def perfaddremove(ui, repo, **opts):
373 timer, fm = gettimer(ui, opts)
373 timer, fm = gettimer(ui, opts)
374 try:
374 try:
375 oldquiet = repo.ui.quiet
375 oldquiet = repo.ui.quiet
376 repo.ui.quiet = True
376 repo.ui.quiet = True
377 matcher = scmutil.match(repo[None])
377 matcher = scmutil.match(repo[None])
378 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
378 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
379 finally:
379 finally:
380 repo.ui.quiet = oldquiet
380 repo.ui.quiet = oldquiet
381 fm.end()
381 fm.end()
382
382
383 def clearcaches(cl):
383 def clearcaches(cl):
384 # behave somewhat consistently across internal API changes
384 # behave somewhat consistently across internal API changes
385 if util.safehasattr(cl, 'clearcaches'):
385 if util.safehasattr(cl, 'clearcaches'):
386 cl.clearcaches()
386 cl.clearcaches()
387 elif util.safehasattr(cl, '_nodecache'):
387 elif util.safehasattr(cl, '_nodecache'):
388 from mercurial.node import nullid, nullrev
388 from mercurial.node import nullid, nullrev
389 cl._nodecache = {nullid: nullrev}
389 cl._nodecache = {nullid: nullrev}
390 cl._nodepos = None
390 cl._nodepos = None
391
391
392 @command('perfheads', formatteropts)
392 @command('perfheads', formatteropts)
393 def perfheads(ui, repo, **opts):
393 def perfheads(ui, repo, **opts):
394 timer, fm = gettimer(ui, opts)
394 timer, fm = gettimer(ui, opts)
395 cl = repo.changelog
395 cl = repo.changelog
396 def d():
396 def d():
397 len(cl.headrevs())
397 len(cl.headrevs())
398 clearcaches(cl)
398 clearcaches(cl)
399 timer(d)
399 timer(d)
400 fm.end()
400 fm.end()
401
401
402 @command('perftags', formatteropts)
402 @command('perftags', formatteropts)
403 def perftags(ui, repo, **opts):
403 def perftags(ui, repo, **opts):
404 import mercurial.changelog
404 import mercurial.changelog
405 import mercurial.manifest
405 import mercurial.manifest
406 timer, fm = gettimer(ui, opts)
406 timer, fm = gettimer(ui, opts)
407 svfs = getsvfs(repo)
407 svfs = getsvfs(repo)
408 repocleartagscache = repocleartagscachefunc(repo)
408 repocleartagscache = repocleartagscachefunc(repo)
409 def t():
409 def t():
410 repo.changelog = mercurial.changelog.changelog(svfs)
410 repo.changelog = mercurial.changelog.changelog(svfs)
411 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
411 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
412 repocleartagscache()
412 repocleartagscache()
413 return len(repo.tags())
413 return len(repo.tags())
414 timer(t)
414 timer(t)
415 fm.end()
415 fm.end()
416
416
417 @command('perfancestors', formatteropts)
417 @command('perfancestors', formatteropts)
418 def perfancestors(ui, repo, **opts):
418 def perfancestors(ui, repo, **opts):
419 timer, fm = gettimer(ui, opts)
419 timer, fm = gettimer(ui, opts)
420 heads = repo.changelog.headrevs()
420 heads = repo.changelog.headrevs()
421 def d():
421 def d():
422 for a in repo.changelog.ancestors(heads):
422 for a in repo.changelog.ancestors(heads):
423 pass
423 pass
424 timer(d)
424 timer(d)
425 fm.end()
425 fm.end()
426
426
427 @command('perfancestorset', formatteropts)
427 @command('perfancestorset', formatteropts)
428 def perfancestorset(ui, repo, revset, **opts):
428 def perfancestorset(ui, repo, revset, **opts):
429 timer, fm = gettimer(ui, opts)
429 timer, fm = gettimer(ui, opts)
430 revs = repo.revs(revset)
430 revs = repo.revs(revset)
431 heads = repo.changelog.headrevs()
431 heads = repo.changelog.headrevs()
432 def d():
432 def d():
433 s = repo.changelog.ancestors(heads)
433 s = repo.changelog.ancestors(heads)
434 for rev in revs:
434 for rev in revs:
435 rev in s
435 rev in s
436 timer(d)
436 timer(d)
437 fm.end()
437 fm.end()
438
438
439 @command('perfchangegroupchangelog', formatteropts +
439 @command('perfchangegroupchangelog', formatteropts +
440 [('', 'version', '02', 'changegroup version'),
440 [('', 'version', '02', 'changegroup version'),
441 ('r', 'rev', '', 'revisions to add to changegroup')])
441 ('r', 'rev', '', 'revisions to add to changegroup')])
442 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
442 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
443 """Benchmark producing a changelog group for a changegroup.
443 """Benchmark producing a changelog group for a changegroup.
444
444
445 This measures the time spent processing the changelog during a
445 This measures the time spent processing the changelog during a
446 bundle operation. This occurs during `hg bundle` and on a server
446 bundle operation. This occurs during `hg bundle` and on a server
447 processing a `getbundle` wire protocol request (handles clones
447 processing a `getbundle` wire protocol request (handles clones
448 and pull requests).
448 and pull requests).
449
449
450 By default, all revisions are added to the changegroup.
450 By default, all revisions are added to the changegroup.
451 """
451 """
452 cl = repo.changelog
452 cl = repo.changelog
453 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
453 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
454 bundler = changegroup.getbundler(version, repo)
454 bundler = changegroup.getbundler(version, repo)
455
455
456 def lookup(node):
456 def lookup(node):
457 # The real bundler reads the revision in order to access the
457 # The real bundler reads the revision in order to access the
458 # manifest node and files list. Do that here.
458 # manifest node and files list. Do that here.
459 cl.read(node)
459 cl.read(node)
460 return node
460 return node
461
461
462 def d():
462 def d():
463 for chunk in bundler.group(revs, cl, lookup):
463 for chunk in bundler.group(revs, cl, lookup):
464 pass
464 pass
465
465
466 timer, fm = gettimer(ui, opts)
466 timer, fm = gettimer(ui, opts)
467 timer(d)
467 timer(d)
468 fm.end()
468 fm.end()
469
469
470 @command('perfdirs', formatteropts)
470 @command('perfdirs', formatteropts)
471 def perfdirs(ui, repo, **opts):
471 def perfdirs(ui, repo, **opts):
472 timer, fm = gettimer(ui, opts)
472 timer, fm = gettimer(ui, opts)
473 dirstate = repo.dirstate
473 dirstate = repo.dirstate
474 'a' in dirstate
474 'a' in dirstate
475 def d():
475 def d():
476 dirstate.dirs()
476 dirstate.dirs()
477 del dirstate._dirs
477 del dirstate._dirs
478 timer(d)
478 timer(d)
479 fm.end()
479 fm.end()
480
480
481 @command('perfdirstate', formatteropts)
481 @command('perfdirstate', formatteropts)
482 def perfdirstate(ui, repo, **opts):
482 def perfdirstate(ui, repo, **opts):
483 timer, fm = gettimer(ui, opts)
483 timer, fm = gettimer(ui, opts)
484 "a" in repo.dirstate
484 "a" in repo.dirstate
485 def d():
485 def d():
486 repo.dirstate.invalidate()
486 repo.dirstate.invalidate()
487 "a" in repo.dirstate
487 "a" in repo.dirstate
488 timer(d)
488 timer(d)
489 fm.end()
489 fm.end()
490
490
491 @command('perfdirstatedirs', formatteropts)
491 @command('perfdirstatedirs', formatteropts)
492 def perfdirstatedirs(ui, repo, **opts):
492 def perfdirstatedirs(ui, repo, **opts):
493 timer, fm = gettimer(ui, opts)
493 timer, fm = gettimer(ui, opts)
494 "a" in repo.dirstate
494 "a" in repo.dirstate
495 def d():
495 def d():
496 "a" in repo.dirstate._dirs
496 "a" in repo.dirstate._dirs
497 del repo.dirstate._dirs
497 del repo.dirstate._dirs
498 timer(d)
498 timer(d)
499 fm.end()
499 fm.end()
500
500
501 @command('perfdirstatefoldmap', formatteropts)
501 @command('perfdirstatefoldmap', formatteropts)
502 def perfdirstatefoldmap(ui, repo, **opts):
502 def perfdirstatefoldmap(ui, repo, **opts):
503 timer, fm = gettimer(ui, opts)
503 timer, fm = gettimer(ui, opts)
504 dirstate = repo.dirstate
504 dirstate = repo.dirstate
505 'a' in dirstate
505 'a' in dirstate
506 def d():
506 def d():
507 dirstate._filefoldmap.get('a')
507 dirstate._filefoldmap.get('a')
508 del dirstate._filefoldmap
508 del dirstate._filefoldmap
509 timer(d)
509 timer(d)
510 fm.end()
510 fm.end()
511
511
512 @command('perfdirfoldmap', formatteropts)
512 @command('perfdirfoldmap', formatteropts)
513 def perfdirfoldmap(ui, repo, **opts):
513 def perfdirfoldmap(ui, repo, **opts):
514 timer, fm = gettimer(ui, opts)
514 timer, fm = gettimer(ui, opts)
515 dirstate = repo.dirstate
515 dirstate = repo.dirstate
516 'a' in dirstate
516 'a' in dirstate
517 def d():
517 def d():
518 dirstate._dirfoldmap.get('a')
518 dirstate._dirfoldmap.get('a')
519 del dirstate._dirfoldmap
519 del dirstate._dirfoldmap
520 del dirstate._dirs
520 del dirstate._dirs
521 timer(d)
521 timer(d)
522 fm.end()
522 fm.end()
523
523
524 @command('perfdirstatewrite', formatteropts)
524 @command('perfdirstatewrite', formatteropts)
525 def perfdirstatewrite(ui, repo, **opts):
525 def perfdirstatewrite(ui, repo, **opts):
526 timer, fm = gettimer(ui, opts)
526 timer, fm = gettimer(ui, opts)
527 ds = repo.dirstate
527 ds = repo.dirstate
528 "a" in ds
528 "a" in ds
529 def d():
529 def d():
530 ds._dirty = True
530 ds._dirty = True
531 ds.write(repo.currenttransaction())
531 ds.write(repo.currenttransaction())
532 timer(d)
532 timer(d)
533 fm.end()
533 fm.end()
534
534
535 @command('perfmergecalculate',
535 @command('perfmergecalculate',
536 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
536 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
537 def perfmergecalculate(ui, repo, rev, **opts):
537 def perfmergecalculate(ui, repo, rev, **opts):
538 timer, fm = gettimer(ui, opts)
538 timer, fm = gettimer(ui, opts)
539 wctx = repo[None]
539 wctx = repo[None]
540 rctx = scmutil.revsingle(repo, rev, rev)
540 rctx = scmutil.revsingle(repo, rev, rev)
541 ancestor = wctx.ancestor(rctx)
541 ancestor = wctx.ancestor(rctx)
542 # we don't want working dir files to be stat'd in the benchmark, so prime
542 # we don't want working dir files to be stat'd in the benchmark, so prime
543 # that cache
543 # that cache
544 wctx.dirty()
544 wctx.dirty()
545 def d():
545 def d():
546 # acceptremote is True because we don't want prompts in the middle of
546 # acceptremote is True because we don't want prompts in the middle of
547 # our benchmark
547 # our benchmark
548 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
548 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
549 acceptremote=True, followcopies=True)
549 acceptremote=True, followcopies=True)
550 timer(d)
550 timer(d)
551 fm.end()
551 fm.end()
552
552
553 @command('perfpathcopies', [], "REV REV")
553 @command('perfpathcopies', [], "REV REV")
554 def perfpathcopies(ui, repo, rev1, rev2, **opts):
554 def perfpathcopies(ui, repo, rev1, rev2, **opts):
555 timer, fm = gettimer(ui, opts)
555 timer, fm = gettimer(ui, opts)
556 ctx1 = scmutil.revsingle(repo, rev1, rev1)
556 ctx1 = scmutil.revsingle(repo, rev1, rev1)
557 ctx2 = scmutil.revsingle(repo, rev2, rev2)
557 ctx2 = scmutil.revsingle(repo, rev2, rev2)
558 def d():
558 def d():
559 copies.pathcopies(ctx1, ctx2)
559 copies.pathcopies(ctx1, ctx2)
560 timer(d)
560 timer(d)
561 fm.end()
561 fm.end()
562
562
563 @command('perfmanifest', [], 'REV')
563 @command('perfmanifest', [], 'REV')
564 def perfmanifest(ui, repo, rev, **opts):
564 def perfmanifest(ui, repo, rev, **opts):
565 timer, fm = gettimer(ui, opts)
565 timer, fm = gettimer(ui, opts)
566 ctx = scmutil.revsingle(repo, rev, rev)
566 ctx = scmutil.revsingle(repo, rev, rev)
567 t = ctx.manifestnode()
567 t = ctx.manifestnode()
568 def d():
568 def d():
569 repo.manifest.clearcaches()
569 repo.manifestlog.clearcaches()
570 repo.manifestlog[t].read()
570 repo.manifestlog[t].read()
571 timer(d)
571 timer(d)
572 fm.end()
572 fm.end()
573
573
574 @command('perfchangeset', formatteropts)
574 @command('perfchangeset', formatteropts)
575 def perfchangeset(ui, repo, rev, **opts):
575 def perfchangeset(ui, repo, rev, **opts):
576 timer, fm = gettimer(ui, opts)
576 timer, fm = gettimer(ui, opts)
577 n = repo[rev].node()
577 n = repo[rev].node()
578 def d():
578 def d():
579 repo.changelog.read(n)
579 repo.changelog.read(n)
580 #repo.changelog._cache = None
580 #repo.changelog._cache = None
581 timer(d)
581 timer(d)
582 fm.end()
582 fm.end()
583
583
584 @command('perfindex', formatteropts)
584 @command('perfindex', formatteropts)
585 def perfindex(ui, repo, **opts):
585 def perfindex(ui, repo, **opts):
586 import mercurial.revlog
586 import mercurial.revlog
587 timer, fm = gettimer(ui, opts)
587 timer, fm = gettimer(ui, opts)
588 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
588 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
589 n = repo["tip"].node()
589 n = repo["tip"].node()
590 svfs = getsvfs(repo)
590 svfs = getsvfs(repo)
591 def d():
591 def d():
592 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
592 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
593 cl.rev(n)
593 cl.rev(n)
594 timer(d)
594 timer(d)
595 fm.end()
595 fm.end()
596
596
597 @command('perfstartup', formatteropts)
597 @command('perfstartup', formatteropts)
598 def perfstartup(ui, repo, **opts):
598 def perfstartup(ui, repo, **opts):
599 timer, fm = gettimer(ui, opts)
599 timer, fm = gettimer(ui, opts)
600 cmd = sys.argv[0]
600 cmd = sys.argv[0]
601 def d():
601 def d():
602 if os.name != 'nt':
602 if os.name != 'nt':
603 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
603 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
604 else:
604 else:
605 os.environ['HGRCPATH'] = ''
605 os.environ['HGRCPATH'] = ''
606 os.system("%s version -q > NUL" % cmd)
606 os.system("%s version -q > NUL" % cmd)
607 timer(d)
607 timer(d)
608 fm.end()
608 fm.end()
609
609
610 @command('perfparents', formatteropts)
610 @command('perfparents', formatteropts)
611 def perfparents(ui, repo, **opts):
611 def perfparents(ui, repo, **opts):
612 timer, fm = gettimer(ui, opts)
612 timer, fm = gettimer(ui, opts)
613 # control the number of commits perfparents iterates over
613 # control the number of commits perfparents iterates over
614 # experimental config: perf.parentscount
614 # experimental config: perf.parentscount
615 count = getint(ui, "perf", "parentscount", 1000)
615 count = getint(ui, "perf", "parentscount", 1000)
616 if len(repo.changelog) < count:
616 if len(repo.changelog) < count:
617 raise error.Abort("repo needs %d commits for this test" % count)
617 raise error.Abort("repo needs %d commits for this test" % count)
618 repo = repo.unfiltered()
618 repo = repo.unfiltered()
619 nl = [repo.changelog.node(i) for i in xrange(count)]
619 nl = [repo.changelog.node(i) for i in xrange(count)]
620 def d():
620 def d():
621 for n in nl:
621 for n in nl:
622 repo.changelog.parents(n)
622 repo.changelog.parents(n)
623 timer(d)
623 timer(d)
624 fm.end()
624 fm.end()
625
625
626 @command('perfctxfiles', formatteropts)
626 @command('perfctxfiles', formatteropts)
627 def perfctxfiles(ui, repo, x, **opts):
627 def perfctxfiles(ui, repo, x, **opts):
628 x = int(x)
628 x = int(x)
629 timer, fm = gettimer(ui, opts)
629 timer, fm = gettimer(ui, opts)
630 def d():
630 def d():
631 len(repo[x].files())
631 len(repo[x].files())
632 timer(d)
632 timer(d)
633 fm.end()
633 fm.end()
634
634
635 @command('perfrawfiles', formatteropts)
635 @command('perfrawfiles', formatteropts)
636 def perfrawfiles(ui, repo, x, **opts):
636 def perfrawfiles(ui, repo, x, **opts):
637 x = int(x)
637 x = int(x)
638 timer, fm = gettimer(ui, opts)
638 timer, fm = gettimer(ui, opts)
639 cl = repo.changelog
639 cl = repo.changelog
640 def d():
640 def d():
641 len(cl.read(x)[3])
641 len(cl.read(x)[3])
642 timer(d)
642 timer(d)
643 fm.end()
643 fm.end()
644
644
645 @command('perflookup', formatteropts)
645 @command('perflookup', formatteropts)
646 def perflookup(ui, repo, rev, **opts):
646 def perflookup(ui, repo, rev, **opts):
647 timer, fm = gettimer(ui, opts)
647 timer, fm = gettimer(ui, opts)
648 timer(lambda: len(repo.lookup(rev)))
648 timer(lambda: len(repo.lookup(rev)))
649 fm.end()
649 fm.end()
650
650
651 @command('perfrevrange', formatteropts)
651 @command('perfrevrange', formatteropts)
652 def perfrevrange(ui, repo, *specs, **opts):
652 def perfrevrange(ui, repo, *specs, **opts):
653 timer, fm = gettimer(ui, opts)
653 timer, fm = gettimer(ui, opts)
654 revrange = scmutil.revrange
654 revrange = scmutil.revrange
655 timer(lambda: len(revrange(repo, specs)))
655 timer(lambda: len(revrange(repo, specs)))
656 fm.end()
656 fm.end()
657
657
658 @command('perfnodelookup', formatteropts)
658 @command('perfnodelookup', formatteropts)
659 def perfnodelookup(ui, repo, rev, **opts):
659 def perfnodelookup(ui, repo, rev, **opts):
660 timer, fm = gettimer(ui, opts)
660 timer, fm = gettimer(ui, opts)
661 import mercurial.revlog
661 import mercurial.revlog
662 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
662 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
663 n = repo[rev].node()
663 n = repo[rev].node()
664 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
664 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
665 def d():
665 def d():
666 cl.rev(n)
666 cl.rev(n)
667 clearcaches(cl)
667 clearcaches(cl)
668 timer(d)
668 timer(d)
669 fm.end()
669 fm.end()
670
670
671 @command('perflog',
671 @command('perflog',
672 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
672 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
673 def perflog(ui, repo, rev=None, **opts):
673 def perflog(ui, repo, rev=None, **opts):
674 if rev is None:
674 if rev is None:
675 rev=[]
675 rev=[]
676 timer, fm = gettimer(ui, opts)
676 timer, fm = gettimer(ui, opts)
677 ui.pushbuffer()
677 ui.pushbuffer()
678 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
678 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
679 copies=opts.get('rename')))
679 copies=opts.get('rename')))
680 ui.popbuffer()
680 ui.popbuffer()
681 fm.end()
681 fm.end()
682
682
683 @command('perfmoonwalk', formatteropts)
683 @command('perfmoonwalk', formatteropts)
684 def perfmoonwalk(ui, repo, **opts):
684 def perfmoonwalk(ui, repo, **opts):
685 """benchmark walking the changelog backwards
685 """benchmark walking the changelog backwards
686
686
687 This also loads the changelog data for each revision in the changelog.
687 This also loads the changelog data for each revision in the changelog.
688 """
688 """
689 timer, fm = gettimer(ui, opts)
689 timer, fm = gettimer(ui, opts)
690 def moonwalk():
690 def moonwalk():
691 for i in xrange(len(repo), -1, -1):
691 for i in xrange(len(repo), -1, -1):
692 ctx = repo[i]
692 ctx = repo[i]
693 ctx.branch() # read changelog data (in addition to the index)
693 ctx.branch() # read changelog data (in addition to the index)
694 timer(moonwalk)
694 timer(moonwalk)
695 fm.end()
695 fm.end()
696
696
697 @command('perftemplating', formatteropts)
697 @command('perftemplating', formatteropts)
698 def perftemplating(ui, repo, rev=None, **opts):
698 def perftemplating(ui, repo, rev=None, **opts):
699 if rev is None:
699 if rev is None:
700 rev=[]
700 rev=[]
701 timer, fm = gettimer(ui, opts)
701 timer, fm = gettimer(ui, opts)
702 ui.pushbuffer()
702 ui.pushbuffer()
703 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
703 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
704 template='{date|shortdate} [{rev}:{node|short}]'
704 template='{date|shortdate} [{rev}:{node|short}]'
705 ' {author|person}: {desc|firstline}\n'))
705 ' {author|person}: {desc|firstline}\n'))
706 ui.popbuffer()
706 ui.popbuffer()
707 fm.end()
707 fm.end()
708
708
709 @command('perfcca', formatteropts)
709 @command('perfcca', formatteropts)
710 def perfcca(ui, repo, **opts):
710 def perfcca(ui, repo, **opts):
711 timer, fm = gettimer(ui, opts)
711 timer, fm = gettimer(ui, opts)
712 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
712 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
713 fm.end()
713 fm.end()
714
714
715 @command('perffncacheload', formatteropts)
715 @command('perffncacheload', formatteropts)
716 def perffncacheload(ui, repo, **opts):
716 def perffncacheload(ui, repo, **opts):
717 timer, fm = gettimer(ui, opts)
717 timer, fm = gettimer(ui, opts)
718 s = repo.store
718 s = repo.store
719 def d():
719 def d():
720 s.fncache._load()
720 s.fncache._load()
721 timer(d)
721 timer(d)
722 fm.end()
722 fm.end()
723
723
724 @command('perffncachewrite', formatteropts)
724 @command('perffncachewrite', formatteropts)
725 def perffncachewrite(ui, repo, **opts):
725 def perffncachewrite(ui, repo, **opts):
726 timer, fm = gettimer(ui, opts)
726 timer, fm = gettimer(ui, opts)
727 s = repo.store
727 s = repo.store
728 s.fncache._load()
728 s.fncache._load()
729 lock = repo.lock()
729 lock = repo.lock()
730 tr = repo.transaction('perffncachewrite')
730 tr = repo.transaction('perffncachewrite')
731 def d():
731 def d():
732 s.fncache._dirty = True
732 s.fncache._dirty = True
733 s.fncache.write(tr)
733 s.fncache.write(tr)
734 timer(d)
734 timer(d)
735 tr.close()
735 tr.close()
736 lock.release()
736 lock.release()
737 fm.end()
737 fm.end()
738
738
739 @command('perffncacheencode', formatteropts)
739 @command('perffncacheencode', formatteropts)
740 def perffncacheencode(ui, repo, **opts):
740 def perffncacheencode(ui, repo, **opts):
741 timer, fm = gettimer(ui, opts)
741 timer, fm = gettimer(ui, opts)
742 s = repo.store
742 s = repo.store
743 s.fncache._load()
743 s.fncache._load()
744 def d():
744 def d():
745 for p in s.fncache.entries:
745 for p in s.fncache.entries:
746 s.encode(p)
746 s.encode(p)
747 timer(d)
747 timer(d)
748 fm.end()
748 fm.end()
749
749
750 @command('perfbdiff', revlogopts + formatteropts + [
750 @command('perfbdiff', revlogopts + formatteropts + [
751 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
751 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
752 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
752 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
753 '-c|-m|FILE REV')
753 '-c|-m|FILE REV')
754 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
754 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
755 """benchmark a bdiff between revisions
755 """benchmark a bdiff between revisions
756
756
757 By default, benchmark a bdiff between its delta parent and itself.
757 By default, benchmark a bdiff between its delta parent and itself.
758
758
759 With ``--count``, benchmark bdiffs between delta parents and self for N
759 With ``--count``, benchmark bdiffs between delta parents and self for N
760 revisions starting at the specified revision.
760 revisions starting at the specified revision.
761
761
762 With ``--alldata``, assume the requested revision is a changeset and
762 With ``--alldata``, assume the requested revision is a changeset and
763 measure bdiffs for all changes related to that changeset (manifest
763 measure bdiffs for all changes related to that changeset (manifest
764 and filelogs).
764 and filelogs).
765 """
765 """
766 if opts['alldata']:
766 if opts['alldata']:
767 opts['changelog'] = True
767 opts['changelog'] = True
768
768
769 if opts.get('changelog') or opts.get('manifest'):
769 if opts.get('changelog') or opts.get('manifest'):
770 file_, rev = None, file_
770 file_, rev = None, file_
771 elif rev is None:
771 elif rev is None:
772 raise error.CommandError('perfbdiff', 'invalid arguments')
772 raise error.CommandError('perfbdiff', 'invalid arguments')
773
773
774 textpairs = []
774 textpairs = []
775
775
776 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
776 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
777
777
778 startrev = r.rev(r.lookup(rev))
778 startrev = r.rev(r.lookup(rev))
779 for rev in range(startrev, min(startrev + count, len(r) - 1)):
779 for rev in range(startrev, min(startrev + count, len(r) - 1)):
780 if opts['alldata']:
780 if opts['alldata']:
781 # Load revisions associated with changeset.
781 # Load revisions associated with changeset.
782 ctx = repo[rev]
782 ctx = repo[rev]
783 mtext = repo.manifest.revision(ctx.manifestnode())
783 mtext = repo.manifest.revision(ctx.manifestnode())
784 for pctx in ctx.parents():
784 for pctx in ctx.parents():
785 pman = repo.manifest.revision(pctx.manifestnode())
785 pman = repo.manifest.revision(pctx.manifestnode())
786 textpairs.append((pman, mtext))
786 textpairs.append((pman, mtext))
787
787
788 # Load filelog revisions by iterating manifest delta.
788 # Load filelog revisions by iterating manifest delta.
789 man = ctx.manifest()
789 man = ctx.manifest()
790 pman = ctx.p1().manifest()
790 pman = ctx.p1().manifest()
791 for filename, change in pman.diff(man).items():
791 for filename, change in pman.diff(man).items():
792 fctx = repo.file(filename)
792 fctx = repo.file(filename)
793 f1 = fctx.revision(change[0][0] or -1)
793 f1 = fctx.revision(change[0][0] or -1)
794 f2 = fctx.revision(change[1][0] or -1)
794 f2 = fctx.revision(change[1][0] or -1)
795 textpairs.append((f1, f2))
795 textpairs.append((f1, f2))
796 else:
796 else:
797 dp = r.deltaparent(rev)
797 dp = r.deltaparent(rev)
798 textpairs.append((r.revision(dp), r.revision(rev)))
798 textpairs.append((r.revision(dp), r.revision(rev)))
799
799
800 def d():
800 def d():
801 for pair in textpairs:
801 for pair in textpairs:
802 bdiff.bdiff(*pair)
802 bdiff.bdiff(*pair)
803
803
804 timer, fm = gettimer(ui, opts)
804 timer, fm = gettimer(ui, opts)
805 timer(d)
805 timer(d)
806 fm.end()
806 fm.end()
807
807
808 @command('perfdiffwd', formatteropts)
808 @command('perfdiffwd', formatteropts)
809 def perfdiffwd(ui, repo, **opts):
809 def perfdiffwd(ui, repo, **opts):
810 """Profile diff of working directory changes"""
810 """Profile diff of working directory changes"""
811 timer, fm = gettimer(ui, opts)
811 timer, fm = gettimer(ui, opts)
812 options = {
812 options = {
813 'w': 'ignore_all_space',
813 'w': 'ignore_all_space',
814 'b': 'ignore_space_change',
814 'b': 'ignore_space_change',
815 'B': 'ignore_blank_lines',
815 'B': 'ignore_blank_lines',
816 }
816 }
817
817
818 for diffopt in ('', 'w', 'b', 'B', 'wB'):
818 for diffopt in ('', 'w', 'b', 'B', 'wB'):
819 opts = dict((options[c], '1') for c in diffopt)
819 opts = dict((options[c], '1') for c in diffopt)
820 def d():
820 def d():
821 ui.pushbuffer()
821 ui.pushbuffer()
822 commands.diff(ui, repo, **opts)
822 commands.diff(ui, repo, **opts)
823 ui.popbuffer()
823 ui.popbuffer()
824 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
824 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
825 timer(d, title)
825 timer(d, title)
826 fm.end()
826 fm.end()
827
827
828 @command('perfrevlog', revlogopts + formatteropts +
828 @command('perfrevlog', revlogopts + formatteropts +
829 [('d', 'dist', 100, 'distance between the revisions'),
829 [('d', 'dist', 100, 'distance between the revisions'),
830 ('s', 'startrev', 0, 'revision to start reading at'),
830 ('s', 'startrev', 0, 'revision to start reading at'),
831 ('', 'reverse', False, 'read in reverse')],
831 ('', 'reverse', False, 'read in reverse')],
832 '-c|-m|FILE')
832 '-c|-m|FILE')
833 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
833 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
834 """Benchmark reading a series of revisions from a revlog.
834 """Benchmark reading a series of revisions from a revlog.
835
835
836 By default, we read every ``-d/--dist`` revision from 0 to tip of
836 By default, we read every ``-d/--dist`` revision from 0 to tip of
837 the specified revlog.
837 the specified revlog.
838
838
839 The start revision can be defined via ``-s/--startrev``.
839 The start revision can be defined via ``-s/--startrev``.
840 """
840 """
841 timer, fm = gettimer(ui, opts)
841 timer, fm = gettimer(ui, opts)
842 _len = getlen(ui)
842 _len = getlen(ui)
843
843
844 def d():
844 def d():
845 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
845 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
846
846
847 startrev = 0
847 startrev = 0
848 endrev = _len(r)
848 endrev = _len(r)
849 dist = opts['dist']
849 dist = opts['dist']
850
850
851 if reverse:
851 if reverse:
852 startrev, endrev = endrev, startrev
852 startrev, endrev = endrev, startrev
853 dist = -1 * dist
853 dist = -1 * dist
854
854
855 for x in xrange(startrev, endrev, dist):
855 for x in xrange(startrev, endrev, dist):
856 r.revision(r.node(x))
856 r.revision(r.node(x))
857
857
858 timer(d)
858 timer(d)
859 fm.end()
859 fm.end()
860
860
861 @command('perfrevlogrevision', revlogopts + formatteropts +
861 @command('perfrevlogrevision', revlogopts + formatteropts +
862 [('', 'cache', False, 'use caches instead of clearing')],
862 [('', 'cache', False, 'use caches instead of clearing')],
863 '-c|-m|FILE REV')
863 '-c|-m|FILE REV')
864 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
864 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
865 """Benchmark obtaining a revlog revision.
865 """Benchmark obtaining a revlog revision.
866
866
867 Obtaining a revlog revision consists of roughly the following steps:
867 Obtaining a revlog revision consists of roughly the following steps:
868
868
869 1. Compute the delta chain
869 1. Compute the delta chain
870 2. Obtain the raw chunks for that delta chain
870 2. Obtain the raw chunks for that delta chain
871 3. Decompress each raw chunk
871 3. Decompress each raw chunk
872 4. Apply binary patches to obtain fulltext
872 4. Apply binary patches to obtain fulltext
873 5. Verify hash of fulltext
873 5. Verify hash of fulltext
874
874
875 This command measures the time spent in each of these phases.
875 This command measures the time spent in each of these phases.
876 """
876 """
877 if opts.get('changelog') or opts.get('manifest'):
877 if opts.get('changelog') or opts.get('manifest'):
878 file_, rev = None, file_
878 file_, rev = None, file_
879 elif rev is None:
879 elif rev is None:
880 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
880 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
881
881
882 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
882 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
883 node = r.lookup(rev)
883 node = r.lookup(rev)
884 rev = r.rev(node)
884 rev = r.rev(node)
885
885
886 def dodeltachain(rev):
886 def dodeltachain(rev):
887 if not cache:
887 if not cache:
888 r.clearcaches()
888 r.clearcaches()
889 r._deltachain(rev)
889 r._deltachain(rev)
890
890
891 def doread(chain):
891 def doread(chain):
892 if not cache:
892 if not cache:
893 r.clearcaches()
893 r.clearcaches()
894 r._chunkraw(chain[0], chain[-1])
894 r._chunkraw(chain[0], chain[-1])
895
895
896 def dodecompress(data, chain):
896 def dodecompress(data, chain):
897 if not cache:
897 if not cache:
898 r.clearcaches()
898 r.clearcaches()
899
899
900 start = r.start
900 start = r.start
901 length = r.length
901 length = r.length
902 inline = r._inline
902 inline = r._inline
903 iosize = r._io.size
903 iosize = r._io.size
904 buffer = util.buffer
904 buffer = util.buffer
905 offset = start(chain[0])
905 offset = start(chain[0])
906
906
907 for rev in chain:
907 for rev in chain:
908 chunkstart = start(rev)
908 chunkstart = start(rev)
909 if inline:
909 if inline:
910 chunkstart += (rev + 1) * iosize
910 chunkstart += (rev + 1) * iosize
911 chunklength = length(rev)
911 chunklength = length(rev)
912 b = buffer(data, chunkstart - offset, chunklength)
912 b = buffer(data, chunkstart - offset, chunklength)
913 revlog.decompress(b)
913 revlog.decompress(b)
914
914
915 def dopatch(text, bins):
915 def dopatch(text, bins):
916 if not cache:
916 if not cache:
917 r.clearcaches()
917 r.clearcaches()
918 mdiff.patches(text, bins)
918 mdiff.patches(text, bins)
919
919
920 def dohash(text):
920 def dohash(text):
921 if not cache:
921 if not cache:
922 r.clearcaches()
922 r.clearcaches()
923 r._checkhash(text, node, rev)
923 r._checkhash(text, node, rev)
924
924
925 def dorevision():
925 def dorevision():
926 if not cache:
926 if not cache:
927 r.clearcaches()
927 r.clearcaches()
928 r.revision(node)
928 r.revision(node)
929
929
930 chain = r._deltachain(rev)[0]
930 chain = r._deltachain(rev)[0]
931 data = r._chunkraw(chain[0], chain[-1])[1]
931 data = r._chunkraw(chain[0], chain[-1])[1]
932 bins = r._chunks(chain)
932 bins = r._chunks(chain)
933 text = str(bins[0])
933 text = str(bins[0])
934 bins = bins[1:]
934 bins = bins[1:]
935 text = mdiff.patches(text, bins)
935 text = mdiff.patches(text, bins)
936
936
937 benches = [
937 benches = [
938 (lambda: dorevision(), 'full'),
938 (lambda: dorevision(), 'full'),
939 (lambda: dodeltachain(rev), 'deltachain'),
939 (lambda: dodeltachain(rev), 'deltachain'),
940 (lambda: doread(chain), 'read'),
940 (lambda: doread(chain), 'read'),
941 (lambda: dodecompress(data, chain), 'decompress'),
941 (lambda: dodecompress(data, chain), 'decompress'),
942 (lambda: dopatch(text, bins), 'patch'),
942 (lambda: dopatch(text, bins), 'patch'),
943 (lambda: dohash(text), 'hash'),
943 (lambda: dohash(text), 'hash'),
944 ]
944 ]
945
945
946 for fn, title in benches:
946 for fn, title in benches:
947 timer, fm = gettimer(ui, opts)
947 timer, fm = gettimer(ui, opts)
948 timer(fn, title=title)
948 timer(fn, title=title)
949 fm.end()
949 fm.end()
950
950
951 @command('perfrevset',
951 @command('perfrevset',
952 [('C', 'clear', False, 'clear volatile cache between each call.'),
952 [('C', 'clear', False, 'clear volatile cache between each call.'),
953 ('', 'contexts', False, 'obtain changectx for each revision')]
953 ('', 'contexts', False, 'obtain changectx for each revision')]
954 + formatteropts, "REVSET")
954 + formatteropts, "REVSET")
955 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
955 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
956 """benchmark the execution time of a revset
956 """benchmark the execution time of a revset
957
957
958 Use the --clean option if need to evaluate the impact of build volatile
958 Use the --clean option if need to evaluate the impact of build volatile
959 revisions set cache on the revset execution. Volatile cache hold filtered
959 revisions set cache on the revset execution. Volatile cache hold filtered
960 and obsolete related cache."""
960 and obsolete related cache."""
961 timer, fm = gettimer(ui, opts)
961 timer, fm = gettimer(ui, opts)
962 def d():
962 def d():
963 if clear:
963 if clear:
964 repo.invalidatevolatilesets()
964 repo.invalidatevolatilesets()
965 if contexts:
965 if contexts:
966 for ctx in repo.set(expr): pass
966 for ctx in repo.set(expr): pass
967 else:
967 else:
968 for r in repo.revs(expr): pass
968 for r in repo.revs(expr): pass
969 timer(d)
969 timer(d)
970 fm.end()
970 fm.end()
971
971
972 @command('perfvolatilesets', formatteropts)
972 @command('perfvolatilesets', formatteropts)
973 def perfvolatilesets(ui, repo, *names, **opts):
973 def perfvolatilesets(ui, repo, *names, **opts):
974 """benchmark the computation of various volatile set
974 """benchmark the computation of various volatile set
975
975
976 Volatile set computes element related to filtering and obsolescence."""
976 Volatile set computes element related to filtering and obsolescence."""
977 timer, fm = gettimer(ui, opts)
977 timer, fm = gettimer(ui, opts)
978 repo = repo.unfiltered()
978 repo = repo.unfiltered()
979
979
980 def getobs(name):
980 def getobs(name):
981 def d():
981 def d():
982 repo.invalidatevolatilesets()
982 repo.invalidatevolatilesets()
983 obsolete.getrevs(repo, name)
983 obsolete.getrevs(repo, name)
984 return d
984 return d
985
985
986 allobs = sorted(obsolete.cachefuncs)
986 allobs = sorted(obsolete.cachefuncs)
987 if names:
987 if names:
988 allobs = [n for n in allobs if n in names]
988 allobs = [n for n in allobs if n in names]
989
989
990 for name in allobs:
990 for name in allobs:
991 timer(getobs(name), title=name)
991 timer(getobs(name), title=name)
992
992
993 def getfiltered(name):
993 def getfiltered(name):
994 def d():
994 def d():
995 repo.invalidatevolatilesets()
995 repo.invalidatevolatilesets()
996 repoview.filterrevs(repo, name)
996 repoview.filterrevs(repo, name)
997 return d
997 return d
998
998
999 allfilter = sorted(repoview.filtertable)
999 allfilter = sorted(repoview.filtertable)
1000 if names:
1000 if names:
1001 allfilter = [n for n in allfilter if n in names]
1001 allfilter = [n for n in allfilter if n in names]
1002
1002
1003 for name in allfilter:
1003 for name in allfilter:
1004 timer(getfiltered(name), title=name)
1004 timer(getfiltered(name), title=name)
1005 fm.end()
1005 fm.end()
1006
1006
1007 @command('perfbranchmap',
1007 @command('perfbranchmap',
1008 [('f', 'full', False,
1008 [('f', 'full', False,
1009 'Includes build time of subset'),
1009 'Includes build time of subset'),
1010 ] + formatteropts)
1010 ] + formatteropts)
1011 def perfbranchmap(ui, repo, full=False, **opts):
1011 def perfbranchmap(ui, repo, full=False, **opts):
1012 """benchmark the update of a branchmap
1012 """benchmark the update of a branchmap
1013
1013
1014 This benchmarks the full repo.branchmap() call with read and write disabled
1014 This benchmarks the full repo.branchmap() call with read and write disabled
1015 """
1015 """
1016 timer, fm = gettimer(ui, opts)
1016 timer, fm = gettimer(ui, opts)
1017 def getbranchmap(filtername):
1017 def getbranchmap(filtername):
1018 """generate a benchmark function for the filtername"""
1018 """generate a benchmark function for the filtername"""
1019 if filtername is None:
1019 if filtername is None:
1020 view = repo
1020 view = repo
1021 else:
1021 else:
1022 view = repo.filtered(filtername)
1022 view = repo.filtered(filtername)
1023 def d():
1023 def d():
1024 if full:
1024 if full:
1025 view._branchcaches.clear()
1025 view._branchcaches.clear()
1026 else:
1026 else:
1027 view._branchcaches.pop(filtername, None)
1027 view._branchcaches.pop(filtername, None)
1028 view.branchmap()
1028 view.branchmap()
1029 return d
1029 return d
1030 # add filter in smaller subset to bigger subset
1030 # add filter in smaller subset to bigger subset
1031 possiblefilters = set(repoview.filtertable)
1031 possiblefilters = set(repoview.filtertable)
1032 subsettable = getbranchmapsubsettable()
1032 subsettable = getbranchmapsubsettable()
1033 allfilters = []
1033 allfilters = []
1034 while possiblefilters:
1034 while possiblefilters:
1035 for name in possiblefilters:
1035 for name in possiblefilters:
1036 subset = subsettable.get(name)
1036 subset = subsettable.get(name)
1037 if subset not in possiblefilters:
1037 if subset not in possiblefilters:
1038 break
1038 break
1039 else:
1039 else:
1040 assert False, 'subset cycle %s!' % possiblefilters
1040 assert False, 'subset cycle %s!' % possiblefilters
1041 allfilters.append(name)
1041 allfilters.append(name)
1042 possiblefilters.remove(name)
1042 possiblefilters.remove(name)
1043
1043
1044 # warm the cache
1044 # warm the cache
1045 if not full:
1045 if not full:
1046 for name in allfilters:
1046 for name in allfilters:
1047 repo.filtered(name).branchmap()
1047 repo.filtered(name).branchmap()
1048 # add unfiltered
1048 # add unfiltered
1049 allfilters.append(None)
1049 allfilters.append(None)
1050
1050
1051 branchcacheread = safeattrsetter(branchmap, 'read')
1051 branchcacheread = safeattrsetter(branchmap, 'read')
1052 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1052 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1053 branchcacheread.set(lambda repo: None)
1053 branchcacheread.set(lambda repo: None)
1054 branchcachewrite.set(lambda bc, repo: None)
1054 branchcachewrite.set(lambda bc, repo: None)
1055 try:
1055 try:
1056 for name in allfilters:
1056 for name in allfilters:
1057 timer(getbranchmap(name), title=str(name))
1057 timer(getbranchmap(name), title=str(name))
1058 finally:
1058 finally:
1059 branchcacheread.restore()
1059 branchcacheread.restore()
1060 branchcachewrite.restore()
1060 branchcachewrite.restore()
1061 fm.end()
1061 fm.end()
1062
1062
1063 @command('perfloadmarkers')
1063 @command('perfloadmarkers')
1064 def perfloadmarkers(ui, repo):
1064 def perfloadmarkers(ui, repo):
1065 """benchmark the time to parse the on-disk markers for a repo
1065 """benchmark the time to parse the on-disk markers for a repo
1066
1066
1067 Result is the number of markers in the repo."""
1067 Result is the number of markers in the repo."""
1068 timer, fm = gettimer(ui)
1068 timer, fm = gettimer(ui)
1069 svfs = getsvfs(repo)
1069 svfs = getsvfs(repo)
1070 timer(lambda: len(obsolete.obsstore(svfs)))
1070 timer(lambda: len(obsolete.obsstore(svfs)))
1071 fm.end()
1071 fm.end()
1072
1072
1073 @command('perflrucachedict', formatteropts +
1073 @command('perflrucachedict', formatteropts +
1074 [('', 'size', 4, 'size of cache'),
1074 [('', 'size', 4, 'size of cache'),
1075 ('', 'gets', 10000, 'number of key lookups'),
1075 ('', 'gets', 10000, 'number of key lookups'),
1076 ('', 'sets', 10000, 'number of key sets'),
1076 ('', 'sets', 10000, 'number of key sets'),
1077 ('', 'mixed', 10000, 'number of mixed mode operations'),
1077 ('', 'mixed', 10000, 'number of mixed mode operations'),
1078 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1078 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1079 norepo=True)
1079 norepo=True)
1080 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1080 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1081 mixedgetfreq=50, **opts):
1081 mixedgetfreq=50, **opts):
1082 def doinit():
1082 def doinit():
1083 for i in xrange(10000):
1083 for i in xrange(10000):
1084 util.lrucachedict(size)
1084 util.lrucachedict(size)
1085
1085
1086 values = []
1086 values = []
1087 for i in xrange(size):
1087 for i in xrange(size):
1088 values.append(random.randint(0, sys.maxint))
1088 values.append(random.randint(0, sys.maxint))
1089
1089
1090 # Get mode fills the cache and tests raw lookup performance with no
1090 # Get mode fills the cache and tests raw lookup performance with no
1091 # eviction.
1091 # eviction.
1092 getseq = []
1092 getseq = []
1093 for i in xrange(gets):
1093 for i in xrange(gets):
1094 getseq.append(random.choice(values))
1094 getseq.append(random.choice(values))
1095
1095
1096 def dogets():
1096 def dogets():
1097 d = util.lrucachedict(size)
1097 d = util.lrucachedict(size)
1098 for v in values:
1098 for v in values:
1099 d[v] = v
1099 d[v] = v
1100 for key in getseq:
1100 for key in getseq:
1101 value = d[key]
1101 value = d[key]
1102 value # silence pyflakes warning
1102 value # silence pyflakes warning
1103
1103
1104 # Set mode tests insertion speed with cache eviction.
1104 # Set mode tests insertion speed with cache eviction.
1105 setseq = []
1105 setseq = []
1106 for i in xrange(sets):
1106 for i in xrange(sets):
1107 setseq.append(random.randint(0, sys.maxint))
1107 setseq.append(random.randint(0, sys.maxint))
1108
1108
1109 def dosets():
1109 def dosets():
1110 d = util.lrucachedict(size)
1110 d = util.lrucachedict(size)
1111 for v in setseq:
1111 for v in setseq:
1112 d[v] = v
1112 d[v] = v
1113
1113
1114 # Mixed mode randomly performs gets and sets with eviction.
1114 # Mixed mode randomly performs gets and sets with eviction.
1115 mixedops = []
1115 mixedops = []
1116 for i in xrange(mixed):
1116 for i in xrange(mixed):
1117 r = random.randint(0, 100)
1117 r = random.randint(0, 100)
1118 if r < mixedgetfreq:
1118 if r < mixedgetfreq:
1119 op = 0
1119 op = 0
1120 else:
1120 else:
1121 op = 1
1121 op = 1
1122
1122
1123 mixedops.append((op, random.randint(0, size * 2)))
1123 mixedops.append((op, random.randint(0, size * 2)))
1124
1124
1125 def domixed():
1125 def domixed():
1126 d = util.lrucachedict(size)
1126 d = util.lrucachedict(size)
1127
1127
1128 for op, v in mixedops:
1128 for op, v in mixedops:
1129 if op == 0:
1129 if op == 0:
1130 try:
1130 try:
1131 d[v]
1131 d[v]
1132 except KeyError:
1132 except KeyError:
1133 pass
1133 pass
1134 else:
1134 else:
1135 d[v] = v
1135 d[v] = v
1136
1136
1137 benches = [
1137 benches = [
1138 (doinit, 'init'),
1138 (doinit, 'init'),
1139 (dogets, 'gets'),
1139 (dogets, 'gets'),
1140 (dosets, 'sets'),
1140 (dosets, 'sets'),
1141 (domixed, 'mixed')
1141 (domixed, 'mixed')
1142 ]
1142 ]
1143
1143
1144 for fn, title in benches:
1144 for fn, title in benches:
1145 timer, fm = gettimer(ui, opts)
1145 timer, fm = gettimer(ui, opts)
1146 timer(fn, title=title)
1146 timer(fn, title=title)
1147 fm.end()
1147 fm.end()
1148
1148
1149 def uisetup(ui):
1149 def uisetup(ui):
1150 if (util.safehasattr(cmdutil, 'openrevlog') and
1150 if (util.safehasattr(cmdutil, 'openrevlog') and
1151 not util.safehasattr(commands, 'debugrevlogopts')):
1151 not util.safehasattr(commands, 'debugrevlogopts')):
1152 # for "historical portability":
1152 # for "historical portability":
1153 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1153 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1154 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1154 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1155 # openrevlog() should cause failure, because it has been
1155 # openrevlog() should cause failure, because it has been
1156 # available since 3.5 (or 49c583ca48c4).
1156 # available since 3.5 (or 49c583ca48c4).
1157 def openrevlog(orig, repo, cmd, file_, opts):
1157 def openrevlog(orig, repo, cmd, file_, opts):
1158 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1158 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1159 raise error.Abort("This version doesn't support --dir option",
1159 raise error.Abort("This version doesn't support --dir option",
1160 hint="use 3.5 or later")
1160 hint="use 3.5 or later")
1161 return orig(repo, cmd, file_, opts)
1161 return orig(repo, cmd, file_, opts)
1162 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
1162 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
@@ -1,1599 +1,1599
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 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 array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class lazymanifestiter(object):
107 class lazymanifestiter(object):
108 def __init__(self, lm):
108 def __init__(self, lm):
109 self.pos = 0
109 self.pos = 0
110 self.lm = lm
110 self.lm = lm
111
111
112 def __iter__(self):
112 def __iter__(self):
113 return self
113 return self
114
114
115 def next(self):
115 def next(self):
116 try:
116 try:
117 data, pos = self.lm._get(self.pos)
117 data, pos = self.lm._get(self.pos)
118 except IndexError:
118 except IndexError:
119 raise StopIteration
119 raise StopIteration
120 if pos == -1:
120 if pos == -1:
121 self.pos += 1
121 self.pos += 1
122 return data[0]
122 return data[0]
123 self.pos += 1
123 self.pos += 1
124 zeropos = data.find('\x00', pos)
124 zeropos = data.find('\x00', pos)
125 return data[pos:zeropos]
125 return data[pos:zeropos]
126
126
127 class lazymanifestiterentries(object):
127 class lazymanifestiterentries(object):
128 def __init__(self, lm):
128 def __init__(self, lm):
129 self.lm = lm
129 self.lm = lm
130 self.pos = 0
130 self.pos = 0
131
131
132 def __iter__(self):
132 def __iter__(self):
133 return self
133 return self
134
134
135 def next(self):
135 def next(self):
136 try:
136 try:
137 data, pos = self.lm._get(self.pos)
137 data, pos = self.lm._get(self.pos)
138 except IndexError:
138 except IndexError:
139 raise StopIteration
139 raise StopIteration
140 if pos == -1:
140 if pos == -1:
141 self.pos += 1
141 self.pos += 1
142 return data
142 return data
143 zeropos = data.find('\x00', pos)
143 zeropos = data.find('\x00', pos)
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 zeropos + 1, 40)
145 zeropos + 1, 40)
146 flags = self.lm._getflags(data, self.pos, zeropos)
146 flags = self.lm._getflags(data, self.pos, zeropos)
147 self.pos += 1
147 self.pos += 1
148 return (data[pos:zeropos], hashval, flags)
148 return (data[pos:zeropos], hashval, flags)
149
149
150 def unhexlify(data, extra, pos, length):
150 def unhexlify(data, extra, pos, length):
151 s = data[pos:pos + length].decode('hex')
151 s = data[pos:pos + length].decode('hex')
152 if extra:
152 if extra:
153 s += chr(extra & 0xff)
153 s += chr(extra & 0xff)
154 return s
154 return s
155
155
156 def _cmp(a, b):
156 def _cmp(a, b):
157 return (a > b) - (a < b)
157 return (a > b) - (a < b)
158
158
159 class _lazymanifest(object):
159 class _lazymanifest(object):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 if positions is None:
161 if positions is None:
162 self.positions = self.findlines(data)
162 self.positions = self.findlines(data)
163 self.extrainfo = [0] * len(self.positions)
163 self.extrainfo = [0] * len(self.positions)
164 self.data = data
164 self.data = data
165 self.extradata = []
165 self.extradata = []
166 else:
166 else:
167 self.positions = positions[:]
167 self.positions = positions[:]
168 self.extrainfo = extrainfo[:]
168 self.extrainfo = extrainfo[:]
169 self.extradata = extradata[:]
169 self.extradata = extradata[:]
170 self.data = data
170 self.data = data
171
171
172 def findlines(self, data):
172 def findlines(self, data):
173 if not data:
173 if not data:
174 return []
174 return []
175 pos = data.find("\n")
175 pos = data.find("\n")
176 if pos == -1 or data[-1] != '\n':
176 if pos == -1 or data[-1] != '\n':
177 raise ValueError("Manifest did not end in a newline.")
177 raise ValueError("Manifest did not end in a newline.")
178 positions = [0]
178 positions = [0]
179 prev = data[:data.find('\x00')]
179 prev = data[:data.find('\x00')]
180 while pos < len(data) - 1 and pos != -1:
180 while pos < len(data) - 1 and pos != -1:
181 positions.append(pos + 1)
181 positions.append(pos + 1)
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 if nexts < prev:
183 if nexts < prev:
184 raise ValueError("Manifest lines not in sorted order.")
184 raise ValueError("Manifest lines not in sorted order.")
185 prev = nexts
185 prev = nexts
186 pos = data.find("\n", pos + 1)
186 pos = data.find("\n", pos + 1)
187 return positions
187 return positions
188
188
189 def _get(self, index):
189 def _get(self, index):
190 # get the position encoded in pos:
190 # get the position encoded in pos:
191 # positive number is an index in 'data'
191 # positive number is an index in 'data'
192 # negative number is in extrapieces
192 # negative number is in extrapieces
193 pos = self.positions[index]
193 pos = self.positions[index]
194 if pos >= 0:
194 if pos >= 0:
195 return self.data, pos
195 return self.data, pos
196 return self.extradata[-pos - 1], -1
196 return self.extradata[-pos - 1], -1
197
197
198 def _getkey(self, pos):
198 def _getkey(self, pos):
199 if pos >= 0:
199 if pos >= 0:
200 return self.data[pos:self.data.find('\x00', pos + 1)]
200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 return self.extradata[-pos - 1][0]
201 return self.extradata[-pos - 1][0]
202
202
203 def bsearch(self, key):
203 def bsearch(self, key):
204 first = 0
204 first = 0
205 last = len(self.positions) - 1
205 last = len(self.positions) - 1
206
206
207 while first <= last:
207 while first <= last:
208 midpoint = (first + last)//2
208 midpoint = (first + last)//2
209 nextpos = self.positions[midpoint]
209 nextpos = self.positions[midpoint]
210 candidate = self._getkey(nextpos)
210 candidate = self._getkey(nextpos)
211 r = _cmp(key, candidate)
211 r = _cmp(key, candidate)
212 if r == 0:
212 if r == 0:
213 return midpoint
213 return midpoint
214 else:
214 else:
215 if r < 0:
215 if r < 0:
216 last = midpoint - 1
216 last = midpoint - 1
217 else:
217 else:
218 first = midpoint + 1
218 first = midpoint + 1
219 return -1
219 return -1
220
220
221 def bsearch2(self, key):
221 def bsearch2(self, key):
222 # same as the above, but will always return the position
222 # same as the above, but will always return the position
223 # done for performance reasons
223 # done for performance reasons
224 first = 0
224 first = 0
225 last = len(self.positions) - 1
225 last = len(self.positions) - 1
226
226
227 while first <= last:
227 while first <= last:
228 midpoint = (first + last)//2
228 midpoint = (first + last)//2
229 nextpos = self.positions[midpoint]
229 nextpos = self.positions[midpoint]
230 candidate = self._getkey(nextpos)
230 candidate = self._getkey(nextpos)
231 r = _cmp(key, candidate)
231 r = _cmp(key, candidate)
232 if r == 0:
232 if r == 0:
233 return (midpoint, True)
233 return (midpoint, True)
234 else:
234 else:
235 if r < 0:
235 if r < 0:
236 last = midpoint - 1
236 last = midpoint - 1
237 else:
237 else:
238 first = midpoint + 1
238 first = midpoint + 1
239 return (first, False)
239 return (first, False)
240
240
241 def __contains__(self, key):
241 def __contains__(self, key):
242 return self.bsearch(key) != -1
242 return self.bsearch(key) != -1
243
243
244 def _getflags(self, data, needle, pos):
244 def _getflags(self, data, needle, pos):
245 start = pos + 41
245 start = pos + 41
246 end = data.find("\n", start)
246 end = data.find("\n", start)
247 if end == -1:
247 if end == -1:
248 end = len(data) - 1
248 end = len(data) - 1
249 if start == end:
249 if start == end:
250 return ''
250 return ''
251 return self.data[start:end]
251 return self.data[start:end]
252
252
253 def __getitem__(self, key):
253 def __getitem__(self, key):
254 if not isinstance(key, str):
254 if not isinstance(key, str):
255 raise TypeError("getitem: manifest keys must be a string.")
255 raise TypeError("getitem: manifest keys must be a string.")
256 needle = self.bsearch(key)
256 needle = self.bsearch(key)
257 if needle == -1:
257 if needle == -1:
258 raise KeyError
258 raise KeyError
259 data, pos = self._get(needle)
259 data, pos = self._get(needle)
260 if pos == -1:
260 if pos == -1:
261 return (data[1], data[2])
261 return (data[1], data[2])
262 zeropos = data.find('\x00', pos)
262 zeropos = data.find('\x00', pos)
263 assert 0 <= needle <= len(self.positions)
263 assert 0 <= needle <= len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 flags = self._getflags(data, needle, zeropos)
266 flags = self._getflags(data, needle, zeropos)
267 return (hashval, flags)
267 return (hashval, flags)
268
268
269 def __delitem__(self, key):
269 def __delitem__(self, key):
270 needle, found = self.bsearch2(key)
270 needle, found = self.bsearch2(key)
271 if not found:
271 if not found:
272 raise KeyError
272 raise KeyError
273 cur = self.positions[needle]
273 cur = self.positions[needle]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 if cur >= 0:
276 if cur >= 0:
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278
278
279 def __setitem__(self, key, value):
279 def __setitem__(self, key, value):
280 if not isinstance(key, str):
280 if not isinstance(key, str):
281 raise TypeError("setitem: manifest keys must be a string.")
281 raise TypeError("setitem: manifest keys must be a string.")
282 if not isinstance(value, tuple) or len(value) != 2:
282 if not isinstance(value, tuple) or len(value) != 2:
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 hashval = value[0]
284 hashval = value[0]
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 raise TypeError("node must be a 20-byte string")
286 raise TypeError("node must be a 20-byte string")
287 flags = value[1]
287 flags = value[1]
288 if len(hashval) == 22:
288 if len(hashval) == 22:
289 hashval = hashval[:-1]
289 hashval = hashval[:-1]
290 if not isinstance(flags, str) or len(flags) > 1:
290 if not isinstance(flags, str) or len(flags) > 1:
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 needle, found = self.bsearch2(key)
292 needle, found = self.bsearch2(key)
293 if found:
293 if found:
294 # put the item
294 # put the item
295 pos = self.positions[needle]
295 pos = self.positions[needle]
296 if pos < 0:
296 if pos < 0:
297 self.extradata[-pos - 1] = (key, hashval, value[1])
297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 else:
298 else:
299 # just don't bother
299 # just don't bother
300 self.extradata.append((key, hashval, value[1]))
300 self.extradata.append((key, hashval, value[1]))
301 self.positions[needle] = -len(self.extradata)
301 self.positions[needle] = -len(self.extradata)
302 else:
302 else:
303 # not found, put it in with extra positions
303 # not found, put it in with extra positions
304 self.extradata.append((key, hashval, value[1]))
304 self.extradata.append((key, hashval, value[1]))
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 + self.positions[needle:])
306 + self.positions[needle:])
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 self.extrainfo[needle:])
308 self.extrainfo[needle:])
309
309
310 def copy(self):
310 def copy(self):
311 # XXX call _compact like in C?
311 # XXX call _compact like in C?
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
313 self.extradata)
313 self.extradata)
314
314
315 def _compact(self):
315 def _compact(self):
316 # hopefully not called TOO often
316 # hopefully not called TOO often
317 if len(self.extradata) == 0:
317 if len(self.extradata) == 0:
318 return
318 return
319 l = []
319 l = []
320 last_cut = 0
320 last_cut = 0
321 i = 0
321 i = 0
322 offset = 0
322 offset = 0
323 self.extrainfo = [0] * len(self.positions)
323 self.extrainfo = [0] * len(self.positions)
324 while i < len(self.positions):
324 while i < len(self.positions):
325 if self.positions[i] >= 0:
325 if self.positions[i] >= 0:
326 cur = self.positions[i]
326 cur = self.positions[i]
327 last_cut = cur
327 last_cut = cur
328 while True:
328 while True:
329 self.positions[i] = offset
329 self.positions[i] = offset
330 i += 1
330 i += 1
331 if i == len(self.positions) or self.positions[i] < 0:
331 if i == len(self.positions) or self.positions[i] < 0:
332 break
332 break
333 offset += self.positions[i] - cur
333 offset += self.positions[i] - cur
334 cur = self.positions[i]
334 cur = self.positions[i]
335 end_cut = self.data.find('\n', cur)
335 end_cut = self.data.find('\n', cur)
336 if end_cut != -1:
336 if end_cut != -1:
337 end_cut += 1
337 end_cut += 1
338 offset += end_cut - cur
338 offset += end_cut - cur
339 l.append(self.data[last_cut:end_cut])
339 l.append(self.data[last_cut:end_cut])
340 else:
340 else:
341 while i < len(self.positions) and self.positions[i] < 0:
341 while i < len(self.positions) and self.positions[i] < 0:
342 cur = self.positions[i]
342 cur = self.positions[i]
343 t = self.extradata[-cur - 1]
343 t = self.extradata[-cur - 1]
344 l.append(self._pack(t))
344 l.append(self._pack(t))
345 self.positions[i] = offset
345 self.positions[i] = offset
346 if len(t[1]) > 20:
346 if len(t[1]) > 20:
347 self.extrainfo[i] = ord(t[1][21])
347 self.extrainfo[i] = ord(t[1][21])
348 offset += len(l[-1])
348 offset += len(l[-1])
349 i += 1
349 i += 1
350 self.data = ''.join(l)
350 self.data = ''.join(l)
351 self.extradata = []
351 self.extradata = []
352
352
353 def _pack(self, d):
353 def _pack(self, d):
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355
355
356 def text(self):
356 def text(self):
357 self._compact()
357 self._compact()
358 return self.data
358 return self.data
359
359
360 def diff(self, m2, clean=False):
360 def diff(self, m2, clean=False):
361 '''Finds changes between the current manifest and m2.'''
361 '''Finds changes between the current manifest and m2.'''
362 # XXX think whether efficiency matters here
362 # XXX think whether efficiency matters here
363 diff = {}
363 diff = {}
364
364
365 for fn, e1, flags in self.iterentries():
365 for fn, e1, flags in self.iterentries():
366 if fn not in m2:
366 if fn not in m2:
367 diff[fn] = (e1, flags), (None, '')
367 diff[fn] = (e1, flags), (None, '')
368 else:
368 else:
369 e2 = m2[fn]
369 e2 = m2[fn]
370 if (e1, flags) != e2:
370 if (e1, flags) != e2:
371 diff[fn] = (e1, flags), e2
371 diff[fn] = (e1, flags), e2
372 elif clean:
372 elif clean:
373 diff[fn] = None
373 diff[fn] = None
374
374
375 for fn, e2, flags in m2.iterentries():
375 for fn, e2, flags in m2.iterentries():
376 if fn not in self:
376 if fn not in self:
377 diff[fn] = (None, ''), (e2, flags)
377 diff[fn] = (None, ''), (e2, flags)
378
378
379 return diff
379 return diff
380
380
381 def iterentries(self):
381 def iterentries(self):
382 return lazymanifestiterentries(self)
382 return lazymanifestiterentries(self)
383
383
384 def iterkeys(self):
384 def iterkeys(self):
385 return lazymanifestiter(self)
385 return lazymanifestiter(self)
386
386
387 def __iter__(self):
387 def __iter__(self):
388 return lazymanifestiter(self)
388 return lazymanifestiter(self)
389
389
390 def __len__(self):
390 def __len__(self):
391 return len(self.positions)
391 return len(self.positions)
392
392
393 def filtercopy(self, filterfn):
393 def filtercopy(self, filterfn):
394 # XXX should be optimized
394 # XXX should be optimized
395 c = _lazymanifest('')
395 c = _lazymanifest('')
396 for f, n, fl in self.iterentries():
396 for f, n, fl in self.iterentries():
397 if filterfn(f):
397 if filterfn(f):
398 c[f] = n, fl
398 c[f] = n, fl
399 return c
399 return c
400
400
401 try:
401 try:
402 _lazymanifest = parsers.lazymanifest
402 _lazymanifest = parsers.lazymanifest
403 except AttributeError:
403 except AttributeError:
404 pass
404 pass
405
405
406 class manifestdict(object):
406 class manifestdict(object):
407 def __init__(self, data=''):
407 def __init__(self, data=''):
408 if data.startswith('\0'):
408 if data.startswith('\0'):
409 #_lazymanifest can not parse v2
409 #_lazymanifest can not parse v2
410 self._lm = _lazymanifest('')
410 self._lm = _lazymanifest('')
411 for f, n, fl in _parsev2(data):
411 for f, n, fl in _parsev2(data):
412 self._lm[f] = n, fl
412 self._lm[f] = n, fl
413 else:
413 else:
414 self._lm = _lazymanifest(data)
414 self._lm = _lazymanifest(data)
415
415
416 def __getitem__(self, key):
416 def __getitem__(self, key):
417 return self._lm[key][0]
417 return self._lm[key][0]
418
418
419 def find(self, key):
419 def find(self, key):
420 return self._lm[key]
420 return self._lm[key]
421
421
422 def __len__(self):
422 def __len__(self):
423 return len(self._lm)
423 return len(self._lm)
424
424
425 def __nonzero__(self):
425 def __nonzero__(self):
426 # nonzero is covered by the __len__ function, but implementing it here
426 # nonzero is covered by the __len__ function, but implementing it here
427 # makes it easier for extensions to override.
427 # makes it easier for extensions to override.
428 return len(self._lm) != 0
428 return len(self._lm) != 0
429
429
430 def __setitem__(self, key, node):
430 def __setitem__(self, key, node):
431 self._lm[key] = node, self.flags(key, '')
431 self._lm[key] = node, self.flags(key, '')
432
432
433 def __contains__(self, key):
433 def __contains__(self, key):
434 return key in self._lm
434 return key in self._lm
435
435
436 def __delitem__(self, key):
436 def __delitem__(self, key):
437 del self._lm[key]
437 del self._lm[key]
438
438
439 def __iter__(self):
439 def __iter__(self):
440 return self._lm.__iter__()
440 return self._lm.__iter__()
441
441
442 def iterkeys(self):
442 def iterkeys(self):
443 return self._lm.iterkeys()
443 return self._lm.iterkeys()
444
444
445 def keys(self):
445 def keys(self):
446 return list(self.iterkeys())
446 return list(self.iterkeys())
447
447
448 def filesnotin(self, m2):
448 def filesnotin(self, m2):
449 '''Set of files in this manifest that are not in the other'''
449 '''Set of files in this manifest that are not in the other'''
450 diff = self.diff(m2)
450 diff = self.diff(m2)
451 files = set(filepath
451 files = set(filepath
452 for filepath, hashflags in diff.iteritems()
452 for filepath, hashflags in diff.iteritems()
453 if hashflags[1][0] is None)
453 if hashflags[1][0] is None)
454 return files
454 return files
455
455
456 @propertycache
456 @propertycache
457 def _dirs(self):
457 def _dirs(self):
458 return util.dirs(self)
458 return util.dirs(self)
459
459
460 def dirs(self):
460 def dirs(self):
461 return self._dirs
461 return self._dirs
462
462
463 def hasdir(self, dir):
463 def hasdir(self, dir):
464 return dir in self._dirs
464 return dir in self._dirs
465
465
466 def _filesfastpath(self, match):
466 def _filesfastpath(self, match):
467 '''Checks whether we can correctly and quickly iterate over matcher
467 '''Checks whether we can correctly and quickly iterate over matcher
468 files instead of over manifest files.'''
468 files instead of over manifest files.'''
469 files = match.files()
469 files = match.files()
470 return (len(files) < 100 and (match.isexact() or
470 return (len(files) < 100 and (match.isexact() or
471 (match.prefix() and all(fn in self for fn in files))))
471 (match.prefix() and all(fn in self for fn in files))))
472
472
473 def walk(self, match):
473 def walk(self, match):
474 '''Generates matching file names.
474 '''Generates matching file names.
475
475
476 Equivalent to manifest.matches(match).iterkeys(), but without creating
476 Equivalent to manifest.matches(match).iterkeys(), but without creating
477 an entirely new manifest.
477 an entirely new manifest.
478
478
479 It also reports nonexistent files by marking them bad with match.bad().
479 It also reports nonexistent files by marking them bad with match.bad().
480 '''
480 '''
481 if match.always():
481 if match.always():
482 for f in iter(self):
482 for f in iter(self):
483 yield f
483 yield f
484 return
484 return
485
485
486 fset = set(match.files())
486 fset = set(match.files())
487
487
488 # avoid the entire walk if we're only looking for specific files
488 # avoid the entire walk if we're only looking for specific files
489 if self._filesfastpath(match):
489 if self._filesfastpath(match):
490 for fn in sorted(fset):
490 for fn in sorted(fset):
491 yield fn
491 yield fn
492 return
492 return
493
493
494 for fn in self:
494 for fn in self:
495 if fn in fset:
495 if fn in fset:
496 # specified pattern is the exact name
496 # specified pattern is the exact name
497 fset.remove(fn)
497 fset.remove(fn)
498 if match(fn):
498 if match(fn):
499 yield fn
499 yield fn
500
500
501 # for dirstate.walk, files=['.'] means "walk the whole tree".
501 # for dirstate.walk, files=['.'] means "walk the whole tree".
502 # follow that here, too
502 # follow that here, too
503 fset.discard('.')
503 fset.discard('.')
504
504
505 for fn in sorted(fset):
505 for fn in sorted(fset):
506 if not self.hasdir(fn):
506 if not self.hasdir(fn):
507 match.bad(fn, None)
507 match.bad(fn, None)
508
508
509 def matches(self, match):
509 def matches(self, match):
510 '''generate a new manifest filtered by the match argument'''
510 '''generate a new manifest filtered by the match argument'''
511 if match.always():
511 if match.always():
512 return self.copy()
512 return self.copy()
513
513
514 if self._filesfastpath(match):
514 if self._filesfastpath(match):
515 m = manifestdict()
515 m = manifestdict()
516 lm = self._lm
516 lm = self._lm
517 for fn in match.files():
517 for fn in match.files():
518 if fn in lm:
518 if fn in lm:
519 m._lm[fn] = lm[fn]
519 m._lm[fn] = lm[fn]
520 return m
520 return m
521
521
522 m = manifestdict()
522 m = manifestdict()
523 m._lm = self._lm.filtercopy(match)
523 m._lm = self._lm.filtercopy(match)
524 return m
524 return m
525
525
526 def diff(self, m2, clean=False):
526 def diff(self, m2, clean=False):
527 '''Finds changes between the current manifest and m2.
527 '''Finds changes between the current manifest and m2.
528
528
529 Args:
529 Args:
530 m2: the manifest to which this manifest should be compared.
530 m2: the manifest to which this manifest should be compared.
531 clean: if true, include files unchanged between these manifests
531 clean: if true, include files unchanged between these manifests
532 with a None value in the returned dictionary.
532 with a None value in the returned dictionary.
533
533
534 The result is returned as a dict with filename as key and
534 The result is returned as a dict with filename as key and
535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
536 nodeid in the current/other manifest and fl1/fl2 is the flag
536 nodeid in the current/other manifest and fl1/fl2 is the flag
537 in the current/other manifest. Where the file does not exist,
537 in the current/other manifest. Where the file does not exist,
538 the nodeid will be None and the flags will be the empty
538 the nodeid will be None and the flags will be the empty
539 string.
539 string.
540 '''
540 '''
541 return self._lm.diff(m2._lm, clean)
541 return self._lm.diff(m2._lm, clean)
542
542
543 def setflag(self, key, flag):
543 def setflag(self, key, flag):
544 self._lm[key] = self[key], flag
544 self._lm[key] = self[key], flag
545
545
546 def get(self, key, default=None):
546 def get(self, key, default=None):
547 try:
547 try:
548 return self._lm[key][0]
548 return self._lm[key][0]
549 except KeyError:
549 except KeyError:
550 return default
550 return default
551
551
552 def flags(self, key, default=''):
552 def flags(self, key, default=''):
553 try:
553 try:
554 return self._lm[key][1]
554 return self._lm[key][1]
555 except KeyError:
555 except KeyError:
556 return default
556 return default
557
557
558 def copy(self):
558 def copy(self):
559 c = manifestdict()
559 c = manifestdict()
560 c._lm = self._lm.copy()
560 c._lm = self._lm.copy()
561 return c
561 return c
562
562
563 def iteritems(self):
563 def iteritems(self):
564 return (x[:2] for x in self._lm.iterentries())
564 return (x[:2] for x in self._lm.iterentries())
565
565
566 def iterentries(self):
566 def iterentries(self):
567 return self._lm.iterentries()
567 return self._lm.iterentries()
568
568
569 def text(self, usemanifestv2=False):
569 def text(self, usemanifestv2=False):
570 if usemanifestv2:
570 if usemanifestv2:
571 return _textv2(self._lm.iterentries())
571 return _textv2(self._lm.iterentries())
572 else:
572 else:
573 # use (probably) native version for v1
573 # use (probably) native version for v1
574 return self._lm.text()
574 return self._lm.text()
575
575
576 def fastdelta(self, base, changes):
576 def fastdelta(self, base, changes):
577 """Given a base manifest text as an array.array and a list of changes
577 """Given a base manifest text as an array.array and a list of changes
578 relative to that text, compute a delta that can be used by revlog.
578 relative to that text, compute a delta that can be used by revlog.
579 """
579 """
580 delta = []
580 delta = []
581 dstart = None
581 dstart = None
582 dend = None
582 dend = None
583 dline = [""]
583 dline = [""]
584 start = 0
584 start = 0
585 # zero copy representation of base as a buffer
585 # zero copy representation of base as a buffer
586 addbuf = util.buffer(base)
586 addbuf = util.buffer(base)
587
587
588 changes = list(changes)
588 changes = list(changes)
589 if len(changes) < 1000:
589 if len(changes) < 1000:
590 # start with a readonly loop that finds the offset of
590 # start with a readonly loop that finds the offset of
591 # each line and creates the deltas
591 # each line and creates the deltas
592 for f, todelete in changes:
592 for f, todelete in changes:
593 # bs will either be the index of the item or the insert point
593 # bs will either be the index of the item or the insert point
594 start, end = _msearch(addbuf, f, start)
594 start, end = _msearch(addbuf, f, start)
595 if not todelete:
595 if not todelete:
596 h, fl = self._lm[f]
596 h, fl = self._lm[f]
597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
598 else:
598 else:
599 if start == end:
599 if start == end:
600 # item we want to delete was not found, error out
600 # item we want to delete was not found, error out
601 raise AssertionError(
601 raise AssertionError(
602 _("failed to remove %s from manifest") % f)
602 _("failed to remove %s from manifest") % f)
603 l = ""
603 l = ""
604 if dstart is not None and dstart <= start and dend >= start:
604 if dstart is not None and dstart <= start and dend >= start:
605 if dend < end:
605 if dend < end:
606 dend = end
606 dend = end
607 if l:
607 if l:
608 dline.append(l)
608 dline.append(l)
609 else:
609 else:
610 if dstart is not None:
610 if dstart is not None:
611 delta.append([dstart, dend, "".join(dline)])
611 delta.append([dstart, dend, "".join(dline)])
612 dstart = start
612 dstart = start
613 dend = end
613 dend = end
614 dline = [l]
614 dline = [l]
615
615
616 if dstart is not None:
616 if dstart is not None:
617 delta.append([dstart, dend, "".join(dline)])
617 delta.append([dstart, dend, "".join(dline)])
618 # apply the delta to the base, and get a delta for addrevision
618 # apply the delta to the base, and get a delta for addrevision
619 deltatext, arraytext = _addlistdelta(base, delta)
619 deltatext, arraytext = _addlistdelta(base, delta)
620 else:
620 else:
621 # For large changes, it's much cheaper to just build the text and
621 # For large changes, it's much cheaper to just build the text and
622 # diff it.
622 # diff it.
623 arraytext = array.array('c', self.text())
623 arraytext = array.array('c', self.text())
624 deltatext = mdiff.textdiff(base, arraytext)
624 deltatext = mdiff.textdiff(base, arraytext)
625
625
626 return arraytext, deltatext
626 return arraytext, deltatext
627
627
628 def _msearch(m, s, lo=0, hi=None):
628 def _msearch(m, s, lo=0, hi=None):
629 '''return a tuple (start, end) that says where to find s within m.
629 '''return a tuple (start, end) that says where to find s within m.
630
630
631 If the string is found m[start:end] are the line containing
631 If the string is found m[start:end] are the line containing
632 that string. If start == end the string was not found and
632 that string. If start == end the string was not found and
633 they indicate the proper sorted insertion point.
633 they indicate the proper sorted insertion point.
634
634
635 m should be a buffer or a string
635 m should be a buffer or a string
636 s is a string'''
636 s is a string'''
637 def advance(i, c):
637 def advance(i, c):
638 while i < lenm and m[i] != c:
638 while i < lenm and m[i] != c:
639 i += 1
639 i += 1
640 return i
640 return i
641 if not s:
641 if not s:
642 return (lo, lo)
642 return (lo, lo)
643 lenm = len(m)
643 lenm = len(m)
644 if not hi:
644 if not hi:
645 hi = lenm
645 hi = lenm
646 while lo < hi:
646 while lo < hi:
647 mid = (lo + hi) // 2
647 mid = (lo + hi) // 2
648 start = mid
648 start = mid
649 while start > 0 and m[start - 1] != '\n':
649 while start > 0 and m[start - 1] != '\n':
650 start -= 1
650 start -= 1
651 end = advance(start, '\0')
651 end = advance(start, '\0')
652 if m[start:end] < s:
652 if m[start:end] < s:
653 # we know that after the null there are 40 bytes of sha1
653 # we know that after the null there are 40 bytes of sha1
654 # this translates to the bisect lo = mid + 1
654 # this translates to the bisect lo = mid + 1
655 lo = advance(end + 40, '\n') + 1
655 lo = advance(end + 40, '\n') + 1
656 else:
656 else:
657 # this translates to the bisect hi = mid
657 # this translates to the bisect hi = mid
658 hi = start
658 hi = start
659 end = advance(lo, '\0')
659 end = advance(lo, '\0')
660 found = m[lo:end]
660 found = m[lo:end]
661 if s == found:
661 if s == found:
662 # we know that after the null there are 40 bytes of sha1
662 # we know that after the null there are 40 bytes of sha1
663 end = advance(end + 40, '\n')
663 end = advance(end + 40, '\n')
664 return (lo, end + 1)
664 return (lo, end + 1)
665 else:
665 else:
666 return (lo, lo)
666 return (lo, lo)
667
667
668 def _checkforbidden(l):
668 def _checkforbidden(l):
669 """Check filenames for illegal characters."""
669 """Check filenames for illegal characters."""
670 for f in l:
670 for f in l:
671 if '\n' in f or '\r' in f:
671 if '\n' in f or '\r' in f:
672 raise error.RevlogError(
672 raise error.RevlogError(
673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
674
674
675
675
676 # apply the changes collected during the bisect loop to our addlist
676 # apply the changes collected during the bisect loop to our addlist
677 # return a delta suitable for addrevision
677 # return a delta suitable for addrevision
678 def _addlistdelta(addlist, x):
678 def _addlistdelta(addlist, x):
679 # for large addlist arrays, building a new array is cheaper
679 # for large addlist arrays, building a new array is cheaper
680 # than repeatedly modifying the existing one
680 # than repeatedly modifying the existing one
681 currentposition = 0
681 currentposition = 0
682 newaddlist = array.array('c')
682 newaddlist = array.array('c')
683
683
684 for start, end, content in x:
684 for start, end, content in x:
685 newaddlist += addlist[currentposition:start]
685 newaddlist += addlist[currentposition:start]
686 if content:
686 if content:
687 newaddlist += array.array('c', content)
687 newaddlist += array.array('c', content)
688
688
689 currentposition = end
689 currentposition = end
690
690
691 newaddlist += addlist[currentposition:]
691 newaddlist += addlist[currentposition:]
692
692
693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
694 + content for start, end, content in x)
694 + content for start, end, content in x)
695 return deltatext, newaddlist
695 return deltatext, newaddlist
696
696
697 def _splittopdir(f):
697 def _splittopdir(f):
698 if '/' in f:
698 if '/' in f:
699 dir, subpath = f.split('/', 1)
699 dir, subpath = f.split('/', 1)
700 return dir + '/', subpath
700 return dir + '/', subpath
701 else:
701 else:
702 return '', f
702 return '', f
703
703
704 _noop = lambda s: None
704 _noop = lambda s: None
705
705
706 class treemanifest(object):
706 class treemanifest(object):
707 def __init__(self, dir='', text=''):
707 def __init__(self, dir='', text=''):
708 self._dir = dir
708 self._dir = dir
709 self._node = revlog.nullid
709 self._node = revlog.nullid
710 self._loadfunc = _noop
710 self._loadfunc = _noop
711 self._copyfunc = _noop
711 self._copyfunc = _noop
712 self._dirty = False
712 self._dirty = False
713 self._dirs = {}
713 self._dirs = {}
714 # Using _lazymanifest here is a little slower than plain old dicts
714 # Using _lazymanifest here is a little slower than plain old dicts
715 self._files = {}
715 self._files = {}
716 self._flags = {}
716 self._flags = {}
717 if text:
717 if text:
718 def readsubtree(subdir, subm):
718 def readsubtree(subdir, subm):
719 raise AssertionError('treemanifest constructor only accepts '
719 raise AssertionError('treemanifest constructor only accepts '
720 'flat manifests')
720 'flat manifests')
721 self.parse(text, readsubtree)
721 self.parse(text, readsubtree)
722 self._dirty = True # Mark flat manifest dirty after parsing
722 self._dirty = True # Mark flat manifest dirty after parsing
723
723
724 def _subpath(self, path):
724 def _subpath(self, path):
725 return self._dir + path
725 return self._dir + path
726
726
727 def __len__(self):
727 def __len__(self):
728 self._load()
728 self._load()
729 size = len(self._files)
729 size = len(self._files)
730 for m in self._dirs.values():
730 for m in self._dirs.values():
731 size += m.__len__()
731 size += m.__len__()
732 return size
732 return size
733
733
734 def _isempty(self):
734 def _isempty(self):
735 self._load() # for consistency; already loaded by all callers
735 self._load() # for consistency; already loaded by all callers
736 return (not self._files and (not self._dirs or
736 return (not self._files and (not self._dirs or
737 all(m._isempty() for m in self._dirs.values())))
737 all(m._isempty() for m in self._dirs.values())))
738
738
739 def __repr__(self):
739 def __repr__(self):
740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
741 (self._dir, revlog.hex(self._node),
741 (self._dir, revlog.hex(self._node),
742 bool(self._loadfunc is _noop),
742 bool(self._loadfunc is _noop),
743 self._dirty, id(self)))
743 self._dirty, id(self)))
744
744
745 def dir(self):
745 def dir(self):
746 '''The directory that this tree manifest represents, including a
746 '''The directory that this tree manifest represents, including a
747 trailing '/'. Empty string for the repo root directory.'''
747 trailing '/'. Empty string for the repo root directory.'''
748 return self._dir
748 return self._dir
749
749
750 def node(self):
750 def node(self):
751 '''This node of this instance. nullid for unsaved instances. Should
751 '''This node of this instance. nullid for unsaved instances. Should
752 be updated when the instance is read or written from a revlog.
752 be updated when the instance is read or written from a revlog.
753 '''
753 '''
754 assert not self._dirty
754 assert not self._dirty
755 return self._node
755 return self._node
756
756
757 def setnode(self, node):
757 def setnode(self, node):
758 self._node = node
758 self._node = node
759 self._dirty = False
759 self._dirty = False
760
760
761 def iterentries(self):
761 def iterentries(self):
762 self._load()
762 self._load()
763 for p, n in sorted(self._dirs.items() + self._files.items()):
763 for p, n in sorted(self._dirs.items() + self._files.items()):
764 if p in self._files:
764 if p in self._files:
765 yield self._subpath(p), n, self._flags.get(p, '')
765 yield self._subpath(p), n, self._flags.get(p, '')
766 else:
766 else:
767 for x in n.iterentries():
767 for x in n.iterentries():
768 yield x
768 yield x
769
769
770 def iteritems(self):
770 def iteritems(self):
771 self._load()
771 self._load()
772 for p, n in sorted(self._dirs.items() + self._files.items()):
772 for p, n in sorted(self._dirs.items() + self._files.items()):
773 if p in self._files:
773 if p in self._files:
774 yield self._subpath(p), n
774 yield self._subpath(p), n
775 else:
775 else:
776 for f, sn in n.iteritems():
776 for f, sn in n.iteritems():
777 yield f, sn
777 yield f, sn
778
778
779 def iterkeys(self):
779 def iterkeys(self):
780 self._load()
780 self._load()
781 for p in sorted(self._dirs.keys() + self._files.keys()):
781 for p in sorted(self._dirs.keys() + self._files.keys()):
782 if p in self._files:
782 if p in self._files:
783 yield self._subpath(p)
783 yield self._subpath(p)
784 else:
784 else:
785 for f in self._dirs[p].iterkeys():
785 for f in self._dirs[p].iterkeys():
786 yield f
786 yield f
787
787
788 def keys(self):
788 def keys(self):
789 return list(self.iterkeys())
789 return list(self.iterkeys())
790
790
791 def __iter__(self):
791 def __iter__(self):
792 return self.iterkeys()
792 return self.iterkeys()
793
793
794 def __contains__(self, f):
794 def __contains__(self, f):
795 if f is None:
795 if f is None:
796 return False
796 return False
797 self._load()
797 self._load()
798 dir, subpath = _splittopdir(f)
798 dir, subpath = _splittopdir(f)
799 if dir:
799 if dir:
800 if dir not in self._dirs:
800 if dir not in self._dirs:
801 return False
801 return False
802 return self._dirs[dir].__contains__(subpath)
802 return self._dirs[dir].__contains__(subpath)
803 else:
803 else:
804 return f in self._files
804 return f in self._files
805
805
806 def get(self, f, default=None):
806 def get(self, f, default=None):
807 self._load()
807 self._load()
808 dir, subpath = _splittopdir(f)
808 dir, subpath = _splittopdir(f)
809 if dir:
809 if dir:
810 if dir not in self._dirs:
810 if dir not in self._dirs:
811 return default
811 return default
812 return self._dirs[dir].get(subpath, default)
812 return self._dirs[dir].get(subpath, default)
813 else:
813 else:
814 return self._files.get(f, default)
814 return self._files.get(f, default)
815
815
816 def __getitem__(self, f):
816 def __getitem__(self, f):
817 self._load()
817 self._load()
818 dir, subpath = _splittopdir(f)
818 dir, subpath = _splittopdir(f)
819 if dir:
819 if dir:
820 return self._dirs[dir].__getitem__(subpath)
820 return self._dirs[dir].__getitem__(subpath)
821 else:
821 else:
822 return self._files[f]
822 return self._files[f]
823
823
824 def flags(self, f):
824 def flags(self, f):
825 self._load()
825 self._load()
826 dir, subpath = _splittopdir(f)
826 dir, subpath = _splittopdir(f)
827 if dir:
827 if dir:
828 if dir not in self._dirs:
828 if dir not in self._dirs:
829 return ''
829 return ''
830 return self._dirs[dir].flags(subpath)
830 return self._dirs[dir].flags(subpath)
831 else:
831 else:
832 if f in self._dirs:
832 if f in self._dirs:
833 return ''
833 return ''
834 return self._flags.get(f, '')
834 return self._flags.get(f, '')
835
835
836 def find(self, f):
836 def find(self, f):
837 self._load()
837 self._load()
838 dir, subpath = _splittopdir(f)
838 dir, subpath = _splittopdir(f)
839 if dir:
839 if dir:
840 return self._dirs[dir].find(subpath)
840 return self._dirs[dir].find(subpath)
841 else:
841 else:
842 return self._files[f], self._flags.get(f, '')
842 return self._files[f], self._flags.get(f, '')
843
843
844 def __delitem__(self, f):
844 def __delitem__(self, f):
845 self._load()
845 self._load()
846 dir, subpath = _splittopdir(f)
846 dir, subpath = _splittopdir(f)
847 if dir:
847 if dir:
848 self._dirs[dir].__delitem__(subpath)
848 self._dirs[dir].__delitem__(subpath)
849 # If the directory is now empty, remove it
849 # If the directory is now empty, remove it
850 if self._dirs[dir]._isempty():
850 if self._dirs[dir]._isempty():
851 del self._dirs[dir]
851 del self._dirs[dir]
852 else:
852 else:
853 del self._files[f]
853 del self._files[f]
854 if f in self._flags:
854 if f in self._flags:
855 del self._flags[f]
855 del self._flags[f]
856 self._dirty = True
856 self._dirty = True
857
857
858 def __setitem__(self, f, n):
858 def __setitem__(self, f, n):
859 assert n is not None
859 assert n is not None
860 self._load()
860 self._load()
861 dir, subpath = _splittopdir(f)
861 dir, subpath = _splittopdir(f)
862 if dir:
862 if dir:
863 if dir not in self._dirs:
863 if dir not in self._dirs:
864 self._dirs[dir] = treemanifest(self._subpath(dir))
864 self._dirs[dir] = treemanifest(self._subpath(dir))
865 self._dirs[dir].__setitem__(subpath, n)
865 self._dirs[dir].__setitem__(subpath, n)
866 else:
866 else:
867 self._files[f] = n[:21] # to match manifestdict's behavior
867 self._files[f] = n[:21] # to match manifestdict's behavior
868 self._dirty = True
868 self._dirty = True
869
869
870 def _load(self):
870 def _load(self):
871 if self._loadfunc is not _noop:
871 if self._loadfunc is not _noop:
872 lf, self._loadfunc = self._loadfunc, _noop
872 lf, self._loadfunc = self._loadfunc, _noop
873 lf(self)
873 lf(self)
874 elif self._copyfunc is not _noop:
874 elif self._copyfunc is not _noop:
875 cf, self._copyfunc = self._copyfunc, _noop
875 cf, self._copyfunc = self._copyfunc, _noop
876 cf(self)
876 cf(self)
877
877
878 def setflag(self, f, flags):
878 def setflag(self, f, flags):
879 """Set the flags (symlink, executable) for path f."""
879 """Set the flags (symlink, executable) for path f."""
880 self._load()
880 self._load()
881 dir, subpath = _splittopdir(f)
881 dir, subpath = _splittopdir(f)
882 if dir:
882 if dir:
883 if dir not in self._dirs:
883 if dir not in self._dirs:
884 self._dirs[dir] = treemanifest(self._subpath(dir))
884 self._dirs[dir] = treemanifest(self._subpath(dir))
885 self._dirs[dir].setflag(subpath, flags)
885 self._dirs[dir].setflag(subpath, flags)
886 else:
886 else:
887 self._flags[f] = flags
887 self._flags[f] = flags
888 self._dirty = True
888 self._dirty = True
889
889
890 def copy(self):
890 def copy(self):
891 copy = treemanifest(self._dir)
891 copy = treemanifest(self._dir)
892 copy._node = self._node
892 copy._node = self._node
893 copy._dirty = self._dirty
893 copy._dirty = self._dirty
894 if self._copyfunc is _noop:
894 if self._copyfunc is _noop:
895 def _copyfunc(s):
895 def _copyfunc(s):
896 self._load()
896 self._load()
897 for d in self._dirs:
897 for d in self._dirs:
898 s._dirs[d] = self._dirs[d].copy()
898 s._dirs[d] = self._dirs[d].copy()
899 s._files = dict.copy(self._files)
899 s._files = dict.copy(self._files)
900 s._flags = dict.copy(self._flags)
900 s._flags = dict.copy(self._flags)
901 if self._loadfunc is _noop:
901 if self._loadfunc is _noop:
902 _copyfunc(copy)
902 _copyfunc(copy)
903 else:
903 else:
904 copy._copyfunc = _copyfunc
904 copy._copyfunc = _copyfunc
905 else:
905 else:
906 copy._copyfunc = self._copyfunc
906 copy._copyfunc = self._copyfunc
907 return copy
907 return copy
908
908
909 def filesnotin(self, m2):
909 def filesnotin(self, m2):
910 '''Set of files in this manifest that are not in the other'''
910 '''Set of files in this manifest that are not in the other'''
911 files = set()
911 files = set()
912 def _filesnotin(t1, t2):
912 def _filesnotin(t1, t2):
913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
914 return
914 return
915 t1._load()
915 t1._load()
916 t2._load()
916 t2._load()
917 for d, m1 in t1._dirs.iteritems():
917 for d, m1 in t1._dirs.iteritems():
918 if d in t2._dirs:
918 if d in t2._dirs:
919 m2 = t2._dirs[d]
919 m2 = t2._dirs[d]
920 _filesnotin(m1, m2)
920 _filesnotin(m1, m2)
921 else:
921 else:
922 files.update(m1.iterkeys())
922 files.update(m1.iterkeys())
923
923
924 for fn in t1._files.iterkeys():
924 for fn in t1._files.iterkeys():
925 if fn not in t2._files:
925 if fn not in t2._files:
926 files.add(t1._subpath(fn))
926 files.add(t1._subpath(fn))
927
927
928 _filesnotin(self, m2)
928 _filesnotin(self, m2)
929 return files
929 return files
930
930
931 @propertycache
931 @propertycache
932 def _alldirs(self):
932 def _alldirs(self):
933 return util.dirs(self)
933 return util.dirs(self)
934
934
935 def dirs(self):
935 def dirs(self):
936 return self._alldirs
936 return self._alldirs
937
937
938 def hasdir(self, dir):
938 def hasdir(self, dir):
939 self._load()
939 self._load()
940 topdir, subdir = _splittopdir(dir)
940 topdir, subdir = _splittopdir(dir)
941 if topdir:
941 if topdir:
942 if topdir in self._dirs:
942 if topdir in self._dirs:
943 return self._dirs[topdir].hasdir(subdir)
943 return self._dirs[topdir].hasdir(subdir)
944 return False
944 return False
945 return (dir + '/') in self._dirs
945 return (dir + '/') in self._dirs
946
946
947 def walk(self, match):
947 def walk(self, match):
948 '''Generates matching file names.
948 '''Generates matching file names.
949
949
950 Equivalent to manifest.matches(match).iterkeys(), but without creating
950 Equivalent to manifest.matches(match).iterkeys(), but without creating
951 an entirely new manifest.
951 an entirely new manifest.
952
952
953 It also reports nonexistent files by marking them bad with match.bad().
953 It also reports nonexistent files by marking them bad with match.bad().
954 '''
954 '''
955 if match.always():
955 if match.always():
956 for f in iter(self):
956 for f in iter(self):
957 yield f
957 yield f
958 return
958 return
959
959
960 fset = set(match.files())
960 fset = set(match.files())
961
961
962 for fn in self._walk(match):
962 for fn in self._walk(match):
963 if fn in fset:
963 if fn in fset:
964 # specified pattern is the exact name
964 # specified pattern is the exact name
965 fset.remove(fn)
965 fset.remove(fn)
966 yield fn
966 yield fn
967
967
968 # for dirstate.walk, files=['.'] means "walk the whole tree".
968 # for dirstate.walk, files=['.'] means "walk the whole tree".
969 # follow that here, too
969 # follow that here, too
970 fset.discard('.')
970 fset.discard('.')
971
971
972 for fn in sorted(fset):
972 for fn in sorted(fset):
973 if not self.hasdir(fn):
973 if not self.hasdir(fn):
974 match.bad(fn, None)
974 match.bad(fn, None)
975
975
976 def _walk(self, match):
976 def _walk(self, match):
977 '''Recursively generates matching file names for walk().'''
977 '''Recursively generates matching file names for walk().'''
978 if not match.visitdir(self._dir[:-1] or '.'):
978 if not match.visitdir(self._dir[:-1] or '.'):
979 return
979 return
980
980
981 # yield this dir's files and walk its submanifests
981 # yield this dir's files and walk its submanifests
982 self._load()
982 self._load()
983 for p in sorted(self._dirs.keys() + self._files.keys()):
983 for p in sorted(self._dirs.keys() + self._files.keys()):
984 if p in self._files:
984 if p in self._files:
985 fullp = self._subpath(p)
985 fullp = self._subpath(p)
986 if match(fullp):
986 if match(fullp):
987 yield fullp
987 yield fullp
988 else:
988 else:
989 for f in self._dirs[p]._walk(match):
989 for f in self._dirs[p]._walk(match):
990 yield f
990 yield f
991
991
992 def matches(self, match):
992 def matches(self, match):
993 '''generate a new manifest filtered by the match argument'''
993 '''generate a new manifest filtered by the match argument'''
994 if match.always():
994 if match.always():
995 return self.copy()
995 return self.copy()
996
996
997 return self._matches(match)
997 return self._matches(match)
998
998
999 def _matches(self, match):
999 def _matches(self, match):
1000 '''recursively generate a new manifest filtered by the match argument.
1000 '''recursively generate a new manifest filtered by the match argument.
1001 '''
1001 '''
1002
1002
1003 visit = match.visitdir(self._dir[:-1] or '.')
1003 visit = match.visitdir(self._dir[:-1] or '.')
1004 if visit == 'all':
1004 if visit == 'all':
1005 return self.copy()
1005 return self.copy()
1006 ret = treemanifest(self._dir)
1006 ret = treemanifest(self._dir)
1007 if not visit:
1007 if not visit:
1008 return ret
1008 return ret
1009
1009
1010 self._load()
1010 self._load()
1011 for fn in self._files:
1011 for fn in self._files:
1012 fullp = self._subpath(fn)
1012 fullp = self._subpath(fn)
1013 if not match(fullp):
1013 if not match(fullp):
1014 continue
1014 continue
1015 ret._files[fn] = self._files[fn]
1015 ret._files[fn] = self._files[fn]
1016 if fn in self._flags:
1016 if fn in self._flags:
1017 ret._flags[fn] = self._flags[fn]
1017 ret._flags[fn] = self._flags[fn]
1018
1018
1019 for dir, subm in self._dirs.iteritems():
1019 for dir, subm in self._dirs.iteritems():
1020 m = subm._matches(match)
1020 m = subm._matches(match)
1021 if not m._isempty():
1021 if not m._isempty():
1022 ret._dirs[dir] = m
1022 ret._dirs[dir] = m
1023
1023
1024 if not ret._isempty():
1024 if not ret._isempty():
1025 ret._dirty = True
1025 ret._dirty = True
1026 return ret
1026 return ret
1027
1027
1028 def diff(self, m2, clean=False):
1028 def diff(self, m2, clean=False):
1029 '''Finds changes between the current manifest and m2.
1029 '''Finds changes between the current manifest and m2.
1030
1030
1031 Args:
1031 Args:
1032 m2: the manifest to which this manifest should be compared.
1032 m2: the manifest to which this manifest should be compared.
1033 clean: if true, include files unchanged between these manifests
1033 clean: if true, include files unchanged between these manifests
1034 with a None value in the returned dictionary.
1034 with a None value in the returned dictionary.
1035
1035
1036 The result is returned as a dict with filename as key and
1036 The result is returned as a dict with filename as key and
1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1039 in the current/other manifest. Where the file does not exist,
1039 in the current/other manifest. Where the file does not exist,
1040 the nodeid will be None and the flags will be the empty
1040 the nodeid will be None and the flags will be the empty
1041 string.
1041 string.
1042 '''
1042 '''
1043 result = {}
1043 result = {}
1044 emptytree = treemanifest()
1044 emptytree = treemanifest()
1045 def _diff(t1, t2):
1045 def _diff(t1, t2):
1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1047 return
1047 return
1048 t1._load()
1048 t1._load()
1049 t2._load()
1049 t2._load()
1050 for d, m1 in t1._dirs.iteritems():
1050 for d, m1 in t1._dirs.iteritems():
1051 m2 = t2._dirs.get(d, emptytree)
1051 m2 = t2._dirs.get(d, emptytree)
1052 _diff(m1, m2)
1052 _diff(m1, m2)
1053
1053
1054 for d, m2 in t2._dirs.iteritems():
1054 for d, m2 in t2._dirs.iteritems():
1055 if d not in t1._dirs:
1055 if d not in t1._dirs:
1056 _diff(emptytree, m2)
1056 _diff(emptytree, m2)
1057
1057
1058 for fn, n1 in t1._files.iteritems():
1058 for fn, n1 in t1._files.iteritems():
1059 fl1 = t1._flags.get(fn, '')
1059 fl1 = t1._flags.get(fn, '')
1060 n2 = t2._files.get(fn, None)
1060 n2 = t2._files.get(fn, None)
1061 fl2 = t2._flags.get(fn, '')
1061 fl2 = t2._flags.get(fn, '')
1062 if n1 != n2 or fl1 != fl2:
1062 if n1 != n2 or fl1 != fl2:
1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1064 elif clean:
1064 elif clean:
1065 result[t1._subpath(fn)] = None
1065 result[t1._subpath(fn)] = None
1066
1066
1067 for fn, n2 in t2._files.iteritems():
1067 for fn, n2 in t2._files.iteritems():
1068 if fn not in t1._files:
1068 if fn not in t1._files:
1069 fl2 = t2._flags.get(fn, '')
1069 fl2 = t2._flags.get(fn, '')
1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1071
1071
1072 _diff(self, m2)
1072 _diff(self, m2)
1073 return result
1073 return result
1074
1074
1075 def unmodifiedsince(self, m2):
1075 def unmodifiedsince(self, m2):
1076 return not self._dirty and not m2._dirty and self._node == m2._node
1076 return not self._dirty and not m2._dirty and self._node == m2._node
1077
1077
1078 def parse(self, text, readsubtree):
1078 def parse(self, text, readsubtree):
1079 for f, n, fl in _parse(text):
1079 for f, n, fl in _parse(text):
1080 if fl == 't':
1080 if fl == 't':
1081 f = f + '/'
1081 f = f + '/'
1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1083 elif '/' in f:
1083 elif '/' in f:
1084 # This is a flat manifest, so use __setitem__ and setflag rather
1084 # This is a flat manifest, so use __setitem__ and setflag rather
1085 # than assigning directly to _files and _flags, so we can
1085 # than assigning directly to _files and _flags, so we can
1086 # assign a path in a subdirectory, and to mark dirty (compared
1086 # assign a path in a subdirectory, and to mark dirty (compared
1087 # to nullid).
1087 # to nullid).
1088 self[f] = n
1088 self[f] = n
1089 if fl:
1089 if fl:
1090 self.setflag(f, fl)
1090 self.setflag(f, fl)
1091 else:
1091 else:
1092 # Assigning to _files and _flags avoids marking as dirty,
1092 # Assigning to _files and _flags avoids marking as dirty,
1093 # and should be a little faster.
1093 # and should be a little faster.
1094 self._files[f] = n
1094 self._files[f] = n
1095 if fl:
1095 if fl:
1096 self._flags[f] = fl
1096 self._flags[f] = fl
1097
1097
1098 def text(self, usemanifestv2=False):
1098 def text(self, usemanifestv2=False):
1099 """Get the full data of this manifest as a bytestring."""
1099 """Get the full data of this manifest as a bytestring."""
1100 self._load()
1100 self._load()
1101 return _text(self.iterentries(), usemanifestv2)
1101 return _text(self.iterentries(), usemanifestv2)
1102
1102
1103 def dirtext(self, usemanifestv2=False):
1103 def dirtext(self, usemanifestv2=False):
1104 """Get the full data of this directory as a bytestring. Make sure that
1104 """Get the full data of this directory as a bytestring. Make sure that
1105 any submanifests have been written first, so their nodeids are correct.
1105 any submanifests have been written first, so their nodeids are correct.
1106 """
1106 """
1107 self._load()
1107 self._load()
1108 flags = self.flags
1108 flags = self.flags
1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1111 return _text(sorted(dirs + files), usemanifestv2)
1111 return _text(sorted(dirs + files), usemanifestv2)
1112
1112
1113 def read(self, gettext, readsubtree):
1113 def read(self, gettext, readsubtree):
1114 def _load_for_read(s):
1114 def _load_for_read(s):
1115 s.parse(gettext(), readsubtree)
1115 s.parse(gettext(), readsubtree)
1116 s._dirty = False
1116 s._dirty = False
1117 self._loadfunc = _load_for_read
1117 self._loadfunc = _load_for_read
1118
1118
1119 def writesubtrees(self, m1, m2, writesubtree):
1119 def writesubtrees(self, m1, m2, writesubtree):
1120 self._load() # for consistency; should never have any effect here
1120 self._load() # for consistency; should never have any effect here
1121 m1._load()
1121 m1._load()
1122 m2._load()
1122 m2._load()
1123 emptytree = treemanifest()
1123 emptytree = treemanifest()
1124 for d, subm in self._dirs.iteritems():
1124 for d, subm in self._dirs.iteritems():
1125 subp1 = m1._dirs.get(d, emptytree)._node
1125 subp1 = m1._dirs.get(d, emptytree)._node
1126 subp2 = m2._dirs.get(d, emptytree)._node
1126 subp2 = m2._dirs.get(d, emptytree)._node
1127 if subp1 == revlog.nullid:
1127 if subp1 == revlog.nullid:
1128 subp1, subp2 = subp2, subp1
1128 subp1, subp2 = subp2, subp1
1129 writesubtree(subm, subp1, subp2)
1129 writesubtree(subm, subp1, subp2)
1130
1130
1131 class manifestrevlog(revlog.revlog):
1131 class manifestrevlog(revlog.revlog):
1132 '''A revlog that stores manifest texts. This is responsible for caching the
1132 '''A revlog that stores manifest texts. This is responsible for caching the
1133 full-text manifest contents.
1133 full-text manifest contents.
1134 '''
1134 '''
1135 def __init__(self, opener, dir='', dirlogcache=None):
1135 def __init__(self, opener, dir='', dirlogcache=None):
1136 # During normal operations, we expect to deal with not more than four
1136 # During normal operations, we expect to deal with not more than four
1137 # revs at a time (such as during commit --amend). When rebasing large
1137 # revs at a time (such as during commit --amend). When rebasing large
1138 # stacks of commits, the number can go up, hence the config knob below.
1138 # stacks of commits, the number can go up, hence the config knob below.
1139 cachesize = 4
1139 cachesize = 4
1140 usetreemanifest = False
1140 usetreemanifest = False
1141 usemanifestv2 = False
1141 usemanifestv2 = False
1142 opts = getattr(opener, 'options', None)
1142 opts = getattr(opener, 'options', None)
1143 if opts is not None:
1143 if opts is not None:
1144 cachesize = opts.get('manifestcachesize', cachesize)
1144 cachesize = opts.get('manifestcachesize', cachesize)
1145 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1145 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1146 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1146 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1147
1147
1148 self._treeondisk = usetreemanifest
1148 self._treeondisk = usetreemanifest
1149 self._usemanifestv2 = usemanifestv2
1149 self._usemanifestv2 = usemanifestv2
1150
1150
1151 self._fulltextcache = util.lrucachedict(cachesize)
1151 self._fulltextcache = util.lrucachedict(cachesize)
1152
1152
1153 indexfile = "00manifest.i"
1153 indexfile = "00manifest.i"
1154 if dir:
1154 if dir:
1155 assert self._treeondisk, 'opts is %r' % opts
1155 assert self._treeondisk, 'opts is %r' % opts
1156 if not dir.endswith('/'):
1156 if not dir.endswith('/'):
1157 dir = dir + '/'
1157 dir = dir + '/'
1158 indexfile = "meta/" + dir + "00manifest.i"
1158 indexfile = "meta/" + dir + "00manifest.i"
1159 self._dir = dir
1159 self._dir = dir
1160 # The dirlogcache is kept on the root manifest log
1160 # The dirlogcache is kept on the root manifest log
1161 if dir:
1161 if dir:
1162 self._dirlogcache = dirlogcache
1162 self._dirlogcache = dirlogcache
1163 else:
1163 else:
1164 self._dirlogcache = {'': self}
1164 self._dirlogcache = {'': self}
1165
1165
1166 super(manifestrevlog, self).__init__(opener, indexfile,
1166 super(manifestrevlog, self).__init__(opener, indexfile,
1167 checkambig=bool(dir))
1167 checkambig=bool(dir))
1168
1168
1169 @property
1169 @property
1170 def fulltextcache(self):
1170 def fulltextcache(self):
1171 return self._fulltextcache
1171 return self._fulltextcache
1172
1172
1173 def clearcaches(self):
1173 def clearcaches(self):
1174 super(manifestrevlog, self).clearcaches()
1174 super(manifestrevlog, self).clearcaches()
1175 self._fulltextcache.clear()
1175 self._fulltextcache.clear()
1176 self._dirlogcache = {'': self}
1176 self._dirlogcache = {'': self}
1177
1177
1178 def dirlog(self, dir):
1178 def dirlog(self, dir):
1179 if dir:
1179 if dir:
1180 assert self._treeondisk
1180 assert self._treeondisk
1181 if dir not in self._dirlogcache:
1181 if dir not in self._dirlogcache:
1182 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1182 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1183 self._dirlogcache)
1183 self._dirlogcache)
1184 return self._dirlogcache[dir]
1184 return self._dirlogcache[dir]
1185
1185
1186 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1186 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1187 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1187 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1188 and not self._usemanifestv2):
1188 and not self._usemanifestv2):
1189 # If our first parent is in the manifest cache, we can
1189 # If our first parent is in the manifest cache, we can
1190 # compute a delta here using properties we know about the
1190 # compute a delta here using properties we know about the
1191 # manifest up-front, which may save time later for the
1191 # manifest up-front, which may save time later for the
1192 # revlog layer.
1192 # revlog layer.
1193
1193
1194 _checkforbidden(added)
1194 _checkforbidden(added)
1195 # combine the changed lists into one sorted iterator
1195 # combine the changed lists into one sorted iterator
1196 work = heapq.merge([(x, False) for x in added],
1196 work = heapq.merge([(x, False) for x in added],
1197 [(x, True) for x in removed])
1197 [(x, True) for x in removed])
1198
1198
1199 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1199 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1200 cachedelta = self.rev(p1), deltatext
1200 cachedelta = self.rev(p1), deltatext
1201 text = util.buffer(arraytext)
1201 text = util.buffer(arraytext)
1202 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1202 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1203 else:
1203 else:
1204 # The first parent manifest isn't already loaded, so we'll
1204 # The first parent manifest isn't already loaded, so we'll
1205 # just encode a fulltext of the manifest and pass that
1205 # just encode a fulltext of the manifest and pass that
1206 # through to the revlog layer, and let it handle the delta
1206 # through to the revlog layer, and let it handle the delta
1207 # process.
1207 # process.
1208 if self._treeondisk:
1208 if self._treeondisk:
1209 assert readtree, "readtree must be set for treemanifest writes"
1209 assert readtree, "readtree must be set for treemanifest writes"
1210 m1 = readtree(self._dir, p1)
1210 m1 = readtree(self._dir, p1)
1211 m2 = readtree(self._dir, p2)
1211 m2 = readtree(self._dir, p2)
1212 n = self._addtree(m, transaction, link, m1, m2, readtree)
1212 n = self._addtree(m, transaction, link, m1, m2, readtree)
1213 arraytext = None
1213 arraytext = None
1214 else:
1214 else:
1215 text = m.text(self._usemanifestv2)
1215 text = m.text(self._usemanifestv2)
1216 n = self.addrevision(text, transaction, link, p1, p2)
1216 n = self.addrevision(text, transaction, link, p1, p2)
1217 arraytext = array.array('c', text)
1217 arraytext = array.array('c', text)
1218
1218
1219 if arraytext is not None:
1219 if arraytext is not None:
1220 self.fulltextcache[n] = arraytext
1220 self.fulltextcache[n] = arraytext
1221
1221
1222 return n
1222 return n
1223
1223
1224 def _addtree(self, m, transaction, link, m1, m2, readtree):
1224 def _addtree(self, m, transaction, link, m1, m2, readtree):
1225 # If the manifest is unchanged compared to one parent,
1225 # If the manifest is unchanged compared to one parent,
1226 # don't write a new revision
1226 # don't write a new revision
1227 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1227 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1228 return m.node()
1228 return m.node()
1229 def writesubtree(subm, subp1, subp2):
1229 def writesubtree(subm, subp1, subp2):
1230 sublog = self.dirlog(subm.dir())
1230 sublog = self.dirlog(subm.dir())
1231 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1231 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1232 readtree=readtree)
1232 readtree=readtree)
1233 m.writesubtrees(m1, m2, writesubtree)
1233 m.writesubtrees(m1, m2, writesubtree)
1234 text = m.dirtext(self._usemanifestv2)
1234 text = m.dirtext(self._usemanifestv2)
1235 # Double-check whether contents are unchanged to one parent
1235 # Double-check whether contents are unchanged to one parent
1236 if text == m1.dirtext(self._usemanifestv2):
1236 if text == m1.dirtext(self._usemanifestv2):
1237 n = m1.node()
1237 n = m1.node()
1238 elif text == m2.dirtext(self._usemanifestv2):
1238 elif text == m2.dirtext(self._usemanifestv2):
1239 n = m2.node()
1239 n = m2.node()
1240 else:
1240 else:
1241 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1241 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1242 # Save nodeid so parent manifest can calculate its nodeid
1242 # Save nodeid so parent manifest can calculate its nodeid
1243 m.setnode(n)
1243 m.setnode(n)
1244 return n
1244 return n
1245
1245
1246 class manifestlog(object):
1246 class manifestlog(object):
1247 """A collection class representing the collection of manifest snapshots
1247 """A collection class representing the collection of manifest snapshots
1248 referenced by commits in the repository.
1248 referenced by commits in the repository.
1249
1249
1250 In this situation, 'manifest' refers to the abstract concept of a snapshot
1250 In this situation, 'manifest' refers to the abstract concept of a snapshot
1251 of the list of files in the given commit. Consumers of the output of this
1251 of the list of files in the given commit. Consumers of the output of this
1252 class do not care about the implementation details of the actual manifests
1252 class do not care about the implementation details of the actual manifests
1253 they receive (i.e. tree or flat or lazily loaded, etc)."""
1253 they receive (i.e. tree or flat or lazily loaded, etc)."""
1254 def __init__(self, opener, repo):
1254 def __init__(self, opener, repo):
1255 self._repo = repo
1255 self._repo = repo
1256
1256
1257 usetreemanifest = False
1257 usetreemanifest = False
1258
1258
1259 opts = getattr(opener, 'options', None)
1259 opts = getattr(opener, 'options', None)
1260 if opts is not None:
1260 if opts is not None:
1261 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1261 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1262 self._treeinmem = usetreemanifest
1262 self._treeinmem = usetreemanifest
1263
1263
1264 self._oldmanifest = repo._constructmanifest()
1264 self._oldmanifest = repo._constructmanifest()
1265 self._revlog = self._oldmanifest
1265 self._revlog = self._oldmanifest
1266
1266
1267 # A cache of the manifestctx or treemanifestctx for each directory
1267 # A cache of the manifestctx or treemanifestctx for each directory
1268 self._dirmancache = {}
1268 self._dirmancache = {}
1269
1269
1270 # We'll separate this into it's own cache once oldmanifest is no longer
1270 # We'll separate this into it's own cache once oldmanifest is no longer
1271 # used
1271 # used
1272 self._mancache = self._oldmanifest._mancache
1272 self._mancache = self._oldmanifest._mancache
1273 self._dirmancache[''] = self._mancache
1273 self._dirmancache[''] = self._mancache
1274
1274
1275 # A future patch makes this use the same config value as the existing
1275 # A future patch makes this use the same config value as the existing
1276 # mancache
1276 # mancache
1277 self.cachesize = 4
1277 self.cachesize = 4
1278
1278
1279 def __getitem__(self, node):
1279 def __getitem__(self, node):
1280 """Retrieves the manifest instance for the given node. Throws a
1280 """Retrieves the manifest instance for the given node. Throws a
1281 LookupError if not found.
1281 LookupError if not found.
1282 """
1282 """
1283 return self.get('', node)
1283 return self.get('', node)
1284
1284
1285 def get(self, dir, node):
1285 def get(self, dir, node):
1286 """Retrieves the manifest instance for the given node. Throws a
1286 """Retrieves the manifest instance for the given node. Throws a
1287 LookupError if not found.
1287 LookupError if not found.
1288 """
1288 """
1289 if node in self._dirmancache.get(dir, ()):
1289 if node in self._dirmancache.get(dir, ()):
1290 cachemf = self._dirmancache[dir][node]
1290 cachemf = self._dirmancache[dir][node]
1291 # The old manifest may put non-ctx manifests in the cache, so
1291 # The old manifest may put non-ctx manifests in the cache, so
1292 # skip those since they don't implement the full api.
1292 # skip those since they don't implement the full api.
1293 if (isinstance(cachemf, manifestctx) or
1293 if (isinstance(cachemf, manifestctx) or
1294 isinstance(cachemf, treemanifestctx)):
1294 isinstance(cachemf, treemanifestctx)):
1295 return cachemf
1295 return cachemf
1296
1296
1297 if dir:
1297 if dir:
1298 if self._revlog._treeondisk:
1298 if self._revlog._treeondisk:
1299 dirlog = self._revlog.dirlog(dir)
1299 dirlog = self._revlog.dirlog(dir)
1300 if node not in dirlog.nodemap:
1300 if node not in dirlog.nodemap:
1301 raise LookupError(node, dirlog.indexfile,
1301 raise LookupError(node, dirlog.indexfile,
1302 _('no node'))
1302 _('no node'))
1303 m = treemanifestctx(self._repo, dir, node)
1303 m = treemanifestctx(self._repo, dir, node)
1304 else:
1304 else:
1305 raise error.Abort(
1305 raise error.Abort(
1306 _("cannot ask for manifest directory '%s' in a flat "
1306 _("cannot ask for manifest directory '%s' in a flat "
1307 "manifest") % dir)
1307 "manifest") % dir)
1308 else:
1308 else:
1309 if node not in self._revlog.nodemap:
1309 if node not in self._revlog.nodemap:
1310 raise LookupError(node, self._revlog.indexfile,
1310 raise LookupError(node, self._revlog.indexfile,
1311 _('no node'))
1311 _('no node'))
1312 if self._treeinmem:
1312 if self._treeinmem:
1313 m = treemanifestctx(self._repo, '', node)
1313 m = treemanifestctx(self._repo, '', node)
1314 else:
1314 else:
1315 m = manifestctx(self._repo, node)
1315 m = manifestctx(self._repo, node)
1316
1316
1317 if node != revlog.nullid:
1317 if node != revlog.nullid:
1318 mancache = self._dirmancache.get(dir)
1318 mancache = self._dirmancache.get(dir)
1319 if not mancache:
1319 if not mancache:
1320 mancache = util.lrucachedict(self.cachesize)
1320 mancache = util.lrucachedict(self.cachesize)
1321 self._dirmancache[dir] = mancache
1321 self._dirmancache[dir] = mancache
1322 mancache[node] = m
1322 mancache[node] = m
1323 return m
1323 return m
1324
1324
1325 def clearcaches(self):
1326 self._dirmancache.clear()
1327 self._revlog.clearcaches()
1328
1325 class memmanifestctx(object):
1329 class memmanifestctx(object):
1326 def __init__(self, repo):
1330 def __init__(self, repo):
1327 self._repo = repo
1331 self._repo = repo
1328 self._manifestdict = manifestdict()
1332 self._manifestdict = manifestdict()
1329
1333
1330 def _revlog(self):
1334 def _revlog(self):
1331 return self._repo.manifestlog._revlog
1335 return self._repo.manifestlog._revlog
1332
1336
1333 def new(self):
1337 def new(self):
1334 return memmanifestctx(self._repo)
1338 return memmanifestctx(self._repo)
1335
1339
1336 def copy(self):
1340 def copy(self):
1337 memmf = memmanifestctx(self._repo)
1341 memmf = memmanifestctx(self._repo)
1338 memmf._manifestdict = self.read().copy()
1342 memmf._manifestdict = self.read().copy()
1339 return memmf
1343 return memmf
1340
1344
1341 def read(self):
1345 def read(self):
1342 return self._manifestdict
1346 return self._manifestdict
1343
1347
1344 def write(self, transaction, link, p1, p2, added, removed):
1348 def write(self, transaction, link, p1, p2, added, removed):
1345 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1349 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1346 added, removed)
1350 added, removed)
1347
1351
1348 class manifestctx(object):
1352 class manifestctx(object):
1349 """A class representing a single revision of a manifest, including its
1353 """A class representing a single revision of a manifest, including its
1350 contents, its parent revs, and its linkrev.
1354 contents, its parent revs, and its linkrev.
1351 """
1355 """
1352 def __init__(self, repo, node):
1356 def __init__(self, repo, node):
1353 self._repo = repo
1357 self._repo = repo
1354 self._data = None
1358 self._data = None
1355
1359
1356 self._node = node
1360 self._node = node
1357
1361
1358 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1362 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1359 # but let's add it later when something needs it and we can load it
1363 # but let's add it later when something needs it and we can load it
1360 # lazily.
1364 # lazily.
1361 #self.p1, self.p2 = revlog.parents(node)
1365 #self.p1, self.p2 = revlog.parents(node)
1362 #rev = revlog.rev(node)
1366 #rev = revlog.rev(node)
1363 #self.linkrev = revlog.linkrev(rev)
1367 #self.linkrev = revlog.linkrev(rev)
1364
1368
1365 def _revlog(self):
1369 def _revlog(self):
1366 return self._repo.manifestlog._revlog
1370 return self._repo.manifestlog._revlog
1367
1371
1368 def node(self):
1372 def node(self):
1369 return self._node
1373 return self._node
1370
1374
1371 def new(self):
1375 def new(self):
1372 return memmanifestctx(self._repo)
1376 return memmanifestctx(self._repo)
1373
1377
1374 def copy(self):
1378 def copy(self):
1375 memmf = memmanifestctx(self._repo)
1379 memmf = memmanifestctx(self._repo)
1376 memmf._manifestdict = self.read().copy()
1380 memmf._manifestdict = self.read().copy()
1377 return memmf
1381 return memmf
1378
1382
1379 def read(self):
1383 def read(self):
1380 if not self._data:
1384 if not self._data:
1381 if self._node == revlog.nullid:
1385 if self._node == revlog.nullid:
1382 self._data = manifestdict()
1386 self._data = manifestdict()
1383 else:
1387 else:
1384 rl = self._revlog()
1388 rl = self._revlog()
1385 text = rl.revision(self._node)
1389 text = rl.revision(self._node)
1386 arraytext = array.array('c', text)
1390 arraytext = array.array('c', text)
1387 rl._fulltextcache[self._node] = arraytext
1391 rl._fulltextcache[self._node] = arraytext
1388 self._data = manifestdict(text)
1392 self._data = manifestdict(text)
1389 return self._data
1393 return self._data
1390
1394
1391 def readfast(self, shallow=False):
1395 def readfast(self, shallow=False):
1392 '''Calls either readdelta or read, based on which would be less work.
1396 '''Calls either readdelta or read, based on which would be less work.
1393 readdelta is called if the delta is against the p1, and therefore can be
1397 readdelta is called if the delta is against the p1, and therefore can be
1394 read quickly.
1398 read quickly.
1395
1399
1396 If `shallow` is True, nothing changes since this is a flat manifest.
1400 If `shallow` is True, nothing changes since this is a flat manifest.
1397 '''
1401 '''
1398 rl = self._revlog()
1402 rl = self._revlog()
1399 r = rl.rev(self._node)
1403 r = rl.rev(self._node)
1400 deltaparent = rl.deltaparent(r)
1404 deltaparent = rl.deltaparent(r)
1401 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1405 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1402 return self.readdelta()
1406 return self.readdelta()
1403 return self.read()
1407 return self.read()
1404
1408
1405 def readdelta(self, shallow=False):
1409 def readdelta(self, shallow=False):
1406 '''Returns a manifest containing just the entries that are present
1410 '''Returns a manifest containing just the entries that are present
1407 in this manifest, but not in its p1 manifest. This is efficient to read
1411 in this manifest, but not in its p1 manifest. This is efficient to read
1408 if the revlog delta is already p1.
1412 if the revlog delta is already p1.
1409
1413
1410 Changing the value of `shallow` has no effect on flat manifests.
1414 Changing the value of `shallow` has no effect on flat manifests.
1411 '''
1415 '''
1412 revlog = self._revlog()
1416 revlog = self._revlog()
1413 if revlog._usemanifestv2:
1417 if revlog._usemanifestv2:
1414 # Need to perform a slow delta
1418 # Need to perform a slow delta
1415 r0 = revlog.deltaparent(revlog.rev(self._node))
1419 r0 = revlog.deltaparent(revlog.rev(self._node))
1416 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1420 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1417 m1 = self.read()
1421 m1 = self.read()
1418 md = manifestdict()
1422 md = manifestdict()
1419 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1423 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1420 if n1:
1424 if n1:
1421 md[f] = n1
1425 md[f] = n1
1422 if fl1:
1426 if fl1:
1423 md.setflag(f, fl1)
1427 md.setflag(f, fl1)
1424 return md
1428 return md
1425
1429
1426 r = revlog.rev(self._node)
1430 r = revlog.rev(self._node)
1427 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1431 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1428 return manifestdict(d)
1432 return manifestdict(d)
1429
1433
1430 def find(self, key):
1434 def find(self, key):
1431 return self.read().find(key)
1435 return self.read().find(key)
1432
1436
1433 class memtreemanifestctx(object):
1437 class memtreemanifestctx(object):
1434 def __init__(self, repo, dir=''):
1438 def __init__(self, repo, dir=''):
1435 self._repo = repo
1439 self._repo = repo
1436 self._dir = dir
1440 self._dir = dir
1437 self._treemanifest = treemanifest()
1441 self._treemanifest = treemanifest()
1438
1442
1439 def _revlog(self):
1443 def _revlog(self):
1440 return self._repo.manifestlog._revlog
1444 return self._repo.manifestlog._revlog
1441
1445
1442 def new(self, dir=''):
1446 def new(self, dir=''):
1443 return memtreemanifestctx(self._repo, dir=dir)
1447 return memtreemanifestctx(self._repo, dir=dir)
1444
1448
1445 def copy(self):
1449 def copy(self):
1446 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1450 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1447 memmf._treemanifest = self._treemanifest.copy()
1451 memmf._treemanifest = self._treemanifest.copy()
1448 return memmf
1452 return memmf
1449
1453
1450 def read(self):
1454 def read(self):
1451 return self._treemanifest
1455 return self._treemanifest
1452
1456
1453 def write(self, transaction, link, p1, p2, added, removed):
1457 def write(self, transaction, link, p1, p2, added, removed):
1454 def readtree(dir, node):
1458 def readtree(dir, node):
1455 return self._repo.manifestlog.get(dir, node).read()
1459 return self._repo.manifestlog.get(dir, node).read()
1456 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1460 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1457 added, removed, readtree=readtree)
1461 added, removed, readtree=readtree)
1458
1462
1459 class treemanifestctx(object):
1463 class treemanifestctx(object):
1460 def __init__(self, repo, dir, node):
1464 def __init__(self, repo, dir, node):
1461 self._repo = repo
1465 self._repo = repo
1462 self._dir = dir
1466 self._dir = dir
1463 self._data = None
1467 self._data = None
1464
1468
1465 self._node = node
1469 self._node = node
1466
1470
1467 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1471 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1468 # we can instantiate treemanifestctx objects for directories we don't
1472 # we can instantiate treemanifestctx objects for directories we don't
1469 # have on disk.
1473 # have on disk.
1470 #self.p1, self.p2 = revlog.parents(node)
1474 #self.p1, self.p2 = revlog.parents(node)
1471 #rev = revlog.rev(node)
1475 #rev = revlog.rev(node)
1472 #self.linkrev = revlog.linkrev(rev)
1476 #self.linkrev = revlog.linkrev(rev)
1473
1477
1474 def _revlog(self):
1478 def _revlog(self):
1475 return self._repo.manifestlog._revlog.dirlog(self._dir)
1479 return self._repo.manifestlog._revlog.dirlog(self._dir)
1476
1480
1477 def read(self):
1481 def read(self):
1478 if not self._data:
1482 if not self._data:
1479 rl = self._revlog()
1483 rl = self._revlog()
1480 if self._node == revlog.nullid:
1484 if self._node == revlog.nullid:
1481 self._data = treemanifest()
1485 self._data = treemanifest()
1482 elif rl._treeondisk:
1486 elif rl._treeondisk:
1483 m = treemanifest(dir=self._dir)
1487 m = treemanifest(dir=self._dir)
1484 def gettext():
1488 def gettext():
1485 return rl.revision(self._node)
1489 return rl.revision(self._node)
1486 def readsubtree(dir, subm):
1490 def readsubtree(dir, subm):
1487 return treemanifestctx(self._repo, dir, subm).read()
1491 return treemanifestctx(self._repo, dir, subm).read()
1488 m.read(gettext, readsubtree)
1492 m.read(gettext, readsubtree)
1489 m.setnode(self._node)
1493 m.setnode(self._node)
1490 self._data = m
1494 self._data = m
1491 else:
1495 else:
1492 text = rl.revision(self._node)
1496 text = rl.revision(self._node)
1493 arraytext = array.array('c', text)
1497 arraytext = array.array('c', text)
1494 rl.fulltextcache[self._node] = arraytext
1498 rl.fulltextcache[self._node] = arraytext
1495 self._data = treemanifest(dir=self._dir, text=text)
1499 self._data = treemanifest(dir=self._dir, text=text)
1496
1500
1497 return self._data
1501 return self._data
1498
1502
1499 def node(self):
1503 def node(self):
1500 return self._node
1504 return self._node
1501
1505
1502 def new(self, dir=''):
1506 def new(self, dir=''):
1503 return memtreemanifestctx(self._repo, dir=dir)
1507 return memtreemanifestctx(self._repo, dir=dir)
1504
1508
1505 def copy(self):
1509 def copy(self):
1506 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1510 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1507 memmf._treemanifest = self.read().copy()
1511 memmf._treemanifest = self.read().copy()
1508 return memmf
1512 return memmf
1509
1513
1510 def readdelta(self, shallow=False):
1514 def readdelta(self, shallow=False):
1511 '''Returns a manifest containing just the entries that are present
1515 '''Returns a manifest containing just the entries that are present
1512 in this manifest, but not in its p1 manifest. This is efficient to read
1516 in this manifest, but not in its p1 manifest. This is efficient to read
1513 if the revlog delta is already p1.
1517 if the revlog delta is already p1.
1514
1518
1515 If `shallow` is True, this will read the delta for this directory,
1519 If `shallow` is True, this will read the delta for this directory,
1516 without recursively reading subdirectory manifests. Instead, any
1520 without recursively reading subdirectory manifests. Instead, any
1517 subdirectory entry will be reported as it appears in the manifest, i.e.
1521 subdirectory entry will be reported as it appears in the manifest, i.e.
1518 the subdirectory will be reported among files and distinguished only by
1522 the subdirectory will be reported among files and distinguished only by
1519 its 't' flag.
1523 its 't' flag.
1520 '''
1524 '''
1521 revlog = self._revlog()
1525 revlog = self._revlog()
1522 if shallow and not revlog._usemanifestv2:
1526 if shallow and not revlog._usemanifestv2:
1523 r = revlog.rev(self._node)
1527 r = revlog.rev(self._node)
1524 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1528 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1525 return manifestdict(d)
1529 return manifestdict(d)
1526 else:
1530 else:
1527 # Need to perform a slow delta
1531 # Need to perform a slow delta
1528 r0 = revlog.deltaparent(revlog.rev(self._node))
1532 r0 = revlog.deltaparent(revlog.rev(self._node))
1529 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1533 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1530 m1 = self.read()
1534 m1 = self.read()
1531 md = treemanifest(dir=self._dir)
1535 md = treemanifest(dir=self._dir)
1532 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1536 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1533 if n1:
1537 if n1:
1534 md[f] = n1
1538 md[f] = n1
1535 if fl1:
1539 if fl1:
1536 md.setflag(f, fl1)
1540 md.setflag(f, fl1)
1537 return md
1541 return md
1538
1542
1539 def readfast(self, shallow=False):
1543 def readfast(self, shallow=False):
1540 '''Calls either readdelta or read, based on which would be less work.
1544 '''Calls either readdelta or read, based on which would be less work.
1541 readdelta is called if the delta is against the p1, and therefore can be
1545 readdelta is called if the delta is against the p1, and therefore can be
1542 read quickly.
1546 read quickly.
1543
1547
1544 If `shallow` is True, it only returns the entries from this manifest,
1548 If `shallow` is True, it only returns the entries from this manifest,
1545 and not any submanifests.
1549 and not any submanifests.
1546 '''
1550 '''
1547 rl = self._revlog()
1551 rl = self._revlog()
1548 r = rl.rev(self._node)
1552 r = rl.rev(self._node)
1549 deltaparent = rl.deltaparent(r)
1553 deltaparent = rl.deltaparent(r)
1550 if (deltaparent != revlog.nullrev and
1554 if (deltaparent != revlog.nullrev and
1551 deltaparent in rl.parentrevs(r)):
1555 deltaparent in rl.parentrevs(r)):
1552 return self.readdelta(shallow=shallow)
1556 return self.readdelta(shallow=shallow)
1553
1557
1554 if shallow:
1558 if shallow:
1555 return manifestdict(rl.revision(self._node))
1559 return manifestdict(rl.revision(self._node))
1556 else:
1560 else:
1557 return self.read()
1561 return self.read()
1558
1562
1559 def find(self, key):
1563 def find(self, key):
1560 return self.read().find(key)
1564 return self.read().find(key)
1561
1565
1562 class manifest(manifestrevlog):
1566 class manifest(manifestrevlog):
1563 def __init__(self, opener, dir='', dirlogcache=None):
1567 def __init__(self, opener, dir='', dirlogcache=None):
1564 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1568 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1565 manifest.manifest only. External users should create a root manifest
1569 manifest.manifest only. External users should create a root manifest
1566 log with manifest.manifest(opener) and call dirlog() on it.
1570 log with manifest.manifest(opener) and call dirlog() on it.
1567 '''
1571 '''
1568 # During normal operations, we expect to deal with not more than four
1572 # During normal operations, we expect to deal with not more than four
1569 # revs at a time (such as during commit --amend). When rebasing large
1573 # revs at a time (such as during commit --amend). When rebasing large
1570 # stacks of commits, the number can go up, hence the config knob below.
1574 # stacks of commits, the number can go up, hence the config knob below.
1571 cachesize = 4
1575 cachesize = 4
1572 usetreemanifest = False
1576 usetreemanifest = False
1573 opts = getattr(opener, 'options', None)
1577 opts = getattr(opener, 'options', None)
1574 if opts is not None:
1578 if opts is not None:
1575 cachesize = opts.get('manifestcachesize', cachesize)
1579 cachesize = opts.get('manifestcachesize', cachesize)
1576 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1580 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1577 self._mancache = util.lrucachedict(cachesize)
1581 self._mancache = util.lrucachedict(cachesize)
1578 self._treeinmem = usetreemanifest
1582 self._treeinmem = usetreemanifest
1579 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1583 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1580
1584
1581 def _newmanifest(self, data=''):
1585 def _newmanifest(self, data=''):
1582 if self._treeinmem:
1586 if self._treeinmem:
1583 return treemanifest(self._dir, data)
1587 return treemanifest(self._dir, data)
1584 return manifestdict(data)
1588 return manifestdict(data)
1585
1589
1586 def dirlog(self, dir):
1590 def dirlog(self, dir):
1587 """This overrides the base revlog implementation to allow construction
1591 """This overrides the base revlog implementation to allow construction
1588 'manifest' types instead of manifestrevlog types. This is only needed
1592 'manifest' types instead of manifestrevlog types. This is only needed
1589 until we migrate off the 'manifest' type."""
1593 until we migrate off the 'manifest' type."""
1590 if dir:
1594 if dir:
1591 assert self._treeondisk
1595 assert self._treeondisk
1592 if dir not in self._dirlogcache:
1596 if dir not in self._dirlogcache:
1593 self._dirlogcache[dir] = manifest(self.opener, dir,
1597 self._dirlogcache[dir] = manifest(self.opener, dir,
1594 self._dirlogcache)
1598 self._dirlogcache)
1595 return self._dirlogcache[dir]
1599 return self._dirlogcache[dir]
1596
1597 def clearcaches(self):
1598 super(manifest, self).clearcaches()
1599 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now