##// END OF EJS Templates
dirstate: remove _filefoldmap property cache...
Durham Goode -
r34677:bfddc3d6 default
parent child Browse files
Show More
@@ -1,1502 +1,1502
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._dirs
522 del dirstate._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._dirs
541 "a" in repo.dirstate._dirs
542 del repo.dirstate._dirs
542 del repo.dirstate._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._filefoldmap.get('a')
552 dirstate._map.filefoldmap.get('a')
553 del dirstate._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._dirfoldmap.get('a')
564 del dirstate._dirfoldmap
564 del dirstate._dirfoldmap
565 del dirstate._dirs
565 del dirstate._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,1406 +1,1403
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
135 @propertycache
136 def _filefoldmap(self):
137 return self._map.filefoldmap()
138
139 @propertycache
140 def _dirfoldmap(self):
136 def _dirfoldmap(self):
141 f = {}
137 f = {}
142 normcase = util.normcase
138 normcase = util.normcase
143 for name in self._dirs:
139 for name in self._dirs:
144 f[normcase(name)] = name
140 f[normcase(name)] = name
145 return f
141 return f
146
142
147 @property
143 @property
148 def _sparsematcher(self):
144 def _sparsematcher(self):
149 """The matcher for the sparse checkout.
145 """The matcher for the sparse checkout.
150
146
151 The working directory may not include every file from a manifest. The
147 The working directory may not include every file from a manifest. The
152 matcher obtained by this property will match a path if it is to be
148 matcher obtained by this property will match a path if it is to be
153 included in the working directory.
149 included in the working directory.
154 """
150 """
155 # TODO there is potential to cache this property. For now, the matcher
151 # TODO there is potential to cache this property. For now, the matcher
156 # is resolved on every access. (But the called function does use a
152 # is resolved on every access. (But the called function does use a
157 # cache to keep the lookup fast.)
153 # cache to keep the lookup fast.)
158 return self._sparsematchfn()
154 return self._sparsematchfn()
159
155
160 @repocache('branch')
156 @repocache('branch')
161 def _branch(self):
157 def _branch(self):
162 try:
158 try:
163 return self._opener.read("branch").strip() or "default"
159 return self._opener.read("branch").strip() or "default"
164 except IOError as inst:
160 except IOError as inst:
165 if inst.errno != errno.ENOENT:
161 if inst.errno != errno.ENOENT:
166 raise
162 raise
167 return "default"
163 return "default"
168
164
169 @property
165 @property
170 def _pl(self):
166 def _pl(self):
171 return self._map.parents()
167 return self._map.parents()
172
168
173 @propertycache
169 @propertycache
174 def _dirs(self):
170 def _dirs(self):
175 return self._map.dirs()
171 return self._map.dirs()
176
172
177 def dirs(self):
173 def dirs(self):
178 return self._dirs
174 return self._dirs
179
175
180 @rootcache('.hgignore')
176 @rootcache('.hgignore')
181 def _ignore(self):
177 def _ignore(self):
182 files = self._ignorefiles()
178 files = self._ignorefiles()
183 if not files:
179 if not files:
184 return matchmod.never(self._root, '')
180 return matchmod.never(self._root, '')
185
181
186 pats = ['include:%s' % f for f in files]
182 pats = ['include:%s' % f for f in files]
187 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
183 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
188
184
189 @propertycache
185 @propertycache
190 def _slash(self):
186 def _slash(self):
191 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
187 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
192
188
193 @propertycache
189 @propertycache
194 def _checklink(self):
190 def _checklink(self):
195 return util.checklink(self._root)
191 return util.checklink(self._root)
196
192
197 @propertycache
193 @propertycache
198 def _checkexec(self):
194 def _checkexec(self):
199 return util.checkexec(self._root)
195 return util.checkexec(self._root)
200
196
201 @propertycache
197 @propertycache
202 def _checkcase(self):
198 def _checkcase(self):
203 return not util.fscasesensitive(self._join('.hg'))
199 return not util.fscasesensitive(self._join('.hg'))
204
200
205 def _join(self, f):
201 def _join(self, f):
206 # much faster than os.path.join()
202 # much faster than os.path.join()
207 # it's safe because f is always a relative path
203 # it's safe because f is always a relative path
208 return self._rootdir + f
204 return self._rootdir + f
209
205
210 def flagfunc(self, buildfallback):
206 def flagfunc(self, buildfallback):
211 if self._checklink and self._checkexec:
207 if self._checklink and self._checkexec:
212 def f(x):
208 def f(x):
213 try:
209 try:
214 st = os.lstat(self._join(x))
210 st = os.lstat(self._join(x))
215 if util.statislink(st):
211 if util.statislink(st):
216 return 'l'
212 return 'l'
217 if util.statisexec(st):
213 if util.statisexec(st):
218 return 'x'
214 return 'x'
219 except OSError:
215 except OSError:
220 pass
216 pass
221 return ''
217 return ''
222 return f
218 return f
223
219
224 fallback = buildfallback()
220 fallback = buildfallback()
225 if self._checklink:
221 if self._checklink:
226 def f(x):
222 def f(x):
227 if os.path.islink(self._join(x)):
223 if os.path.islink(self._join(x)):
228 return 'l'
224 return 'l'
229 if 'x' in fallback(x):
225 if 'x' in fallback(x):
230 return 'x'
226 return 'x'
231 return ''
227 return ''
232 return f
228 return f
233 if self._checkexec:
229 if self._checkexec:
234 def f(x):
230 def f(x):
235 if 'l' in fallback(x):
231 if 'l' in fallback(x):
236 return 'l'
232 return 'l'
237 if util.isexec(self._join(x)):
233 if util.isexec(self._join(x)):
238 return 'x'
234 return 'x'
239 return ''
235 return ''
240 return f
236 return f
241 else:
237 else:
242 return fallback
238 return fallback
243
239
244 @propertycache
240 @propertycache
245 def _cwd(self):
241 def _cwd(self):
246 # internal config: ui.forcecwd
242 # internal config: ui.forcecwd
247 forcecwd = self._ui.config('ui', 'forcecwd')
243 forcecwd = self._ui.config('ui', 'forcecwd')
248 if forcecwd:
244 if forcecwd:
249 return forcecwd
245 return forcecwd
250 return pycompat.getcwd()
246 return pycompat.getcwd()
251
247
252 def getcwd(self):
248 def getcwd(self):
253 '''Return the path from which a canonical path is calculated.
249 '''Return the path from which a canonical path is calculated.
254
250
255 This path should be used to resolve file patterns or to convert
251 This path should be used to resolve file patterns or to convert
256 canonical paths back to file paths for display. It shouldn't be
252 canonical paths back to file paths for display. It shouldn't be
257 used to get real file paths. Use vfs functions instead.
253 used to get real file paths. Use vfs functions instead.
258 '''
254 '''
259 cwd = self._cwd
255 cwd = self._cwd
260 if cwd == self._root:
256 if cwd == self._root:
261 return ''
257 return ''
262 # self._root ends with a path separator if self._root is '/' or 'C:\'
258 # self._root ends with a path separator if self._root is '/' or 'C:\'
263 rootsep = self._root
259 rootsep = self._root
264 if not util.endswithsep(rootsep):
260 if not util.endswithsep(rootsep):
265 rootsep += pycompat.ossep
261 rootsep += pycompat.ossep
266 if cwd.startswith(rootsep):
262 if cwd.startswith(rootsep):
267 return cwd[len(rootsep):]
263 return cwd[len(rootsep):]
268 else:
264 else:
269 # we're outside the repo. return an absolute path.
265 # we're outside the repo. return an absolute path.
270 return cwd
266 return cwd
271
267
272 def pathto(self, f, cwd=None):
268 def pathto(self, f, cwd=None):
273 if cwd is None:
269 if cwd is None:
274 cwd = self.getcwd()
270 cwd = self.getcwd()
275 path = util.pathto(self._root, cwd, f)
271 path = util.pathto(self._root, cwd, f)
276 if self._slash:
272 if self._slash:
277 return util.pconvert(path)
273 return util.pconvert(path)
278 return path
274 return path
279
275
280 def __getitem__(self, key):
276 def __getitem__(self, key):
281 '''Return the current state of key (a filename) in the dirstate.
277 '''Return the current state of key (a filename) in the dirstate.
282
278
283 States are:
279 States are:
284 n normal
280 n normal
285 m needs merging
281 m needs merging
286 r marked for removal
282 r marked for removal
287 a marked for addition
283 a marked for addition
288 ? not tracked
284 ? not tracked
289 '''
285 '''
290 return self._map.get(key, ("?",))[0]
286 return self._map.get(key, ("?",))[0]
291
287
292 def __contains__(self, key):
288 def __contains__(self, key):
293 return key in self._map
289 return key in self._map
294
290
295 def __iter__(self):
291 def __iter__(self):
296 return iter(sorted(self._map))
292 return iter(sorted(self._map))
297
293
298 def items(self):
294 def items(self):
299 return self._map.iteritems()
295 return self._map.iteritems()
300
296
301 iteritems = items
297 iteritems = items
302
298
303 def parents(self):
299 def parents(self):
304 return [self._validate(p) for p in self._pl]
300 return [self._validate(p) for p in self._pl]
305
301
306 def p1(self):
302 def p1(self):
307 return self._validate(self._pl[0])
303 return self._validate(self._pl[0])
308
304
309 def p2(self):
305 def p2(self):
310 return self._validate(self._pl[1])
306 return self._validate(self._pl[1])
311
307
312 def branch(self):
308 def branch(self):
313 return encoding.tolocal(self._branch)
309 return encoding.tolocal(self._branch)
314
310
315 def setparents(self, p1, p2=nullid):
311 def setparents(self, p1, p2=nullid):
316 """Set dirstate parents to p1 and p2.
312 """Set dirstate parents to p1 and p2.
317
313
318 When moving from two parents to one, 'm' merged entries a
314 When moving from two parents to one, 'm' merged entries a
319 adjusted to normal and previous copy records discarded and
315 adjusted to normal and previous copy records discarded and
320 returned by the call.
316 returned by the call.
321
317
322 See localrepo.setparents()
318 See localrepo.setparents()
323 """
319 """
324 if self._parentwriters == 0:
320 if self._parentwriters == 0:
325 raise ValueError("cannot set dirstate parent without "
321 raise ValueError("cannot set dirstate parent without "
326 "calling dirstate.beginparentchange")
322 "calling dirstate.beginparentchange")
327
323
328 self._dirty = True
324 self._dirty = True
329 oldp2 = self._pl[1]
325 oldp2 = self._pl[1]
330 if self._origpl is None:
326 if self._origpl is None:
331 self._origpl = self._pl
327 self._origpl = self._pl
332 self._map.setparents(p1, p2)
328 self._map.setparents(p1, p2)
333 copies = {}
329 copies = {}
334 if oldp2 != nullid and p2 == nullid:
330 if oldp2 != nullid and p2 == nullid:
335 candidatefiles = self._map.nonnormalset.union(
331 candidatefiles = self._map.nonnormalset.union(
336 self._map.otherparentset)
332 self._map.otherparentset)
337 for f in candidatefiles:
333 for f in candidatefiles:
338 s = self._map.get(f)
334 s = self._map.get(f)
339 if s is None:
335 if s is None:
340 continue
336 continue
341
337
342 # Discard 'm' markers when moving away from a merge state
338 # Discard 'm' markers when moving away from a merge state
343 if s[0] == 'm':
339 if s[0] == 'm':
344 source = self._map.copymap.get(f)
340 source = self._map.copymap.get(f)
345 if source:
341 if source:
346 copies[f] = source
342 copies[f] = source
347 self.normallookup(f)
343 self.normallookup(f)
348 # Also fix up otherparent markers
344 # Also fix up otherparent markers
349 elif s[0] == 'n' and s[2] == -2:
345 elif s[0] == 'n' and s[2] == -2:
350 source = self._map.copymap.get(f)
346 source = self._map.copymap.get(f)
351 if source:
347 if source:
352 copies[f] = source
348 copies[f] = source
353 self.add(f)
349 self.add(f)
354 return copies
350 return copies
355
351
356 def setbranch(self, branch):
352 def setbranch(self, branch):
357 self._branch = encoding.fromlocal(branch)
353 self._branch = encoding.fromlocal(branch)
358 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
354 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
359 try:
355 try:
360 f.write(self._branch + '\n')
356 f.write(self._branch + '\n')
361 f.close()
357 f.close()
362
358
363 # make sure filecache has the correct stat info for _branch after
359 # make sure filecache has the correct stat info for _branch after
364 # replacing the underlying file
360 # replacing the underlying file
365 ce = self._filecache['_branch']
361 ce = self._filecache['_branch']
366 if ce:
362 if ce:
367 ce.refresh()
363 ce.refresh()
368 except: # re-raises
364 except: # re-raises
369 f.discard()
365 f.discard()
370 raise
366 raise
371
367
372 def _read(self):
368 def _read(self):
373 self._map = dirstatemap(self._ui, self._opener, self._root)
369 self._map = dirstatemap(self._ui, self._opener, self._root)
374 self._map.read()
370 self._map.read()
375
371
376 def invalidate(self):
372 def invalidate(self):
377 '''Causes the next access to reread the dirstate.
373 '''Causes the next access to reread the dirstate.
378
374
379 This is different from localrepo.invalidatedirstate() because it always
375 This is different from localrepo.invalidatedirstate() because it always
380 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
376 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
381 check whether the dirstate has changed before rereading it.'''
377 check whether the dirstate has changed before rereading it.'''
382
378
383 for a in ("_map", "_filefoldmap", "_dirfoldmap", "_branch",
379 for a in ("_map", "_dirfoldmap", "_branch",
384 "_dirs", "_ignore"):
380 "_dirs", "_ignore"):
385 if a in self.__dict__:
381 if a in self.__dict__:
386 delattr(self, a)
382 delattr(self, a)
387 self._lastnormaltime = 0
383 self._lastnormaltime = 0
388 self._dirty = False
384 self._dirty = False
389 self._updatedfiles.clear()
385 self._updatedfiles.clear()
390 self._parentwriters = 0
386 self._parentwriters = 0
391 self._origpl = None
387 self._origpl = None
392
388
393 def copy(self, source, dest):
389 def copy(self, source, dest):
394 """Mark dest as a copy of source. Unmark dest if source is None."""
390 """Mark dest as a copy of source. Unmark dest if source is None."""
395 if source == dest:
391 if source == dest:
396 return
392 return
397 self._dirty = True
393 self._dirty = True
398 if source is not None:
394 if source is not None:
399 self._map.copymap[dest] = source
395 self._map.copymap[dest] = source
400 self._updatedfiles.add(source)
396 self._updatedfiles.add(source)
401 self._updatedfiles.add(dest)
397 self._updatedfiles.add(dest)
402 elif self._map.copymap.pop(dest, None):
398 elif self._map.copymap.pop(dest, None):
403 self._updatedfiles.add(dest)
399 self._updatedfiles.add(dest)
404
400
405 def copied(self, file):
401 def copied(self, file):
406 return self._map.copymap.get(file, None)
402 return self._map.copymap.get(file, None)
407
403
408 def copies(self):
404 def copies(self):
409 return self._map.copymap
405 return self._map.copymap
410
406
411 def _droppath(self, f):
407 def _droppath(self, f):
412 if self[f] not in "?r" and "_dirs" in self.__dict__:
408 if self[f] not in "?r" and "_dirs" in self.__dict__:
413 self._dirs.delpath(f)
409 self._dirs.delpath(f)
414
410
415 if "_filefoldmap" in self.__dict__:
411 if "filefoldmap" in self._map.__dict__:
416 normed = util.normcase(f)
412 normed = util.normcase(f)
417 if normed in self._filefoldmap:
413 if normed in self._map.filefoldmap:
418 del self._filefoldmap[normed]
414 del self._map.filefoldmap[normed]
419
415
420 self._updatedfiles.add(f)
416 self._updatedfiles.add(f)
421
417
422 def _addpath(self, f, state, mode, size, mtime):
418 def _addpath(self, f, state, mode, size, mtime):
423 oldstate = self[f]
419 oldstate = self[f]
424 if state == 'a' or oldstate == 'r':
420 if state == 'a' or oldstate == 'r':
425 scmutil.checkfilename(f)
421 scmutil.checkfilename(f)
426 if f in self._dirs:
422 if f in self._dirs:
427 raise error.Abort(_('directory %r already in dirstate') % f)
423 raise error.Abort(_('directory %r already in dirstate') % f)
428 # shadows
424 # shadows
429 for d in util.finddirs(f):
425 for d in util.finddirs(f):
430 if d in self._dirs:
426 if d in self._dirs:
431 break
427 break
432 entry = self._map.get(d)
428 entry = self._map.get(d)
433 if entry is not None and entry[0] != 'r':
429 if entry is not None and entry[0] != 'r':
434 raise error.Abort(
430 raise error.Abort(
435 _('file %r in dirstate clashes with %r') % (d, f))
431 _('file %r in dirstate clashes with %r') % (d, f))
436 if oldstate in "?r" and "_dirs" in self.__dict__:
432 if oldstate in "?r" and "_dirs" in self.__dict__:
437 self._dirs.addpath(f)
433 self._dirs.addpath(f)
438 self._dirty = True
434 self._dirty = True
439 self._updatedfiles.add(f)
435 self._updatedfiles.add(f)
440 self._map[f] = dirstatetuple(state, mode, size, mtime)
436 self._map[f] = dirstatetuple(state, mode, size, mtime)
441 if state != 'n' or mtime == -1:
437 if state != 'n' or mtime == -1:
442 self._map.nonnormalset.add(f)
438 self._map.nonnormalset.add(f)
443 if size == -2:
439 if size == -2:
444 self._map.otherparentset.add(f)
440 self._map.otherparentset.add(f)
445
441
446 def normal(self, f):
442 def normal(self, f):
447 '''Mark a file normal and clean.'''
443 '''Mark a file normal and clean.'''
448 s = os.lstat(self._join(f))
444 s = os.lstat(self._join(f))
449 mtime = s.st_mtime
445 mtime = s.st_mtime
450 self._addpath(f, 'n', s.st_mode,
446 self._addpath(f, 'n', s.st_mode,
451 s.st_size & _rangemask, mtime & _rangemask)
447 s.st_size & _rangemask, mtime & _rangemask)
452 self._map.copymap.pop(f, None)
448 self._map.copymap.pop(f, None)
453 if f in self._map.nonnormalset:
449 if f in self._map.nonnormalset:
454 self._map.nonnormalset.remove(f)
450 self._map.nonnormalset.remove(f)
455 if mtime > self._lastnormaltime:
451 if mtime > self._lastnormaltime:
456 # Remember the most recent modification timeslot for status(),
452 # Remember the most recent modification timeslot for status(),
457 # to make sure we won't miss future size-preserving file content
453 # to make sure we won't miss future size-preserving file content
458 # modifications that happen within the same timeslot.
454 # modifications that happen within the same timeslot.
459 self._lastnormaltime = mtime
455 self._lastnormaltime = mtime
460
456
461 def normallookup(self, f):
457 def normallookup(self, f):
462 '''Mark a file normal, but possibly dirty.'''
458 '''Mark a file normal, but possibly dirty.'''
463 if self._pl[1] != nullid:
459 if self._pl[1] != nullid:
464 # if there is a merge going on and the file was either
460 # if there is a merge going on and the file was either
465 # in state 'm' (-1) or coming from other parent (-2) before
461 # in state 'm' (-1) or coming from other parent (-2) before
466 # being removed, restore that state.
462 # being removed, restore that state.
467 entry = self._map.get(f)
463 entry = self._map.get(f)
468 if entry is not None:
464 if entry is not None:
469 if entry[0] == 'r' and entry[2] in (-1, -2):
465 if entry[0] == 'r' and entry[2] in (-1, -2):
470 source = self._map.copymap.get(f)
466 source = self._map.copymap.get(f)
471 if entry[2] == -1:
467 if entry[2] == -1:
472 self.merge(f)
468 self.merge(f)
473 elif entry[2] == -2:
469 elif entry[2] == -2:
474 self.otherparent(f)
470 self.otherparent(f)
475 if source:
471 if source:
476 self.copy(source, f)
472 self.copy(source, f)
477 return
473 return
478 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
474 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
479 return
475 return
480 self._addpath(f, 'n', 0, -1, -1)
476 self._addpath(f, 'n', 0, -1, -1)
481 self._map.copymap.pop(f, None)
477 self._map.copymap.pop(f, None)
482 if f in self._map.nonnormalset:
478 if f in self._map.nonnormalset:
483 self._map.nonnormalset.remove(f)
479 self._map.nonnormalset.remove(f)
484
480
485 def otherparent(self, f):
481 def otherparent(self, f):
486 '''Mark as coming from the other parent, always dirty.'''
482 '''Mark as coming from the other parent, always dirty.'''
487 if self._pl[1] == nullid:
483 if self._pl[1] == nullid:
488 raise error.Abort(_("setting %r to other parent "
484 raise error.Abort(_("setting %r to other parent "
489 "only allowed in merges") % f)
485 "only allowed in merges") % f)
490 if f in self and self[f] == 'n':
486 if f in self and self[f] == 'n':
491 # merge-like
487 # merge-like
492 self._addpath(f, 'm', 0, -2, -1)
488 self._addpath(f, 'm', 0, -2, -1)
493 else:
489 else:
494 # add-like
490 # add-like
495 self._addpath(f, 'n', 0, -2, -1)
491 self._addpath(f, 'n', 0, -2, -1)
496 self._map.copymap.pop(f, None)
492 self._map.copymap.pop(f, None)
497
493
498 def add(self, f):
494 def add(self, f):
499 '''Mark a file added.'''
495 '''Mark a file added.'''
500 self._addpath(f, 'a', 0, -1, -1)
496 self._addpath(f, 'a', 0, -1, -1)
501 self._map.copymap.pop(f, None)
497 self._map.copymap.pop(f, None)
502
498
503 def remove(self, f):
499 def remove(self, f):
504 '''Mark a file removed.'''
500 '''Mark a file removed.'''
505 self._dirty = True
501 self._dirty = True
506 self._droppath(f)
502 self._droppath(f)
507 size = 0
503 size = 0
508 if self._pl[1] != nullid:
504 if self._pl[1] != nullid:
509 entry = self._map.get(f)
505 entry = self._map.get(f)
510 if entry is not None:
506 if entry is not None:
511 # backup the previous state
507 # backup the previous state
512 if entry[0] == 'm': # merge
508 if entry[0] == 'm': # merge
513 size = -1
509 size = -1
514 elif entry[0] == 'n' and entry[2] == -2: # other parent
510 elif entry[0] == 'n' and entry[2] == -2: # other parent
515 size = -2
511 size = -2
516 self._map.otherparentset.add(f)
512 self._map.otherparentset.add(f)
517 self._map[f] = dirstatetuple('r', 0, size, 0)
513 self._map[f] = dirstatetuple('r', 0, size, 0)
518 self._map.nonnormalset.add(f)
514 self._map.nonnormalset.add(f)
519 if size == 0:
515 if size == 0:
520 self._map.copymap.pop(f, None)
516 self._map.copymap.pop(f, None)
521
517
522 def merge(self, f):
518 def merge(self, f):
523 '''Mark a file merged.'''
519 '''Mark a file merged.'''
524 if self._pl[1] == nullid:
520 if self._pl[1] == nullid:
525 return self.normallookup(f)
521 return self.normallookup(f)
526 return self.otherparent(f)
522 return self.otherparent(f)
527
523
528 def drop(self, f):
524 def drop(self, f):
529 '''Drop a file from the dirstate'''
525 '''Drop a file from the dirstate'''
530 if f in self._map:
526 if f in self._map:
531 self._dirty = True
527 self._dirty = True
532 self._droppath(f)
528 self._droppath(f)
533 del self._map[f]
529 del self._map[f]
534 if f in self._map.nonnormalset:
530 if f in self._map.nonnormalset:
535 self._map.nonnormalset.remove(f)
531 self._map.nonnormalset.remove(f)
536 self._map.copymap.pop(f, None)
532 self._map.copymap.pop(f, None)
537
533
538 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
534 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
539 if exists is None:
535 if exists is None:
540 exists = os.path.lexists(os.path.join(self._root, path))
536 exists = os.path.lexists(os.path.join(self._root, path))
541 if not exists:
537 if not exists:
542 # Maybe a path component exists
538 # Maybe a path component exists
543 if not ignoremissing and '/' in path:
539 if not ignoremissing and '/' in path:
544 d, f = path.rsplit('/', 1)
540 d, f = path.rsplit('/', 1)
545 d = self._normalize(d, False, ignoremissing, None)
541 d = self._normalize(d, False, ignoremissing, None)
546 folded = d + "/" + f
542 folded = d + "/" + f
547 else:
543 else:
548 # No path components, preserve original case
544 # No path components, preserve original case
549 folded = path
545 folded = path
550 else:
546 else:
551 # recursively normalize leading directory components
547 # recursively normalize leading directory components
552 # against dirstate
548 # against dirstate
553 if '/' in normed:
549 if '/' in normed:
554 d, f = normed.rsplit('/', 1)
550 d, f = normed.rsplit('/', 1)
555 d = self._normalize(d, False, ignoremissing, True)
551 d = self._normalize(d, False, ignoremissing, True)
556 r = self._root + "/" + d
552 r = self._root + "/" + d
557 folded = d + "/" + util.fspath(f, r)
553 folded = d + "/" + util.fspath(f, r)
558 else:
554 else:
559 folded = util.fspath(normed, self._root)
555 folded = util.fspath(normed, self._root)
560 storemap[normed] = folded
556 storemap[normed] = folded
561
557
562 return folded
558 return folded
563
559
564 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
560 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
565 normed = util.normcase(path)
561 normed = util.normcase(path)
566 folded = self._filefoldmap.get(normed, None)
562 folded = self._map.filefoldmap.get(normed, None)
567 if folded is None:
563 if folded is None:
568 if isknown:
564 if isknown:
569 folded = path
565 folded = path
570 else:
566 else:
571 folded = self._discoverpath(path, normed, ignoremissing, exists,
567 folded = self._discoverpath(path, normed, ignoremissing, exists,
572 self._filefoldmap)
568 self._map.filefoldmap)
573 return folded
569 return folded
574
570
575 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
571 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
576 normed = util.normcase(path)
572 normed = util.normcase(path)
577 folded = self._filefoldmap.get(normed, None)
573 folded = self._map.filefoldmap.get(normed, None)
578 if folded is None:
574 if folded is None:
579 folded = self._dirfoldmap.get(normed, None)
575 folded = self._dirfoldmap.get(normed, None)
580 if folded is None:
576 if folded is None:
581 if isknown:
577 if isknown:
582 folded = path
578 folded = path
583 else:
579 else:
584 # store discovered result in dirfoldmap so that future
580 # store discovered result in dirfoldmap so that future
585 # normalizefile calls don't start matching directories
581 # normalizefile calls don't start matching directories
586 folded = self._discoverpath(path, normed, ignoremissing, exists,
582 folded = self._discoverpath(path, normed, ignoremissing, exists,
587 self._dirfoldmap)
583 self._dirfoldmap)
588 return folded
584 return folded
589
585
590 def normalize(self, path, isknown=False, ignoremissing=False):
586 def normalize(self, path, isknown=False, ignoremissing=False):
591 '''
587 '''
592 normalize the case of a pathname when on a casefolding filesystem
588 normalize the case of a pathname when on a casefolding filesystem
593
589
594 isknown specifies whether the filename came from walking the
590 isknown specifies whether the filename came from walking the
595 disk, to avoid extra filesystem access.
591 disk, to avoid extra filesystem access.
596
592
597 If ignoremissing is True, missing path are returned
593 If ignoremissing is True, missing path are returned
598 unchanged. Otherwise, we try harder to normalize possibly
594 unchanged. Otherwise, we try harder to normalize possibly
599 existing path components.
595 existing path components.
600
596
601 The normalized case is determined based on the following precedence:
597 The normalized case is determined based on the following precedence:
602
598
603 - version of name already stored in the dirstate
599 - version of name already stored in the dirstate
604 - version of name stored on disk
600 - version of name stored on disk
605 - version provided via command arguments
601 - version provided via command arguments
606 '''
602 '''
607
603
608 if self._checkcase:
604 if self._checkcase:
609 return self._normalize(path, isknown, ignoremissing)
605 return self._normalize(path, isknown, ignoremissing)
610 return path
606 return path
611
607
612 def clear(self):
608 def clear(self):
613 self._map = dirstatemap(self._ui, self._opener, self._root)
609 self._map = dirstatemap(self._ui, self._opener, self._root)
614 if "_dirs" in self.__dict__:
610 if "_dirs" in self.__dict__:
615 delattr(self, "_dirs")
611 delattr(self, "_dirs")
616 self._map.setparents(nullid, nullid)
612 self._map.setparents(nullid, nullid)
617 self._lastnormaltime = 0
613 self._lastnormaltime = 0
618 self._updatedfiles.clear()
614 self._updatedfiles.clear()
619 self._dirty = True
615 self._dirty = True
620
616
621 def rebuild(self, parent, allfiles, changedfiles=None):
617 def rebuild(self, parent, allfiles, changedfiles=None):
622 if changedfiles is None:
618 if changedfiles is None:
623 # Rebuild entire dirstate
619 # Rebuild entire dirstate
624 changedfiles = allfiles
620 changedfiles = allfiles
625 lastnormaltime = self._lastnormaltime
621 lastnormaltime = self._lastnormaltime
626 self.clear()
622 self.clear()
627 self._lastnormaltime = lastnormaltime
623 self._lastnormaltime = lastnormaltime
628
624
629 if self._origpl is None:
625 if self._origpl is None:
630 self._origpl = self._pl
626 self._origpl = self._pl
631 self._map.setparents(parent, nullid)
627 self._map.setparents(parent, nullid)
632 for f in changedfiles:
628 for f in changedfiles:
633 if f in allfiles:
629 if f in allfiles:
634 self.normallookup(f)
630 self.normallookup(f)
635 else:
631 else:
636 self.drop(f)
632 self.drop(f)
637
633
638 self._dirty = True
634 self._dirty = True
639
635
640 def identity(self):
636 def identity(self):
641 '''Return identity of dirstate itself to detect changing in storage
637 '''Return identity of dirstate itself to detect changing in storage
642
638
643 If identity of previous dirstate is equal to this, writing
639 If identity of previous dirstate is equal to this, writing
644 changes based on the former dirstate out can keep consistency.
640 changes based on the former dirstate out can keep consistency.
645 '''
641 '''
646 return self._map.identity
642 return self._map.identity
647
643
648 def write(self, tr):
644 def write(self, tr):
649 if not self._dirty:
645 if not self._dirty:
650 return
646 return
651
647
652 filename = self._filename
648 filename = self._filename
653 if tr:
649 if tr:
654 # 'dirstate.write()' is not only for writing in-memory
650 # 'dirstate.write()' is not only for writing in-memory
655 # changes out, but also for dropping ambiguous timestamp.
651 # changes out, but also for dropping ambiguous timestamp.
656 # delayed writing re-raise "ambiguous timestamp issue".
652 # delayed writing re-raise "ambiguous timestamp issue".
657 # See also the wiki page below for detail:
653 # See also the wiki page below for detail:
658 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
654 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
659
655
660 # emulate dropping timestamp in 'parsers.pack_dirstate'
656 # emulate dropping timestamp in 'parsers.pack_dirstate'
661 now = _getfsnow(self._opener)
657 now = _getfsnow(self._opener)
662 dmap = self._map
658 dmap = self._map
663 for f in self._updatedfiles:
659 for f in self._updatedfiles:
664 e = dmap.get(f)
660 e = dmap.get(f)
665 if e is not None and e[0] == 'n' and e[3] == now:
661 if e is not None and e[0] == 'n' and e[3] == now:
666 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
662 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
667 self._map.nonnormalset.add(f)
663 self._map.nonnormalset.add(f)
668
664
669 # emulate that all 'dirstate.normal' results are written out
665 # emulate that all 'dirstate.normal' results are written out
670 self._lastnormaltime = 0
666 self._lastnormaltime = 0
671 self._updatedfiles.clear()
667 self._updatedfiles.clear()
672
668
673 # delay writing in-memory changes out
669 # delay writing in-memory changes out
674 tr.addfilegenerator('dirstate', (self._filename,),
670 tr.addfilegenerator('dirstate', (self._filename,),
675 self._writedirstate, location='plain')
671 self._writedirstate, location='plain')
676 return
672 return
677
673
678 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
674 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
679 self._writedirstate(st)
675 self._writedirstate(st)
680
676
681 def addparentchangecallback(self, category, callback):
677 def addparentchangecallback(self, category, callback):
682 """add a callback to be called when the wd parents are changed
678 """add a callback to be called when the wd parents are changed
683
679
684 Callback will be called with the following arguments:
680 Callback will be called with the following arguments:
685 dirstate, (oldp1, oldp2), (newp1, newp2)
681 dirstate, (oldp1, oldp2), (newp1, newp2)
686
682
687 Category is a unique identifier to allow overwriting an old callback
683 Category is a unique identifier to allow overwriting an old callback
688 with a newer callback.
684 with a newer callback.
689 """
685 """
690 self._plchangecallbacks[category] = callback
686 self._plchangecallbacks[category] = callback
691
687
692 def _writedirstate(self, st):
688 def _writedirstate(self, st):
693 # notify callbacks about parents change
689 # notify callbacks about parents change
694 if self._origpl is not None and self._origpl != self._pl:
690 if self._origpl is not None and self._origpl != self._pl:
695 for c, callback in sorted(self._plchangecallbacks.iteritems()):
691 for c, callback in sorted(self._plchangecallbacks.iteritems()):
696 callback(self, self._origpl, self._pl)
692 callback(self, self._origpl, self._pl)
697 self._origpl = None
693 self._origpl = None
698 # use the modification time of the newly created temporary file as the
694 # use the modification time of the newly created temporary file as the
699 # filesystem's notion of 'now'
695 # filesystem's notion of 'now'
700 now = util.fstat(st).st_mtime & _rangemask
696 now = util.fstat(st).st_mtime & _rangemask
701
697
702 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
698 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
703 # timestamp of each entries in dirstate, because of 'now > mtime'
699 # timestamp of each entries in dirstate, because of 'now > mtime'
704 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
700 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
705 if delaywrite > 0:
701 if delaywrite > 0:
706 # do we have any files to delay for?
702 # do we have any files to delay for?
707 for f, e in self._map.iteritems():
703 for f, e in self._map.iteritems():
708 if e[0] == 'n' and e[3] == now:
704 if e[0] == 'n' and e[3] == now:
709 import time # to avoid useless import
705 import time # to avoid useless import
710 # rather than sleep n seconds, sleep until the next
706 # rather than sleep n seconds, sleep until the next
711 # multiple of n seconds
707 # multiple of n seconds
712 clock = time.time()
708 clock = time.time()
713 start = int(clock) - (int(clock) % delaywrite)
709 start = int(clock) - (int(clock) % delaywrite)
714 end = start + delaywrite
710 end = start + delaywrite
715 time.sleep(end - clock)
711 time.sleep(end - clock)
716 now = end # trust our estimate that the end is near now
712 now = end # trust our estimate that the end is near now
717 break
713 break
718
714
719 self._map.write(st, now)
715 self._map.write(st, now)
720 self._lastnormaltime = 0
716 self._lastnormaltime = 0
721 self._dirty = False
717 self._dirty = False
722
718
723 def _dirignore(self, f):
719 def _dirignore(self, f):
724 if f == '.':
720 if f == '.':
725 return False
721 return False
726 if self._ignore(f):
722 if self._ignore(f):
727 return True
723 return True
728 for p in util.finddirs(f):
724 for p in util.finddirs(f):
729 if self._ignore(p):
725 if self._ignore(p):
730 return True
726 return True
731 return False
727 return False
732
728
733 def _ignorefiles(self):
729 def _ignorefiles(self):
734 files = []
730 files = []
735 if os.path.exists(self._join('.hgignore')):
731 if os.path.exists(self._join('.hgignore')):
736 files.append(self._join('.hgignore'))
732 files.append(self._join('.hgignore'))
737 for name, path in self._ui.configitems("ui"):
733 for name, path in self._ui.configitems("ui"):
738 if name == 'ignore' or name.startswith('ignore.'):
734 if name == 'ignore' or name.startswith('ignore.'):
739 # we need to use os.path.join here rather than self._join
735 # we need to use os.path.join here rather than self._join
740 # because path is arbitrary and user-specified
736 # because path is arbitrary and user-specified
741 files.append(os.path.join(self._rootdir, util.expandpath(path)))
737 files.append(os.path.join(self._rootdir, util.expandpath(path)))
742 return files
738 return files
743
739
744 def _ignorefileandline(self, f):
740 def _ignorefileandline(self, f):
745 files = collections.deque(self._ignorefiles())
741 files = collections.deque(self._ignorefiles())
746 visited = set()
742 visited = set()
747 while files:
743 while files:
748 i = files.popleft()
744 i = files.popleft()
749 patterns = matchmod.readpatternfile(i, self._ui.warn,
745 patterns = matchmod.readpatternfile(i, self._ui.warn,
750 sourceinfo=True)
746 sourceinfo=True)
751 for pattern, lineno, line in patterns:
747 for pattern, lineno, line in patterns:
752 kind, p = matchmod._patsplit(pattern, 'glob')
748 kind, p = matchmod._patsplit(pattern, 'glob')
753 if kind == "subinclude":
749 if kind == "subinclude":
754 if p not in visited:
750 if p not in visited:
755 files.append(p)
751 files.append(p)
756 continue
752 continue
757 m = matchmod.match(self._root, '', [], [pattern],
753 m = matchmod.match(self._root, '', [], [pattern],
758 warn=self._ui.warn)
754 warn=self._ui.warn)
759 if m(f):
755 if m(f):
760 return (i, lineno, line)
756 return (i, lineno, line)
761 visited.add(i)
757 visited.add(i)
762 return (None, -1, "")
758 return (None, -1, "")
763
759
764 def _walkexplicit(self, match, subrepos):
760 def _walkexplicit(self, match, subrepos):
765 '''Get stat data about the files explicitly specified by match.
761 '''Get stat data about the files explicitly specified by match.
766
762
767 Return a triple (results, dirsfound, dirsnotfound).
763 Return a triple (results, dirsfound, dirsnotfound).
768 - results is a mapping from filename to stat result. It also contains
764 - results is a mapping from filename to stat result. It also contains
769 listings mapping subrepos and .hg to None.
765 listings mapping subrepos and .hg to None.
770 - dirsfound is a list of files found to be directories.
766 - dirsfound is a list of files found to be directories.
771 - dirsnotfound is a list of files that the dirstate thinks are
767 - dirsnotfound is a list of files that the dirstate thinks are
772 directories and that were not found.'''
768 directories and that were not found.'''
773
769
774 def badtype(mode):
770 def badtype(mode):
775 kind = _('unknown')
771 kind = _('unknown')
776 if stat.S_ISCHR(mode):
772 if stat.S_ISCHR(mode):
777 kind = _('character device')
773 kind = _('character device')
778 elif stat.S_ISBLK(mode):
774 elif stat.S_ISBLK(mode):
779 kind = _('block device')
775 kind = _('block device')
780 elif stat.S_ISFIFO(mode):
776 elif stat.S_ISFIFO(mode):
781 kind = _('fifo')
777 kind = _('fifo')
782 elif stat.S_ISSOCK(mode):
778 elif stat.S_ISSOCK(mode):
783 kind = _('socket')
779 kind = _('socket')
784 elif stat.S_ISDIR(mode):
780 elif stat.S_ISDIR(mode):
785 kind = _('directory')
781 kind = _('directory')
786 return _('unsupported file type (type is %s)') % kind
782 return _('unsupported file type (type is %s)') % kind
787
783
788 matchedir = match.explicitdir
784 matchedir = match.explicitdir
789 badfn = match.bad
785 badfn = match.bad
790 dmap = self._map
786 dmap = self._map
791 lstat = os.lstat
787 lstat = os.lstat
792 getkind = stat.S_IFMT
788 getkind = stat.S_IFMT
793 dirkind = stat.S_IFDIR
789 dirkind = stat.S_IFDIR
794 regkind = stat.S_IFREG
790 regkind = stat.S_IFREG
795 lnkkind = stat.S_IFLNK
791 lnkkind = stat.S_IFLNK
796 join = self._join
792 join = self._join
797 dirsfound = []
793 dirsfound = []
798 foundadd = dirsfound.append
794 foundadd = dirsfound.append
799 dirsnotfound = []
795 dirsnotfound = []
800 notfoundadd = dirsnotfound.append
796 notfoundadd = dirsnotfound.append
801
797
802 if not match.isexact() and self._checkcase:
798 if not match.isexact() and self._checkcase:
803 normalize = self._normalize
799 normalize = self._normalize
804 else:
800 else:
805 normalize = None
801 normalize = None
806
802
807 files = sorted(match.files())
803 files = sorted(match.files())
808 subrepos.sort()
804 subrepos.sort()
809 i, j = 0, 0
805 i, j = 0, 0
810 while i < len(files) and j < len(subrepos):
806 while i < len(files) and j < len(subrepos):
811 subpath = subrepos[j] + "/"
807 subpath = subrepos[j] + "/"
812 if files[i] < subpath:
808 if files[i] < subpath:
813 i += 1
809 i += 1
814 continue
810 continue
815 while i < len(files) and files[i].startswith(subpath):
811 while i < len(files) and files[i].startswith(subpath):
816 del files[i]
812 del files[i]
817 j += 1
813 j += 1
818
814
819 if not files or '.' in files:
815 if not files or '.' in files:
820 files = ['.']
816 files = ['.']
821 results = dict.fromkeys(subrepos)
817 results = dict.fromkeys(subrepos)
822 results['.hg'] = None
818 results['.hg'] = None
823
819
824 alldirs = None
820 alldirs = None
825 for ff in files:
821 for ff in files:
826 # constructing the foldmap is expensive, so don't do it for the
822 # constructing the foldmap is expensive, so don't do it for the
827 # common case where files is ['.']
823 # common case where files is ['.']
828 if normalize and ff != '.':
824 if normalize and ff != '.':
829 nf = normalize(ff, False, True)
825 nf = normalize(ff, False, True)
830 else:
826 else:
831 nf = ff
827 nf = ff
832 if nf in results:
828 if nf in results:
833 continue
829 continue
834
830
835 try:
831 try:
836 st = lstat(join(nf))
832 st = lstat(join(nf))
837 kind = getkind(st.st_mode)
833 kind = getkind(st.st_mode)
838 if kind == dirkind:
834 if kind == dirkind:
839 if nf in dmap:
835 if nf in dmap:
840 # file replaced by dir on disk but still in dirstate
836 # file replaced by dir on disk but still in dirstate
841 results[nf] = None
837 results[nf] = None
842 if matchedir:
838 if matchedir:
843 matchedir(nf)
839 matchedir(nf)
844 foundadd((nf, ff))
840 foundadd((nf, ff))
845 elif kind == regkind or kind == lnkkind:
841 elif kind == regkind or kind == lnkkind:
846 results[nf] = st
842 results[nf] = st
847 else:
843 else:
848 badfn(ff, badtype(kind))
844 badfn(ff, badtype(kind))
849 if nf in dmap:
845 if nf in dmap:
850 results[nf] = None
846 results[nf] = None
851 except OSError as inst: # nf not found on disk - it is dirstate only
847 except OSError as inst: # nf not found on disk - it is dirstate only
852 if nf in dmap: # does it exactly match a missing file?
848 if nf in dmap: # does it exactly match a missing file?
853 results[nf] = None
849 results[nf] = None
854 else: # does it match a missing directory?
850 else: # does it match a missing directory?
855 if alldirs is None:
851 if alldirs is None:
856 alldirs = util.dirs(dmap._map)
852 alldirs = util.dirs(dmap._map)
857 if nf in alldirs:
853 if nf in alldirs:
858 if matchedir:
854 if matchedir:
859 matchedir(nf)
855 matchedir(nf)
860 notfoundadd(nf)
856 notfoundadd(nf)
861 else:
857 else:
862 badfn(ff, encoding.strtolocal(inst.strerror))
858 badfn(ff, encoding.strtolocal(inst.strerror))
863
859
864 # Case insensitive filesystems cannot rely on lstat() failing to detect
860 # Case insensitive filesystems cannot rely on lstat() failing to detect
865 # a case-only rename. Prune the stat object for any file that does not
861 # a case-only rename. Prune the stat object for any file that does not
866 # match the case in the filesystem, if there are multiple files that
862 # match the case in the filesystem, if there are multiple files that
867 # normalize to the same path.
863 # normalize to the same path.
868 if match.isexact() and self._checkcase:
864 if match.isexact() and self._checkcase:
869 normed = {}
865 normed = {}
870
866
871 for f, st in results.iteritems():
867 for f, st in results.iteritems():
872 if st is None:
868 if st is None:
873 continue
869 continue
874
870
875 nc = util.normcase(f)
871 nc = util.normcase(f)
876 paths = normed.get(nc)
872 paths = normed.get(nc)
877
873
878 if paths is None:
874 if paths is None:
879 paths = set()
875 paths = set()
880 normed[nc] = paths
876 normed[nc] = paths
881
877
882 paths.add(f)
878 paths.add(f)
883
879
884 for norm, paths in normed.iteritems():
880 for norm, paths in normed.iteritems():
885 if len(paths) > 1:
881 if len(paths) > 1:
886 for path in paths:
882 for path in paths:
887 folded = self._discoverpath(path, norm, True, None,
883 folded = self._discoverpath(path, norm, True, None,
888 self._dirfoldmap)
884 self._dirfoldmap)
889 if path != folded:
885 if path != folded:
890 results[path] = None
886 results[path] = None
891
887
892 return results, dirsfound, dirsnotfound
888 return results, dirsfound, dirsnotfound
893
889
894 def walk(self, match, subrepos, unknown, ignored, full=True):
890 def walk(self, match, subrepos, unknown, ignored, full=True):
895 '''
891 '''
896 Walk recursively through the directory tree, finding all files
892 Walk recursively through the directory tree, finding all files
897 matched by match.
893 matched by match.
898
894
899 If full is False, maybe skip some known-clean files.
895 If full is False, maybe skip some known-clean files.
900
896
901 Return a dict mapping filename to stat-like object (either
897 Return a dict mapping filename to stat-like object (either
902 mercurial.osutil.stat instance or return value of os.stat()).
898 mercurial.osutil.stat instance or return value of os.stat()).
903
899
904 '''
900 '''
905 # full is a flag that extensions that hook into walk can use -- this
901 # full is a flag that extensions that hook into walk can use -- this
906 # implementation doesn't use it at all. This satisfies the contract
902 # implementation doesn't use it at all. This satisfies the contract
907 # because we only guarantee a "maybe".
903 # because we only guarantee a "maybe".
908
904
909 if ignored:
905 if ignored:
910 ignore = util.never
906 ignore = util.never
911 dirignore = util.never
907 dirignore = util.never
912 elif unknown:
908 elif unknown:
913 ignore = self._ignore
909 ignore = self._ignore
914 dirignore = self._dirignore
910 dirignore = self._dirignore
915 else:
911 else:
916 # if not unknown and not ignored, drop dir recursion and step 2
912 # if not unknown and not ignored, drop dir recursion and step 2
917 ignore = util.always
913 ignore = util.always
918 dirignore = util.always
914 dirignore = util.always
919
915
920 matchfn = match.matchfn
916 matchfn = match.matchfn
921 matchalways = match.always()
917 matchalways = match.always()
922 matchtdir = match.traversedir
918 matchtdir = match.traversedir
923 dmap = self._map
919 dmap = self._map
924 listdir = util.listdir
920 listdir = util.listdir
925 lstat = os.lstat
921 lstat = os.lstat
926 dirkind = stat.S_IFDIR
922 dirkind = stat.S_IFDIR
927 regkind = stat.S_IFREG
923 regkind = stat.S_IFREG
928 lnkkind = stat.S_IFLNK
924 lnkkind = stat.S_IFLNK
929 join = self._join
925 join = self._join
930
926
931 exact = skipstep3 = False
927 exact = skipstep3 = False
932 if match.isexact(): # match.exact
928 if match.isexact(): # match.exact
933 exact = True
929 exact = True
934 dirignore = util.always # skip step 2
930 dirignore = util.always # skip step 2
935 elif match.prefix(): # match.match, no patterns
931 elif match.prefix(): # match.match, no patterns
936 skipstep3 = True
932 skipstep3 = True
937
933
938 if not exact and self._checkcase:
934 if not exact and self._checkcase:
939 normalize = self._normalize
935 normalize = self._normalize
940 normalizefile = self._normalizefile
936 normalizefile = self._normalizefile
941 skipstep3 = False
937 skipstep3 = False
942 else:
938 else:
943 normalize = self._normalize
939 normalize = self._normalize
944 normalizefile = None
940 normalizefile = None
945
941
946 # step 1: find all explicit files
942 # step 1: find all explicit files
947 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
943 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
948
944
949 skipstep3 = skipstep3 and not (work or dirsnotfound)
945 skipstep3 = skipstep3 and not (work or dirsnotfound)
950 work = [d for d in work if not dirignore(d[0])]
946 work = [d for d in work if not dirignore(d[0])]
951
947
952 # step 2: visit subdirectories
948 # step 2: visit subdirectories
953 def traverse(work, alreadynormed):
949 def traverse(work, alreadynormed):
954 wadd = work.append
950 wadd = work.append
955 while work:
951 while work:
956 nd = work.pop()
952 nd = work.pop()
957 if not match.visitdir(nd):
953 if not match.visitdir(nd):
958 continue
954 continue
959 skip = None
955 skip = None
960 if nd == '.':
956 if nd == '.':
961 nd = ''
957 nd = ''
962 else:
958 else:
963 skip = '.hg'
959 skip = '.hg'
964 try:
960 try:
965 entries = listdir(join(nd), stat=True, skip=skip)
961 entries = listdir(join(nd), stat=True, skip=skip)
966 except OSError as inst:
962 except OSError as inst:
967 if inst.errno in (errno.EACCES, errno.ENOENT):
963 if inst.errno in (errno.EACCES, errno.ENOENT):
968 match.bad(self.pathto(nd),
964 match.bad(self.pathto(nd),
969 encoding.strtolocal(inst.strerror))
965 encoding.strtolocal(inst.strerror))
970 continue
966 continue
971 raise
967 raise
972 for f, kind, st in entries:
968 for f, kind, st in entries:
973 if normalizefile:
969 if normalizefile:
974 # even though f might be a directory, we're only
970 # even though f might be a directory, we're only
975 # interested in comparing it to files currently in the
971 # interested in comparing it to files currently in the
976 # dmap -- therefore normalizefile is enough
972 # dmap -- therefore normalizefile is enough
977 nf = normalizefile(nd and (nd + "/" + f) or f, True,
973 nf = normalizefile(nd and (nd + "/" + f) or f, True,
978 True)
974 True)
979 else:
975 else:
980 nf = nd and (nd + "/" + f) or f
976 nf = nd and (nd + "/" + f) or f
981 if nf not in results:
977 if nf not in results:
982 if kind == dirkind:
978 if kind == dirkind:
983 if not ignore(nf):
979 if not ignore(nf):
984 if matchtdir:
980 if matchtdir:
985 matchtdir(nf)
981 matchtdir(nf)
986 wadd(nf)
982 wadd(nf)
987 if nf in dmap and (matchalways or matchfn(nf)):
983 if nf in dmap and (matchalways or matchfn(nf)):
988 results[nf] = None
984 results[nf] = None
989 elif kind == regkind or kind == lnkkind:
985 elif kind == regkind or kind == lnkkind:
990 if nf in dmap:
986 if nf in dmap:
991 if matchalways or matchfn(nf):
987 if matchalways or matchfn(nf):
992 results[nf] = st
988 results[nf] = st
993 elif ((matchalways or matchfn(nf))
989 elif ((matchalways or matchfn(nf))
994 and not ignore(nf)):
990 and not ignore(nf)):
995 # unknown file -- normalize if necessary
991 # unknown file -- normalize if necessary
996 if not alreadynormed:
992 if not alreadynormed:
997 nf = normalize(nf, False, True)
993 nf = normalize(nf, False, True)
998 results[nf] = st
994 results[nf] = st
999 elif nf in dmap and (matchalways or matchfn(nf)):
995 elif nf in dmap and (matchalways or matchfn(nf)):
1000 results[nf] = None
996 results[nf] = None
1001
997
1002 for nd, d in work:
998 for nd, d in work:
1003 # alreadynormed means that processwork doesn't have to do any
999 # alreadynormed means that processwork doesn't have to do any
1004 # expensive directory normalization
1000 # expensive directory normalization
1005 alreadynormed = not normalize or nd == d
1001 alreadynormed = not normalize or nd == d
1006 traverse([d], alreadynormed)
1002 traverse([d], alreadynormed)
1007
1003
1008 for s in subrepos:
1004 for s in subrepos:
1009 del results[s]
1005 del results[s]
1010 del results['.hg']
1006 del results['.hg']
1011
1007
1012 # step 3: visit remaining files from dmap
1008 # step 3: visit remaining files from dmap
1013 if not skipstep3 and not exact:
1009 if not skipstep3 and not exact:
1014 # If a dmap file is not in results yet, it was either
1010 # If a dmap file is not in results yet, it was either
1015 # a) not matching matchfn b) ignored, c) missing, or d) under a
1011 # a) not matching matchfn b) ignored, c) missing, or d) under a
1016 # symlink directory.
1012 # symlink directory.
1017 if not results and matchalways:
1013 if not results and matchalways:
1018 visit = [f for f in dmap]
1014 visit = [f for f in dmap]
1019 else:
1015 else:
1020 visit = [f for f in dmap if f not in results and matchfn(f)]
1016 visit = [f for f in dmap if f not in results and matchfn(f)]
1021 visit.sort()
1017 visit.sort()
1022
1018
1023 if unknown:
1019 if unknown:
1024 # unknown == True means we walked all dirs under the roots
1020 # unknown == True means we walked all dirs under the roots
1025 # that wasn't ignored, and everything that matched was stat'ed
1021 # that wasn't ignored, and everything that matched was stat'ed
1026 # and is already in results.
1022 # and is already in results.
1027 # The rest must thus be ignored or under a symlink.
1023 # The rest must thus be ignored or under a symlink.
1028 audit_path = pathutil.pathauditor(self._root, cached=True)
1024 audit_path = pathutil.pathauditor(self._root, cached=True)
1029
1025
1030 for nf in iter(visit):
1026 for nf in iter(visit):
1031 # If a stat for the same file was already added with a
1027 # If a stat for the same file was already added with a
1032 # different case, don't add one for this, since that would
1028 # different case, don't add one for this, since that would
1033 # make it appear as if the file exists under both names
1029 # make it appear as if the file exists under both names
1034 # on disk.
1030 # on disk.
1035 if (normalizefile and
1031 if (normalizefile and
1036 normalizefile(nf, True, True) in results):
1032 normalizefile(nf, True, True) in results):
1037 results[nf] = None
1033 results[nf] = None
1038 # Report ignored items in the dmap as long as they are not
1034 # Report ignored items in the dmap as long as they are not
1039 # under a symlink directory.
1035 # under a symlink directory.
1040 elif audit_path.check(nf):
1036 elif audit_path.check(nf):
1041 try:
1037 try:
1042 results[nf] = lstat(join(nf))
1038 results[nf] = lstat(join(nf))
1043 # file was just ignored, no links, and exists
1039 # file was just ignored, no links, and exists
1044 except OSError:
1040 except OSError:
1045 # file doesn't exist
1041 # file doesn't exist
1046 results[nf] = None
1042 results[nf] = None
1047 else:
1043 else:
1048 # It's either missing or under a symlink directory
1044 # It's either missing or under a symlink directory
1049 # which we in this case report as missing
1045 # which we in this case report as missing
1050 results[nf] = None
1046 results[nf] = None
1051 else:
1047 else:
1052 # We may not have walked the full directory tree above,
1048 # We may not have walked the full directory tree above,
1053 # so stat and check everything we missed.
1049 # so stat and check everything we missed.
1054 iv = iter(visit)
1050 iv = iter(visit)
1055 for st in util.statfiles([join(i) for i in visit]):
1051 for st in util.statfiles([join(i) for i in visit]):
1056 results[next(iv)] = st
1052 results[next(iv)] = st
1057 return results
1053 return results
1058
1054
1059 def status(self, match, subrepos, ignored, clean, unknown):
1055 def status(self, match, subrepos, ignored, clean, unknown):
1060 '''Determine the status of the working copy relative to the
1056 '''Determine the status of the working copy relative to the
1061 dirstate and return a pair of (unsure, status), where status is of type
1057 dirstate and return a pair of (unsure, status), where status is of type
1062 scmutil.status and:
1058 scmutil.status and:
1063
1059
1064 unsure:
1060 unsure:
1065 files that might have been modified since the dirstate was
1061 files that might have been modified since the dirstate was
1066 written, but need to be read to be sure (size is the same
1062 written, but need to be read to be sure (size is the same
1067 but mtime differs)
1063 but mtime differs)
1068 status.modified:
1064 status.modified:
1069 files that have definitely been modified since the dirstate
1065 files that have definitely been modified since the dirstate
1070 was written (different size or mode)
1066 was written (different size or mode)
1071 status.clean:
1067 status.clean:
1072 files that have definitely not been modified since the
1068 files that have definitely not been modified since the
1073 dirstate was written
1069 dirstate was written
1074 '''
1070 '''
1075 listignored, listclean, listunknown = ignored, clean, unknown
1071 listignored, listclean, listunknown = ignored, clean, unknown
1076 lookup, modified, added, unknown, ignored = [], [], [], [], []
1072 lookup, modified, added, unknown, ignored = [], [], [], [], []
1077 removed, deleted, clean = [], [], []
1073 removed, deleted, clean = [], [], []
1078
1074
1079 dmap = self._map
1075 dmap = self._map
1080 ladd = lookup.append # aka "unsure"
1076 ladd = lookup.append # aka "unsure"
1081 madd = modified.append
1077 madd = modified.append
1082 aadd = added.append
1078 aadd = added.append
1083 uadd = unknown.append
1079 uadd = unknown.append
1084 iadd = ignored.append
1080 iadd = ignored.append
1085 radd = removed.append
1081 radd = removed.append
1086 dadd = deleted.append
1082 dadd = deleted.append
1087 cadd = clean.append
1083 cadd = clean.append
1088 mexact = match.exact
1084 mexact = match.exact
1089 dirignore = self._dirignore
1085 dirignore = self._dirignore
1090 checkexec = self._checkexec
1086 checkexec = self._checkexec
1091 copymap = self._map.copymap
1087 copymap = self._map.copymap
1092 lastnormaltime = self._lastnormaltime
1088 lastnormaltime = self._lastnormaltime
1093
1089
1094 # We need to do full walks when either
1090 # We need to do full walks when either
1095 # - we're listing all clean files, or
1091 # - we're listing all clean files, or
1096 # - match.traversedir does something, because match.traversedir should
1092 # - match.traversedir does something, because match.traversedir should
1097 # be called for every dir in the working dir
1093 # be called for every dir in the working dir
1098 full = listclean or match.traversedir is not None
1094 full = listclean or match.traversedir is not None
1099 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1095 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1100 full=full).iteritems():
1096 full=full).iteritems():
1101 if fn not in dmap:
1097 if fn not in dmap:
1102 if (listignored or mexact(fn)) and dirignore(fn):
1098 if (listignored or mexact(fn)) and dirignore(fn):
1103 if listignored:
1099 if listignored:
1104 iadd(fn)
1100 iadd(fn)
1105 else:
1101 else:
1106 uadd(fn)
1102 uadd(fn)
1107 continue
1103 continue
1108
1104
1109 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1105 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1110 # written like that for performance reasons. dmap[fn] is not a
1106 # written like that for performance reasons. dmap[fn] is not a
1111 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1107 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1112 # opcode has fast paths when the value to be unpacked is a tuple or
1108 # opcode has fast paths when the value to be unpacked is a tuple or
1113 # a list, but falls back to creating a full-fledged iterator in
1109 # a list, but falls back to creating a full-fledged iterator in
1114 # general. That is much slower than simply accessing and storing the
1110 # general. That is much slower than simply accessing and storing the
1115 # tuple members one by one.
1111 # tuple members one by one.
1116 t = dmap[fn]
1112 t = dmap[fn]
1117 state = t[0]
1113 state = t[0]
1118 mode = t[1]
1114 mode = t[1]
1119 size = t[2]
1115 size = t[2]
1120 time = t[3]
1116 time = t[3]
1121
1117
1122 if not st and state in "nma":
1118 if not st and state in "nma":
1123 dadd(fn)
1119 dadd(fn)
1124 elif state == 'n':
1120 elif state == 'n':
1125 if (size >= 0 and
1121 if (size >= 0 and
1126 ((size != st.st_size and size != st.st_size & _rangemask)
1122 ((size != st.st_size and size != st.st_size & _rangemask)
1127 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1123 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1128 or size == -2 # other parent
1124 or size == -2 # other parent
1129 or fn in copymap):
1125 or fn in copymap):
1130 madd(fn)
1126 madd(fn)
1131 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1127 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1132 ladd(fn)
1128 ladd(fn)
1133 elif st.st_mtime == lastnormaltime:
1129 elif st.st_mtime == lastnormaltime:
1134 # fn may have just been marked as normal and it may have
1130 # fn may have just been marked as normal and it may have
1135 # changed in the same second without changing its size.
1131 # changed in the same second without changing its size.
1136 # This can happen if we quickly do multiple commits.
1132 # This can happen if we quickly do multiple commits.
1137 # Force lookup, so we don't miss such a racy file change.
1133 # Force lookup, so we don't miss such a racy file change.
1138 ladd(fn)
1134 ladd(fn)
1139 elif listclean:
1135 elif listclean:
1140 cadd(fn)
1136 cadd(fn)
1141 elif state == 'm':
1137 elif state == 'm':
1142 madd(fn)
1138 madd(fn)
1143 elif state == 'a':
1139 elif state == 'a':
1144 aadd(fn)
1140 aadd(fn)
1145 elif state == 'r':
1141 elif state == 'r':
1146 radd(fn)
1142 radd(fn)
1147
1143
1148 return (lookup, scmutil.status(modified, added, removed, deleted,
1144 return (lookup, scmutil.status(modified, added, removed, deleted,
1149 unknown, ignored, clean))
1145 unknown, ignored, clean))
1150
1146
1151 def matches(self, match):
1147 def matches(self, match):
1152 '''
1148 '''
1153 return files in the dirstate (in whatever state) filtered by match
1149 return files in the dirstate (in whatever state) filtered by match
1154 '''
1150 '''
1155 dmap = self._map
1151 dmap = self._map
1156 if match.always():
1152 if match.always():
1157 return dmap.keys()
1153 return dmap.keys()
1158 files = match.files()
1154 files = match.files()
1159 if match.isexact():
1155 if match.isexact():
1160 # fast path -- filter the other way around, since typically files is
1156 # fast path -- filter the other way around, since typically files is
1161 # much smaller than dmap
1157 # much smaller than dmap
1162 return [f for f in files if f in dmap]
1158 return [f for f in files if f in dmap]
1163 if match.prefix() and all(fn in dmap for fn in files):
1159 if match.prefix() and all(fn in dmap for fn in files):
1164 # fast path -- all the values are known to be files, so just return
1160 # fast path -- all the values are known to be files, so just return
1165 # that
1161 # that
1166 return list(files)
1162 return list(files)
1167 return [f for f in dmap if match(f)]
1163 return [f for f in dmap if match(f)]
1168
1164
1169 def _actualfilename(self, tr):
1165 def _actualfilename(self, tr):
1170 if tr:
1166 if tr:
1171 return self._pendingfilename
1167 return self._pendingfilename
1172 else:
1168 else:
1173 return self._filename
1169 return self._filename
1174
1170
1175 def savebackup(self, tr, backupname):
1171 def savebackup(self, tr, backupname):
1176 '''Save current dirstate into backup file'''
1172 '''Save current dirstate into backup file'''
1177 filename = self._actualfilename(tr)
1173 filename = self._actualfilename(tr)
1178 assert backupname != filename
1174 assert backupname != filename
1179
1175
1180 # use '_writedirstate' instead of 'write' to write changes certainly,
1176 # use '_writedirstate' instead of 'write' to write changes certainly,
1181 # because the latter omits writing out if transaction is running.
1177 # because the latter omits writing out if transaction is running.
1182 # output file will be used to create backup of dirstate at this point.
1178 # output file will be used to create backup of dirstate at this point.
1183 if self._dirty or not self._opener.exists(filename):
1179 if self._dirty or not self._opener.exists(filename):
1184 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1180 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1185 checkambig=True))
1181 checkambig=True))
1186
1182
1187 if tr:
1183 if tr:
1188 # ensure that subsequent tr.writepending returns True for
1184 # ensure that subsequent tr.writepending returns True for
1189 # changes written out above, even if dirstate is never
1185 # changes written out above, even if dirstate is never
1190 # changed after this
1186 # changed after this
1191 tr.addfilegenerator('dirstate', (self._filename,),
1187 tr.addfilegenerator('dirstate', (self._filename,),
1192 self._writedirstate, location='plain')
1188 self._writedirstate, location='plain')
1193
1189
1194 # ensure that pending file written above is unlinked at
1190 # ensure that pending file written above is unlinked at
1195 # failure, even if tr.writepending isn't invoked until the
1191 # failure, even if tr.writepending isn't invoked until the
1196 # end of this transaction
1192 # end of this transaction
1197 tr.registertmp(filename, location='plain')
1193 tr.registertmp(filename, location='plain')
1198
1194
1199 self._opener.tryunlink(backupname)
1195 self._opener.tryunlink(backupname)
1200 # hardlink backup is okay because _writedirstate is always called
1196 # hardlink backup is okay because _writedirstate is always called
1201 # with an "atomictemp=True" file.
1197 # with an "atomictemp=True" file.
1202 util.copyfile(self._opener.join(filename),
1198 util.copyfile(self._opener.join(filename),
1203 self._opener.join(backupname), hardlink=True)
1199 self._opener.join(backupname), hardlink=True)
1204
1200
1205 def restorebackup(self, tr, backupname):
1201 def restorebackup(self, tr, backupname):
1206 '''Restore dirstate by backup file'''
1202 '''Restore dirstate by backup file'''
1207 # this "invalidate()" prevents "wlock.release()" from writing
1203 # this "invalidate()" prevents "wlock.release()" from writing
1208 # changes of dirstate out after restoring from backup file
1204 # changes of dirstate out after restoring from backup file
1209 self.invalidate()
1205 self.invalidate()
1210 filename = self._actualfilename(tr)
1206 filename = self._actualfilename(tr)
1211 self._opener.rename(backupname, filename, checkambig=True)
1207 self._opener.rename(backupname, filename, checkambig=True)
1212
1208
1213 def clearbackup(self, tr, backupname):
1209 def clearbackup(self, tr, backupname):
1214 '''Clear backup file'''
1210 '''Clear backup file'''
1215 self._opener.unlink(backupname)
1211 self._opener.unlink(backupname)
1216
1212
1217 class dirstatemap(object):
1213 class dirstatemap(object):
1218 def __init__(self, ui, opener, root):
1214 def __init__(self, ui, opener, root):
1219 self._ui = ui
1215 self._ui = ui
1220 self._opener = opener
1216 self._opener = opener
1221 self._root = root
1217 self._root = root
1222 self._filename = 'dirstate'
1218 self._filename = 'dirstate'
1223
1219
1224 self._map = {}
1220 self._map = {}
1225 self.copymap = {}
1221 self.copymap = {}
1226 self._parents = None
1222 self._parents = None
1227 self._dirtyparents = False
1223 self._dirtyparents = False
1228
1224
1229 # for consistent view between _pl() and _read() invocations
1225 # for consistent view between _pl() and _read() invocations
1230 self._pendingmode = None
1226 self._pendingmode = None
1231
1227
1232 def iteritems(self):
1228 def iteritems(self):
1233 return self._map.iteritems()
1229 return self._map.iteritems()
1234
1230
1235 def __len__(self):
1231 def __len__(self):
1236 return len(self._map)
1232 return len(self._map)
1237
1233
1238 def __iter__(self):
1234 def __iter__(self):
1239 return iter(self._map)
1235 return iter(self._map)
1240
1236
1241 def get(self, key, default=None):
1237 def get(self, key, default=None):
1242 return self._map.get(key, default)
1238 return self._map.get(key, default)
1243
1239
1244 def __contains__(self, key):
1240 def __contains__(self, key):
1245 return key in self._map
1241 return key in self._map
1246
1242
1247 def __setitem__(self, key, value):
1243 def __setitem__(self, key, value):
1248 self._map[key] = value
1244 self._map[key] = value
1249
1245
1250 def __getitem__(self, key):
1246 def __getitem__(self, key):
1251 return self._map[key]
1247 return self._map[key]
1252
1248
1253 def __delitem__(self, key):
1249 def __delitem__(self, key):
1254 del self._map[key]
1250 del self._map[key]
1255
1251
1256 def keys(self):
1252 def keys(self):
1257 return self._map.keys()
1253 return self._map.keys()
1258
1254
1259 def nonnormalentries(self):
1255 def nonnormalentries(self):
1260 '''Compute the nonnormal dirstate entries from the dmap'''
1256 '''Compute the nonnormal dirstate entries from the dmap'''
1261 try:
1257 try:
1262 return parsers.nonnormalotherparententries(self._map)
1258 return parsers.nonnormalotherparententries(self._map)
1263 except AttributeError:
1259 except AttributeError:
1264 nonnorm = set()
1260 nonnorm = set()
1265 otherparent = set()
1261 otherparent = set()
1266 for fname, e in self._map.iteritems():
1262 for fname, e in self._map.iteritems():
1267 if e[0] != 'n' or e[3] == -1:
1263 if e[0] != 'n' or e[3] == -1:
1268 nonnorm.add(fname)
1264 nonnorm.add(fname)
1269 if e[0] == 'n' and e[2] == -2:
1265 if e[0] == 'n' and e[2] == -2:
1270 otherparent.add(fname)
1266 otherparent.add(fname)
1271 return nonnorm, otherparent
1267 return nonnorm, otherparent
1272
1268
1269 @propertycache
1273 def filefoldmap(self):
1270 def filefoldmap(self):
1274 """Returns a dictionary mapping normalized case paths to their
1271 """Returns a dictionary mapping normalized case paths to their
1275 non-normalized versions.
1272 non-normalized versions.
1276 """
1273 """
1277 try:
1274 try:
1278 makefilefoldmap = parsers.make_file_foldmap
1275 makefilefoldmap = parsers.make_file_foldmap
1279 except AttributeError:
1276 except AttributeError:
1280 pass
1277 pass
1281 else:
1278 else:
1282 return makefilefoldmap(self._map, util.normcasespec,
1279 return makefilefoldmap(self._map, util.normcasespec,
1283 util.normcasefallback)
1280 util.normcasefallback)
1284
1281
1285 f = {}
1282 f = {}
1286 normcase = util.normcase
1283 normcase = util.normcase
1287 for name, s in self._map.iteritems():
1284 for name, s in self._map.iteritems():
1288 if s[0] != 'r':
1285 if s[0] != 'r':
1289 f[normcase(name)] = name
1286 f[normcase(name)] = name
1290 f['.'] = '.' # prevents useless util.fspath() invocation
1287 f['.'] = '.' # prevents useless util.fspath() invocation
1291 return f
1288 return f
1292
1289
1293 def dirs(self):
1290 def dirs(self):
1294 """Returns a set-like object containing all the directories in the
1291 """Returns a set-like object containing all the directories in the
1295 current dirstate.
1292 current dirstate.
1296 """
1293 """
1297 return util.dirs(self._map, 'r')
1294 return util.dirs(self._map, 'r')
1298
1295
1299 def _opendirstatefile(self):
1296 def _opendirstatefile(self):
1300 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1297 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1301 if self._pendingmode is not None and self._pendingmode != mode:
1298 if self._pendingmode is not None and self._pendingmode != mode:
1302 fp.close()
1299 fp.close()
1303 raise error.Abort(_('working directory state may be '
1300 raise error.Abort(_('working directory state may be '
1304 'changed parallelly'))
1301 'changed parallelly'))
1305 self._pendingmode = mode
1302 self._pendingmode = mode
1306 return fp
1303 return fp
1307
1304
1308 def parents(self):
1305 def parents(self):
1309 if not self._parents:
1306 if not self._parents:
1310 try:
1307 try:
1311 fp = self._opendirstatefile()
1308 fp = self._opendirstatefile()
1312 st = fp.read(40)
1309 st = fp.read(40)
1313 fp.close()
1310 fp.close()
1314 except IOError as err:
1311 except IOError as err:
1315 if err.errno != errno.ENOENT:
1312 if err.errno != errno.ENOENT:
1316 raise
1313 raise
1317 # File doesn't exist, so the current state is empty
1314 # File doesn't exist, so the current state is empty
1318 st = ''
1315 st = ''
1319
1316
1320 l = len(st)
1317 l = len(st)
1321 if l == 40:
1318 if l == 40:
1322 self._parents = st[:20], st[20:40]
1319 self._parents = st[:20], st[20:40]
1323 elif l == 0:
1320 elif l == 0:
1324 self._parents = [nullid, nullid]
1321 self._parents = [nullid, nullid]
1325 else:
1322 else:
1326 raise error.Abort(_('working directory state appears '
1323 raise error.Abort(_('working directory state appears '
1327 'damaged!'))
1324 'damaged!'))
1328
1325
1329 return self._parents
1326 return self._parents
1330
1327
1331 def setparents(self, p1, p2):
1328 def setparents(self, p1, p2):
1332 self._parents = (p1, p2)
1329 self._parents = (p1, p2)
1333 self._dirtyparents = True
1330 self._dirtyparents = True
1334
1331
1335 def read(self):
1332 def read(self):
1336 # ignore HG_PENDING because identity is used only for writing
1333 # ignore HG_PENDING because identity is used only for writing
1337 self.identity = util.filestat.frompath(
1334 self.identity = util.filestat.frompath(
1338 self._opener.join(self._filename))
1335 self._opener.join(self._filename))
1339
1336
1340 try:
1337 try:
1341 fp = self._opendirstatefile()
1338 fp = self._opendirstatefile()
1342 try:
1339 try:
1343 st = fp.read()
1340 st = fp.read()
1344 finally:
1341 finally:
1345 fp.close()
1342 fp.close()
1346 except IOError as err:
1343 except IOError as err:
1347 if err.errno != errno.ENOENT:
1344 if err.errno != errno.ENOENT:
1348 raise
1345 raise
1349 return
1346 return
1350 if not st:
1347 if not st:
1351 return
1348 return
1352
1349
1353 if util.safehasattr(parsers, 'dict_new_presized'):
1350 if util.safehasattr(parsers, 'dict_new_presized'):
1354 # Make an estimate of the number of files in the dirstate based on
1351 # Make an estimate of the number of files in the dirstate based on
1355 # its size. From a linear regression on a set of real-world repos,
1352 # its size. From a linear regression on a set of real-world repos,
1356 # all over 10,000 files, the size of a dirstate entry is 85
1353 # all over 10,000 files, the size of a dirstate entry is 85
1357 # bytes. The cost of resizing is significantly higher than the cost
1354 # bytes. The cost of resizing is significantly higher than the cost
1358 # of filling in a larger presized dict, so subtract 20% from the
1355 # of filling in a larger presized dict, so subtract 20% from the
1359 # size.
1356 # size.
1360 #
1357 #
1361 # This heuristic is imperfect in many ways, so in a future dirstate
1358 # This heuristic is imperfect in many ways, so in a future dirstate
1362 # format update it makes sense to just record the number of entries
1359 # format update it makes sense to just record the number of entries
1363 # on write.
1360 # on write.
1364 self._map = parsers.dict_new_presized(len(st) / 71)
1361 self._map = parsers.dict_new_presized(len(st) / 71)
1365
1362
1366 # Python's garbage collector triggers a GC each time a certain number
1363 # Python's garbage collector triggers a GC each time a certain number
1367 # of container objects (the number being defined by
1364 # of container objects (the number being defined by
1368 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1365 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1369 # for each file in the dirstate. The C version then immediately marks
1366 # for each file in the dirstate. The C version then immediately marks
1370 # them as not to be tracked by the collector. However, this has no
1367 # them as not to be tracked by the collector. However, this has no
1371 # effect on when GCs are triggered, only on what objects the GC looks
1368 # effect on when GCs are triggered, only on what objects the GC looks
1372 # into. This means that O(number of files) GCs are unavoidable.
1369 # into. This means that O(number of files) GCs are unavoidable.
1373 # Depending on when in the process's lifetime the dirstate is parsed,
1370 # Depending on when in the process's lifetime the dirstate is parsed,
1374 # this can get very expensive. As a workaround, disable GC while
1371 # this can get very expensive. As a workaround, disable GC while
1375 # parsing the dirstate.
1372 # parsing the dirstate.
1376 #
1373 #
1377 # (we cannot decorate the function directly since it is in a C module)
1374 # (we cannot decorate the function directly since it is in a C module)
1378 parse_dirstate = util.nogc(parsers.parse_dirstate)
1375 parse_dirstate = util.nogc(parsers.parse_dirstate)
1379 p = parse_dirstate(self._map, self.copymap, st)
1376 p = parse_dirstate(self._map, self.copymap, st)
1380 if not self._dirtyparents:
1377 if not self._dirtyparents:
1381 self.setparents(*p)
1378 self.setparents(*p)
1382
1379
1383 def write(self, st, now):
1380 def write(self, st, now):
1384 st.write(parsers.pack_dirstate(self._map, self.copymap,
1381 st.write(parsers.pack_dirstate(self._map, self.copymap,
1385 self.parents(), now))
1382 self.parents(), now))
1386 st.close()
1383 st.close()
1387 self._dirtyparents = False
1384 self._dirtyparents = False
1388 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1385 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1389
1386
1390 @propertycache
1387 @propertycache
1391 def nonnormalset(self):
1388 def nonnormalset(self):
1392 nonnorm, otherparents = self.nonnormalentries()
1389 nonnorm, otherparents = self.nonnormalentries()
1393 self.otherparentset = otherparents
1390 self.otherparentset = otherparents
1394 return nonnorm
1391 return nonnorm
1395
1392
1396 @propertycache
1393 @propertycache
1397 def otherparentset(self):
1394 def otherparentset(self):
1398 nonnorm, otherparents = self.nonnormalentries()
1395 nonnorm, otherparents = self.nonnormalentries()
1399 self.nonnormalset = nonnorm
1396 self.nonnormalset = nonnorm
1400 return otherparents
1397 return otherparents
1401
1398
1402 @propertycache
1399 @propertycache
1403 def identity(self):
1400 def identity(self):
1404 self.read()
1401 self.read()
1405 return self.identity
1402 return self.identity
1406
1403
General Comments 0
You need to be logged in to leave comments. Login now