##// END OF EJS Templates
dirstate: move the _dirfoldmap to dirstatemap...
Durham Goode -
r34679:e8a89ed7 default
parent child Browse files
Show More
@@ -1,1502 +1,1502 b''
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 gc
23 import gc
24 import os
24 import os
25 import random
25 import random
26 import struct
26 import struct
27 import sys
27 import sys
28 import time
28 import time
29 from mercurial import (
29 from mercurial import (
30 changegroup,
30 changegroup,
31 cmdutil,
31 cmdutil,
32 commands,
32 commands,
33 copies,
33 copies,
34 error,
34 error,
35 extensions,
35 extensions,
36 mdiff,
36 mdiff,
37 merge,
37 merge,
38 revlog,
38 revlog,
39 util,
39 util,
40 )
40 )
41
41
42 # for "historical portability":
42 # for "historical portability":
43 # try to import modules separately (in dict order), and ignore
43 # try to import modules separately (in dict order), and ignore
44 # failure, because these aren't available with early Mercurial
44 # failure, because these aren't available with early Mercurial
45 try:
45 try:
46 from mercurial import branchmap # since 2.5 (or bcee63733aad)
46 from mercurial import branchmap # since 2.5 (or bcee63733aad)
47 except ImportError:
47 except ImportError:
48 pass
48 pass
49 try:
49 try:
50 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
50 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
51 except ImportError:
51 except ImportError:
52 pass
52 pass
53 try:
53 try:
54 from mercurial import registrar # since 3.7 (or 37d50250b696)
54 from mercurial import registrar # since 3.7 (or 37d50250b696)
55 dir(registrar) # forcibly load it
55 dir(registrar) # forcibly load it
56 except ImportError:
56 except ImportError:
57 registrar = None
57 registrar = None
58 try:
58 try:
59 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
59 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
60 except ImportError:
60 except ImportError:
61 pass
61 pass
62 try:
62 try:
63 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
63 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
64 except ImportError:
64 except ImportError:
65 pass
65 pass
66
66
67 # for "historical portability":
67 # for "historical portability":
68 # define util.safehasattr forcibly, because util.safehasattr has been
68 # define util.safehasattr forcibly, because util.safehasattr has been
69 # available since 1.9.3 (or 94b200a11cf7)
69 # available since 1.9.3 (or 94b200a11cf7)
70 _undefined = object()
70 _undefined = object()
71 def safehasattr(thing, attr):
71 def safehasattr(thing, attr):
72 return getattr(thing, attr, _undefined) is not _undefined
72 return getattr(thing, attr, _undefined) is not _undefined
73 setattr(util, 'safehasattr', safehasattr)
73 setattr(util, 'safehasattr', safehasattr)
74
74
75 # for "historical portability":
75 # for "historical portability":
76 # define util.timer forcibly, because util.timer has been available
76 # define util.timer forcibly, because util.timer has been available
77 # since ae5d60bb70c9
77 # since ae5d60bb70c9
78 if safehasattr(time, 'perf_counter'):
78 if safehasattr(time, 'perf_counter'):
79 util.timer = time.perf_counter
79 util.timer = time.perf_counter
80 elif os.name == 'nt':
80 elif os.name == 'nt':
81 util.timer = time.clock
81 util.timer = time.clock
82 else:
82 else:
83 util.timer = time.time
83 util.timer = time.time
84
84
85 # for "historical portability":
85 # for "historical portability":
86 # use locally defined empty option list, if formatteropts isn't
86 # use locally defined empty option list, if formatteropts isn't
87 # available, because commands.formatteropts has been available since
87 # available, because commands.formatteropts has been available since
88 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
88 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
89 # available since 2.2 (or ae5f92e154d3)
89 # available since 2.2 (or ae5f92e154d3)
90 formatteropts = getattr(cmdutil, "formatteropts",
90 formatteropts = getattr(cmdutil, "formatteropts",
91 getattr(commands, "formatteropts", []))
91 getattr(commands, "formatteropts", []))
92
92
93 # for "historical portability":
93 # for "historical portability":
94 # use locally defined option list, if debugrevlogopts isn't available,
94 # use locally defined option list, if debugrevlogopts isn't available,
95 # because commands.debugrevlogopts has been available since 3.7 (or
95 # because commands.debugrevlogopts has been available since 3.7 (or
96 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
96 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
97 # since 1.9 (or a79fea6b3e77).
97 # since 1.9 (or a79fea6b3e77).
98 revlogopts = getattr(cmdutil, "debugrevlogopts",
98 revlogopts = getattr(cmdutil, "debugrevlogopts",
99 getattr(commands, "debugrevlogopts", [
99 getattr(commands, "debugrevlogopts", [
100 ('c', 'changelog', False, ('open changelog')),
100 ('c', 'changelog', False, ('open changelog')),
101 ('m', 'manifest', False, ('open manifest')),
101 ('m', 'manifest', False, ('open manifest')),
102 ('', 'dir', False, ('open directory manifest')),
102 ('', 'dir', False, ('open directory manifest')),
103 ]))
103 ]))
104
104
105 cmdtable = {}
105 cmdtable = {}
106
106
107 # for "historical portability":
107 # for "historical portability":
108 # define parsealiases locally, because cmdutil.parsealiases has been
108 # define parsealiases locally, because cmdutil.parsealiases has been
109 # available since 1.5 (or 6252852b4332)
109 # available since 1.5 (or 6252852b4332)
110 def parsealiases(cmd):
110 def parsealiases(cmd):
111 return cmd.lstrip("^").split("|")
111 return cmd.lstrip("^").split("|")
112
112
113 if safehasattr(registrar, 'command'):
113 if safehasattr(registrar, 'command'):
114 command = registrar.command(cmdtable)
114 command = registrar.command(cmdtable)
115 elif safehasattr(cmdutil, 'command'):
115 elif safehasattr(cmdutil, 'command'):
116 import inspect
116 import inspect
117 command = cmdutil.command(cmdtable)
117 command = cmdutil.command(cmdtable)
118 if 'norepo' not in inspect.getargspec(command)[0]:
118 if 'norepo' not in inspect.getargspec(command)[0]:
119 # for "historical portability":
119 # for "historical portability":
120 # wrap original cmdutil.command, because "norepo" option has
120 # wrap original cmdutil.command, because "norepo" option has
121 # been available since 3.1 (or 75a96326cecb)
121 # been available since 3.1 (or 75a96326cecb)
122 _command = command
122 _command = command
123 def command(name, options=(), synopsis=None, norepo=False):
123 def command(name, options=(), synopsis=None, norepo=False):
124 if norepo:
124 if norepo:
125 commands.norepo += ' %s' % ' '.join(parsealiases(name))
125 commands.norepo += ' %s' % ' '.join(parsealiases(name))
126 return _command(name, list(options), synopsis)
126 return _command(name, list(options), synopsis)
127 else:
127 else:
128 # for "historical portability":
128 # for "historical portability":
129 # define "@command" annotation locally, because cmdutil.command
129 # define "@command" annotation locally, because cmdutil.command
130 # has been available since 1.9 (or 2daa5179e73f)
130 # has been available since 1.9 (or 2daa5179e73f)
131 def command(name, options=(), synopsis=None, norepo=False):
131 def command(name, options=(), synopsis=None, norepo=False):
132 def decorator(func):
132 def decorator(func):
133 if synopsis:
133 if synopsis:
134 cmdtable[name] = func, list(options), synopsis
134 cmdtable[name] = func, list(options), synopsis
135 else:
135 else:
136 cmdtable[name] = func, list(options)
136 cmdtable[name] = func, list(options)
137 if norepo:
137 if norepo:
138 commands.norepo += ' %s' % ' '.join(parsealiases(name))
138 commands.norepo += ' %s' % ' '.join(parsealiases(name))
139 return func
139 return func
140 return decorator
140 return decorator
141
141
142 try:
142 try:
143 import registrar
143 import registrar
144 configtable = {}
144 configtable = {}
145 configitem = registrar.configitem(configtable)
145 configitem = registrar.configitem(configtable)
146 configitem('perf', 'stub',
146 configitem('perf', 'stub',
147 default=False,
147 default=False,
148 )
148 )
149 except (ImportError, AttributeError):
149 except (ImportError, AttributeError):
150 pass
150 pass
151
151
152 def getlen(ui):
152 def getlen(ui):
153 if ui.configbool("perf", "stub"):
153 if ui.configbool("perf", "stub"):
154 return lambda x: 1
154 return lambda x: 1
155 return len
155 return len
156
156
157 def gettimer(ui, opts=None):
157 def gettimer(ui, opts=None):
158 """return a timer function and formatter: (timer, formatter)
158 """return a timer function and formatter: (timer, formatter)
159
159
160 This function exists to gather the creation of formatter in a single
160 This function exists to gather the creation of formatter in a single
161 place instead of duplicating it in all performance commands."""
161 place instead of duplicating it in all performance commands."""
162
162
163 # enforce an idle period before execution to counteract power management
163 # enforce an idle period before execution to counteract power management
164 # experimental config: perf.presleep
164 # experimental config: perf.presleep
165 time.sleep(getint(ui, "perf", "presleep", 1))
165 time.sleep(getint(ui, "perf", "presleep", 1))
166
166
167 if opts is None:
167 if opts is None:
168 opts = {}
168 opts = {}
169 # redirect all to stderr unless buffer api is in use
169 # redirect all to stderr unless buffer api is in use
170 if not ui._buffers:
170 if not ui._buffers:
171 ui = ui.copy()
171 ui = ui.copy()
172 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
172 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
173 if uifout:
173 if uifout:
174 # for "historical portability":
174 # for "historical portability":
175 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
175 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
176 uifout.set(ui.ferr)
176 uifout.set(ui.ferr)
177
177
178 # get a formatter
178 # get a formatter
179 uiformatter = getattr(ui, 'formatter', None)
179 uiformatter = getattr(ui, 'formatter', None)
180 if uiformatter:
180 if uiformatter:
181 fm = uiformatter('perf', opts)
181 fm = uiformatter('perf', opts)
182 else:
182 else:
183 # for "historical portability":
183 # for "historical portability":
184 # define formatter locally, because ui.formatter has been
184 # define formatter locally, because ui.formatter has been
185 # available since 2.2 (or ae5f92e154d3)
185 # available since 2.2 (or ae5f92e154d3)
186 from mercurial import node
186 from mercurial import node
187 class defaultformatter(object):
187 class defaultformatter(object):
188 """Minimized composition of baseformatter and plainformatter
188 """Minimized composition of baseformatter and plainformatter
189 """
189 """
190 def __init__(self, ui, topic, opts):
190 def __init__(self, ui, topic, opts):
191 self._ui = ui
191 self._ui = ui
192 if ui.debugflag:
192 if ui.debugflag:
193 self.hexfunc = node.hex
193 self.hexfunc = node.hex
194 else:
194 else:
195 self.hexfunc = node.short
195 self.hexfunc = node.short
196 def __nonzero__(self):
196 def __nonzero__(self):
197 return False
197 return False
198 __bool__ = __nonzero__
198 __bool__ = __nonzero__
199 def startitem(self):
199 def startitem(self):
200 pass
200 pass
201 def data(self, **data):
201 def data(self, **data):
202 pass
202 pass
203 def write(self, fields, deftext, *fielddata, **opts):
203 def write(self, fields, deftext, *fielddata, **opts):
204 self._ui.write(deftext % fielddata, **opts)
204 self._ui.write(deftext % fielddata, **opts)
205 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
205 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
206 if cond:
206 if cond:
207 self._ui.write(deftext % fielddata, **opts)
207 self._ui.write(deftext % fielddata, **opts)
208 def plain(self, text, **opts):
208 def plain(self, text, **opts):
209 self._ui.write(text, **opts)
209 self._ui.write(text, **opts)
210 def end(self):
210 def end(self):
211 pass
211 pass
212 fm = defaultformatter(ui, 'perf', opts)
212 fm = defaultformatter(ui, 'perf', opts)
213
213
214 # stub function, runs code only once instead of in a loop
214 # stub function, runs code only once instead of in a loop
215 # experimental config: perf.stub
215 # experimental config: perf.stub
216 if ui.configbool("perf", "stub"):
216 if ui.configbool("perf", "stub"):
217 return functools.partial(stub_timer, fm), fm
217 return functools.partial(stub_timer, fm), fm
218 return functools.partial(_timer, fm), fm
218 return functools.partial(_timer, fm), fm
219
219
220 def stub_timer(fm, func, title=None):
220 def stub_timer(fm, func, title=None):
221 func()
221 func()
222
222
223 def _timer(fm, func, title=None):
223 def _timer(fm, func, title=None):
224 gc.collect()
224 gc.collect()
225 results = []
225 results = []
226 begin = util.timer()
226 begin = util.timer()
227 count = 0
227 count = 0
228 while True:
228 while True:
229 ostart = os.times()
229 ostart = os.times()
230 cstart = util.timer()
230 cstart = util.timer()
231 r = func()
231 r = func()
232 cstop = util.timer()
232 cstop = util.timer()
233 ostop = os.times()
233 ostop = os.times()
234 count += 1
234 count += 1
235 a, b = ostart, ostop
235 a, b = ostart, ostop
236 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
236 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
237 if cstop - begin > 3 and count >= 100:
237 if cstop - begin > 3 and count >= 100:
238 break
238 break
239 if cstop - begin > 10 and count >= 3:
239 if cstop - begin > 10 and count >= 3:
240 break
240 break
241
241
242 fm.startitem()
242 fm.startitem()
243
243
244 if title:
244 if title:
245 fm.write('title', '! %s\n', title)
245 fm.write('title', '! %s\n', title)
246 if r:
246 if r:
247 fm.write('result', '! result: %s\n', r)
247 fm.write('result', '! result: %s\n', r)
248 m = min(results)
248 m = min(results)
249 fm.plain('!')
249 fm.plain('!')
250 fm.write('wall', ' wall %f', m[0])
250 fm.write('wall', ' wall %f', m[0])
251 fm.write('comb', ' comb %f', m[1] + m[2])
251 fm.write('comb', ' comb %f', m[1] + m[2])
252 fm.write('user', ' user %f', m[1])
252 fm.write('user', ' user %f', m[1])
253 fm.write('sys', ' sys %f', m[2])
253 fm.write('sys', ' sys %f', m[2])
254 fm.write('count', ' (best of %d)', count)
254 fm.write('count', ' (best of %d)', count)
255 fm.plain('\n')
255 fm.plain('\n')
256
256
257 # utilities for historical portability
257 # utilities for historical portability
258
258
259 def getint(ui, section, name, default):
259 def getint(ui, section, name, default):
260 # for "historical portability":
260 # for "historical portability":
261 # ui.configint has been available since 1.9 (or fa2b596db182)
261 # ui.configint has been available since 1.9 (or fa2b596db182)
262 v = ui.config(section, name, None)
262 v = ui.config(section, name, None)
263 if v is None:
263 if v is None:
264 return default
264 return default
265 try:
265 try:
266 return int(v)
266 return int(v)
267 except ValueError:
267 except ValueError:
268 raise error.ConfigError(("%s.%s is not an integer ('%s')")
268 raise error.ConfigError(("%s.%s is not an integer ('%s')")
269 % (section, name, v))
269 % (section, name, v))
270
270
271 def safeattrsetter(obj, name, ignoremissing=False):
271 def safeattrsetter(obj, name, ignoremissing=False):
272 """Ensure that 'obj' has 'name' attribute before subsequent setattr
272 """Ensure that 'obj' has 'name' attribute before subsequent setattr
273
273
274 This function is aborted, if 'obj' doesn't have 'name' attribute
274 This function is aborted, if 'obj' doesn't have 'name' attribute
275 at runtime. This avoids overlooking removal of an attribute, which
275 at runtime. This avoids overlooking removal of an attribute, which
276 breaks assumption of performance measurement, in the future.
276 breaks assumption of performance measurement, in the future.
277
277
278 This function returns the object to (1) assign a new value, and
278 This function returns the object to (1) assign a new value, and
279 (2) restore an original value to the attribute.
279 (2) restore an original value to the attribute.
280
280
281 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
281 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
282 abortion, and this function returns None. This is useful to
282 abortion, and this function returns None. This is useful to
283 examine an attribute, which isn't ensured in all Mercurial
283 examine an attribute, which isn't ensured in all Mercurial
284 versions.
284 versions.
285 """
285 """
286 if not util.safehasattr(obj, name):
286 if not util.safehasattr(obj, name):
287 if ignoremissing:
287 if ignoremissing:
288 return None
288 return None
289 raise error.Abort(("missing attribute %s of %s might break assumption"
289 raise error.Abort(("missing attribute %s of %s might break assumption"
290 " of performance measurement") % (name, obj))
290 " of performance measurement") % (name, obj))
291
291
292 origvalue = getattr(obj, name)
292 origvalue = getattr(obj, name)
293 class attrutil(object):
293 class attrutil(object):
294 def set(self, newvalue):
294 def set(self, newvalue):
295 setattr(obj, name, newvalue)
295 setattr(obj, name, newvalue)
296 def restore(self):
296 def restore(self):
297 setattr(obj, name, origvalue)
297 setattr(obj, name, origvalue)
298
298
299 return attrutil()
299 return attrutil()
300
300
301 # utilities to examine each internal API changes
301 # utilities to examine each internal API changes
302
302
303 def getbranchmapsubsettable():
303 def getbranchmapsubsettable():
304 # for "historical portability":
304 # for "historical portability":
305 # subsettable is defined in:
305 # subsettable is defined in:
306 # - branchmap since 2.9 (or 175c6fd8cacc)
306 # - branchmap since 2.9 (or 175c6fd8cacc)
307 # - repoview since 2.5 (or 59a9f18d4587)
307 # - repoview since 2.5 (or 59a9f18d4587)
308 for mod in (branchmap, repoview):
308 for mod in (branchmap, repoview):
309 subsettable = getattr(mod, 'subsettable', None)
309 subsettable = getattr(mod, 'subsettable', None)
310 if subsettable:
310 if subsettable:
311 return subsettable
311 return subsettable
312
312
313 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
313 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
314 # branchmap and repoview modules exist, but subsettable attribute
314 # branchmap and repoview modules exist, but subsettable attribute
315 # doesn't)
315 # doesn't)
316 raise error.Abort(("perfbranchmap not available with this Mercurial"),
316 raise error.Abort(("perfbranchmap not available with this Mercurial"),
317 hint="use 2.5 or later")
317 hint="use 2.5 or later")
318
318
319 def getsvfs(repo):
319 def getsvfs(repo):
320 """Return appropriate object to access files under .hg/store
320 """Return appropriate object to access files under .hg/store
321 """
321 """
322 # for "historical portability":
322 # for "historical portability":
323 # repo.svfs has been available since 2.3 (or 7034365089bf)
323 # repo.svfs has been available since 2.3 (or 7034365089bf)
324 svfs = getattr(repo, 'svfs', None)
324 svfs = getattr(repo, 'svfs', None)
325 if svfs:
325 if svfs:
326 return svfs
326 return svfs
327 else:
327 else:
328 return getattr(repo, 'sopener')
328 return getattr(repo, 'sopener')
329
329
330 def getvfs(repo):
330 def getvfs(repo):
331 """Return appropriate object to access files under .hg
331 """Return appropriate object to access files under .hg
332 """
332 """
333 # for "historical portability":
333 # for "historical portability":
334 # repo.vfs has been available since 2.3 (or 7034365089bf)
334 # repo.vfs has been available since 2.3 (or 7034365089bf)
335 vfs = getattr(repo, 'vfs', None)
335 vfs = getattr(repo, 'vfs', None)
336 if vfs:
336 if vfs:
337 return vfs
337 return vfs
338 else:
338 else:
339 return getattr(repo, 'opener')
339 return getattr(repo, 'opener')
340
340
341 def repocleartagscachefunc(repo):
341 def repocleartagscachefunc(repo):
342 """Return the function to clear tags cache according to repo internal API
342 """Return the function to clear tags cache according to repo internal API
343 """
343 """
344 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
344 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
345 # in this case, setattr(repo, '_tagscache', None) or so isn't
345 # in this case, setattr(repo, '_tagscache', None) or so isn't
346 # correct way to clear tags cache, because existing code paths
346 # correct way to clear tags cache, because existing code paths
347 # expect _tagscache to be a structured object.
347 # expect _tagscache to be a structured object.
348 def clearcache():
348 def clearcache():
349 # _tagscache has been filteredpropertycache since 2.5 (or
349 # _tagscache has been filteredpropertycache since 2.5 (or
350 # 98c867ac1330), and delattr() can't work in such case
350 # 98c867ac1330), and delattr() can't work in such case
351 if '_tagscache' in vars(repo):
351 if '_tagscache' in vars(repo):
352 del repo.__dict__['_tagscache']
352 del repo.__dict__['_tagscache']
353 return clearcache
353 return clearcache
354
354
355 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
355 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
356 if repotags: # since 1.4 (or 5614a628d173)
356 if repotags: # since 1.4 (or 5614a628d173)
357 return lambda : repotags.set(None)
357 return lambda : repotags.set(None)
358
358
359 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
359 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
360 if repotagscache: # since 0.6 (or d7df759d0e97)
360 if repotagscache: # since 0.6 (or d7df759d0e97)
361 return lambda : repotagscache.set(None)
361 return lambda : repotagscache.set(None)
362
362
363 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
363 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
364 # this point, but it isn't so problematic, because:
364 # this point, but it isn't so problematic, because:
365 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
365 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
366 # in perftags() causes failure soon
366 # in perftags() causes failure soon
367 # - perf.py itself has been available since 1.1 (or eb240755386d)
367 # - perf.py itself has been available since 1.1 (or eb240755386d)
368 raise error.Abort(("tags API of this hg command is unknown"))
368 raise error.Abort(("tags API of this hg command is unknown"))
369
369
370 # utilities to clear cache
370 # utilities to clear cache
371
371
372 def clearfilecache(repo, attrname):
372 def clearfilecache(repo, attrname):
373 unfi = repo.unfiltered()
373 unfi = repo.unfiltered()
374 if attrname in vars(unfi):
374 if attrname in vars(unfi):
375 delattr(unfi, attrname)
375 delattr(unfi, attrname)
376 unfi._filecache.pop(attrname, None)
376 unfi._filecache.pop(attrname, None)
377
377
378 # perf commands
378 # perf commands
379
379
380 @command('perfwalk', formatteropts)
380 @command('perfwalk', formatteropts)
381 def perfwalk(ui, repo, *pats, **opts):
381 def perfwalk(ui, repo, *pats, **opts):
382 timer, fm = gettimer(ui, opts)
382 timer, fm = gettimer(ui, opts)
383 m = scmutil.match(repo[None], pats, {})
383 m = scmutil.match(repo[None], pats, {})
384 timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
384 timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
385 ignored=False))))
385 ignored=False))))
386 fm.end()
386 fm.end()
387
387
388 @command('perfannotate', formatteropts)
388 @command('perfannotate', formatteropts)
389 def perfannotate(ui, repo, f, **opts):
389 def perfannotate(ui, repo, f, **opts):
390 timer, fm = gettimer(ui, opts)
390 timer, fm = gettimer(ui, opts)
391 fc = repo['.'][f]
391 fc = repo['.'][f]
392 timer(lambda: len(fc.annotate(True)))
392 timer(lambda: len(fc.annotate(True)))
393 fm.end()
393 fm.end()
394
394
395 @command('perfstatus',
395 @command('perfstatus',
396 [('u', 'unknown', False,
396 [('u', 'unknown', False,
397 'ask status to look for unknown files')] + formatteropts)
397 'ask status to look for unknown files')] + formatteropts)
398 def perfstatus(ui, repo, **opts):
398 def perfstatus(ui, repo, **opts):
399 #m = match.always(repo.root, repo.getcwd())
399 #m = match.always(repo.root, repo.getcwd())
400 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
400 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
401 # False))))
401 # False))))
402 timer, fm = gettimer(ui, opts)
402 timer, fm = gettimer(ui, opts)
403 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
403 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
404 fm.end()
404 fm.end()
405
405
406 @command('perfaddremove', formatteropts)
406 @command('perfaddremove', formatteropts)
407 def perfaddremove(ui, repo, **opts):
407 def perfaddremove(ui, repo, **opts):
408 timer, fm = gettimer(ui, opts)
408 timer, fm = gettimer(ui, opts)
409 try:
409 try:
410 oldquiet = repo.ui.quiet
410 oldquiet = repo.ui.quiet
411 repo.ui.quiet = True
411 repo.ui.quiet = True
412 matcher = scmutil.match(repo[None])
412 matcher = scmutil.match(repo[None])
413 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
413 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
414 finally:
414 finally:
415 repo.ui.quiet = oldquiet
415 repo.ui.quiet = oldquiet
416 fm.end()
416 fm.end()
417
417
418 def clearcaches(cl):
418 def clearcaches(cl):
419 # behave somewhat consistently across internal API changes
419 # behave somewhat consistently across internal API changes
420 if util.safehasattr(cl, 'clearcaches'):
420 if util.safehasattr(cl, 'clearcaches'):
421 cl.clearcaches()
421 cl.clearcaches()
422 elif util.safehasattr(cl, '_nodecache'):
422 elif util.safehasattr(cl, '_nodecache'):
423 from mercurial.node import nullid, nullrev
423 from mercurial.node import nullid, nullrev
424 cl._nodecache = {nullid: nullrev}
424 cl._nodecache = {nullid: nullrev}
425 cl._nodepos = None
425 cl._nodepos = None
426
426
427 @command('perfheads', formatteropts)
427 @command('perfheads', formatteropts)
428 def perfheads(ui, repo, **opts):
428 def perfheads(ui, repo, **opts):
429 timer, fm = gettimer(ui, opts)
429 timer, fm = gettimer(ui, opts)
430 cl = repo.changelog
430 cl = repo.changelog
431 def d():
431 def d():
432 len(cl.headrevs())
432 len(cl.headrevs())
433 clearcaches(cl)
433 clearcaches(cl)
434 timer(d)
434 timer(d)
435 fm.end()
435 fm.end()
436
436
437 @command('perftags', formatteropts)
437 @command('perftags', formatteropts)
438 def perftags(ui, repo, **opts):
438 def perftags(ui, repo, **opts):
439 import mercurial.changelog
439 import mercurial.changelog
440 import mercurial.manifest
440 import mercurial.manifest
441 timer, fm = gettimer(ui, opts)
441 timer, fm = gettimer(ui, opts)
442 svfs = getsvfs(repo)
442 svfs = getsvfs(repo)
443 repocleartagscache = repocleartagscachefunc(repo)
443 repocleartagscache = repocleartagscachefunc(repo)
444 def t():
444 def t():
445 repo.changelog = mercurial.changelog.changelog(svfs)
445 repo.changelog = mercurial.changelog.changelog(svfs)
446 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
446 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
447 repocleartagscache()
447 repocleartagscache()
448 return len(repo.tags())
448 return len(repo.tags())
449 timer(t)
449 timer(t)
450 fm.end()
450 fm.end()
451
451
452 @command('perfancestors', formatteropts)
452 @command('perfancestors', formatteropts)
453 def perfancestors(ui, repo, **opts):
453 def perfancestors(ui, repo, **opts):
454 timer, fm = gettimer(ui, opts)
454 timer, fm = gettimer(ui, opts)
455 heads = repo.changelog.headrevs()
455 heads = repo.changelog.headrevs()
456 def d():
456 def d():
457 for a in repo.changelog.ancestors(heads):
457 for a in repo.changelog.ancestors(heads):
458 pass
458 pass
459 timer(d)
459 timer(d)
460 fm.end()
460 fm.end()
461
461
462 @command('perfancestorset', formatteropts)
462 @command('perfancestorset', formatteropts)
463 def perfancestorset(ui, repo, revset, **opts):
463 def perfancestorset(ui, repo, revset, **opts):
464 timer, fm = gettimer(ui, opts)
464 timer, fm = gettimer(ui, opts)
465 revs = repo.revs(revset)
465 revs = repo.revs(revset)
466 heads = repo.changelog.headrevs()
466 heads = repo.changelog.headrevs()
467 def d():
467 def d():
468 s = repo.changelog.ancestors(heads)
468 s = repo.changelog.ancestors(heads)
469 for rev in revs:
469 for rev in revs:
470 rev in s
470 rev in s
471 timer(d)
471 timer(d)
472 fm.end()
472 fm.end()
473
473
474 @command('perfbookmarks', formatteropts)
474 @command('perfbookmarks', formatteropts)
475 def perfbookmarks(ui, repo, **opts):
475 def perfbookmarks(ui, repo, **opts):
476 """benchmark parsing bookmarks from disk to memory"""
476 """benchmark parsing bookmarks from disk to memory"""
477 timer, fm = gettimer(ui, opts)
477 timer, fm = gettimer(ui, opts)
478 def d():
478 def d():
479 clearfilecache(repo, '_bookmarks')
479 clearfilecache(repo, '_bookmarks')
480 repo._bookmarks
480 repo._bookmarks
481 timer(d)
481 timer(d)
482 fm.end()
482 fm.end()
483
483
484 @command('perfchangegroupchangelog', formatteropts +
484 @command('perfchangegroupchangelog', formatteropts +
485 [('', 'version', '02', 'changegroup version'),
485 [('', 'version', '02', 'changegroup version'),
486 ('r', 'rev', '', 'revisions to add to changegroup')])
486 ('r', 'rev', '', 'revisions to add to changegroup')])
487 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
487 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
488 """Benchmark producing a changelog group for a changegroup.
488 """Benchmark producing a changelog group for a changegroup.
489
489
490 This measures the time spent processing the changelog during a
490 This measures the time spent processing the changelog during a
491 bundle operation. This occurs during `hg bundle` and on a server
491 bundle operation. This occurs during `hg bundle` and on a server
492 processing a `getbundle` wire protocol request (handles clones
492 processing a `getbundle` wire protocol request (handles clones
493 and pull requests).
493 and pull requests).
494
494
495 By default, all revisions are added to the changegroup.
495 By default, all revisions are added to the changegroup.
496 """
496 """
497 cl = repo.changelog
497 cl = repo.changelog
498 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
498 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
499 bundler = changegroup.getbundler(version, repo)
499 bundler = changegroup.getbundler(version, repo)
500
500
501 def lookup(node):
501 def lookup(node):
502 # The real bundler reads the revision in order to access the
502 # The real bundler reads the revision in order to access the
503 # manifest node and files list. Do that here.
503 # manifest node and files list. Do that here.
504 cl.read(node)
504 cl.read(node)
505 return node
505 return node
506
506
507 def d():
507 def d():
508 for chunk in bundler.group(revs, cl, lookup):
508 for chunk in bundler.group(revs, cl, lookup):
509 pass
509 pass
510
510
511 timer, fm = gettimer(ui, opts)
511 timer, fm = gettimer(ui, opts)
512 timer(d)
512 timer(d)
513 fm.end()
513 fm.end()
514
514
515 @command('perfdirs', formatteropts)
515 @command('perfdirs', formatteropts)
516 def perfdirs(ui, repo, **opts):
516 def perfdirs(ui, repo, **opts):
517 timer, fm = gettimer(ui, opts)
517 timer, fm = gettimer(ui, opts)
518 dirstate = repo.dirstate
518 dirstate = repo.dirstate
519 'a' in dirstate
519 'a' in dirstate
520 def d():
520 def d():
521 dirstate.dirs()
521 dirstate.dirs()
522 del dirstate._map.dirs
522 del dirstate._map.dirs
523 timer(d)
523 timer(d)
524 fm.end()
524 fm.end()
525
525
526 @command('perfdirstate', formatteropts)
526 @command('perfdirstate', formatteropts)
527 def perfdirstate(ui, repo, **opts):
527 def perfdirstate(ui, repo, **opts):
528 timer, fm = gettimer(ui, opts)
528 timer, fm = gettimer(ui, opts)
529 "a" in repo.dirstate
529 "a" in repo.dirstate
530 def d():
530 def d():
531 repo.dirstate.invalidate()
531 repo.dirstate.invalidate()
532 "a" in repo.dirstate
532 "a" in repo.dirstate
533 timer(d)
533 timer(d)
534 fm.end()
534 fm.end()
535
535
536 @command('perfdirstatedirs', formatteropts)
536 @command('perfdirstatedirs', formatteropts)
537 def perfdirstatedirs(ui, repo, **opts):
537 def perfdirstatedirs(ui, repo, **opts):
538 timer, fm = gettimer(ui, opts)
538 timer, fm = gettimer(ui, opts)
539 "a" in repo.dirstate
539 "a" in repo.dirstate
540 def d():
540 def d():
541 "a" in repo.dirstate._map.dirs
541 "a" in repo.dirstate._map.dirs
542 del repo.dirstate._map.dirs
542 del repo.dirstate._map.dirs
543 timer(d)
543 timer(d)
544 fm.end()
544 fm.end()
545
545
546 @command('perfdirstatefoldmap', formatteropts)
546 @command('perfdirstatefoldmap', formatteropts)
547 def perfdirstatefoldmap(ui, repo, **opts):
547 def perfdirstatefoldmap(ui, repo, **opts):
548 timer, fm = gettimer(ui, opts)
548 timer, fm = gettimer(ui, opts)
549 dirstate = repo.dirstate
549 dirstate = repo.dirstate
550 'a' in dirstate
550 'a' in dirstate
551 def d():
551 def d():
552 dirstate._map.filefoldmap.get('a')
552 dirstate._map.filefoldmap.get('a')
553 del dirstate._map.filefoldmap
553 del dirstate._map.filefoldmap
554 timer(d)
554 timer(d)
555 fm.end()
555 fm.end()
556
556
557 @command('perfdirfoldmap', formatteropts)
557 @command('perfdirfoldmap', formatteropts)
558 def perfdirfoldmap(ui, repo, **opts):
558 def perfdirfoldmap(ui, repo, **opts):
559 timer, fm = gettimer(ui, opts)
559 timer, fm = gettimer(ui, opts)
560 dirstate = repo.dirstate
560 dirstate = repo.dirstate
561 'a' in dirstate
561 'a' in dirstate
562 def d():
562 def d():
563 dirstate._dirfoldmap.get('a')
563 dirstate._map.dirfoldmap.get('a')
564 del dirstate._dirfoldmap
564 del dirstate._map.dirfoldmap
565 del dirstate._map.dirs
565 del dirstate._map.dirs
566 timer(d)
566 timer(d)
567 fm.end()
567 fm.end()
568
568
569 @command('perfdirstatewrite', formatteropts)
569 @command('perfdirstatewrite', formatteropts)
570 def perfdirstatewrite(ui, repo, **opts):
570 def perfdirstatewrite(ui, repo, **opts):
571 timer, fm = gettimer(ui, opts)
571 timer, fm = gettimer(ui, opts)
572 ds = repo.dirstate
572 ds = repo.dirstate
573 "a" in ds
573 "a" in ds
574 def d():
574 def d():
575 ds._dirty = True
575 ds._dirty = True
576 ds.write(repo.currenttransaction())
576 ds.write(repo.currenttransaction())
577 timer(d)
577 timer(d)
578 fm.end()
578 fm.end()
579
579
580 @command('perfmergecalculate',
580 @command('perfmergecalculate',
581 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
581 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
582 def perfmergecalculate(ui, repo, rev, **opts):
582 def perfmergecalculate(ui, repo, rev, **opts):
583 timer, fm = gettimer(ui, opts)
583 timer, fm = gettimer(ui, opts)
584 wctx = repo[None]
584 wctx = repo[None]
585 rctx = scmutil.revsingle(repo, rev, rev)
585 rctx = scmutil.revsingle(repo, rev, rev)
586 ancestor = wctx.ancestor(rctx)
586 ancestor = wctx.ancestor(rctx)
587 # we don't want working dir files to be stat'd in the benchmark, so prime
587 # we don't want working dir files to be stat'd in the benchmark, so prime
588 # that cache
588 # that cache
589 wctx.dirty()
589 wctx.dirty()
590 def d():
590 def d():
591 # acceptremote is True because we don't want prompts in the middle of
591 # acceptremote is True because we don't want prompts in the middle of
592 # our benchmark
592 # our benchmark
593 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
593 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
594 acceptremote=True, followcopies=True)
594 acceptremote=True, followcopies=True)
595 timer(d)
595 timer(d)
596 fm.end()
596 fm.end()
597
597
598 @command('perfpathcopies', [], "REV REV")
598 @command('perfpathcopies', [], "REV REV")
599 def perfpathcopies(ui, repo, rev1, rev2, **opts):
599 def perfpathcopies(ui, repo, rev1, rev2, **opts):
600 timer, fm = gettimer(ui, opts)
600 timer, fm = gettimer(ui, opts)
601 ctx1 = scmutil.revsingle(repo, rev1, rev1)
601 ctx1 = scmutil.revsingle(repo, rev1, rev1)
602 ctx2 = scmutil.revsingle(repo, rev2, rev2)
602 ctx2 = scmutil.revsingle(repo, rev2, rev2)
603 def d():
603 def d():
604 copies.pathcopies(ctx1, ctx2)
604 copies.pathcopies(ctx1, ctx2)
605 timer(d)
605 timer(d)
606 fm.end()
606 fm.end()
607
607
608 @command('perfphases',
608 @command('perfphases',
609 [('', 'full', False, 'include file reading time too'),
609 [('', 'full', False, 'include file reading time too'),
610 ], "")
610 ], "")
611 def perfphases(ui, repo, **opts):
611 def perfphases(ui, repo, **opts):
612 """benchmark phasesets computation"""
612 """benchmark phasesets computation"""
613 timer, fm = gettimer(ui, opts)
613 timer, fm = gettimer(ui, opts)
614 _phases = repo._phasecache
614 _phases = repo._phasecache
615 full = opts.get('full')
615 full = opts.get('full')
616 def d():
616 def d():
617 phases = _phases
617 phases = _phases
618 if full:
618 if full:
619 clearfilecache(repo, '_phasecache')
619 clearfilecache(repo, '_phasecache')
620 phases = repo._phasecache
620 phases = repo._phasecache
621 phases.invalidate()
621 phases.invalidate()
622 phases.loadphaserevs(repo)
622 phases.loadphaserevs(repo)
623 timer(d)
623 timer(d)
624 fm.end()
624 fm.end()
625
625
626 @command('perfmanifest', [], 'REV')
626 @command('perfmanifest', [], 'REV')
627 def perfmanifest(ui, repo, rev, **opts):
627 def perfmanifest(ui, repo, rev, **opts):
628 timer, fm = gettimer(ui, opts)
628 timer, fm = gettimer(ui, opts)
629 ctx = scmutil.revsingle(repo, rev, rev)
629 ctx = scmutil.revsingle(repo, rev, rev)
630 t = ctx.manifestnode()
630 t = ctx.manifestnode()
631 def d():
631 def d():
632 repo.manifestlog.clearcaches()
632 repo.manifestlog.clearcaches()
633 repo.manifestlog[t].read()
633 repo.manifestlog[t].read()
634 timer(d)
634 timer(d)
635 fm.end()
635 fm.end()
636
636
637 @command('perfchangeset', formatteropts)
637 @command('perfchangeset', formatteropts)
638 def perfchangeset(ui, repo, rev, **opts):
638 def perfchangeset(ui, repo, rev, **opts):
639 timer, fm = gettimer(ui, opts)
639 timer, fm = gettimer(ui, opts)
640 n = repo[rev].node()
640 n = repo[rev].node()
641 def d():
641 def d():
642 repo.changelog.read(n)
642 repo.changelog.read(n)
643 #repo.changelog._cache = None
643 #repo.changelog._cache = None
644 timer(d)
644 timer(d)
645 fm.end()
645 fm.end()
646
646
647 @command('perfindex', formatteropts)
647 @command('perfindex', formatteropts)
648 def perfindex(ui, repo, **opts):
648 def perfindex(ui, repo, **opts):
649 import mercurial.revlog
649 import mercurial.revlog
650 timer, fm = gettimer(ui, opts)
650 timer, fm = gettimer(ui, opts)
651 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
651 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
652 n = repo["tip"].node()
652 n = repo["tip"].node()
653 svfs = getsvfs(repo)
653 svfs = getsvfs(repo)
654 def d():
654 def d():
655 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
655 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
656 cl.rev(n)
656 cl.rev(n)
657 timer(d)
657 timer(d)
658 fm.end()
658 fm.end()
659
659
660 @command('perfstartup', formatteropts)
660 @command('perfstartup', formatteropts)
661 def perfstartup(ui, repo, **opts):
661 def perfstartup(ui, repo, **opts):
662 timer, fm = gettimer(ui, opts)
662 timer, fm = gettimer(ui, opts)
663 cmd = sys.argv[0]
663 cmd = sys.argv[0]
664 def d():
664 def d():
665 if os.name != 'nt':
665 if os.name != 'nt':
666 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
666 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
667 else:
667 else:
668 os.environ['HGRCPATH'] = ' '
668 os.environ['HGRCPATH'] = ' '
669 os.system("%s version -q > NUL" % cmd)
669 os.system("%s version -q > NUL" % cmd)
670 timer(d)
670 timer(d)
671 fm.end()
671 fm.end()
672
672
673 @command('perfparents', formatteropts)
673 @command('perfparents', formatteropts)
674 def perfparents(ui, repo, **opts):
674 def perfparents(ui, repo, **opts):
675 timer, fm = gettimer(ui, opts)
675 timer, fm = gettimer(ui, opts)
676 # control the number of commits perfparents iterates over
676 # control the number of commits perfparents iterates over
677 # experimental config: perf.parentscount
677 # experimental config: perf.parentscount
678 count = getint(ui, "perf", "parentscount", 1000)
678 count = getint(ui, "perf", "parentscount", 1000)
679 if len(repo.changelog) < count:
679 if len(repo.changelog) < count:
680 raise error.Abort("repo needs %d commits for this test" % count)
680 raise error.Abort("repo needs %d commits for this test" % count)
681 repo = repo.unfiltered()
681 repo = repo.unfiltered()
682 nl = [repo.changelog.node(i) for i in xrange(count)]
682 nl = [repo.changelog.node(i) for i in xrange(count)]
683 def d():
683 def d():
684 for n in nl:
684 for n in nl:
685 repo.changelog.parents(n)
685 repo.changelog.parents(n)
686 timer(d)
686 timer(d)
687 fm.end()
687 fm.end()
688
688
689 @command('perfctxfiles', formatteropts)
689 @command('perfctxfiles', formatteropts)
690 def perfctxfiles(ui, repo, x, **opts):
690 def perfctxfiles(ui, repo, x, **opts):
691 x = int(x)
691 x = int(x)
692 timer, fm = gettimer(ui, opts)
692 timer, fm = gettimer(ui, opts)
693 def d():
693 def d():
694 len(repo[x].files())
694 len(repo[x].files())
695 timer(d)
695 timer(d)
696 fm.end()
696 fm.end()
697
697
698 @command('perfrawfiles', formatteropts)
698 @command('perfrawfiles', formatteropts)
699 def perfrawfiles(ui, repo, x, **opts):
699 def perfrawfiles(ui, repo, x, **opts):
700 x = int(x)
700 x = int(x)
701 timer, fm = gettimer(ui, opts)
701 timer, fm = gettimer(ui, opts)
702 cl = repo.changelog
702 cl = repo.changelog
703 def d():
703 def d():
704 len(cl.read(x)[3])
704 len(cl.read(x)[3])
705 timer(d)
705 timer(d)
706 fm.end()
706 fm.end()
707
707
708 @command('perflookup', formatteropts)
708 @command('perflookup', formatteropts)
709 def perflookup(ui, repo, rev, **opts):
709 def perflookup(ui, repo, rev, **opts):
710 timer, fm = gettimer(ui, opts)
710 timer, fm = gettimer(ui, opts)
711 timer(lambda: len(repo.lookup(rev)))
711 timer(lambda: len(repo.lookup(rev)))
712 fm.end()
712 fm.end()
713
713
714 @command('perfrevrange', formatteropts)
714 @command('perfrevrange', formatteropts)
715 def perfrevrange(ui, repo, *specs, **opts):
715 def perfrevrange(ui, repo, *specs, **opts):
716 timer, fm = gettimer(ui, opts)
716 timer, fm = gettimer(ui, opts)
717 revrange = scmutil.revrange
717 revrange = scmutil.revrange
718 timer(lambda: len(revrange(repo, specs)))
718 timer(lambda: len(revrange(repo, specs)))
719 fm.end()
719 fm.end()
720
720
721 @command('perfnodelookup', formatteropts)
721 @command('perfnodelookup', formatteropts)
722 def perfnodelookup(ui, repo, rev, **opts):
722 def perfnodelookup(ui, repo, rev, **opts):
723 timer, fm = gettimer(ui, opts)
723 timer, fm = gettimer(ui, opts)
724 import mercurial.revlog
724 import mercurial.revlog
725 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
725 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
726 n = repo[rev].node()
726 n = repo[rev].node()
727 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
727 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
728 def d():
728 def d():
729 cl.rev(n)
729 cl.rev(n)
730 clearcaches(cl)
730 clearcaches(cl)
731 timer(d)
731 timer(d)
732 fm.end()
732 fm.end()
733
733
734 @command('perflog',
734 @command('perflog',
735 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
735 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
736 def perflog(ui, repo, rev=None, **opts):
736 def perflog(ui, repo, rev=None, **opts):
737 if rev is None:
737 if rev is None:
738 rev=[]
738 rev=[]
739 timer, fm = gettimer(ui, opts)
739 timer, fm = gettimer(ui, opts)
740 ui.pushbuffer()
740 ui.pushbuffer()
741 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
741 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
742 copies=opts.get('rename')))
742 copies=opts.get('rename')))
743 ui.popbuffer()
743 ui.popbuffer()
744 fm.end()
744 fm.end()
745
745
746 @command('perfmoonwalk', formatteropts)
746 @command('perfmoonwalk', formatteropts)
747 def perfmoonwalk(ui, repo, **opts):
747 def perfmoonwalk(ui, repo, **opts):
748 """benchmark walking the changelog backwards
748 """benchmark walking the changelog backwards
749
749
750 This also loads the changelog data for each revision in the changelog.
750 This also loads the changelog data for each revision in the changelog.
751 """
751 """
752 timer, fm = gettimer(ui, opts)
752 timer, fm = gettimer(ui, opts)
753 def moonwalk():
753 def moonwalk():
754 for i in xrange(len(repo), -1, -1):
754 for i in xrange(len(repo), -1, -1):
755 ctx = repo[i]
755 ctx = repo[i]
756 ctx.branch() # read changelog data (in addition to the index)
756 ctx.branch() # read changelog data (in addition to the index)
757 timer(moonwalk)
757 timer(moonwalk)
758 fm.end()
758 fm.end()
759
759
760 @command('perftemplating', formatteropts)
760 @command('perftemplating', formatteropts)
761 def perftemplating(ui, repo, rev=None, **opts):
761 def perftemplating(ui, repo, rev=None, **opts):
762 if rev is None:
762 if rev is None:
763 rev=[]
763 rev=[]
764 timer, fm = gettimer(ui, opts)
764 timer, fm = gettimer(ui, opts)
765 ui.pushbuffer()
765 ui.pushbuffer()
766 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
766 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
767 template='{date|shortdate} [{rev}:{node|short}]'
767 template='{date|shortdate} [{rev}:{node|short}]'
768 ' {author|person}: {desc|firstline}\n'))
768 ' {author|person}: {desc|firstline}\n'))
769 ui.popbuffer()
769 ui.popbuffer()
770 fm.end()
770 fm.end()
771
771
772 @command('perfcca', formatteropts)
772 @command('perfcca', formatteropts)
773 def perfcca(ui, repo, **opts):
773 def perfcca(ui, repo, **opts):
774 timer, fm = gettimer(ui, opts)
774 timer, fm = gettimer(ui, opts)
775 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
775 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
776 fm.end()
776 fm.end()
777
777
778 @command('perffncacheload', formatteropts)
778 @command('perffncacheload', formatteropts)
779 def perffncacheload(ui, repo, **opts):
779 def perffncacheload(ui, repo, **opts):
780 timer, fm = gettimer(ui, opts)
780 timer, fm = gettimer(ui, opts)
781 s = repo.store
781 s = repo.store
782 def d():
782 def d():
783 s.fncache._load()
783 s.fncache._load()
784 timer(d)
784 timer(d)
785 fm.end()
785 fm.end()
786
786
787 @command('perffncachewrite', formatteropts)
787 @command('perffncachewrite', formatteropts)
788 def perffncachewrite(ui, repo, **opts):
788 def perffncachewrite(ui, repo, **opts):
789 timer, fm = gettimer(ui, opts)
789 timer, fm = gettimer(ui, opts)
790 s = repo.store
790 s = repo.store
791 s.fncache._load()
791 s.fncache._load()
792 lock = repo.lock()
792 lock = repo.lock()
793 tr = repo.transaction('perffncachewrite')
793 tr = repo.transaction('perffncachewrite')
794 def d():
794 def d():
795 s.fncache._dirty = True
795 s.fncache._dirty = True
796 s.fncache.write(tr)
796 s.fncache.write(tr)
797 timer(d)
797 timer(d)
798 tr.close()
798 tr.close()
799 lock.release()
799 lock.release()
800 fm.end()
800 fm.end()
801
801
802 @command('perffncacheencode', formatteropts)
802 @command('perffncacheencode', formatteropts)
803 def perffncacheencode(ui, repo, **opts):
803 def perffncacheencode(ui, repo, **opts):
804 timer, fm = gettimer(ui, opts)
804 timer, fm = gettimer(ui, opts)
805 s = repo.store
805 s = repo.store
806 s.fncache._load()
806 s.fncache._load()
807 def d():
807 def d():
808 for p in s.fncache.entries:
808 for p in s.fncache.entries:
809 s.encode(p)
809 s.encode(p)
810 timer(d)
810 timer(d)
811 fm.end()
811 fm.end()
812
812
813 @command('perfbdiff', revlogopts + formatteropts + [
813 @command('perfbdiff', revlogopts + formatteropts + [
814 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
814 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
815 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
815 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
816 '-c|-m|FILE REV')
816 '-c|-m|FILE REV')
817 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
817 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
818 """benchmark a bdiff between revisions
818 """benchmark a bdiff between revisions
819
819
820 By default, benchmark a bdiff between its delta parent and itself.
820 By default, benchmark a bdiff between its delta parent and itself.
821
821
822 With ``--count``, benchmark bdiffs between delta parents and self for N
822 With ``--count``, benchmark bdiffs between delta parents and self for N
823 revisions starting at the specified revision.
823 revisions starting at the specified revision.
824
824
825 With ``--alldata``, assume the requested revision is a changeset and
825 With ``--alldata``, assume the requested revision is a changeset and
826 measure bdiffs for all changes related to that changeset (manifest
826 measure bdiffs for all changes related to that changeset (manifest
827 and filelogs).
827 and filelogs).
828 """
828 """
829 if opts['alldata']:
829 if opts['alldata']:
830 opts['changelog'] = True
830 opts['changelog'] = True
831
831
832 if opts.get('changelog') or opts.get('manifest'):
832 if opts.get('changelog') or opts.get('manifest'):
833 file_, rev = None, file_
833 file_, rev = None, file_
834 elif rev is None:
834 elif rev is None:
835 raise error.CommandError('perfbdiff', 'invalid arguments')
835 raise error.CommandError('perfbdiff', 'invalid arguments')
836
836
837 textpairs = []
837 textpairs = []
838
838
839 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
839 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
840
840
841 startrev = r.rev(r.lookup(rev))
841 startrev = r.rev(r.lookup(rev))
842 for rev in range(startrev, min(startrev + count, len(r) - 1)):
842 for rev in range(startrev, min(startrev + count, len(r) - 1)):
843 if opts['alldata']:
843 if opts['alldata']:
844 # Load revisions associated with changeset.
844 # Load revisions associated with changeset.
845 ctx = repo[rev]
845 ctx = repo[rev]
846 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
846 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
847 for pctx in ctx.parents():
847 for pctx in ctx.parents():
848 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
848 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
849 textpairs.append((pman, mtext))
849 textpairs.append((pman, mtext))
850
850
851 # Load filelog revisions by iterating manifest delta.
851 # Load filelog revisions by iterating manifest delta.
852 man = ctx.manifest()
852 man = ctx.manifest()
853 pman = ctx.p1().manifest()
853 pman = ctx.p1().manifest()
854 for filename, change in pman.diff(man).items():
854 for filename, change in pman.diff(man).items():
855 fctx = repo.file(filename)
855 fctx = repo.file(filename)
856 f1 = fctx.revision(change[0][0] or -1)
856 f1 = fctx.revision(change[0][0] or -1)
857 f2 = fctx.revision(change[1][0] or -1)
857 f2 = fctx.revision(change[1][0] or -1)
858 textpairs.append((f1, f2))
858 textpairs.append((f1, f2))
859 else:
859 else:
860 dp = r.deltaparent(rev)
860 dp = r.deltaparent(rev)
861 textpairs.append((r.revision(dp), r.revision(rev)))
861 textpairs.append((r.revision(dp), r.revision(rev)))
862
862
863 def d():
863 def d():
864 for pair in textpairs:
864 for pair in textpairs:
865 mdiff.textdiff(*pair)
865 mdiff.textdiff(*pair)
866
866
867 timer, fm = gettimer(ui, opts)
867 timer, fm = gettimer(ui, opts)
868 timer(d)
868 timer(d)
869 fm.end()
869 fm.end()
870
870
871 @command('perfdiffwd', formatteropts)
871 @command('perfdiffwd', formatteropts)
872 def perfdiffwd(ui, repo, **opts):
872 def perfdiffwd(ui, repo, **opts):
873 """Profile diff of working directory changes"""
873 """Profile diff of working directory changes"""
874 timer, fm = gettimer(ui, opts)
874 timer, fm = gettimer(ui, opts)
875 options = {
875 options = {
876 'w': 'ignore_all_space',
876 'w': 'ignore_all_space',
877 'b': 'ignore_space_change',
877 'b': 'ignore_space_change',
878 'B': 'ignore_blank_lines',
878 'B': 'ignore_blank_lines',
879 }
879 }
880
880
881 for diffopt in ('', 'w', 'b', 'B', 'wB'):
881 for diffopt in ('', 'w', 'b', 'B', 'wB'):
882 opts = dict((options[c], '1') for c in diffopt)
882 opts = dict((options[c], '1') for c in diffopt)
883 def d():
883 def d():
884 ui.pushbuffer()
884 ui.pushbuffer()
885 commands.diff(ui, repo, **opts)
885 commands.diff(ui, repo, **opts)
886 ui.popbuffer()
886 ui.popbuffer()
887 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
887 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
888 timer(d, title)
888 timer(d, title)
889 fm.end()
889 fm.end()
890
890
891 @command('perfrevlogindex', revlogopts + formatteropts,
891 @command('perfrevlogindex', revlogopts + formatteropts,
892 '-c|-m|FILE')
892 '-c|-m|FILE')
893 def perfrevlogindex(ui, repo, file_=None, **opts):
893 def perfrevlogindex(ui, repo, file_=None, **opts):
894 """Benchmark operations against a revlog index.
894 """Benchmark operations against a revlog index.
895
895
896 This tests constructing a revlog instance, reading index data,
896 This tests constructing a revlog instance, reading index data,
897 parsing index data, and performing various operations related to
897 parsing index data, and performing various operations related to
898 index data.
898 index data.
899 """
899 """
900
900
901 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
901 rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
902
902
903 opener = getattr(rl, 'opener') # trick linter
903 opener = getattr(rl, 'opener') # trick linter
904 indexfile = rl.indexfile
904 indexfile = rl.indexfile
905 data = opener.read(indexfile)
905 data = opener.read(indexfile)
906
906
907 header = struct.unpack('>I', data[0:4])[0]
907 header = struct.unpack('>I', data[0:4])[0]
908 version = header & 0xFFFF
908 version = header & 0xFFFF
909 if version == 1:
909 if version == 1:
910 revlogio = revlog.revlogio()
910 revlogio = revlog.revlogio()
911 inline = header & (1 << 16)
911 inline = header & (1 << 16)
912 else:
912 else:
913 raise error.Abort(('unsupported revlog version: %d') % version)
913 raise error.Abort(('unsupported revlog version: %d') % version)
914
914
915 rllen = len(rl)
915 rllen = len(rl)
916
916
917 node0 = rl.node(0)
917 node0 = rl.node(0)
918 node25 = rl.node(rllen // 4)
918 node25 = rl.node(rllen // 4)
919 node50 = rl.node(rllen // 2)
919 node50 = rl.node(rllen // 2)
920 node75 = rl.node(rllen // 4 * 3)
920 node75 = rl.node(rllen // 4 * 3)
921 node100 = rl.node(rllen - 1)
921 node100 = rl.node(rllen - 1)
922
922
923 allrevs = range(rllen)
923 allrevs = range(rllen)
924 allrevsrev = list(reversed(allrevs))
924 allrevsrev = list(reversed(allrevs))
925 allnodes = [rl.node(rev) for rev in range(rllen)]
925 allnodes = [rl.node(rev) for rev in range(rllen)]
926 allnodesrev = list(reversed(allnodes))
926 allnodesrev = list(reversed(allnodes))
927
927
928 def constructor():
928 def constructor():
929 revlog.revlog(opener, indexfile)
929 revlog.revlog(opener, indexfile)
930
930
931 def read():
931 def read():
932 with opener(indexfile) as fh:
932 with opener(indexfile) as fh:
933 fh.read()
933 fh.read()
934
934
935 def parseindex():
935 def parseindex():
936 revlogio.parseindex(data, inline)
936 revlogio.parseindex(data, inline)
937
937
938 def getentry(revornode):
938 def getentry(revornode):
939 index = revlogio.parseindex(data, inline)[0]
939 index = revlogio.parseindex(data, inline)[0]
940 index[revornode]
940 index[revornode]
941
941
942 def getentries(revs, count=1):
942 def getentries(revs, count=1):
943 index = revlogio.parseindex(data, inline)[0]
943 index = revlogio.parseindex(data, inline)[0]
944
944
945 for i in range(count):
945 for i in range(count):
946 for rev in revs:
946 for rev in revs:
947 index[rev]
947 index[rev]
948
948
949 def resolvenode(node):
949 def resolvenode(node):
950 nodemap = revlogio.parseindex(data, inline)[1]
950 nodemap = revlogio.parseindex(data, inline)[1]
951 # This only works for the C code.
951 # This only works for the C code.
952 if nodemap is None:
952 if nodemap is None:
953 return
953 return
954
954
955 try:
955 try:
956 nodemap[node]
956 nodemap[node]
957 except error.RevlogError:
957 except error.RevlogError:
958 pass
958 pass
959
959
960 def resolvenodes(nodes, count=1):
960 def resolvenodes(nodes, count=1):
961 nodemap = revlogio.parseindex(data, inline)[1]
961 nodemap = revlogio.parseindex(data, inline)[1]
962 if nodemap is None:
962 if nodemap is None:
963 return
963 return
964
964
965 for i in range(count):
965 for i in range(count):
966 for node in nodes:
966 for node in nodes:
967 try:
967 try:
968 nodemap[node]
968 nodemap[node]
969 except error.RevlogError:
969 except error.RevlogError:
970 pass
970 pass
971
971
972 benches = [
972 benches = [
973 (constructor, 'revlog constructor'),
973 (constructor, 'revlog constructor'),
974 (read, 'read'),
974 (read, 'read'),
975 (parseindex, 'create index object'),
975 (parseindex, 'create index object'),
976 (lambda: getentry(0), 'retrieve index entry for rev 0'),
976 (lambda: getentry(0), 'retrieve index entry for rev 0'),
977 (lambda: resolvenode('a' * 20), 'look up missing node'),
977 (lambda: resolvenode('a' * 20), 'look up missing node'),
978 (lambda: resolvenode(node0), 'look up node at rev 0'),
978 (lambda: resolvenode(node0), 'look up node at rev 0'),
979 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
979 (lambda: resolvenode(node25), 'look up node at 1/4 len'),
980 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
980 (lambda: resolvenode(node50), 'look up node at 1/2 len'),
981 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
981 (lambda: resolvenode(node75), 'look up node at 3/4 len'),
982 (lambda: resolvenode(node100), 'look up node at tip'),
982 (lambda: resolvenode(node100), 'look up node at tip'),
983 # 2x variation is to measure caching impact.
983 # 2x variation is to measure caching impact.
984 (lambda: resolvenodes(allnodes),
984 (lambda: resolvenodes(allnodes),
985 'look up all nodes (forward)'),
985 'look up all nodes (forward)'),
986 (lambda: resolvenodes(allnodes, 2),
986 (lambda: resolvenodes(allnodes, 2),
987 'look up all nodes 2x (forward)'),
987 'look up all nodes 2x (forward)'),
988 (lambda: resolvenodes(allnodesrev),
988 (lambda: resolvenodes(allnodesrev),
989 'look up all nodes (reverse)'),
989 'look up all nodes (reverse)'),
990 (lambda: resolvenodes(allnodesrev, 2),
990 (lambda: resolvenodes(allnodesrev, 2),
991 'look up all nodes 2x (reverse)'),
991 'look up all nodes 2x (reverse)'),
992 (lambda: getentries(allrevs),
992 (lambda: getentries(allrevs),
993 'retrieve all index entries (forward)'),
993 'retrieve all index entries (forward)'),
994 (lambda: getentries(allrevs, 2),
994 (lambda: getentries(allrevs, 2),
995 'retrieve all index entries 2x (forward)'),
995 'retrieve all index entries 2x (forward)'),
996 (lambda: getentries(allrevsrev),
996 (lambda: getentries(allrevsrev),
997 'retrieve all index entries (reverse)'),
997 'retrieve all index entries (reverse)'),
998 (lambda: getentries(allrevsrev, 2),
998 (lambda: getentries(allrevsrev, 2),
999 'retrieve all index entries 2x (reverse)'),
999 'retrieve all index entries 2x (reverse)'),
1000 ]
1000 ]
1001
1001
1002 for fn, title in benches:
1002 for fn, title in benches:
1003 timer, fm = gettimer(ui, opts)
1003 timer, fm = gettimer(ui, opts)
1004 timer(fn, title=title)
1004 timer(fn, title=title)
1005 fm.end()
1005 fm.end()
1006
1006
1007 @command('perfrevlogrevisions', revlogopts + formatteropts +
1007 @command('perfrevlogrevisions', revlogopts + formatteropts +
1008 [('d', 'dist', 100, 'distance between the revisions'),
1008 [('d', 'dist', 100, 'distance between the revisions'),
1009 ('s', 'startrev', 0, 'revision to start reading at'),
1009 ('s', 'startrev', 0, 'revision to start reading at'),
1010 ('', 'reverse', False, 'read in reverse')],
1010 ('', 'reverse', False, 'read in reverse')],
1011 '-c|-m|FILE')
1011 '-c|-m|FILE')
1012 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1012 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
1013 **opts):
1013 **opts):
1014 """Benchmark reading a series of revisions from a revlog.
1014 """Benchmark reading a series of revisions from a revlog.
1015
1015
1016 By default, we read every ``-d/--dist`` revision from 0 to tip of
1016 By default, we read every ``-d/--dist`` revision from 0 to tip of
1017 the specified revlog.
1017 the specified revlog.
1018
1018
1019 The start revision can be defined via ``-s/--startrev``.
1019 The start revision can be defined via ``-s/--startrev``.
1020 """
1020 """
1021 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1021 rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
1022 rllen = getlen(ui)(rl)
1022 rllen = getlen(ui)(rl)
1023
1023
1024 def d():
1024 def d():
1025 rl.clearcaches()
1025 rl.clearcaches()
1026
1026
1027 beginrev = startrev
1027 beginrev = startrev
1028 endrev = rllen
1028 endrev = rllen
1029 dist = opts['dist']
1029 dist = opts['dist']
1030
1030
1031 if reverse:
1031 if reverse:
1032 beginrev, endrev = endrev, beginrev
1032 beginrev, endrev = endrev, beginrev
1033 dist = -1 * dist
1033 dist = -1 * dist
1034
1034
1035 for x in xrange(beginrev, endrev, dist):
1035 for x in xrange(beginrev, endrev, dist):
1036 # Old revisions don't support passing int.
1036 # Old revisions don't support passing int.
1037 n = rl.node(x)
1037 n = rl.node(x)
1038 rl.revision(n)
1038 rl.revision(n)
1039
1039
1040 timer, fm = gettimer(ui, opts)
1040 timer, fm = gettimer(ui, opts)
1041 timer(d)
1041 timer(d)
1042 fm.end()
1042 fm.end()
1043
1043
1044 @command('perfrevlogchunks', revlogopts + formatteropts +
1044 @command('perfrevlogchunks', revlogopts + formatteropts +
1045 [('e', 'engines', '', 'compression engines to use'),
1045 [('e', 'engines', '', 'compression engines to use'),
1046 ('s', 'startrev', 0, 'revision to start at')],
1046 ('s', 'startrev', 0, 'revision to start at')],
1047 '-c|-m|FILE')
1047 '-c|-m|FILE')
1048 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1048 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
1049 """Benchmark operations on revlog chunks.
1049 """Benchmark operations on revlog chunks.
1050
1050
1051 Logically, each revlog is a collection of fulltext revisions. However,
1051 Logically, each revlog is a collection of fulltext revisions. However,
1052 stored within each revlog are "chunks" of possibly compressed data. This
1052 stored within each revlog are "chunks" of possibly compressed data. This
1053 data needs to be read and decompressed or compressed and written.
1053 data needs to be read and decompressed or compressed and written.
1054
1054
1055 This command measures the time it takes to read+decompress and recompress
1055 This command measures the time it takes to read+decompress and recompress
1056 chunks in a revlog. It effectively isolates I/O and compression performance.
1056 chunks in a revlog. It effectively isolates I/O and compression performance.
1057 For measurements of higher-level operations like resolving revisions,
1057 For measurements of higher-level operations like resolving revisions,
1058 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1058 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
1059 """
1059 """
1060 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1060 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
1061
1061
1062 # _chunkraw was renamed to _getsegmentforrevs.
1062 # _chunkraw was renamed to _getsegmentforrevs.
1063 try:
1063 try:
1064 segmentforrevs = rl._getsegmentforrevs
1064 segmentforrevs = rl._getsegmentforrevs
1065 except AttributeError:
1065 except AttributeError:
1066 segmentforrevs = rl._chunkraw
1066 segmentforrevs = rl._chunkraw
1067
1067
1068 # Verify engines argument.
1068 # Verify engines argument.
1069 if engines:
1069 if engines:
1070 engines = set(e.strip() for e in engines.split(','))
1070 engines = set(e.strip() for e in engines.split(','))
1071 for engine in engines:
1071 for engine in engines:
1072 try:
1072 try:
1073 util.compressionengines[engine]
1073 util.compressionengines[engine]
1074 except KeyError:
1074 except KeyError:
1075 raise error.Abort('unknown compression engine: %s' % engine)
1075 raise error.Abort('unknown compression engine: %s' % engine)
1076 else:
1076 else:
1077 engines = []
1077 engines = []
1078 for e in util.compengines:
1078 for e in util.compengines:
1079 engine = util.compengines[e]
1079 engine = util.compengines[e]
1080 try:
1080 try:
1081 if engine.available():
1081 if engine.available():
1082 engine.revlogcompressor().compress('dummy')
1082 engine.revlogcompressor().compress('dummy')
1083 engines.append(e)
1083 engines.append(e)
1084 except NotImplementedError:
1084 except NotImplementedError:
1085 pass
1085 pass
1086
1086
1087 revs = list(rl.revs(startrev, len(rl) - 1))
1087 revs = list(rl.revs(startrev, len(rl) - 1))
1088
1088
1089 def rlfh(rl):
1089 def rlfh(rl):
1090 if rl._inline:
1090 if rl._inline:
1091 return getsvfs(repo)(rl.indexfile)
1091 return getsvfs(repo)(rl.indexfile)
1092 else:
1092 else:
1093 return getsvfs(repo)(rl.datafile)
1093 return getsvfs(repo)(rl.datafile)
1094
1094
1095 def doread():
1095 def doread():
1096 rl.clearcaches()
1096 rl.clearcaches()
1097 for rev in revs:
1097 for rev in revs:
1098 segmentforrevs(rev, rev)
1098 segmentforrevs(rev, rev)
1099
1099
1100 def doreadcachedfh():
1100 def doreadcachedfh():
1101 rl.clearcaches()
1101 rl.clearcaches()
1102 fh = rlfh(rl)
1102 fh = rlfh(rl)
1103 for rev in revs:
1103 for rev in revs:
1104 segmentforrevs(rev, rev, df=fh)
1104 segmentforrevs(rev, rev, df=fh)
1105
1105
1106 def doreadbatch():
1106 def doreadbatch():
1107 rl.clearcaches()
1107 rl.clearcaches()
1108 segmentforrevs(revs[0], revs[-1])
1108 segmentforrevs(revs[0], revs[-1])
1109
1109
1110 def doreadbatchcachedfh():
1110 def doreadbatchcachedfh():
1111 rl.clearcaches()
1111 rl.clearcaches()
1112 fh = rlfh(rl)
1112 fh = rlfh(rl)
1113 segmentforrevs(revs[0], revs[-1], df=fh)
1113 segmentforrevs(revs[0], revs[-1], df=fh)
1114
1114
1115 def dochunk():
1115 def dochunk():
1116 rl.clearcaches()
1116 rl.clearcaches()
1117 fh = rlfh(rl)
1117 fh = rlfh(rl)
1118 for rev in revs:
1118 for rev in revs:
1119 rl._chunk(rev, df=fh)
1119 rl._chunk(rev, df=fh)
1120
1120
1121 chunks = [None]
1121 chunks = [None]
1122
1122
1123 def dochunkbatch():
1123 def dochunkbatch():
1124 rl.clearcaches()
1124 rl.clearcaches()
1125 fh = rlfh(rl)
1125 fh = rlfh(rl)
1126 # Save chunks as a side-effect.
1126 # Save chunks as a side-effect.
1127 chunks[0] = rl._chunks(revs, df=fh)
1127 chunks[0] = rl._chunks(revs, df=fh)
1128
1128
1129 def docompress(compressor):
1129 def docompress(compressor):
1130 rl.clearcaches()
1130 rl.clearcaches()
1131
1131
1132 try:
1132 try:
1133 # Swap in the requested compression engine.
1133 # Swap in the requested compression engine.
1134 oldcompressor = rl._compressor
1134 oldcompressor = rl._compressor
1135 rl._compressor = compressor
1135 rl._compressor = compressor
1136 for chunk in chunks[0]:
1136 for chunk in chunks[0]:
1137 rl.compress(chunk)
1137 rl.compress(chunk)
1138 finally:
1138 finally:
1139 rl._compressor = oldcompressor
1139 rl._compressor = oldcompressor
1140
1140
1141 benches = [
1141 benches = [
1142 (lambda: doread(), 'read'),
1142 (lambda: doread(), 'read'),
1143 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1143 (lambda: doreadcachedfh(), 'read w/ reused fd'),
1144 (lambda: doreadbatch(), 'read batch'),
1144 (lambda: doreadbatch(), 'read batch'),
1145 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1145 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
1146 (lambda: dochunk(), 'chunk'),
1146 (lambda: dochunk(), 'chunk'),
1147 (lambda: dochunkbatch(), 'chunk batch'),
1147 (lambda: dochunkbatch(), 'chunk batch'),
1148 ]
1148 ]
1149
1149
1150 for engine in sorted(engines):
1150 for engine in sorted(engines):
1151 compressor = util.compengines[engine].revlogcompressor()
1151 compressor = util.compengines[engine].revlogcompressor()
1152 benches.append((functools.partial(docompress, compressor),
1152 benches.append((functools.partial(docompress, compressor),
1153 'compress w/ %s' % engine))
1153 'compress w/ %s' % engine))
1154
1154
1155 for fn, title in benches:
1155 for fn, title in benches:
1156 timer, fm = gettimer(ui, opts)
1156 timer, fm = gettimer(ui, opts)
1157 timer(fn, title=title)
1157 timer(fn, title=title)
1158 fm.end()
1158 fm.end()
1159
1159
1160 @command('perfrevlogrevision', revlogopts + formatteropts +
1160 @command('perfrevlogrevision', revlogopts + formatteropts +
1161 [('', 'cache', False, 'use caches instead of clearing')],
1161 [('', 'cache', False, 'use caches instead of clearing')],
1162 '-c|-m|FILE REV')
1162 '-c|-m|FILE REV')
1163 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1163 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
1164 """Benchmark obtaining a revlog revision.
1164 """Benchmark obtaining a revlog revision.
1165
1165
1166 Obtaining a revlog revision consists of roughly the following steps:
1166 Obtaining a revlog revision consists of roughly the following steps:
1167
1167
1168 1. Compute the delta chain
1168 1. Compute the delta chain
1169 2. Obtain the raw chunks for that delta chain
1169 2. Obtain the raw chunks for that delta chain
1170 3. Decompress each raw chunk
1170 3. Decompress each raw chunk
1171 4. Apply binary patches to obtain fulltext
1171 4. Apply binary patches to obtain fulltext
1172 5. Verify hash of fulltext
1172 5. Verify hash of fulltext
1173
1173
1174 This command measures the time spent in each of these phases.
1174 This command measures the time spent in each of these phases.
1175 """
1175 """
1176 if opts.get('changelog') or opts.get('manifest'):
1176 if opts.get('changelog') or opts.get('manifest'):
1177 file_, rev = None, file_
1177 file_, rev = None, file_
1178 elif rev is None:
1178 elif rev is None:
1179 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1179 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
1180
1180
1181 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1181 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
1182
1182
1183 # _chunkraw was renamed to _getsegmentforrevs.
1183 # _chunkraw was renamed to _getsegmentforrevs.
1184 try:
1184 try:
1185 segmentforrevs = r._getsegmentforrevs
1185 segmentforrevs = r._getsegmentforrevs
1186 except AttributeError:
1186 except AttributeError:
1187 segmentforrevs = r._chunkraw
1187 segmentforrevs = r._chunkraw
1188
1188
1189 node = r.lookup(rev)
1189 node = r.lookup(rev)
1190 rev = r.rev(node)
1190 rev = r.rev(node)
1191
1191
1192 def getrawchunks(data, chain):
1192 def getrawchunks(data, chain):
1193 start = r.start
1193 start = r.start
1194 length = r.length
1194 length = r.length
1195 inline = r._inline
1195 inline = r._inline
1196 iosize = r._io.size
1196 iosize = r._io.size
1197 buffer = util.buffer
1197 buffer = util.buffer
1198 offset = start(chain[0])
1198 offset = start(chain[0])
1199
1199
1200 chunks = []
1200 chunks = []
1201 ladd = chunks.append
1201 ladd = chunks.append
1202
1202
1203 for rev in chain:
1203 for rev in chain:
1204 chunkstart = start(rev)
1204 chunkstart = start(rev)
1205 if inline:
1205 if inline:
1206 chunkstart += (rev + 1) * iosize
1206 chunkstart += (rev + 1) * iosize
1207 chunklength = length(rev)
1207 chunklength = length(rev)
1208 ladd(buffer(data, chunkstart - offset, chunklength))
1208 ladd(buffer(data, chunkstart - offset, chunklength))
1209
1209
1210 return chunks
1210 return chunks
1211
1211
1212 def dodeltachain(rev):
1212 def dodeltachain(rev):
1213 if not cache:
1213 if not cache:
1214 r.clearcaches()
1214 r.clearcaches()
1215 r._deltachain(rev)
1215 r._deltachain(rev)
1216
1216
1217 def doread(chain):
1217 def doread(chain):
1218 if not cache:
1218 if not cache:
1219 r.clearcaches()
1219 r.clearcaches()
1220 segmentforrevs(chain[0], chain[-1])
1220 segmentforrevs(chain[0], chain[-1])
1221
1221
1222 def dorawchunks(data, chain):
1222 def dorawchunks(data, chain):
1223 if not cache:
1223 if not cache:
1224 r.clearcaches()
1224 r.clearcaches()
1225 getrawchunks(data, chain)
1225 getrawchunks(data, chain)
1226
1226
1227 def dodecompress(chunks):
1227 def dodecompress(chunks):
1228 decomp = r.decompress
1228 decomp = r.decompress
1229 for chunk in chunks:
1229 for chunk in chunks:
1230 decomp(chunk)
1230 decomp(chunk)
1231
1231
1232 def dopatch(text, bins):
1232 def dopatch(text, bins):
1233 if not cache:
1233 if not cache:
1234 r.clearcaches()
1234 r.clearcaches()
1235 mdiff.patches(text, bins)
1235 mdiff.patches(text, bins)
1236
1236
1237 def dohash(text):
1237 def dohash(text):
1238 if not cache:
1238 if not cache:
1239 r.clearcaches()
1239 r.clearcaches()
1240 r.checkhash(text, node, rev=rev)
1240 r.checkhash(text, node, rev=rev)
1241
1241
1242 def dorevision():
1242 def dorevision():
1243 if not cache:
1243 if not cache:
1244 r.clearcaches()
1244 r.clearcaches()
1245 r.revision(node)
1245 r.revision(node)
1246
1246
1247 chain = r._deltachain(rev)[0]
1247 chain = r._deltachain(rev)[0]
1248 data = segmentforrevs(chain[0], chain[-1])[1]
1248 data = segmentforrevs(chain[0], chain[-1])[1]
1249 rawchunks = getrawchunks(data, chain)
1249 rawchunks = getrawchunks(data, chain)
1250 bins = r._chunks(chain)
1250 bins = r._chunks(chain)
1251 text = str(bins[0])
1251 text = str(bins[0])
1252 bins = bins[1:]
1252 bins = bins[1:]
1253 text = mdiff.patches(text, bins)
1253 text = mdiff.patches(text, bins)
1254
1254
1255 benches = [
1255 benches = [
1256 (lambda: dorevision(), 'full'),
1256 (lambda: dorevision(), 'full'),
1257 (lambda: dodeltachain(rev), 'deltachain'),
1257 (lambda: dodeltachain(rev), 'deltachain'),
1258 (lambda: doread(chain), 'read'),
1258 (lambda: doread(chain), 'read'),
1259 (lambda: dorawchunks(data, chain), 'rawchunks'),
1259 (lambda: dorawchunks(data, chain), 'rawchunks'),
1260 (lambda: dodecompress(rawchunks), 'decompress'),
1260 (lambda: dodecompress(rawchunks), 'decompress'),
1261 (lambda: dopatch(text, bins), 'patch'),
1261 (lambda: dopatch(text, bins), 'patch'),
1262 (lambda: dohash(text), 'hash'),
1262 (lambda: dohash(text), 'hash'),
1263 ]
1263 ]
1264
1264
1265 for fn, title in benches:
1265 for fn, title in benches:
1266 timer, fm = gettimer(ui, opts)
1266 timer, fm = gettimer(ui, opts)
1267 timer(fn, title=title)
1267 timer(fn, title=title)
1268 fm.end()
1268 fm.end()
1269
1269
1270 @command('perfrevset',
1270 @command('perfrevset',
1271 [('C', 'clear', False, 'clear volatile cache between each call.'),
1271 [('C', 'clear', False, 'clear volatile cache between each call.'),
1272 ('', 'contexts', False, 'obtain changectx for each revision')]
1272 ('', 'contexts', False, 'obtain changectx for each revision')]
1273 + formatteropts, "REVSET")
1273 + formatteropts, "REVSET")
1274 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1274 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1275 """benchmark the execution time of a revset
1275 """benchmark the execution time of a revset
1276
1276
1277 Use the --clean option if need to evaluate the impact of build volatile
1277 Use the --clean option if need to evaluate the impact of build volatile
1278 revisions set cache on the revset execution. Volatile cache hold filtered
1278 revisions set cache on the revset execution. Volatile cache hold filtered
1279 and obsolete related cache."""
1279 and obsolete related cache."""
1280 timer, fm = gettimer(ui, opts)
1280 timer, fm = gettimer(ui, opts)
1281 def d():
1281 def d():
1282 if clear:
1282 if clear:
1283 repo.invalidatevolatilesets()
1283 repo.invalidatevolatilesets()
1284 if contexts:
1284 if contexts:
1285 for ctx in repo.set(expr): pass
1285 for ctx in repo.set(expr): pass
1286 else:
1286 else:
1287 for r in repo.revs(expr): pass
1287 for r in repo.revs(expr): pass
1288 timer(d)
1288 timer(d)
1289 fm.end()
1289 fm.end()
1290
1290
1291 @command('perfvolatilesets',
1291 @command('perfvolatilesets',
1292 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1292 [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
1293 ] + formatteropts)
1293 ] + formatteropts)
1294 def perfvolatilesets(ui, repo, *names, **opts):
1294 def perfvolatilesets(ui, repo, *names, **opts):
1295 """benchmark the computation of various volatile set
1295 """benchmark the computation of various volatile set
1296
1296
1297 Volatile set computes element related to filtering and obsolescence."""
1297 Volatile set computes element related to filtering and obsolescence."""
1298 timer, fm = gettimer(ui, opts)
1298 timer, fm = gettimer(ui, opts)
1299 repo = repo.unfiltered()
1299 repo = repo.unfiltered()
1300
1300
1301 def getobs(name):
1301 def getobs(name):
1302 def d():
1302 def d():
1303 repo.invalidatevolatilesets()
1303 repo.invalidatevolatilesets()
1304 if opts['clear_obsstore']:
1304 if opts['clear_obsstore']:
1305 clearfilecache(repo, 'obsstore')
1305 clearfilecache(repo, 'obsstore')
1306 obsolete.getrevs(repo, name)
1306 obsolete.getrevs(repo, name)
1307 return d
1307 return d
1308
1308
1309 allobs = sorted(obsolete.cachefuncs)
1309 allobs = sorted(obsolete.cachefuncs)
1310 if names:
1310 if names:
1311 allobs = [n for n in allobs if n in names]
1311 allobs = [n for n in allobs if n in names]
1312
1312
1313 for name in allobs:
1313 for name in allobs:
1314 timer(getobs(name), title=name)
1314 timer(getobs(name), title=name)
1315
1315
1316 def getfiltered(name):
1316 def getfiltered(name):
1317 def d():
1317 def d():
1318 repo.invalidatevolatilesets()
1318 repo.invalidatevolatilesets()
1319 if opts['clear_obsstore']:
1319 if opts['clear_obsstore']:
1320 clearfilecache(repo, 'obsstore')
1320 clearfilecache(repo, 'obsstore')
1321 repoview.filterrevs(repo, name)
1321 repoview.filterrevs(repo, name)
1322 return d
1322 return d
1323
1323
1324 allfilter = sorted(repoview.filtertable)
1324 allfilter = sorted(repoview.filtertable)
1325 if names:
1325 if names:
1326 allfilter = [n for n in allfilter if n in names]
1326 allfilter = [n for n in allfilter if n in names]
1327
1327
1328 for name in allfilter:
1328 for name in allfilter:
1329 timer(getfiltered(name), title=name)
1329 timer(getfiltered(name), title=name)
1330 fm.end()
1330 fm.end()
1331
1331
1332 @command('perfbranchmap',
1332 @command('perfbranchmap',
1333 [('f', 'full', False,
1333 [('f', 'full', False,
1334 'Includes build time of subset'),
1334 'Includes build time of subset'),
1335 ('', 'clear-revbranch', False,
1335 ('', 'clear-revbranch', False,
1336 'purge the revbranch cache between computation'),
1336 'purge the revbranch cache between computation'),
1337 ] + formatteropts)
1337 ] + formatteropts)
1338 def perfbranchmap(ui, repo, full=False, clear_revbranch=False, **opts):
1338 def perfbranchmap(ui, repo, full=False, clear_revbranch=False, **opts):
1339 """benchmark the update of a branchmap
1339 """benchmark the update of a branchmap
1340
1340
1341 This benchmarks the full repo.branchmap() call with read and write disabled
1341 This benchmarks the full repo.branchmap() call with read and write disabled
1342 """
1342 """
1343 timer, fm = gettimer(ui, opts)
1343 timer, fm = gettimer(ui, opts)
1344 def getbranchmap(filtername):
1344 def getbranchmap(filtername):
1345 """generate a benchmark function for the filtername"""
1345 """generate a benchmark function for the filtername"""
1346 if filtername is None:
1346 if filtername is None:
1347 view = repo
1347 view = repo
1348 else:
1348 else:
1349 view = repo.filtered(filtername)
1349 view = repo.filtered(filtername)
1350 def d():
1350 def d():
1351 if clear_revbranch:
1351 if clear_revbranch:
1352 repo.revbranchcache()._clear()
1352 repo.revbranchcache()._clear()
1353 if full:
1353 if full:
1354 view._branchcaches.clear()
1354 view._branchcaches.clear()
1355 else:
1355 else:
1356 view._branchcaches.pop(filtername, None)
1356 view._branchcaches.pop(filtername, None)
1357 view.branchmap()
1357 view.branchmap()
1358 return d
1358 return d
1359 # add filter in smaller subset to bigger subset
1359 # add filter in smaller subset to bigger subset
1360 possiblefilters = set(repoview.filtertable)
1360 possiblefilters = set(repoview.filtertable)
1361 subsettable = getbranchmapsubsettable()
1361 subsettable = getbranchmapsubsettable()
1362 allfilters = []
1362 allfilters = []
1363 while possiblefilters:
1363 while possiblefilters:
1364 for name in possiblefilters:
1364 for name in possiblefilters:
1365 subset = subsettable.get(name)
1365 subset = subsettable.get(name)
1366 if subset not in possiblefilters:
1366 if subset not in possiblefilters:
1367 break
1367 break
1368 else:
1368 else:
1369 assert False, 'subset cycle %s!' % possiblefilters
1369 assert False, 'subset cycle %s!' % possiblefilters
1370 allfilters.append(name)
1370 allfilters.append(name)
1371 possiblefilters.remove(name)
1371 possiblefilters.remove(name)
1372
1372
1373 # warm the cache
1373 # warm the cache
1374 if not full:
1374 if not full:
1375 for name in allfilters:
1375 for name in allfilters:
1376 repo.filtered(name).branchmap()
1376 repo.filtered(name).branchmap()
1377 # add unfiltered
1377 # add unfiltered
1378 allfilters.append(None)
1378 allfilters.append(None)
1379
1379
1380 branchcacheread = safeattrsetter(branchmap, 'read')
1380 branchcacheread = safeattrsetter(branchmap, 'read')
1381 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1381 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1382 branchcacheread.set(lambda repo: None)
1382 branchcacheread.set(lambda repo: None)
1383 branchcachewrite.set(lambda bc, repo: None)
1383 branchcachewrite.set(lambda bc, repo: None)
1384 try:
1384 try:
1385 for name in allfilters:
1385 for name in allfilters:
1386 timer(getbranchmap(name), title=str(name))
1386 timer(getbranchmap(name), title=str(name))
1387 finally:
1387 finally:
1388 branchcacheread.restore()
1388 branchcacheread.restore()
1389 branchcachewrite.restore()
1389 branchcachewrite.restore()
1390 fm.end()
1390 fm.end()
1391
1391
1392 @command('perfloadmarkers')
1392 @command('perfloadmarkers')
1393 def perfloadmarkers(ui, repo):
1393 def perfloadmarkers(ui, repo):
1394 """benchmark the time to parse the on-disk markers for a repo
1394 """benchmark the time to parse the on-disk markers for a repo
1395
1395
1396 Result is the number of markers in the repo."""
1396 Result is the number of markers in the repo."""
1397 timer, fm = gettimer(ui)
1397 timer, fm = gettimer(ui)
1398 svfs = getsvfs(repo)
1398 svfs = getsvfs(repo)
1399 timer(lambda: len(obsolete.obsstore(svfs)))
1399 timer(lambda: len(obsolete.obsstore(svfs)))
1400 fm.end()
1400 fm.end()
1401
1401
1402 @command('perflrucachedict', formatteropts +
1402 @command('perflrucachedict', formatteropts +
1403 [('', 'size', 4, 'size of cache'),
1403 [('', 'size', 4, 'size of cache'),
1404 ('', 'gets', 10000, 'number of key lookups'),
1404 ('', 'gets', 10000, 'number of key lookups'),
1405 ('', 'sets', 10000, 'number of key sets'),
1405 ('', 'sets', 10000, 'number of key sets'),
1406 ('', 'mixed', 10000, 'number of mixed mode operations'),
1406 ('', 'mixed', 10000, 'number of mixed mode operations'),
1407 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1407 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1408 norepo=True)
1408 norepo=True)
1409 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1409 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1410 mixedgetfreq=50, **opts):
1410 mixedgetfreq=50, **opts):
1411 def doinit():
1411 def doinit():
1412 for i in xrange(10000):
1412 for i in xrange(10000):
1413 util.lrucachedict(size)
1413 util.lrucachedict(size)
1414
1414
1415 values = []
1415 values = []
1416 for i in xrange(size):
1416 for i in xrange(size):
1417 values.append(random.randint(0, sys.maxint))
1417 values.append(random.randint(0, sys.maxint))
1418
1418
1419 # Get mode fills the cache and tests raw lookup performance with no
1419 # Get mode fills the cache and tests raw lookup performance with no
1420 # eviction.
1420 # eviction.
1421 getseq = []
1421 getseq = []
1422 for i in xrange(gets):
1422 for i in xrange(gets):
1423 getseq.append(random.choice(values))
1423 getseq.append(random.choice(values))
1424
1424
1425 def dogets():
1425 def dogets():
1426 d = util.lrucachedict(size)
1426 d = util.lrucachedict(size)
1427 for v in values:
1427 for v in values:
1428 d[v] = v
1428 d[v] = v
1429 for key in getseq:
1429 for key in getseq:
1430 value = d[key]
1430 value = d[key]
1431 value # silence pyflakes warning
1431 value # silence pyflakes warning
1432
1432
1433 # Set mode tests insertion speed with cache eviction.
1433 # Set mode tests insertion speed with cache eviction.
1434 setseq = []
1434 setseq = []
1435 for i in xrange(sets):
1435 for i in xrange(sets):
1436 setseq.append(random.randint(0, sys.maxint))
1436 setseq.append(random.randint(0, sys.maxint))
1437
1437
1438 def dosets():
1438 def dosets():
1439 d = util.lrucachedict(size)
1439 d = util.lrucachedict(size)
1440 for v in setseq:
1440 for v in setseq:
1441 d[v] = v
1441 d[v] = v
1442
1442
1443 # Mixed mode randomly performs gets and sets with eviction.
1443 # Mixed mode randomly performs gets and sets with eviction.
1444 mixedops = []
1444 mixedops = []
1445 for i in xrange(mixed):
1445 for i in xrange(mixed):
1446 r = random.randint(0, 100)
1446 r = random.randint(0, 100)
1447 if r < mixedgetfreq:
1447 if r < mixedgetfreq:
1448 op = 0
1448 op = 0
1449 else:
1449 else:
1450 op = 1
1450 op = 1
1451
1451
1452 mixedops.append((op, random.randint(0, size * 2)))
1452 mixedops.append((op, random.randint(0, size * 2)))
1453
1453
1454 def domixed():
1454 def domixed():
1455 d = util.lrucachedict(size)
1455 d = util.lrucachedict(size)
1456
1456
1457 for op, v in mixedops:
1457 for op, v in mixedops:
1458 if op == 0:
1458 if op == 0:
1459 try:
1459 try:
1460 d[v]
1460 d[v]
1461 except KeyError:
1461 except KeyError:
1462 pass
1462 pass
1463 else:
1463 else:
1464 d[v] = v
1464 d[v] = v
1465
1465
1466 benches = [
1466 benches = [
1467 (doinit, 'init'),
1467 (doinit, 'init'),
1468 (dogets, 'gets'),
1468 (dogets, 'gets'),
1469 (dosets, 'sets'),
1469 (dosets, 'sets'),
1470 (domixed, 'mixed')
1470 (domixed, 'mixed')
1471 ]
1471 ]
1472
1472
1473 for fn, title in benches:
1473 for fn, title in benches:
1474 timer, fm = gettimer(ui, opts)
1474 timer, fm = gettimer(ui, opts)
1475 timer(fn, title=title)
1475 timer(fn, title=title)
1476 fm.end()
1476 fm.end()
1477
1477
1478 @command('perfwrite', formatteropts)
1478 @command('perfwrite', formatteropts)
1479 def perfwrite(ui, repo, **opts):
1479 def perfwrite(ui, repo, **opts):
1480 """microbenchmark ui.write
1480 """microbenchmark ui.write
1481 """
1481 """
1482 timer, fm = gettimer(ui, opts)
1482 timer, fm = gettimer(ui, opts)
1483 def write():
1483 def write():
1484 for i in range(100000):
1484 for i in range(100000):
1485 ui.write(('Testing write performance\n'))
1485 ui.write(('Testing write performance\n'))
1486 timer(write)
1486 timer(write)
1487 fm.end()
1487 fm.end()
1488
1488
1489 def uisetup(ui):
1489 def uisetup(ui):
1490 if (util.safehasattr(cmdutil, 'openrevlog') and
1490 if (util.safehasattr(cmdutil, 'openrevlog') and
1491 not util.safehasattr(commands, 'debugrevlogopts')):
1491 not util.safehasattr(commands, 'debugrevlogopts')):
1492 # for "historical portability":
1492 # for "historical portability":
1493 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1493 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1494 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1494 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1495 # openrevlog() should cause failure, because it has been
1495 # openrevlog() should cause failure, because it has been
1496 # available since 3.5 (or 49c583ca48c4).
1496 # available since 3.5 (or 49c583ca48c4).
1497 def openrevlog(orig, repo, cmd, file_, opts):
1497 def openrevlog(orig, repo, cmd, file_, opts):
1498 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1498 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1499 raise error.Abort("This version doesn't support --dir option",
1499 raise error.Abort("This version doesn't support --dir option",
1500 hint="use 3.5 or later")
1500 hint="use 3.5 or later")
1501 return orig(repo, cmd, file_, opts)
1501 return orig(repo, cmd, file_, opts)
1502 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
1502 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
@@ -1,1398 +1,1396 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking 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 collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import os
13 import os
14 import stat
14 import stat
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 match as matchmod,
21 match as matchmod,
22 pathutil,
22 pathutil,
23 policy,
23 policy,
24 pycompat,
24 pycompat,
25 scmutil,
25 scmutil,
26 txnutil,
26 txnutil,
27 util,
27 util,
28 )
28 )
29
29
30 parsers = policy.importmod(r'parsers')
30 parsers = policy.importmod(r'parsers')
31
31
32 propertycache = util.propertycache
32 propertycache = util.propertycache
33 filecache = scmutil.filecache
33 filecache = scmutil.filecache
34 _rangemask = 0x7fffffff
34 _rangemask = 0x7fffffff
35
35
36 dirstatetuple = parsers.dirstatetuple
36 dirstatetuple = parsers.dirstatetuple
37
37
38 class repocache(filecache):
38 class repocache(filecache):
39 """filecache for files in .hg/"""
39 """filecache for files in .hg/"""
40 def join(self, obj, fname):
40 def join(self, obj, fname):
41 return obj._opener.join(fname)
41 return obj._opener.join(fname)
42
42
43 class rootcache(filecache):
43 class rootcache(filecache):
44 """filecache for files in the repository root"""
44 """filecache for files in the repository root"""
45 def join(self, obj, fname):
45 def join(self, obj, fname):
46 return obj._join(fname)
46 return obj._join(fname)
47
47
48 def _getfsnow(vfs):
48 def _getfsnow(vfs):
49 '''Get "now" timestamp on filesystem'''
49 '''Get "now" timestamp on filesystem'''
50 tmpfd, tmpname = vfs.mkstemp()
50 tmpfd, tmpname = vfs.mkstemp()
51 try:
51 try:
52 return os.fstat(tmpfd).st_mtime
52 return os.fstat(tmpfd).st_mtime
53 finally:
53 finally:
54 os.close(tmpfd)
54 os.close(tmpfd)
55 vfs.unlink(tmpname)
55 vfs.unlink(tmpname)
56
56
57 class dirstate(object):
57 class dirstate(object):
58
58
59 def __init__(self, opener, ui, root, validate, sparsematchfn):
59 def __init__(self, opener, ui, root, validate, sparsematchfn):
60 '''Create a new dirstate object.
60 '''Create a new dirstate object.
61
61
62 opener is an open()-like callable that can be used to open the
62 opener is an open()-like callable that can be used to open the
63 dirstate file; root is the root of the directory tracked by
63 dirstate file; root is the root of the directory tracked by
64 the dirstate.
64 the dirstate.
65 '''
65 '''
66 self._opener = opener
66 self._opener = opener
67 self._validate = validate
67 self._validate = validate
68 self._root = root
68 self._root = root
69 self._sparsematchfn = sparsematchfn
69 self._sparsematchfn = sparsematchfn
70 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
70 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
71 # UNC path pointing to root share (issue4557)
71 # UNC path pointing to root share (issue4557)
72 self._rootdir = pathutil.normasprefix(root)
72 self._rootdir = pathutil.normasprefix(root)
73 self._dirty = False
73 self._dirty = False
74 self._lastnormaltime = 0
74 self._lastnormaltime = 0
75 self._ui = ui
75 self._ui = ui
76 self._filecache = {}
76 self._filecache = {}
77 self._parentwriters = 0
77 self._parentwriters = 0
78 self._filename = 'dirstate'
78 self._filename = 'dirstate'
79 self._pendingfilename = '%s.pending' % self._filename
79 self._pendingfilename = '%s.pending' % self._filename
80 self._plchangecallbacks = {}
80 self._plchangecallbacks = {}
81 self._origpl = None
81 self._origpl = None
82 self._updatedfiles = set()
82 self._updatedfiles = set()
83
83
84 @contextlib.contextmanager
84 @contextlib.contextmanager
85 def parentchange(self):
85 def parentchange(self):
86 '''Context manager for handling dirstate parents.
86 '''Context manager for handling dirstate parents.
87
87
88 If an exception occurs in the scope of the context manager,
88 If an exception occurs in the scope of the context manager,
89 the incoherent dirstate won't be written when wlock is
89 the incoherent dirstate won't be written when wlock is
90 released.
90 released.
91 '''
91 '''
92 self._parentwriters += 1
92 self._parentwriters += 1
93 yield
93 yield
94 # Typically we want the "undo" step of a context manager in a
94 # Typically we want the "undo" step of a context manager in a
95 # finally block so it happens even when an exception
95 # finally block so it happens even when an exception
96 # occurs. In this case, however, we only want to decrement
96 # occurs. In this case, however, we only want to decrement
97 # parentwriters if the code in the with statement exits
97 # parentwriters if the code in the with statement exits
98 # normally, so we don't have a try/finally here on purpose.
98 # normally, so we don't have a try/finally here on purpose.
99 self._parentwriters -= 1
99 self._parentwriters -= 1
100
100
101 def beginparentchange(self):
101 def beginparentchange(self):
102 '''Marks the beginning of a set of changes that involve changing
102 '''Marks the beginning of a set of changes that involve changing
103 the dirstate parents. If there is an exception during this time,
103 the dirstate parents. If there is an exception during this time,
104 the dirstate will not be written when the wlock is released. This
104 the dirstate will not be written when the wlock is released. This
105 prevents writing an incoherent dirstate where the parent doesn't
105 prevents writing an incoherent dirstate where the parent doesn't
106 match the contents.
106 match the contents.
107 '''
107 '''
108 self._ui.deprecwarn('beginparentchange is obsoleted by the '
108 self._ui.deprecwarn('beginparentchange is obsoleted by the '
109 'parentchange context manager.', '4.3')
109 'parentchange context manager.', '4.3')
110 self._parentwriters += 1
110 self._parentwriters += 1
111
111
112 def endparentchange(self):
112 def endparentchange(self):
113 '''Marks the end of a set of changes that involve changing the
113 '''Marks the end of a set of changes that involve changing the
114 dirstate parents. Once all parent changes have been marked done,
114 dirstate parents. Once all parent changes have been marked done,
115 the wlock will be free to write the dirstate on release.
115 the wlock will be free to write the dirstate on release.
116 '''
116 '''
117 self._ui.deprecwarn('endparentchange is obsoleted by the '
117 self._ui.deprecwarn('endparentchange is obsoleted by the '
118 'parentchange context manager.', '4.3')
118 'parentchange context manager.', '4.3')
119 if self._parentwriters > 0:
119 if self._parentwriters > 0:
120 self._parentwriters -= 1
120 self._parentwriters -= 1
121
121
122 def pendingparentchange(self):
122 def pendingparentchange(self):
123 '''Returns true if the dirstate is in the middle of a set of changes
123 '''Returns true if the dirstate is in the middle of a set of changes
124 that modify the dirstate parent.
124 that modify the dirstate parent.
125 '''
125 '''
126 return self._parentwriters > 0
126 return self._parentwriters > 0
127
127
128 @propertycache
128 @propertycache
129 def _map(self):
129 def _map(self):
130 '''Return the dirstate contents as a map from filename to
130 '''Return the dirstate contents as a map from filename to
131 (state, mode, size, time).'''
131 (state, mode, size, time).'''
132 self._read()
132 self._read()
133 return self._map
133 return self._map
134
134
135 @propertycache
136 def _dirfoldmap(self):
137 f = {}
138 normcase = util.normcase
139 for name in self._map.dirs:
140 f[normcase(name)] = name
141 return f
142
143 @property
135 @property
144 def _sparsematcher(self):
136 def _sparsematcher(self):
145 """The matcher for the sparse checkout.
137 """The matcher for the sparse checkout.
146
138
147 The working directory may not include every file from a manifest. The
139 The working directory may not include every file from a manifest. The
148 matcher obtained by this property will match a path if it is to be
140 matcher obtained by this property will match a path if it is to be
149 included in the working directory.
141 included in the working directory.
150 """
142 """
151 # TODO there is potential to cache this property. For now, the matcher
143 # TODO there is potential to cache this property. For now, the matcher
152 # is resolved on every access. (But the called function does use a
144 # is resolved on every access. (But the called function does use a
153 # cache to keep the lookup fast.)
145 # cache to keep the lookup fast.)
154 return self._sparsematchfn()
146 return self._sparsematchfn()
155
147
156 @repocache('branch')
148 @repocache('branch')
157 def _branch(self):
149 def _branch(self):
158 try:
150 try:
159 return self._opener.read("branch").strip() or "default"
151 return self._opener.read("branch").strip() or "default"
160 except IOError as inst:
152 except IOError as inst:
161 if inst.errno != errno.ENOENT:
153 if inst.errno != errno.ENOENT:
162 raise
154 raise
163 return "default"
155 return "default"
164
156
165 @property
157 @property
166 def _pl(self):
158 def _pl(self):
167 return self._map.parents()
159 return self._map.parents()
168
160
169 def dirs(self):
161 def dirs(self):
170 return self._map.dirs
162 return self._map.dirs
171
163
172 @rootcache('.hgignore')
164 @rootcache('.hgignore')
173 def _ignore(self):
165 def _ignore(self):
174 files = self._ignorefiles()
166 files = self._ignorefiles()
175 if not files:
167 if not files:
176 return matchmod.never(self._root, '')
168 return matchmod.never(self._root, '')
177
169
178 pats = ['include:%s' % f for f in files]
170 pats = ['include:%s' % f for f in files]
179 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
171 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
180
172
181 @propertycache
173 @propertycache
182 def _slash(self):
174 def _slash(self):
183 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
175 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
184
176
185 @propertycache
177 @propertycache
186 def _checklink(self):
178 def _checklink(self):
187 return util.checklink(self._root)
179 return util.checklink(self._root)
188
180
189 @propertycache
181 @propertycache
190 def _checkexec(self):
182 def _checkexec(self):
191 return util.checkexec(self._root)
183 return util.checkexec(self._root)
192
184
193 @propertycache
185 @propertycache
194 def _checkcase(self):
186 def _checkcase(self):
195 return not util.fscasesensitive(self._join('.hg'))
187 return not util.fscasesensitive(self._join('.hg'))
196
188
197 def _join(self, f):
189 def _join(self, f):
198 # much faster than os.path.join()
190 # much faster than os.path.join()
199 # it's safe because f is always a relative path
191 # it's safe because f is always a relative path
200 return self._rootdir + f
192 return self._rootdir + f
201
193
202 def flagfunc(self, buildfallback):
194 def flagfunc(self, buildfallback):
203 if self._checklink and self._checkexec:
195 if self._checklink and self._checkexec:
204 def f(x):
196 def f(x):
205 try:
197 try:
206 st = os.lstat(self._join(x))
198 st = os.lstat(self._join(x))
207 if util.statislink(st):
199 if util.statislink(st):
208 return 'l'
200 return 'l'
209 if util.statisexec(st):
201 if util.statisexec(st):
210 return 'x'
202 return 'x'
211 except OSError:
203 except OSError:
212 pass
204 pass
213 return ''
205 return ''
214 return f
206 return f
215
207
216 fallback = buildfallback()
208 fallback = buildfallback()
217 if self._checklink:
209 if self._checklink:
218 def f(x):
210 def f(x):
219 if os.path.islink(self._join(x)):
211 if os.path.islink(self._join(x)):
220 return 'l'
212 return 'l'
221 if 'x' in fallback(x):
213 if 'x' in fallback(x):
222 return 'x'
214 return 'x'
223 return ''
215 return ''
224 return f
216 return f
225 if self._checkexec:
217 if self._checkexec:
226 def f(x):
218 def f(x):
227 if 'l' in fallback(x):
219 if 'l' in fallback(x):
228 return 'l'
220 return 'l'
229 if util.isexec(self._join(x)):
221 if util.isexec(self._join(x)):
230 return 'x'
222 return 'x'
231 return ''
223 return ''
232 return f
224 return f
233 else:
225 else:
234 return fallback
226 return fallback
235
227
236 @propertycache
228 @propertycache
237 def _cwd(self):
229 def _cwd(self):
238 # internal config: ui.forcecwd
230 # internal config: ui.forcecwd
239 forcecwd = self._ui.config('ui', 'forcecwd')
231 forcecwd = self._ui.config('ui', 'forcecwd')
240 if forcecwd:
232 if forcecwd:
241 return forcecwd
233 return forcecwd
242 return pycompat.getcwd()
234 return pycompat.getcwd()
243
235
244 def getcwd(self):
236 def getcwd(self):
245 '''Return the path from which a canonical path is calculated.
237 '''Return the path from which a canonical path is calculated.
246
238
247 This path should be used to resolve file patterns or to convert
239 This path should be used to resolve file patterns or to convert
248 canonical paths back to file paths for display. It shouldn't be
240 canonical paths back to file paths for display. It shouldn't be
249 used to get real file paths. Use vfs functions instead.
241 used to get real file paths. Use vfs functions instead.
250 '''
242 '''
251 cwd = self._cwd
243 cwd = self._cwd
252 if cwd == self._root:
244 if cwd == self._root:
253 return ''
245 return ''
254 # self._root ends with a path separator if self._root is '/' or 'C:\'
246 # self._root ends with a path separator if self._root is '/' or 'C:\'
255 rootsep = self._root
247 rootsep = self._root
256 if not util.endswithsep(rootsep):
248 if not util.endswithsep(rootsep):
257 rootsep += pycompat.ossep
249 rootsep += pycompat.ossep
258 if cwd.startswith(rootsep):
250 if cwd.startswith(rootsep):
259 return cwd[len(rootsep):]
251 return cwd[len(rootsep):]
260 else:
252 else:
261 # we're outside the repo. return an absolute path.
253 # we're outside the repo. return an absolute path.
262 return cwd
254 return cwd
263
255
264 def pathto(self, f, cwd=None):
256 def pathto(self, f, cwd=None):
265 if cwd is None:
257 if cwd is None:
266 cwd = self.getcwd()
258 cwd = self.getcwd()
267 path = util.pathto(self._root, cwd, f)
259 path = util.pathto(self._root, cwd, f)
268 if self._slash:
260 if self._slash:
269 return util.pconvert(path)
261 return util.pconvert(path)
270 return path
262 return path
271
263
272 def __getitem__(self, key):
264 def __getitem__(self, key):
273 '''Return the current state of key (a filename) in the dirstate.
265 '''Return the current state of key (a filename) in the dirstate.
274
266
275 States are:
267 States are:
276 n normal
268 n normal
277 m needs merging
269 m needs merging
278 r marked for removal
270 r marked for removal
279 a marked for addition
271 a marked for addition
280 ? not tracked
272 ? not tracked
281 '''
273 '''
282 return self._map.get(key, ("?",))[0]
274 return self._map.get(key, ("?",))[0]
283
275
284 def __contains__(self, key):
276 def __contains__(self, key):
285 return key in self._map
277 return key in self._map
286
278
287 def __iter__(self):
279 def __iter__(self):
288 return iter(sorted(self._map))
280 return iter(sorted(self._map))
289
281
290 def items(self):
282 def items(self):
291 return self._map.iteritems()
283 return self._map.iteritems()
292
284
293 iteritems = items
285 iteritems = items
294
286
295 def parents(self):
287 def parents(self):
296 return [self._validate(p) for p in self._pl]
288 return [self._validate(p) for p in self._pl]
297
289
298 def p1(self):
290 def p1(self):
299 return self._validate(self._pl[0])
291 return self._validate(self._pl[0])
300
292
301 def p2(self):
293 def p2(self):
302 return self._validate(self._pl[1])
294 return self._validate(self._pl[1])
303
295
304 def branch(self):
296 def branch(self):
305 return encoding.tolocal(self._branch)
297 return encoding.tolocal(self._branch)
306
298
307 def setparents(self, p1, p2=nullid):
299 def setparents(self, p1, p2=nullid):
308 """Set dirstate parents to p1 and p2.
300 """Set dirstate parents to p1 and p2.
309
301
310 When moving from two parents to one, 'm' merged entries a
302 When moving from two parents to one, 'm' merged entries a
311 adjusted to normal and previous copy records discarded and
303 adjusted to normal and previous copy records discarded and
312 returned by the call.
304 returned by the call.
313
305
314 See localrepo.setparents()
306 See localrepo.setparents()
315 """
307 """
316 if self._parentwriters == 0:
308 if self._parentwriters == 0:
317 raise ValueError("cannot set dirstate parent without "
309 raise ValueError("cannot set dirstate parent without "
318 "calling dirstate.beginparentchange")
310 "calling dirstate.beginparentchange")
319
311
320 self._dirty = True
312 self._dirty = True
321 oldp2 = self._pl[1]
313 oldp2 = self._pl[1]
322 if self._origpl is None:
314 if self._origpl is None:
323 self._origpl = self._pl
315 self._origpl = self._pl
324 self._map.setparents(p1, p2)
316 self._map.setparents(p1, p2)
325 copies = {}
317 copies = {}
326 if oldp2 != nullid and p2 == nullid:
318 if oldp2 != nullid and p2 == nullid:
327 candidatefiles = self._map.nonnormalset.union(
319 candidatefiles = self._map.nonnormalset.union(
328 self._map.otherparentset)
320 self._map.otherparentset)
329 for f in candidatefiles:
321 for f in candidatefiles:
330 s = self._map.get(f)
322 s = self._map.get(f)
331 if s is None:
323 if s is None:
332 continue
324 continue
333
325
334 # Discard 'm' markers when moving away from a merge state
326 # Discard 'm' markers when moving away from a merge state
335 if s[0] == 'm':
327 if s[0] == 'm':
336 source = self._map.copymap.get(f)
328 source = self._map.copymap.get(f)
337 if source:
329 if source:
338 copies[f] = source
330 copies[f] = source
339 self.normallookup(f)
331 self.normallookup(f)
340 # Also fix up otherparent markers
332 # Also fix up otherparent markers
341 elif s[0] == 'n' and s[2] == -2:
333 elif s[0] == 'n' and s[2] == -2:
342 source = self._map.copymap.get(f)
334 source = self._map.copymap.get(f)
343 if source:
335 if source:
344 copies[f] = source
336 copies[f] = source
345 self.add(f)
337 self.add(f)
346 return copies
338 return copies
347
339
348 def setbranch(self, branch):
340 def setbranch(self, branch):
349 self._branch = encoding.fromlocal(branch)
341 self._branch = encoding.fromlocal(branch)
350 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
342 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
351 try:
343 try:
352 f.write(self._branch + '\n')
344 f.write(self._branch + '\n')
353 f.close()
345 f.close()
354
346
355 # make sure filecache has the correct stat info for _branch after
347 # make sure filecache has the correct stat info for _branch after
356 # replacing the underlying file
348 # replacing the underlying file
357 ce = self._filecache['_branch']
349 ce = self._filecache['_branch']
358 if ce:
350 if ce:
359 ce.refresh()
351 ce.refresh()
360 except: # re-raises
352 except: # re-raises
361 f.discard()
353 f.discard()
362 raise
354 raise
363
355
364 def _read(self):
356 def _read(self):
365 self._map = dirstatemap(self._ui, self._opener, self._root)
357 self._map = dirstatemap(self._ui, self._opener, self._root)
366 self._map.read()
358 self._map.read()
367
359
368 def invalidate(self):
360 def invalidate(self):
369 '''Causes the next access to reread the dirstate.
361 '''Causes the next access to reread the dirstate.
370
362
371 This is different from localrepo.invalidatedirstate() because it always
363 This is different from localrepo.invalidatedirstate() because it always
372 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
364 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
373 check whether the dirstate has changed before rereading it.'''
365 check whether the dirstate has changed before rereading it.'''
374
366
375 for a in ("_map", "_dirfoldmap", "_branch",
367 for a in ("_map", "_branch", "_ignore"):
376 "_ignore"):
377 if a in self.__dict__:
368 if a in self.__dict__:
378 delattr(self, a)
369 delattr(self, a)
379 self._lastnormaltime = 0
370 self._lastnormaltime = 0
380 self._dirty = False
371 self._dirty = False
381 self._updatedfiles.clear()
372 self._updatedfiles.clear()
382 self._parentwriters = 0
373 self._parentwriters = 0
383 self._origpl = None
374 self._origpl = None
384
375
385 def copy(self, source, dest):
376 def copy(self, source, dest):
386 """Mark dest as a copy of source. Unmark dest if source is None."""
377 """Mark dest as a copy of source. Unmark dest if source is None."""
387 if source == dest:
378 if source == dest:
388 return
379 return
389 self._dirty = True
380 self._dirty = True
390 if source is not None:
381 if source is not None:
391 self._map.copymap[dest] = source
382 self._map.copymap[dest] = source
392 self._updatedfiles.add(source)
383 self._updatedfiles.add(source)
393 self._updatedfiles.add(dest)
384 self._updatedfiles.add(dest)
394 elif self._map.copymap.pop(dest, None):
385 elif self._map.copymap.pop(dest, None):
395 self._updatedfiles.add(dest)
386 self._updatedfiles.add(dest)
396
387
397 def copied(self, file):
388 def copied(self, file):
398 return self._map.copymap.get(file, None)
389 return self._map.copymap.get(file, None)
399
390
400 def copies(self):
391 def copies(self):
401 return self._map.copymap
392 return self._map.copymap
402
393
403 def _droppath(self, f):
394 def _droppath(self, f):
404 if self[f] not in "?r" and "dirs" in self._map.__dict__:
395 if self[f] not in "?r" and "dirs" in self._map.__dict__:
405 self._map.dirs.delpath(f)
396 self._map.dirs.delpath(f)
406
397
407 if "filefoldmap" in self._map.__dict__:
398 if "filefoldmap" in self._map.__dict__:
408 normed = util.normcase(f)
399 normed = util.normcase(f)
409 if normed in self._map.filefoldmap:
400 if normed in self._map.filefoldmap:
410 del self._map.filefoldmap[normed]
401 del self._map.filefoldmap[normed]
411
402
412 self._updatedfiles.add(f)
403 self._updatedfiles.add(f)
413
404
414 def _addpath(self, f, state, mode, size, mtime):
405 def _addpath(self, f, state, mode, size, mtime):
415 oldstate = self[f]
406 oldstate = self[f]
416 if state == 'a' or oldstate == 'r':
407 if state == 'a' or oldstate == 'r':
417 scmutil.checkfilename(f)
408 scmutil.checkfilename(f)
418 if f in self._map.dirs:
409 if f in self._map.dirs:
419 raise error.Abort(_('directory %r already in dirstate') % f)
410 raise error.Abort(_('directory %r already in dirstate') % f)
420 # shadows
411 # shadows
421 for d in util.finddirs(f):
412 for d in util.finddirs(f):
422 if d in self._map.dirs:
413 if d in self._map.dirs:
423 break
414 break
424 entry = self._map.get(d)
415 entry = self._map.get(d)
425 if entry is not None and entry[0] != 'r':
416 if entry is not None and entry[0] != 'r':
426 raise error.Abort(
417 raise error.Abort(
427 _('file %r in dirstate clashes with %r') % (d, f))
418 _('file %r in dirstate clashes with %r') % (d, f))
428 if oldstate in "?r" and "dirs" in self._map.__dict__:
419 if oldstate in "?r" and "dirs" in self._map.__dict__:
429 self._map.dirs.addpath(f)
420 self._map.dirs.addpath(f)
430 self._dirty = True
421 self._dirty = True
431 self._updatedfiles.add(f)
422 self._updatedfiles.add(f)
432 self._map[f] = dirstatetuple(state, mode, size, mtime)
423 self._map[f] = dirstatetuple(state, mode, size, mtime)
433 if state != 'n' or mtime == -1:
424 if state != 'n' or mtime == -1:
434 self._map.nonnormalset.add(f)
425 self._map.nonnormalset.add(f)
435 if size == -2:
426 if size == -2:
436 self._map.otherparentset.add(f)
427 self._map.otherparentset.add(f)
437
428
438 def normal(self, f):
429 def normal(self, f):
439 '''Mark a file normal and clean.'''
430 '''Mark a file normal and clean.'''
440 s = os.lstat(self._join(f))
431 s = os.lstat(self._join(f))
441 mtime = s.st_mtime
432 mtime = s.st_mtime
442 self._addpath(f, 'n', s.st_mode,
433 self._addpath(f, 'n', s.st_mode,
443 s.st_size & _rangemask, mtime & _rangemask)
434 s.st_size & _rangemask, mtime & _rangemask)
444 self._map.copymap.pop(f, None)
435 self._map.copymap.pop(f, None)
445 if f in self._map.nonnormalset:
436 if f in self._map.nonnormalset:
446 self._map.nonnormalset.remove(f)
437 self._map.nonnormalset.remove(f)
447 if mtime > self._lastnormaltime:
438 if mtime > self._lastnormaltime:
448 # Remember the most recent modification timeslot for status(),
439 # Remember the most recent modification timeslot for status(),
449 # to make sure we won't miss future size-preserving file content
440 # to make sure we won't miss future size-preserving file content
450 # modifications that happen within the same timeslot.
441 # modifications that happen within the same timeslot.
451 self._lastnormaltime = mtime
442 self._lastnormaltime = mtime
452
443
453 def normallookup(self, f):
444 def normallookup(self, f):
454 '''Mark a file normal, but possibly dirty.'''
445 '''Mark a file normal, but possibly dirty.'''
455 if self._pl[1] != nullid:
446 if self._pl[1] != nullid:
456 # if there is a merge going on and the file was either
447 # if there is a merge going on and the file was either
457 # in state 'm' (-1) or coming from other parent (-2) before
448 # in state 'm' (-1) or coming from other parent (-2) before
458 # being removed, restore that state.
449 # being removed, restore that state.
459 entry = self._map.get(f)
450 entry = self._map.get(f)
460 if entry is not None:
451 if entry is not None:
461 if entry[0] == 'r' and entry[2] in (-1, -2):
452 if entry[0] == 'r' and entry[2] in (-1, -2):
462 source = self._map.copymap.get(f)
453 source = self._map.copymap.get(f)
463 if entry[2] == -1:
454 if entry[2] == -1:
464 self.merge(f)
455 self.merge(f)
465 elif entry[2] == -2:
456 elif entry[2] == -2:
466 self.otherparent(f)
457 self.otherparent(f)
467 if source:
458 if source:
468 self.copy(source, f)
459 self.copy(source, f)
469 return
460 return
470 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
461 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
471 return
462 return
472 self._addpath(f, 'n', 0, -1, -1)
463 self._addpath(f, 'n', 0, -1, -1)
473 self._map.copymap.pop(f, None)
464 self._map.copymap.pop(f, None)
474 if f in self._map.nonnormalset:
465 if f in self._map.nonnormalset:
475 self._map.nonnormalset.remove(f)
466 self._map.nonnormalset.remove(f)
476
467
477 def otherparent(self, f):
468 def otherparent(self, f):
478 '''Mark as coming from the other parent, always dirty.'''
469 '''Mark as coming from the other parent, always dirty.'''
479 if self._pl[1] == nullid:
470 if self._pl[1] == nullid:
480 raise error.Abort(_("setting %r to other parent "
471 raise error.Abort(_("setting %r to other parent "
481 "only allowed in merges") % f)
472 "only allowed in merges") % f)
482 if f in self and self[f] == 'n':
473 if f in self and self[f] == 'n':
483 # merge-like
474 # merge-like
484 self._addpath(f, 'm', 0, -2, -1)
475 self._addpath(f, 'm', 0, -2, -1)
485 else:
476 else:
486 # add-like
477 # add-like
487 self._addpath(f, 'n', 0, -2, -1)
478 self._addpath(f, 'n', 0, -2, -1)
488 self._map.copymap.pop(f, None)
479 self._map.copymap.pop(f, None)
489
480
490 def add(self, f):
481 def add(self, f):
491 '''Mark a file added.'''
482 '''Mark a file added.'''
492 self._addpath(f, 'a', 0, -1, -1)
483 self._addpath(f, 'a', 0, -1, -1)
493 self._map.copymap.pop(f, None)
484 self._map.copymap.pop(f, None)
494
485
495 def remove(self, f):
486 def remove(self, f):
496 '''Mark a file removed.'''
487 '''Mark a file removed.'''
497 self._dirty = True
488 self._dirty = True
498 self._droppath(f)
489 self._droppath(f)
499 size = 0
490 size = 0
500 if self._pl[1] != nullid:
491 if self._pl[1] != nullid:
501 entry = self._map.get(f)
492 entry = self._map.get(f)
502 if entry is not None:
493 if entry is not None:
503 # backup the previous state
494 # backup the previous state
504 if entry[0] == 'm': # merge
495 if entry[0] == 'm': # merge
505 size = -1
496 size = -1
506 elif entry[0] == 'n' and entry[2] == -2: # other parent
497 elif entry[0] == 'n' and entry[2] == -2: # other parent
507 size = -2
498 size = -2
508 self._map.otherparentset.add(f)
499 self._map.otherparentset.add(f)
509 self._map[f] = dirstatetuple('r', 0, size, 0)
500 self._map[f] = dirstatetuple('r', 0, size, 0)
510 self._map.nonnormalset.add(f)
501 self._map.nonnormalset.add(f)
511 if size == 0:
502 if size == 0:
512 self._map.copymap.pop(f, None)
503 self._map.copymap.pop(f, None)
513
504
514 def merge(self, f):
505 def merge(self, f):
515 '''Mark a file merged.'''
506 '''Mark a file merged.'''
516 if self._pl[1] == nullid:
507 if self._pl[1] == nullid:
517 return self.normallookup(f)
508 return self.normallookup(f)
518 return self.otherparent(f)
509 return self.otherparent(f)
519
510
520 def drop(self, f):
511 def drop(self, f):
521 '''Drop a file from the dirstate'''
512 '''Drop a file from the dirstate'''
522 if f in self._map:
513 if f in self._map:
523 self._dirty = True
514 self._dirty = True
524 self._droppath(f)
515 self._droppath(f)
525 del self._map[f]
516 del self._map[f]
526 if f in self._map.nonnormalset:
517 if f in self._map.nonnormalset:
527 self._map.nonnormalset.remove(f)
518 self._map.nonnormalset.remove(f)
528 self._map.copymap.pop(f, None)
519 self._map.copymap.pop(f, None)
529
520
530 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
521 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
531 if exists is None:
522 if exists is None:
532 exists = os.path.lexists(os.path.join(self._root, path))
523 exists = os.path.lexists(os.path.join(self._root, path))
533 if not exists:
524 if not exists:
534 # Maybe a path component exists
525 # Maybe a path component exists
535 if not ignoremissing and '/' in path:
526 if not ignoremissing and '/' in path:
536 d, f = path.rsplit('/', 1)
527 d, f = path.rsplit('/', 1)
537 d = self._normalize(d, False, ignoremissing, None)
528 d = self._normalize(d, False, ignoremissing, None)
538 folded = d + "/" + f
529 folded = d + "/" + f
539 else:
530 else:
540 # No path components, preserve original case
531 # No path components, preserve original case
541 folded = path
532 folded = path
542 else:
533 else:
543 # recursively normalize leading directory components
534 # recursively normalize leading directory components
544 # against dirstate
535 # against dirstate
545 if '/' in normed:
536 if '/' in normed:
546 d, f = normed.rsplit('/', 1)
537 d, f = normed.rsplit('/', 1)
547 d = self._normalize(d, False, ignoremissing, True)
538 d = self._normalize(d, False, ignoremissing, True)
548 r = self._root + "/" + d
539 r = self._root + "/" + d
549 folded = d + "/" + util.fspath(f, r)
540 folded = d + "/" + util.fspath(f, r)
550 else:
541 else:
551 folded = util.fspath(normed, self._root)
542 folded = util.fspath(normed, self._root)
552 storemap[normed] = folded
543 storemap[normed] = folded
553
544
554 return folded
545 return folded
555
546
556 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
547 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
557 normed = util.normcase(path)
548 normed = util.normcase(path)
558 folded = self._map.filefoldmap.get(normed, None)
549 folded = self._map.filefoldmap.get(normed, None)
559 if folded is None:
550 if folded is None:
560 if isknown:
551 if isknown:
561 folded = path
552 folded = path
562 else:
553 else:
563 folded = self._discoverpath(path, normed, ignoremissing, exists,
554 folded = self._discoverpath(path, normed, ignoremissing, exists,
564 self._map.filefoldmap)
555 self._map.filefoldmap)
565 return folded
556 return folded
566
557
567 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
558 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
568 normed = util.normcase(path)
559 normed = util.normcase(path)
569 folded = self._map.filefoldmap.get(normed, None)
560 folded = self._map.filefoldmap.get(normed, None)
570 if folded is None:
561 if folded is None:
571 folded = self._dirfoldmap.get(normed, None)
562 folded = self._map.dirfoldmap.get(normed, None)
572 if folded is None:
563 if folded is None:
573 if isknown:
564 if isknown:
574 folded = path
565 folded = path
575 else:
566 else:
576 # store discovered result in dirfoldmap so that future
567 # store discovered result in dirfoldmap so that future
577 # normalizefile calls don't start matching directories
568 # normalizefile calls don't start matching directories
578 folded = self._discoverpath(path, normed, ignoremissing, exists,
569 folded = self._discoverpath(path, normed, ignoremissing, exists,
579 self._dirfoldmap)
570 self._map.dirfoldmap)
580 return folded
571 return folded
581
572
582 def normalize(self, path, isknown=False, ignoremissing=False):
573 def normalize(self, path, isknown=False, ignoremissing=False):
583 '''
574 '''
584 normalize the case of a pathname when on a casefolding filesystem
575 normalize the case of a pathname when on a casefolding filesystem
585
576
586 isknown specifies whether the filename came from walking the
577 isknown specifies whether the filename came from walking the
587 disk, to avoid extra filesystem access.
578 disk, to avoid extra filesystem access.
588
579
589 If ignoremissing is True, missing path are returned
580 If ignoremissing is True, missing path are returned
590 unchanged. Otherwise, we try harder to normalize possibly
581 unchanged. Otherwise, we try harder to normalize possibly
591 existing path components.
582 existing path components.
592
583
593 The normalized case is determined based on the following precedence:
584 The normalized case is determined based on the following precedence:
594
585
595 - version of name already stored in the dirstate
586 - version of name already stored in the dirstate
596 - version of name stored on disk
587 - version of name stored on disk
597 - version provided via command arguments
588 - version provided via command arguments
598 '''
589 '''
599
590
600 if self._checkcase:
591 if self._checkcase:
601 return self._normalize(path, isknown, ignoremissing)
592 return self._normalize(path, isknown, ignoremissing)
602 return path
593 return path
603
594
604 def clear(self):
595 def clear(self):
605 self._map = dirstatemap(self._ui, self._opener, self._root)
596 self._map = dirstatemap(self._ui, self._opener, self._root)
606 self._map.setparents(nullid, nullid)
597 self._map.setparents(nullid, nullid)
607 self._lastnormaltime = 0
598 self._lastnormaltime = 0
608 self._updatedfiles.clear()
599 self._updatedfiles.clear()
609 self._dirty = True
600 self._dirty = True
610
601
611 def rebuild(self, parent, allfiles, changedfiles=None):
602 def rebuild(self, parent, allfiles, changedfiles=None):
612 if changedfiles is None:
603 if changedfiles is None:
613 # Rebuild entire dirstate
604 # Rebuild entire dirstate
614 changedfiles = allfiles
605 changedfiles = allfiles
615 lastnormaltime = self._lastnormaltime
606 lastnormaltime = self._lastnormaltime
616 self.clear()
607 self.clear()
617 self._lastnormaltime = lastnormaltime
608 self._lastnormaltime = lastnormaltime
618
609
619 if self._origpl is None:
610 if self._origpl is None:
620 self._origpl = self._pl
611 self._origpl = self._pl
621 self._map.setparents(parent, nullid)
612 self._map.setparents(parent, nullid)
622 for f in changedfiles:
613 for f in changedfiles:
623 if f in allfiles:
614 if f in allfiles:
624 self.normallookup(f)
615 self.normallookup(f)
625 else:
616 else:
626 self.drop(f)
617 self.drop(f)
627
618
628 self._dirty = True
619 self._dirty = True
629
620
630 def identity(self):
621 def identity(self):
631 '''Return identity of dirstate itself to detect changing in storage
622 '''Return identity of dirstate itself to detect changing in storage
632
623
633 If identity of previous dirstate is equal to this, writing
624 If identity of previous dirstate is equal to this, writing
634 changes based on the former dirstate out can keep consistency.
625 changes based on the former dirstate out can keep consistency.
635 '''
626 '''
636 return self._map.identity
627 return self._map.identity
637
628
638 def write(self, tr):
629 def write(self, tr):
639 if not self._dirty:
630 if not self._dirty:
640 return
631 return
641
632
642 filename = self._filename
633 filename = self._filename
643 if tr:
634 if tr:
644 # 'dirstate.write()' is not only for writing in-memory
635 # 'dirstate.write()' is not only for writing in-memory
645 # changes out, but also for dropping ambiguous timestamp.
636 # changes out, but also for dropping ambiguous timestamp.
646 # delayed writing re-raise "ambiguous timestamp issue".
637 # delayed writing re-raise "ambiguous timestamp issue".
647 # See also the wiki page below for detail:
638 # See also the wiki page below for detail:
648 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
639 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
649
640
650 # emulate dropping timestamp in 'parsers.pack_dirstate'
641 # emulate dropping timestamp in 'parsers.pack_dirstate'
651 now = _getfsnow(self._opener)
642 now = _getfsnow(self._opener)
652 dmap = self._map
643 dmap = self._map
653 for f in self._updatedfiles:
644 for f in self._updatedfiles:
654 e = dmap.get(f)
645 e = dmap.get(f)
655 if e is not None and e[0] == 'n' and e[3] == now:
646 if e is not None and e[0] == 'n' and e[3] == now:
656 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
647 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
657 self._map.nonnormalset.add(f)
648 self._map.nonnormalset.add(f)
658
649
659 # emulate that all 'dirstate.normal' results are written out
650 # emulate that all 'dirstate.normal' results are written out
660 self._lastnormaltime = 0
651 self._lastnormaltime = 0
661 self._updatedfiles.clear()
652 self._updatedfiles.clear()
662
653
663 # delay writing in-memory changes out
654 # delay writing in-memory changes out
664 tr.addfilegenerator('dirstate', (self._filename,),
655 tr.addfilegenerator('dirstate', (self._filename,),
665 self._writedirstate, location='plain')
656 self._writedirstate, location='plain')
666 return
657 return
667
658
668 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
659 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
669 self._writedirstate(st)
660 self._writedirstate(st)
670
661
671 def addparentchangecallback(self, category, callback):
662 def addparentchangecallback(self, category, callback):
672 """add a callback to be called when the wd parents are changed
663 """add a callback to be called when the wd parents are changed
673
664
674 Callback will be called with the following arguments:
665 Callback will be called with the following arguments:
675 dirstate, (oldp1, oldp2), (newp1, newp2)
666 dirstate, (oldp1, oldp2), (newp1, newp2)
676
667
677 Category is a unique identifier to allow overwriting an old callback
668 Category is a unique identifier to allow overwriting an old callback
678 with a newer callback.
669 with a newer callback.
679 """
670 """
680 self._plchangecallbacks[category] = callback
671 self._plchangecallbacks[category] = callback
681
672
682 def _writedirstate(self, st):
673 def _writedirstate(self, st):
683 # notify callbacks about parents change
674 # notify callbacks about parents change
684 if self._origpl is not None and self._origpl != self._pl:
675 if self._origpl is not None and self._origpl != self._pl:
685 for c, callback in sorted(self._plchangecallbacks.iteritems()):
676 for c, callback in sorted(self._plchangecallbacks.iteritems()):
686 callback(self, self._origpl, self._pl)
677 callback(self, self._origpl, self._pl)
687 self._origpl = None
678 self._origpl = None
688 # use the modification time of the newly created temporary file as the
679 # use the modification time of the newly created temporary file as the
689 # filesystem's notion of 'now'
680 # filesystem's notion of 'now'
690 now = util.fstat(st).st_mtime & _rangemask
681 now = util.fstat(st).st_mtime & _rangemask
691
682
692 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
683 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
693 # timestamp of each entries in dirstate, because of 'now > mtime'
684 # timestamp of each entries in dirstate, because of 'now > mtime'
694 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
685 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
695 if delaywrite > 0:
686 if delaywrite > 0:
696 # do we have any files to delay for?
687 # do we have any files to delay for?
697 for f, e in self._map.iteritems():
688 for f, e in self._map.iteritems():
698 if e[0] == 'n' and e[3] == now:
689 if e[0] == 'n' and e[3] == now:
699 import time # to avoid useless import
690 import time # to avoid useless import
700 # rather than sleep n seconds, sleep until the next
691 # rather than sleep n seconds, sleep until the next
701 # multiple of n seconds
692 # multiple of n seconds
702 clock = time.time()
693 clock = time.time()
703 start = int(clock) - (int(clock) % delaywrite)
694 start = int(clock) - (int(clock) % delaywrite)
704 end = start + delaywrite
695 end = start + delaywrite
705 time.sleep(end - clock)
696 time.sleep(end - clock)
706 now = end # trust our estimate that the end is near now
697 now = end # trust our estimate that the end is near now
707 break
698 break
708
699
709 self._map.write(st, now)
700 self._map.write(st, now)
710 self._lastnormaltime = 0
701 self._lastnormaltime = 0
711 self._dirty = False
702 self._dirty = False
712
703
713 def _dirignore(self, f):
704 def _dirignore(self, f):
714 if f == '.':
705 if f == '.':
715 return False
706 return False
716 if self._ignore(f):
707 if self._ignore(f):
717 return True
708 return True
718 for p in util.finddirs(f):
709 for p in util.finddirs(f):
719 if self._ignore(p):
710 if self._ignore(p):
720 return True
711 return True
721 return False
712 return False
722
713
723 def _ignorefiles(self):
714 def _ignorefiles(self):
724 files = []
715 files = []
725 if os.path.exists(self._join('.hgignore')):
716 if os.path.exists(self._join('.hgignore')):
726 files.append(self._join('.hgignore'))
717 files.append(self._join('.hgignore'))
727 for name, path in self._ui.configitems("ui"):
718 for name, path in self._ui.configitems("ui"):
728 if name == 'ignore' or name.startswith('ignore.'):
719 if name == 'ignore' or name.startswith('ignore.'):
729 # we need to use os.path.join here rather than self._join
720 # we need to use os.path.join here rather than self._join
730 # because path is arbitrary and user-specified
721 # because path is arbitrary and user-specified
731 files.append(os.path.join(self._rootdir, util.expandpath(path)))
722 files.append(os.path.join(self._rootdir, util.expandpath(path)))
732 return files
723 return files
733
724
734 def _ignorefileandline(self, f):
725 def _ignorefileandline(self, f):
735 files = collections.deque(self._ignorefiles())
726 files = collections.deque(self._ignorefiles())
736 visited = set()
727 visited = set()
737 while files:
728 while files:
738 i = files.popleft()
729 i = files.popleft()
739 patterns = matchmod.readpatternfile(i, self._ui.warn,
730 patterns = matchmod.readpatternfile(i, self._ui.warn,
740 sourceinfo=True)
731 sourceinfo=True)
741 for pattern, lineno, line in patterns:
732 for pattern, lineno, line in patterns:
742 kind, p = matchmod._patsplit(pattern, 'glob')
733 kind, p = matchmod._patsplit(pattern, 'glob')
743 if kind == "subinclude":
734 if kind == "subinclude":
744 if p not in visited:
735 if p not in visited:
745 files.append(p)
736 files.append(p)
746 continue
737 continue
747 m = matchmod.match(self._root, '', [], [pattern],
738 m = matchmod.match(self._root, '', [], [pattern],
748 warn=self._ui.warn)
739 warn=self._ui.warn)
749 if m(f):
740 if m(f):
750 return (i, lineno, line)
741 return (i, lineno, line)
751 visited.add(i)
742 visited.add(i)
752 return (None, -1, "")
743 return (None, -1, "")
753
744
754 def _walkexplicit(self, match, subrepos):
745 def _walkexplicit(self, match, subrepos):
755 '''Get stat data about the files explicitly specified by match.
746 '''Get stat data about the files explicitly specified by match.
756
747
757 Return a triple (results, dirsfound, dirsnotfound).
748 Return a triple (results, dirsfound, dirsnotfound).
758 - results is a mapping from filename to stat result. It also contains
749 - results is a mapping from filename to stat result. It also contains
759 listings mapping subrepos and .hg to None.
750 listings mapping subrepos and .hg to None.
760 - dirsfound is a list of files found to be directories.
751 - dirsfound is a list of files found to be directories.
761 - dirsnotfound is a list of files that the dirstate thinks are
752 - dirsnotfound is a list of files that the dirstate thinks are
762 directories and that were not found.'''
753 directories and that were not found.'''
763
754
764 def badtype(mode):
755 def badtype(mode):
765 kind = _('unknown')
756 kind = _('unknown')
766 if stat.S_ISCHR(mode):
757 if stat.S_ISCHR(mode):
767 kind = _('character device')
758 kind = _('character device')
768 elif stat.S_ISBLK(mode):
759 elif stat.S_ISBLK(mode):
769 kind = _('block device')
760 kind = _('block device')
770 elif stat.S_ISFIFO(mode):
761 elif stat.S_ISFIFO(mode):
771 kind = _('fifo')
762 kind = _('fifo')
772 elif stat.S_ISSOCK(mode):
763 elif stat.S_ISSOCK(mode):
773 kind = _('socket')
764 kind = _('socket')
774 elif stat.S_ISDIR(mode):
765 elif stat.S_ISDIR(mode):
775 kind = _('directory')
766 kind = _('directory')
776 return _('unsupported file type (type is %s)') % kind
767 return _('unsupported file type (type is %s)') % kind
777
768
778 matchedir = match.explicitdir
769 matchedir = match.explicitdir
779 badfn = match.bad
770 badfn = match.bad
780 dmap = self._map
771 dmap = self._map
781 lstat = os.lstat
772 lstat = os.lstat
782 getkind = stat.S_IFMT
773 getkind = stat.S_IFMT
783 dirkind = stat.S_IFDIR
774 dirkind = stat.S_IFDIR
784 regkind = stat.S_IFREG
775 regkind = stat.S_IFREG
785 lnkkind = stat.S_IFLNK
776 lnkkind = stat.S_IFLNK
786 join = self._join
777 join = self._join
787 dirsfound = []
778 dirsfound = []
788 foundadd = dirsfound.append
779 foundadd = dirsfound.append
789 dirsnotfound = []
780 dirsnotfound = []
790 notfoundadd = dirsnotfound.append
781 notfoundadd = dirsnotfound.append
791
782
792 if not match.isexact() and self._checkcase:
783 if not match.isexact() and self._checkcase:
793 normalize = self._normalize
784 normalize = self._normalize
794 else:
785 else:
795 normalize = None
786 normalize = None
796
787
797 files = sorted(match.files())
788 files = sorted(match.files())
798 subrepos.sort()
789 subrepos.sort()
799 i, j = 0, 0
790 i, j = 0, 0
800 while i < len(files) and j < len(subrepos):
791 while i < len(files) and j < len(subrepos):
801 subpath = subrepos[j] + "/"
792 subpath = subrepos[j] + "/"
802 if files[i] < subpath:
793 if files[i] < subpath:
803 i += 1
794 i += 1
804 continue
795 continue
805 while i < len(files) and files[i].startswith(subpath):
796 while i < len(files) and files[i].startswith(subpath):
806 del files[i]
797 del files[i]
807 j += 1
798 j += 1
808
799
809 if not files or '.' in files:
800 if not files or '.' in files:
810 files = ['.']
801 files = ['.']
811 results = dict.fromkeys(subrepos)
802 results = dict.fromkeys(subrepos)
812 results['.hg'] = None
803 results['.hg'] = None
813
804
814 alldirs = None
805 alldirs = None
815 for ff in files:
806 for ff in files:
816 # constructing the foldmap is expensive, so don't do it for the
807 # constructing the foldmap is expensive, so don't do it for the
817 # common case where files is ['.']
808 # common case where files is ['.']
818 if normalize and ff != '.':
809 if normalize and ff != '.':
819 nf = normalize(ff, False, True)
810 nf = normalize(ff, False, True)
820 else:
811 else:
821 nf = ff
812 nf = ff
822 if nf in results:
813 if nf in results:
823 continue
814 continue
824
815
825 try:
816 try:
826 st = lstat(join(nf))
817 st = lstat(join(nf))
827 kind = getkind(st.st_mode)
818 kind = getkind(st.st_mode)
828 if kind == dirkind:
819 if kind == dirkind:
829 if nf in dmap:
820 if nf in dmap:
830 # file replaced by dir on disk but still in dirstate
821 # file replaced by dir on disk but still in dirstate
831 results[nf] = None
822 results[nf] = None
832 if matchedir:
823 if matchedir:
833 matchedir(nf)
824 matchedir(nf)
834 foundadd((nf, ff))
825 foundadd((nf, ff))
835 elif kind == regkind or kind == lnkkind:
826 elif kind == regkind or kind == lnkkind:
836 results[nf] = st
827 results[nf] = st
837 else:
828 else:
838 badfn(ff, badtype(kind))
829 badfn(ff, badtype(kind))
839 if nf in dmap:
830 if nf in dmap:
840 results[nf] = None
831 results[nf] = None
841 except OSError as inst: # nf not found on disk - it is dirstate only
832 except OSError as inst: # nf not found on disk - it is dirstate only
842 if nf in dmap: # does it exactly match a missing file?
833 if nf in dmap: # does it exactly match a missing file?
843 results[nf] = None
834 results[nf] = None
844 else: # does it match a missing directory?
835 else: # does it match a missing directory?
845 if alldirs is None:
836 if alldirs is None:
846 alldirs = util.dirs(dmap._map)
837 alldirs = util.dirs(dmap._map)
847 if nf in alldirs:
838 if nf in alldirs:
848 if matchedir:
839 if matchedir:
849 matchedir(nf)
840 matchedir(nf)
850 notfoundadd(nf)
841 notfoundadd(nf)
851 else:
842 else:
852 badfn(ff, encoding.strtolocal(inst.strerror))
843 badfn(ff, encoding.strtolocal(inst.strerror))
853
844
854 # Case insensitive filesystems cannot rely on lstat() failing to detect
845 # Case insensitive filesystems cannot rely on lstat() failing to detect
855 # a case-only rename. Prune the stat object for any file that does not
846 # a case-only rename. Prune the stat object for any file that does not
856 # match the case in the filesystem, if there are multiple files that
847 # match the case in the filesystem, if there are multiple files that
857 # normalize to the same path.
848 # normalize to the same path.
858 if match.isexact() and self._checkcase:
849 if match.isexact() and self._checkcase:
859 normed = {}
850 normed = {}
860
851
861 for f, st in results.iteritems():
852 for f, st in results.iteritems():
862 if st is None:
853 if st is None:
863 continue
854 continue
864
855
865 nc = util.normcase(f)
856 nc = util.normcase(f)
866 paths = normed.get(nc)
857 paths = normed.get(nc)
867
858
868 if paths is None:
859 if paths is None:
869 paths = set()
860 paths = set()
870 normed[nc] = paths
861 normed[nc] = paths
871
862
872 paths.add(f)
863 paths.add(f)
873
864
874 for norm, paths in normed.iteritems():
865 for norm, paths in normed.iteritems():
875 if len(paths) > 1:
866 if len(paths) > 1:
876 for path in paths:
867 for path in paths:
877 folded = self._discoverpath(path, norm, True, None,
868 folded = self._discoverpath(path, norm, True, None,
878 self._dirfoldmap)
869 self._map.dirfoldmap)
879 if path != folded:
870 if path != folded:
880 results[path] = None
871 results[path] = None
881
872
882 return results, dirsfound, dirsnotfound
873 return results, dirsfound, dirsnotfound
883
874
884 def walk(self, match, subrepos, unknown, ignored, full=True):
875 def walk(self, match, subrepos, unknown, ignored, full=True):
885 '''
876 '''
886 Walk recursively through the directory tree, finding all files
877 Walk recursively through the directory tree, finding all files
887 matched by match.
878 matched by match.
888
879
889 If full is False, maybe skip some known-clean files.
880 If full is False, maybe skip some known-clean files.
890
881
891 Return a dict mapping filename to stat-like object (either
882 Return a dict mapping filename to stat-like object (either
892 mercurial.osutil.stat instance or return value of os.stat()).
883 mercurial.osutil.stat instance or return value of os.stat()).
893
884
894 '''
885 '''
895 # full is a flag that extensions that hook into walk can use -- this
886 # full is a flag that extensions that hook into walk can use -- this
896 # implementation doesn't use it at all. This satisfies the contract
887 # implementation doesn't use it at all. This satisfies the contract
897 # because we only guarantee a "maybe".
888 # because we only guarantee a "maybe".
898
889
899 if ignored:
890 if ignored:
900 ignore = util.never
891 ignore = util.never
901 dirignore = util.never
892 dirignore = util.never
902 elif unknown:
893 elif unknown:
903 ignore = self._ignore
894 ignore = self._ignore
904 dirignore = self._dirignore
895 dirignore = self._dirignore
905 else:
896 else:
906 # if not unknown and not ignored, drop dir recursion and step 2
897 # if not unknown and not ignored, drop dir recursion and step 2
907 ignore = util.always
898 ignore = util.always
908 dirignore = util.always
899 dirignore = util.always
909
900
910 matchfn = match.matchfn
901 matchfn = match.matchfn
911 matchalways = match.always()
902 matchalways = match.always()
912 matchtdir = match.traversedir
903 matchtdir = match.traversedir
913 dmap = self._map
904 dmap = self._map
914 listdir = util.listdir
905 listdir = util.listdir
915 lstat = os.lstat
906 lstat = os.lstat
916 dirkind = stat.S_IFDIR
907 dirkind = stat.S_IFDIR
917 regkind = stat.S_IFREG
908 regkind = stat.S_IFREG
918 lnkkind = stat.S_IFLNK
909 lnkkind = stat.S_IFLNK
919 join = self._join
910 join = self._join
920
911
921 exact = skipstep3 = False
912 exact = skipstep3 = False
922 if match.isexact(): # match.exact
913 if match.isexact(): # match.exact
923 exact = True
914 exact = True
924 dirignore = util.always # skip step 2
915 dirignore = util.always # skip step 2
925 elif match.prefix(): # match.match, no patterns
916 elif match.prefix(): # match.match, no patterns
926 skipstep3 = True
917 skipstep3 = True
927
918
928 if not exact and self._checkcase:
919 if not exact and self._checkcase:
929 normalize = self._normalize
920 normalize = self._normalize
930 normalizefile = self._normalizefile
921 normalizefile = self._normalizefile
931 skipstep3 = False
922 skipstep3 = False
932 else:
923 else:
933 normalize = self._normalize
924 normalize = self._normalize
934 normalizefile = None
925 normalizefile = None
935
926
936 # step 1: find all explicit files
927 # step 1: find all explicit files
937 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
928 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
938
929
939 skipstep3 = skipstep3 and not (work or dirsnotfound)
930 skipstep3 = skipstep3 and not (work or dirsnotfound)
940 work = [d for d in work if not dirignore(d[0])]
931 work = [d for d in work if not dirignore(d[0])]
941
932
942 # step 2: visit subdirectories
933 # step 2: visit subdirectories
943 def traverse(work, alreadynormed):
934 def traverse(work, alreadynormed):
944 wadd = work.append
935 wadd = work.append
945 while work:
936 while work:
946 nd = work.pop()
937 nd = work.pop()
947 if not match.visitdir(nd):
938 if not match.visitdir(nd):
948 continue
939 continue
949 skip = None
940 skip = None
950 if nd == '.':
941 if nd == '.':
951 nd = ''
942 nd = ''
952 else:
943 else:
953 skip = '.hg'
944 skip = '.hg'
954 try:
945 try:
955 entries = listdir(join(nd), stat=True, skip=skip)
946 entries = listdir(join(nd), stat=True, skip=skip)
956 except OSError as inst:
947 except OSError as inst:
957 if inst.errno in (errno.EACCES, errno.ENOENT):
948 if inst.errno in (errno.EACCES, errno.ENOENT):
958 match.bad(self.pathto(nd),
949 match.bad(self.pathto(nd),
959 encoding.strtolocal(inst.strerror))
950 encoding.strtolocal(inst.strerror))
960 continue
951 continue
961 raise
952 raise
962 for f, kind, st in entries:
953 for f, kind, st in entries:
963 if normalizefile:
954 if normalizefile:
964 # even though f might be a directory, we're only
955 # even though f might be a directory, we're only
965 # interested in comparing it to files currently in the
956 # interested in comparing it to files currently in the
966 # dmap -- therefore normalizefile is enough
957 # dmap -- therefore normalizefile is enough
967 nf = normalizefile(nd and (nd + "/" + f) or f, True,
958 nf = normalizefile(nd and (nd + "/" + f) or f, True,
968 True)
959 True)
969 else:
960 else:
970 nf = nd and (nd + "/" + f) or f
961 nf = nd and (nd + "/" + f) or f
971 if nf not in results:
962 if nf not in results:
972 if kind == dirkind:
963 if kind == dirkind:
973 if not ignore(nf):
964 if not ignore(nf):
974 if matchtdir:
965 if matchtdir:
975 matchtdir(nf)
966 matchtdir(nf)
976 wadd(nf)
967 wadd(nf)
977 if nf in dmap and (matchalways or matchfn(nf)):
968 if nf in dmap and (matchalways or matchfn(nf)):
978 results[nf] = None
969 results[nf] = None
979 elif kind == regkind or kind == lnkkind:
970 elif kind == regkind or kind == lnkkind:
980 if nf in dmap:
971 if nf in dmap:
981 if matchalways or matchfn(nf):
972 if matchalways or matchfn(nf):
982 results[nf] = st
973 results[nf] = st
983 elif ((matchalways or matchfn(nf))
974 elif ((matchalways or matchfn(nf))
984 and not ignore(nf)):
975 and not ignore(nf)):
985 # unknown file -- normalize if necessary
976 # unknown file -- normalize if necessary
986 if not alreadynormed:
977 if not alreadynormed:
987 nf = normalize(nf, False, True)
978 nf = normalize(nf, False, True)
988 results[nf] = st
979 results[nf] = st
989 elif nf in dmap and (matchalways or matchfn(nf)):
980 elif nf in dmap and (matchalways or matchfn(nf)):
990 results[nf] = None
981 results[nf] = None
991
982
992 for nd, d in work:
983 for nd, d in work:
993 # alreadynormed means that processwork doesn't have to do any
984 # alreadynormed means that processwork doesn't have to do any
994 # expensive directory normalization
985 # expensive directory normalization
995 alreadynormed = not normalize or nd == d
986 alreadynormed = not normalize or nd == d
996 traverse([d], alreadynormed)
987 traverse([d], alreadynormed)
997
988
998 for s in subrepos:
989 for s in subrepos:
999 del results[s]
990 del results[s]
1000 del results['.hg']
991 del results['.hg']
1001
992
1002 # step 3: visit remaining files from dmap
993 # step 3: visit remaining files from dmap
1003 if not skipstep3 and not exact:
994 if not skipstep3 and not exact:
1004 # If a dmap file is not in results yet, it was either
995 # If a dmap file is not in results yet, it was either
1005 # a) not matching matchfn b) ignored, c) missing, or d) under a
996 # a) not matching matchfn b) ignored, c) missing, or d) under a
1006 # symlink directory.
997 # symlink directory.
1007 if not results and matchalways:
998 if not results and matchalways:
1008 visit = [f for f in dmap]
999 visit = [f for f in dmap]
1009 else:
1000 else:
1010 visit = [f for f in dmap if f not in results and matchfn(f)]
1001 visit = [f for f in dmap if f not in results and matchfn(f)]
1011 visit.sort()
1002 visit.sort()
1012
1003
1013 if unknown:
1004 if unknown:
1014 # unknown == True means we walked all dirs under the roots
1005 # unknown == True means we walked all dirs under the roots
1015 # that wasn't ignored, and everything that matched was stat'ed
1006 # that wasn't ignored, and everything that matched was stat'ed
1016 # and is already in results.
1007 # and is already in results.
1017 # The rest must thus be ignored or under a symlink.
1008 # The rest must thus be ignored or under a symlink.
1018 audit_path = pathutil.pathauditor(self._root, cached=True)
1009 audit_path = pathutil.pathauditor(self._root, cached=True)
1019
1010
1020 for nf in iter(visit):
1011 for nf in iter(visit):
1021 # If a stat for the same file was already added with a
1012 # If a stat for the same file was already added with a
1022 # different case, don't add one for this, since that would
1013 # different case, don't add one for this, since that would
1023 # make it appear as if the file exists under both names
1014 # make it appear as if the file exists under both names
1024 # on disk.
1015 # on disk.
1025 if (normalizefile and
1016 if (normalizefile and
1026 normalizefile(nf, True, True) in results):
1017 normalizefile(nf, True, True) in results):
1027 results[nf] = None
1018 results[nf] = None
1028 # Report ignored items in the dmap as long as they are not
1019 # Report ignored items in the dmap as long as they are not
1029 # under a symlink directory.
1020 # under a symlink directory.
1030 elif audit_path.check(nf):
1021 elif audit_path.check(nf):
1031 try:
1022 try:
1032 results[nf] = lstat(join(nf))
1023 results[nf] = lstat(join(nf))
1033 # file was just ignored, no links, and exists
1024 # file was just ignored, no links, and exists
1034 except OSError:
1025 except OSError:
1035 # file doesn't exist
1026 # file doesn't exist
1036 results[nf] = None
1027 results[nf] = None
1037 else:
1028 else:
1038 # It's either missing or under a symlink directory
1029 # It's either missing or under a symlink directory
1039 # which we in this case report as missing
1030 # which we in this case report as missing
1040 results[nf] = None
1031 results[nf] = None
1041 else:
1032 else:
1042 # We may not have walked the full directory tree above,
1033 # We may not have walked the full directory tree above,
1043 # so stat and check everything we missed.
1034 # so stat and check everything we missed.
1044 iv = iter(visit)
1035 iv = iter(visit)
1045 for st in util.statfiles([join(i) for i in visit]):
1036 for st in util.statfiles([join(i) for i in visit]):
1046 results[next(iv)] = st
1037 results[next(iv)] = st
1047 return results
1038 return results
1048
1039
1049 def status(self, match, subrepos, ignored, clean, unknown):
1040 def status(self, match, subrepos, ignored, clean, unknown):
1050 '''Determine the status of the working copy relative to the
1041 '''Determine the status of the working copy relative to the
1051 dirstate and return a pair of (unsure, status), where status is of type
1042 dirstate and return a pair of (unsure, status), where status is of type
1052 scmutil.status and:
1043 scmutil.status and:
1053
1044
1054 unsure:
1045 unsure:
1055 files that might have been modified since the dirstate was
1046 files that might have been modified since the dirstate was
1056 written, but need to be read to be sure (size is the same
1047 written, but need to be read to be sure (size is the same
1057 but mtime differs)
1048 but mtime differs)
1058 status.modified:
1049 status.modified:
1059 files that have definitely been modified since the dirstate
1050 files that have definitely been modified since the dirstate
1060 was written (different size or mode)
1051 was written (different size or mode)
1061 status.clean:
1052 status.clean:
1062 files that have definitely not been modified since the
1053 files that have definitely not been modified since the
1063 dirstate was written
1054 dirstate was written
1064 '''
1055 '''
1065 listignored, listclean, listunknown = ignored, clean, unknown
1056 listignored, listclean, listunknown = ignored, clean, unknown
1066 lookup, modified, added, unknown, ignored = [], [], [], [], []
1057 lookup, modified, added, unknown, ignored = [], [], [], [], []
1067 removed, deleted, clean = [], [], []
1058 removed, deleted, clean = [], [], []
1068
1059
1069 dmap = self._map
1060 dmap = self._map
1070 ladd = lookup.append # aka "unsure"
1061 ladd = lookup.append # aka "unsure"
1071 madd = modified.append
1062 madd = modified.append
1072 aadd = added.append
1063 aadd = added.append
1073 uadd = unknown.append
1064 uadd = unknown.append
1074 iadd = ignored.append
1065 iadd = ignored.append
1075 radd = removed.append
1066 radd = removed.append
1076 dadd = deleted.append
1067 dadd = deleted.append
1077 cadd = clean.append
1068 cadd = clean.append
1078 mexact = match.exact
1069 mexact = match.exact
1079 dirignore = self._dirignore
1070 dirignore = self._dirignore
1080 checkexec = self._checkexec
1071 checkexec = self._checkexec
1081 copymap = self._map.copymap
1072 copymap = self._map.copymap
1082 lastnormaltime = self._lastnormaltime
1073 lastnormaltime = self._lastnormaltime
1083
1074
1084 # We need to do full walks when either
1075 # We need to do full walks when either
1085 # - we're listing all clean files, or
1076 # - we're listing all clean files, or
1086 # - match.traversedir does something, because match.traversedir should
1077 # - match.traversedir does something, because match.traversedir should
1087 # be called for every dir in the working dir
1078 # be called for every dir in the working dir
1088 full = listclean or match.traversedir is not None
1079 full = listclean or match.traversedir is not None
1089 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1080 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1090 full=full).iteritems():
1081 full=full).iteritems():
1091 if fn not in dmap:
1082 if fn not in dmap:
1092 if (listignored or mexact(fn)) and dirignore(fn):
1083 if (listignored or mexact(fn)) and dirignore(fn):
1093 if listignored:
1084 if listignored:
1094 iadd(fn)
1085 iadd(fn)
1095 else:
1086 else:
1096 uadd(fn)
1087 uadd(fn)
1097 continue
1088 continue
1098
1089
1099 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1090 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1100 # written like that for performance reasons. dmap[fn] is not a
1091 # written like that for performance reasons. dmap[fn] is not a
1101 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1092 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1102 # opcode has fast paths when the value to be unpacked is a tuple or
1093 # opcode has fast paths when the value to be unpacked is a tuple or
1103 # a list, but falls back to creating a full-fledged iterator in
1094 # a list, but falls back to creating a full-fledged iterator in
1104 # general. That is much slower than simply accessing and storing the
1095 # general. That is much slower than simply accessing and storing the
1105 # tuple members one by one.
1096 # tuple members one by one.
1106 t = dmap[fn]
1097 t = dmap[fn]
1107 state = t[0]
1098 state = t[0]
1108 mode = t[1]
1099 mode = t[1]
1109 size = t[2]
1100 size = t[2]
1110 time = t[3]
1101 time = t[3]
1111
1102
1112 if not st and state in "nma":
1103 if not st and state in "nma":
1113 dadd(fn)
1104 dadd(fn)
1114 elif state == 'n':
1105 elif state == 'n':
1115 if (size >= 0 and
1106 if (size >= 0 and
1116 ((size != st.st_size and size != st.st_size & _rangemask)
1107 ((size != st.st_size and size != st.st_size & _rangemask)
1117 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1108 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1118 or size == -2 # other parent
1109 or size == -2 # other parent
1119 or fn in copymap):
1110 or fn in copymap):
1120 madd(fn)
1111 madd(fn)
1121 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1112 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1122 ladd(fn)
1113 ladd(fn)
1123 elif st.st_mtime == lastnormaltime:
1114 elif st.st_mtime == lastnormaltime:
1124 # fn may have just been marked as normal and it may have
1115 # fn may have just been marked as normal and it may have
1125 # changed in the same second without changing its size.
1116 # changed in the same second without changing its size.
1126 # This can happen if we quickly do multiple commits.
1117 # This can happen if we quickly do multiple commits.
1127 # Force lookup, so we don't miss such a racy file change.
1118 # Force lookup, so we don't miss such a racy file change.
1128 ladd(fn)
1119 ladd(fn)
1129 elif listclean:
1120 elif listclean:
1130 cadd(fn)
1121 cadd(fn)
1131 elif state == 'm':
1122 elif state == 'm':
1132 madd(fn)
1123 madd(fn)
1133 elif state == 'a':
1124 elif state == 'a':
1134 aadd(fn)
1125 aadd(fn)
1135 elif state == 'r':
1126 elif state == 'r':
1136 radd(fn)
1127 radd(fn)
1137
1128
1138 return (lookup, scmutil.status(modified, added, removed, deleted,
1129 return (lookup, scmutil.status(modified, added, removed, deleted,
1139 unknown, ignored, clean))
1130 unknown, ignored, clean))
1140
1131
1141 def matches(self, match):
1132 def matches(self, match):
1142 '''
1133 '''
1143 return files in the dirstate (in whatever state) filtered by match
1134 return files in the dirstate (in whatever state) filtered by match
1144 '''
1135 '''
1145 dmap = self._map
1136 dmap = self._map
1146 if match.always():
1137 if match.always():
1147 return dmap.keys()
1138 return dmap.keys()
1148 files = match.files()
1139 files = match.files()
1149 if match.isexact():
1140 if match.isexact():
1150 # fast path -- filter the other way around, since typically files is
1141 # fast path -- filter the other way around, since typically files is
1151 # much smaller than dmap
1142 # much smaller than dmap
1152 return [f for f in files if f in dmap]
1143 return [f for f in files if f in dmap]
1153 if match.prefix() and all(fn in dmap for fn in files):
1144 if match.prefix() and all(fn in dmap for fn in files):
1154 # fast path -- all the values are known to be files, so just return
1145 # fast path -- all the values are known to be files, so just return
1155 # that
1146 # that
1156 return list(files)
1147 return list(files)
1157 return [f for f in dmap if match(f)]
1148 return [f for f in dmap if match(f)]
1158
1149
1159 def _actualfilename(self, tr):
1150 def _actualfilename(self, tr):
1160 if tr:
1151 if tr:
1161 return self._pendingfilename
1152 return self._pendingfilename
1162 else:
1153 else:
1163 return self._filename
1154 return self._filename
1164
1155
1165 def savebackup(self, tr, backupname):
1156 def savebackup(self, tr, backupname):
1166 '''Save current dirstate into backup file'''
1157 '''Save current dirstate into backup file'''
1167 filename = self._actualfilename(tr)
1158 filename = self._actualfilename(tr)
1168 assert backupname != filename
1159 assert backupname != filename
1169
1160
1170 # use '_writedirstate' instead of 'write' to write changes certainly,
1161 # use '_writedirstate' instead of 'write' to write changes certainly,
1171 # because the latter omits writing out if transaction is running.
1162 # because the latter omits writing out if transaction is running.
1172 # output file will be used to create backup of dirstate at this point.
1163 # output file will be used to create backup of dirstate at this point.
1173 if self._dirty or not self._opener.exists(filename):
1164 if self._dirty or not self._opener.exists(filename):
1174 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1165 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1175 checkambig=True))
1166 checkambig=True))
1176
1167
1177 if tr:
1168 if tr:
1178 # ensure that subsequent tr.writepending returns True for
1169 # ensure that subsequent tr.writepending returns True for
1179 # changes written out above, even if dirstate is never
1170 # changes written out above, even if dirstate is never
1180 # changed after this
1171 # changed after this
1181 tr.addfilegenerator('dirstate', (self._filename,),
1172 tr.addfilegenerator('dirstate', (self._filename,),
1182 self._writedirstate, location='plain')
1173 self._writedirstate, location='plain')
1183
1174
1184 # ensure that pending file written above is unlinked at
1175 # ensure that pending file written above is unlinked at
1185 # failure, even if tr.writepending isn't invoked until the
1176 # failure, even if tr.writepending isn't invoked until the
1186 # end of this transaction
1177 # end of this transaction
1187 tr.registertmp(filename, location='plain')
1178 tr.registertmp(filename, location='plain')
1188
1179
1189 self._opener.tryunlink(backupname)
1180 self._opener.tryunlink(backupname)
1190 # hardlink backup is okay because _writedirstate is always called
1181 # hardlink backup is okay because _writedirstate is always called
1191 # with an "atomictemp=True" file.
1182 # with an "atomictemp=True" file.
1192 util.copyfile(self._opener.join(filename),
1183 util.copyfile(self._opener.join(filename),
1193 self._opener.join(backupname), hardlink=True)
1184 self._opener.join(backupname), hardlink=True)
1194
1185
1195 def restorebackup(self, tr, backupname):
1186 def restorebackup(self, tr, backupname):
1196 '''Restore dirstate by backup file'''
1187 '''Restore dirstate by backup file'''
1197 # this "invalidate()" prevents "wlock.release()" from writing
1188 # this "invalidate()" prevents "wlock.release()" from writing
1198 # changes of dirstate out after restoring from backup file
1189 # changes of dirstate out after restoring from backup file
1199 self.invalidate()
1190 self.invalidate()
1200 filename = self._actualfilename(tr)
1191 filename = self._actualfilename(tr)
1201 self._opener.rename(backupname, filename, checkambig=True)
1192 self._opener.rename(backupname, filename, checkambig=True)
1202
1193
1203 def clearbackup(self, tr, backupname):
1194 def clearbackup(self, tr, backupname):
1204 '''Clear backup file'''
1195 '''Clear backup file'''
1205 self._opener.unlink(backupname)
1196 self._opener.unlink(backupname)
1206
1197
1207 class dirstatemap(object):
1198 class dirstatemap(object):
1208 def __init__(self, ui, opener, root):
1199 def __init__(self, ui, opener, root):
1209 self._ui = ui
1200 self._ui = ui
1210 self._opener = opener
1201 self._opener = opener
1211 self._root = root
1202 self._root = root
1212 self._filename = 'dirstate'
1203 self._filename = 'dirstate'
1213
1204
1214 self._map = {}
1205 self._map = {}
1215 self.copymap = {}
1206 self.copymap = {}
1216 self._parents = None
1207 self._parents = None
1217 self._dirtyparents = False
1208 self._dirtyparents = False
1218
1209
1219 # for consistent view between _pl() and _read() invocations
1210 # for consistent view between _pl() and _read() invocations
1220 self._pendingmode = None
1211 self._pendingmode = None
1221
1212
1222 def iteritems(self):
1213 def iteritems(self):
1223 return self._map.iteritems()
1214 return self._map.iteritems()
1224
1215
1225 def __len__(self):
1216 def __len__(self):
1226 return len(self._map)
1217 return len(self._map)
1227
1218
1228 def __iter__(self):
1219 def __iter__(self):
1229 return iter(self._map)
1220 return iter(self._map)
1230
1221
1231 def get(self, key, default=None):
1222 def get(self, key, default=None):
1232 return self._map.get(key, default)
1223 return self._map.get(key, default)
1233
1224
1234 def __contains__(self, key):
1225 def __contains__(self, key):
1235 return key in self._map
1226 return key in self._map
1236
1227
1237 def __setitem__(self, key, value):
1228 def __setitem__(self, key, value):
1238 self._map[key] = value
1229 self._map[key] = value
1239
1230
1240 def __getitem__(self, key):
1231 def __getitem__(self, key):
1241 return self._map[key]
1232 return self._map[key]
1242
1233
1243 def __delitem__(self, key):
1234 def __delitem__(self, key):
1244 del self._map[key]
1235 del self._map[key]
1245
1236
1246 def keys(self):
1237 def keys(self):
1247 return self._map.keys()
1238 return self._map.keys()
1248
1239
1249 def nonnormalentries(self):
1240 def nonnormalentries(self):
1250 '''Compute the nonnormal dirstate entries from the dmap'''
1241 '''Compute the nonnormal dirstate entries from the dmap'''
1251 try:
1242 try:
1252 return parsers.nonnormalotherparententries(self._map)
1243 return parsers.nonnormalotherparententries(self._map)
1253 except AttributeError:
1244 except AttributeError:
1254 nonnorm = set()
1245 nonnorm = set()
1255 otherparent = set()
1246 otherparent = set()
1256 for fname, e in self._map.iteritems():
1247 for fname, e in self._map.iteritems():
1257 if e[0] != 'n' or e[3] == -1:
1248 if e[0] != 'n' or e[3] == -1:
1258 nonnorm.add(fname)
1249 nonnorm.add(fname)
1259 if e[0] == 'n' and e[2] == -2:
1250 if e[0] == 'n' and e[2] == -2:
1260 otherparent.add(fname)
1251 otherparent.add(fname)
1261 return nonnorm, otherparent
1252 return nonnorm, otherparent
1262
1253
1263 @propertycache
1254 @propertycache
1264 def filefoldmap(self):
1255 def filefoldmap(self):
1265 """Returns a dictionary mapping normalized case paths to their
1256 """Returns a dictionary mapping normalized case paths to their
1266 non-normalized versions.
1257 non-normalized versions.
1267 """
1258 """
1268 try:
1259 try:
1269 makefilefoldmap = parsers.make_file_foldmap
1260 makefilefoldmap = parsers.make_file_foldmap
1270 except AttributeError:
1261 except AttributeError:
1271 pass
1262 pass
1272 else:
1263 else:
1273 return makefilefoldmap(self._map, util.normcasespec,
1264 return makefilefoldmap(self._map, util.normcasespec,
1274 util.normcasefallback)
1265 util.normcasefallback)
1275
1266
1276 f = {}
1267 f = {}
1277 normcase = util.normcase
1268 normcase = util.normcase
1278 for name, s in self._map.iteritems():
1269 for name, s in self._map.iteritems():
1279 if s[0] != 'r':
1270 if s[0] != 'r':
1280 f[normcase(name)] = name
1271 f[normcase(name)] = name
1281 f['.'] = '.' # prevents useless util.fspath() invocation
1272 f['.'] = '.' # prevents useless util.fspath() invocation
1282 return f
1273 return f
1283
1274
1284 @propertycache
1275 @propertycache
1285 def dirs(self):
1276 def dirs(self):
1286 """Returns a set-like object containing all the directories in the
1277 """Returns a set-like object containing all the directories in the
1287 current dirstate.
1278 current dirstate.
1288 """
1279 """
1289 return util.dirs(self._map, 'r')
1280 return util.dirs(self._map, 'r')
1290
1281
1291 def _opendirstatefile(self):
1282 def _opendirstatefile(self):
1292 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1283 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1293 if self._pendingmode is not None and self._pendingmode != mode:
1284 if self._pendingmode is not None and self._pendingmode != mode:
1294 fp.close()
1285 fp.close()
1295 raise error.Abort(_('working directory state may be '
1286 raise error.Abort(_('working directory state may be '
1296 'changed parallelly'))
1287 'changed parallelly'))
1297 self._pendingmode = mode
1288 self._pendingmode = mode
1298 return fp
1289 return fp
1299
1290
1300 def parents(self):
1291 def parents(self):
1301 if not self._parents:
1292 if not self._parents:
1302 try:
1293 try:
1303 fp = self._opendirstatefile()
1294 fp = self._opendirstatefile()
1304 st = fp.read(40)
1295 st = fp.read(40)
1305 fp.close()
1296 fp.close()
1306 except IOError as err:
1297 except IOError as err:
1307 if err.errno != errno.ENOENT:
1298 if err.errno != errno.ENOENT:
1308 raise
1299 raise
1309 # File doesn't exist, so the current state is empty
1300 # File doesn't exist, so the current state is empty
1310 st = ''
1301 st = ''
1311
1302
1312 l = len(st)
1303 l = len(st)
1313 if l == 40:
1304 if l == 40:
1314 self._parents = st[:20], st[20:40]
1305 self._parents = st[:20], st[20:40]
1315 elif l == 0:
1306 elif l == 0:
1316 self._parents = [nullid, nullid]
1307 self._parents = [nullid, nullid]
1317 else:
1308 else:
1318 raise error.Abort(_('working directory state appears '
1309 raise error.Abort(_('working directory state appears '
1319 'damaged!'))
1310 'damaged!'))
1320
1311
1321 return self._parents
1312 return self._parents
1322
1313
1323 def setparents(self, p1, p2):
1314 def setparents(self, p1, p2):
1324 self._parents = (p1, p2)
1315 self._parents = (p1, p2)
1325 self._dirtyparents = True
1316 self._dirtyparents = True
1326
1317
1327 def read(self):
1318 def read(self):
1328 # ignore HG_PENDING because identity is used only for writing
1319 # ignore HG_PENDING because identity is used only for writing
1329 self.identity = util.filestat.frompath(
1320 self.identity = util.filestat.frompath(
1330 self._opener.join(self._filename))
1321 self._opener.join(self._filename))
1331
1322
1332 try:
1323 try:
1333 fp = self._opendirstatefile()
1324 fp = self._opendirstatefile()
1334 try:
1325 try:
1335 st = fp.read()
1326 st = fp.read()
1336 finally:
1327 finally:
1337 fp.close()
1328 fp.close()
1338 except IOError as err:
1329 except IOError as err:
1339 if err.errno != errno.ENOENT:
1330 if err.errno != errno.ENOENT:
1340 raise
1331 raise
1341 return
1332 return
1342 if not st:
1333 if not st:
1343 return
1334 return
1344
1335
1345 if util.safehasattr(parsers, 'dict_new_presized'):
1336 if util.safehasattr(parsers, 'dict_new_presized'):
1346 # Make an estimate of the number of files in the dirstate based on
1337 # Make an estimate of the number of files in the dirstate based on
1347 # its size. From a linear regression on a set of real-world repos,
1338 # its size. From a linear regression on a set of real-world repos,
1348 # all over 10,000 files, the size of a dirstate entry is 85
1339 # all over 10,000 files, the size of a dirstate entry is 85
1349 # bytes. The cost of resizing is significantly higher than the cost
1340 # bytes. The cost of resizing is significantly higher than the cost
1350 # of filling in a larger presized dict, so subtract 20% from the
1341 # of filling in a larger presized dict, so subtract 20% from the
1351 # size.
1342 # size.
1352 #
1343 #
1353 # This heuristic is imperfect in many ways, so in a future dirstate
1344 # This heuristic is imperfect in many ways, so in a future dirstate
1354 # format update it makes sense to just record the number of entries
1345 # format update it makes sense to just record the number of entries
1355 # on write.
1346 # on write.
1356 self._map = parsers.dict_new_presized(len(st) / 71)
1347 self._map = parsers.dict_new_presized(len(st) / 71)
1357
1348
1358 # Python's garbage collector triggers a GC each time a certain number
1349 # Python's garbage collector triggers a GC each time a certain number
1359 # of container objects (the number being defined by
1350 # of container objects (the number being defined by
1360 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1351 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1361 # for each file in the dirstate. The C version then immediately marks
1352 # for each file in the dirstate. The C version then immediately marks
1362 # them as not to be tracked by the collector. However, this has no
1353 # them as not to be tracked by the collector. However, this has no
1363 # effect on when GCs are triggered, only on what objects the GC looks
1354 # effect on when GCs are triggered, only on what objects the GC looks
1364 # into. This means that O(number of files) GCs are unavoidable.
1355 # into. This means that O(number of files) GCs are unavoidable.
1365 # Depending on when in the process's lifetime the dirstate is parsed,
1356 # Depending on when in the process's lifetime the dirstate is parsed,
1366 # this can get very expensive. As a workaround, disable GC while
1357 # this can get very expensive. As a workaround, disable GC while
1367 # parsing the dirstate.
1358 # parsing the dirstate.
1368 #
1359 #
1369 # (we cannot decorate the function directly since it is in a C module)
1360 # (we cannot decorate the function directly since it is in a C module)
1370 parse_dirstate = util.nogc(parsers.parse_dirstate)
1361 parse_dirstate = util.nogc(parsers.parse_dirstate)
1371 p = parse_dirstate(self._map, self.copymap, st)
1362 p = parse_dirstate(self._map, self.copymap, st)
1372 if not self._dirtyparents:
1363 if not self._dirtyparents:
1373 self.setparents(*p)
1364 self.setparents(*p)
1374
1365
1375 def write(self, st, now):
1366 def write(self, st, now):
1376 st.write(parsers.pack_dirstate(self._map, self.copymap,
1367 st.write(parsers.pack_dirstate(self._map, self.copymap,
1377 self.parents(), now))
1368 self.parents(), now))
1378 st.close()
1369 st.close()
1379 self._dirtyparents = False
1370 self._dirtyparents = False
1380 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1371 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1381
1372
1382 @propertycache
1373 @propertycache
1383 def nonnormalset(self):
1374 def nonnormalset(self):
1384 nonnorm, otherparents = self.nonnormalentries()
1375 nonnorm, otherparents = self.nonnormalentries()
1385 self.otherparentset = otherparents
1376 self.otherparentset = otherparents
1386 return nonnorm
1377 return nonnorm
1387
1378
1388 @propertycache
1379 @propertycache
1389 def otherparentset(self):
1380 def otherparentset(self):
1390 nonnorm, otherparents = self.nonnormalentries()
1381 nonnorm, otherparents = self.nonnormalentries()
1391 self.nonnormalset = nonnorm
1382 self.nonnormalset = nonnorm
1392 return otherparents
1383 return otherparents
1393
1384
1394 @propertycache
1385 @propertycache
1395 def identity(self):
1386 def identity(self):
1396 self.read()
1387 self.read()
1397 return self.identity
1388 return self.identity
1398
1389
1390 @propertycache
1391 def dirfoldmap(self):
1392 f = {}
1393 normcase = util.normcase
1394 for name in self.dirs:
1395 f[normcase(name)] = name
1396 return f
General Comments 0
You need to be logged in to leave comments. Login now