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