##// END OF EJS Templates
revlog: no longer return the nodemap after parsing...
marmoute -
r43926:e258ad11 default
parent child Browse files
Show More
@@ -1,3837 +1,3837 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 Configurations
4 Configurations
5 ==============
5 ==============
6
6
7 ``perf``
7 ``perf``
8 --------
8 --------
9
9
10 ``all-timing``
10 ``all-timing``
11 When set, additional statistics will be reported for each benchmark: best,
11 When set, additional statistics will be reported for each benchmark: best,
12 worst, median average. If not set only the best timing is reported
12 worst, median average. If not set only the best timing is reported
13 (default: off).
13 (default: off).
14
14
15 ``presleep``
15 ``presleep``
16 number of second to wait before any group of runs (default: 1)
16 number of second to wait before any group of runs (default: 1)
17
17
18 ``pre-run``
18 ``pre-run``
19 number of run to perform before starting measurement.
19 number of run to perform before starting measurement.
20
20
21 ``profile-benchmark``
21 ``profile-benchmark``
22 Enable profiling for the benchmarked section.
22 Enable profiling for the benchmarked section.
23 (The first iteration is benchmarked)
23 (The first iteration is benchmarked)
24
24
25 ``run-limits``
25 ``run-limits``
26 Control the number of runs each benchmark will perform. The option value
26 Control the number of runs each benchmark will perform. The option value
27 should be a list of `<time>-<numberofrun>` pairs. After each run the
27 should be a list of `<time>-<numberofrun>` pairs. After each run the
28 conditions are considered in order with the following logic:
28 conditions are considered in order with the following logic:
29
29
30 If benchmark has been running for <time> seconds, and we have performed
30 If benchmark has been running for <time> seconds, and we have performed
31 <numberofrun> iterations, stop the benchmark,
31 <numberofrun> iterations, stop the benchmark,
32
32
33 The default value is: `3.0-100, 10.0-3`
33 The default value is: `3.0-100, 10.0-3`
34
34
35 ``stub``
35 ``stub``
36 When set, benchmarks will only be run once, useful for testing
36 When set, benchmarks will only be run once, useful for testing
37 (default: off)
37 (default: off)
38 '''
38 '''
39
39
40 # "historical portability" policy of perf.py:
40 # "historical portability" policy of perf.py:
41 #
41 #
42 # We have to do:
42 # We have to do:
43 # - make perf.py "loadable" with as wide Mercurial version as possible
43 # - make perf.py "loadable" with as wide Mercurial version as possible
44 # This doesn't mean that perf commands work correctly with that Mercurial.
44 # This doesn't mean that perf commands work correctly with that Mercurial.
45 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
45 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
46 # - make historical perf command work correctly with as wide Mercurial
46 # - make historical perf command work correctly with as wide Mercurial
47 # version as possible
47 # version as possible
48 #
48 #
49 # We have to do, if possible with reasonable cost:
49 # We have to do, if possible with reasonable cost:
50 # - make recent perf command for historical feature work correctly
50 # - make recent perf command for historical feature work correctly
51 # with early Mercurial
51 # with early Mercurial
52 #
52 #
53 # We don't have to do:
53 # We don't have to do:
54 # - make perf command for recent feature work correctly with early
54 # - make perf command for recent feature work correctly with early
55 # Mercurial
55 # Mercurial
56
56
57 from __future__ import absolute_import
57 from __future__ import absolute_import
58 import contextlib
58 import contextlib
59 import functools
59 import functools
60 import gc
60 import gc
61 import os
61 import os
62 import random
62 import random
63 import shutil
63 import shutil
64 import struct
64 import struct
65 import sys
65 import sys
66 import tempfile
66 import tempfile
67 import threading
67 import threading
68 import time
68 import time
69 from mercurial import (
69 from mercurial import (
70 changegroup,
70 changegroup,
71 cmdutil,
71 cmdutil,
72 commands,
72 commands,
73 copies,
73 copies,
74 error,
74 error,
75 extensions,
75 extensions,
76 hg,
76 hg,
77 mdiff,
77 mdiff,
78 merge,
78 merge,
79 revlog,
79 revlog,
80 util,
80 util,
81 )
81 )
82
82
83 # for "historical portability":
83 # for "historical portability":
84 # try to import modules separately (in dict order), and ignore
84 # try to import modules separately (in dict order), and ignore
85 # failure, because these aren't available with early Mercurial
85 # failure, because these aren't available with early Mercurial
86 try:
86 try:
87 from mercurial import branchmap # since 2.5 (or bcee63733aad)
87 from mercurial import branchmap # since 2.5 (or bcee63733aad)
88 except ImportError:
88 except ImportError:
89 pass
89 pass
90 try:
90 try:
91 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
91 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
92 except ImportError:
92 except ImportError:
93 pass
93 pass
94 try:
94 try:
95 from mercurial import registrar # since 3.7 (or 37d50250b696)
95 from mercurial import registrar # since 3.7 (or 37d50250b696)
96
96
97 dir(registrar) # forcibly load it
97 dir(registrar) # forcibly load it
98 except ImportError:
98 except ImportError:
99 registrar = None
99 registrar = None
100 try:
100 try:
101 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
101 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
102 except ImportError:
102 except ImportError:
103 pass
103 pass
104 try:
104 try:
105 from mercurial.utils import repoviewutil # since 5.0
105 from mercurial.utils import repoviewutil # since 5.0
106 except ImportError:
106 except ImportError:
107 repoviewutil = None
107 repoviewutil = None
108 try:
108 try:
109 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
109 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
110 except ImportError:
110 except ImportError:
111 pass
111 pass
112 try:
112 try:
113 from mercurial import setdiscovery # since 1.9 (or cb98fed52495)
113 from mercurial import setdiscovery # since 1.9 (or cb98fed52495)
114 except ImportError:
114 except ImportError:
115 pass
115 pass
116
116
117 try:
117 try:
118 from mercurial import profiling
118 from mercurial import profiling
119 except ImportError:
119 except ImportError:
120 profiling = None
120 profiling = None
121
121
122
122
123 def identity(a):
123 def identity(a):
124 return a
124 return a
125
125
126
126
127 try:
127 try:
128 from mercurial import pycompat
128 from mercurial import pycompat
129
129
130 getargspec = pycompat.getargspec # added to module after 4.5
130 getargspec = pycompat.getargspec # added to module after 4.5
131 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
131 _byteskwargs = pycompat.byteskwargs # since 4.1 (or fbc3f73dc802)
132 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
132 _sysstr = pycompat.sysstr # since 4.0 (or 2219f4f82ede)
133 _bytestr = pycompat.bytestr # since 4.2 (or b70407bd84d5)
133 _bytestr = pycompat.bytestr # since 4.2 (or b70407bd84d5)
134 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
134 _xrange = pycompat.xrange # since 4.8 (or 7eba8f83129b)
135 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
135 fsencode = pycompat.fsencode # since 3.9 (or f4a5e0e86a7e)
136 if pycompat.ispy3:
136 if pycompat.ispy3:
137 _maxint = sys.maxsize # per py3 docs for replacing maxint
137 _maxint = sys.maxsize # per py3 docs for replacing maxint
138 else:
138 else:
139 _maxint = sys.maxint
139 _maxint = sys.maxint
140 except (NameError, ImportError, AttributeError):
140 except (NameError, ImportError, AttributeError):
141 import inspect
141 import inspect
142
142
143 getargspec = inspect.getargspec
143 getargspec = inspect.getargspec
144 _byteskwargs = identity
144 _byteskwargs = identity
145 _bytestr = str
145 _bytestr = str
146 fsencode = identity # no py3 support
146 fsencode = identity # no py3 support
147 _maxint = sys.maxint # no py3 support
147 _maxint = sys.maxint # no py3 support
148 _sysstr = lambda x: x # no py3 support
148 _sysstr = lambda x: x # no py3 support
149 _xrange = xrange
149 _xrange = xrange
150
150
151 try:
151 try:
152 # 4.7+
152 # 4.7+
153 queue = pycompat.queue.Queue
153 queue = pycompat.queue.Queue
154 except (NameError, AttributeError, ImportError):
154 except (NameError, AttributeError, ImportError):
155 # <4.7.
155 # <4.7.
156 try:
156 try:
157 queue = pycompat.queue
157 queue = pycompat.queue
158 except (NameError, AttributeError, ImportError):
158 except (NameError, AttributeError, ImportError):
159 import Queue as queue
159 import Queue as queue
160
160
161 try:
161 try:
162 from mercurial import logcmdutil
162 from mercurial import logcmdutil
163
163
164 makelogtemplater = logcmdutil.maketemplater
164 makelogtemplater = logcmdutil.maketemplater
165 except (AttributeError, ImportError):
165 except (AttributeError, ImportError):
166 try:
166 try:
167 makelogtemplater = cmdutil.makelogtemplater
167 makelogtemplater = cmdutil.makelogtemplater
168 except (AttributeError, ImportError):
168 except (AttributeError, ImportError):
169 makelogtemplater = None
169 makelogtemplater = None
170
170
171 # for "historical portability":
171 # for "historical portability":
172 # define util.safehasattr forcibly, because util.safehasattr has been
172 # define util.safehasattr forcibly, because util.safehasattr has been
173 # available since 1.9.3 (or 94b200a11cf7)
173 # available since 1.9.3 (or 94b200a11cf7)
174 _undefined = object()
174 _undefined = object()
175
175
176
176
177 def safehasattr(thing, attr):
177 def safehasattr(thing, attr):
178 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
178 return getattr(thing, _sysstr(attr), _undefined) is not _undefined
179
179
180
180
181 setattr(util, 'safehasattr', safehasattr)
181 setattr(util, 'safehasattr', safehasattr)
182
182
183 # for "historical portability":
183 # for "historical portability":
184 # define util.timer forcibly, because util.timer has been available
184 # define util.timer forcibly, because util.timer has been available
185 # since ae5d60bb70c9
185 # since ae5d60bb70c9
186 if safehasattr(time, 'perf_counter'):
186 if safehasattr(time, 'perf_counter'):
187 util.timer = time.perf_counter
187 util.timer = time.perf_counter
188 elif os.name == b'nt':
188 elif os.name == b'nt':
189 util.timer = time.clock
189 util.timer = time.clock
190 else:
190 else:
191 util.timer = time.time
191 util.timer = time.time
192
192
193 # for "historical portability":
193 # for "historical portability":
194 # use locally defined empty option list, if formatteropts isn't
194 # use locally defined empty option list, if formatteropts isn't
195 # available, because commands.formatteropts has been available since
195 # available, because commands.formatteropts has been available since
196 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
196 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
197 # available since 2.2 (or ae5f92e154d3)
197 # available since 2.2 (or ae5f92e154d3)
198 formatteropts = getattr(
198 formatteropts = getattr(
199 cmdutil, "formatteropts", getattr(commands, "formatteropts", [])
199 cmdutil, "formatteropts", getattr(commands, "formatteropts", [])
200 )
200 )
201
201
202 # for "historical portability":
202 # for "historical portability":
203 # use locally defined option list, if debugrevlogopts isn't available,
203 # use locally defined option list, if debugrevlogopts isn't available,
204 # because commands.debugrevlogopts has been available since 3.7 (or
204 # because commands.debugrevlogopts has been available since 3.7 (or
205 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
205 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
206 # since 1.9 (or a79fea6b3e77).
206 # since 1.9 (or a79fea6b3e77).
207 revlogopts = getattr(
207 revlogopts = getattr(
208 cmdutil,
208 cmdutil,
209 "debugrevlogopts",
209 "debugrevlogopts",
210 getattr(
210 getattr(
211 commands,
211 commands,
212 "debugrevlogopts",
212 "debugrevlogopts",
213 [
213 [
214 (b'c', b'changelog', False, b'open changelog'),
214 (b'c', b'changelog', False, b'open changelog'),
215 (b'm', b'manifest', False, b'open manifest'),
215 (b'm', b'manifest', False, b'open manifest'),
216 (b'', b'dir', False, b'open directory manifest'),
216 (b'', b'dir', False, b'open directory manifest'),
217 ],
217 ],
218 ),
218 ),
219 )
219 )
220
220
221 cmdtable = {}
221 cmdtable = {}
222
222
223 # for "historical portability":
223 # for "historical portability":
224 # define parsealiases locally, because cmdutil.parsealiases has been
224 # define parsealiases locally, because cmdutil.parsealiases has been
225 # available since 1.5 (or 6252852b4332)
225 # available since 1.5 (or 6252852b4332)
226 def parsealiases(cmd):
226 def parsealiases(cmd):
227 return cmd.split(b"|")
227 return cmd.split(b"|")
228
228
229
229
230 if safehasattr(registrar, 'command'):
230 if safehasattr(registrar, 'command'):
231 command = registrar.command(cmdtable)
231 command = registrar.command(cmdtable)
232 elif safehasattr(cmdutil, 'command'):
232 elif safehasattr(cmdutil, 'command'):
233 command = cmdutil.command(cmdtable)
233 command = cmdutil.command(cmdtable)
234 if b'norepo' not in getargspec(command).args:
234 if b'norepo' not in getargspec(command).args:
235 # for "historical portability":
235 # for "historical portability":
236 # wrap original cmdutil.command, because "norepo" option has
236 # wrap original cmdutil.command, because "norepo" option has
237 # been available since 3.1 (or 75a96326cecb)
237 # been available since 3.1 (or 75a96326cecb)
238 _command = command
238 _command = command
239
239
240 def command(name, options=(), synopsis=None, norepo=False):
240 def command(name, options=(), synopsis=None, norepo=False):
241 if norepo:
241 if norepo:
242 commands.norepo += b' %s' % b' '.join(parsealiases(name))
242 commands.norepo += b' %s' % b' '.join(parsealiases(name))
243 return _command(name, list(options), synopsis)
243 return _command(name, list(options), synopsis)
244
244
245
245
246 else:
246 else:
247 # for "historical portability":
247 # for "historical portability":
248 # define "@command" annotation locally, because cmdutil.command
248 # define "@command" annotation locally, because cmdutil.command
249 # has been available since 1.9 (or 2daa5179e73f)
249 # has been available since 1.9 (or 2daa5179e73f)
250 def command(name, options=(), synopsis=None, norepo=False):
250 def command(name, options=(), synopsis=None, norepo=False):
251 def decorator(func):
251 def decorator(func):
252 if synopsis:
252 if synopsis:
253 cmdtable[name] = func, list(options), synopsis
253 cmdtable[name] = func, list(options), synopsis
254 else:
254 else:
255 cmdtable[name] = func, list(options)
255 cmdtable[name] = func, list(options)
256 if norepo:
256 if norepo:
257 commands.norepo += b' %s' % b' '.join(parsealiases(name))
257 commands.norepo += b' %s' % b' '.join(parsealiases(name))
258 return func
258 return func
259
259
260 return decorator
260 return decorator
261
261
262
262
263 try:
263 try:
264 import mercurial.registrar
264 import mercurial.registrar
265 import mercurial.configitems
265 import mercurial.configitems
266
266
267 configtable = {}
267 configtable = {}
268 configitem = mercurial.registrar.configitem(configtable)
268 configitem = mercurial.registrar.configitem(configtable)
269 configitem(
269 configitem(
270 b'perf',
270 b'perf',
271 b'presleep',
271 b'presleep',
272 default=mercurial.configitems.dynamicdefault,
272 default=mercurial.configitems.dynamicdefault,
273 experimental=True,
273 experimental=True,
274 )
274 )
275 configitem(
275 configitem(
276 b'perf',
276 b'perf',
277 b'stub',
277 b'stub',
278 default=mercurial.configitems.dynamicdefault,
278 default=mercurial.configitems.dynamicdefault,
279 experimental=True,
279 experimental=True,
280 )
280 )
281 configitem(
281 configitem(
282 b'perf',
282 b'perf',
283 b'parentscount',
283 b'parentscount',
284 default=mercurial.configitems.dynamicdefault,
284 default=mercurial.configitems.dynamicdefault,
285 experimental=True,
285 experimental=True,
286 )
286 )
287 configitem(
287 configitem(
288 b'perf',
288 b'perf',
289 b'all-timing',
289 b'all-timing',
290 default=mercurial.configitems.dynamicdefault,
290 default=mercurial.configitems.dynamicdefault,
291 experimental=True,
291 experimental=True,
292 )
292 )
293 configitem(
293 configitem(
294 b'perf', b'pre-run', default=mercurial.configitems.dynamicdefault,
294 b'perf', b'pre-run', default=mercurial.configitems.dynamicdefault,
295 )
295 )
296 configitem(
296 configitem(
297 b'perf',
297 b'perf',
298 b'profile-benchmark',
298 b'profile-benchmark',
299 default=mercurial.configitems.dynamicdefault,
299 default=mercurial.configitems.dynamicdefault,
300 )
300 )
301 configitem(
301 configitem(
302 b'perf',
302 b'perf',
303 b'run-limits',
303 b'run-limits',
304 default=mercurial.configitems.dynamicdefault,
304 default=mercurial.configitems.dynamicdefault,
305 experimental=True,
305 experimental=True,
306 )
306 )
307 except (ImportError, AttributeError):
307 except (ImportError, AttributeError):
308 pass
308 pass
309 except TypeError:
309 except TypeError:
310 # compatibility fix for a11fd395e83f
310 # compatibility fix for a11fd395e83f
311 # hg version: 5.2
311 # hg version: 5.2
312 configitem(
312 configitem(
313 b'perf', b'presleep', default=mercurial.configitems.dynamicdefault,
313 b'perf', b'presleep', default=mercurial.configitems.dynamicdefault,
314 )
314 )
315 configitem(
315 configitem(
316 b'perf', b'stub', default=mercurial.configitems.dynamicdefault,
316 b'perf', b'stub', default=mercurial.configitems.dynamicdefault,
317 )
317 )
318 configitem(
318 configitem(
319 b'perf', b'parentscount', default=mercurial.configitems.dynamicdefault,
319 b'perf', b'parentscount', default=mercurial.configitems.dynamicdefault,
320 )
320 )
321 configitem(
321 configitem(
322 b'perf', b'all-timing', default=mercurial.configitems.dynamicdefault,
322 b'perf', b'all-timing', default=mercurial.configitems.dynamicdefault,
323 )
323 )
324 configitem(
324 configitem(
325 b'perf', b'pre-run', default=mercurial.configitems.dynamicdefault,
325 b'perf', b'pre-run', default=mercurial.configitems.dynamicdefault,
326 )
326 )
327 configitem(
327 configitem(
328 b'perf',
328 b'perf',
329 b'profile-benchmark',
329 b'profile-benchmark',
330 default=mercurial.configitems.dynamicdefault,
330 default=mercurial.configitems.dynamicdefault,
331 )
331 )
332 configitem(
332 configitem(
333 b'perf', b'run-limits', default=mercurial.configitems.dynamicdefault,
333 b'perf', b'run-limits', default=mercurial.configitems.dynamicdefault,
334 )
334 )
335
335
336
336
337 def getlen(ui):
337 def getlen(ui):
338 if ui.configbool(b"perf", b"stub", False):
338 if ui.configbool(b"perf", b"stub", False):
339 return lambda x: 1
339 return lambda x: 1
340 return len
340 return len
341
341
342
342
343 class noop(object):
343 class noop(object):
344 """dummy context manager"""
344 """dummy context manager"""
345
345
346 def __enter__(self):
346 def __enter__(self):
347 pass
347 pass
348
348
349 def __exit__(self, *args):
349 def __exit__(self, *args):
350 pass
350 pass
351
351
352
352
353 NOOPCTX = noop()
353 NOOPCTX = noop()
354
354
355
355
356 def gettimer(ui, opts=None):
356 def gettimer(ui, opts=None):
357 """return a timer function and formatter: (timer, formatter)
357 """return a timer function and formatter: (timer, formatter)
358
358
359 This function exists to gather the creation of formatter in a single
359 This function exists to gather the creation of formatter in a single
360 place instead of duplicating it in all performance commands."""
360 place instead of duplicating it in all performance commands."""
361
361
362 # enforce an idle period before execution to counteract power management
362 # enforce an idle period before execution to counteract power management
363 # experimental config: perf.presleep
363 # experimental config: perf.presleep
364 time.sleep(getint(ui, b"perf", b"presleep", 1))
364 time.sleep(getint(ui, b"perf", b"presleep", 1))
365
365
366 if opts is None:
366 if opts is None:
367 opts = {}
367 opts = {}
368 # redirect all to stderr unless buffer api is in use
368 # redirect all to stderr unless buffer api is in use
369 if not ui._buffers:
369 if not ui._buffers:
370 ui = ui.copy()
370 ui = ui.copy()
371 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
371 uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
372 if uifout:
372 if uifout:
373 # for "historical portability":
373 # for "historical portability":
374 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
374 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
375 uifout.set(ui.ferr)
375 uifout.set(ui.ferr)
376
376
377 # get a formatter
377 # get a formatter
378 uiformatter = getattr(ui, 'formatter', None)
378 uiformatter = getattr(ui, 'formatter', None)
379 if uiformatter:
379 if uiformatter:
380 fm = uiformatter(b'perf', opts)
380 fm = uiformatter(b'perf', opts)
381 else:
381 else:
382 # for "historical portability":
382 # for "historical portability":
383 # define formatter locally, because ui.formatter has been
383 # define formatter locally, because ui.formatter has been
384 # available since 2.2 (or ae5f92e154d3)
384 # available since 2.2 (or ae5f92e154d3)
385 from mercurial import node
385 from mercurial import node
386
386
387 class defaultformatter(object):
387 class defaultformatter(object):
388 """Minimized composition of baseformatter and plainformatter
388 """Minimized composition of baseformatter and plainformatter
389 """
389 """
390
390
391 def __init__(self, ui, topic, opts):
391 def __init__(self, ui, topic, opts):
392 self._ui = ui
392 self._ui = ui
393 if ui.debugflag:
393 if ui.debugflag:
394 self.hexfunc = node.hex
394 self.hexfunc = node.hex
395 else:
395 else:
396 self.hexfunc = node.short
396 self.hexfunc = node.short
397
397
398 def __nonzero__(self):
398 def __nonzero__(self):
399 return False
399 return False
400
400
401 __bool__ = __nonzero__
401 __bool__ = __nonzero__
402
402
403 def startitem(self):
403 def startitem(self):
404 pass
404 pass
405
405
406 def data(self, **data):
406 def data(self, **data):
407 pass
407 pass
408
408
409 def write(self, fields, deftext, *fielddata, **opts):
409 def write(self, fields, deftext, *fielddata, **opts):
410 self._ui.write(deftext % fielddata, **opts)
410 self._ui.write(deftext % fielddata, **opts)
411
411
412 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
412 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
413 if cond:
413 if cond:
414 self._ui.write(deftext % fielddata, **opts)
414 self._ui.write(deftext % fielddata, **opts)
415
415
416 def plain(self, text, **opts):
416 def plain(self, text, **opts):
417 self._ui.write(text, **opts)
417 self._ui.write(text, **opts)
418
418
419 def end(self):
419 def end(self):
420 pass
420 pass
421
421
422 fm = defaultformatter(ui, b'perf', opts)
422 fm = defaultformatter(ui, b'perf', opts)
423
423
424 # stub function, runs code only once instead of in a loop
424 # stub function, runs code only once instead of in a loop
425 # experimental config: perf.stub
425 # experimental config: perf.stub
426 if ui.configbool(b"perf", b"stub", False):
426 if ui.configbool(b"perf", b"stub", False):
427 return functools.partial(stub_timer, fm), fm
427 return functools.partial(stub_timer, fm), fm
428
428
429 # experimental config: perf.all-timing
429 # experimental config: perf.all-timing
430 displayall = ui.configbool(b"perf", b"all-timing", False)
430 displayall = ui.configbool(b"perf", b"all-timing", False)
431
431
432 # experimental config: perf.run-limits
432 # experimental config: perf.run-limits
433 limitspec = ui.configlist(b"perf", b"run-limits", [])
433 limitspec = ui.configlist(b"perf", b"run-limits", [])
434 limits = []
434 limits = []
435 for item in limitspec:
435 for item in limitspec:
436 parts = item.split(b'-', 1)
436 parts = item.split(b'-', 1)
437 if len(parts) < 2:
437 if len(parts) < 2:
438 ui.warn((b'malformatted run limit entry, missing "-": %s\n' % item))
438 ui.warn((b'malformatted run limit entry, missing "-": %s\n' % item))
439 continue
439 continue
440 try:
440 try:
441 time_limit = float(_sysstr(parts[0]))
441 time_limit = float(_sysstr(parts[0]))
442 except ValueError as e:
442 except ValueError as e:
443 ui.warn(
443 ui.warn(
444 (
444 (
445 b'malformatted run limit entry, %s: %s\n'
445 b'malformatted run limit entry, %s: %s\n'
446 % (_bytestr(e), item)
446 % (_bytestr(e), item)
447 )
447 )
448 )
448 )
449 continue
449 continue
450 try:
450 try:
451 run_limit = int(_sysstr(parts[1]))
451 run_limit = int(_sysstr(parts[1]))
452 except ValueError as e:
452 except ValueError as e:
453 ui.warn(
453 ui.warn(
454 (
454 (
455 b'malformatted run limit entry, %s: %s\n'
455 b'malformatted run limit entry, %s: %s\n'
456 % (_bytestr(e), item)
456 % (_bytestr(e), item)
457 )
457 )
458 )
458 )
459 continue
459 continue
460 limits.append((time_limit, run_limit))
460 limits.append((time_limit, run_limit))
461 if not limits:
461 if not limits:
462 limits = DEFAULTLIMITS
462 limits = DEFAULTLIMITS
463
463
464 profiler = None
464 profiler = None
465 if profiling is not None:
465 if profiling is not None:
466 if ui.configbool(b"perf", b"profile-benchmark", False):
466 if ui.configbool(b"perf", b"profile-benchmark", False):
467 profiler = profiling.profile(ui)
467 profiler = profiling.profile(ui)
468
468
469 prerun = getint(ui, b"perf", b"pre-run", 0)
469 prerun = getint(ui, b"perf", b"pre-run", 0)
470 t = functools.partial(
470 t = functools.partial(
471 _timer,
471 _timer,
472 fm,
472 fm,
473 displayall=displayall,
473 displayall=displayall,
474 limits=limits,
474 limits=limits,
475 prerun=prerun,
475 prerun=prerun,
476 profiler=profiler,
476 profiler=profiler,
477 )
477 )
478 return t, fm
478 return t, fm
479
479
480
480
481 def stub_timer(fm, func, setup=None, title=None):
481 def stub_timer(fm, func, setup=None, title=None):
482 if setup is not None:
482 if setup is not None:
483 setup()
483 setup()
484 func()
484 func()
485
485
486
486
487 @contextlib.contextmanager
487 @contextlib.contextmanager
488 def timeone():
488 def timeone():
489 r = []
489 r = []
490 ostart = os.times()
490 ostart = os.times()
491 cstart = util.timer()
491 cstart = util.timer()
492 yield r
492 yield r
493 cstop = util.timer()
493 cstop = util.timer()
494 ostop = os.times()
494 ostop = os.times()
495 a, b = ostart, ostop
495 a, b = ostart, ostop
496 r.append((cstop - cstart, b[0] - a[0], b[1] - a[1]))
496 r.append((cstop - cstart, b[0] - a[0], b[1] - a[1]))
497
497
498
498
499 # list of stop condition (elapsed time, minimal run count)
499 # list of stop condition (elapsed time, minimal run count)
500 DEFAULTLIMITS = (
500 DEFAULTLIMITS = (
501 (3.0, 100),
501 (3.0, 100),
502 (10.0, 3),
502 (10.0, 3),
503 )
503 )
504
504
505
505
506 def _timer(
506 def _timer(
507 fm,
507 fm,
508 func,
508 func,
509 setup=None,
509 setup=None,
510 title=None,
510 title=None,
511 displayall=False,
511 displayall=False,
512 limits=DEFAULTLIMITS,
512 limits=DEFAULTLIMITS,
513 prerun=0,
513 prerun=0,
514 profiler=None,
514 profiler=None,
515 ):
515 ):
516 gc.collect()
516 gc.collect()
517 results = []
517 results = []
518 begin = util.timer()
518 begin = util.timer()
519 count = 0
519 count = 0
520 if profiler is None:
520 if profiler is None:
521 profiler = NOOPCTX
521 profiler = NOOPCTX
522 for i in range(prerun):
522 for i in range(prerun):
523 if setup is not None:
523 if setup is not None:
524 setup()
524 setup()
525 func()
525 func()
526 keepgoing = True
526 keepgoing = True
527 while keepgoing:
527 while keepgoing:
528 if setup is not None:
528 if setup is not None:
529 setup()
529 setup()
530 with profiler:
530 with profiler:
531 with timeone() as item:
531 with timeone() as item:
532 r = func()
532 r = func()
533 profiler = NOOPCTX
533 profiler = NOOPCTX
534 count += 1
534 count += 1
535 results.append(item[0])
535 results.append(item[0])
536 cstop = util.timer()
536 cstop = util.timer()
537 # Look for a stop condition.
537 # Look for a stop condition.
538 elapsed = cstop - begin
538 elapsed = cstop - begin
539 for t, mincount in limits:
539 for t, mincount in limits:
540 if elapsed >= t and count >= mincount:
540 if elapsed >= t and count >= mincount:
541 keepgoing = False
541 keepgoing = False
542 break
542 break
543
543
544 formatone(fm, results, title=title, result=r, displayall=displayall)
544 formatone(fm, results, title=title, result=r, displayall=displayall)
545
545
546
546
547 def formatone(fm, timings, title=None, result=None, displayall=False):
547 def formatone(fm, timings, title=None, result=None, displayall=False):
548
548
549 count = len(timings)
549 count = len(timings)
550
550
551 fm.startitem()
551 fm.startitem()
552
552
553 if title:
553 if title:
554 fm.write(b'title', b'! %s\n', title)
554 fm.write(b'title', b'! %s\n', title)
555 if result:
555 if result:
556 fm.write(b'result', b'! result: %s\n', result)
556 fm.write(b'result', b'! result: %s\n', result)
557
557
558 def display(role, entry):
558 def display(role, entry):
559 prefix = b''
559 prefix = b''
560 if role != b'best':
560 if role != b'best':
561 prefix = b'%s.' % role
561 prefix = b'%s.' % role
562 fm.plain(b'!')
562 fm.plain(b'!')
563 fm.write(prefix + b'wall', b' wall %f', entry[0])
563 fm.write(prefix + b'wall', b' wall %f', entry[0])
564 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
564 fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
565 fm.write(prefix + b'user', b' user %f', entry[1])
565 fm.write(prefix + b'user', b' user %f', entry[1])
566 fm.write(prefix + b'sys', b' sys %f', entry[2])
566 fm.write(prefix + b'sys', b' sys %f', entry[2])
567 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
567 fm.write(prefix + b'count', b' (%s of %%d)' % role, count)
568 fm.plain(b'\n')
568 fm.plain(b'\n')
569
569
570 timings.sort()
570 timings.sort()
571 min_val = timings[0]
571 min_val = timings[0]
572 display(b'best', min_val)
572 display(b'best', min_val)
573 if displayall:
573 if displayall:
574 max_val = timings[-1]
574 max_val = timings[-1]
575 display(b'max', max_val)
575 display(b'max', max_val)
576 avg = tuple([sum(x) / count for x in zip(*timings)])
576 avg = tuple([sum(x) / count for x in zip(*timings)])
577 display(b'avg', avg)
577 display(b'avg', avg)
578 median = timings[len(timings) // 2]
578 median = timings[len(timings) // 2]
579 display(b'median', median)
579 display(b'median', median)
580
580
581
581
582 # utilities for historical portability
582 # utilities for historical portability
583
583
584
584
585 def getint(ui, section, name, default):
585 def getint(ui, section, name, default):
586 # for "historical portability":
586 # for "historical portability":
587 # ui.configint has been available since 1.9 (or fa2b596db182)
587 # ui.configint has been available since 1.9 (or fa2b596db182)
588 v = ui.config(section, name, None)
588 v = ui.config(section, name, None)
589 if v is None:
589 if v is None:
590 return default
590 return default
591 try:
591 try:
592 return int(v)
592 return int(v)
593 except ValueError:
593 except ValueError:
594 raise error.ConfigError(
594 raise error.ConfigError(
595 b"%s.%s is not an integer ('%s')" % (section, name, v)
595 b"%s.%s is not an integer ('%s')" % (section, name, v)
596 )
596 )
597
597
598
598
599 def safeattrsetter(obj, name, ignoremissing=False):
599 def safeattrsetter(obj, name, ignoremissing=False):
600 """Ensure that 'obj' has 'name' attribute before subsequent setattr
600 """Ensure that 'obj' has 'name' attribute before subsequent setattr
601
601
602 This function is aborted, if 'obj' doesn't have 'name' attribute
602 This function is aborted, if 'obj' doesn't have 'name' attribute
603 at runtime. This avoids overlooking removal of an attribute, which
603 at runtime. This avoids overlooking removal of an attribute, which
604 breaks assumption of performance measurement, in the future.
604 breaks assumption of performance measurement, in the future.
605
605
606 This function returns the object to (1) assign a new value, and
606 This function returns the object to (1) assign a new value, and
607 (2) restore an original value to the attribute.
607 (2) restore an original value to the attribute.
608
608
609 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
609 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
610 abortion, and this function returns None. This is useful to
610 abortion, and this function returns None. This is useful to
611 examine an attribute, which isn't ensured in all Mercurial
611 examine an attribute, which isn't ensured in all Mercurial
612 versions.
612 versions.
613 """
613 """
614 if not util.safehasattr(obj, name):
614 if not util.safehasattr(obj, name):
615 if ignoremissing:
615 if ignoremissing:
616 return None
616 return None
617 raise error.Abort(
617 raise error.Abort(
618 (
618 (
619 b"missing attribute %s of %s might break assumption"
619 b"missing attribute %s of %s might break assumption"
620 b" of performance measurement"
620 b" of performance measurement"
621 )
621 )
622 % (name, obj)
622 % (name, obj)
623 )
623 )
624
624
625 origvalue = getattr(obj, _sysstr(name))
625 origvalue = getattr(obj, _sysstr(name))
626
626
627 class attrutil(object):
627 class attrutil(object):
628 def set(self, newvalue):
628 def set(self, newvalue):
629 setattr(obj, _sysstr(name), newvalue)
629 setattr(obj, _sysstr(name), newvalue)
630
630
631 def restore(self):
631 def restore(self):
632 setattr(obj, _sysstr(name), origvalue)
632 setattr(obj, _sysstr(name), origvalue)
633
633
634 return attrutil()
634 return attrutil()
635
635
636
636
637 # utilities to examine each internal API changes
637 # utilities to examine each internal API changes
638
638
639
639
640 def getbranchmapsubsettable():
640 def getbranchmapsubsettable():
641 # for "historical portability":
641 # for "historical portability":
642 # subsettable is defined in:
642 # subsettable is defined in:
643 # - branchmap since 2.9 (or 175c6fd8cacc)
643 # - branchmap since 2.9 (or 175c6fd8cacc)
644 # - repoview since 2.5 (or 59a9f18d4587)
644 # - repoview since 2.5 (or 59a9f18d4587)
645 # - repoviewutil since 5.0
645 # - repoviewutil since 5.0
646 for mod in (branchmap, repoview, repoviewutil):
646 for mod in (branchmap, repoview, repoviewutil):
647 subsettable = getattr(mod, 'subsettable', None)
647 subsettable = getattr(mod, 'subsettable', None)
648 if subsettable:
648 if subsettable:
649 return subsettable
649 return subsettable
650
650
651 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
651 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
652 # branchmap and repoview modules exist, but subsettable attribute
652 # branchmap and repoview modules exist, but subsettable attribute
653 # doesn't)
653 # doesn't)
654 raise error.Abort(
654 raise error.Abort(
655 b"perfbranchmap not available with this Mercurial",
655 b"perfbranchmap not available with this Mercurial",
656 hint=b"use 2.5 or later",
656 hint=b"use 2.5 or later",
657 )
657 )
658
658
659
659
660 def getsvfs(repo):
660 def getsvfs(repo):
661 """Return appropriate object to access files under .hg/store
661 """Return appropriate object to access files under .hg/store
662 """
662 """
663 # for "historical portability":
663 # for "historical portability":
664 # repo.svfs has been available since 2.3 (or 7034365089bf)
664 # repo.svfs has been available since 2.3 (or 7034365089bf)
665 svfs = getattr(repo, 'svfs', None)
665 svfs = getattr(repo, 'svfs', None)
666 if svfs:
666 if svfs:
667 return svfs
667 return svfs
668 else:
668 else:
669 return getattr(repo, 'sopener')
669 return getattr(repo, 'sopener')
670
670
671
671
672 def getvfs(repo):
672 def getvfs(repo):
673 """Return appropriate object to access files under .hg
673 """Return appropriate object to access files under .hg
674 """
674 """
675 # for "historical portability":
675 # for "historical portability":
676 # repo.vfs has been available since 2.3 (or 7034365089bf)
676 # repo.vfs has been available since 2.3 (or 7034365089bf)
677 vfs = getattr(repo, 'vfs', None)
677 vfs = getattr(repo, 'vfs', None)
678 if vfs:
678 if vfs:
679 return vfs
679 return vfs
680 else:
680 else:
681 return getattr(repo, 'opener')
681 return getattr(repo, 'opener')
682
682
683
683
684 def repocleartagscachefunc(repo):
684 def repocleartagscachefunc(repo):
685 """Return the function to clear tags cache according to repo internal API
685 """Return the function to clear tags cache according to repo internal API
686 """
686 """
687 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
687 if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
688 # in this case, setattr(repo, '_tagscache', None) or so isn't
688 # in this case, setattr(repo, '_tagscache', None) or so isn't
689 # correct way to clear tags cache, because existing code paths
689 # correct way to clear tags cache, because existing code paths
690 # expect _tagscache to be a structured object.
690 # expect _tagscache to be a structured object.
691 def clearcache():
691 def clearcache():
692 # _tagscache has been filteredpropertycache since 2.5 (or
692 # _tagscache has been filteredpropertycache since 2.5 (or
693 # 98c867ac1330), and delattr() can't work in such case
693 # 98c867ac1330), and delattr() can't work in such case
694 if '_tagscache' in vars(repo):
694 if '_tagscache' in vars(repo):
695 del repo.__dict__['_tagscache']
695 del repo.__dict__['_tagscache']
696
696
697 return clearcache
697 return clearcache
698
698
699 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
699 repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
700 if repotags: # since 1.4 (or 5614a628d173)
700 if repotags: # since 1.4 (or 5614a628d173)
701 return lambda: repotags.set(None)
701 return lambda: repotags.set(None)
702
702
703 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
703 repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
704 if repotagscache: # since 0.6 (or d7df759d0e97)
704 if repotagscache: # since 0.6 (or d7df759d0e97)
705 return lambda: repotagscache.set(None)
705 return lambda: repotagscache.set(None)
706
706
707 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
707 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
708 # this point, but it isn't so problematic, because:
708 # this point, but it isn't so problematic, because:
709 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
709 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
710 # in perftags() causes failure soon
710 # in perftags() causes failure soon
711 # - perf.py itself has been available since 1.1 (or eb240755386d)
711 # - perf.py itself has been available since 1.1 (or eb240755386d)
712 raise error.Abort(b"tags API of this hg command is unknown")
712 raise error.Abort(b"tags API of this hg command is unknown")
713
713
714
714
715 # utilities to clear cache
715 # utilities to clear cache
716
716
717
717
718 def clearfilecache(obj, attrname):
718 def clearfilecache(obj, attrname):
719 unfiltered = getattr(obj, 'unfiltered', None)
719 unfiltered = getattr(obj, 'unfiltered', None)
720 if unfiltered is not None:
720 if unfiltered is not None:
721 obj = obj.unfiltered()
721 obj = obj.unfiltered()
722 if attrname in vars(obj):
722 if attrname in vars(obj):
723 delattr(obj, attrname)
723 delattr(obj, attrname)
724 obj._filecache.pop(attrname, None)
724 obj._filecache.pop(attrname, None)
725
725
726
726
727 def clearchangelog(repo):
727 def clearchangelog(repo):
728 if repo is not repo.unfiltered():
728 if repo is not repo.unfiltered():
729 object.__setattr__(repo, '_clcachekey', None)
729 object.__setattr__(repo, '_clcachekey', None)
730 object.__setattr__(repo, '_clcache', None)
730 object.__setattr__(repo, '_clcache', None)
731 clearfilecache(repo.unfiltered(), 'changelog')
731 clearfilecache(repo.unfiltered(), 'changelog')
732
732
733
733
734 # perf commands
734 # perf commands
735
735
736
736
737 @command(b'perfwalk', formatteropts)
737 @command(b'perfwalk', formatteropts)
738 def perfwalk(ui, repo, *pats, **opts):
738 def perfwalk(ui, repo, *pats, **opts):
739 opts = _byteskwargs(opts)
739 opts = _byteskwargs(opts)
740 timer, fm = gettimer(ui, opts)
740 timer, fm = gettimer(ui, opts)
741 m = scmutil.match(repo[None], pats, {})
741 m = scmutil.match(repo[None], pats, {})
742 timer(
742 timer(
743 lambda: len(
743 lambda: len(
744 list(
744 list(
745 repo.dirstate.walk(m, subrepos=[], unknown=True, ignored=False)
745 repo.dirstate.walk(m, subrepos=[], unknown=True, ignored=False)
746 )
746 )
747 )
747 )
748 )
748 )
749 fm.end()
749 fm.end()
750
750
751
751
752 @command(b'perfannotate', formatteropts)
752 @command(b'perfannotate', formatteropts)
753 def perfannotate(ui, repo, f, **opts):
753 def perfannotate(ui, repo, f, **opts):
754 opts = _byteskwargs(opts)
754 opts = _byteskwargs(opts)
755 timer, fm = gettimer(ui, opts)
755 timer, fm = gettimer(ui, opts)
756 fc = repo[b'.'][f]
756 fc = repo[b'.'][f]
757 timer(lambda: len(fc.annotate(True)))
757 timer(lambda: len(fc.annotate(True)))
758 fm.end()
758 fm.end()
759
759
760
760
761 @command(
761 @command(
762 b'perfstatus',
762 b'perfstatus',
763 [
763 [
764 (b'u', b'unknown', False, b'ask status to look for unknown files'),
764 (b'u', b'unknown', False, b'ask status to look for unknown files'),
765 (b'', b'dirstate', False, b'benchmark the internal dirstate call'),
765 (b'', b'dirstate', False, b'benchmark the internal dirstate call'),
766 ]
766 ]
767 + formatteropts,
767 + formatteropts,
768 )
768 )
769 def perfstatus(ui, repo, **opts):
769 def perfstatus(ui, repo, **opts):
770 """benchmark the performance of a single status call
770 """benchmark the performance of a single status call
771
771
772 The repository data are preserved between each call.
772 The repository data are preserved between each call.
773
773
774 By default, only the status of the tracked file are requested. If
774 By default, only the status of the tracked file are requested. If
775 `--unknown` is passed, the "unknown" files are also tracked.
775 `--unknown` is passed, the "unknown" files are also tracked.
776 """
776 """
777 opts = _byteskwargs(opts)
777 opts = _byteskwargs(opts)
778 # m = match.always(repo.root, repo.getcwd())
778 # m = match.always(repo.root, repo.getcwd())
779 # timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
779 # timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
780 # False))))
780 # False))))
781 timer, fm = gettimer(ui, opts)
781 timer, fm = gettimer(ui, opts)
782 if opts[b'dirstate']:
782 if opts[b'dirstate']:
783 dirstate = repo.dirstate
783 dirstate = repo.dirstate
784 m = scmutil.matchall(repo)
784 m = scmutil.matchall(repo)
785 unknown = opts[b'unknown']
785 unknown = opts[b'unknown']
786
786
787 def status_dirstate():
787 def status_dirstate():
788 s = dirstate.status(
788 s = dirstate.status(
789 m, subrepos=[], ignored=False, clean=False, unknown=unknown
789 m, subrepos=[], ignored=False, clean=False, unknown=unknown
790 )
790 )
791 sum(map(len, s))
791 sum(map(len, s))
792
792
793 timer(status_dirstate)
793 timer(status_dirstate)
794 else:
794 else:
795 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
795 timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
796 fm.end()
796 fm.end()
797
797
798
798
799 @command(b'perfaddremove', formatteropts)
799 @command(b'perfaddremove', formatteropts)
800 def perfaddremove(ui, repo, **opts):
800 def perfaddremove(ui, repo, **opts):
801 opts = _byteskwargs(opts)
801 opts = _byteskwargs(opts)
802 timer, fm = gettimer(ui, opts)
802 timer, fm = gettimer(ui, opts)
803 try:
803 try:
804 oldquiet = repo.ui.quiet
804 oldquiet = repo.ui.quiet
805 repo.ui.quiet = True
805 repo.ui.quiet = True
806 matcher = scmutil.match(repo[None])
806 matcher = scmutil.match(repo[None])
807 opts[b'dry_run'] = True
807 opts[b'dry_run'] = True
808 if b'uipathfn' in getargspec(scmutil.addremove).args:
808 if b'uipathfn' in getargspec(scmutil.addremove).args:
809 uipathfn = scmutil.getuipathfn(repo)
809 uipathfn = scmutil.getuipathfn(repo)
810 timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts))
810 timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts))
811 else:
811 else:
812 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
812 timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
813 finally:
813 finally:
814 repo.ui.quiet = oldquiet
814 repo.ui.quiet = oldquiet
815 fm.end()
815 fm.end()
816
816
817
817
818 def clearcaches(cl):
818 def clearcaches(cl):
819 # behave somewhat consistently across internal API changes
819 # behave somewhat consistently across internal API changes
820 if util.safehasattr(cl, b'clearcaches'):
820 if util.safehasattr(cl, b'clearcaches'):
821 cl.clearcaches()
821 cl.clearcaches()
822 elif util.safehasattr(cl, b'_nodecache'):
822 elif util.safehasattr(cl, b'_nodecache'):
823 from mercurial.node import nullid, nullrev
823 from mercurial.node import nullid, nullrev
824
824
825 cl._nodecache = {nullid: nullrev}
825 cl._nodecache = {nullid: nullrev}
826 cl._nodepos = None
826 cl._nodepos = None
827
827
828
828
829 @command(b'perfheads', formatteropts)
829 @command(b'perfheads', formatteropts)
830 def perfheads(ui, repo, **opts):
830 def perfheads(ui, repo, **opts):
831 """benchmark the computation of a changelog heads"""
831 """benchmark the computation of a changelog heads"""
832 opts = _byteskwargs(opts)
832 opts = _byteskwargs(opts)
833 timer, fm = gettimer(ui, opts)
833 timer, fm = gettimer(ui, opts)
834 cl = repo.changelog
834 cl = repo.changelog
835
835
836 def s():
836 def s():
837 clearcaches(cl)
837 clearcaches(cl)
838
838
839 def d():
839 def d():
840 len(cl.headrevs())
840 len(cl.headrevs())
841
841
842 timer(d, setup=s)
842 timer(d, setup=s)
843 fm.end()
843 fm.end()
844
844
845
845
846 @command(
846 @command(
847 b'perftags',
847 b'perftags',
848 formatteropts
848 formatteropts
849 + [(b'', b'clear-revlogs', False, b'refresh changelog and manifest'),],
849 + [(b'', b'clear-revlogs', False, b'refresh changelog and manifest'),],
850 )
850 )
851 def perftags(ui, repo, **opts):
851 def perftags(ui, repo, **opts):
852 opts = _byteskwargs(opts)
852 opts = _byteskwargs(opts)
853 timer, fm = gettimer(ui, opts)
853 timer, fm = gettimer(ui, opts)
854 repocleartagscache = repocleartagscachefunc(repo)
854 repocleartagscache = repocleartagscachefunc(repo)
855 clearrevlogs = opts[b'clear_revlogs']
855 clearrevlogs = opts[b'clear_revlogs']
856
856
857 def s():
857 def s():
858 if clearrevlogs:
858 if clearrevlogs:
859 clearchangelog(repo)
859 clearchangelog(repo)
860 clearfilecache(repo.unfiltered(), 'manifest')
860 clearfilecache(repo.unfiltered(), 'manifest')
861 repocleartagscache()
861 repocleartagscache()
862
862
863 def t():
863 def t():
864 return len(repo.tags())
864 return len(repo.tags())
865
865
866 timer(t, setup=s)
866 timer(t, setup=s)
867 fm.end()
867 fm.end()
868
868
869
869
870 @command(b'perfancestors', formatteropts)
870 @command(b'perfancestors', formatteropts)
871 def perfancestors(ui, repo, **opts):
871 def perfancestors(ui, repo, **opts):
872 opts = _byteskwargs(opts)
872 opts = _byteskwargs(opts)
873 timer, fm = gettimer(ui, opts)
873 timer, fm = gettimer(ui, opts)
874 heads = repo.changelog.headrevs()
874 heads = repo.changelog.headrevs()
875
875
876 def d():
876 def d():
877 for a in repo.changelog.ancestors(heads):
877 for a in repo.changelog.ancestors(heads):
878 pass
878 pass
879
879
880 timer(d)
880 timer(d)
881 fm.end()
881 fm.end()
882
882
883
883
884 @command(b'perfancestorset', formatteropts)
884 @command(b'perfancestorset', formatteropts)
885 def perfancestorset(ui, repo, revset, **opts):
885 def perfancestorset(ui, repo, revset, **opts):
886 opts = _byteskwargs(opts)
886 opts = _byteskwargs(opts)
887 timer, fm = gettimer(ui, opts)
887 timer, fm = gettimer(ui, opts)
888 revs = repo.revs(revset)
888 revs = repo.revs(revset)
889 heads = repo.changelog.headrevs()
889 heads = repo.changelog.headrevs()
890
890
891 def d():
891 def d():
892 s = repo.changelog.ancestors(heads)
892 s = repo.changelog.ancestors(heads)
893 for rev in revs:
893 for rev in revs:
894 rev in s
894 rev in s
895
895
896 timer(d)
896 timer(d)
897 fm.end()
897 fm.end()
898
898
899
899
900 @command(b'perfdiscovery', formatteropts, b'PATH')
900 @command(b'perfdiscovery', formatteropts, b'PATH')
901 def perfdiscovery(ui, repo, path, **opts):
901 def perfdiscovery(ui, repo, path, **opts):
902 """benchmark discovery between local repo and the peer at given path
902 """benchmark discovery between local repo and the peer at given path
903 """
903 """
904 repos = [repo, None]
904 repos = [repo, None]
905 timer, fm = gettimer(ui, opts)
905 timer, fm = gettimer(ui, opts)
906 path = ui.expandpath(path)
906 path = ui.expandpath(path)
907
907
908 def s():
908 def s():
909 repos[1] = hg.peer(ui, opts, path)
909 repos[1] = hg.peer(ui, opts, path)
910
910
911 def d():
911 def d():
912 setdiscovery.findcommonheads(ui, *repos)
912 setdiscovery.findcommonheads(ui, *repos)
913
913
914 timer(d, setup=s)
914 timer(d, setup=s)
915 fm.end()
915 fm.end()
916
916
917
917
918 @command(
918 @command(
919 b'perfbookmarks',
919 b'perfbookmarks',
920 formatteropts
920 formatteropts
921 + [(b'', b'clear-revlogs', False, b'refresh changelog and manifest'),],
921 + [(b'', b'clear-revlogs', False, b'refresh changelog and manifest'),],
922 )
922 )
923 def perfbookmarks(ui, repo, **opts):
923 def perfbookmarks(ui, repo, **opts):
924 """benchmark parsing bookmarks from disk to memory"""
924 """benchmark parsing bookmarks from disk to memory"""
925 opts = _byteskwargs(opts)
925 opts = _byteskwargs(opts)
926 timer, fm = gettimer(ui, opts)
926 timer, fm = gettimer(ui, opts)
927
927
928 clearrevlogs = opts[b'clear_revlogs']
928 clearrevlogs = opts[b'clear_revlogs']
929
929
930 def s():
930 def s():
931 if clearrevlogs:
931 if clearrevlogs:
932 clearchangelog(repo)
932 clearchangelog(repo)
933 clearfilecache(repo, b'_bookmarks')
933 clearfilecache(repo, b'_bookmarks')
934
934
935 def d():
935 def d():
936 repo._bookmarks
936 repo._bookmarks
937
937
938 timer(d, setup=s)
938 timer(d, setup=s)
939 fm.end()
939 fm.end()
940
940
941
941
942 @command(b'perfbundleread', formatteropts, b'BUNDLE')
942 @command(b'perfbundleread', formatteropts, b'BUNDLE')
943 def perfbundleread(ui, repo, bundlepath, **opts):
943 def perfbundleread(ui, repo, bundlepath, **opts):
944 """Benchmark reading of bundle files.
944 """Benchmark reading of bundle files.
945
945
946 This command is meant to isolate the I/O part of bundle reading as
946 This command is meant to isolate the I/O part of bundle reading as
947 much as possible.
947 much as possible.
948 """
948 """
949 from mercurial import (
949 from mercurial import (
950 bundle2,
950 bundle2,
951 exchange,
951 exchange,
952 streamclone,
952 streamclone,
953 )
953 )
954
954
955 opts = _byteskwargs(opts)
955 opts = _byteskwargs(opts)
956
956
957 def makebench(fn):
957 def makebench(fn):
958 def run():
958 def run():
959 with open(bundlepath, b'rb') as fh:
959 with open(bundlepath, b'rb') as fh:
960 bundle = exchange.readbundle(ui, fh, bundlepath)
960 bundle = exchange.readbundle(ui, fh, bundlepath)
961 fn(bundle)
961 fn(bundle)
962
962
963 return run
963 return run
964
964
965 def makereadnbytes(size):
965 def makereadnbytes(size):
966 def run():
966 def run():
967 with open(bundlepath, b'rb') as fh:
967 with open(bundlepath, b'rb') as fh:
968 bundle = exchange.readbundle(ui, fh, bundlepath)
968 bundle = exchange.readbundle(ui, fh, bundlepath)
969 while bundle.read(size):
969 while bundle.read(size):
970 pass
970 pass
971
971
972 return run
972 return run
973
973
974 def makestdioread(size):
974 def makestdioread(size):
975 def run():
975 def run():
976 with open(bundlepath, b'rb') as fh:
976 with open(bundlepath, b'rb') as fh:
977 while fh.read(size):
977 while fh.read(size):
978 pass
978 pass
979
979
980 return run
980 return run
981
981
982 # bundle1
982 # bundle1
983
983
984 def deltaiter(bundle):
984 def deltaiter(bundle):
985 for delta in bundle.deltaiter():
985 for delta in bundle.deltaiter():
986 pass
986 pass
987
987
988 def iterchunks(bundle):
988 def iterchunks(bundle):
989 for chunk in bundle.getchunks():
989 for chunk in bundle.getchunks():
990 pass
990 pass
991
991
992 # bundle2
992 # bundle2
993
993
994 def forwardchunks(bundle):
994 def forwardchunks(bundle):
995 for chunk in bundle._forwardchunks():
995 for chunk in bundle._forwardchunks():
996 pass
996 pass
997
997
998 def iterparts(bundle):
998 def iterparts(bundle):
999 for part in bundle.iterparts():
999 for part in bundle.iterparts():
1000 pass
1000 pass
1001
1001
1002 def iterpartsseekable(bundle):
1002 def iterpartsseekable(bundle):
1003 for part in bundle.iterparts(seekable=True):
1003 for part in bundle.iterparts(seekable=True):
1004 pass
1004 pass
1005
1005
1006 def seek(bundle):
1006 def seek(bundle):
1007 for part in bundle.iterparts(seekable=True):
1007 for part in bundle.iterparts(seekable=True):
1008 part.seek(0, os.SEEK_END)
1008 part.seek(0, os.SEEK_END)
1009
1009
1010 def makepartreadnbytes(size):
1010 def makepartreadnbytes(size):
1011 def run():
1011 def run():
1012 with open(bundlepath, b'rb') as fh:
1012 with open(bundlepath, b'rb') as fh:
1013 bundle = exchange.readbundle(ui, fh, bundlepath)
1013 bundle = exchange.readbundle(ui, fh, bundlepath)
1014 for part in bundle.iterparts():
1014 for part in bundle.iterparts():
1015 while part.read(size):
1015 while part.read(size):
1016 pass
1016 pass
1017
1017
1018 return run
1018 return run
1019
1019
1020 benches = [
1020 benches = [
1021 (makestdioread(8192), b'read(8k)'),
1021 (makestdioread(8192), b'read(8k)'),
1022 (makestdioread(16384), b'read(16k)'),
1022 (makestdioread(16384), b'read(16k)'),
1023 (makestdioread(32768), b'read(32k)'),
1023 (makestdioread(32768), b'read(32k)'),
1024 (makestdioread(131072), b'read(128k)'),
1024 (makestdioread(131072), b'read(128k)'),
1025 ]
1025 ]
1026
1026
1027 with open(bundlepath, b'rb') as fh:
1027 with open(bundlepath, b'rb') as fh:
1028 bundle = exchange.readbundle(ui, fh, bundlepath)
1028 bundle = exchange.readbundle(ui, fh, bundlepath)
1029
1029
1030 if isinstance(bundle, changegroup.cg1unpacker):
1030 if isinstance(bundle, changegroup.cg1unpacker):
1031 benches.extend(
1031 benches.extend(
1032 [
1032 [
1033 (makebench(deltaiter), b'cg1 deltaiter()'),
1033 (makebench(deltaiter), b'cg1 deltaiter()'),
1034 (makebench(iterchunks), b'cg1 getchunks()'),
1034 (makebench(iterchunks), b'cg1 getchunks()'),
1035 (makereadnbytes(8192), b'cg1 read(8k)'),
1035 (makereadnbytes(8192), b'cg1 read(8k)'),
1036 (makereadnbytes(16384), b'cg1 read(16k)'),
1036 (makereadnbytes(16384), b'cg1 read(16k)'),
1037 (makereadnbytes(32768), b'cg1 read(32k)'),
1037 (makereadnbytes(32768), b'cg1 read(32k)'),
1038 (makereadnbytes(131072), b'cg1 read(128k)'),
1038 (makereadnbytes(131072), b'cg1 read(128k)'),
1039 ]
1039 ]
1040 )
1040 )
1041 elif isinstance(bundle, bundle2.unbundle20):
1041 elif isinstance(bundle, bundle2.unbundle20):
1042 benches.extend(
1042 benches.extend(
1043 [
1043 [
1044 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
1044 (makebench(forwardchunks), b'bundle2 forwardchunks()'),
1045 (makebench(iterparts), b'bundle2 iterparts()'),
1045 (makebench(iterparts), b'bundle2 iterparts()'),
1046 (
1046 (
1047 makebench(iterpartsseekable),
1047 makebench(iterpartsseekable),
1048 b'bundle2 iterparts() seekable',
1048 b'bundle2 iterparts() seekable',
1049 ),
1049 ),
1050 (makebench(seek), b'bundle2 part seek()'),
1050 (makebench(seek), b'bundle2 part seek()'),
1051 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
1051 (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
1052 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
1052 (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
1053 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
1053 (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
1054 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
1054 (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
1055 ]
1055 ]
1056 )
1056 )
1057 elif isinstance(bundle, streamclone.streamcloneapplier):
1057 elif isinstance(bundle, streamclone.streamcloneapplier):
1058 raise error.Abort(b'stream clone bundles not supported')
1058 raise error.Abort(b'stream clone bundles not supported')
1059 else:
1059 else:
1060 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
1060 raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
1061
1061
1062 for fn, title in benches:
1062 for fn, title in benches:
1063 timer, fm = gettimer(ui, opts)
1063 timer, fm = gettimer(ui, opts)
1064 timer(fn, title=title)
1064 timer(fn, title=title)
1065 fm.end()
1065 fm.end()
1066
1066
1067
1067
1068 @command(
1068 @command(
1069 b'perfchangegroupchangelog',
1069 b'perfchangegroupchangelog',
1070 formatteropts
1070 formatteropts
1071 + [
1071 + [
1072 (b'', b'cgversion', b'02', b'changegroup version'),
1072 (b'', b'cgversion', b'02', b'changegroup version'),
1073 (b'r', b'rev', b'', b'revisions to add to changegroup'),
1073 (b'r', b'rev', b'', b'revisions to add to changegroup'),
1074 ],
1074 ],
1075 )
1075 )
1076 def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts):
1076 def perfchangegroupchangelog(ui, repo, cgversion=b'02', rev=None, **opts):
1077 """Benchmark producing a changelog group for a changegroup.
1077 """Benchmark producing a changelog group for a changegroup.
1078
1078
1079 This measures the time spent processing the changelog during a
1079 This measures the time spent processing the changelog during a
1080 bundle operation. This occurs during `hg bundle` and on a server
1080 bundle operation. This occurs during `hg bundle` and on a server
1081 processing a `getbundle` wire protocol request (handles clones
1081 processing a `getbundle` wire protocol request (handles clones
1082 and pull requests).
1082 and pull requests).
1083
1083
1084 By default, all revisions are added to the changegroup.
1084 By default, all revisions are added to the changegroup.
1085 """
1085 """
1086 opts = _byteskwargs(opts)
1086 opts = _byteskwargs(opts)
1087 cl = repo.changelog
1087 cl = repo.changelog
1088 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
1088 nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
1089 bundler = changegroup.getbundler(cgversion, repo)
1089 bundler = changegroup.getbundler(cgversion, repo)
1090
1090
1091 def d():
1091 def d():
1092 state, chunks = bundler._generatechangelog(cl, nodes)
1092 state, chunks = bundler._generatechangelog(cl, nodes)
1093 for chunk in chunks:
1093 for chunk in chunks:
1094 pass
1094 pass
1095
1095
1096 timer, fm = gettimer(ui, opts)
1096 timer, fm = gettimer(ui, opts)
1097
1097
1098 # Terminal printing can interfere with timing. So disable it.
1098 # Terminal printing can interfere with timing. So disable it.
1099 with ui.configoverride({(b'progress', b'disable'): True}):
1099 with ui.configoverride({(b'progress', b'disable'): True}):
1100 timer(d)
1100 timer(d)
1101
1101
1102 fm.end()
1102 fm.end()
1103
1103
1104
1104
1105 @command(b'perfdirs', formatteropts)
1105 @command(b'perfdirs', formatteropts)
1106 def perfdirs(ui, repo, **opts):
1106 def perfdirs(ui, repo, **opts):
1107 opts = _byteskwargs(opts)
1107 opts = _byteskwargs(opts)
1108 timer, fm = gettimer(ui, opts)
1108 timer, fm = gettimer(ui, opts)
1109 dirstate = repo.dirstate
1109 dirstate = repo.dirstate
1110 b'a' in dirstate
1110 b'a' in dirstate
1111
1111
1112 def d():
1112 def d():
1113 dirstate.hasdir(b'a')
1113 dirstate.hasdir(b'a')
1114 del dirstate._map._dirs
1114 del dirstate._map._dirs
1115
1115
1116 timer(d)
1116 timer(d)
1117 fm.end()
1117 fm.end()
1118
1118
1119
1119
1120 @command(
1120 @command(
1121 b'perfdirstate',
1121 b'perfdirstate',
1122 [
1122 [
1123 (
1123 (
1124 b'',
1124 b'',
1125 b'iteration',
1125 b'iteration',
1126 None,
1126 None,
1127 b'benchmark a full iteration for the dirstate',
1127 b'benchmark a full iteration for the dirstate',
1128 ),
1128 ),
1129 (
1129 (
1130 b'',
1130 b'',
1131 b'contains',
1131 b'contains',
1132 None,
1132 None,
1133 b'benchmark a large amount of `nf in dirstate` calls',
1133 b'benchmark a large amount of `nf in dirstate` calls',
1134 ),
1134 ),
1135 ]
1135 ]
1136 + formatteropts,
1136 + formatteropts,
1137 )
1137 )
1138 def perfdirstate(ui, repo, **opts):
1138 def perfdirstate(ui, repo, **opts):
1139 """benchmap the time of various distate operations
1139 """benchmap the time of various distate operations
1140
1140
1141 By default benchmark the time necessary to load a dirstate from scratch.
1141 By default benchmark the time necessary to load a dirstate from scratch.
1142 The dirstate is loaded to the point were a "contains" request can be
1142 The dirstate is loaded to the point were a "contains" request can be
1143 answered.
1143 answered.
1144 """
1144 """
1145 opts = _byteskwargs(opts)
1145 opts = _byteskwargs(opts)
1146 timer, fm = gettimer(ui, opts)
1146 timer, fm = gettimer(ui, opts)
1147 b"a" in repo.dirstate
1147 b"a" in repo.dirstate
1148
1148
1149 if opts[b'iteration'] and opts[b'contains']:
1149 if opts[b'iteration'] and opts[b'contains']:
1150 msg = b'only specify one of --iteration or --contains'
1150 msg = b'only specify one of --iteration or --contains'
1151 raise error.Abort(msg)
1151 raise error.Abort(msg)
1152
1152
1153 if opts[b'iteration']:
1153 if opts[b'iteration']:
1154 setup = None
1154 setup = None
1155 dirstate = repo.dirstate
1155 dirstate = repo.dirstate
1156
1156
1157 def d():
1157 def d():
1158 for f in dirstate:
1158 for f in dirstate:
1159 pass
1159 pass
1160
1160
1161 elif opts[b'contains']:
1161 elif opts[b'contains']:
1162 setup = None
1162 setup = None
1163 dirstate = repo.dirstate
1163 dirstate = repo.dirstate
1164 allfiles = list(dirstate)
1164 allfiles = list(dirstate)
1165 # also add file path that will be "missing" from the dirstate
1165 # also add file path that will be "missing" from the dirstate
1166 allfiles.extend([f[::-1] for f in allfiles])
1166 allfiles.extend([f[::-1] for f in allfiles])
1167
1167
1168 def d():
1168 def d():
1169 for f in allfiles:
1169 for f in allfiles:
1170 f in dirstate
1170 f in dirstate
1171
1171
1172 else:
1172 else:
1173
1173
1174 def setup():
1174 def setup():
1175 repo.dirstate.invalidate()
1175 repo.dirstate.invalidate()
1176
1176
1177 def d():
1177 def d():
1178 b"a" in repo.dirstate
1178 b"a" in repo.dirstate
1179
1179
1180 timer(d, setup=setup)
1180 timer(d, setup=setup)
1181 fm.end()
1181 fm.end()
1182
1182
1183
1183
1184 @command(b'perfdirstatedirs', formatteropts)
1184 @command(b'perfdirstatedirs', formatteropts)
1185 def perfdirstatedirs(ui, repo, **opts):
1185 def perfdirstatedirs(ui, repo, **opts):
1186 """benchmap a 'dirstate.hasdir' call from an empty `dirs` cache
1186 """benchmap a 'dirstate.hasdir' call from an empty `dirs` cache
1187 """
1187 """
1188 opts = _byteskwargs(opts)
1188 opts = _byteskwargs(opts)
1189 timer, fm = gettimer(ui, opts)
1189 timer, fm = gettimer(ui, opts)
1190 repo.dirstate.hasdir(b"a")
1190 repo.dirstate.hasdir(b"a")
1191
1191
1192 def setup():
1192 def setup():
1193 del repo.dirstate._map._dirs
1193 del repo.dirstate._map._dirs
1194
1194
1195 def d():
1195 def d():
1196 repo.dirstate.hasdir(b"a")
1196 repo.dirstate.hasdir(b"a")
1197
1197
1198 timer(d, setup=setup)
1198 timer(d, setup=setup)
1199 fm.end()
1199 fm.end()
1200
1200
1201
1201
1202 @command(b'perfdirstatefoldmap', formatteropts)
1202 @command(b'perfdirstatefoldmap', formatteropts)
1203 def perfdirstatefoldmap(ui, repo, **opts):
1203 def perfdirstatefoldmap(ui, repo, **opts):
1204 """benchmap a `dirstate._map.filefoldmap.get()` request
1204 """benchmap a `dirstate._map.filefoldmap.get()` request
1205
1205
1206 The dirstate filefoldmap cache is dropped between every request.
1206 The dirstate filefoldmap cache is dropped between every request.
1207 """
1207 """
1208 opts = _byteskwargs(opts)
1208 opts = _byteskwargs(opts)
1209 timer, fm = gettimer(ui, opts)
1209 timer, fm = gettimer(ui, opts)
1210 dirstate = repo.dirstate
1210 dirstate = repo.dirstate
1211 dirstate._map.filefoldmap.get(b'a')
1211 dirstate._map.filefoldmap.get(b'a')
1212
1212
1213 def setup():
1213 def setup():
1214 del dirstate._map.filefoldmap
1214 del dirstate._map.filefoldmap
1215
1215
1216 def d():
1216 def d():
1217 dirstate._map.filefoldmap.get(b'a')
1217 dirstate._map.filefoldmap.get(b'a')
1218
1218
1219 timer(d, setup=setup)
1219 timer(d, setup=setup)
1220 fm.end()
1220 fm.end()
1221
1221
1222
1222
1223 @command(b'perfdirfoldmap', formatteropts)
1223 @command(b'perfdirfoldmap', formatteropts)
1224 def perfdirfoldmap(ui, repo, **opts):
1224 def perfdirfoldmap(ui, repo, **opts):
1225 """benchmap a `dirstate._map.dirfoldmap.get()` request
1225 """benchmap a `dirstate._map.dirfoldmap.get()` request
1226
1226
1227 The dirstate dirfoldmap cache is dropped between every request.
1227 The dirstate dirfoldmap cache is dropped between every request.
1228 """
1228 """
1229 opts = _byteskwargs(opts)
1229 opts = _byteskwargs(opts)
1230 timer, fm = gettimer(ui, opts)
1230 timer, fm = gettimer(ui, opts)
1231 dirstate = repo.dirstate
1231 dirstate = repo.dirstate
1232 dirstate._map.dirfoldmap.get(b'a')
1232 dirstate._map.dirfoldmap.get(b'a')
1233
1233
1234 def setup():
1234 def setup():
1235 del dirstate._map.dirfoldmap
1235 del dirstate._map.dirfoldmap
1236 del dirstate._map._dirs
1236 del dirstate._map._dirs
1237
1237
1238 def d():
1238 def d():
1239 dirstate._map.dirfoldmap.get(b'a')
1239 dirstate._map.dirfoldmap.get(b'a')
1240
1240
1241 timer(d, setup=setup)
1241 timer(d, setup=setup)
1242 fm.end()
1242 fm.end()
1243
1243
1244
1244
1245 @command(b'perfdirstatewrite', formatteropts)
1245 @command(b'perfdirstatewrite', formatteropts)
1246 def perfdirstatewrite(ui, repo, **opts):
1246 def perfdirstatewrite(ui, repo, **opts):
1247 """benchmap the time it take to write a dirstate on disk
1247 """benchmap the time it take to write a dirstate on disk
1248 """
1248 """
1249 opts = _byteskwargs(opts)
1249 opts = _byteskwargs(opts)
1250 timer, fm = gettimer(ui, opts)
1250 timer, fm = gettimer(ui, opts)
1251 ds = repo.dirstate
1251 ds = repo.dirstate
1252 b"a" in ds
1252 b"a" in ds
1253
1253
1254 def setup():
1254 def setup():
1255 ds._dirty = True
1255 ds._dirty = True
1256
1256
1257 def d():
1257 def d():
1258 ds.write(repo.currenttransaction())
1258 ds.write(repo.currenttransaction())
1259
1259
1260 timer(d, setup=setup)
1260 timer(d, setup=setup)
1261 fm.end()
1261 fm.end()
1262
1262
1263
1263
1264 def _getmergerevs(repo, opts):
1264 def _getmergerevs(repo, opts):
1265 """parse command argument to return rev involved in merge
1265 """parse command argument to return rev involved in merge
1266
1266
1267 input: options dictionnary with `rev`, `from` and `bse`
1267 input: options dictionnary with `rev`, `from` and `bse`
1268 output: (localctx, otherctx, basectx)
1268 output: (localctx, otherctx, basectx)
1269 """
1269 """
1270 if opts[b'from']:
1270 if opts[b'from']:
1271 fromrev = scmutil.revsingle(repo, opts[b'from'])
1271 fromrev = scmutil.revsingle(repo, opts[b'from'])
1272 wctx = repo[fromrev]
1272 wctx = repo[fromrev]
1273 else:
1273 else:
1274 wctx = repo[None]
1274 wctx = repo[None]
1275 # we don't want working dir files to be stat'd in the benchmark, so
1275 # we don't want working dir files to be stat'd in the benchmark, so
1276 # prime that cache
1276 # prime that cache
1277 wctx.dirty()
1277 wctx.dirty()
1278 rctx = scmutil.revsingle(repo, opts[b'rev'], opts[b'rev'])
1278 rctx = scmutil.revsingle(repo, opts[b'rev'], opts[b'rev'])
1279 if opts[b'base']:
1279 if opts[b'base']:
1280 fromrev = scmutil.revsingle(repo, opts[b'base'])
1280 fromrev = scmutil.revsingle(repo, opts[b'base'])
1281 ancestor = repo[fromrev]
1281 ancestor = repo[fromrev]
1282 else:
1282 else:
1283 ancestor = wctx.ancestor(rctx)
1283 ancestor = wctx.ancestor(rctx)
1284 return (wctx, rctx, ancestor)
1284 return (wctx, rctx, ancestor)
1285
1285
1286
1286
1287 @command(
1287 @command(
1288 b'perfmergecalculate',
1288 b'perfmergecalculate',
1289 [
1289 [
1290 (b'r', b'rev', b'.', b'rev to merge against'),
1290 (b'r', b'rev', b'.', b'rev to merge against'),
1291 (b'', b'from', b'', b'rev to merge from'),
1291 (b'', b'from', b'', b'rev to merge from'),
1292 (b'', b'base', b'', b'the revision to use as base'),
1292 (b'', b'base', b'', b'the revision to use as base'),
1293 ]
1293 ]
1294 + formatteropts,
1294 + formatteropts,
1295 )
1295 )
1296 def perfmergecalculate(ui, repo, **opts):
1296 def perfmergecalculate(ui, repo, **opts):
1297 opts = _byteskwargs(opts)
1297 opts = _byteskwargs(opts)
1298 timer, fm = gettimer(ui, opts)
1298 timer, fm = gettimer(ui, opts)
1299
1299
1300 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1300 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1301
1301
1302 def d():
1302 def d():
1303 # acceptremote is True because we don't want prompts in the middle of
1303 # acceptremote is True because we don't want prompts in the middle of
1304 # our benchmark
1304 # our benchmark
1305 merge.calculateupdates(
1305 merge.calculateupdates(
1306 repo,
1306 repo,
1307 wctx,
1307 wctx,
1308 rctx,
1308 rctx,
1309 [ancestor],
1309 [ancestor],
1310 branchmerge=False,
1310 branchmerge=False,
1311 force=False,
1311 force=False,
1312 acceptremote=True,
1312 acceptremote=True,
1313 followcopies=True,
1313 followcopies=True,
1314 )
1314 )
1315
1315
1316 timer(d)
1316 timer(d)
1317 fm.end()
1317 fm.end()
1318
1318
1319
1319
1320 @command(
1320 @command(
1321 b'perfmergecopies',
1321 b'perfmergecopies',
1322 [
1322 [
1323 (b'r', b'rev', b'.', b'rev to merge against'),
1323 (b'r', b'rev', b'.', b'rev to merge against'),
1324 (b'', b'from', b'', b'rev to merge from'),
1324 (b'', b'from', b'', b'rev to merge from'),
1325 (b'', b'base', b'', b'the revision to use as base'),
1325 (b'', b'base', b'', b'the revision to use as base'),
1326 ]
1326 ]
1327 + formatteropts,
1327 + formatteropts,
1328 )
1328 )
1329 def perfmergecopies(ui, repo, **opts):
1329 def perfmergecopies(ui, repo, **opts):
1330 """measure runtime of `copies.mergecopies`"""
1330 """measure runtime of `copies.mergecopies`"""
1331 opts = _byteskwargs(opts)
1331 opts = _byteskwargs(opts)
1332 timer, fm = gettimer(ui, opts)
1332 timer, fm = gettimer(ui, opts)
1333 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1333 wctx, rctx, ancestor = _getmergerevs(repo, opts)
1334
1334
1335 def d():
1335 def d():
1336 # acceptremote is True because we don't want prompts in the middle of
1336 # acceptremote is True because we don't want prompts in the middle of
1337 # our benchmark
1337 # our benchmark
1338 copies.mergecopies(repo, wctx, rctx, ancestor)
1338 copies.mergecopies(repo, wctx, rctx, ancestor)
1339
1339
1340 timer(d)
1340 timer(d)
1341 fm.end()
1341 fm.end()
1342
1342
1343
1343
1344 @command(b'perfpathcopies', [], b"REV REV")
1344 @command(b'perfpathcopies', [], b"REV REV")
1345 def perfpathcopies(ui, repo, rev1, rev2, **opts):
1345 def perfpathcopies(ui, repo, rev1, rev2, **opts):
1346 """benchmark the copy tracing logic"""
1346 """benchmark the copy tracing logic"""
1347 opts = _byteskwargs(opts)
1347 opts = _byteskwargs(opts)
1348 timer, fm = gettimer(ui, opts)
1348 timer, fm = gettimer(ui, opts)
1349 ctx1 = scmutil.revsingle(repo, rev1, rev1)
1349 ctx1 = scmutil.revsingle(repo, rev1, rev1)
1350 ctx2 = scmutil.revsingle(repo, rev2, rev2)
1350 ctx2 = scmutil.revsingle(repo, rev2, rev2)
1351
1351
1352 def d():
1352 def d():
1353 copies.pathcopies(ctx1, ctx2)
1353 copies.pathcopies(ctx1, ctx2)
1354
1354
1355 timer(d)
1355 timer(d)
1356 fm.end()
1356 fm.end()
1357
1357
1358
1358
1359 @command(
1359 @command(
1360 b'perfphases',
1360 b'perfphases',
1361 [(b'', b'full', False, b'include file reading time too'),],
1361 [(b'', b'full', False, b'include file reading time too'),],
1362 b"",
1362 b"",
1363 )
1363 )
1364 def perfphases(ui, repo, **opts):
1364 def perfphases(ui, repo, **opts):
1365 """benchmark phasesets computation"""
1365 """benchmark phasesets computation"""
1366 opts = _byteskwargs(opts)
1366 opts = _byteskwargs(opts)
1367 timer, fm = gettimer(ui, opts)
1367 timer, fm = gettimer(ui, opts)
1368 _phases = repo._phasecache
1368 _phases = repo._phasecache
1369 full = opts.get(b'full')
1369 full = opts.get(b'full')
1370
1370
1371 def d():
1371 def d():
1372 phases = _phases
1372 phases = _phases
1373 if full:
1373 if full:
1374 clearfilecache(repo, b'_phasecache')
1374 clearfilecache(repo, b'_phasecache')
1375 phases = repo._phasecache
1375 phases = repo._phasecache
1376 phases.invalidate()
1376 phases.invalidate()
1377 phases.loadphaserevs(repo)
1377 phases.loadphaserevs(repo)
1378
1378
1379 timer(d)
1379 timer(d)
1380 fm.end()
1380 fm.end()
1381
1381
1382
1382
1383 @command(b'perfphasesremote', [], b"[DEST]")
1383 @command(b'perfphasesremote', [], b"[DEST]")
1384 def perfphasesremote(ui, repo, dest=None, **opts):
1384 def perfphasesremote(ui, repo, dest=None, **opts):
1385 """benchmark time needed to analyse phases of the remote server"""
1385 """benchmark time needed to analyse phases of the remote server"""
1386 from mercurial.node import bin
1386 from mercurial.node import bin
1387 from mercurial import (
1387 from mercurial import (
1388 exchange,
1388 exchange,
1389 hg,
1389 hg,
1390 phases,
1390 phases,
1391 )
1391 )
1392
1392
1393 opts = _byteskwargs(opts)
1393 opts = _byteskwargs(opts)
1394 timer, fm = gettimer(ui, opts)
1394 timer, fm = gettimer(ui, opts)
1395
1395
1396 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
1396 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
1397 if not path:
1397 if not path:
1398 raise error.Abort(
1398 raise error.Abort(
1399 b'default repository not configured!',
1399 b'default repository not configured!',
1400 hint=b"see 'hg help config.paths'",
1400 hint=b"see 'hg help config.paths'",
1401 )
1401 )
1402 dest = path.pushloc or path.loc
1402 dest = path.pushloc or path.loc
1403 ui.statusnoi18n(b'analysing phase of %s\n' % util.hidepassword(dest))
1403 ui.statusnoi18n(b'analysing phase of %s\n' % util.hidepassword(dest))
1404 other = hg.peer(repo, opts, dest)
1404 other = hg.peer(repo, opts, dest)
1405
1405
1406 # easier to perform discovery through the operation
1406 # easier to perform discovery through the operation
1407 op = exchange.pushoperation(repo, other)
1407 op = exchange.pushoperation(repo, other)
1408 exchange._pushdiscoverychangeset(op)
1408 exchange._pushdiscoverychangeset(op)
1409
1409
1410 remotesubset = op.fallbackheads
1410 remotesubset = op.fallbackheads
1411
1411
1412 with other.commandexecutor() as e:
1412 with other.commandexecutor() as e:
1413 remotephases = e.callcommand(
1413 remotephases = e.callcommand(
1414 b'listkeys', {b'namespace': b'phases'}
1414 b'listkeys', {b'namespace': b'phases'}
1415 ).result()
1415 ).result()
1416 del other
1416 del other
1417 publishing = remotephases.get(b'publishing', False)
1417 publishing = remotephases.get(b'publishing', False)
1418 if publishing:
1418 if publishing:
1419 ui.statusnoi18n(b'publishing: yes\n')
1419 ui.statusnoi18n(b'publishing: yes\n')
1420 else:
1420 else:
1421 ui.statusnoi18n(b'publishing: no\n')
1421 ui.statusnoi18n(b'publishing: no\n')
1422
1422
1423 nodemap = repo.changelog.nodemap
1423 nodemap = repo.changelog.nodemap
1424 nonpublishroots = 0
1424 nonpublishroots = 0
1425 for nhex, phase in remotephases.iteritems():
1425 for nhex, phase in remotephases.iteritems():
1426 if nhex == b'publishing': # ignore data related to publish option
1426 if nhex == b'publishing': # ignore data related to publish option
1427 continue
1427 continue
1428 node = bin(nhex)
1428 node = bin(nhex)
1429 if node in nodemap and int(phase):
1429 if node in nodemap and int(phase):
1430 nonpublishroots += 1
1430 nonpublishroots += 1
1431 ui.statusnoi18n(b'number of roots: %d\n' % len(remotephases))
1431 ui.statusnoi18n(b'number of roots: %d\n' % len(remotephases))
1432 ui.statusnoi18n(b'number of known non public roots: %d\n' % nonpublishroots)
1432 ui.statusnoi18n(b'number of known non public roots: %d\n' % nonpublishroots)
1433
1433
1434 def d():
1434 def d():
1435 phases.remotephasessummary(repo, remotesubset, remotephases)
1435 phases.remotephasessummary(repo, remotesubset, remotephases)
1436
1436
1437 timer(d)
1437 timer(d)
1438 fm.end()
1438 fm.end()
1439
1439
1440
1440
1441 @command(
1441 @command(
1442 b'perfmanifest',
1442 b'perfmanifest',
1443 [
1443 [
1444 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
1444 (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
1445 (b'', b'clear-disk', False, b'clear on-disk caches too'),
1445 (b'', b'clear-disk', False, b'clear on-disk caches too'),
1446 ]
1446 ]
1447 + formatteropts,
1447 + formatteropts,
1448 b'REV|NODE',
1448 b'REV|NODE',
1449 )
1449 )
1450 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
1450 def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
1451 """benchmark the time to read a manifest from disk and return a usable
1451 """benchmark the time to read a manifest from disk and return a usable
1452 dict-like object
1452 dict-like object
1453
1453
1454 Manifest caches are cleared before retrieval."""
1454 Manifest caches are cleared before retrieval."""
1455 opts = _byteskwargs(opts)
1455 opts = _byteskwargs(opts)
1456 timer, fm = gettimer(ui, opts)
1456 timer, fm = gettimer(ui, opts)
1457 if not manifest_rev:
1457 if not manifest_rev:
1458 ctx = scmutil.revsingle(repo, rev, rev)
1458 ctx = scmutil.revsingle(repo, rev, rev)
1459 t = ctx.manifestnode()
1459 t = ctx.manifestnode()
1460 else:
1460 else:
1461 from mercurial.node import bin
1461 from mercurial.node import bin
1462
1462
1463 if len(rev) == 40:
1463 if len(rev) == 40:
1464 t = bin(rev)
1464 t = bin(rev)
1465 else:
1465 else:
1466 try:
1466 try:
1467 rev = int(rev)
1467 rev = int(rev)
1468
1468
1469 if util.safehasattr(repo.manifestlog, b'getstorage'):
1469 if util.safehasattr(repo.manifestlog, b'getstorage'):
1470 t = repo.manifestlog.getstorage(b'').node(rev)
1470 t = repo.manifestlog.getstorage(b'').node(rev)
1471 else:
1471 else:
1472 t = repo.manifestlog._revlog.lookup(rev)
1472 t = repo.manifestlog._revlog.lookup(rev)
1473 except ValueError:
1473 except ValueError:
1474 raise error.Abort(
1474 raise error.Abort(
1475 b'manifest revision must be integer or full node'
1475 b'manifest revision must be integer or full node'
1476 )
1476 )
1477
1477
1478 def d():
1478 def d():
1479 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
1479 repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
1480 repo.manifestlog[t].read()
1480 repo.manifestlog[t].read()
1481
1481
1482 timer(d)
1482 timer(d)
1483 fm.end()
1483 fm.end()
1484
1484
1485
1485
1486 @command(b'perfchangeset', formatteropts)
1486 @command(b'perfchangeset', formatteropts)
1487 def perfchangeset(ui, repo, rev, **opts):
1487 def perfchangeset(ui, repo, rev, **opts):
1488 opts = _byteskwargs(opts)
1488 opts = _byteskwargs(opts)
1489 timer, fm = gettimer(ui, opts)
1489 timer, fm = gettimer(ui, opts)
1490 n = scmutil.revsingle(repo, rev).node()
1490 n = scmutil.revsingle(repo, rev).node()
1491
1491
1492 def d():
1492 def d():
1493 repo.changelog.read(n)
1493 repo.changelog.read(n)
1494 # repo.changelog._cache = None
1494 # repo.changelog._cache = None
1495
1495
1496 timer(d)
1496 timer(d)
1497 fm.end()
1497 fm.end()
1498
1498
1499
1499
1500 @command(b'perfignore', formatteropts)
1500 @command(b'perfignore', formatteropts)
1501 def perfignore(ui, repo, **opts):
1501 def perfignore(ui, repo, **opts):
1502 """benchmark operation related to computing ignore"""
1502 """benchmark operation related to computing ignore"""
1503 opts = _byteskwargs(opts)
1503 opts = _byteskwargs(opts)
1504 timer, fm = gettimer(ui, opts)
1504 timer, fm = gettimer(ui, opts)
1505 dirstate = repo.dirstate
1505 dirstate = repo.dirstate
1506
1506
1507 def setupone():
1507 def setupone():
1508 dirstate.invalidate()
1508 dirstate.invalidate()
1509 clearfilecache(dirstate, b'_ignore')
1509 clearfilecache(dirstate, b'_ignore')
1510
1510
1511 def runone():
1511 def runone():
1512 dirstate._ignore
1512 dirstate._ignore
1513
1513
1514 timer(runone, setup=setupone, title=b"load")
1514 timer(runone, setup=setupone, title=b"load")
1515 fm.end()
1515 fm.end()
1516
1516
1517
1517
1518 @command(
1518 @command(
1519 b'perfindex',
1519 b'perfindex',
1520 [
1520 [
1521 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1521 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1522 (b'', b'no-lookup', None, b'do not revision lookup post creation'),
1522 (b'', b'no-lookup', None, b'do not revision lookup post creation'),
1523 ]
1523 ]
1524 + formatteropts,
1524 + formatteropts,
1525 )
1525 )
1526 def perfindex(ui, repo, **opts):
1526 def perfindex(ui, repo, **opts):
1527 """benchmark index creation time followed by a lookup
1527 """benchmark index creation time followed by a lookup
1528
1528
1529 The default is to look `tip` up. Depending on the index implementation,
1529 The default is to look `tip` up. Depending on the index implementation,
1530 the revision looked up can matters. For example, an implementation
1530 the revision looked up can matters. For example, an implementation
1531 scanning the index will have a faster lookup time for `--rev tip` than for
1531 scanning the index will have a faster lookup time for `--rev tip` than for
1532 `--rev 0`. The number of looked up revisions and their order can also
1532 `--rev 0`. The number of looked up revisions and their order can also
1533 matters.
1533 matters.
1534
1534
1535 Example of useful set to test:
1535 Example of useful set to test:
1536 * tip
1536 * tip
1537 * 0
1537 * 0
1538 * -10:
1538 * -10:
1539 * :10
1539 * :10
1540 * -10: + :10
1540 * -10: + :10
1541 * :10: + -10:
1541 * :10: + -10:
1542 * -10000:
1542 * -10000:
1543 * -10000: + 0
1543 * -10000: + 0
1544
1544
1545 It is not currently possible to check for lookup of a missing node. For
1545 It is not currently possible to check for lookup of a missing node. For
1546 deeper lookup benchmarking, checkout the `perfnodemap` command."""
1546 deeper lookup benchmarking, checkout the `perfnodemap` command."""
1547 import mercurial.revlog
1547 import mercurial.revlog
1548
1548
1549 opts = _byteskwargs(opts)
1549 opts = _byteskwargs(opts)
1550 timer, fm = gettimer(ui, opts)
1550 timer, fm = gettimer(ui, opts)
1551 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1551 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1552 if opts[b'no_lookup']:
1552 if opts[b'no_lookup']:
1553 if opts['rev']:
1553 if opts['rev']:
1554 raise error.Abort('--no-lookup and --rev are mutually exclusive')
1554 raise error.Abort('--no-lookup and --rev are mutually exclusive')
1555 nodes = []
1555 nodes = []
1556 elif not opts[b'rev']:
1556 elif not opts[b'rev']:
1557 nodes = [repo[b"tip"].node()]
1557 nodes = [repo[b"tip"].node()]
1558 else:
1558 else:
1559 revs = scmutil.revrange(repo, opts[b'rev'])
1559 revs = scmutil.revrange(repo, opts[b'rev'])
1560 cl = repo.changelog
1560 cl = repo.changelog
1561 nodes = [cl.node(r) for r in revs]
1561 nodes = [cl.node(r) for r in revs]
1562
1562
1563 unfi = repo.unfiltered()
1563 unfi = repo.unfiltered()
1564 # find the filecache func directly
1564 # find the filecache func directly
1565 # This avoid polluting the benchmark with the filecache logic
1565 # This avoid polluting the benchmark with the filecache logic
1566 makecl = unfi.__class__.changelog.func
1566 makecl = unfi.__class__.changelog.func
1567
1567
1568 def setup():
1568 def setup():
1569 # probably not necessary, but for good measure
1569 # probably not necessary, but for good measure
1570 clearchangelog(unfi)
1570 clearchangelog(unfi)
1571
1571
1572 def d():
1572 def d():
1573 cl = makecl(unfi)
1573 cl = makecl(unfi)
1574 for n in nodes:
1574 for n in nodes:
1575 cl.rev(n)
1575 cl.rev(n)
1576
1576
1577 timer(d, setup=setup)
1577 timer(d, setup=setup)
1578 fm.end()
1578 fm.end()
1579
1579
1580
1580
1581 @command(
1581 @command(
1582 b'perfnodemap',
1582 b'perfnodemap',
1583 [
1583 [
1584 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1584 (b'', b'rev', [], b'revision to be looked up (default tip)'),
1585 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1585 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
1586 ]
1586 ]
1587 + formatteropts,
1587 + formatteropts,
1588 )
1588 )
1589 def perfnodemap(ui, repo, **opts):
1589 def perfnodemap(ui, repo, **opts):
1590 """benchmark the time necessary to look up revision from a cold nodemap
1590 """benchmark the time necessary to look up revision from a cold nodemap
1591
1591
1592 Depending on the implementation, the amount and order of revision we look
1592 Depending on the implementation, the amount and order of revision we look
1593 up can varies. Example of useful set to test:
1593 up can varies. Example of useful set to test:
1594 * tip
1594 * tip
1595 * 0
1595 * 0
1596 * -10:
1596 * -10:
1597 * :10
1597 * :10
1598 * -10: + :10
1598 * -10: + :10
1599 * :10: + -10:
1599 * :10: + -10:
1600 * -10000:
1600 * -10000:
1601 * -10000: + 0
1601 * -10000: + 0
1602
1602
1603 The command currently focus on valid binary lookup. Benchmarking for
1603 The command currently focus on valid binary lookup. Benchmarking for
1604 hexlookup, prefix lookup and missing lookup would also be valuable.
1604 hexlookup, prefix lookup and missing lookup would also be valuable.
1605 """
1605 """
1606 import mercurial.revlog
1606 import mercurial.revlog
1607
1607
1608 opts = _byteskwargs(opts)
1608 opts = _byteskwargs(opts)
1609 timer, fm = gettimer(ui, opts)
1609 timer, fm = gettimer(ui, opts)
1610 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1610 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1611
1611
1612 unfi = repo.unfiltered()
1612 unfi = repo.unfiltered()
1613 clearcaches = opts['clear_caches']
1613 clearcaches = opts['clear_caches']
1614 # find the filecache func directly
1614 # find the filecache func directly
1615 # This avoid polluting the benchmark with the filecache logic
1615 # This avoid polluting the benchmark with the filecache logic
1616 makecl = unfi.__class__.changelog.func
1616 makecl = unfi.__class__.changelog.func
1617 if not opts[b'rev']:
1617 if not opts[b'rev']:
1618 raise error.Abort('use --rev to specify revisions to look up')
1618 raise error.Abort('use --rev to specify revisions to look up')
1619 revs = scmutil.revrange(repo, opts[b'rev'])
1619 revs = scmutil.revrange(repo, opts[b'rev'])
1620 cl = repo.changelog
1620 cl = repo.changelog
1621 nodes = [cl.node(r) for r in revs]
1621 nodes = [cl.node(r) for r in revs]
1622
1622
1623 # use a list to pass reference to a nodemap from one closure to the next
1623 # use a list to pass reference to a nodemap from one closure to the next
1624 nodeget = [None]
1624 nodeget = [None]
1625
1625
1626 def setnodeget():
1626 def setnodeget():
1627 # probably not necessary, but for good measure
1627 # probably not necessary, but for good measure
1628 clearchangelog(unfi)
1628 clearchangelog(unfi)
1629 nodeget[0] = makecl(unfi).nodemap.get
1629 nodeget[0] = makecl(unfi).nodemap.get
1630
1630
1631 def d():
1631 def d():
1632 get = nodeget[0]
1632 get = nodeget[0]
1633 for n in nodes:
1633 for n in nodes:
1634 get(n)
1634 get(n)
1635
1635
1636 setup = None
1636 setup = None
1637 if clearcaches:
1637 if clearcaches:
1638
1638
1639 def setup():
1639 def setup():
1640 setnodeget()
1640 setnodeget()
1641
1641
1642 else:
1642 else:
1643 setnodeget()
1643 setnodeget()
1644 d() # prewarm the data structure
1644 d() # prewarm the data structure
1645 timer(d, setup=setup)
1645 timer(d, setup=setup)
1646 fm.end()
1646 fm.end()
1647
1647
1648
1648
1649 @command(b'perfstartup', formatteropts)
1649 @command(b'perfstartup', formatteropts)
1650 def perfstartup(ui, repo, **opts):
1650 def perfstartup(ui, repo, **opts):
1651 opts = _byteskwargs(opts)
1651 opts = _byteskwargs(opts)
1652 timer, fm = gettimer(ui, opts)
1652 timer, fm = gettimer(ui, opts)
1653
1653
1654 def d():
1654 def d():
1655 if os.name != 'nt':
1655 if os.name != 'nt':
1656 os.system(
1656 os.system(
1657 b"HGRCPATH= %s version -q > /dev/null" % fsencode(sys.argv[0])
1657 b"HGRCPATH= %s version -q > /dev/null" % fsencode(sys.argv[0])
1658 )
1658 )
1659 else:
1659 else:
1660 os.environ['HGRCPATH'] = r' '
1660 os.environ['HGRCPATH'] = r' '
1661 os.system("%s version -q > NUL" % sys.argv[0])
1661 os.system("%s version -q > NUL" % sys.argv[0])
1662
1662
1663 timer(d)
1663 timer(d)
1664 fm.end()
1664 fm.end()
1665
1665
1666
1666
1667 @command(b'perfparents', formatteropts)
1667 @command(b'perfparents', formatteropts)
1668 def perfparents(ui, repo, **opts):
1668 def perfparents(ui, repo, **opts):
1669 """benchmark the time necessary to fetch one changeset's parents.
1669 """benchmark the time necessary to fetch one changeset's parents.
1670
1670
1671 The fetch is done using the `node identifier`, traversing all object layers
1671 The fetch is done using the `node identifier`, traversing all object layers
1672 from the repository object. The first N revisions will be used for this
1672 from the repository object. The first N revisions will be used for this
1673 benchmark. N is controlled by the ``perf.parentscount`` config option
1673 benchmark. N is controlled by the ``perf.parentscount`` config option
1674 (default: 1000).
1674 (default: 1000).
1675 """
1675 """
1676 opts = _byteskwargs(opts)
1676 opts = _byteskwargs(opts)
1677 timer, fm = gettimer(ui, opts)
1677 timer, fm = gettimer(ui, opts)
1678 # control the number of commits perfparents iterates over
1678 # control the number of commits perfparents iterates over
1679 # experimental config: perf.parentscount
1679 # experimental config: perf.parentscount
1680 count = getint(ui, b"perf", b"parentscount", 1000)
1680 count = getint(ui, b"perf", b"parentscount", 1000)
1681 if len(repo.changelog) < count:
1681 if len(repo.changelog) < count:
1682 raise error.Abort(b"repo needs %d commits for this test" % count)
1682 raise error.Abort(b"repo needs %d commits for this test" % count)
1683 repo = repo.unfiltered()
1683 repo = repo.unfiltered()
1684 nl = [repo.changelog.node(i) for i in _xrange(count)]
1684 nl = [repo.changelog.node(i) for i in _xrange(count)]
1685
1685
1686 def d():
1686 def d():
1687 for n in nl:
1687 for n in nl:
1688 repo.changelog.parents(n)
1688 repo.changelog.parents(n)
1689
1689
1690 timer(d)
1690 timer(d)
1691 fm.end()
1691 fm.end()
1692
1692
1693
1693
1694 @command(b'perfctxfiles', formatteropts)
1694 @command(b'perfctxfiles', formatteropts)
1695 def perfctxfiles(ui, repo, x, **opts):
1695 def perfctxfiles(ui, repo, x, **opts):
1696 opts = _byteskwargs(opts)
1696 opts = _byteskwargs(opts)
1697 x = int(x)
1697 x = int(x)
1698 timer, fm = gettimer(ui, opts)
1698 timer, fm = gettimer(ui, opts)
1699
1699
1700 def d():
1700 def d():
1701 len(repo[x].files())
1701 len(repo[x].files())
1702
1702
1703 timer(d)
1703 timer(d)
1704 fm.end()
1704 fm.end()
1705
1705
1706
1706
1707 @command(b'perfrawfiles', formatteropts)
1707 @command(b'perfrawfiles', formatteropts)
1708 def perfrawfiles(ui, repo, x, **opts):
1708 def perfrawfiles(ui, repo, x, **opts):
1709 opts = _byteskwargs(opts)
1709 opts = _byteskwargs(opts)
1710 x = int(x)
1710 x = int(x)
1711 timer, fm = gettimer(ui, opts)
1711 timer, fm = gettimer(ui, opts)
1712 cl = repo.changelog
1712 cl = repo.changelog
1713
1713
1714 def d():
1714 def d():
1715 len(cl.read(x)[3])
1715 len(cl.read(x)[3])
1716
1716
1717 timer(d)
1717 timer(d)
1718 fm.end()
1718 fm.end()
1719
1719
1720
1720
1721 @command(b'perflookup', formatteropts)
1721 @command(b'perflookup', formatteropts)
1722 def perflookup(ui, repo, rev, **opts):
1722 def perflookup(ui, repo, rev, **opts):
1723 opts = _byteskwargs(opts)
1723 opts = _byteskwargs(opts)
1724 timer, fm = gettimer(ui, opts)
1724 timer, fm = gettimer(ui, opts)
1725 timer(lambda: len(repo.lookup(rev)))
1725 timer(lambda: len(repo.lookup(rev)))
1726 fm.end()
1726 fm.end()
1727
1727
1728
1728
1729 @command(
1729 @command(
1730 b'perflinelogedits',
1730 b'perflinelogedits',
1731 [
1731 [
1732 (b'n', b'edits', 10000, b'number of edits'),
1732 (b'n', b'edits', 10000, b'number of edits'),
1733 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
1733 (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
1734 ],
1734 ],
1735 norepo=True,
1735 norepo=True,
1736 )
1736 )
1737 def perflinelogedits(ui, **opts):
1737 def perflinelogedits(ui, **opts):
1738 from mercurial import linelog
1738 from mercurial import linelog
1739
1739
1740 opts = _byteskwargs(opts)
1740 opts = _byteskwargs(opts)
1741
1741
1742 edits = opts[b'edits']
1742 edits = opts[b'edits']
1743 maxhunklines = opts[b'max_hunk_lines']
1743 maxhunklines = opts[b'max_hunk_lines']
1744
1744
1745 maxb1 = 100000
1745 maxb1 = 100000
1746 random.seed(0)
1746 random.seed(0)
1747 randint = random.randint
1747 randint = random.randint
1748 currentlines = 0
1748 currentlines = 0
1749 arglist = []
1749 arglist = []
1750 for rev in _xrange(edits):
1750 for rev in _xrange(edits):
1751 a1 = randint(0, currentlines)
1751 a1 = randint(0, currentlines)
1752 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
1752 a2 = randint(a1, min(currentlines, a1 + maxhunklines))
1753 b1 = randint(0, maxb1)
1753 b1 = randint(0, maxb1)
1754 b2 = randint(b1, b1 + maxhunklines)
1754 b2 = randint(b1, b1 + maxhunklines)
1755 currentlines += (b2 - b1) - (a2 - a1)
1755 currentlines += (b2 - b1) - (a2 - a1)
1756 arglist.append((rev, a1, a2, b1, b2))
1756 arglist.append((rev, a1, a2, b1, b2))
1757
1757
1758 def d():
1758 def d():
1759 ll = linelog.linelog()
1759 ll = linelog.linelog()
1760 for args in arglist:
1760 for args in arglist:
1761 ll.replacelines(*args)
1761 ll.replacelines(*args)
1762
1762
1763 timer, fm = gettimer(ui, opts)
1763 timer, fm = gettimer(ui, opts)
1764 timer(d)
1764 timer(d)
1765 fm.end()
1765 fm.end()
1766
1766
1767
1767
1768 @command(b'perfrevrange', formatteropts)
1768 @command(b'perfrevrange', formatteropts)
1769 def perfrevrange(ui, repo, *specs, **opts):
1769 def perfrevrange(ui, repo, *specs, **opts):
1770 opts = _byteskwargs(opts)
1770 opts = _byteskwargs(opts)
1771 timer, fm = gettimer(ui, opts)
1771 timer, fm = gettimer(ui, opts)
1772 revrange = scmutil.revrange
1772 revrange = scmutil.revrange
1773 timer(lambda: len(revrange(repo, specs)))
1773 timer(lambda: len(revrange(repo, specs)))
1774 fm.end()
1774 fm.end()
1775
1775
1776
1776
1777 @command(b'perfnodelookup', formatteropts)
1777 @command(b'perfnodelookup', formatteropts)
1778 def perfnodelookup(ui, repo, rev, **opts):
1778 def perfnodelookup(ui, repo, rev, **opts):
1779 opts = _byteskwargs(opts)
1779 opts = _byteskwargs(opts)
1780 timer, fm = gettimer(ui, opts)
1780 timer, fm = gettimer(ui, opts)
1781 import mercurial.revlog
1781 import mercurial.revlog
1782
1782
1783 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1783 mercurial.revlog._prereadsize = 2 ** 24 # disable lazy parser in old hg
1784 n = scmutil.revsingle(repo, rev).node()
1784 n = scmutil.revsingle(repo, rev).node()
1785 cl = mercurial.revlog.revlog(getsvfs(repo), b"00changelog.i")
1785 cl = mercurial.revlog.revlog(getsvfs(repo), b"00changelog.i")
1786
1786
1787 def d():
1787 def d():
1788 cl.rev(n)
1788 cl.rev(n)
1789 clearcaches(cl)
1789 clearcaches(cl)
1790
1790
1791 timer(d)
1791 timer(d)
1792 fm.end()
1792 fm.end()
1793
1793
1794
1794
1795 @command(
1795 @command(
1796 b'perflog',
1796 b'perflog',
1797 [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts,
1797 [(b'', b'rename', False, b'ask log to follow renames')] + formatteropts,
1798 )
1798 )
1799 def perflog(ui, repo, rev=None, **opts):
1799 def perflog(ui, repo, rev=None, **opts):
1800 opts = _byteskwargs(opts)
1800 opts = _byteskwargs(opts)
1801 if rev is None:
1801 if rev is None:
1802 rev = []
1802 rev = []
1803 timer, fm = gettimer(ui, opts)
1803 timer, fm = gettimer(ui, opts)
1804 ui.pushbuffer()
1804 ui.pushbuffer()
1805 timer(
1805 timer(
1806 lambda: commands.log(
1806 lambda: commands.log(
1807 ui, repo, rev=rev, date=b'', user=b'', copies=opts.get(b'rename')
1807 ui, repo, rev=rev, date=b'', user=b'', copies=opts.get(b'rename')
1808 )
1808 )
1809 )
1809 )
1810 ui.popbuffer()
1810 ui.popbuffer()
1811 fm.end()
1811 fm.end()
1812
1812
1813
1813
1814 @command(b'perfmoonwalk', formatteropts)
1814 @command(b'perfmoonwalk', formatteropts)
1815 def perfmoonwalk(ui, repo, **opts):
1815 def perfmoonwalk(ui, repo, **opts):
1816 """benchmark walking the changelog backwards
1816 """benchmark walking the changelog backwards
1817
1817
1818 This also loads the changelog data for each revision in the changelog.
1818 This also loads the changelog data for each revision in the changelog.
1819 """
1819 """
1820 opts = _byteskwargs(opts)
1820 opts = _byteskwargs(opts)
1821 timer, fm = gettimer(ui, opts)
1821 timer, fm = gettimer(ui, opts)
1822
1822
1823 def moonwalk():
1823 def moonwalk():
1824 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
1824 for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
1825 ctx = repo[i]
1825 ctx = repo[i]
1826 ctx.branch() # read changelog data (in addition to the index)
1826 ctx.branch() # read changelog data (in addition to the index)
1827
1827
1828 timer(moonwalk)
1828 timer(moonwalk)
1829 fm.end()
1829 fm.end()
1830
1830
1831
1831
1832 @command(
1832 @command(
1833 b'perftemplating',
1833 b'perftemplating',
1834 [(b'r', b'rev', [], b'revisions to run the template on'),] + formatteropts,
1834 [(b'r', b'rev', [], b'revisions to run the template on'),] + formatteropts,
1835 )
1835 )
1836 def perftemplating(ui, repo, testedtemplate=None, **opts):
1836 def perftemplating(ui, repo, testedtemplate=None, **opts):
1837 """test the rendering time of a given template"""
1837 """test the rendering time of a given template"""
1838 if makelogtemplater is None:
1838 if makelogtemplater is None:
1839 raise error.Abort(
1839 raise error.Abort(
1840 b"perftemplating not available with this Mercurial",
1840 b"perftemplating not available with this Mercurial",
1841 hint=b"use 4.3 or later",
1841 hint=b"use 4.3 or later",
1842 )
1842 )
1843
1843
1844 opts = _byteskwargs(opts)
1844 opts = _byteskwargs(opts)
1845
1845
1846 nullui = ui.copy()
1846 nullui = ui.copy()
1847 nullui.fout = open(os.devnull, 'wb')
1847 nullui.fout = open(os.devnull, 'wb')
1848 nullui.disablepager()
1848 nullui.disablepager()
1849 revs = opts.get(b'rev')
1849 revs = opts.get(b'rev')
1850 if not revs:
1850 if not revs:
1851 revs = [b'all()']
1851 revs = [b'all()']
1852 revs = list(scmutil.revrange(repo, revs))
1852 revs = list(scmutil.revrange(repo, revs))
1853
1853
1854 defaulttemplate = (
1854 defaulttemplate = (
1855 b'{date|shortdate} [{rev}:{node|short}]'
1855 b'{date|shortdate} [{rev}:{node|short}]'
1856 b' {author|person}: {desc|firstline}\n'
1856 b' {author|person}: {desc|firstline}\n'
1857 )
1857 )
1858 if testedtemplate is None:
1858 if testedtemplate is None:
1859 testedtemplate = defaulttemplate
1859 testedtemplate = defaulttemplate
1860 displayer = makelogtemplater(nullui, repo, testedtemplate)
1860 displayer = makelogtemplater(nullui, repo, testedtemplate)
1861
1861
1862 def format():
1862 def format():
1863 for r in revs:
1863 for r in revs:
1864 ctx = repo[r]
1864 ctx = repo[r]
1865 displayer.show(ctx)
1865 displayer.show(ctx)
1866 displayer.flush(ctx)
1866 displayer.flush(ctx)
1867
1867
1868 timer, fm = gettimer(ui, opts)
1868 timer, fm = gettimer(ui, opts)
1869 timer(format)
1869 timer(format)
1870 fm.end()
1870 fm.end()
1871
1871
1872
1872
1873 def _displaystats(ui, opts, entries, data):
1873 def _displaystats(ui, opts, entries, data):
1874 pass
1874 pass
1875 # use a second formatter because the data are quite different, not sure
1875 # use a second formatter because the data are quite different, not sure
1876 # how it flies with the templater.
1876 # how it flies with the templater.
1877 fm = ui.formatter(b'perf-stats', opts)
1877 fm = ui.formatter(b'perf-stats', opts)
1878 for key, title in entries:
1878 for key, title in entries:
1879 values = data[key]
1879 values = data[key]
1880 nbvalues = len(data)
1880 nbvalues = len(data)
1881 values.sort()
1881 values.sort()
1882 stats = {
1882 stats = {
1883 'key': key,
1883 'key': key,
1884 'title': title,
1884 'title': title,
1885 'nbitems': len(values),
1885 'nbitems': len(values),
1886 'min': values[0][0],
1886 'min': values[0][0],
1887 '10%': values[(nbvalues * 10) // 100][0],
1887 '10%': values[(nbvalues * 10) // 100][0],
1888 '25%': values[(nbvalues * 25) // 100][0],
1888 '25%': values[(nbvalues * 25) // 100][0],
1889 '50%': values[(nbvalues * 50) // 100][0],
1889 '50%': values[(nbvalues * 50) // 100][0],
1890 '75%': values[(nbvalues * 75) // 100][0],
1890 '75%': values[(nbvalues * 75) // 100][0],
1891 '80%': values[(nbvalues * 80) // 100][0],
1891 '80%': values[(nbvalues * 80) // 100][0],
1892 '85%': values[(nbvalues * 85) // 100][0],
1892 '85%': values[(nbvalues * 85) // 100][0],
1893 '90%': values[(nbvalues * 90) // 100][0],
1893 '90%': values[(nbvalues * 90) // 100][0],
1894 '95%': values[(nbvalues * 95) // 100][0],
1894 '95%': values[(nbvalues * 95) // 100][0],
1895 '99%': values[(nbvalues * 99) // 100][0],
1895 '99%': values[(nbvalues * 99) // 100][0],
1896 'max': values[-1][0],
1896 'max': values[-1][0],
1897 }
1897 }
1898 fm.startitem()
1898 fm.startitem()
1899 fm.data(**stats)
1899 fm.data(**stats)
1900 # make node pretty for the human output
1900 # make node pretty for the human output
1901 fm.plain('### %s (%d items)\n' % (title, len(values)))
1901 fm.plain('### %s (%d items)\n' % (title, len(values)))
1902 lines = [
1902 lines = [
1903 'min',
1903 'min',
1904 '10%',
1904 '10%',
1905 '25%',
1905 '25%',
1906 '50%',
1906 '50%',
1907 '75%',
1907 '75%',
1908 '80%',
1908 '80%',
1909 '85%',
1909 '85%',
1910 '90%',
1910 '90%',
1911 '95%',
1911 '95%',
1912 '99%',
1912 '99%',
1913 'max',
1913 'max',
1914 ]
1914 ]
1915 for l in lines:
1915 for l in lines:
1916 fm.plain('%s: %s\n' % (l, stats[l]))
1916 fm.plain('%s: %s\n' % (l, stats[l]))
1917 fm.end()
1917 fm.end()
1918
1918
1919
1919
1920 @command(
1920 @command(
1921 b'perfhelper-mergecopies',
1921 b'perfhelper-mergecopies',
1922 formatteropts
1922 formatteropts
1923 + [
1923 + [
1924 (b'r', b'revs', [], b'restrict search to these revisions'),
1924 (b'r', b'revs', [], b'restrict search to these revisions'),
1925 (b'', b'timing', False, b'provides extra data (costly)'),
1925 (b'', b'timing', False, b'provides extra data (costly)'),
1926 (b'', b'stats', False, b'provides statistic about the measured data'),
1926 (b'', b'stats', False, b'provides statistic about the measured data'),
1927 ],
1927 ],
1928 )
1928 )
1929 def perfhelpermergecopies(ui, repo, revs=[], **opts):
1929 def perfhelpermergecopies(ui, repo, revs=[], **opts):
1930 """find statistics about potential parameters for `perfmergecopies`
1930 """find statistics about potential parameters for `perfmergecopies`
1931
1931
1932 This command find (base, p1, p2) triplet relevant for copytracing
1932 This command find (base, p1, p2) triplet relevant for copytracing
1933 benchmarking in the context of a merge. It reports values for some of the
1933 benchmarking in the context of a merge. It reports values for some of the
1934 parameters that impact merge copy tracing time during merge.
1934 parameters that impact merge copy tracing time during merge.
1935
1935
1936 If `--timing` is set, rename detection is run and the associated timing
1936 If `--timing` is set, rename detection is run and the associated timing
1937 will be reported. The extra details come at the cost of slower command
1937 will be reported. The extra details come at the cost of slower command
1938 execution.
1938 execution.
1939
1939
1940 Since rename detection is only run once, other factors might easily
1940 Since rename detection is only run once, other factors might easily
1941 affect the precision of the timing. However it should give a good
1941 affect the precision of the timing. However it should give a good
1942 approximation of which revision triplets are very costly.
1942 approximation of which revision triplets are very costly.
1943 """
1943 """
1944 opts = _byteskwargs(opts)
1944 opts = _byteskwargs(opts)
1945 fm = ui.formatter(b'perf', opts)
1945 fm = ui.formatter(b'perf', opts)
1946 dotiming = opts[b'timing']
1946 dotiming = opts[b'timing']
1947 dostats = opts[b'stats']
1947 dostats = opts[b'stats']
1948
1948
1949 output_template = [
1949 output_template = [
1950 ("base", "%(base)12s"),
1950 ("base", "%(base)12s"),
1951 ("p1", "%(p1.node)12s"),
1951 ("p1", "%(p1.node)12s"),
1952 ("p2", "%(p2.node)12s"),
1952 ("p2", "%(p2.node)12s"),
1953 ("p1.nb-revs", "%(p1.nbrevs)12d"),
1953 ("p1.nb-revs", "%(p1.nbrevs)12d"),
1954 ("p1.nb-files", "%(p1.nbmissingfiles)12d"),
1954 ("p1.nb-files", "%(p1.nbmissingfiles)12d"),
1955 ("p1.renames", "%(p1.renamedfiles)12d"),
1955 ("p1.renames", "%(p1.renamedfiles)12d"),
1956 ("p1.time", "%(p1.time)12.3f"),
1956 ("p1.time", "%(p1.time)12.3f"),
1957 ("p2.nb-revs", "%(p2.nbrevs)12d"),
1957 ("p2.nb-revs", "%(p2.nbrevs)12d"),
1958 ("p2.nb-files", "%(p2.nbmissingfiles)12d"),
1958 ("p2.nb-files", "%(p2.nbmissingfiles)12d"),
1959 ("p2.renames", "%(p2.renamedfiles)12d"),
1959 ("p2.renames", "%(p2.renamedfiles)12d"),
1960 ("p2.time", "%(p2.time)12.3f"),
1960 ("p2.time", "%(p2.time)12.3f"),
1961 ("renames", "%(nbrenamedfiles)12d"),
1961 ("renames", "%(nbrenamedfiles)12d"),
1962 ("total.time", "%(time)12.3f"),
1962 ("total.time", "%(time)12.3f"),
1963 ]
1963 ]
1964 if not dotiming:
1964 if not dotiming:
1965 output_template = [
1965 output_template = [
1966 i
1966 i
1967 for i in output_template
1967 for i in output_template
1968 if not ('time' in i[0] or 'renames' in i[0])
1968 if not ('time' in i[0] or 'renames' in i[0])
1969 ]
1969 ]
1970 header_names = [h for (h, v) in output_template]
1970 header_names = [h for (h, v) in output_template]
1971 output = ' '.join([v for (h, v) in output_template]) + '\n'
1971 output = ' '.join([v for (h, v) in output_template]) + '\n'
1972 header = ' '.join(['%12s'] * len(header_names)) + '\n'
1972 header = ' '.join(['%12s'] * len(header_names)) + '\n'
1973 fm.plain(header % tuple(header_names))
1973 fm.plain(header % tuple(header_names))
1974
1974
1975 if not revs:
1975 if not revs:
1976 revs = ['all()']
1976 revs = ['all()']
1977 revs = scmutil.revrange(repo, revs)
1977 revs = scmutil.revrange(repo, revs)
1978
1978
1979 if dostats:
1979 if dostats:
1980 alldata = {
1980 alldata = {
1981 'nbrevs': [],
1981 'nbrevs': [],
1982 'nbmissingfiles': [],
1982 'nbmissingfiles': [],
1983 }
1983 }
1984 if dotiming:
1984 if dotiming:
1985 alldata['parentnbrenames'] = []
1985 alldata['parentnbrenames'] = []
1986 alldata['totalnbrenames'] = []
1986 alldata['totalnbrenames'] = []
1987 alldata['parenttime'] = []
1987 alldata['parenttime'] = []
1988 alldata['totaltime'] = []
1988 alldata['totaltime'] = []
1989
1989
1990 roi = repo.revs('merge() and %ld', revs)
1990 roi = repo.revs('merge() and %ld', revs)
1991 for r in roi:
1991 for r in roi:
1992 ctx = repo[r]
1992 ctx = repo[r]
1993 p1 = ctx.p1()
1993 p1 = ctx.p1()
1994 p2 = ctx.p2()
1994 p2 = ctx.p2()
1995 bases = repo.changelog._commonancestorsheads(p1.rev(), p2.rev())
1995 bases = repo.changelog._commonancestorsheads(p1.rev(), p2.rev())
1996 for b in bases:
1996 for b in bases:
1997 b = repo[b]
1997 b = repo[b]
1998 p1missing = copies._computeforwardmissing(b, p1)
1998 p1missing = copies._computeforwardmissing(b, p1)
1999 p2missing = copies._computeforwardmissing(b, p2)
1999 p2missing = copies._computeforwardmissing(b, p2)
2000 data = {
2000 data = {
2001 b'base': b.hex(),
2001 b'base': b.hex(),
2002 b'p1.node': p1.hex(),
2002 b'p1.node': p1.hex(),
2003 b'p1.nbrevs': len(repo.revs('only(%d, %d)', p1.rev(), b.rev())),
2003 b'p1.nbrevs': len(repo.revs('only(%d, %d)', p1.rev(), b.rev())),
2004 b'p1.nbmissingfiles': len(p1missing),
2004 b'p1.nbmissingfiles': len(p1missing),
2005 b'p2.node': p2.hex(),
2005 b'p2.node': p2.hex(),
2006 b'p2.nbrevs': len(repo.revs('only(%d, %d)', p2.rev(), b.rev())),
2006 b'p2.nbrevs': len(repo.revs('only(%d, %d)', p2.rev(), b.rev())),
2007 b'p2.nbmissingfiles': len(p2missing),
2007 b'p2.nbmissingfiles': len(p2missing),
2008 }
2008 }
2009 if dostats:
2009 if dostats:
2010 if p1missing:
2010 if p1missing:
2011 alldata['nbrevs'].append(
2011 alldata['nbrevs'].append(
2012 (data['p1.nbrevs'], b.hex(), p1.hex())
2012 (data['p1.nbrevs'], b.hex(), p1.hex())
2013 )
2013 )
2014 alldata['nbmissingfiles'].append(
2014 alldata['nbmissingfiles'].append(
2015 (data['p1.nbmissingfiles'], b.hex(), p1.hex())
2015 (data['p1.nbmissingfiles'], b.hex(), p1.hex())
2016 )
2016 )
2017 if p2missing:
2017 if p2missing:
2018 alldata['nbrevs'].append(
2018 alldata['nbrevs'].append(
2019 (data['p2.nbrevs'], b.hex(), p2.hex())
2019 (data['p2.nbrevs'], b.hex(), p2.hex())
2020 )
2020 )
2021 alldata['nbmissingfiles'].append(
2021 alldata['nbmissingfiles'].append(
2022 (data['p2.nbmissingfiles'], b.hex(), p2.hex())
2022 (data['p2.nbmissingfiles'], b.hex(), p2.hex())
2023 )
2023 )
2024 if dotiming:
2024 if dotiming:
2025 begin = util.timer()
2025 begin = util.timer()
2026 mergedata = copies.mergecopies(repo, p1, p2, b)
2026 mergedata = copies.mergecopies(repo, p1, p2, b)
2027 end = util.timer()
2027 end = util.timer()
2028 # not very stable timing since we did only one run
2028 # not very stable timing since we did only one run
2029 data['time'] = end - begin
2029 data['time'] = end - begin
2030 # mergedata contains five dicts: "copy", "movewithdir",
2030 # mergedata contains five dicts: "copy", "movewithdir",
2031 # "diverge", "renamedelete" and "dirmove".
2031 # "diverge", "renamedelete" and "dirmove".
2032 # The first 4 are about renamed file so lets count that.
2032 # The first 4 are about renamed file so lets count that.
2033 renames = len(mergedata[0])
2033 renames = len(mergedata[0])
2034 renames += len(mergedata[1])
2034 renames += len(mergedata[1])
2035 renames += len(mergedata[2])
2035 renames += len(mergedata[2])
2036 renames += len(mergedata[3])
2036 renames += len(mergedata[3])
2037 data['nbrenamedfiles'] = renames
2037 data['nbrenamedfiles'] = renames
2038 begin = util.timer()
2038 begin = util.timer()
2039 p1renames = copies.pathcopies(b, p1)
2039 p1renames = copies.pathcopies(b, p1)
2040 end = util.timer()
2040 end = util.timer()
2041 data['p1.time'] = end - begin
2041 data['p1.time'] = end - begin
2042 begin = util.timer()
2042 begin = util.timer()
2043 p2renames = copies.pathcopies(b, p2)
2043 p2renames = copies.pathcopies(b, p2)
2044 data['p2.time'] = end - begin
2044 data['p2.time'] = end - begin
2045 end = util.timer()
2045 end = util.timer()
2046 data['p1.renamedfiles'] = len(p1renames)
2046 data['p1.renamedfiles'] = len(p1renames)
2047 data['p2.renamedfiles'] = len(p2renames)
2047 data['p2.renamedfiles'] = len(p2renames)
2048
2048
2049 if dostats:
2049 if dostats:
2050 if p1missing:
2050 if p1missing:
2051 alldata['parentnbrenames'].append(
2051 alldata['parentnbrenames'].append(
2052 (data['p1.renamedfiles'], b.hex(), p1.hex())
2052 (data['p1.renamedfiles'], b.hex(), p1.hex())
2053 )
2053 )
2054 alldata['parenttime'].append(
2054 alldata['parenttime'].append(
2055 (data['p1.time'], b.hex(), p1.hex())
2055 (data['p1.time'], b.hex(), p1.hex())
2056 )
2056 )
2057 if p2missing:
2057 if p2missing:
2058 alldata['parentnbrenames'].append(
2058 alldata['parentnbrenames'].append(
2059 (data['p2.renamedfiles'], b.hex(), p2.hex())
2059 (data['p2.renamedfiles'], b.hex(), p2.hex())
2060 )
2060 )
2061 alldata['parenttime'].append(
2061 alldata['parenttime'].append(
2062 (data['p2.time'], b.hex(), p2.hex())
2062 (data['p2.time'], b.hex(), p2.hex())
2063 )
2063 )
2064 if p1missing or p2missing:
2064 if p1missing or p2missing:
2065 alldata['totalnbrenames'].append(
2065 alldata['totalnbrenames'].append(
2066 (
2066 (
2067 data['nbrenamedfiles'],
2067 data['nbrenamedfiles'],
2068 b.hex(),
2068 b.hex(),
2069 p1.hex(),
2069 p1.hex(),
2070 p2.hex(),
2070 p2.hex(),
2071 )
2071 )
2072 )
2072 )
2073 alldata['totaltime'].append(
2073 alldata['totaltime'].append(
2074 (data['time'], b.hex(), p1.hex(), p2.hex())
2074 (data['time'], b.hex(), p1.hex(), p2.hex())
2075 )
2075 )
2076 fm.startitem()
2076 fm.startitem()
2077 fm.data(**data)
2077 fm.data(**data)
2078 # make node pretty for the human output
2078 # make node pretty for the human output
2079 out = data.copy()
2079 out = data.copy()
2080 out['base'] = fm.hexfunc(b.node())
2080 out['base'] = fm.hexfunc(b.node())
2081 out['p1.node'] = fm.hexfunc(p1.node())
2081 out['p1.node'] = fm.hexfunc(p1.node())
2082 out['p2.node'] = fm.hexfunc(p2.node())
2082 out['p2.node'] = fm.hexfunc(p2.node())
2083 fm.plain(output % out)
2083 fm.plain(output % out)
2084
2084
2085 fm.end()
2085 fm.end()
2086 if dostats:
2086 if dostats:
2087 # use a second formatter because the data are quite different, not sure
2087 # use a second formatter because the data are quite different, not sure
2088 # how it flies with the templater.
2088 # how it flies with the templater.
2089 entries = [
2089 entries = [
2090 ('nbrevs', 'number of revision covered'),
2090 ('nbrevs', 'number of revision covered'),
2091 ('nbmissingfiles', 'number of missing files at head'),
2091 ('nbmissingfiles', 'number of missing files at head'),
2092 ]
2092 ]
2093 if dotiming:
2093 if dotiming:
2094 entries.append(
2094 entries.append(
2095 ('parentnbrenames', 'rename from one parent to base')
2095 ('parentnbrenames', 'rename from one parent to base')
2096 )
2096 )
2097 entries.append(('totalnbrenames', 'total number of renames'))
2097 entries.append(('totalnbrenames', 'total number of renames'))
2098 entries.append(('parenttime', 'time for one parent'))
2098 entries.append(('parenttime', 'time for one parent'))
2099 entries.append(('totaltime', 'time for both parents'))
2099 entries.append(('totaltime', 'time for both parents'))
2100 _displaystats(ui, opts, entries, alldata)
2100 _displaystats(ui, opts, entries, alldata)
2101
2101
2102
2102
2103 @command(
2103 @command(
2104 b'perfhelper-pathcopies',
2104 b'perfhelper-pathcopies',
2105 formatteropts
2105 formatteropts
2106 + [
2106 + [
2107 (b'r', b'revs', [], b'restrict search to these revisions'),
2107 (b'r', b'revs', [], b'restrict search to these revisions'),
2108 (b'', b'timing', False, b'provides extra data (costly)'),
2108 (b'', b'timing', False, b'provides extra data (costly)'),
2109 (b'', b'stats', False, b'provides statistic about the measured data'),
2109 (b'', b'stats', False, b'provides statistic about the measured data'),
2110 ],
2110 ],
2111 )
2111 )
2112 def perfhelperpathcopies(ui, repo, revs=[], **opts):
2112 def perfhelperpathcopies(ui, repo, revs=[], **opts):
2113 """find statistic about potential parameters for the `perftracecopies`
2113 """find statistic about potential parameters for the `perftracecopies`
2114
2114
2115 This command find source-destination pair relevant for copytracing testing.
2115 This command find source-destination pair relevant for copytracing testing.
2116 It report value for some of the parameters that impact copy tracing time.
2116 It report value for some of the parameters that impact copy tracing time.
2117
2117
2118 If `--timing` is set, rename detection is run and the associated timing
2118 If `--timing` is set, rename detection is run and the associated timing
2119 will be reported. The extra details comes at the cost of a slower command
2119 will be reported. The extra details comes at the cost of a slower command
2120 execution.
2120 execution.
2121
2121
2122 Since the rename detection is only run once, other factors might easily
2122 Since the rename detection is only run once, other factors might easily
2123 affect the precision of the timing. However it should give a good
2123 affect the precision of the timing. However it should give a good
2124 approximation of which revision pairs are very costly.
2124 approximation of which revision pairs are very costly.
2125 """
2125 """
2126 opts = _byteskwargs(opts)
2126 opts = _byteskwargs(opts)
2127 fm = ui.formatter(b'perf', opts)
2127 fm = ui.formatter(b'perf', opts)
2128 dotiming = opts[b'timing']
2128 dotiming = opts[b'timing']
2129 dostats = opts[b'stats']
2129 dostats = opts[b'stats']
2130
2130
2131 if dotiming:
2131 if dotiming:
2132 header = '%12s %12s %12s %12s %12s %12s\n'
2132 header = '%12s %12s %12s %12s %12s %12s\n'
2133 output = (
2133 output = (
2134 "%(source)12s %(destination)12s "
2134 "%(source)12s %(destination)12s "
2135 "%(nbrevs)12d %(nbmissingfiles)12d "
2135 "%(nbrevs)12d %(nbmissingfiles)12d "
2136 "%(nbrenamedfiles)12d %(time)18.5f\n"
2136 "%(nbrenamedfiles)12d %(time)18.5f\n"
2137 )
2137 )
2138 header_names = (
2138 header_names = (
2139 "source",
2139 "source",
2140 "destination",
2140 "destination",
2141 "nb-revs",
2141 "nb-revs",
2142 "nb-files",
2142 "nb-files",
2143 "nb-renames",
2143 "nb-renames",
2144 "time",
2144 "time",
2145 )
2145 )
2146 fm.plain(header % header_names)
2146 fm.plain(header % header_names)
2147 else:
2147 else:
2148 header = '%12s %12s %12s %12s\n'
2148 header = '%12s %12s %12s %12s\n'
2149 output = (
2149 output = (
2150 "%(source)12s %(destination)12s "
2150 "%(source)12s %(destination)12s "
2151 "%(nbrevs)12d %(nbmissingfiles)12d\n"
2151 "%(nbrevs)12d %(nbmissingfiles)12d\n"
2152 )
2152 )
2153 fm.plain(header % ("source", "destination", "nb-revs", "nb-files"))
2153 fm.plain(header % ("source", "destination", "nb-revs", "nb-files"))
2154
2154
2155 if not revs:
2155 if not revs:
2156 revs = ['all()']
2156 revs = ['all()']
2157 revs = scmutil.revrange(repo, revs)
2157 revs = scmutil.revrange(repo, revs)
2158
2158
2159 if dostats:
2159 if dostats:
2160 alldata = {
2160 alldata = {
2161 'nbrevs': [],
2161 'nbrevs': [],
2162 'nbmissingfiles': [],
2162 'nbmissingfiles': [],
2163 }
2163 }
2164 if dotiming:
2164 if dotiming:
2165 alldata['nbrenames'] = []
2165 alldata['nbrenames'] = []
2166 alldata['time'] = []
2166 alldata['time'] = []
2167
2167
2168 roi = repo.revs('merge() and %ld', revs)
2168 roi = repo.revs('merge() and %ld', revs)
2169 for r in roi:
2169 for r in roi:
2170 ctx = repo[r]
2170 ctx = repo[r]
2171 p1 = ctx.p1().rev()
2171 p1 = ctx.p1().rev()
2172 p2 = ctx.p2().rev()
2172 p2 = ctx.p2().rev()
2173 bases = repo.changelog._commonancestorsheads(p1, p2)
2173 bases = repo.changelog._commonancestorsheads(p1, p2)
2174 for p in (p1, p2):
2174 for p in (p1, p2):
2175 for b in bases:
2175 for b in bases:
2176 base = repo[b]
2176 base = repo[b]
2177 parent = repo[p]
2177 parent = repo[p]
2178 missing = copies._computeforwardmissing(base, parent)
2178 missing = copies._computeforwardmissing(base, parent)
2179 if not missing:
2179 if not missing:
2180 continue
2180 continue
2181 data = {
2181 data = {
2182 b'source': base.hex(),
2182 b'source': base.hex(),
2183 b'destination': parent.hex(),
2183 b'destination': parent.hex(),
2184 b'nbrevs': len(repo.revs('only(%d, %d)', p, b)),
2184 b'nbrevs': len(repo.revs('only(%d, %d)', p, b)),
2185 b'nbmissingfiles': len(missing),
2185 b'nbmissingfiles': len(missing),
2186 }
2186 }
2187 if dostats:
2187 if dostats:
2188 alldata['nbrevs'].append(
2188 alldata['nbrevs'].append(
2189 (data['nbrevs'], base.hex(), parent.hex(),)
2189 (data['nbrevs'], base.hex(), parent.hex(),)
2190 )
2190 )
2191 alldata['nbmissingfiles'].append(
2191 alldata['nbmissingfiles'].append(
2192 (data['nbmissingfiles'], base.hex(), parent.hex(),)
2192 (data['nbmissingfiles'], base.hex(), parent.hex(),)
2193 )
2193 )
2194 if dotiming:
2194 if dotiming:
2195 begin = util.timer()
2195 begin = util.timer()
2196 renames = copies.pathcopies(base, parent)
2196 renames = copies.pathcopies(base, parent)
2197 end = util.timer()
2197 end = util.timer()
2198 # not very stable timing since we did only one run
2198 # not very stable timing since we did only one run
2199 data['time'] = end - begin
2199 data['time'] = end - begin
2200 data['nbrenamedfiles'] = len(renames)
2200 data['nbrenamedfiles'] = len(renames)
2201 if dostats:
2201 if dostats:
2202 alldata['time'].append(
2202 alldata['time'].append(
2203 (data['time'], base.hex(), parent.hex(),)
2203 (data['time'], base.hex(), parent.hex(),)
2204 )
2204 )
2205 alldata['nbrenames'].append(
2205 alldata['nbrenames'].append(
2206 (data['nbrenamedfiles'], base.hex(), parent.hex(),)
2206 (data['nbrenamedfiles'], base.hex(), parent.hex(),)
2207 )
2207 )
2208 fm.startitem()
2208 fm.startitem()
2209 fm.data(**data)
2209 fm.data(**data)
2210 out = data.copy()
2210 out = data.copy()
2211 out['source'] = fm.hexfunc(base.node())
2211 out['source'] = fm.hexfunc(base.node())
2212 out['destination'] = fm.hexfunc(parent.node())
2212 out['destination'] = fm.hexfunc(parent.node())
2213 fm.plain(output % out)
2213 fm.plain(output % out)
2214
2214
2215 fm.end()
2215 fm.end()
2216 if dostats:
2216 if dostats:
2217 # use a second formatter because the data are quite different, not sure
2217 # use a second formatter because the data are quite different, not sure
2218 # how it flies with the templater.
2218 # how it flies with the templater.
2219 fm = ui.formatter(b'perf', opts)
2219 fm = ui.formatter(b'perf', opts)
2220 entries = [
2220 entries = [
2221 ('nbrevs', 'number of revision covered'),
2221 ('nbrevs', 'number of revision covered'),
2222 ('nbmissingfiles', 'number of missing files at head'),
2222 ('nbmissingfiles', 'number of missing files at head'),
2223 ]
2223 ]
2224 if dotiming:
2224 if dotiming:
2225 entries.append(('nbrenames', 'renamed files'))
2225 entries.append(('nbrenames', 'renamed files'))
2226 entries.append(('time', 'time'))
2226 entries.append(('time', 'time'))
2227 _displaystats(ui, opts, entries, alldata)
2227 _displaystats(ui, opts, entries, alldata)
2228
2228
2229
2229
2230 @command(b'perfcca', formatteropts)
2230 @command(b'perfcca', formatteropts)
2231 def perfcca(ui, repo, **opts):
2231 def perfcca(ui, repo, **opts):
2232 opts = _byteskwargs(opts)
2232 opts = _byteskwargs(opts)
2233 timer, fm = gettimer(ui, opts)
2233 timer, fm = gettimer(ui, opts)
2234 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
2234 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
2235 fm.end()
2235 fm.end()
2236
2236
2237
2237
2238 @command(b'perffncacheload', formatteropts)
2238 @command(b'perffncacheload', formatteropts)
2239 def perffncacheload(ui, repo, **opts):
2239 def perffncacheload(ui, repo, **opts):
2240 opts = _byteskwargs(opts)
2240 opts = _byteskwargs(opts)
2241 timer, fm = gettimer(ui, opts)
2241 timer, fm = gettimer(ui, opts)
2242 s = repo.store
2242 s = repo.store
2243
2243
2244 def d():
2244 def d():
2245 s.fncache._load()
2245 s.fncache._load()
2246
2246
2247 timer(d)
2247 timer(d)
2248 fm.end()
2248 fm.end()
2249
2249
2250
2250
2251 @command(b'perffncachewrite', formatteropts)
2251 @command(b'perffncachewrite', formatteropts)
2252 def perffncachewrite(ui, repo, **opts):
2252 def perffncachewrite(ui, repo, **opts):
2253 opts = _byteskwargs(opts)
2253 opts = _byteskwargs(opts)
2254 timer, fm = gettimer(ui, opts)
2254 timer, fm = gettimer(ui, opts)
2255 s = repo.store
2255 s = repo.store
2256 lock = repo.lock()
2256 lock = repo.lock()
2257 s.fncache._load()
2257 s.fncache._load()
2258 tr = repo.transaction(b'perffncachewrite')
2258 tr = repo.transaction(b'perffncachewrite')
2259 tr.addbackup(b'fncache')
2259 tr.addbackup(b'fncache')
2260
2260
2261 def d():
2261 def d():
2262 s.fncache._dirty = True
2262 s.fncache._dirty = True
2263 s.fncache.write(tr)
2263 s.fncache.write(tr)
2264
2264
2265 timer(d)
2265 timer(d)
2266 tr.close()
2266 tr.close()
2267 lock.release()
2267 lock.release()
2268 fm.end()
2268 fm.end()
2269
2269
2270
2270
2271 @command(b'perffncacheencode', formatteropts)
2271 @command(b'perffncacheencode', formatteropts)
2272 def perffncacheencode(ui, repo, **opts):
2272 def perffncacheencode(ui, repo, **opts):
2273 opts = _byteskwargs(opts)
2273 opts = _byteskwargs(opts)
2274 timer, fm = gettimer(ui, opts)
2274 timer, fm = gettimer(ui, opts)
2275 s = repo.store
2275 s = repo.store
2276 s.fncache._load()
2276 s.fncache._load()
2277
2277
2278 def d():
2278 def d():
2279 for p in s.fncache.entries:
2279 for p in s.fncache.entries:
2280 s.encode(p)
2280 s.encode(p)
2281
2281
2282 timer(d)
2282 timer(d)
2283 fm.end()
2283 fm.end()
2284
2284
2285
2285
2286 def _bdiffworker(q, blocks, xdiff, ready, done):
2286 def _bdiffworker(q, blocks, xdiff, ready, done):
2287 while not done.is_set():
2287 while not done.is_set():
2288 pair = q.get()
2288 pair = q.get()
2289 while pair is not None:
2289 while pair is not None:
2290 if xdiff:
2290 if xdiff:
2291 mdiff.bdiff.xdiffblocks(*pair)
2291 mdiff.bdiff.xdiffblocks(*pair)
2292 elif blocks:
2292 elif blocks:
2293 mdiff.bdiff.blocks(*pair)
2293 mdiff.bdiff.blocks(*pair)
2294 else:
2294 else:
2295 mdiff.textdiff(*pair)
2295 mdiff.textdiff(*pair)
2296 q.task_done()
2296 q.task_done()
2297 pair = q.get()
2297 pair = q.get()
2298 q.task_done() # for the None one
2298 q.task_done() # for the None one
2299 with ready:
2299 with ready:
2300 ready.wait()
2300 ready.wait()
2301
2301
2302
2302
2303 def _manifestrevision(repo, mnode):
2303 def _manifestrevision(repo, mnode):
2304 ml = repo.manifestlog
2304 ml = repo.manifestlog
2305
2305
2306 if util.safehasattr(ml, b'getstorage'):
2306 if util.safehasattr(ml, b'getstorage'):
2307 store = ml.getstorage(b'')
2307 store = ml.getstorage(b'')
2308 else:
2308 else:
2309 store = ml._revlog
2309 store = ml._revlog
2310
2310
2311 return store.revision(mnode)
2311 return store.revision(mnode)
2312
2312
2313
2313
2314 @command(
2314 @command(
2315 b'perfbdiff',
2315 b'perfbdiff',
2316 revlogopts
2316 revlogopts
2317 + formatteropts
2317 + formatteropts
2318 + [
2318 + [
2319 (
2319 (
2320 b'',
2320 b'',
2321 b'count',
2321 b'count',
2322 1,
2322 1,
2323 b'number of revisions to test (when using --startrev)',
2323 b'number of revisions to test (when using --startrev)',
2324 ),
2324 ),
2325 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
2325 (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
2326 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
2326 (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
2327 (b'', b'blocks', False, b'test computing diffs into blocks'),
2327 (b'', b'blocks', False, b'test computing diffs into blocks'),
2328 (b'', b'xdiff', False, b'use xdiff algorithm'),
2328 (b'', b'xdiff', False, b'use xdiff algorithm'),
2329 ],
2329 ],
2330 b'-c|-m|FILE REV',
2330 b'-c|-m|FILE REV',
2331 )
2331 )
2332 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
2332 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
2333 """benchmark a bdiff between revisions
2333 """benchmark a bdiff between revisions
2334
2334
2335 By default, benchmark a bdiff between its delta parent and itself.
2335 By default, benchmark a bdiff between its delta parent and itself.
2336
2336
2337 With ``--count``, benchmark bdiffs between delta parents and self for N
2337 With ``--count``, benchmark bdiffs between delta parents and self for N
2338 revisions starting at the specified revision.
2338 revisions starting at the specified revision.
2339
2339
2340 With ``--alldata``, assume the requested revision is a changeset and
2340 With ``--alldata``, assume the requested revision is a changeset and
2341 measure bdiffs for all changes related to that changeset (manifest
2341 measure bdiffs for all changes related to that changeset (manifest
2342 and filelogs).
2342 and filelogs).
2343 """
2343 """
2344 opts = _byteskwargs(opts)
2344 opts = _byteskwargs(opts)
2345
2345
2346 if opts[b'xdiff'] and not opts[b'blocks']:
2346 if opts[b'xdiff'] and not opts[b'blocks']:
2347 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
2347 raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
2348
2348
2349 if opts[b'alldata']:
2349 if opts[b'alldata']:
2350 opts[b'changelog'] = True
2350 opts[b'changelog'] = True
2351
2351
2352 if opts.get(b'changelog') or opts.get(b'manifest'):
2352 if opts.get(b'changelog') or opts.get(b'manifest'):
2353 file_, rev = None, file_
2353 file_, rev = None, file_
2354 elif rev is None:
2354 elif rev is None:
2355 raise error.CommandError(b'perfbdiff', b'invalid arguments')
2355 raise error.CommandError(b'perfbdiff', b'invalid arguments')
2356
2356
2357 blocks = opts[b'blocks']
2357 blocks = opts[b'blocks']
2358 xdiff = opts[b'xdiff']
2358 xdiff = opts[b'xdiff']
2359 textpairs = []
2359 textpairs = []
2360
2360
2361 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
2361 r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
2362
2362
2363 startrev = r.rev(r.lookup(rev))
2363 startrev = r.rev(r.lookup(rev))
2364 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2364 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2365 if opts[b'alldata']:
2365 if opts[b'alldata']:
2366 # Load revisions associated with changeset.
2366 # Load revisions associated with changeset.
2367 ctx = repo[rev]
2367 ctx = repo[rev]
2368 mtext = _manifestrevision(repo, ctx.manifestnode())
2368 mtext = _manifestrevision(repo, ctx.manifestnode())
2369 for pctx in ctx.parents():
2369 for pctx in ctx.parents():
2370 pman = _manifestrevision(repo, pctx.manifestnode())
2370 pman = _manifestrevision(repo, pctx.manifestnode())
2371 textpairs.append((pman, mtext))
2371 textpairs.append((pman, mtext))
2372
2372
2373 # Load filelog revisions by iterating manifest delta.
2373 # Load filelog revisions by iterating manifest delta.
2374 man = ctx.manifest()
2374 man = ctx.manifest()
2375 pman = ctx.p1().manifest()
2375 pman = ctx.p1().manifest()
2376 for filename, change in pman.diff(man).items():
2376 for filename, change in pman.diff(man).items():
2377 fctx = repo.file(filename)
2377 fctx = repo.file(filename)
2378 f1 = fctx.revision(change[0][0] or -1)
2378 f1 = fctx.revision(change[0][0] or -1)
2379 f2 = fctx.revision(change[1][0] or -1)
2379 f2 = fctx.revision(change[1][0] or -1)
2380 textpairs.append((f1, f2))
2380 textpairs.append((f1, f2))
2381 else:
2381 else:
2382 dp = r.deltaparent(rev)
2382 dp = r.deltaparent(rev)
2383 textpairs.append((r.revision(dp), r.revision(rev)))
2383 textpairs.append((r.revision(dp), r.revision(rev)))
2384
2384
2385 withthreads = threads > 0
2385 withthreads = threads > 0
2386 if not withthreads:
2386 if not withthreads:
2387
2387
2388 def d():
2388 def d():
2389 for pair in textpairs:
2389 for pair in textpairs:
2390 if xdiff:
2390 if xdiff:
2391 mdiff.bdiff.xdiffblocks(*pair)
2391 mdiff.bdiff.xdiffblocks(*pair)
2392 elif blocks:
2392 elif blocks:
2393 mdiff.bdiff.blocks(*pair)
2393 mdiff.bdiff.blocks(*pair)
2394 else:
2394 else:
2395 mdiff.textdiff(*pair)
2395 mdiff.textdiff(*pair)
2396
2396
2397 else:
2397 else:
2398 q = queue()
2398 q = queue()
2399 for i in _xrange(threads):
2399 for i in _xrange(threads):
2400 q.put(None)
2400 q.put(None)
2401 ready = threading.Condition()
2401 ready = threading.Condition()
2402 done = threading.Event()
2402 done = threading.Event()
2403 for i in _xrange(threads):
2403 for i in _xrange(threads):
2404 threading.Thread(
2404 threading.Thread(
2405 target=_bdiffworker, args=(q, blocks, xdiff, ready, done)
2405 target=_bdiffworker, args=(q, blocks, xdiff, ready, done)
2406 ).start()
2406 ).start()
2407 q.join()
2407 q.join()
2408
2408
2409 def d():
2409 def d():
2410 for pair in textpairs:
2410 for pair in textpairs:
2411 q.put(pair)
2411 q.put(pair)
2412 for i in _xrange(threads):
2412 for i in _xrange(threads):
2413 q.put(None)
2413 q.put(None)
2414 with ready:
2414 with ready:
2415 ready.notify_all()
2415 ready.notify_all()
2416 q.join()
2416 q.join()
2417
2417
2418 timer, fm = gettimer(ui, opts)
2418 timer, fm = gettimer(ui, opts)
2419 timer(d)
2419 timer(d)
2420 fm.end()
2420 fm.end()
2421
2421
2422 if withthreads:
2422 if withthreads:
2423 done.set()
2423 done.set()
2424 for i in _xrange(threads):
2424 for i in _xrange(threads):
2425 q.put(None)
2425 q.put(None)
2426 with ready:
2426 with ready:
2427 ready.notify_all()
2427 ready.notify_all()
2428
2428
2429
2429
2430 @command(
2430 @command(
2431 b'perfunidiff',
2431 b'perfunidiff',
2432 revlogopts
2432 revlogopts
2433 + formatteropts
2433 + formatteropts
2434 + [
2434 + [
2435 (
2435 (
2436 b'',
2436 b'',
2437 b'count',
2437 b'count',
2438 1,
2438 1,
2439 b'number of revisions to test (when using --startrev)',
2439 b'number of revisions to test (when using --startrev)',
2440 ),
2440 ),
2441 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
2441 (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
2442 ],
2442 ],
2443 b'-c|-m|FILE REV',
2443 b'-c|-m|FILE REV',
2444 )
2444 )
2445 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
2445 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
2446 """benchmark a unified diff between revisions
2446 """benchmark a unified diff between revisions
2447
2447
2448 This doesn't include any copy tracing - it's just a unified diff
2448 This doesn't include any copy tracing - it's just a unified diff
2449 of the texts.
2449 of the texts.
2450
2450
2451 By default, benchmark a diff between its delta parent and itself.
2451 By default, benchmark a diff between its delta parent and itself.
2452
2452
2453 With ``--count``, benchmark diffs between delta parents and self for N
2453 With ``--count``, benchmark diffs between delta parents and self for N
2454 revisions starting at the specified revision.
2454 revisions starting at the specified revision.
2455
2455
2456 With ``--alldata``, assume the requested revision is a changeset and
2456 With ``--alldata``, assume the requested revision is a changeset and
2457 measure diffs for all changes related to that changeset (manifest
2457 measure diffs for all changes related to that changeset (manifest
2458 and filelogs).
2458 and filelogs).
2459 """
2459 """
2460 opts = _byteskwargs(opts)
2460 opts = _byteskwargs(opts)
2461 if opts[b'alldata']:
2461 if opts[b'alldata']:
2462 opts[b'changelog'] = True
2462 opts[b'changelog'] = True
2463
2463
2464 if opts.get(b'changelog') or opts.get(b'manifest'):
2464 if opts.get(b'changelog') or opts.get(b'manifest'):
2465 file_, rev = None, file_
2465 file_, rev = None, file_
2466 elif rev is None:
2466 elif rev is None:
2467 raise error.CommandError(b'perfunidiff', b'invalid arguments')
2467 raise error.CommandError(b'perfunidiff', b'invalid arguments')
2468
2468
2469 textpairs = []
2469 textpairs = []
2470
2470
2471 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
2471 r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
2472
2472
2473 startrev = r.rev(r.lookup(rev))
2473 startrev = r.rev(r.lookup(rev))
2474 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2474 for rev in range(startrev, min(startrev + count, len(r) - 1)):
2475 if opts[b'alldata']:
2475 if opts[b'alldata']:
2476 # Load revisions associated with changeset.
2476 # Load revisions associated with changeset.
2477 ctx = repo[rev]
2477 ctx = repo[rev]
2478 mtext = _manifestrevision(repo, ctx.manifestnode())
2478 mtext = _manifestrevision(repo, ctx.manifestnode())
2479 for pctx in ctx.parents():
2479 for pctx in ctx.parents():
2480 pman = _manifestrevision(repo, pctx.manifestnode())
2480 pman = _manifestrevision(repo, pctx.manifestnode())
2481 textpairs.append((pman, mtext))
2481 textpairs.append((pman, mtext))
2482
2482
2483 # Load filelog revisions by iterating manifest delta.
2483 # Load filelog revisions by iterating manifest delta.
2484 man = ctx.manifest()
2484 man = ctx.manifest()
2485 pman = ctx.p1().manifest()
2485 pman = ctx.p1().manifest()
2486 for filename, change in pman.diff(man).items():
2486 for filename, change in pman.diff(man).items():
2487 fctx = repo.file(filename)
2487 fctx = repo.file(filename)
2488 f1 = fctx.revision(change[0][0] or -1)
2488 f1 = fctx.revision(change[0][0] or -1)
2489 f2 = fctx.revision(change[1][0] or -1)
2489 f2 = fctx.revision(change[1][0] or -1)
2490 textpairs.append((f1, f2))
2490 textpairs.append((f1, f2))
2491 else:
2491 else:
2492 dp = r.deltaparent(rev)
2492 dp = r.deltaparent(rev)
2493 textpairs.append((r.revision(dp), r.revision(rev)))
2493 textpairs.append((r.revision(dp), r.revision(rev)))
2494
2494
2495 def d():
2495 def d():
2496 for left, right in textpairs:
2496 for left, right in textpairs:
2497 # The date strings don't matter, so we pass empty strings.
2497 # The date strings don't matter, so we pass empty strings.
2498 headerlines, hunks = mdiff.unidiff(
2498 headerlines, hunks = mdiff.unidiff(
2499 left, b'', right, b'', b'left', b'right', binary=False
2499 left, b'', right, b'', b'left', b'right', binary=False
2500 )
2500 )
2501 # consume iterators in roughly the way patch.py does
2501 # consume iterators in roughly the way patch.py does
2502 b'\n'.join(headerlines)
2502 b'\n'.join(headerlines)
2503 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2503 b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2504
2504
2505 timer, fm = gettimer(ui, opts)
2505 timer, fm = gettimer(ui, opts)
2506 timer(d)
2506 timer(d)
2507 fm.end()
2507 fm.end()
2508
2508
2509
2509
2510 @command(b'perfdiffwd', formatteropts)
2510 @command(b'perfdiffwd', formatteropts)
2511 def perfdiffwd(ui, repo, **opts):
2511 def perfdiffwd(ui, repo, **opts):
2512 """Profile diff of working directory changes"""
2512 """Profile diff of working directory changes"""
2513 opts = _byteskwargs(opts)
2513 opts = _byteskwargs(opts)
2514 timer, fm = gettimer(ui, opts)
2514 timer, fm = gettimer(ui, opts)
2515 options = {
2515 options = {
2516 'w': 'ignore_all_space',
2516 'w': 'ignore_all_space',
2517 'b': 'ignore_space_change',
2517 'b': 'ignore_space_change',
2518 'B': 'ignore_blank_lines',
2518 'B': 'ignore_blank_lines',
2519 }
2519 }
2520
2520
2521 for diffopt in ('', 'w', 'b', 'B', 'wB'):
2521 for diffopt in ('', 'w', 'b', 'B', 'wB'):
2522 opts = dict((options[c], b'1') for c in diffopt)
2522 opts = dict((options[c], b'1') for c in diffopt)
2523
2523
2524 def d():
2524 def d():
2525 ui.pushbuffer()
2525 ui.pushbuffer()
2526 commands.diff(ui, repo, **opts)
2526 commands.diff(ui, repo, **opts)
2527 ui.popbuffer()
2527 ui.popbuffer()
2528
2528
2529 diffopt = diffopt.encode('ascii')
2529 diffopt = diffopt.encode('ascii')
2530 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
2530 title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
2531 timer(d, title=title)
2531 timer(d, title=title)
2532 fm.end()
2532 fm.end()
2533
2533
2534
2534
2535 @command(b'perfrevlogindex', revlogopts + formatteropts, b'-c|-m|FILE')
2535 @command(b'perfrevlogindex', revlogopts + formatteropts, b'-c|-m|FILE')
2536 def perfrevlogindex(ui, repo, file_=None, **opts):
2536 def perfrevlogindex(ui, repo, file_=None, **opts):
2537 """Benchmark operations against a revlog index.
2537 """Benchmark operations against a revlog index.
2538
2538
2539 This tests constructing a revlog instance, reading index data,
2539 This tests constructing a revlog instance, reading index data,
2540 parsing index data, and performing various operations related to
2540 parsing index data, and performing various operations related to
2541 index data.
2541 index data.
2542 """
2542 """
2543
2543
2544 opts = _byteskwargs(opts)
2544 opts = _byteskwargs(opts)
2545
2545
2546 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
2546 rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
2547
2547
2548 opener = getattr(rl, 'opener') # trick linter
2548 opener = getattr(rl, 'opener') # trick linter
2549 indexfile = rl.indexfile
2549 indexfile = rl.indexfile
2550 data = opener.read(indexfile)
2550 data = opener.read(indexfile)
2551
2551
2552 header = struct.unpack(b'>I', data[0:4])[0]
2552 header = struct.unpack(b'>I', data[0:4])[0]
2553 version = header & 0xFFFF
2553 version = header & 0xFFFF
2554 if version == 1:
2554 if version == 1:
2555 revlogio = revlog.revlogio()
2555 revlogio = revlog.revlogio()
2556 inline = header & (1 << 16)
2556 inline = header & (1 << 16)
2557 else:
2557 else:
2558 raise error.Abort(b'unsupported revlog version: %d' % version)
2558 raise error.Abort(b'unsupported revlog version: %d' % version)
2559
2559
2560 rllen = len(rl)
2560 rllen = len(rl)
2561
2561
2562 node0 = rl.node(0)
2562 node0 = rl.node(0)
2563 node25 = rl.node(rllen // 4)
2563 node25 = rl.node(rllen // 4)
2564 node50 = rl.node(rllen // 2)
2564 node50 = rl.node(rllen // 2)
2565 node75 = rl.node(rllen // 4 * 3)
2565 node75 = rl.node(rllen // 4 * 3)
2566 node100 = rl.node(rllen - 1)
2566 node100 = rl.node(rllen - 1)
2567
2567
2568 allrevs = range(rllen)
2568 allrevs = range(rllen)
2569 allrevsrev = list(reversed(allrevs))
2569 allrevsrev = list(reversed(allrevs))
2570 allnodes = [rl.node(rev) for rev in range(rllen)]
2570 allnodes = [rl.node(rev) for rev in range(rllen)]
2571 allnodesrev = list(reversed(allnodes))
2571 allnodesrev = list(reversed(allnodes))
2572
2572
2573 def constructor():
2573 def constructor():
2574 revlog.revlog(opener, indexfile)
2574 revlog.revlog(opener, indexfile)
2575
2575
2576 def read():
2576 def read():
2577 with opener(indexfile) as fh:
2577 with opener(indexfile) as fh:
2578 fh.read()
2578 fh.read()
2579
2579
2580 def parseindex():
2580 def parseindex():
2581 revlogio.parseindex(data, inline)
2581 revlogio.parseindex(data, inline)
2582
2582
2583 def getentry(revornode):
2583 def getentry(revornode):
2584 index = revlogio.parseindex(data, inline)[0]
2584 index = revlogio.parseindex(data, inline)[0]
2585 index[revornode]
2585 index[revornode]
2586
2586
2587 def getentries(revs, count=1):
2587 def getentries(revs, count=1):
2588 index = revlogio.parseindex(data, inline)[0]
2588 index = revlogio.parseindex(data, inline)[0]
2589
2589
2590 for i in range(count):
2590 for i in range(count):
2591 for rev in revs:
2591 for rev in revs:
2592 index[rev]
2592 index[rev]
2593
2593
2594 def resolvenode(node):
2594 def resolvenode(node):
2595 nodemap = revlogio.parseindex(data, inline)[1]
2595 nodemap = getattr(revlogio.parseindex(data, inline)[0], 'nodemap', None)
2596 # This only works for the C code.
2596 # This only works for the C code.
2597 if nodemap is None:
2597 if nodemap is None:
2598 return
2598 return
2599
2599
2600 try:
2600 try:
2601 nodemap[node]
2601 nodemap[node]
2602 except error.RevlogError:
2602 except error.RevlogError:
2603 pass
2603 pass
2604
2604
2605 def resolvenodes(nodes, count=1):
2605 def resolvenodes(nodes, count=1):
2606 nodemap = revlogio.parseindex(data, inline)[1]
2606 nodemap = getattr(revlogio.parseindex(data, inline)[0], 'nodemap', None)
2607 if nodemap is None:
2607 if nodemap is None:
2608 return
2608 return
2609
2609
2610 for i in range(count):
2610 for i in range(count):
2611 for node in nodes:
2611 for node in nodes:
2612 try:
2612 try:
2613 nodemap[node]
2613 nodemap[node]
2614 except error.RevlogError:
2614 except error.RevlogError:
2615 pass
2615 pass
2616
2616
2617 benches = [
2617 benches = [
2618 (constructor, b'revlog constructor'),
2618 (constructor, b'revlog constructor'),
2619 (read, b'read'),
2619 (read, b'read'),
2620 (parseindex, b'create index object'),
2620 (parseindex, b'create index object'),
2621 (lambda: getentry(0), b'retrieve index entry for rev 0'),
2621 (lambda: getentry(0), b'retrieve index entry for rev 0'),
2622 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
2622 (lambda: resolvenode(b'a' * 20), b'look up missing node'),
2623 (lambda: resolvenode(node0), b'look up node at rev 0'),
2623 (lambda: resolvenode(node0), b'look up node at rev 0'),
2624 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
2624 (lambda: resolvenode(node25), b'look up node at 1/4 len'),
2625 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
2625 (lambda: resolvenode(node50), b'look up node at 1/2 len'),
2626 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
2626 (lambda: resolvenode(node75), b'look up node at 3/4 len'),
2627 (lambda: resolvenode(node100), b'look up node at tip'),
2627 (lambda: resolvenode(node100), b'look up node at tip'),
2628 # 2x variation is to measure caching impact.
2628 # 2x variation is to measure caching impact.
2629 (lambda: resolvenodes(allnodes), b'look up all nodes (forward)'),
2629 (lambda: resolvenodes(allnodes), b'look up all nodes (forward)'),
2630 (lambda: resolvenodes(allnodes, 2), b'look up all nodes 2x (forward)'),
2630 (lambda: resolvenodes(allnodes, 2), b'look up all nodes 2x (forward)'),
2631 (lambda: resolvenodes(allnodesrev), b'look up all nodes (reverse)'),
2631 (lambda: resolvenodes(allnodesrev), b'look up all nodes (reverse)'),
2632 (
2632 (
2633 lambda: resolvenodes(allnodesrev, 2),
2633 lambda: resolvenodes(allnodesrev, 2),
2634 b'look up all nodes 2x (reverse)',
2634 b'look up all nodes 2x (reverse)',
2635 ),
2635 ),
2636 (lambda: getentries(allrevs), b'retrieve all index entries (forward)'),
2636 (lambda: getentries(allrevs), b'retrieve all index entries (forward)'),
2637 (
2637 (
2638 lambda: getentries(allrevs, 2),
2638 lambda: getentries(allrevs, 2),
2639 b'retrieve all index entries 2x (forward)',
2639 b'retrieve all index entries 2x (forward)',
2640 ),
2640 ),
2641 (
2641 (
2642 lambda: getentries(allrevsrev),
2642 lambda: getentries(allrevsrev),
2643 b'retrieve all index entries (reverse)',
2643 b'retrieve all index entries (reverse)',
2644 ),
2644 ),
2645 (
2645 (
2646 lambda: getentries(allrevsrev, 2),
2646 lambda: getentries(allrevsrev, 2),
2647 b'retrieve all index entries 2x (reverse)',
2647 b'retrieve all index entries 2x (reverse)',
2648 ),
2648 ),
2649 ]
2649 ]
2650
2650
2651 for fn, title in benches:
2651 for fn, title in benches:
2652 timer, fm = gettimer(ui, opts)
2652 timer, fm = gettimer(ui, opts)
2653 timer(fn, title=title)
2653 timer(fn, title=title)
2654 fm.end()
2654 fm.end()
2655
2655
2656
2656
2657 @command(
2657 @command(
2658 b'perfrevlogrevisions',
2658 b'perfrevlogrevisions',
2659 revlogopts
2659 revlogopts
2660 + formatteropts
2660 + formatteropts
2661 + [
2661 + [
2662 (b'd', b'dist', 100, b'distance between the revisions'),
2662 (b'd', b'dist', 100, b'distance between the revisions'),
2663 (b's', b'startrev', 0, b'revision to start reading at'),
2663 (b's', b'startrev', 0, b'revision to start reading at'),
2664 (b'', b'reverse', False, b'read in reverse'),
2664 (b'', b'reverse', False, b'read in reverse'),
2665 ],
2665 ],
2666 b'-c|-m|FILE',
2666 b'-c|-m|FILE',
2667 )
2667 )
2668 def perfrevlogrevisions(
2668 def perfrevlogrevisions(
2669 ui, repo, file_=None, startrev=0, reverse=False, **opts
2669 ui, repo, file_=None, startrev=0, reverse=False, **opts
2670 ):
2670 ):
2671 """Benchmark reading a series of revisions from a revlog.
2671 """Benchmark reading a series of revisions from a revlog.
2672
2672
2673 By default, we read every ``-d/--dist`` revision from 0 to tip of
2673 By default, we read every ``-d/--dist`` revision from 0 to tip of
2674 the specified revlog.
2674 the specified revlog.
2675
2675
2676 The start revision can be defined via ``-s/--startrev``.
2676 The start revision can be defined via ``-s/--startrev``.
2677 """
2677 """
2678 opts = _byteskwargs(opts)
2678 opts = _byteskwargs(opts)
2679
2679
2680 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
2680 rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
2681 rllen = getlen(ui)(rl)
2681 rllen = getlen(ui)(rl)
2682
2682
2683 if startrev < 0:
2683 if startrev < 0:
2684 startrev = rllen + startrev
2684 startrev = rllen + startrev
2685
2685
2686 def d():
2686 def d():
2687 rl.clearcaches()
2687 rl.clearcaches()
2688
2688
2689 beginrev = startrev
2689 beginrev = startrev
2690 endrev = rllen
2690 endrev = rllen
2691 dist = opts[b'dist']
2691 dist = opts[b'dist']
2692
2692
2693 if reverse:
2693 if reverse:
2694 beginrev, endrev = endrev - 1, beginrev - 1
2694 beginrev, endrev = endrev - 1, beginrev - 1
2695 dist = -1 * dist
2695 dist = -1 * dist
2696
2696
2697 for x in _xrange(beginrev, endrev, dist):
2697 for x in _xrange(beginrev, endrev, dist):
2698 # Old revisions don't support passing int.
2698 # Old revisions don't support passing int.
2699 n = rl.node(x)
2699 n = rl.node(x)
2700 rl.revision(n)
2700 rl.revision(n)
2701
2701
2702 timer, fm = gettimer(ui, opts)
2702 timer, fm = gettimer(ui, opts)
2703 timer(d)
2703 timer(d)
2704 fm.end()
2704 fm.end()
2705
2705
2706
2706
2707 @command(
2707 @command(
2708 b'perfrevlogwrite',
2708 b'perfrevlogwrite',
2709 revlogopts
2709 revlogopts
2710 + formatteropts
2710 + formatteropts
2711 + [
2711 + [
2712 (b's', b'startrev', 1000, b'revision to start writing at'),
2712 (b's', b'startrev', 1000, b'revision to start writing at'),
2713 (b'', b'stoprev', -1, b'last revision to write'),
2713 (b'', b'stoprev', -1, b'last revision to write'),
2714 (b'', b'count', 3, b'number of passes to perform'),
2714 (b'', b'count', 3, b'number of passes to perform'),
2715 (b'', b'details', False, b'print timing for every revisions tested'),
2715 (b'', b'details', False, b'print timing for every revisions tested'),
2716 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
2716 (b'', b'source', b'full', b'the kind of data feed in the revlog'),
2717 (b'', b'lazydeltabase', True, b'try the provided delta first'),
2717 (b'', b'lazydeltabase', True, b'try the provided delta first'),
2718 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
2718 (b'', b'clear-caches', True, b'clear revlog cache between calls'),
2719 ],
2719 ],
2720 b'-c|-m|FILE',
2720 b'-c|-m|FILE',
2721 )
2721 )
2722 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
2722 def perfrevlogwrite(ui, repo, file_=None, startrev=1000, stoprev=-1, **opts):
2723 """Benchmark writing a series of revisions to a revlog.
2723 """Benchmark writing a series of revisions to a revlog.
2724
2724
2725 Possible source values are:
2725 Possible source values are:
2726 * `full`: add from a full text (default).
2726 * `full`: add from a full text (default).
2727 * `parent-1`: add from a delta to the first parent
2727 * `parent-1`: add from a delta to the first parent
2728 * `parent-2`: add from a delta to the second parent if it exists
2728 * `parent-2`: add from a delta to the second parent if it exists
2729 (use a delta from the first parent otherwise)
2729 (use a delta from the first parent otherwise)
2730 * `parent-smallest`: add from the smallest delta (either p1 or p2)
2730 * `parent-smallest`: add from the smallest delta (either p1 or p2)
2731 * `storage`: add from the existing precomputed deltas
2731 * `storage`: add from the existing precomputed deltas
2732
2732
2733 Note: This performance command measures performance in a custom way. As a
2733 Note: This performance command measures performance in a custom way. As a
2734 result some of the global configuration of the 'perf' command does not
2734 result some of the global configuration of the 'perf' command does not
2735 apply to it:
2735 apply to it:
2736
2736
2737 * ``pre-run``: disabled
2737 * ``pre-run``: disabled
2738
2738
2739 * ``profile-benchmark``: disabled
2739 * ``profile-benchmark``: disabled
2740
2740
2741 * ``run-limits``: disabled use --count instead
2741 * ``run-limits``: disabled use --count instead
2742 """
2742 """
2743 opts = _byteskwargs(opts)
2743 opts = _byteskwargs(opts)
2744
2744
2745 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
2745 rl = cmdutil.openrevlog(repo, b'perfrevlogwrite', file_, opts)
2746 rllen = getlen(ui)(rl)
2746 rllen = getlen(ui)(rl)
2747 if startrev < 0:
2747 if startrev < 0:
2748 startrev = rllen + startrev
2748 startrev = rllen + startrev
2749 if stoprev < 0:
2749 if stoprev < 0:
2750 stoprev = rllen + stoprev
2750 stoprev = rllen + stoprev
2751
2751
2752 lazydeltabase = opts['lazydeltabase']
2752 lazydeltabase = opts['lazydeltabase']
2753 source = opts['source']
2753 source = opts['source']
2754 clearcaches = opts['clear_caches']
2754 clearcaches = opts['clear_caches']
2755 validsource = (
2755 validsource = (
2756 b'full',
2756 b'full',
2757 b'parent-1',
2757 b'parent-1',
2758 b'parent-2',
2758 b'parent-2',
2759 b'parent-smallest',
2759 b'parent-smallest',
2760 b'storage',
2760 b'storage',
2761 )
2761 )
2762 if source not in validsource:
2762 if source not in validsource:
2763 raise error.Abort('invalid source type: %s' % source)
2763 raise error.Abort('invalid source type: %s' % source)
2764
2764
2765 ### actually gather results
2765 ### actually gather results
2766 count = opts['count']
2766 count = opts['count']
2767 if count <= 0:
2767 if count <= 0:
2768 raise error.Abort('invalide run count: %d' % count)
2768 raise error.Abort('invalide run count: %d' % count)
2769 allresults = []
2769 allresults = []
2770 for c in range(count):
2770 for c in range(count):
2771 timing = _timeonewrite(
2771 timing = _timeonewrite(
2772 ui,
2772 ui,
2773 rl,
2773 rl,
2774 source,
2774 source,
2775 startrev,
2775 startrev,
2776 stoprev,
2776 stoprev,
2777 c + 1,
2777 c + 1,
2778 lazydeltabase=lazydeltabase,
2778 lazydeltabase=lazydeltabase,
2779 clearcaches=clearcaches,
2779 clearcaches=clearcaches,
2780 )
2780 )
2781 allresults.append(timing)
2781 allresults.append(timing)
2782
2782
2783 ### consolidate the results in a single list
2783 ### consolidate the results in a single list
2784 results = []
2784 results = []
2785 for idx, (rev, t) in enumerate(allresults[0]):
2785 for idx, (rev, t) in enumerate(allresults[0]):
2786 ts = [t]
2786 ts = [t]
2787 for other in allresults[1:]:
2787 for other in allresults[1:]:
2788 orev, ot = other[idx]
2788 orev, ot = other[idx]
2789 assert orev == rev
2789 assert orev == rev
2790 ts.append(ot)
2790 ts.append(ot)
2791 results.append((rev, ts))
2791 results.append((rev, ts))
2792 resultcount = len(results)
2792 resultcount = len(results)
2793
2793
2794 ### Compute and display relevant statistics
2794 ### Compute and display relevant statistics
2795
2795
2796 # get a formatter
2796 # get a formatter
2797 fm = ui.formatter(b'perf', opts)
2797 fm = ui.formatter(b'perf', opts)
2798 displayall = ui.configbool(b"perf", b"all-timing", False)
2798 displayall = ui.configbool(b"perf", b"all-timing", False)
2799
2799
2800 # print individual details if requested
2800 # print individual details if requested
2801 if opts['details']:
2801 if opts['details']:
2802 for idx, item in enumerate(results, 1):
2802 for idx, item in enumerate(results, 1):
2803 rev, data = item
2803 rev, data = item
2804 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
2804 title = 'revisions #%d of %d, rev %d' % (idx, resultcount, rev)
2805 formatone(fm, data, title=title, displayall=displayall)
2805 formatone(fm, data, title=title, displayall=displayall)
2806
2806
2807 # sorts results by median time
2807 # sorts results by median time
2808 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
2808 results.sort(key=lambda x: sorted(x[1])[len(x[1]) // 2])
2809 # list of (name, index) to display)
2809 # list of (name, index) to display)
2810 relevants = [
2810 relevants = [
2811 ("min", 0),
2811 ("min", 0),
2812 ("10%", resultcount * 10 // 100),
2812 ("10%", resultcount * 10 // 100),
2813 ("25%", resultcount * 25 // 100),
2813 ("25%", resultcount * 25 // 100),
2814 ("50%", resultcount * 70 // 100),
2814 ("50%", resultcount * 70 // 100),
2815 ("75%", resultcount * 75 // 100),
2815 ("75%", resultcount * 75 // 100),
2816 ("90%", resultcount * 90 // 100),
2816 ("90%", resultcount * 90 // 100),
2817 ("95%", resultcount * 95 // 100),
2817 ("95%", resultcount * 95 // 100),
2818 ("99%", resultcount * 99 // 100),
2818 ("99%", resultcount * 99 // 100),
2819 ("99.9%", resultcount * 999 // 1000),
2819 ("99.9%", resultcount * 999 // 1000),
2820 ("99.99%", resultcount * 9999 // 10000),
2820 ("99.99%", resultcount * 9999 // 10000),
2821 ("99.999%", resultcount * 99999 // 100000),
2821 ("99.999%", resultcount * 99999 // 100000),
2822 ("max", -1),
2822 ("max", -1),
2823 ]
2823 ]
2824 if not ui.quiet:
2824 if not ui.quiet:
2825 for name, idx in relevants:
2825 for name, idx in relevants:
2826 data = results[idx]
2826 data = results[idx]
2827 title = '%s of %d, rev %d' % (name, resultcount, data[0])
2827 title = '%s of %d, rev %d' % (name, resultcount, data[0])
2828 formatone(fm, data[1], title=title, displayall=displayall)
2828 formatone(fm, data[1], title=title, displayall=displayall)
2829
2829
2830 # XXX summing that many float will not be very precise, we ignore this fact
2830 # XXX summing that many float will not be very precise, we ignore this fact
2831 # for now
2831 # for now
2832 totaltime = []
2832 totaltime = []
2833 for item in allresults:
2833 for item in allresults:
2834 totaltime.append(
2834 totaltime.append(
2835 (
2835 (
2836 sum(x[1][0] for x in item),
2836 sum(x[1][0] for x in item),
2837 sum(x[1][1] for x in item),
2837 sum(x[1][1] for x in item),
2838 sum(x[1][2] for x in item),
2838 sum(x[1][2] for x in item),
2839 )
2839 )
2840 )
2840 )
2841 formatone(
2841 formatone(
2842 fm,
2842 fm,
2843 totaltime,
2843 totaltime,
2844 title="total time (%d revs)" % resultcount,
2844 title="total time (%d revs)" % resultcount,
2845 displayall=displayall,
2845 displayall=displayall,
2846 )
2846 )
2847 fm.end()
2847 fm.end()
2848
2848
2849
2849
2850 class _faketr(object):
2850 class _faketr(object):
2851 def add(s, x, y, z=None):
2851 def add(s, x, y, z=None):
2852 return None
2852 return None
2853
2853
2854
2854
2855 def _timeonewrite(
2855 def _timeonewrite(
2856 ui,
2856 ui,
2857 orig,
2857 orig,
2858 source,
2858 source,
2859 startrev,
2859 startrev,
2860 stoprev,
2860 stoprev,
2861 runidx=None,
2861 runidx=None,
2862 lazydeltabase=True,
2862 lazydeltabase=True,
2863 clearcaches=True,
2863 clearcaches=True,
2864 ):
2864 ):
2865 timings = []
2865 timings = []
2866 tr = _faketr()
2866 tr = _faketr()
2867 with _temprevlog(ui, orig, startrev) as dest:
2867 with _temprevlog(ui, orig, startrev) as dest:
2868 dest._lazydeltabase = lazydeltabase
2868 dest._lazydeltabase = lazydeltabase
2869 revs = list(orig.revs(startrev, stoprev))
2869 revs = list(orig.revs(startrev, stoprev))
2870 total = len(revs)
2870 total = len(revs)
2871 topic = 'adding'
2871 topic = 'adding'
2872 if runidx is not None:
2872 if runidx is not None:
2873 topic += ' (run #%d)' % runidx
2873 topic += ' (run #%d)' % runidx
2874 # Support both old and new progress API
2874 # Support both old and new progress API
2875 if util.safehasattr(ui, 'makeprogress'):
2875 if util.safehasattr(ui, 'makeprogress'):
2876 progress = ui.makeprogress(topic, unit='revs', total=total)
2876 progress = ui.makeprogress(topic, unit='revs', total=total)
2877
2877
2878 def updateprogress(pos):
2878 def updateprogress(pos):
2879 progress.update(pos)
2879 progress.update(pos)
2880
2880
2881 def completeprogress():
2881 def completeprogress():
2882 progress.complete()
2882 progress.complete()
2883
2883
2884 else:
2884 else:
2885
2885
2886 def updateprogress(pos):
2886 def updateprogress(pos):
2887 ui.progress(topic, pos, unit='revs', total=total)
2887 ui.progress(topic, pos, unit='revs', total=total)
2888
2888
2889 def completeprogress():
2889 def completeprogress():
2890 ui.progress(topic, None, unit='revs', total=total)
2890 ui.progress(topic, None, unit='revs', total=total)
2891
2891
2892 for idx, rev in enumerate(revs):
2892 for idx, rev in enumerate(revs):
2893 updateprogress(idx)
2893 updateprogress(idx)
2894 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
2894 addargs, addkwargs = _getrevisionseed(orig, rev, tr, source)
2895 if clearcaches:
2895 if clearcaches:
2896 dest.index.clearcaches()
2896 dest.index.clearcaches()
2897 dest.clearcaches()
2897 dest.clearcaches()
2898 with timeone() as r:
2898 with timeone() as r:
2899 dest.addrawrevision(*addargs, **addkwargs)
2899 dest.addrawrevision(*addargs, **addkwargs)
2900 timings.append((rev, r[0]))
2900 timings.append((rev, r[0]))
2901 updateprogress(total)
2901 updateprogress(total)
2902 completeprogress()
2902 completeprogress()
2903 return timings
2903 return timings
2904
2904
2905
2905
2906 def _getrevisionseed(orig, rev, tr, source):
2906 def _getrevisionseed(orig, rev, tr, source):
2907 from mercurial.node import nullid
2907 from mercurial.node import nullid
2908
2908
2909 linkrev = orig.linkrev(rev)
2909 linkrev = orig.linkrev(rev)
2910 node = orig.node(rev)
2910 node = orig.node(rev)
2911 p1, p2 = orig.parents(node)
2911 p1, p2 = orig.parents(node)
2912 flags = orig.flags(rev)
2912 flags = orig.flags(rev)
2913 cachedelta = None
2913 cachedelta = None
2914 text = None
2914 text = None
2915
2915
2916 if source == b'full':
2916 if source == b'full':
2917 text = orig.revision(rev)
2917 text = orig.revision(rev)
2918 elif source == b'parent-1':
2918 elif source == b'parent-1':
2919 baserev = orig.rev(p1)
2919 baserev = orig.rev(p1)
2920 cachedelta = (baserev, orig.revdiff(p1, rev))
2920 cachedelta = (baserev, orig.revdiff(p1, rev))
2921 elif source == b'parent-2':
2921 elif source == b'parent-2':
2922 parent = p2
2922 parent = p2
2923 if p2 == nullid:
2923 if p2 == nullid:
2924 parent = p1
2924 parent = p1
2925 baserev = orig.rev(parent)
2925 baserev = orig.rev(parent)
2926 cachedelta = (baserev, orig.revdiff(parent, rev))
2926 cachedelta = (baserev, orig.revdiff(parent, rev))
2927 elif source == b'parent-smallest':
2927 elif source == b'parent-smallest':
2928 p1diff = orig.revdiff(p1, rev)
2928 p1diff = orig.revdiff(p1, rev)
2929 parent = p1
2929 parent = p1
2930 diff = p1diff
2930 diff = p1diff
2931 if p2 != nullid:
2931 if p2 != nullid:
2932 p2diff = orig.revdiff(p2, rev)
2932 p2diff = orig.revdiff(p2, rev)
2933 if len(p1diff) > len(p2diff):
2933 if len(p1diff) > len(p2diff):
2934 parent = p2
2934 parent = p2
2935 diff = p2diff
2935 diff = p2diff
2936 baserev = orig.rev(parent)
2936 baserev = orig.rev(parent)
2937 cachedelta = (baserev, diff)
2937 cachedelta = (baserev, diff)
2938 elif source == b'storage':
2938 elif source == b'storage':
2939 baserev = orig.deltaparent(rev)
2939 baserev = orig.deltaparent(rev)
2940 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
2940 cachedelta = (baserev, orig.revdiff(orig.node(baserev), rev))
2941
2941
2942 return (
2942 return (
2943 (text, tr, linkrev, p1, p2),
2943 (text, tr, linkrev, p1, p2),
2944 {'node': node, 'flags': flags, 'cachedelta': cachedelta},
2944 {'node': node, 'flags': flags, 'cachedelta': cachedelta},
2945 )
2945 )
2946
2946
2947
2947
2948 @contextlib.contextmanager
2948 @contextlib.contextmanager
2949 def _temprevlog(ui, orig, truncaterev):
2949 def _temprevlog(ui, orig, truncaterev):
2950 from mercurial import vfs as vfsmod
2950 from mercurial import vfs as vfsmod
2951
2951
2952 if orig._inline:
2952 if orig._inline:
2953 raise error.Abort('not supporting inline revlog (yet)')
2953 raise error.Abort('not supporting inline revlog (yet)')
2954 revlogkwargs = {}
2954 revlogkwargs = {}
2955 k = 'upperboundcomp'
2955 k = 'upperboundcomp'
2956 if util.safehasattr(orig, k):
2956 if util.safehasattr(orig, k):
2957 revlogkwargs[k] = getattr(orig, k)
2957 revlogkwargs[k] = getattr(orig, k)
2958
2958
2959 origindexpath = orig.opener.join(orig.indexfile)
2959 origindexpath = orig.opener.join(orig.indexfile)
2960 origdatapath = orig.opener.join(orig.datafile)
2960 origdatapath = orig.opener.join(orig.datafile)
2961 indexname = 'revlog.i'
2961 indexname = 'revlog.i'
2962 dataname = 'revlog.d'
2962 dataname = 'revlog.d'
2963
2963
2964 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
2964 tmpdir = tempfile.mkdtemp(prefix='tmp-hgperf-')
2965 try:
2965 try:
2966 # copy the data file in a temporary directory
2966 # copy the data file in a temporary directory
2967 ui.debug('copying data in %s\n' % tmpdir)
2967 ui.debug('copying data in %s\n' % tmpdir)
2968 destindexpath = os.path.join(tmpdir, 'revlog.i')
2968 destindexpath = os.path.join(tmpdir, 'revlog.i')
2969 destdatapath = os.path.join(tmpdir, 'revlog.d')
2969 destdatapath = os.path.join(tmpdir, 'revlog.d')
2970 shutil.copyfile(origindexpath, destindexpath)
2970 shutil.copyfile(origindexpath, destindexpath)
2971 shutil.copyfile(origdatapath, destdatapath)
2971 shutil.copyfile(origdatapath, destdatapath)
2972
2972
2973 # remove the data we want to add again
2973 # remove the data we want to add again
2974 ui.debug('truncating data to be rewritten\n')
2974 ui.debug('truncating data to be rewritten\n')
2975 with open(destindexpath, 'ab') as index:
2975 with open(destindexpath, 'ab') as index:
2976 index.seek(0)
2976 index.seek(0)
2977 index.truncate(truncaterev * orig._io.size)
2977 index.truncate(truncaterev * orig._io.size)
2978 with open(destdatapath, 'ab') as data:
2978 with open(destdatapath, 'ab') as data:
2979 data.seek(0)
2979 data.seek(0)
2980 data.truncate(orig.start(truncaterev))
2980 data.truncate(orig.start(truncaterev))
2981
2981
2982 # instantiate a new revlog from the temporary copy
2982 # instantiate a new revlog from the temporary copy
2983 ui.debug('truncating adding to be rewritten\n')
2983 ui.debug('truncating adding to be rewritten\n')
2984 vfs = vfsmod.vfs(tmpdir)
2984 vfs = vfsmod.vfs(tmpdir)
2985 vfs.options = getattr(orig.opener, 'options', None)
2985 vfs.options = getattr(orig.opener, 'options', None)
2986
2986
2987 dest = revlog.revlog(
2987 dest = revlog.revlog(
2988 vfs, indexfile=indexname, datafile=dataname, **revlogkwargs
2988 vfs, indexfile=indexname, datafile=dataname, **revlogkwargs
2989 )
2989 )
2990 if dest._inline:
2990 if dest._inline:
2991 raise error.Abort('not supporting inline revlog (yet)')
2991 raise error.Abort('not supporting inline revlog (yet)')
2992 # make sure internals are initialized
2992 # make sure internals are initialized
2993 dest.revision(len(dest) - 1)
2993 dest.revision(len(dest) - 1)
2994 yield dest
2994 yield dest
2995 del dest, vfs
2995 del dest, vfs
2996 finally:
2996 finally:
2997 shutil.rmtree(tmpdir, True)
2997 shutil.rmtree(tmpdir, True)
2998
2998
2999
2999
3000 @command(
3000 @command(
3001 b'perfrevlogchunks',
3001 b'perfrevlogchunks',
3002 revlogopts
3002 revlogopts
3003 + formatteropts
3003 + formatteropts
3004 + [
3004 + [
3005 (b'e', b'engines', b'', b'compression engines to use'),
3005 (b'e', b'engines', b'', b'compression engines to use'),
3006 (b's', b'startrev', 0, b'revision to start at'),
3006 (b's', b'startrev', 0, b'revision to start at'),
3007 ],
3007 ],
3008 b'-c|-m|FILE',
3008 b'-c|-m|FILE',
3009 )
3009 )
3010 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
3010 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
3011 """Benchmark operations on revlog chunks.
3011 """Benchmark operations on revlog chunks.
3012
3012
3013 Logically, each revlog is a collection of fulltext revisions. However,
3013 Logically, each revlog is a collection of fulltext revisions. However,
3014 stored within each revlog are "chunks" of possibly compressed data. This
3014 stored within each revlog are "chunks" of possibly compressed data. This
3015 data needs to be read and decompressed or compressed and written.
3015 data needs to be read and decompressed or compressed and written.
3016
3016
3017 This command measures the time it takes to read+decompress and recompress
3017 This command measures the time it takes to read+decompress and recompress
3018 chunks in a revlog. It effectively isolates I/O and compression performance.
3018 chunks in a revlog. It effectively isolates I/O and compression performance.
3019 For measurements of higher-level operations like resolving revisions,
3019 For measurements of higher-level operations like resolving revisions,
3020 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
3020 see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
3021 """
3021 """
3022 opts = _byteskwargs(opts)
3022 opts = _byteskwargs(opts)
3023
3023
3024 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
3024 rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
3025
3025
3026 # _chunkraw was renamed to _getsegmentforrevs.
3026 # _chunkraw was renamed to _getsegmentforrevs.
3027 try:
3027 try:
3028 segmentforrevs = rl._getsegmentforrevs
3028 segmentforrevs = rl._getsegmentforrevs
3029 except AttributeError:
3029 except AttributeError:
3030 segmentforrevs = rl._chunkraw
3030 segmentforrevs = rl._chunkraw
3031
3031
3032 # Verify engines argument.
3032 # Verify engines argument.
3033 if engines:
3033 if engines:
3034 engines = set(e.strip() for e in engines.split(b','))
3034 engines = set(e.strip() for e in engines.split(b','))
3035 for engine in engines:
3035 for engine in engines:
3036 try:
3036 try:
3037 util.compressionengines[engine]
3037 util.compressionengines[engine]
3038 except KeyError:
3038 except KeyError:
3039 raise error.Abort(b'unknown compression engine: %s' % engine)
3039 raise error.Abort(b'unknown compression engine: %s' % engine)
3040 else:
3040 else:
3041 engines = []
3041 engines = []
3042 for e in util.compengines:
3042 for e in util.compengines:
3043 engine = util.compengines[e]
3043 engine = util.compengines[e]
3044 try:
3044 try:
3045 if engine.available():
3045 if engine.available():
3046 engine.revlogcompressor().compress(b'dummy')
3046 engine.revlogcompressor().compress(b'dummy')
3047 engines.append(e)
3047 engines.append(e)
3048 except NotImplementedError:
3048 except NotImplementedError:
3049 pass
3049 pass
3050
3050
3051 revs = list(rl.revs(startrev, len(rl) - 1))
3051 revs = list(rl.revs(startrev, len(rl) - 1))
3052
3052
3053 def rlfh(rl):
3053 def rlfh(rl):
3054 if rl._inline:
3054 if rl._inline:
3055 return getsvfs(repo)(rl.indexfile)
3055 return getsvfs(repo)(rl.indexfile)
3056 else:
3056 else:
3057 return getsvfs(repo)(rl.datafile)
3057 return getsvfs(repo)(rl.datafile)
3058
3058
3059 def doread():
3059 def doread():
3060 rl.clearcaches()
3060 rl.clearcaches()
3061 for rev in revs:
3061 for rev in revs:
3062 segmentforrevs(rev, rev)
3062 segmentforrevs(rev, rev)
3063
3063
3064 def doreadcachedfh():
3064 def doreadcachedfh():
3065 rl.clearcaches()
3065 rl.clearcaches()
3066 fh = rlfh(rl)
3066 fh = rlfh(rl)
3067 for rev in revs:
3067 for rev in revs:
3068 segmentforrevs(rev, rev, df=fh)
3068 segmentforrevs(rev, rev, df=fh)
3069
3069
3070 def doreadbatch():
3070 def doreadbatch():
3071 rl.clearcaches()
3071 rl.clearcaches()
3072 segmentforrevs(revs[0], revs[-1])
3072 segmentforrevs(revs[0], revs[-1])
3073
3073
3074 def doreadbatchcachedfh():
3074 def doreadbatchcachedfh():
3075 rl.clearcaches()
3075 rl.clearcaches()
3076 fh = rlfh(rl)
3076 fh = rlfh(rl)
3077 segmentforrevs(revs[0], revs[-1], df=fh)
3077 segmentforrevs(revs[0], revs[-1], df=fh)
3078
3078
3079 def dochunk():
3079 def dochunk():
3080 rl.clearcaches()
3080 rl.clearcaches()
3081 fh = rlfh(rl)
3081 fh = rlfh(rl)
3082 for rev in revs:
3082 for rev in revs:
3083 rl._chunk(rev, df=fh)
3083 rl._chunk(rev, df=fh)
3084
3084
3085 chunks = [None]
3085 chunks = [None]
3086
3086
3087 def dochunkbatch():
3087 def dochunkbatch():
3088 rl.clearcaches()
3088 rl.clearcaches()
3089 fh = rlfh(rl)
3089 fh = rlfh(rl)
3090 # Save chunks as a side-effect.
3090 # Save chunks as a side-effect.
3091 chunks[0] = rl._chunks(revs, df=fh)
3091 chunks[0] = rl._chunks(revs, df=fh)
3092
3092
3093 def docompress(compressor):
3093 def docompress(compressor):
3094 rl.clearcaches()
3094 rl.clearcaches()
3095
3095
3096 try:
3096 try:
3097 # Swap in the requested compression engine.
3097 # Swap in the requested compression engine.
3098 oldcompressor = rl._compressor
3098 oldcompressor = rl._compressor
3099 rl._compressor = compressor
3099 rl._compressor = compressor
3100 for chunk in chunks[0]:
3100 for chunk in chunks[0]:
3101 rl.compress(chunk)
3101 rl.compress(chunk)
3102 finally:
3102 finally:
3103 rl._compressor = oldcompressor
3103 rl._compressor = oldcompressor
3104
3104
3105 benches = [
3105 benches = [
3106 (lambda: doread(), b'read'),
3106 (lambda: doread(), b'read'),
3107 (lambda: doreadcachedfh(), b'read w/ reused fd'),
3107 (lambda: doreadcachedfh(), b'read w/ reused fd'),
3108 (lambda: doreadbatch(), b'read batch'),
3108 (lambda: doreadbatch(), b'read batch'),
3109 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
3109 (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
3110 (lambda: dochunk(), b'chunk'),
3110 (lambda: dochunk(), b'chunk'),
3111 (lambda: dochunkbatch(), b'chunk batch'),
3111 (lambda: dochunkbatch(), b'chunk batch'),
3112 ]
3112 ]
3113
3113
3114 for engine in sorted(engines):
3114 for engine in sorted(engines):
3115 compressor = util.compengines[engine].revlogcompressor()
3115 compressor = util.compengines[engine].revlogcompressor()
3116 benches.append(
3116 benches.append(
3117 (
3117 (
3118 functools.partial(docompress, compressor),
3118 functools.partial(docompress, compressor),
3119 b'compress w/ %s' % engine,
3119 b'compress w/ %s' % engine,
3120 )
3120 )
3121 )
3121 )
3122
3122
3123 for fn, title in benches:
3123 for fn, title in benches:
3124 timer, fm = gettimer(ui, opts)
3124 timer, fm = gettimer(ui, opts)
3125 timer(fn, title=title)
3125 timer(fn, title=title)
3126 fm.end()
3126 fm.end()
3127
3127
3128
3128
3129 @command(
3129 @command(
3130 b'perfrevlogrevision',
3130 b'perfrevlogrevision',
3131 revlogopts
3131 revlogopts
3132 + formatteropts
3132 + formatteropts
3133 + [(b'', b'cache', False, b'use caches instead of clearing')],
3133 + [(b'', b'cache', False, b'use caches instead of clearing')],
3134 b'-c|-m|FILE REV',
3134 b'-c|-m|FILE REV',
3135 )
3135 )
3136 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
3136 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
3137 """Benchmark obtaining a revlog revision.
3137 """Benchmark obtaining a revlog revision.
3138
3138
3139 Obtaining a revlog revision consists of roughly the following steps:
3139 Obtaining a revlog revision consists of roughly the following steps:
3140
3140
3141 1. Compute the delta chain
3141 1. Compute the delta chain
3142 2. Slice the delta chain if applicable
3142 2. Slice the delta chain if applicable
3143 3. Obtain the raw chunks for that delta chain
3143 3. Obtain the raw chunks for that delta chain
3144 4. Decompress each raw chunk
3144 4. Decompress each raw chunk
3145 5. Apply binary patches to obtain fulltext
3145 5. Apply binary patches to obtain fulltext
3146 6. Verify hash of fulltext
3146 6. Verify hash of fulltext
3147
3147
3148 This command measures the time spent in each of these phases.
3148 This command measures the time spent in each of these phases.
3149 """
3149 """
3150 opts = _byteskwargs(opts)
3150 opts = _byteskwargs(opts)
3151
3151
3152 if opts.get(b'changelog') or opts.get(b'manifest'):
3152 if opts.get(b'changelog') or opts.get(b'manifest'):
3153 file_, rev = None, file_
3153 file_, rev = None, file_
3154 elif rev is None:
3154 elif rev is None:
3155 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
3155 raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
3156
3156
3157 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
3157 r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
3158
3158
3159 # _chunkraw was renamed to _getsegmentforrevs.
3159 # _chunkraw was renamed to _getsegmentforrevs.
3160 try:
3160 try:
3161 segmentforrevs = r._getsegmentforrevs
3161 segmentforrevs = r._getsegmentforrevs
3162 except AttributeError:
3162 except AttributeError:
3163 segmentforrevs = r._chunkraw
3163 segmentforrevs = r._chunkraw
3164
3164
3165 node = r.lookup(rev)
3165 node = r.lookup(rev)
3166 rev = r.rev(node)
3166 rev = r.rev(node)
3167
3167
3168 def getrawchunks(data, chain):
3168 def getrawchunks(data, chain):
3169 start = r.start
3169 start = r.start
3170 length = r.length
3170 length = r.length
3171 inline = r._inline
3171 inline = r._inline
3172 iosize = r._io.size
3172 iosize = r._io.size
3173 buffer = util.buffer
3173 buffer = util.buffer
3174
3174
3175 chunks = []
3175 chunks = []
3176 ladd = chunks.append
3176 ladd = chunks.append
3177 for idx, item in enumerate(chain):
3177 for idx, item in enumerate(chain):
3178 offset = start(item[0])
3178 offset = start(item[0])
3179 bits = data[idx]
3179 bits = data[idx]
3180 for rev in item:
3180 for rev in item:
3181 chunkstart = start(rev)
3181 chunkstart = start(rev)
3182 if inline:
3182 if inline:
3183 chunkstart += (rev + 1) * iosize
3183 chunkstart += (rev + 1) * iosize
3184 chunklength = length(rev)
3184 chunklength = length(rev)
3185 ladd(buffer(bits, chunkstart - offset, chunklength))
3185 ladd(buffer(bits, chunkstart - offset, chunklength))
3186
3186
3187 return chunks
3187 return chunks
3188
3188
3189 def dodeltachain(rev):
3189 def dodeltachain(rev):
3190 if not cache:
3190 if not cache:
3191 r.clearcaches()
3191 r.clearcaches()
3192 r._deltachain(rev)
3192 r._deltachain(rev)
3193
3193
3194 def doread(chain):
3194 def doread(chain):
3195 if not cache:
3195 if not cache:
3196 r.clearcaches()
3196 r.clearcaches()
3197 for item in slicedchain:
3197 for item in slicedchain:
3198 segmentforrevs(item[0], item[-1])
3198 segmentforrevs(item[0], item[-1])
3199
3199
3200 def doslice(r, chain, size):
3200 def doslice(r, chain, size):
3201 for s in slicechunk(r, chain, targetsize=size):
3201 for s in slicechunk(r, chain, targetsize=size):
3202 pass
3202 pass
3203
3203
3204 def dorawchunks(data, chain):
3204 def dorawchunks(data, chain):
3205 if not cache:
3205 if not cache:
3206 r.clearcaches()
3206 r.clearcaches()
3207 getrawchunks(data, chain)
3207 getrawchunks(data, chain)
3208
3208
3209 def dodecompress(chunks):
3209 def dodecompress(chunks):
3210 decomp = r.decompress
3210 decomp = r.decompress
3211 for chunk in chunks:
3211 for chunk in chunks:
3212 decomp(chunk)
3212 decomp(chunk)
3213
3213
3214 def dopatch(text, bins):
3214 def dopatch(text, bins):
3215 if not cache:
3215 if not cache:
3216 r.clearcaches()
3216 r.clearcaches()
3217 mdiff.patches(text, bins)
3217 mdiff.patches(text, bins)
3218
3218
3219 def dohash(text):
3219 def dohash(text):
3220 if not cache:
3220 if not cache:
3221 r.clearcaches()
3221 r.clearcaches()
3222 r.checkhash(text, node, rev=rev)
3222 r.checkhash(text, node, rev=rev)
3223
3223
3224 def dorevision():
3224 def dorevision():
3225 if not cache:
3225 if not cache:
3226 r.clearcaches()
3226 r.clearcaches()
3227 r.revision(node)
3227 r.revision(node)
3228
3228
3229 try:
3229 try:
3230 from mercurial.revlogutils.deltas import slicechunk
3230 from mercurial.revlogutils.deltas import slicechunk
3231 except ImportError:
3231 except ImportError:
3232 slicechunk = getattr(revlog, '_slicechunk', None)
3232 slicechunk = getattr(revlog, '_slicechunk', None)
3233
3233
3234 size = r.length(rev)
3234 size = r.length(rev)
3235 chain = r._deltachain(rev)[0]
3235 chain = r._deltachain(rev)[0]
3236 if not getattr(r, '_withsparseread', False):
3236 if not getattr(r, '_withsparseread', False):
3237 slicedchain = (chain,)
3237 slicedchain = (chain,)
3238 else:
3238 else:
3239 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
3239 slicedchain = tuple(slicechunk(r, chain, targetsize=size))
3240 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
3240 data = [segmentforrevs(seg[0], seg[-1])[1] for seg in slicedchain]
3241 rawchunks = getrawchunks(data, slicedchain)
3241 rawchunks = getrawchunks(data, slicedchain)
3242 bins = r._chunks(chain)
3242 bins = r._chunks(chain)
3243 text = bytes(bins[0])
3243 text = bytes(bins[0])
3244 bins = bins[1:]
3244 bins = bins[1:]
3245 text = mdiff.patches(text, bins)
3245 text = mdiff.patches(text, bins)
3246
3246
3247 benches = [
3247 benches = [
3248 (lambda: dorevision(), b'full'),
3248 (lambda: dorevision(), b'full'),
3249 (lambda: dodeltachain(rev), b'deltachain'),
3249 (lambda: dodeltachain(rev), b'deltachain'),
3250 (lambda: doread(chain), b'read'),
3250 (lambda: doread(chain), b'read'),
3251 ]
3251 ]
3252
3252
3253 if getattr(r, '_withsparseread', False):
3253 if getattr(r, '_withsparseread', False):
3254 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
3254 slicing = (lambda: doslice(r, chain, size), b'slice-sparse-chain')
3255 benches.append(slicing)
3255 benches.append(slicing)
3256
3256
3257 benches.extend(
3257 benches.extend(
3258 [
3258 [
3259 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
3259 (lambda: dorawchunks(data, slicedchain), b'rawchunks'),
3260 (lambda: dodecompress(rawchunks), b'decompress'),
3260 (lambda: dodecompress(rawchunks), b'decompress'),
3261 (lambda: dopatch(text, bins), b'patch'),
3261 (lambda: dopatch(text, bins), b'patch'),
3262 (lambda: dohash(text), b'hash'),
3262 (lambda: dohash(text), b'hash'),
3263 ]
3263 ]
3264 )
3264 )
3265
3265
3266 timer, fm = gettimer(ui, opts)
3266 timer, fm = gettimer(ui, opts)
3267 for fn, title in benches:
3267 for fn, title in benches:
3268 timer(fn, title=title)
3268 timer(fn, title=title)
3269 fm.end()
3269 fm.end()
3270
3270
3271
3271
3272 @command(
3272 @command(
3273 b'perfrevset',
3273 b'perfrevset',
3274 [
3274 [
3275 (b'C', b'clear', False, b'clear volatile cache between each call.'),
3275 (b'C', b'clear', False, b'clear volatile cache between each call.'),
3276 (b'', b'contexts', False, b'obtain changectx for each revision'),
3276 (b'', b'contexts', False, b'obtain changectx for each revision'),
3277 ]
3277 ]
3278 + formatteropts,
3278 + formatteropts,
3279 b"REVSET",
3279 b"REVSET",
3280 )
3280 )
3281 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
3281 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
3282 """benchmark the execution time of a revset
3282 """benchmark the execution time of a revset
3283
3283
3284 Use the --clean option if need to evaluate the impact of build volatile
3284 Use the --clean option if need to evaluate the impact of build volatile
3285 revisions set cache on the revset execution. Volatile cache hold filtered
3285 revisions set cache on the revset execution. Volatile cache hold filtered
3286 and obsolete related cache."""
3286 and obsolete related cache."""
3287 opts = _byteskwargs(opts)
3287 opts = _byteskwargs(opts)
3288
3288
3289 timer, fm = gettimer(ui, opts)
3289 timer, fm = gettimer(ui, opts)
3290
3290
3291 def d():
3291 def d():
3292 if clear:
3292 if clear:
3293 repo.invalidatevolatilesets()
3293 repo.invalidatevolatilesets()
3294 if contexts:
3294 if contexts:
3295 for ctx in repo.set(expr):
3295 for ctx in repo.set(expr):
3296 pass
3296 pass
3297 else:
3297 else:
3298 for r in repo.revs(expr):
3298 for r in repo.revs(expr):
3299 pass
3299 pass
3300
3300
3301 timer(d)
3301 timer(d)
3302 fm.end()
3302 fm.end()
3303
3303
3304
3304
3305 @command(
3305 @command(
3306 b'perfvolatilesets',
3306 b'perfvolatilesets',
3307 [(b'', b'clear-obsstore', False, b'drop obsstore between each call.'),]
3307 [(b'', b'clear-obsstore', False, b'drop obsstore between each call.'),]
3308 + formatteropts,
3308 + formatteropts,
3309 )
3309 )
3310 def perfvolatilesets(ui, repo, *names, **opts):
3310 def perfvolatilesets(ui, repo, *names, **opts):
3311 """benchmark the computation of various volatile set
3311 """benchmark the computation of various volatile set
3312
3312
3313 Volatile set computes element related to filtering and obsolescence."""
3313 Volatile set computes element related to filtering and obsolescence."""
3314 opts = _byteskwargs(opts)
3314 opts = _byteskwargs(opts)
3315 timer, fm = gettimer(ui, opts)
3315 timer, fm = gettimer(ui, opts)
3316 repo = repo.unfiltered()
3316 repo = repo.unfiltered()
3317
3317
3318 def getobs(name):
3318 def getobs(name):
3319 def d():
3319 def d():
3320 repo.invalidatevolatilesets()
3320 repo.invalidatevolatilesets()
3321 if opts[b'clear_obsstore']:
3321 if opts[b'clear_obsstore']:
3322 clearfilecache(repo, b'obsstore')
3322 clearfilecache(repo, b'obsstore')
3323 obsolete.getrevs(repo, name)
3323 obsolete.getrevs(repo, name)
3324
3324
3325 return d
3325 return d
3326
3326
3327 allobs = sorted(obsolete.cachefuncs)
3327 allobs = sorted(obsolete.cachefuncs)
3328 if names:
3328 if names:
3329 allobs = [n for n in allobs if n in names]
3329 allobs = [n for n in allobs if n in names]
3330
3330
3331 for name in allobs:
3331 for name in allobs:
3332 timer(getobs(name), title=name)
3332 timer(getobs(name), title=name)
3333
3333
3334 def getfiltered(name):
3334 def getfiltered(name):
3335 def d():
3335 def d():
3336 repo.invalidatevolatilesets()
3336 repo.invalidatevolatilesets()
3337 if opts[b'clear_obsstore']:
3337 if opts[b'clear_obsstore']:
3338 clearfilecache(repo, b'obsstore')
3338 clearfilecache(repo, b'obsstore')
3339 repoview.filterrevs(repo, name)
3339 repoview.filterrevs(repo, name)
3340
3340
3341 return d
3341 return d
3342
3342
3343 allfilter = sorted(repoview.filtertable)
3343 allfilter = sorted(repoview.filtertable)
3344 if names:
3344 if names:
3345 allfilter = [n for n in allfilter if n in names]
3345 allfilter = [n for n in allfilter if n in names]
3346
3346
3347 for name in allfilter:
3347 for name in allfilter:
3348 timer(getfiltered(name), title=name)
3348 timer(getfiltered(name), title=name)
3349 fm.end()
3349 fm.end()
3350
3350
3351
3351
3352 @command(
3352 @command(
3353 b'perfbranchmap',
3353 b'perfbranchmap',
3354 [
3354 [
3355 (b'f', b'full', False, b'Includes build time of subset'),
3355 (b'f', b'full', False, b'Includes build time of subset'),
3356 (
3356 (
3357 b'',
3357 b'',
3358 b'clear-revbranch',
3358 b'clear-revbranch',
3359 False,
3359 False,
3360 b'purge the revbranch cache between computation',
3360 b'purge the revbranch cache between computation',
3361 ),
3361 ),
3362 ]
3362 ]
3363 + formatteropts,
3363 + formatteropts,
3364 )
3364 )
3365 def perfbranchmap(ui, repo, *filternames, **opts):
3365 def perfbranchmap(ui, repo, *filternames, **opts):
3366 """benchmark the update of a branchmap
3366 """benchmark the update of a branchmap
3367
3367
3368 This benchmarks the full repo.branchmap() call with read and write disabled
3368 This benchmarks the full repo.branchmap() call with read and write disabled
3369 """
3369 """
3370 opts = _byteskwargs(opts)
3370 opts = _byteskwargs(opts)
3371 full = opts.get(b"full", False)
3371 full = opts.get(b"full", False)
3372 clear_revbranch = opts.get(b"clear_revbranch", False)
3372 clear_revbranch = opts.get(b"clear_revbranch", False)
3373 timer, fm = gettimer(ui, opts)
3373 timer, fm = gettimer(ui, opts)
3374
3374
3375 def getbranchmap(filtername):
3375 def getbranchmap(filtername):
3376 """generate a benchmark function for the filtername"""
3376 """generate a benchmark function for the filtername"""
3377 if filtername is None:
3377 if filtername is None:
3378 view = repo
3378 view = repo
3379 else:
3379 else:
3380 view = repo.filtered(filtername)
3380 view = repo.filtered(filtername)
3381 if util.safehasattr(view._branchcaches, '_per_filter'):
3381 if util.safehasattr(view._branchcaches, '_per_filter'):
3382 filtered = view._branchcaches._per_filter
3382 filtered = view._branchcaches._per_filter
3383 else:
3383 else:
3384 # older versions
3384 # older versions
3385 filtered = view._branchcaches
3385 filtered = view._branchcaches
3386
3386
3387 def d():
3387 def d():
3388 if clear_revbranch:
3388 if clear_revbranch:
3389 repo.revbranchcache()._clear()
3389 repo.revbranchcache()._clear()
3390 if full:
3390 if full:
3391 view._branchcaches.clear()
3391 view._branchcaches.clear()
3392 else:
3392 else:
3393 filtered.pop(filtername, None)
3393 filtered.pop(filtername, None)
3394 view.branchmap()
3394 view.branchmap()
3395
3395
3396 return d
3396 return d
3397
3397
3398 # add filter in smaller subset to bigger subset
3398 # add filter in smaller subset to bigger subset
3399 possiblefilters = set(repoview.filtertable)
3399 possiblefilters = set(repoview.filtertable)
3400 if filternames:
3400 if filternames:
3401 possiblefilters &= set(filternames)
3401 possiblefilters &= set(filternames)
3402 subsettable = getbranchmapsubsettable()
3402 subsettable = getbranchmapsubsettable()
3403 allfilters = []
3403 allfilters = []
3404 while possiblefilters:
3404 while possiblefilters:
3405 for name in possiblefilters:
3405 for name in possiblefilters:
3406 subset = subsettable.get(name)
3406 subset = subsettable.get(name)
3407 if subset not in possiblefilters:
3407 if subset not in possiblefilters:
3408 break
3408 break
3409 else:
3409 else:
3410 assert False, b'subset cycle %s!' % possiblefilters
3410 assert False, b'subset cycle %s!' % possiblefilters
3411 allfilters.append(name)
3411 allfilters.append(name)
3412 possiblefilters.remove(name)
3412 possiblefilters.remove(name)
3413
3413
3414 # warm the cache
3414 # warm the cache
3415 if not full:
3415 if not full:
3416 for name in allfilters:
3416 for name in allfilters:
3417 repo.filtered(name).branchmap()
3417 repo.filtered(name).branchmap()
3418 if not filternames or b'unfiltered' in filternames:
3418 if not filternames or b'unfiltered' in filternames:
3419 # add unfiltered
3419 # add unfiltered
3420 allfilters.append(None)
3420 allfilters.append(None)
3421
3421
3422 if util.safehasattr(branchmap.branchcache, 'fromfile'):
3422 if util.safehasattr(branchmap.branchcache, 'fromfile'):
3423 branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
3423 branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile')
3424 branchcacheread.set(classmethod(lambda *args: None))
3424 branchcacheread.set(classmethod(lambda *args: None))
3425 else:
3425 else:
3426 # older versions
3426 # older versions
3427 branchcacheread = safeattrsetter(branchmap, b'read')
3427 branchcacheread = safeattrsetter(branchmap, b'read')
3428 branchcacheread.set(lambda *args: None)
3428 branchcacheread.set(lambda *args: None)
3429 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
3429 branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
3430 branchcachewrite.set(lambda *args: None)
3430 branchcachewrite.set(lambda *args: None)
3431 try:
3431 try:
3432 for name in allfilters:
3432 for name in allfilters:
3433 printname = name
3433 printname = name
3434 if name is None:
3434 if name is None:
3435 printname = b'unfiltered'
3435 printname = b'unfiltered'
3436 timer(getbranchmap(name), title=str(printname))
3436 timer(getbranchmap(name), title=str(printname))
3437 finally:
3437 finally:
3438 branchcacheread.restore()
3438 branchcacheread.restore()
3439 branchcachewrite.restore()
3439 branchcachewrite.restore()
3440 fm.end()
3440 fm.end()
3441
3441
3442
3442
3443 @command(
3443 @command(
3444 b'perfbranchmapupdate',
3444 b'perfbranchmapupdate',
3445 [
3445 [
3446 (b'', b'base', [], b'subset of revision to start from'),
3446 (b'', b'base', [], b'subset of revision to start from'),
3447 (b'', b'target', [], b'subset of revision to end with'),
3447 (b'', b'target', [], b'subset of revision to end with'),
3448 (b'', b'clear-caches', False, b'clear cache between each runs'),
3448 (b'', b'clear-caches', False, b'clear cache between each runs'),
3449 ]
3449 ]
3450 + formatteropts,
3450 + formatteropts,
3451 )
3451 )
3452 def perfbranchmapupdate(ui, repo, base=(), target=(), **opts):
3452 def perfbranchmapupdate(ui, repo, base=(), target=(), **opts):
3453 """benchmark branchmap update from for <base> revs to <target> revs
3453 """benchmark branchmap update from for <base> revs to <target> revs
3454
3454
3455 If `--clear-caches` is passed, the following items will be reset before
3455 If `--clear-caches` is passed, the following items will be reset before
3456 each update:
3456 each update:
3457 * the changelog instance and associated indexes
3457 * the changelog instance and associated indexes
3458 * the rev-branch-cache instance
3458 * the rev-branch-cache instance
3459
3459
3460 Examples:
3460 Examples:
3461
3461
3462 # update for the one last revision
3462 # update for the one last revision
3463 $ hg perfbranchmapupdate --base 'not tip' --target 'tip'
3463 $ hg perfbranchmapupdate --base 'not tip' --target 'tip'
3464
3464
3465 $ update for change coming with a new branch
3465 $ update for change coming with a new branch
3466 $ hg perfbranchmapupdate --base 'stable' --target 'default'
3466 $ hg perfbranchmapupdate --base 'stable' --target 'default'
3467 """
3467 """
3468 from mercurial import branchmap
3468 from mercurial import branchmap
3469 from mercurial import repoview
3469 from mercurial import repoview
3470
3470
3471 opts = _byteskwargs(opts)
3471 opts = _byteskwargs(opts)
3472 timer, fm = gettimer(ui, opts)
3472 timer, fm = gettimer(ui, opts)
3473 clearcaches = opts[b'clear_caches']
3473 clearcaches = opts[b'clear_caches']
3474 unfi = repo.unfiltered()
3474 unfi = repo.unfiltered()
3475 x = [None] # used to pass data between closure
3475 x = [None] # used to pass data between closure
3476
3476
3477 # we use a `list` here to avoid possible side effect from smartset
3477 # we use a `list` here to avoid possible side effect from smartset
3478 baserevs = list(scmutil.revrange(repo, base))
3478 baserevs = list(scmutil.revrange(repo, base))
3479 targetrevs = list(scmutil.revrange(repo, target))
3479 targetrevs = list(scmutil.revrange(repo, target))
3480 if not baserevs:
3480 if not baserevs:
3481 raise error.Abort(b'no revisions selected for --base')
3481 raise error.Abort(b'no revisions selected for --base')
3482 if not targetrevs:
3482 if not targetrevs:
3483 raise error.Abort(b'no revisions selected for --target')
3483 raise error.Abort(b'no revisions selected for --target')
3484
3484
3485 # make sure the target branchmap also contains the one in the base
3485 # make sure the target branchmap also contains the one in the base
3486 targetrevs = list(set(baserevs) | set(targetrevs))
3486 targetrevs = list(set(baserevs) | set(targetrevs))
3487 targetrevs.sort()
3487 targetrevs.sort()
3488
3488
3489 cl = repo.changelog
3489 cl = repo.changelog
3490 allbaserevs = list(cl.ancestors(baserevs, inclusive=True))
3490 allbaserevs = list(cl.ancestors(baserevs, inclusive=True))
3491 allbaserevs.sort()
3491 allbaserevs.sort()
3492 alltargetrevs = frozenset(cl.ancestors(targetrevs, inclusive=True))
3492 alltargetrevs = frozenset(cl.ancestors(targetrevs, inclusive=True))
3493
3493
3494 newrevs = list(alltargetrevs.difference(allbaserevs))
3494 newrevs = list(alltargetrevs.difference(allbaserevs))
3495 newrevs.sort()
3495 newrevs.sort()
3496
3496
3497 allrevs = frozenset(unfi.changelog.revs())
3497 allrevs = frozenset(unfi.changelog.revs())
3498 basefilterrevs = frozenset(allrevs.difference(allbaserevs))
3498 basefilterrevs = frozenset(allrevs.difference(allbaserevs))
3499 targetfilterrevs = frozenset(allrevs.difference(alltargetrevs))
3499 targetfilterrevs = frozenset(allrevs.difference(alltargetrevs))
3500
3500
3501 def basefilter(repo, visibilityexceptions=None):
3501 def basefilter(repo, visibilityexceptions=None):
3502 return basefilterrevs
3502 return basefilterrevs
3503
3503
3504 def targetfilter(repo, visibilityexceptions=None):
3504 def targetfilter(repo, visibilityexceptions=None):
3505 return targetfilterrevs
3505 return targetfilterrevs
3506
3506
3507 msg = b'benchmark of branchmap with %d revisions with %d new ones\n'
3507 msg = b'benchmark of branchmap with %d revisions with %d new ones\n'
3508 ui.status(msg % (len(allbaserevs), len(newrevs)))
3508 ui.status(msg % (len(allbaserevs), len(newrevs)))
3509 if targetfilterrevs:
3509 if targetfilterrevs:
3510 msg = b'(%d revisions still filtered)\n'
3510 msg = b'(%d revisions still filtered)\n'
3511 ui.status(msg % len(targetfilterrevs))
3511 ui.status(msg % len(targetfilterrevs))
3512
3512
3513 try:
3513 try:
3514 repoview.filtertable[b'__perf_branchmap_update_base'] = basefilter
3514 repoview.filtertable[b'__perf_branchmap_update_base'] = basefilter
3515 repoview.filtertable[b'__perf_branchmap_update_target'] = targetfilter
3515 repoview.filtertable[b'__perf_branchmap_update_target'] = targetfilter
3516
3516
3517 baserepo = repo.filtered(b'__perf_branchmap_update_base')
3517 baserepo = repo.filtered(b'__perf_branchmap_update_base')
3518 targetrepo = repo.filtered(b'__perf_branchmap_update_target')
3518 targetrepo = repo.filtered(b'__perf_branchmap_update_target')
3519
3519
3520 # try to find an existing branchmap to reuse
3520 # try to find an existing branchmap to reuse
3521 subsettable = getbranchmapsubsettable()
3521 subsettable = getbranchmapsubsettable()
3522 candidatefilter = subsettable.get(None)
3522 candidatefilter = subsettable.get(None)
3523 while candidatefilter is not None:
3523 while candidatefilter is not None:
3524 candidatebm = repo.filtered(candidatefilter).branchmap()
3524 candidatebm = repo.filtered(candidatefilter).branchmap()
3525 if candidatebm.validfor(baserepo):
3525 if candidatebm.validfor(baserepo):
3526 filtered = repoview.filterrevs(repo, candidatefilter)
3526 filtered = repoview.filterrevs(repo, candidatefilter)
3527 missing = [r for r in allbaserevs if r in filtered]
3527 missing = [r for r in allbaserevs if r in filtered]
3528 base = candidatebm.copy()
3528 base = candidatebm.copy()
3529 base.update(baserepo, missing)
3529 base.update(baserepo, missing)
3530 break
3530 break
3531 candidatefilter = subsettable.get(candidatefilter)
3531 candidatefilter = subsettable.get(candidatefilter)
3532 else:
3532 else:
3533 # no suitable subset where found
3533 # no suitable subset where found
3534 base = branchmap.branchcache()
3534 base = branchmap.branchcache()
3535 base.update(baserepo, allbaserevs)
3535 base.update(baserepo, allbaserevs)
3536
3536
3537 def setup():
3537 def setup():
3538 x[0] = base.copy()
3538 x[0] = base.copy()
3539 if clearcaches:
3539 if clearcaches:
3540 unfi._revbranchcache = None
3540 unfi._revbranchcache = None
3541 clearchangelog(repo)
3541 clearchangelog(repo)
3542
3542
3543 def bench():
3543 def bench():
3544 x[0].update(targetrepo, newrevs)
3544 x[0].update(targetrepo, newrevs)
3545
3545
3546 timer(bench, setup=setup)
3546 timer(bench, setup=setup)
3547 fm.end()
3547 fm.end()
3548 finally:
3548 finally:
3549 repoview.filtertable.pop(b'__perf_branchmap_update_base', None)
3549 repoview.filtertable.pop(b'__perf_branchmap_update_base', None)
3550 repoview.filtertable.pop(b'__perf_branchmap_update_target', None)
3550 repoview.filtertable.pop(b'__perf_branchmap_update_target', None)
3551
3551
3552
3552
3553 @command(
3553 @command(
3554 b'perfbranchmapload',
3554 b'perfbranchmapload',
3555 [
3555 [
3556 (b'f', b'filter', b'', b'Specify repoview filter'),
3556 (b'f', b'filter', b'', b'Specify repoview filter'),
3557 (b'', b'list', False, b'List brachmap filter caches'),
3557 (b'', b'list', False, b'List brachmap filter caches'),
3558 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
3558 (b'', b'clear-revlogs', False, b'refresh changelog and manifest'),
3559 ]
3559 ]
3560 + formatteropts,
3560 + formatteropts,
3561 )
3561 )
3562 def perfbranchmapload(ui, repo, filter=b'', list=False, **opts):
3562 def perfbranchmapload(ui, repo, filter=b'', list=False, **opts):
3563 """benchmark reading the branchmap"""
3563 """benchmark reading the branchmap"""
3564 opts = _byteskwargs(opts)
3564 opts = _byteskwargs(opts)
3565 clearrevlogs = opts[b'clear_revlogs']
3565 clearrevlogs = opts[b'clear_revlogs']
3566
3566
3567 if list:
3567 if list:
3568 for name, kind, st in repo.cachevfs.readdir(stat=True):
3568 for name, kind, st in repo.cachevfs.readdir(stat=True):
3569 if name.startswith(b'branch2'):
3569 if name.startswith(b'branch2'):
3570 filtername = name.partition(b'-')[2] or b'unfiltered'
3570 filtername = name.partition(b'-')[2] or b'unfiltered'
3571 ui.status(
3571 ui.status(
3572 b'%s - %s\n' % (filtername, util.bytecount(st.st_size))
3572 b'%s - %s\n' % (filtername, util.bytecount(st.st_size))
3573 )
3573 )
3574 return
3574 return
3575 if not filter:
3575 if not filter:
3576 filter = None
3576 filter = None
3577 subsettable = getbranchmapsubsettable()
3577 subsettable = getbranchmapsubsettable()
3578 if filter is None:
3578 if filter is None:
3579 repo = repo.unfiltered()
3579 repo = repo.unfiltered()
3580 else:
3580 else:
3581 repo = repoview.repoview(repo, filter)
3581 repo = repoview.repoview(repo, filter)
3582
3582
3583 repo.branchmap() # make sure we have a relevant, up to date branchmap
3583 repo.branchmap() # make sure we have a relevant, up to date branchmap
3584
3584
3585 try:
3585 try:
3586 fromfile = branchmap.branchcache.fromfile
3586 fromfile = branchmap.branchcache.fromfile
3587 except AttributeError:
3587 except AttributeError:
3588 # older versions
3588 # older versions
3589 fromfile = branchmap.read
3589 fromfile = branchmap.read
3590
3590
3591 currentfilter = filter
3591 currentfilter = filter
3592 # try once without timer, the filter may not be cached
3592 # try once without timer, the filter may not be cached
3593 while fromfile(repo) is None:
3593 while fromfile(repo) is None:
3594 currentfilter = subsettable.get(currentfilter)
3594 currentfilter = subsettable.get(currentfilter)
3595 if currentfilter is None:
3595 if currentfilter is None:
3596 raise error.Abort(
3596 raise error.Abort(
3597 b'No branchmap cached for %s repo' % (filter or b'unfiltered')
3597 b'No branchmap cached for %s repo' % (filter or b'unfiltered')
3598 )
3598 )
3599 repo = repo.filtered(currentfilter)
3599 repo = repo.filtered(currentfilter)
3600 timer, fm = gettimer(ui, opts)
3600 timer, fm = gettimer(ui, opts)
3601
3601
3602 def setup():
3602 def setup():
3603 if clearrevlogs:
3603 if clearrevlogs:
3604 clearchangelog(repo)
3604 clearchangelog(repo)
3605
3605
3606 def bench():
3606 def bench():
3607 fromfile(repo)
3607 fromfile(repo)
3608
3608
3609 timer(bench, setup=setup)
3609 timer(bench, setup=setup)
3610 fm.end()
3610 fm.end()
3611
3611
3612
3612
3613 @command(b'perfloadmarkers')
3613 @command(b'perfloadmarkers')
3614 def perfloadmarkers(ui, repo):
3614 def perfloadmarkers(ui, repo):
3615 """benchmark the time to parse the on-disk markers for a repo
3615 """benchmark the time to parse the on-disk markers for a repo
3616
3616
3617 Result is the number of markers in the repo."""
3617 Result is the number of markers in the repo."""
3618 timer, fm = gettimer(ui)
3618 timer, fm = gettimer(ui)
3619 svfs = getsvfs(repo)
3619 svfs = getsvfs(repo)
3620 timer(lambda: len(obsolete.obsstore(svfs)))
3620 timer(lambda: len(obsolete.obsstore(svfs)))
3621 fm.end()
3621 fm.end()
3622
3622
3623
3623
3624 @command(
3624 @command(
3625 b'perflrucachedict',
3625 b'perflrucachedict',
3626 formatteropts
3626 formatteropts
3627 + [
3627 + [
3628 (b'', b'costlimit', 0, b'maximum total cost of items in cache'),
3628 (b'', b'costlimit', 0, b'maximum total cost of items in cache'),
3629 (b'', b'mincost', 0, b'smallest cost of items in cache'),
3629 (b'', b'mincost', 0, b'smallest cost of items in cache'),
3630 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
3630 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
3631 (b'', b'size', 4, b'size of cache'),
3631 (b'', b'size', 4, b'size of cache'),
3632 (b'', b'gets', 10000, b'number of key lookups'),
3632 (b'', b'gets', 10000, b'number of key lookups'),
3633 (b'', b'sets', 10000, b'number of key sets'),
3633 (b'', b'sets', 10000, b'number of key sets'),
3634 (b'', b'mixed', 10000, b'number of mixed mode operations'),
3634 (b'', b'mixed', 10000, b'number of mixed mode operations'),
3635 (
3635 (
3636 b'',
3636 b'',
3637 b'mixedgetfreq',
3637 b'mixedgetfreq',
3638 50,
3638 50,
3639 b'frequency of get vs set ops in mixed mode',
3639 b'frequency of get vs set ops in mixed mode',
3640 ),
3640 ),
3641 ],
3641 ],
3642 norepo=True,
3642 norepo=True,
3643 )
3643 )
3644 def perflrucache(
3644 def perflrucache(
3645 ui,
3645 ui,
3646 mincost=0,
3646 mincost=0,
3647 maxcost=100,
3647 maxcost=100,
3648 costlimit=0,
3648 costlimit=0,
3649 size=4,
3649 size=4,
3650 gets=10000,
3650 gets=10000,
3651 sets=10000,
3651 sets=10000,
3652 mixed=10000,
3652 mixed=10000,
3653 mixedgetfreq=50,
3653 mixedgetfreq=50,
3654 **opts
3654 **opts
3655 ):
3655 ):
3656 opts = _byteskwargs(opts)
3656 opts = _byteskwargs(opts)
3657
3657
3658 def doinit():
3658 def doinit():
3659 for i in _xrange(10000):
3659 for i in _xrange(10000):
3660 util.lrucachedict(size)
3660 util.lrucachedict(size)
3661
3661
3662 costrange = list(range(mincost, maxcost + 1))
3662 costrange = list(range(mincost, maxcost + 1))
3663
3663
3664 values = []
3664 values = []
3665 for i in _xrange(size):
3665 for i in _xrange(size):
3666 values.append(random.randint(0, _maxint))
3666 values.append(random.randint(0, _maxint))
3667
3667
3668 # Get mode fills the cache and tests raw lookup performance with no
3668 # Get mode fills the cache and tests raw lookup performance with no
3669 # eviction.
3669 # eviction.
3670 getseq = []
3670 getseq = []
3671 for i in _xrange(gets):
3671 for i in _xrange(gets):
3672 getseq.append(random.choice(values))
3672 getseq.append(random.choice(values))
3673
3673
3674 def dogets():
3674 def dogets():
3675 d = util.lrucachedict(size)
3675 d = util.lrucachedict(size)
3676 for v in values:
3676 for v in values:
3677 d[v] = v
3677 d[v] = v
3678 for key in getseq:
3678 for key in getseq:
3679 value = d[key]
3679 value = d[key]
3680 value # silence pyflakes warning
3680 value # silence pyflakes warning
3681
3681
3682 def dogetscost():
3682 def dogetscost():
3683 d = util.lrucachedict(size, maxcost=costlimit)
3683 d = util.lrucachedict(size, maxcost=costlimit)
3684 for i, v in enumerate(values):
3684 for i, v in enumerate(values):
3685 d.insert(v, v, cost=costs[i])
3685 d.insert(v, v, cost=costs[i])
3686 for key in getseq:
3686 for key in getseq:
3687 try:
3687 try:
3688 value = d[key]
3688 value = d[key]
3689 value # silence pyflakes warning
3689 value # silence pyflakes warning
3690 except KeyError:
3690 except KeyError:
3691 pass
3691 pass
3692
3692
3693 # Set mode tests insertion speed with cache eviction.
3693 # Set mode tests insertion speed with cache eviction.
3694 setseq = []
3694 setseq = []
3695 costs = []
3695 costs = []
3696 for i in _xrange(sets):
3696 for i in _xrange(sets):
3697 setseq.append(random.randint(0, _maxint))
3697 setseq.append(random.randint(0, _maxint))
3698 costs.append(random.choice(costrange))
3698 costs.append(random.choice(costrange))
3699
3699
3700 def doinserts():
3700 def doinserts():
3701 d = util.lrucachedict(size)
3701 d = util.lrucachedict(size)
3702 for v in setseq:
3702 for v in setseq:
3703 d.insert(v, v)
3703 d.insert(v, v)
3704
3704
3705 def doinsertscost():
3705 def doinsertscost():
3706 d = util.lrucachedict(size, maxcost=costlimit)
3706 d = util.lrucachedict(size, maxcost=costlimit)
3707 for i, v in enumerate(setseq):
3707 for i, v in enumerate(setseq):
3708 d.insert(v, v, cost=costs[i])
3708 d.insert(v, v, cost=costs[i])
3709
3709
3710 def dosets():
3710 def dosets():
3711 d = util.lrucachedict(size)
3711 d = util.lrucachedict(size)
3712 for v in setseq:
3712 for v in setseq:
3713 d[v] = v
3713 d[v] = v
3714
3714
3715 # Mixed mode randomly performs gets and sets with eviction.
3715 # Mixed mode randomly performs gets and sets with eviction.
3716 mixedops = []
3716 mixedops = []
3717 for i in _xrange(mixed):
3717 for i in _xrange(mixed):
3718 r = random.randint(0, 100)
3718 r = random.randint(0, 100)
3719 if r < mixedgetfreq:
3719 if r < mixedgetfreq:
3720 op = 0
3720 op = 0
3721 else:
3721 else:
3722 op = 1
3722 op = 1
3723
3723
3724 mixedops.append(
3724 mixedops.append(
3725 (op, random.randint(0, size * 2), random.choice(costrange))
3725 (op, random.randint(0, size * 2), random.choice(costrange))
3726 )
3726 )
3727
3727
3728 def domixed():
3728 def domixed():
3729 d = util.lrucachedict(size)
3729 d = util.lrucachedict(size)
3730
3730
3731 for op, v, cost in mixedops:
3731 for op, v, cost in mixedops:
3732 if op == 0:
3732 if op == 0:
3733 try:
3733 try:
3734 d[v]
3734 d[v]
3735 except KeyError:
3735 except KeyError:
3736 pass
3736 pass
3737 else:
3737 else:
3738 d[v] = v
3738 d[v] = v
3739
3739
3740 def domixedcost():
3740 def domixedcost():
3741 d = util.lrucachedict(size, maxcost=costlimit)
3741 d = util.lrucachedict(size, maxcost=costlimit)
3742
3742
3743 for op, v, cost in mixedops:
3743 for op, v, cost in mixedops:
3744 if op == 0:
3744 if op == 0:
3745 try:
3745 try:
3746 d[v]
3746 d[v]
3747 except KeyError:
3747 except KeyError:
3748 pass
3748 pass
3749 else:
3749 else:
3750 d.insert(v, v, cost=cost)
3750 d.insert(v, v, cost=cost)
3751
3751
3752 benches = [
3752 benches = [
3753 (doinit, b'init'),
3753 (doinit, b'init'),
3754 ]
3754 ]
3755
3755
3756 if costlimit:
3756 if costlimit:
3757 benches.extend(
3757 benches.extend(
3758 [
3758 [
3759 (dogetscost, b'gets w/ cost limit'),
3759 (dogetscost, b'gets w/ cost limit'),
3760 (doinsertscost, b'inserts w/ cost limit'),
3760 (doinsertscost, b'inserts w/ cost limit'),
3761 (domixedcost, b'mixed w/ cost limit'),
3761 (domixedcost, b'mixed w/ cost limit'),
3762 ]
3762 ]
3763 )
3763 )
3764 else:
3764 else:
3765 benches.extend(
3765 benches.extend(
3766 [
3766 [
3767 (dogets, b'gets'),
3767 (dogets, b'gets'),
3768 (doinserts, b'inserts'),
3768 (doinserts, b'inserts'),
3769 (dosets, b'sets'),
3769 (dosets, b'sets'),
3770 (domixed, b'mixed'),
3770 (domixed, b'mixed'),
3771 ]
3771 ]
3772 )
3772 )
3773
3773
3774 for fn, title in benches:
3774 for fn, title in benches:
3775 timer, fm = gettimer(ui, opts)
3775 timer, fm = gettimer(ui, opts)
3776 timer(fn, title=title)
3776 timer(fn, title=title)
3777 fm.end()
3777 fm.end()
3778
3778
3779
3779
3780 @command(b'perfwrite', formatteropts)
3780 @command(b'perfwrite', formatteropts)
3781 def perfwrite(ui, repo, **opts):
3781 def perfwrite(ui, repo, **opts):
3782 """microbenchmark ui.write
3782 """microbenchmark ui.write
3783 """
3783 """
3784 opts = _byteskwargs(opts)
3784 opts = _byteskwargs(opts)
3785
3785
3786 timer, fm = gettimer(ui, opts)
3786 timer, fm = gettimer(ui, opts)
3787
3787
3788 def write():
3788 def write():
3789 for i in range(100000):
3789 for i in range(100000):
3790 ui.writenoi18n(b'Testing write performance\n')
3790 ui.writenoi18n(b'Testing write performance\n')
3791
3791
3792 timer(write)
3792 timer(write)
3793 fm.end()
3793 fm.end()
3794
3794
3795
3795
3796 def uisetup(ui):
3796 def uisetup(ui):
3797 if util.safehasattr(cmdutil, b'openrevlog') and not util.safehasattr(
3797 if util.safehasattr(cmdutil, b'openrevlog') and not util.safehasattr(
3798 commands, b'debugrevlogopts'
3798 commands, b'debugrevlogopts'
3799 ):
3799 ):
3800 # for "historical portability":
3800 # for "historical portability":
3801 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
3801 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
3802 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
3802 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
3803 # openrevlog() should cause failure, because it has been
3803 # openrevlog() should cause failure, because it has been
3804 # available since 3.5 (or 49c583ca48c4).
3804 # available since 3.5 (or 49c583ca48c4).
3805 def openrevlog(orig, repo, cmd, file_, opts):
3805 def openrevlog(orig, repo, cmd, file_, opts):
3806 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
3806 if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
3807 raise error.Abort(
3807 raise error.Abort(
3808 b"This version doesn't support --dir option",
3808 b"This version doesn't support --dir option",
3809 hint=b"use 3.5 or later",
3809 hint=b"use 3.5 or later",
3810 )
3810 )
3811 return orig(repo, cmd, file_, opts)
3811 return orig(repo, cmd, file_, opts)
3812
3812
3813 extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
3813 extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
3814
3814
3815
3815
3816 @command(
3816 @command(
3817 b'perfprogress',
3817 b'perfprogress',
3818 formatteropts
3818 formatteropts
3819 + [
3819 + [
3820 (b'', b'topic', b'topic', b'topic for progress messages'),
3820 (b'', b'topic', b'topic', b'topic for progress messages'),
3821 (b'c', b'total', 1000000, b'total value we are progressing to'),
3821 (b'c', b'total', 1000000, b'total value we are progressing to'),
3822 ],
3822 ],
3823 norepo=True,
3823 norepo=True,
3824 )
3824 )
3825 def perfprogress(ui, topic=None, total=None, **opts):
3825 def perfprogress(ui, topic=None, total=None, **opts):
3826 """printing of progress bars"""
3826 """printing of progress bars"""
3827 opts = _byteskwargs(opts)
3827 opts = _byteskwargs(opts)
3828
3828
3829 timer, fm = gettimer(ui, opts)
3829 timer, fm = gettimer(ui, opts)
3830
3830
3831 def doprogress():
3831 def doprogress():
3832 with ui.makeprogress(topic, total=total) as progress:
3832 with ui.makeprogress(topic, total=total) as progress:
3833 for i in _xrange(total):
3833 for i in _xrange(total):
3834 progress.increment()
3834 progress.increment()
3835
3835
3836 timer(doprogress)
3836 timer(doprogress)
3837 fm.end()
3837 fm.end()
@@ -1,2947 +1,2947 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end 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 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import contextlib
17 import contextlib
18 import errno
18 import errno
19 import io
19 import io
20 import os
20 import os
21 import struct
21 import struct
22 import zlib
22 import zlib
23
23
24 # import stuff from node for others to import from revlog
24 # import stuff from node for others to import from revlog
25 from .node import (
25 from .node import (
26 bin,
26 bin,
27 hex,
27 hex,
28 nullhex,
28 nullhex,
29 nullid,
29 nullid,
30 nullrev,
30 nullrev,
31 short,
31 short,
32 wdirfilenodeids,
32 wdirfilenodeids,
33 wdirhex,
33 wdirhex,
34 wdirid,
34 wdirid,
35 wdirrev,
35 wdirrev,
36 )
36 )
37 from .i18n import _
37 from .i18n import _
38 from .pycompat import getattr
38 from .pycompat import getattr
39 from .revlogutils.constants import (
39 from .revlogutils.constants import (
40 FLAG_GENERALDELTA,
40 FLAG_GENERALDELTA,
41 FLAG_INLINE_DATA,
41 FLAG_INLINE_DATA,
42 REVLOGV0,
42 REVLOGV0,
43 REVLOGV1,
43 REVLOGV1,
44 REVLOGV1_FLAGS,
44 REVLOGV1_FLAGS,
45 REVLOGV2,
45 REVLOGV2,
46 REVLOGV2_FLAGS,
46 REVLOGV2_FLAGS,
47 REVLOG_DEFAULT_FLAGS,
47 REVLOG_DEFAULT_FLAGS,
48 REVLOG_DEFAULT_FORMAT,
48 REVLOG_DEFAULT_FORMAT,
49 REVLOG_DEFAULT_VERSION,
49 REVLOG_DEFAULT_VERSION,
50 )
50 )
51 from .revlogutils.flagutil import (
51 from .revlogutils.flagutil import (
52 REVIDX_DEFAULT_FLAGS,
52 REVIDX_DEFAULT_FLAGS,
53 REVIDX_ELLIPSIS,
53 REVIDX_ELLIPSIS,
54 REVIDX_EXTSTORED,
54 REVIDX_EXTSTORED,
55 REVIDX_FLAGS_ORDER,
55 REVIDX_FLAGS_ORDER,
56 REVIDX_ISCENSORED,
56 REVIDX_ISCENSORED,
57 REVIDX_RAWTEXT_CHANGING_FLAGS,
57 REVIDX_RAWTEXT_CHANGING_FLAGS,
58 REVIDX_SIDEDATA,
58 REVIDX_SIDEDATA,
59 )
59 )
60 from .thirdparty import attr
60 from .thirdparty import attr
61 from . import (
61 from . import (
62 ancestor,
62 ancestor,
63 dagop,
63 dagop,
64 error,
64 error,
65 mdiff,
65 mdiff,
66 policy,
66 policy,
67 pycompat,
67 pycompat,
68 revlogutils,
68 revlogutils,
69 templatefilters,
69 templatefilters,
70 util,
70 util,
71 )
71 )
72 from .interfaces import (
72 from .interfaces import (
73 repository,
73 repository,
74 util as interfaceutil,
74 util as interfaceutil,
75 )
75 )
76 from .revlogutils import (
76 from .revlogutils import (
77 deltas as deltautil,
77 deltas as deltautil,
78 flagutil,
78 flagutil,
79 sidedata as sidedatautil,
79 sidedata as sidedatautil,
80 )
80 )
81 from .utils import (
81 from .utils import (
82 storageutil,
82 storageutil,
83 stringutil,
83 stringutil,
84 )
84 )
85
85
86 # blanked usage of all the name to prevent pyflakes constraints
86 # blanked usage of all the name to prevent pyflakes constraints
87 # We need these name available in the module for extensions.
87 # We need these name available in the module for extensions.
88 REVLOGV0
88 REVLOGV0
89 REVLOGV1
89 REVLOGV1
90 REVLOGV2
90 REVLOGV2
91 FLAG_INLINE_DATA
91 FLAG_INLINE_DATA
92 FLAG_GENERALDELTA
92 FLAG_GENERALDELTA
93 REVLOG_DEFAULT_FLAGS
93 REVLOG_DEFAULT_FLAGS
94 REVLOG_DEFAULT_FORMAT
94 REVLOG_DEFAULT_FORMAT
95 REVLOG_DEFAULT_VERSION
95 REVLOG_DEFAULT_VERSION
96 REVLOGV1_FLAGS
96 REVLOGV1_FLAGS
97 REVLOGV2_FLAGS
97 REVLOGV2_FLAGS
98 REVIDX_ISCENSORED
98 REVIDX_ISCENSORED
99 REVIDX_ELLIPSIS
99 REVIDX_ELLIPSIS
100 REVIDX_SIDEDATA
100 REVIDX_SIDEDATA
101 REVIDX_EXTSTORED
101 REVIDX_EXTSTORED
102 REVIDX_DEFAULT_FLAGS
102 REVIDX_DEFAULT_FLAGS
103 REVIDX_FLAGS_ORDER
103 REVIDX_FLAGS_ORDER
104 REVIDX_RAWTEXT_CHANGING_FLAGS
104 REVIDX_RAWTEXT_CHANGING_FLAGS
105
105
106 parsers = policy.importmod('parsers')
106 parsers = policy.importmod('parsers')
107 rustancestor = policy.importrust('ancestor')
107 rustancestor = policy.importrust('ancestor')
108 rustdagop = policy.importrust('dagop')
108 rustdagop = policy.importrust('dagop')
109
109
110 # Aliased for performance.
110 # Aliased for performance.
111 _zlibdecompress = zlib.decompress
111 _zlibdecompress = zlib.decompress
112
112
113 # max size of revlog with inline data
113 # max size of revlog with inline data
114 _maxinline = 131072
114 _maxinline = 131072
115 _chunksize = 1048576
115 _chunksize = 1048576
116
116
117 # Flag processors for REVIDX_ELLIPSIS.
117 # Flag processors for REVIDX_ELLIPSIS.
118 def ellipsisreadprocessor(rl, text):
118 def ellipsisreadprocessor(rl, text):
119 return text, False, {}
119 return text, False, {}
120
120
121
121
122 def ellipsiswriteprocessor(rl, text, sidedata):
122 def ellipsiswriteprocessor(rl, text, sidedata):
123 return text, False
123 return text, False
124
124
125
125
126 def ellipsisrawprocessor(rl, text):
126 def ellipsisrawprocessor(rl, text):
127 return False
127 return False
128
128
129
129
130 ellipsisprocessor = (
130 ellipsisprocessor = (
131 ellipsisreadprocessor,
131 ellipsisreadprocessor,
132 ellipsiswriteprocessor,
132 ellipsiswriteprocessor,
133 ellipsisrawprocessor,
133 ellipsisrawprocessor,
134 )
134 )
135
135
136
136
137 def getoffset(q):
137 def getoffset(q):
138 return int(q >> 16)
138 return int(q >> 16)
139
139
140
140
141 def gettype(q):
141 def gettype(q):
142 return int(q & 0xFFFF)
142 return int(q & 0xFFFF)
143
143
144
144
145 def offset_type(offset, type):
145 def offset_type(offset, type):
146 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
146 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
147 raise ValueError(b'unknown revlog index flags')
147 raise ValueError(b'unknown revlog index flags')
148 return int(int(offset) << 16 | type)
148 return int(int(offset) << 16 | type)
149
149
150
150
151 @attr.s(slots=True, frozen=True)
151 @attr.s(slots=True, frozen=True)
152 class _revisioninfo(object):
152 class _revisioninfo(object):
153 """Information about a revision that allows building its fulltext
153 """Information about a revision that allows building its fulltext
154 node: expected hash of the revision
154 node: expected hash of the revision
155 p1, p2: parent revs of the revision
155 p1, p2: parent revs of the revision
156 btext: built text cache consisting of a one-element list
156 btext: built text cache consisting of a one-element list
157 cachedelta: (baserev, uncompressed_delta) or None
157 cachedelta: (baserev, uncompressed_delta) or None
158 flags: flags associated to the revision storage
158 flags: flags associated to the revision storage
159
159
160 One of btext[0] or cachedelta must be set.
160 One of btext[0] or cachedelta must be set.
161 """
161 """
162
162
163 node = attr.ib()
163 node = attr.ib()
164 p1 = attr.ib()
164 p1 = attr.ib()
165 p2 = attr.ib()
165 p2 = attr.ib()
166 btext = attr.ib()
166 btext = attr.ib()
167 textlen = attr.ib()
167 textlen = attr.ib()
168 cachedelta = attr.ib()
168 cachedelta = attr.ib()
169 flags = attr.ib()
169 flags = attr.ib()
170
170
171
171
172 @interfaceutil.implementer(repository.irevisiondelta)
172 @interfaceutil.implementer(repository.irevisiondelta)
173 @attr.s(slots=True)
173 @attr.s(slots=True)
174 class revlogrevisiondelta(object):
174 class revlogrevisiondelta(object):
175 node = attr.ib()
175 node = attr.ib()
176 p1node = attr.ib()
176 p1node = attr.ib()
177 p2node = attr.ib()
177 p2node = attr.ib()
178 basenode = attr.ib()
178 basenode = attr.ib()
179 flags = attr.ib()
179 flags = attr.ib()
180 baserevisionsize = attr.ib()
180 baserevisionsize = attr.ib()
181 revision = attr.ib()
181 revision = attr.ib()
182 delta = attr.ib()
182 delta = attr.ib()
183 linknode = attr.ib(default=None)
183 linknode = attr.ib(default=None)
184
184
185
185
186 @interfaceutil.implementer(repository.iverifyproblem)
186 @interfaceutil.implementer(repository.iverifyproblem)
187 @attr.s(frozen=True)
187 @attr.s(frozen=True)
188 class revlogproblem(object):
188 class revlogproblem(object):
189 warning = attr.ib(default=None)
189 warning = attr.ib(default=None)
190 error = attr.ib(default=None)
190 error = attr.ib(default=None)
191 node = attr.ib(default=None)
191 node = attr.ib(default=None)
192
192
193
193
194 # index v0:
194 # index v0:
195 # 4 bytes: offset
195 # 4 bytes: offset
196 # 4 bytes: compressed length
196 # 4 bytes: compressed length
197 # 4 bytes: base rev
197 # 4 bytes: base rev
198 # 4 bytes: link rev
198 # 4 bytes: link rev
199 # 20 bytes: parent 1 nodeid
199 # 20 bytes: parent 1 nodeid
200 # 20 bytes: parent 2 nodeid
200 # 20 bytes: parent 2 nodeid
201 # 20 bytes: nodeid
201 # 20 bytes: nodeid
202 indexformatv0 = struct.Struct(b">4l20s20s20s")
202 indexformatv0 = struct.Struct(b">4l20s20s20s")
203 indexformatv0_pack = indexformatv0.pack
203 indexformatv0_pack = indexformatv0.pack
204 indexformatv0_unpack = indexformatv0.unpack
204 indexformatv0_unpack = indexformatv0.unpack
205
205
206
206
207 class revlogoldindex(list):
207 class revlogoldindex(list):
208 @util.propertycache
208 @util.propertycache
209 def nodemap(self):
209 def nodemap(self):
210 nodemap = revlogutils.NodeMap({nullid: nullrev})
210 nodemap = revlogutils.NodeMap({nullid: nullrev})
211 for r in range(0, len(self)):
211 for r in range(0, len(self)):
212 n = self[r][7]
212 n = self[r][7]
213 nodemap[n] = r
213 nodemap[n] = r
214 return nodemap
214 return nodemap
215
215
216 def clearcaches(self):
216 def clearcaches(self):
217 self.__dict__.pop('nodemap', None)
217 self.__dict__.pop('nodemap', None)
218
218
219 def __getitem__(self, i):
219 def __getitem__(self, i):
220 if i == -1:
220 if i == -1:
221 return (0, 0, 0, -1, -1, -1, -1, nullid)
221 return (0, 0, 0, -1, -1, -1, -1, nullid)
222 return list.__getitem__(self, i)
222 return list.__getitem__(self, i)
223
223
224
224
225 class revlogoldio(object):
225 class revlogoldio(object):
226 def __init__(self):
226 def __init__(self):
227 self.size = indexformatv0.size
227 self.size = indexformatv0.size
228
228
229 def parseindex(self, data, inline):
229 def parseindex(self, data, inline):
230 s = self.size
230 s = self.size
231 index = []
231 index = []
232 nodemap = revlogutils.NodeMap({nullid: nullrev})
232 nodemap = revlogutils.NodeMap({nullid: nullrev})
233 n = off = 0
233 n = off = 0
234 l = len(data)
234 l = len(data)
235 while off + s <= l:
235 while off + s <= l:
236 cur = data[off : off + s]
236 cur = data[off : off + s]
237 off += s
237 off += s
238 e = indexformatv0_unpack(cur)
238 e = indexformatv0_unpack(cur)
239 # transform to revlogv1 format
239 # transform to revlogv1 format
240 e2 = (
240 e2 = (
241 offset_type(e[0], 0),
241 offset_type(e[0], 0),
242 e[1],
242 e[1],
243 -1,
243 -1,
244 e[2],
244 e[2],
245 e[3],
245 e[3],
246 nodemap.get(e[4], nullrev),
246 nodemap.get(e[4], nullrev),
247 nodemap.get(e[5], nullrev),
247 nodemap.get(e[5], nullrev),
248 e[6],
248 e[6],
249 )
249 )
250 index.append(e2)
250 index.append(e2)
251 nodemap[e[6]] = n
251 nodemap[e[6]] = n
252 n += 1
252 n += 1
253
253
254 index = revlogoldindex(index)
254 index = revlogoldindex(index)
255 return index, index.nodemap, None
255 return index, None
256
256
257 def packentry(self, entry, node, version, rev):
257 def packentry(self, entry, node, version, rev):
258 if gettype(entry[0]):
258 if gettype(entry[0]):
259 raise error.RevlogError(
259 raise error.RevlogError(
260 _(b'index entry flags need revlog version 1')
260 _(b'index entry flags need revlog version 1')
261 )
261 )
262 e2 = (
262 e2 = (
263 getoffset(entry[0]),
263 getoffset(entry[0]),
264 entry[1],
264 entry[1],
265 entry[3],
265 entry[3],
266 entry[4],
266 entry[4],
267 node(entry[5]),
267 node(entry[5]),
268 node(entry[6]),
268 node(entry[6]),
269 entry[7],
269 entry[7],
270 )
270 )
271 return indexformatv0_pack(*e2)
271 return indexformatv0_pack(*e2)
272
272
273
273
274 # index ng:
274 # index ng:
275 # 6 bytes: offset
275 # 6 bytes: offset
276 # 2 bytes: flags
276 # 2 bytes: flags
277 # 4 bytes: compressed length
277 # 4 bytes: compressed length
278 # 4 bytes: uncompressed length
278 # 4 bytes: uncompressed length
279 # 4 bytes: base rev
279 # 4 bytes: base rev
280 # 4 bytes: link rev
280 # 4 bytes: link rev
281 # 4 bytes: parent 1 rev
281 # 4 bytes: parent 1 rev
282 # 4 bytes: parent 2 rev
282 # 4 bytes: parent 2 rev
283 # 32 bytes: nodeid
283 # 32 bytes: nodeid
284 indexformatng = struct.Struct(b">Qiiiiii20s12x")
284 indexformatng = struct.Struct(b">Qiiiiii20s12x")
285 indexformatng_pack = indexformatng.pack
285 indexformatng_pack = indexformatng.pack
286 versionformat = struct.Struct(b">I")
286 versionformat = struct.Struct(b">I")
287 versionformat_pack = versionformat.pack
287 versionformat_pack = versionformat.pack
288 versionformat_unpack = versionformat.unpack
288 versionformat_unpack = versionformat.unpack
289
289
290 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
290 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
291 # signed integer)
291 # signed integer)
292 _maxentrysize = 0x7FFFFFFF
292 _maxentrysize = 0x7FFFFFFF
293
293
294
294
295 class revlogio(object):
295 class revlogio(object):
296 def __init__(self):
296 def __init__(self):
297 self.size = indexformatng.size
297 self.size = indexformatng.size
298
298
299 def parseindex(self, data, inline):
299 def parseindex(self, data, inline):
300 # call the C implementation to parse the index data
300 # call the C implementation to parse the index data
301 index, cache = parsers.parse_index2(data, inline)
301 index, cache = parsers.parse_index2(data, inline)
302 return index, index.nodemap, cache
302 return index, cache
303
303
304 def packentry(self, entry, node, version, rev):
304 def packentry(self, entry, node, version, rev):
305 p = indexformatng_pack(*entry)
305 p = indexformatng_pack(*entry)
306 if rev == 0:
306 if rev == 0:
307 p = versionformat_pack(version) + p[4:]
307 p = versionformat_pack(version) + p[4:]
308 return p
308 return p
309
309
310
310
311 class revlog(object):
311 class revlog(object):
312 """
312 """
313 the underlying revision storage object
313 the underlying revision storage object
314
314
315 A revlog consists of two parts, an index and the revision data.
315 A revlog consists of two parts, an index and the revision data.
316
316
317 The index is a file with a fixed record size containing
317 The index is a file with a fixed record size containing
318 information on each revision, including its nodeid (hash), the
318 information on each revision, including its nodeid (hash), the
319 nodeids of its parents, the position and offset of its data within
319 nodeids of its parents, the position and offset of its data within
320 the data file, and the revision it's based on. Finally, each entry
320 the data file, and the revision it's based on. Finally, each entry
321 contains a linkrev entry that can serve as a pointer to external
321 contains a linkrev entry that can serve as a pointer to external
322 data.
322 data.
323
323
324 The revision data itself is a linear collection of data chunks.
324 The revision data itself is a linear collection of data chunks.
325 Each chunk represents a revision and is usually represented as a
325 Each chunk represents a revision and is usually represented as a
326 delta against the previous chunk. To bound lookup time, runs of
326 delta against the previous chunk. To bound lookup time, runs of
327 deltas are limited to about 2 times the length of the original
327 deltas are limited to about 2 times the length of the original
328 version data. This makes retrieval of a version proportional to
328 version data. This makes retrieval of a version proportional to
329 its size, or O(1) relative to the number of revisions.
329 its size, or O(1) relative to the number of revisions.
330
330
331 Both pieces of the revlog are written to in an append-only
331 Both pieces of the revlog are written to in an append-only
332 fashion, which means we never need to rewrite a file to insert or
332 fashion, which means we never need to rewrite a file to insert or
333 remove data, and can use some simple techniques to avoid the need
333 remove data, and can use some simple techniques to avoid the need
334 for locking while reading.
334 for locking while reading.
335
335
336 If checkambig, indexfile is opened with checkambig=True at
336 If checkambig, indexfile is opened with checkambig=True at
337 writing, to avoid file stat ambiguity.
337 writing, to avoid file stat ambiguity.
338
338
339 If mmaplargeindex is True, and an mmapindexthreshold is set, the
339 If mmaplargeindex is True, and an mmapindexthreshold is set, the
340 index will be mmapped rather than read if it is larger than the
340 index will be mmapped rather than read if it is larger than the
341 configured threshold.
341 configured threshold.
342
342
343 If censorable is True, the revlog can have censored revisions.
343 If censorable is True, the revlog can have censored revisions.
344
344
345 If `upperboundcomp` is not None, this is the expected maximal gain from
345 If `upperboundcomp` is not None, this is the expected maximal gain from
346 compression for the data content.
346 compression for the data content.
347 """
347 """
348
348
349 _flagserrorclass = error.RevlogError
349 _flagserrorclass = error.RevlogError
350
350
351 def __init__(
351 def __init__(
352 self,
352 self,
353 opener,
353 opener,
354 indexfile,
354 indexfile,
355 datafile=None,
355 datafile=None,
356 checkambig=False,
356 checkambig=False,
357 mmaplargeindex=False,
357 mmaplargeindex=False,
358 censorable=False,
358 censorable=False,
359 upperboundcomp=None,
359 upperboundcomp=None,
360 ):
360 ):
361 """
361 """
362 create a revlog object
362 create a revlog object
363
363
364 opener is a function that abstracts the file opening operation
364 opener is a function that abstracts the file opening operation
365 and can be used to implement COW semantics or the like.
365 and can be used to implement COW semantics or the like.
366
366
367 """
367 """
368 self.upperboundcomp = upperboundcomp
368 self.upperboundcomp = upperboundcomp
369 self.indexfile = indexfile
369 self.indexfile = indexfile
370 self.datafile = datafile or (indexfile[:-2] + b".d")
370 self.datafile = datafile or (indexfile[:-2] + b".d")
371 self.opener = opener
371 self.opener = opener
372 # When True, indexfile is opened with checkambig=True at writing, to
372 # When True, indexfile is opened with checkambig=True at writing, to
373 # avoid file stat ambiguity.
373 # avoid file stat ambiguity.
374 self._checkambig = checkambig
374 self._checkambig = checkambig
375 self._mmaplargeindex = mmaplargeindex
375 self._mmaplargeindex = mmaplargeindex
376 self._censorable = censorable
376 self._censorable = censorable
377 # 3-tuple of (node, rev, text) for a raw revision.
377 # 3-tuple of (node, rev, text) for a raw revision.
378 self._revisioncache = None
378 self._revisioncache = None
379 # Maps rev to chain base rev.
379 # Maps rev to chain base rev.
380 self._chainbasecache = util.lrucachedict(100)
380 self._chainbasecache = util.lrucachedict(100)
381 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
381 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
382 self._chunkcache = (0, b'')
382 self._chunkcache = (0, b'')
383 # How much data to read and cache into the raw revlog data cache.
383 # How much data to read and cache into the raw revlog data cache.
384 self._chunkcachesize = 65536
384 self._chunkcachesize = 65536
385 self._maxchainlen = None
385 self._maxchainlen = None
386 self._deltabothparents = True
386 self._deltabothparents = True
387 self.index = None
387 self.index = None
388 # Mapping of partial identifiers to full nodes.
388 # Mapping of partial identifiers to full nodes.
389 self._pcache = {}
389 self._pcache = {}
390 # Mapping of revision integer to full node.
390 # Mapping of revision integer to full node.
391 self._nodecache = None
391 self._nodecache = None
392 self._nodepos = None
392 self._nodepos = None
393 self._compengine = b'zlib'
393 self._compengine = b'zlib'
394 self._compengineopts = {}
394 self._compengineopts = {}
395 self._maxdeltachainspan = -1
395 self._maxdeltachainspan = -1
396 self._withsparseread = False
396 self._withsparseread = False
397 self._sparserevlog = False
397 self._sparserevlog = False
398 self._srdensitythreshold = 0.50
398 self._srdensitythreshold = 0.50
399 self._srmingapsize = 262144
399 self._srmingapsize = 262144
400
400
401 # Make copy of flag processors so each revlog instance can support
401 # Make copy of flag processors so each revlog instance can support
402 # custom flags.
402 # custom flags.
403 self._flagprocessors = dict(flagutil.flagprocessors)
403 self._flagprocessors = dict(flagutil.flagprocessors)
404
404
405 # 2-tuple of file handles being used for active writing.
405 # 2-tuple of file handles being used for active writing.
406 self._writinghandles = None
406 self._writinghandles = None
407
407
408 self._loadindex()
408 self._loadindex()
409
409
410 def _loadindex(self):
410 def _loadindex(self):
411 mmapindexthreshold = None
411 mmapindexthreshold = None
412 opts = self.opener.options
412 opts = self.opener.options
413
413
414 if b'revlogv2' in opts:
414 if b'revlogv2' in opts:
415 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
415 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
416 elif b'revlogv1' in opts:
416 elif b'revlogv1' in opts:
417 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
417 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
418 if b'generaldelta' in opts:
418 if b'generaldelta' in opts:
419 newversionflags |= FLAG_GENERALDELTA
419 newversionflags |= FLAG_GENERALDELTA
420 elif b'revlogv0' in self.opener.options:
420 elif b'revlogv0' in self.opener.options:
421 newversionflags = REVLOGV0
421 newversionflags = REVLOGV0
422 else:
422 else:
423 newversionflags = REVLOG_DEFAULT_VERSION
423 newversionflags = REVLOG_DEFAULT_VERSION
424
424
425 if b'chunkcachesize' in opts:
425 if b'chunkcachesize' in opts:
426 self._chunkcachesize = opts[b'chunkcachesize']
426 self._chunkcachesize = opts[b'chunkcachesize']
427 if b'maxchainlen' in opts:
427 if b'maxchainlen' in opts:
428 self._maxchainlen = opts[b'maxchainlen']
428 self._maxchainlen = opts[b'maxchainlen']
429 if b'deltabothparents' in opts:
429 if b'deltabothparents' in opts:
430 self._deltabothparents = opts[b'deltabothparents']
430 self._deltabothparents = opts[b'deltabothparents']
431 self._lazydelta = bool(opts.get(b'lazydelta', True))
431 self._lazydelta = bool(opts.get(b'lazydelta', True))
432 self._lazydeltabase = False
432 self._lazydeltabase = False
433 if self._lazydelta:
433 if self._lazydelta:
434 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
434 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
435 if b'compengine' in opts:
435 if b'compengine' in opts:
436 self._compengine = opts[b'compengine']
436 self._compengine = opts[b'compengine']
437 if b'zlib.level' in opts:
437 if b'zlib.level' in opts:
438 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
438 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
439 if b'zstd.level' in opts:
439 if b'zstd.level' in opts:
440 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
440 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
441 if b'maxdeltachainspan' in opts:
441 if b'maxdeltachainspan' in opts:
442 self._maxdeltachainspan = opts[b'maxdeltachainspan']
442 self._maxdeltachainspan = opts[b'maxdeltachainspan']
443 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
443 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
444 mmapindexthreshold = opts[b'mmapindexthreshold']
444 mmapindexthreshold = opts[b'mmapindexthreshold']
445 self.hassidedata = bool(opts.get(b'side-data', False))
445 self.hassidedata = bool(opts.get(b'side-data', False))
446 if self.hassidedata:
446 if self.hassidedata:
447 self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors
447 self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors
448 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
448 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
449 withsparseread = bool(opts.get(b'with-sparse-read', False))
449 withsparseread = bool(opts.get(b'with-sparse-read', False))
450 # sparse-revlog forces sparse-read
450 # sparse-revlog forces sparse-read
451 self._withsparseread = self._sparserevlog or withsparseread
451 self._withsparseread = self._sparserevlog or withsparseread
452 if b'sparse-read-density-threshold' in opts:
452 if b'sparse-read-density-threshold' in opts:
453 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
453 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
454 if b'sparse-read-min-gap-size' in opts:
454 if b'sparse-read-min-gap-size' in opts:
455 self._srmingapsize = opts[b'sparse-read-min-gap-size']
455 self._srmingapsize = opts[b'sparse-read-min-gap-size']
456 if opts.get(b'enableellipsis'):
456 if opts.get(b'enableellipsis'):
457 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
457 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
458
458
459 # revlog v0 doesn't have flag processors
459 # revlog v0 doesn't have flag processors
460 for flag, processor in pycompat.iteritems(
460 for flag, processor in pycompat.iteritems(
461 opts.get(b'flagprocessors', {})
461 opts.get(b'flagprocessors', {})
462 ):
462 ):
463 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
463 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
464
464
465 if self._chunkcachesize <= 0:
465 if self._chunkcachesize <= 0:
466 raise error.RevlogError(
466 raise error.RevlogError(
467 _(b'revlog chunk cache size %r is not greater than 0')
467 _(b'revlog chunk cache size %r is not greater than 0')
468 % self._chunkcachesize
468 % self._chunkcachesize
469 )
469 )
470 elif self._chunkcachesize & (self._chunkcachesize - 1):
470 elif self._chunkcachesize & (self._chunkcachesize - 1):
471 raise error.RevlogError(
471 raise error.RevlogError(
472 _(b'revlog chunk cache size %r is not a power of 2')
472 _(b'revlog chunk cache size %r is not a power of 2')
473 % self._chunkcachesize
473 % self._chunkcachesize
474 )
474 )
475
475
476 indexdata = b''
476 indexdata = b''
477 self._initempty = True
477 self._initempty = True
478 try:
478 try:
479 with self._indexfp() as f:
479 with self._indexfp() as f:
480 if (
480 if (
481 mmapindexthreshold is not None
481 mmapindexthreshold is not None
482 and self.opener.fstat(f).st_size >= mmapindexthreshold
482 and self.opener.fstat(f).st_size >= mmapindexthreshold
483 ):
483 ):
484 # TODO: should .close() to release resources without
484 # TODO: should .close() to release resources without
485 # relying on Python GC
485 # relying on Python GC
486 indexdata = util.buffer(util.mmapread(f))
486 indexdata = util.buffer(util.mmapread(f))
487 else:
487 else:
488 indexdata = f.read()
488 indexdata = f.read()
489 if len(indexdata) > 0:
489 if len(indexdata) > 0:
490 versionflags = versionformat_unpack(indexdata[:4])[0]
490 versionflags = versionformat_unpack(indexdata[:4])[0]
491 self._initempty = False
491 self._initempty = False
492 else:
492 else:
493 versionflags = newversionflags
493 versionflags = newversionflags
494 except IOError as inst:
494 except IOError as inst:
495 if inst.errno != errno.ENOENT:
495 if inst.errno != errno.ENOENT:
496 raise
496 raise
497
497
498 versionflags = newversionflags
498 versionflags = newversionflags
499
499
500 self.version = versionflags
500 self.version = versionflags
501
501
502 flags = versionflags & ~0xFFFF
502 flags = versionflags & ~0xFFFF
503 fmt = versionflags & 0xFFFF
503 fmt = versionflags & 0xFFFF
504
504
505 if fmt == REVLOGV0:
505 if fmt == REVLOGV0:
506 if flags:
506 if flags:
507 raise error.RevlogError(
507 raise error.RevlogError(
508 _(b'unknown flags (%#04x) in version %d revlog %s')
508 _(b'unknown flags (%#04x) in version %d revlog %s')
509 % (flags >> 16, fmt, self.indexfile)
509 % (flags >> 16, fmt, self.indexfile)
510 )
510 )
511
511
512 self._inline = False
512 self._inline = False
513 self._generaldelta = False
513 self._generaldelta = False
514
514
515 elif fmt == REVLOGV1:
515 elif fmt == REVLOGV1:
516 if flags & ~REVLOGV1_FLAGS:
516 if flags & ~REVLOGV1_FLAGS:
517 raise error.RevlogError(
517 raise error.RevlogError(
518 _(b'unknown flags (%#04x) in version %d revlog %s')
518 _(b'unknown flags (%#04x) in version %d revlog %s')
519 % (flags >> 16, fmt, self.indexfile)
519 % (flags >> 16, fmt, self.indexfile)
520 )
520 )
521
521
522 self._inline = versionflags & FLAG_INLINE_DATA
522 self._inline = versionflags & FLAG_INLINE_DATA
523 self._generaldelta = versionflags & FLAG_GENERALDELTA
523 self._generaldelta = versionflags & FLAG_GENERALDELTA
524
524
525 elif fmt == REVLOGV2:
525 elif fmt == REVLOGV2:
526 if flags & ~REVLOGV2_FLAGS:
526 if flags & ~REVLOGV2_FLAGS:
527 raise error.RevlogError(
527 raise error.RevlogError(
528 _(b'unknown flags (%#04x) in version %d revlog %s')
528 _(b'unknown flags (%#04x) in version %d revlog %s')
529 % (flags >> 16, fmt, self.indexfile)
529 % (flags >> 16, fmt, self.indexfile)
530 )
530 )
531
531
532 self._inline = versionflags & FLAG_INLINE_DATA
532 self._inline = versionflags & FLAG_INLINE_DATA
533 # generaldelta implied by version 2 revlogs.
533 # generaldelta implied by version 2 revlogs.
534 self._generaldelta = True
534 self._generaldelta = True
535
535
536 else:
536 else:
537 raise error.RevlogError(
537 raise error.RevlogError(
538 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
538 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
539 )
539 )
540 # sparse-revlog can't be on without general-delta (issue6056)
540 # sparse-revlog can't be on without general-delta (issue6056)
541 if not self._generaldelta:
541 if not self._generaldelta:
542 self._sparserevlog = False
542 self._sparserevlog = False
543
543
544 self._storedeltachains = True
544 self._storedeltachains = True
545
545
546 self._io = revlogio()
546 self._io = revlogio()
547 if self.version == REVLOGV0:
547 if self.version == REVLOGV0:
548 self._io = revlogoldio()
548 self._io = revlogoldio()
549 try:
549 try:
550 d = self._io.parseindex(indexdata, self._inline)
550 d = self._io.parseindex(indexdata, self._inline)
551 except (ValueError, IndexError):
551 except (ValueError, IndexError):
552 raise error.RevlogError(
552 raise error.RevlogError(
553 _(b"index %s is corrupted") % self.indexfile
553 _(b"index %s is corrupted") % self.indexfile
554 )
554 )
555 self.index, nodemap, self._chunkcache = d
555 self.index, self._chunkcache = d
556 self.nodemap = self._nodecache = nodemap
556 self.nodemap = self._nodecache = self.index.nodemap
557 if not self._chunkcache:
557 if not self._chunkcache:
558 self._chunkclear()
558 self._chunkclear()
559 # revnum -> (chain-length, sum-delta-length)
559 # revnum -> (chain-length, sum-delta-length)
560 self._chaininfocache = {}
560 self._chaininfocache = {}
561 # revlog header -> revlog compressor
561 # revlog header -> revlog compressor
562 self._decompressors = {}
562 self._decompressors = {}
563
563
564 @util.propertycache
564 @util.propertycache
565 def _compressor(self):
565 def _compressor(self):
566 engine = util.compengines[self._compengine]
566 engine = util.compengines[self._compengine]
567 return engine.revlogcompressor(self._compengineopts)
567 return engine.revlogcompressor(self._compengineopts)
568
568
569 def _indexfp(self, mode=b'r'):
569 def _indexfp(self, mode=b'r'):
570 """file object for the revlog's index file"""
570 """file object for the revlog's index file"""
571 args = {'mode': mode}
571 args = {'mode': mode}
572 if mode != b'r':
572 if mode != b'r':
573 args['checkambig'] = self._checkambig
573 args['checkambig'] = self._checkambig
574 if mode == b'w':
574 if mode == b'w':
575 args['atomictemp'] = True
575 args['atomictemp'] = True
576 return self.opener(self.indexfile, **args)
576 return self.opener(self.indexfile, **args)
577
577
578 def _datafp(self, mode=b'r'):
578 def _datafp(self, mode=b'r'):
579 """file object for the revlog's data file"""
579 """file object for the revlog's data file"""
580 return self.opener(self.datafile, mode=mode)
580 return self.opener(self.datafile, mode=mode)
581
581
582 @contextlib.contextmanager
582 @contextlib.contextmanager
583 def _datareadfp(self, existingfp=None):
583 def _datareadfp(self, existingfp=None):
584 """file object suitable to read data"""
584 """file object suitable to read data"""
585 # Use explicit file handle, if given.
585 # Use explicit file handle, if given.
586 if existingfp is not None:
586 if existingfp is not None:
587 yield existingfp
587 yield existingfp
588
588
589 # Use a file handle being actively used for writes, if available.
589 # Use a file handle being actively used for writes, if available.
590 # There is some danger to doing this because reads will seek the
590 # There is some danger to doing this because reads will seek the
591 # file. However, _writeentry() performs a SEEK_END before all writes,
591 # file. However, _writeentry() performs a SEEK_END before all writes,
592 # so we should be safe.
592 # so we should be safe.
593 elif self._writinghandles:
593 elif self._writinghandles:
594 if self._inline:
594 if self._inline:
595 yield self._writinghandles[0]
595 yield self._writinghandles[0]
596 else:
596 else:
597 yield self._writinghandles[1]
597 yield self._writinghandles[1]
598
598
599 # Otherwise open a new file handle.
599 # Otherwise open a new file handle.
600 else:
600 else:
601 if self._inline:
601 if self._inline:
602 func = self._indexfp
602 func = self._indexfp
603 else:
603 else:
604 func = self._datafp
604 func = self._datafp
605 with func() as fp:
605 with func() as fp:
606 yield fp
606 yield fp
607
607
608 def tiprev(self):
608 def tiprev(self):
609 return len(self.index) - 1
609 return len(self.index) - 1
610
610
611 def tip(self):
611 def tip(self):
612 return self.node(self.tiprev())
612 return self.node(self.tiprev())
613
613
614 def __contains__(self, rev):
614 def __contains__(self, rev):
615 return 0 <= rev < len(self)
615 return 0 <= rev < len(self)
616
616
617 def __len__(self):
617 def __len__(self):
618 return len(self.index)
618 return len(self.index)
619
619
620 def __iter__(self):
620 def __iter__(self):
621 return iter(pycompat.xrange(len(self)))
621 return iter(pycompat.xrange(len(self)))
622
622
623 def revs(self, start=0, stop=None):
623 def revs(self, start=0, stop=None):
624 """iterate over all rev in this revlog (from start to stop)"""
624 """iterate over all rev in this revlog (from start to stop)"""
625 return storageutil.iterrevs(len(self), start=start, stop=stop)
625 return storageutil.iterrevs(len(self), start=start, stop=stop)
626
626
627 @util.propertycache
627 @util.propertycache
628 def nodemap(self):
628 def nodemap(self):
629 if self.index:
629 if self.index:
630 # populate mapping down to the initial node
630 # populate mapping down to the initial node
631 node0 = self.index[0][7] # get around changelog filtering
631 node0 = self.index[0][7] # get around changelog filtering
632 self.rev(node0)
632 self.rev(node0)
633 return self._nodecache
633 return self._nodecache
634
634
635 def hasnode(self, node):
635 def hasnode(self, node):
636 try:
636 try:
637 self.rev(node)
637 self.rev(node)
638 return True
638 return True
639 except KeyError:
639 except KeyError:
640 return False
640 return False
641
641
642 def candelta(self, baserev, rev):
642 def candelta(self, baserev, rev):
643 """whether two revisions (baserev, rev) can be delta-ed or not"""
643 """whether two revisions (baserev, rev) can be delta-ed or not"""
644 # Disable delta if either rev requires a content-changing flag
644 # Disable delta if either rev requires a content-changing flag
645 # processor (ex. LFS). This is because such flag processor can alter
645 # processor (ex. LFS). This is because such flag processor can alter
646 # the rawtext content that the delta will be based on, and two clients
646 # the rawtext content that the delta will be based on, and two clients
647 # could have a same revlog node with different flags (i.e. different
647 # could have a same revlog node with different flags (i.e. different
648 # rawtext contents) and the delta could be incompatible.
648 # rawtext contents) and the delta could be incompatible.
649 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
649 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
650 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
650 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
651 ):
651 ):
652 return False
652 return False
653 return True
653 return True
654
654
655 def clearcaches(self):
655 def clearcaches(self):
656 self._revisioncache = None
656 self._revisioncache = None
657 self._chainbasecache.clear()
657 self._chainbasecache.clear()
658 self._chunkcache = (0, b'')
658 self._chunkcache = (0, b'')
659 self._pcache = {}
659 self._pcache = {}
660 self.index.clearcaches()
660 self.index.clearcaches()
661
661
662 def rev(self, node):
662 def rev(self, node):
663 try:
663 try:
664 return self._nodecache[node]
664 return self._nodecache[node]
665 except TypeError:
665 except TypeError:
666 raise
666 raise
667 except error.RevlogError:
667 except error.RevlogError:
668 # parsers.c radix tree lookup failed
668 # parsers.c radix tree lookup failed
669 if node == wdirid or node in wdirfilenodeids:
669 if node == wdirid or node in wdirfilenodeids:
670 raise error.WdirUnsupported
670 raise error.WdirUnsupported
671 raise error.LookupError(node, self.indexfile, _(b'no node'))
671 raise error.LookupError(node, self.indexfile, _(b'no node'))
672
672
673 # Accessors for index entries.
673 # Accessors for index entries.
674
674
675 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
675 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
676 # are flags.
676 # are flags.
677 def start(self, rev):
677 def start(self, rev):
678 return int(self.index[rev][0] >> 16)
678 return int(self.index[rev][0] >> 16)
679
679
680 def flags(self, rev):
680 def flags(self, rev):
681 return self.index[rev][0] & 0xFFFF
681 return self.index[rev][0] & 0xFFFF
682
682
683 def length(self, rev):
683 def length(self, rev):
684 return self.index[rev][1]
684 return self.index[rev][1]
685
685
686 def rawsize(self, rev):
686 def rawsize(self, rev):
687 """return the length of the uncompressed text for a given revision"""
687 """return the length of the uncompressed text for a given revision"""
688 l = self.index[rev][2]
688 l = self.index[rev][2]
689 if l >= 0:
689 if l >= 0:
690 return l
690 return l
691
691
692 t = self.rawdata(rev)
692 t = self.rawdata(rev)
693 return len(t)
693 return len(t)
694
694
695 def size(self, rev):
695 def size(self, rev):
696 """length of non-raw text (processed by a "read" flag processor)"""
696 """length of non-raw text (processed by a "read" flag processor)"""
697 # fast path: if no "read" flag processor could change the content,
697 # fast path: if no "read" flag processor could change the content,
698 # size is rawsize. note: ELLIPSIS is known to not change the content.
698 # size is rawsize. note: ELLIPSIS is known to not change the content.
699 flags = self.flags(rev)
699 flags = self.flags(rev)
700 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
700 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
701 return self.rawsize(rev)
701 return self.rawsize(rev)
702
702
703 return len(self.revision(rev, raw=False))
703 return len(self.revision(rev, raw=False))
704
704
705 def chainbase(self, rev):
705 def chainbase(self, rev):
706 base = self._chainbasecache.get(rev)
706 base = self._chainbasecache.get(rev)
707 if base is not None:
707 if base is not None:
708 return base
708 return base
709
709
710 index = self.index
710 index = self.index
711 iterrev = rev
711 iterrev = rev
712 base = index[iterrev][3]
712 base = index[iterrev][3]
713 while base != iterrev:
713 while base != iterrev:
714 iterrev = base
714 iterrev = base
715 base = index[iterrev][3]
715 base = index[iterrev][3]
716
716
717 self._chainbasecache[rev] = base
717 self._chainbasecache[rev] = base
718 return base
718 return base
719
719
720 def linkrev(self, rev):
720 def linkrev(self, rev):
721 return self.index[rev][4]
721 return self.index[rev][4]
722
722
723 def parentrevs(self, rev):
723 def parentrevs(self, rev):
724 try:
724 try:
725 entry = self.index[rev]
725 entry = self.index[rev]
726 except IndexError:
726 except IndexError:
727 if rev == wdirrev:
727 if rev == wdirrev:
728 raise error.WdirUnsupported
728 raise error.WdirUnsupported
729 raise
729 raise
730
730
731 return entry[5], entry[6]
731 return entry[5], entry[6]
732
732
733 # fast parentrevs(rev) where rev isn't filtered
733 # fast parentrevs(rev) where rev isn't filtered
734 _uncheckedparentrevs = parentrevs
734 _uncheckedparentrevs = parentrevs
735
735
736 def node(self, rev):
736 def node(self, rev):
737 try:
737 try:
738 return self.index[rev][7]
738 return self.index[rev][7]
739 except IndexError:
739 except IndexError:
740 if rev == wdirrev:
740 if rev == wdirrev:
741 raise error.WdirUnsupported
741 raise error.WdirUnsupported
742 raise
742 raise
743
743
744 # Derived from index values.
744 # Derived from index values.
745
745
746 def end(self, rev):
746 def end(self, rev):
747 return self.start(rev) + self.length(rev)
747 return self.start(rev) + self.length(rev)
748
748
749 def parents(self, node):
749 def parents(self, node):
750 i = self.index
750 i = self.index
751 d = i[self.rev(node)]
751 d = i[self.rev(node)]
752 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
752 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
753
753
754 def chainlen(self, rev):
754 def chainlen(self, rev):
755 return self._chaininfo(rev)[0]
755 return self._chaininfo(rev)[0]
756
756
757 def _chaininfo(self, rev):
757 def _chaininfo(self, rev):
758 chaininfocache = self._chaininfocache
758 chaininfocache = self._chaininfocache
759 if rev in chaininfocache:
759 if rev in chaininfocache:
760 return chaininfocache[rev]
760 return chaininfocache[rev]
761 index = self.index
761 index = self.index
762 generaldelta = self._generaldelta
762 generaldelta = self._generaldelta
763 iterrev = rev
763 iterrev = rev
764 e = index[iterrev]
764 e = index[iterrev]
765 clen = 0
765 clen = 0
766 compresseddeltalen = 0
766 compresseddeltalen = 0
767 while iterrev != e[3]:
767 while iterrev != e[3]:
768 clen += 1
768 clen += 1
769 compresseddeltalen += e[1]
769 compresseddeltalen += e[1]
770 if generaldelta:
770 if generaldelta:
771 iterrev = e[3]
771 iterrev = e[3]
772 else:
772 else:
773 iterrev -= 1
773 iterrev -= 1
774 if iterrev in chaininfocache:
774 if iterrev in chaininfocache:
775 t = chaininfocache[iterrev]
775 t = chaininfocache[iterrev]
776 clen += t[0]
776 clen += t[0]
777 compresseddeltalen += t[1]
777 compresseddeltalen += t[1]
778 break
778 break
779 e = index[iterrev]
779 e = index[iterrev]
780 else:
780 else:
781 # Add text length of base since decompressing that also takes
781 # Add text length of base since decompressing that also takes
782 # work. For cache hits the length is already included.
782 # work. For cache hits the length is already included.
783 compresseddeltalen += e[1]
783 compresseddeltalen += e[1]
784 r = (clen, compresseddeltalen)
784 r = (clen, compresseddeltalen)
785 chaininfocache[rev] = r
785 chaininfocache[rev] = r
786 return r
786 return r
787
787
788 def _deltachain(self, rev, stoprev=None):
788 def _deltachain(self, rev, stoprev=None):
789 """Obtain the delta chain for a revision.
789 """Obtain the delta chain for a revision.
790
790
791 ``stoprev`` specifies a revision to stop at. If not specified, we
791 ``stoprev`` specifies a revision to stop at. If not specified, we
792 stop at the base of the chain.
792 stop at the base of the chain.
793
793
794 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
794 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
795 revs in ascending order and ``stopped`` is a bool indicating whether
795 revs in ascending order and ``stopped`` is a bool indicating whether
796 ``stoprev`` was hit.
796 ``stoprev`` was hit.
797 """
797 """
798 # Try C implementation.
798 # Try C implementation.
799 try:
799 try:
800 return self.index.deltachain(rev, stoprev, self._generaldelta)
800 return self.index.deltachain(rev, stoprev, self._generaldelta)
801 except AttributeError:
801 except AttributeError:
802 pass
802 pass
803
803
804 chain = []
804 chain = []
805
805
806 # Alias to prevent attribute lookup in tight loop.
806 # Alias to prevent attribute lookup in tight loop.
807 index = self.index
807 index = self.index
808 generaldelta = self._generaldelta
808 generaldelta = self._generaldelta
809
809
810 iterrev = rev
810 iterrev = rev
811 e = index[iterrev]
811 e = index[iterrev]
812 while iterrev != e[3] and iterrev != stoprev:
812 while iterrev != e[3] and iterrev != stoprev:
813 chain.append(iterrev)
813 chain.append(iterrev)
814 if generaldelta:
814 if generaldelta:
815 iterrev = e[3]
815 iterrev = e[3]
816 else:
816 else:
817 iterrev -= 1
817 iterrev -= 1
818 e = index[iterrev]
818 e = index[iterrev]
819
819
820 if iterrev == stoprev:
820 if iterrev == stoprev:
821 stopped = True
821 stopped = True
822 else:
822 else:
823 chain.append(iterrev)
823 chain.append(iterrev)
824 stopped = False
824 stopped = False
825
825
826 chain.reverse()
826 chain.reverse()
827 return chain, stopped
827 return chain, stopped
828
828
829 def ancestors(self, revs, stoprev=0, inclusive=False):
829 def ancestors(self, revs, stoprev=0, inclusive=False):
830 """Generate the ancestors of 'revs' in reverse revision order.
830 """Generate the ancestors of 'revs' in reverse revision order.
831 Does not generate revs lower than stoprev.
831 Does not generate revs lower than stoprev.
832
832
833 See the documentation for ancestor.lazyancestors for more details."""
833 See the documentation for ancestor.lazyancestors for more details."""
834
834
835 # first, make sure start revisions aren't filtered
835 # first, make sure start revisions aren't filtered
836 revs = list(revs)
836 revs = list(revs)
837 checkrev = self.node
837 checkrev = self.node
838 for r in revs:
838 for r in revs:
839 checkrev(r)
839 checkrev(r)
840 # and we're sure ancestors aren't filtered as well
840 # and we're sure ancestors aren't filtered as well
841
841
842 if rustancestor is not None:
842 if rustancestor is not None:
843 lazyancestors = rustancestor.LazyAncestors
843 lazyancestors = rustancestor.LazyAncestors
844 arg = self.index
844 arg = self.index
845 elif util.safehasattr(parsers, b'rustlazyancestors'):
845 elif util.safehasattr(parsers, b'rustlazyancestors'):
846 lazyancestors = ancestor.rustlazyancestors
846 lazyancestors = ancestor.rustlazyancestors
847 arg = self.index
847 arg = self.index
848 else:
848 else:
849 lazyancestors = ancestor.lazyancestors
849 lazyancestors = ancestor.lazyancestors
850 arg = self._uncheckedparentrevs
850 arg = self._uncheckedparentrevs
851 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
851 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
852
852
853 def descendants(self, revs):
853 def descendants(self, revs):
854 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
854 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
855
855
856 def findcommonmissing(self, common=None, heads=None):
856 def findcommonmissing(self, common=None, heads=None):
857 """Return a tuple of the ancestors of common and the ancestors of heads
857 """Return a tuple of the ancestors of common and the ancestors of heads
858 that are not ancestors of common. In revset terminology, we return the
858 that are not ancestors of common. In revset terminology, we return the
859 tuple:
859 tuple:
860
860
861 ::common, (::heads) - (::common)
861 ::common, (::heads) - (::common)
862
862
863 The list is sorted by revision number, meaning it is
863 The list is sorted by revision number, meaning it is
864 topologically sorted.
864 topologically sorted.
865
865
866 'heads' and 'common' are both lists of node IDs. If heads is
866 'heads' and 'common' are both lists of node IDs. If heads is
867 not supplied, uses all of the revlog's heads. If common is not
867 not supplied, uses all of the revlog's heads. If common is not
868 supplied, uses nullid."""
868 supplied, uses nullid."""
869 if common is None:
869 if common is None:
870 common = [nullid]
870 common = [nullid]
871 if heads is None:
871 if heads is None:
872 heads = self.heads()
872 heads = self.heads()
873
873
874 common = [self.rev(n) for n in common]
874 common = [self.rev(n) for n in common]
875 heads = [self.rev(n) for n in heads]
875 heads = [self.rev(n) for n in heads]
876
876
877 # we want the ancestors, but inclusive
877 # we want the ancestors, but inclusive
878 class lazyset(object):
878 class lazyset(object):
879 def __init__(self, lazyvalues):
879 def __init__(self, lazyvalues):
880 self.addedvalues = set()
880 self.addedvalues = set()
881 self.lazyvalues = lazyvalues
881 self.lazyvalues = lazyvalues
882
882
883 def __contains__(self, value):
883 def __contains__(self, value):
884 return value in self.addedvalues or value in self.lazyvalues
884 return value in self.addedvalues or value in self.lazyvalues
885
885
886 def __iter__(self):
886 def __iter__(self):
887 added = self.addedvalues
887 added = self.addedvalues
888 for r in added:
888 for r in added:
889 yield r
889 yield r
890 for r in self.lazyvalues:
890 for r in self.lazyvalues:
891 if not r in added:
891 if not r in added:
892 yield r
892 yield r
893
893
894 def add(self, value):
894 def add(self, value):
895 self.addedvalues.add(value)
895 self.addedvalues.add(value)
896
896
897 def update(self, values):
897 def update(self, values):
898 self.addedvalues.update(values)
898 self.addedvalues.update(values)
899
899
900 has = lazyset(self.ancestors(common))
900 has = lazyset(self.ancestors(common))
901 has.add(nullrev)
901 has.add(nullrev)
902 has.update(common)
902 has.update(common)
903
903
904 # take all ancestors from heads that aren't in has
904 # take all ancestors from heads that aren't in has
905 missing = set()
905 missing = set()
906 visit = collections.deque(r for r in heads if r not in has)
906 visit = collections.deque(r for r in heads if r not in has)
907 while visit:
907 while visit:
908 r = visit.popleft()
908 r = visit.popleft()
909 if r in missing:
909 if r in missing:
910 continue
910 continue
911 else:
911 else:
912 missing.add(r)
912 missing.add(r)
913 for p in self.parentrevs(r):
913 for p in self.parentrevs(r):
914 if p not in has:
914 if p not in has:
915 visit.append(p)
915 visit.append(p)
916 missing = list(missing)
916 missing = list(missing)
917 missing.sort()
917 missing.sort()
918 return has, [self.node(miss) for miss in missing]
918 return has, [self.node(miss) for miss in missing]
919
919
920 def incrementalmissingrevs(self, common=None):
920 def incrementalmissingrevs(self, common=None):
921 """Return an object that can be used to incrementally compute the
921 """Return an object that can be used to incrementally compute the
922 revision numbers of the ancestors of arbitrary sets that are not
922 revision numbers of the ancestors of arbitrary sets that are not
923 ancestors of common. This is an ancestor.incrementalmissingancestors
923 ancestors of common. This is an ancestor.incrementalmissingancestors
924 object.
924 object.
925
925
926 'common' is a list of revision numbers. If common is not supplied, uses
926 'common' is a list of revision numbers. If common is not supplied, uses
927 nullrev.
927 nullrev.
928 """
928 """
929 if common is None:
929 if common is None:
930 common = [nullrev]
930 common = [nullrev]
931
931
932 if rustancestor is not None:
932 if rustancestor is not None:
933 return rustancestor.MissingAncestors(self.index, common)
933 return rustancestor.MissingAncestors(self.index, common)
934 return ancestor.incrementalmissingancestors(self.parentrevs, common)
934 return ancestor.incrementalmissingancestors(self.parentrevs, common)
935
935
936 def findmissingrevs(self, common=None, heads=None):
936 def findmissingrevs(self, common=None, heads=None):
937 """Return the revision numbers of the ancestors of heads that
937 """Return the revision numbers of the ancestors of heads that
938 are not ancestors of common.
938 are not ancestors of common.
939
939
940 More specifically, return a list of revision numbers corresponding to
940 More specifically, return a list of revision numbers corresponding to
941 nodes N such that every N satisfies the following constraints:
941 nodes N such that every N satisfies the following constraints:
942
942
943 1. N is an ancestor of some node in 'heads'
943 1. N is an ancestor of some node in 'heads'
944 2. N is not an ancestor of any node in 'common'
944 2. N is not an ancestor of any node in 'common'
945
945
946 The list is sorted by revision number, meaning it is
946 The list is sorted by revision number, meaning it is
947 topologically sorted.
947 topologically sorted.
948
948
949 'heads' and 'common' are both lists of revision numbers. If heads is
949 'heads' and 'common' are both lists of revision numbers. If heads is
950 not supplied, uses all of the revlog's heads. If common is not
950 not supplied, uses all of the revlog's heads. If common is not
951 supplied, uses nullid."""
951 supplied, uses nullid."""
952 if common is None:
952 if common is None:
953 common = [nullrev]
953 common = [nullrev]
954 if heads is None:
954 if heads is None:
955 heads = self.headrevs()
955 heads = self.headrevs()
956
956
957 inc = self.incrementalmissingrevs(common=common)
957 inc = self.incrementalmissingrevs(common=common)
958 return inc.missingancestors(heads)
958 return inc.missingancestors(heads)
959
959
960 def findmissing(self, common=None, heads=None):
960 def findmissing(self, common=None, heads=None):
961 """Return the ancestors of heads that are not ancestors of common.
961 """Return the ancestors of heads that are not ancestors of common.
962
962
963 More specifically, return a list of nodes N such that every N
963 More specifically, return a list of nodes N such that every N
964 satisfies the following constraints:
964 satisfies the following constraints:
965
965
966 1. N is an ancestor of some node in 'heads'
966 1. N is an ancestor of some node in 'heads'
967 2. N is not an ancestor of any node in 'common'
967 2. N is not an ancestor of any node in 'common'
968
968
969 The list is sorted by revision number, meaning it is
969 The list is sorted by revision number, meaning it is
970 topologically sorted.
970 topologically sorted.
971
971
972 'heads' and 'common' are both lists of node IDs. If heads is
972 'heads' and 'common' are both lists of node IDs. If heads is
973 not supplied, uses all of the revlog's heads. If common is not
973 not supplied, uses all of the revlog's heads. If common is not
974 supplied, uses nullid."""
974 supplied, uses nullid."""
975 if common is None:
975 if common is None:
976 common = [nullid]
976 common = [nullid]
977 if heads is None:
977 if heads is None:
978 heads = self.heads()
978 heads = self.heads()
979
979
980 common = [self.rev(n) for n in common]
980 common = [self.rev(n) for n in common]
981 heads = [self.rev(n) for n in heads]
981 heads = [self.rev(n) for n in heads]
982
982
983 inc = self.incrementalmissingrevs(common=common)
983 inc = self.incrementalmissingrevs(common=common)
984 return [self.node(r) for r in inc.missingancestors(heads)]
984 return [self.node(r) for r in inc.missingancestors(heads)]
985
985
986 def nodesbetween(self, roots=None, heads=None):
986 def nodesbetween(self, roots=None, heads=None):
987 """Return a topological path from 'roots' to 'heads'.
987 """Return a topological path from 'roots' to 'heads'.
988
988
989 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
989 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
990 topologically sorted list of all nodes N that satisfy both of
990 topologically sorted list of all nodes N that satisfy both of
991 these constraints:
991 these constraints:
992
992
993 1. N is a descendant of some node in 'roots'
993 1. N is a descendant of some node in 'roots'
994 2. N is an ancestor of some node in 'heads'
994 2. N is an ancestor of some node in 'heads'
995
995
996 Every node is considered to be both a descendant and an ancestor
996 Every node is considered to be both a descendant and an ancestor
997 of itself, so every reachable node in 'roots' and 'heads' will be
997 of itself, so every reachable node in 'roots' and 'heads' will be
998 included in 'nodes'.
998 included in 'nodes'.
999
999
1000 'outroots' is the list of reachable nodes in 'roots', i.e., the
1000 'outroots' is the list of reachable nodes in 'roots', i.e., the
1001 subset of 'roots' that is returned in 'nodes'. Likewise,
1001 subset of 'roots' that is returned in 'nodes'. Likewise,
1002 'outheads' is the subset of 'heads' that is also in 'nodes'.
1002 'outheads' is the subset of 'heads' that is also in 'nodes'.
1003
1003
1004 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1004 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1005 unspecified, uses nullid as the only root. If 'heads' is
1005 unspecified, uses nullid as the only root. If 'heads' is
1006 unspecified, uses list of all of the revlog's heads."""
1006 unspecified, uses list of all of the revlog's heads."""
1007 nonodes = ([], [], [])
1007 nonodes = ([], [], [])
1008 if roots is not None:
1008 if roots is not None:
1009 roots = list(roots)
1009 roots = list(roots)
1010 if not roots:
1010 if not roots:
1011 return nonodes
1011 return nonodes
1012 lowestrev = min([self.rev(n) for n in roots])
1012 lowestrev = min([self.rev(n) for n in roots])
1013 else:
1013 else:
1014 roots = [nullid] # Everybody's a descendant of nullid
1014 roots = [nullid] # Everybody's a descendant of nullid
1015 lowestrev = nullrev
1015 lowestrev = nullrev
1016 if (lowestrev == nullrev) and (heads is None):
1016 if (lowestrev == nullrev) and (heads is None):
1017 # We want _all_ the nodes!
1017 # We want _all_ the nodes!
1018 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1018 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1019 if heads is None:
1019 if heads is None:
1020 # All nodes are ancestors, so the latest ancestor is the last
1020 # All nodes are ancestors, so the latest ancestor is the last
1021 # node.
1021 # node.
1022 highestrev = len(self) - 1
1022 highestrev = len(self) - 1
1023 # Set ancestors to None to signal that every node is an ancestor.
1023 # Set ancestors to None to signal that every node is an ancestor.
1024 ancestors = None
1024 ancestors = None
1025 # Set heads to an empty dictionary for later discovery of heads
1025 # Set heads to an empty dictionary for later discovery of heads
1026 heads = {}
1026 heads = {}
1027 else:
1027 else:
1028 heads = list(heads)
1028 heads = list(heads)
1029 if not heads:
1029 if not heads:
1030 return nonodes
1030 return nonodes
1031 ancestors = set()
1031 ancestors = set()
1032 # Turn heads into a dictionary so we can remove 'fake' heads.
1032 # Turn heads into a dictionary so we can remove 'fake' heads.
1033 # Also, later we will be using it to filter out the heads we can't
1033 # Also, later we will be using it to filter out the heads we can't
1034 # find from roots.
1034 # find from roots.
1035 heads = dict.fromkeys(heads, False)
1035 heads = dict.fromkeys(heads, False)
1036 # Start at the top and keep marking parents until we're done.
1036 # Start at the top and keep marking parents until we're done.
1037 nodestotag = set(heads)
1037 nodestotag = set(heads)
1038 # Remember where the top was so we can use it as a limit later.
1038 # Remember where the top was so we can use it as a limit later.
1039 highestrev = max([self.rev(n) for n in nodestotag])
1039 highestrev = max([self.rev(n) for n in nodestotag])
1040 while nodestotag:
1040 while nodestotag:
1041 # grab a node to tag
1041 # grab a node to tag
1042 n = nodestotag.pop()
1042 n = nodestotag.pop()
1043 # Never tag nullid
1043 # Never tag nullid
1044 if n == nullid:
1044 if n == nullid:
1045 continue
1045 continue
1046 # A node's revision number represents its place in a
1046 # A node's revision number represents its place in a
1047 # topologically sorted list of nodes.
1047 # topologically sorted list of nodes.
1048 r = self.rev(n)
1048 r = self.rev(n)
1049 if r >= lowestrev:
1049 if r >= lowestrev:
1050 if n not in ancestors:
1050 if n not in ancestors:
1051 # If we are possibly a descendant of one of the roots
1051 # If we are possibly a descendant of one of the roots
1052 # and we haven't already been marked as an ancestor
1052 # and we haven't already been marked as an ancestor
1053 ancestors.add(n) # Mark as ancestor
1053 ancestors.add(n) # Mark as ancestor
1054 # Add non-nullid parents to list of nodes to tag.
1054 # Add non-nullid parents to list of nodes to tag.
1055 nodestotag.update(
1055 nodestotag.update(
1056 [p for p in self.parents(n) if p != nullid]
1056 [p for p in self.parents(n) if p != nullid]
1057 )
1057 )
1058 elif n in heads: # We've seen it before, is it a fake head?
1058 elif n in heads: # We've seen it before, is it a fake head?
1059 # So it is, real heads should not be the ancestors of
1059 # So it is, real heads should not be the ancestors of
1060 # any other heads.
1060 # any other heads.
1061 heads.pop(n)
1061 heads.pop(n)
1062 if not ancestors:
1062 if not ancestors:
1063 return nonodes
1063 return nonodes
1064 # Now that we have our set of ancestors, we want to remove any
1064 # Now that we have our set of ancestors, we want to remove any
1065 # roots that are not ancestors.
1065 # roots that are not ancestors.
1066
1066
1067 # If one of the roots was nullid, everything is included anyway.
1067 # If one of the roots was nullid, everything is included anyway.
1068 if lowestrev > nullrev:
1068 if lowestrev > nullrev:
1069 # But, since we weren't, let's recompute the lowest rev to not
1069 # But, since we weren't, let's recompute the lowest rev to not
1070 # include roots that aren't ancestors.
1070 # include roots that aren't ancestors.
1071
1071
1072 # Filter out roots that aren't ancestors of heads
1072 # Filter out roots that aren't ancestors of heads
1073 roots = [root for root in roots if root in ancestors]
1073 roots = [root for root in roots if root in ancestors]
1074 # Recompute the lowest revision
1074 # Recompute the lowest revision
1075 if roots:
1075 if roots:
1076 lowestrev = min([self.rev(root) for root in roots])
1076 lowestrev = min([self.rev(root) for root in roots])
1077 else:
1077 else:
1078 # No more roots? Return empty list
1078 # No more roots? Return empty list
1079 return nonodes
1079 return nonodes
1080 else:
1080 else:
1081 # We are descending from nullid, and don't need to care about
1081 # We are descending from nullid, and don't need to care about
1082 # any other roots.
1082 # any other roots.
1083 lowestrev = nullrev
1083 lowestrev = nullrev
1084 roots = [nullid]
1084 roots = [nullid]
1085 # Transform our roots list into a set.
1085 # Transform our roots list into a set.
1086 descendants = set(roots)
1086 descendants = set(roots)
1087 # Also, keep the original roots so we can filter out roots that aren't
1087 # Also, keep the original roots so we can filter out roots that aren't
1088 # 'real' roots (i.e. are descended from other roots).
1088 # 'real' roots (i.e. are descended from other roots).
1089 roots = descendants.copy()
1089 roots = descendants.copy()
1090 # Our topologically sorted list of output nodes.
1090 # Our topologically sorted list of output nodes.
1091 orderedout = []
1091 orderedout = []
1092 # Don't start at nullid since we don't want nullid in our output list,
1092 # Don't start at nullid since we don't want nullid in our output list,
1093 # and if nullid shows up in descendants, empty parents will look like
1093 # and if nullid shows up in descendants, empty parents will look like
1094 # they're descendants.
1094 # they're descendants.
1095 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1095 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1096 n = self.node(r)
1096 n = self.node(r)
1097 isdescendant = False
1097 isdescendant = False
1098 if lowestrev == nullrev: # Everybody is a descendant of nullid
1098 if lowestrev == nullrev: # Everybody is a descendant of nullid
1099 isdescendant = True
1099 isdescendant = True
1100 elif n in descendants:
1100 elif n in descendants:
1101 # n is already a descendant
1101 # n is already a descendant
1102 isdescendant = True
1102 isdescendant = True
1103 # This check only needs to be done here because all the roots
1103 # This check only needs to be done here because all the roots
1104 # will start being marked is descendants before the loop.
1104 # will start being marked is descendants before the loop.
1105 if n in roots:
1105 if n in roots:
1106 # If n was a root, check if it's a 'real' root.
1106 # If n was a root, check if it's a 'real' root.
1107 p = tuple(self.parents(n))
1107 p = tuple(self.parents(n))
1108 # If any of its parents are descendants, it's not a root.
1108 # If any of its parents are descendants, it's not a root.
1109 if (p[0] in descendants) or (p[1] in descendants):
1109 if (p[0] in descendants) or (p[1] in descendants):
1110 roots.remove(n)
1110 roots.remove(n)
1111 else:
1111 else:
1112 p = tuple(self.parents(n))
1112 p = tuple(self.parents(n))
1113 # A node is a descendant if either of its parents are
1113 # A node is a descendant if either of its parents are
1114 # descendants. (We seeded the dependents list with the roots
1114 # descendants. (We seeded the dependents list with the roots
1115 # up there, remember?)
1115 # up there, remember?)
1116 if (p[0] in descendants) or (p[1] in descendants):
1116 if (p[0] in descendants) or (p[1] in descendants):
1117 descendants.add(n)
1117 descendants.add(n)
1118 isdescendant = True
1118 isdescendant = True
1119 if isdescendant and ((ancestors is None) or (n in ancestors)):
1119 if isdescendant and ((ancestors is None) or (n in ancestors)):
1120 # Only include nodes that are both descendants and ancestors.
1120 # Only include nodes that are both descendants and ancestors.
1121 orderedout.append(n)
1121 orderedout.append(n)
1122 if (ancestors is not None) and (n in heads):
1122 if (ancestors is not None) and (n in heads):
1123 # We're trying to figure out which heads are reachable
1123 # We're trying to figure out which heads are reachable
1124 # from roots.
1124 # from roots.
1125 # Mark this head as having been reached
1125 # Mark this head as having been reached
1126 heads[n] = True
1126 heads[n] = True
1127 elif ancestors is None:
1127 elif ancestors is None:
1128 # Otherwise, we're trying to discover the heads.
1128 # Otherwise, we're trying to discover the heads.
1129 # Assume this is a head because if it isn't, the next step
1129 # Assume this is a head because if it isn't, the next step
1130 # will eventually remove it.
1130 # will eventually remove it.
1131 heads[n] = True
1131 heads[n] = True
1132 # But, obviously its parents aren't.
1132 # But, obviously its parents aren't.
1133 for p in self.parents(n):
1133 for p in self.parents(n):
1134 heads.pop(p, None)
1134 heads.pop(p, None)
1135 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1135 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1136 roots = list(roots)
1136 roots = list(roots)
1137 assert orderedout
1137 assert orderedout
1138 assert roots
1138 assert roots
1139 assert heads
1139 assert heads
1140 return (orderedout, roots, heads)
1140 return (orderedout, roots, heads)
1141
1141
1142 def headrevs(self, revs=None):
1142 def headrevs(self, revs=None):
1143 if revs is None:
1143 if revs is None:
1144 try:
1144 try:
1145 return self.index.headrevs()
1145 return self.index.headrevs()
1146 except AttributeError:
1146 except AttributeError:
1147 return self._headrevs()
1147 return self._headrevs()
1148 if rustdagop is not None:
1148 if rustdagop is not None:
1149 return rustdagop.headrevs(self.index, revs)
1149 return rustdagop.headrevs(self.index, revs)
1150 return dagop.headrevs(revs, self._uncheckedparentrevs)
1150 return dagop.headrevs(revs, self._uncheckedparentrevs)
1151
1151
1152 def computephases(self, roots):
1152 def computephases(self, roots):
1153 return self.index.computephasesmapsets(roots)
1153 return self.index.computephasesmapsets(roots)
1154
1154
1155 def _headrevs(self):
1155 def _headrevs(self):
1156 count = len(self)
1156 count = len(self)
1157 if not count:
1157 if not count:
1158 return [nullrev]
1158 return [nullrev]
1159 # we won't iter over filtered rev so nobody is a head at start
1159 # we won't iter over filtered rev so nobody is a head at start
1160 ishead = [0] * (count + 1)
1160 ishead = [0] * (count + 1)
1161 index = self.index
1161 index = self.index
1162 for r in self:
1162 for r in self:
1163 ishead[r] = 1 # I may be an head
1163 ishead[r] = 1 # I may be an head
1164 e = index[r]
1164 e = index[r]
1165 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1165 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1166 return [r for r, val in enumerate(ishead) if val]
1166 return [r for r, val in enumerate(ishead) if val]
1167
1167
1168 def heads(self, start=None, stop=None):
1168 def heads(self, start=None, stop=None):
1169 """return the list of all nodes that have no children
1169 """return the list of all nodes that have no children
1170
1170
1171 if start is specified, only heads that are descendants of
1171 if start is specified, only heads that are descendants of
1172 start will be returned
1172 start will be returned
1173 if stop is specified, it will consider all the revs from stop
1173 if stop is specified, it will consider all the revs from stop
1174 as if they had no children
1174 as if they had no children
1175 """
1175 """
1176 if start is None and stop is None:
1176 if start is None and stop is None:
1177 if not len(self):
1177 if not len(self):
1178 return [nullid]
1178 return [nullid]
1179 return [self.node(r) for r in self.headrevs()]
1179 return [self.node(r) for r in self.headrevs()]
1180
1180
1181 if start is None:
1181 if start is None:
1182 start = nullrev
1182 start = nullrev
1183 else:
1183 else:
1184 start = self.rev(start)
1184 start = self.rev(start)
1185
1185
1186 stoprevs = set(self.rev(n) for n in stop or [])
1186 stoprevs = set(self.rev(n) for n in stop or [])
1187
1187
1188 revs = dagop.headrevssubset(
1188 revs = dagop.headrevssubset(
1189 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1189 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1190 )
1190 )
1191
1191
1192 return [self.node(rev) for rev in revs]
1192 return [self.node(rev) for rev in revs]
1193
1193
1194 def children(self, node):
1194 def children(self, node):
1195 """find the children of a given node"""
1195 """find the children of a given node"""
1196 c = []
1196 c = []
1197 p = self.rev(node)
1197 p = self.rev(node)
1198 for r in self.revs(start=p + 1):
1198 for r in self.revs(start=p + 1):
1199 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1199 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1200 if prevs:
1200 if prevs:
1201 for pr in prevs:
1201 for pr in prevs:
1202 if pr == p:
1202 if pr == p:
1203 c.append(self.node(r))
1203 c.append(self.node(r))
1204 elif p == nullrev:
1204 elif p == nullrev:
1205 c.append(self.node(r))
1205 c.append(self.node(r))
1206 return c
1206 return c
1207
1207
1208 def commonancestorsheads(self, a, b):
1208 def commonancestorsheads(self, a, b):
1209 """calculate all the heads of the common ancestors of nodes a and b"""
1209 """calculate all the heads of the common ancestors of nodes a and b"""
1210 a, b = self.rev(a), self.rev(b)
1210 a, b = self.rev(a), self.rev(b)
1211 ancs = self._commonancestorsheads(a, b)
1211 ancs = self._commonancestorsheads(a, b)
1212 return pycompat.maplist(self.node, ancs)
1212 return pycompat.maplist(self.node, ancs)
1213
1213
1214 def _commonancestorsheads(self, *revs):
1214 def _commonancestorsheads(self, *revs):
1215 """calculate all the heads of the common ancestors of revs"""
1215 """calculate all the heads of the common ancestors of revs"""
1216 try:
1216 try:
1217 ancs = self.index.commonancestorsheads(*revs)
1217 ancs = self.index.commonancestorsheads(*revs)
1218 except (AttributeError, OverflowError): # C implementation failed
1218 except (AttributeError, OverflowError): # C implementation failed
1219 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1219 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1220 return ancs
1220 return ancs
1221
1221
1222 def isancestor(self, a, b):
1222 def isancestor(self, a, b):
1223 """return True if node a is an ancestor of node b
1223 """return True if node a is an ancestor of node b
1224
1224
1225 A revision is considered an ancestor of itself."""
1225 A revision is considered an ancestor of itself."""
1226 a, b = self.rev(a), self.rev(b)
1226 a, b = self.rev(a), self.rev(b)
1227 return self.isancestorrev(a, b)
1227 return self.isancestorrev(a, b)
1228
1228
1229 def isancestorrev(self, a, b):
1229 def isancestorrev(self, a, b):
1230 """return True if revision a is an ancestor of revision b
1230 """return True if revision a is an ancestor of revision b
1231
1231
1232 A revision is considered an ancestor of itself.
1232 A revision is considered an ancestor of itself.
1233
1233
1234 The implementation of this is trivial but the use of
1234 The implementation of this is trivial but the use of
1235 reachableroots is not."""
1235 reachableroots is not."""
1236 if a == nullrev:
1236 if a == nullrev:
1237 return True
1237 return True
1238 elif a == b:
1238 elif a == b:
1239 return True
1239 return True
1240 elif a > b:
1240 elif a > b:
1241 return False
1241 return False
1242 return bool(self.reachableroots(a, [b], [a], includepath=False))
1242 return bool(self.reachableroots(a, [b], [a], includepath=False))
1243
1243
1244 def reachableroots(self, minroot, heads, roots, includepath=False):
1244 def reachableroots(self, minroot, heads, roots, includepath=False):
1245 """return (heads(::<roots> and <roots>::<heads>))
1245 """return (heads(::<roots> and <roots>::<heads>))
1246
1246
1247 If includepath is True, return (<roots>::<heads>)."""
1247 If includepath is True, return (<roots>::<heads>)."""
1248 try:
1248 try:
1249 return self.index.reachableroots2(
1249 return self.index.reachableroots2(
1250 minroot, heads, roots, includepath
1250 minroot, heads, roots, includepath
1251 )
1251 )
1252 except AttributeError:
1252 except AttributeError:
1253 return dagop._reachablerootspure(
1253 return dagop._reachablerootspure(
1254 self.parentrevs, minroot, roots, heads, includepath
1254 self.parentrevs, minroot, roots, heads, includepath
1255 )
1255 )
1256
1256
1257 def ancestor(self, a, b):
1257 def ancestor(self, a, b):
1258 """calculate the "best" common ancestor of nodes a and b"""
1258 """calculate the "best" common ancestor of nodes a and b"""
1259
1259
1260 a, b = self.rev(a), self.rev(b)
1260 a, b = self.rev(a), self.rev(b)
1261 try:
1261 try:
1262 ancs = self.index.ancestors(a, b)
1262 ancs = self.index.ancestors(a, b)
1263 except (AttributeError, OverflowError):
1263 except (AttributeError, OverflowError):
1264 ancs = ancestor.ancestors(self.parentrevs, a, b)
1264 ancs = ancestor.ancestors(self.parentrevs, a, b)
1265 if ancs:
1265 if ancs:
1266 # choose a consistent winner when there's a tie
1266 # choose a consistent winner when there's a tie
1267 return min(map(self.node, ancs))
1267 return min(map(self.node, ancs))
1268 return nullid
1268 return nullid
1269
1269
1270 def _match(self, id):
1270 def _match(self, id):
1271 if isinstance(id, int):
1271 if isinstance(id, int):
1272 # rev
1272 # rev
1273 return self.node(id)
1273 return self.node(id)
1274 if len(id) == 20:
1274 if len(id) == 20:
1275 # possibly a binary node
1275 # possibly a binary node
1276 # odds of a binary node being all hex in ASCII are 1 in 10**25
1276 # odds of a binary node being all hex in ASCII are 1 in 10**25
1277 try:
1277 try:
1278 node = id
1278 node = id
1279 self.rev(node) # quick search the index
1279 self.rev(node) # quick search the index
1280 return node
1280 return node
1281 except error.LookupError:
1281 except error.LookupError:
1282 pass # may be partial hex id
1282 pass # may be partial hex id
1283 try:
1283 try:
1284 # str(rev)
1284 # str(rev)
1285 rev = int(id)
1285 rev = int(id)
1286 if b"%d" % rev != id:
1286 if b"%d" % rev != id:
1287 raise ValueError
1287 raise ValueError
1288 if rev < 0:
1288 if rev < 0:
1289 rev = len(self) + rev
1289 rev = len(self) + rev
1290 if rev < 0 or rev >= len(self):
1290 if rev < 0 or rev >= len(self):
1291 raise ValueError
1291 raise ValueError
1292 return self.node(rev)
1292 return self.node(rev)
1293 except (ValueError, OverflowError):
1293 except (ValueError, OverflowError):
1294 pass
1294 pass
1295 if len(id) == 40:
1295 if len(id) == 40:
1296 try:
1296 try:
1297 # a full hex nodeid?
1297 # a full hex nodeid?
1298 node = bin(id)
1298 node = bin(id)
1299 self.rev(node)
1299 self.rev(node)
1300 return node
1300 return node
1301 except (TypeError, error.LookupError):
1301 except (TypeError, error.LookupError):
1302 pass
1302 pass
1303
1303
1304 def _partialmatch(self, id):
1304 def _partialmatch(self, id):
1305 # we don't care wdirfilenodeids as they should be always full hash
1305 # we don't care wdirfilenodeids as they should be always full hash
1306 maybewdir = wdirhex.startswith(id)
1306 maybewdir = wdirhex.startswith(id)
1307 try:
1307 try:
1308 partial = self.index.partialmatch(id)
1308 partial = self.index.partialmatch(id)
1309 if partial and self.hasnode(partial):
1309 if partial and self.hasnode(partial):
1310 if maybewdir:
1310 if maybewdir:
1311 # single 'ff...' match in radix tree, ambiguous with wdir
1311 # single 'ff...' match in radix tree, ambiguous with wdir
1312 raise error.RevlogError
1312 raise error.RevlogError
1313 return partial
1313 return partial
1314 if maybewdir:
1314 if maybewdir:
1315 # no 'ff...' match in radix tree, wdir identified
1315 # no 'ff...' match in radix tree, wdir identified
1316 raise error.WdirUnsupported
1316 raise error.WdirUnsupported
1317 return None
1317 return None
1318 except error.RevlogError:
1318 except error.RevlogError:
1319 # parsers.c radix tree lookup gave multiple matches
1319 # parsers.c radix tree lookup gave multiple matches
1320 # fast path: for unfiltered changelog, radix tree is accurate
1320 # fast path: for unfiltered changelog, radix tree is accurate
1321 if not getattr(self, 'filteredrevs', None):
1321 if not getattr(self, 'filteredrevs', None):
1322 raise error.AmbiguousPrefixLookupError(
1322 raise error.AmbiguousPrefixLookupError(
1323 id, self.indexfile, _(b'ambiguous identifier')
1323 id, self.indexfile, _(b'ambiguous identifier')
1324 )
1324 )
1325 # fall through to slow path that filters hidden revisions
1325 # fall through to slow path that filters hidden revisions
1326 except (AttributeError, ValueError):
1326 except (AttributeError, ValueError):
1327 # we are pure python, or key was too short to search radix tree
1327 # we are pure python, or key was too short to search radix tree
1328 pass
1328 pass
1329
1329
1330 if id in self._pcache:
1330 if id in self._pcache:
1331 return self._pcache[id]
1331 return self._pcache[id]
1332
1332
1333 if len(id) <= 40:
1333 if len(id) <= 40:
1334 try:
1334 try:
1335 # hex(node)[:...]
1335 # hex(node)[:...]
1336 l = len(id) // 2 # grab an even number of digits
1336 l = len(id) // 2 # grab an even number of digits
1337 prefix = bin(id[: l * 2])
1337 prefix = bin(id[: l * 2])
1338 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1338 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1339 nl = [
1339 nl = [
1340 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1340 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1341 ]
1341 ]
1342 if nullhex.startswith(id):
1342 if nullhex.startswith(id):
1343 nl.append(nullid)
1343 nl.append(nullid)
1344 if len(nl) > 0:
1344 if len(nl) > 0:
1345 if len(nl) == 1 and not maybewdir:
1345 if len(nl) == 1 and not maybewdir:
1346 self._pcache[id] = nl[0]
1346 self._pcache[id] = nl[0]
1347 return nl[0]
1347 return nl[0]
1348 raise error.AmbiguousPrefixLookupError(
1348 raise error.AmbiguousPrefixLookupError(
1349 id, self.indexfile, _(b'ambiguous identifier')
1349 id, self.indexfile, _(b'ambiguous identifier')
1350 )
1350 )
1351 if maybewdir:
1351 if maybewdir:
1352 raise error.WdirUnsupported
1352 raise error.WdirUnsupported
1353 return None
1353 return None
1354 except TypeError:
1354 except TypeError:
1355 pass
1355 pass
1356
1356
1357 def lookup(self, id):
1357 def lookup(self, id):
1358 """locate a node based on:
1358 """locate a node based on:
1359 - revision number or str(revision number)
1359 - revision number or str(revision number)
1360 - nodeid or subset of hex nodeid
1360 - nodeid or subset of hex nodeid
1361 """
1361 """
1362 n = self._match(id)
1362 n = self._match(id)
1363 if n is not None:
1363 if n is not None:
1364 return n
1364 return n
1365 n = self._partialmatch(id)
1365 n = self._partialmatch(id)
1366 if n:
1366 if n:
1367 return n
1367 return n
1368
1368
1369 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1369 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1370
1370
1371 def shortest(self, node, minlength=1):
1371 def shortest(self, node, minlength=1):
1372 """Find the shortest unambiguous prefix that matches node."""
1372 """Find the shortest unambiguous prefix that matches node."""
1373
1373
1374 def isvalid(prefix):
1374 def isvalid(prefix):
1375 try:
1375 try:
1376 matchednode = self._partialmatch(prefix)
1376 matchednode = self._partialmatch(prefix)
1377 except error.AmbiguousPrefixLookupError:
1377 except error.AmbiguousPrefixLookupError:
1378 return False
1378 return False
1379 except error.WdirUnsupported:
1379 except error.WdirUnsupported:
1380 # single 'ff...' match
1380 # single 'ff...' match
1381 return True
1381 return True
1382 if matchednode is None:
1382 if matchednode is None:
1383 raise error.LookupError(node, self.indexfile, _(b'no node'))
1383 raise error.LookupError(node, self.indexfile, _(b'no node'))
1384 return True
1384 return True
1385
1385
1386 def maybewdir(prefix):
1386 def maybewdir(prefix):
1387 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1387 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1388
1388
1389 hexnode = hex(node)
1389 hexnode = hex(node)
1390
1390
1391 def disambiguate(hexnode, minlength):
1391 def disambiguate(hexnode, minlength):
1392 """Disambiguate against wdirid."""
1392 """Disambiguate against wdirid."""
1393 for length in range(minlength, 41):
1393 for length in range(minlength, 41):
1394 prefix = hexnode[:length]
1394 prefix = hexnode[:length]
1395 if not maybewdir(prefix):
1395 if not maybewdir(prefix):
1396 return prefix
1396 return prefix
1397
1397
1398 if not getattr(self, 'filteredrevs', None):
1398 if not getattr(self, 'filteredrevs', None):
1399 try:
1399 try:
1400 length = max(self.index.shortest(node), minlength)
1400 length = max(self.index.shortest(node), minlength)
1401 return disambiguate(hexnode, length)
1401 return disambiguate(hexnode, length)
1402 except error.RevlogError:
1402 except error.RevlogError:
1403 if node != wdirid:
1403 if node != wdirid:
1404 raise error.LookupError(node, self.indexfile, _(b'no node'))
1404 raise error.LookupError(node, self.indexfile, _(b'no node'))
1405 except AttributeError:
1405 except AttributeError:
1406 # Fall through to pure code
1406 # Fall through to pure code
1407 pass
1407 pass
1408
1408
1409 if node == wdirid:
1409 if node == wdirid:
1410 for length in range(minlength, 41):
1410 for length in range(minlength, 41):
1411 prefix = hexnode[:length]
1411 prefix = hexnode[:length]
1412 if isvalid(prefix):
1412 if isvalid(prefix):
1413 return prefix
1413 return prefix
1414
1414
1415 for length in range(minlength, 41):
1415 for length in range(minlength, 41):
1416 prefix = hexnode[:length]
1416 prefix = hexnode[:length]
1417 if isvalid(prefix):
1417 if isvalid(prefix):
1418 return disambiguate(hexnode, length)
1418 return disambiguate(hexnode, length)
1419
1419
1420 def cmp(self, node, text):
1420 def cmp(self, node, text):
1421 """compare text with a given file revision
1421 """compare text with a given file revision
1422
1422
1423 returns True if text is different than what is stored.
1423 returns True if text is different than what is stored.
1424 """
1424 """
1425 p1, p2 = self.parents(node)
1425 p1, p2 = self.parents(node)
1426 return storageutil.hashrevisionsha1(text, p1, p2) != node
1426 return storageutil.hashrevisionsha1(text, p1, p2) != node
1427
1427
1428 def _cachesegment(self, offset, data):
1428 def _cachesegment(self, offset, data):
1429 """Add a segment to the revlog cache.
1429 """Add a segment to the revlog cache.
1430
1430
1431 Accepts an absolute offset and the data that is at that location.
1431 Accepts an absolute offset and the data that is at that location.
1432 """
1432 """
1433 o, d = self._chunkcache
1433 o, d = self._chunkcache
1434 # try to add to existing cache
1434 # try to add to existing cache
1435 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1435 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1436 self._chunkcache = o, d + data
1436 self._chunkcache = o, d + data
1437 else:
1437 else:
1438 self._chunkcache = offset, data
1438 self._chunkcache = offset, data
1439
1439
1440 def _readsegment(self, offset, length, df=None):
1440 def _readsegment(self, offset, length, df=None):
1441 """Load a segment of raw data from the revlog.
1441 """Load a segment of raw data from the revlog.
1442
1442
1443 Accepts an absolute offset, length to read, and an optional existing
1443 Accepts an absolute offset, length to read, and an optional existing
1444 file handle to read from.
1444 file handle to read from.
1445
1445
1446 If an existing file handle is passed, it will be seeked and the
1446 If an existing file handle is passed, it will be seeked and the
1447 original seek position will NOT be restored.
1447 original seek position will NOT be restored.
1448
1448
1449 Returns a str or buffer of raw byte data.
1449 Returns a str or buffer of raw byte data.
1450
1450
1451 Raises if the requested number of bytes could not be read.
1451 Raises if the requested number of bytes could not be read.
1452 """
1452 """
1453 # Cache data both forward and backward around the requested
1453 # Cache data both forward and backward around the requested
1454 # data, in a fixed size window. This helps speed up operations
1454 # data, in a fixed size window. This helps speed up operations
1455 # involving reading the revlog backwards.
1455 # involving reading the revlog backwards.
1456 cachesize = self._chunkcachesize
1456 cachesize = self._chunkcachesize
1457 realoffset = offset & ~(cachesize - 1)
1457 realoffset = offset & ~(cachesize - 1)
1458 reallength = (
1458 reallength = (
1459 (offset + length + cachesize) & ~(cachesize - 1)
1459 (offset + length + cachesize) & ~(cachesize - 1)
1460 ) - realoffset
1460 ) - realoffset
1461 with self._datareadfp(df) as df:
1461 with self._datareadfp(df) as df:
1462 df.seek(realoffset)
1462 df.seek(realoffset)
1463 d = df.read(reallength)
1463 d = df.read(reallength)
1464
1464
1465 self._cachesegment(realoffset, d)
1465 self._cachesegment(realoffset, d)
1466 if offset != realoffset or reallength != length:
1466 if offset != realoffset or reallength != length:
1467 startoffset = offset - realoffset
1467 startoffset = offset - realoffset
1468 if len(d) - startoffset < length:
1468 if len(d) - startoffset < length:
1469 raise error.RevlogError(
1469 raise error.RevlogError(
1470 _(
1470 _(
1471 b'partial read of revlog %s; expected %d bytes from '
1471 b'partial read of revlog %s; expected %d bytes from '
1472 b'offset %d, got %d'
1472 b'offset %d, got %d'
1473 )
1473 )
1474 % (
1474 % (
1475 self.indexfile if self._inline else self.datafile,
1475 self.indexfile if self._inline else self.datafile,
1476 length,
1476 length,
1477 realoffset,
1477 realoffset,
1478 len(d) - startoffset,
1478 len(d) - startoffset,
1479 )
1479 )
1480 )
1480 )
1481
1481
1482 return util.buffer(d, startoffset, length)
1482 return util.buffer(d, startoffset, length)
1483
1483
1484 if len(d) < length:
1484 if len(d) < length:
1485 raise error.RevlogError(
1485 raise error.RevlogError(
1486 _(
1486 _(
1487 b'partial read of revlog %s; expected %d bytes from offset '
1487 b'partial read of revlog %s; expected %d bytes from offset '
1488 b'%d, got %d'
1488 b'%d, got %d'
1489 )
1489 )
1490 % (
1490 % (
1491 self.indexfile if self._inline else self.datafile,
1491 self.indexfile if self._inline else self.datafile,
1492 length,
1492 length,
1493 offset,
1493 offset,
1494 len(d),
1494 len(d),
1495 )
1495 )
1496 )
1496 )
1497
1497
1498 return d
1498 return d
1499
1499
1500 def _getsegment(self, offset, length, df=None):
1500 def _getsegment(self, offset, length, df=None):
1501 """Obtain a segment of raw data from the revlog.
1501 """Obtain a segment of raw data from the revlog.
1502
1502
1503 Accepts an absolute offset, length of bytes to obtain, and an
1503 Accepts an absolute offset, length of bytes to obtain, and an
1504 optional file handle to the already-opened revlog. If the file
1504 optional file handle to the already-opened revlog. If the file
1505 handle is used, it's original seek position will not be preserved.
1505 handle is used, it's original seek position will not be preserved.
1506
1506
1507 Requests for data may be returned from a cache.
1507 Requests for data may be returned from a cache.
1508
1508
1509 Returns a str or a buffer instance of raw byte data.
1509 Returns a str or a buffer instance of raw byte data.
1510 """
1510 """
1511 o, d = self._chunkcache
1511 o, d = self._chunkcache
1512 l = len(d)
1512 l = len(d)
1513
1513
1514 # is it in the cache?
1514 # is it in the cache?
1515 cachestart = offset - o
1515 cachestart = offset - o
1516 cacheend = cachestart + length
1516 cacheend = cachestart + length
1517 if cachestart >= 0 and cacheend <= l:
1517 if cachestart >= 0 and cacheend <= l:
1518 if cachestart == 0 and cacheend == l:
1518 if cachestart == 0 and cacheend == l:
1519 return d # avoid a copy
1519 return d # avoid a copy
1520 return util.buffer(d, cachestart, cacheend - cachestart)
1520 return util.buffer(d, cachestart, cacheend - cachestart)
1521
1521
1522 return self._readsegment(offset, length, df=df)
1522 return self._readsegment(offset, length, df=df)
1523
1523
1524 def _getsegmentforrevs(self, startrev, endrev, df=None):
1524 def _getsegmentforrevs(self, startrev, endrev, df=None):
1525 """Obtain a segment of raw data corresponding to a range of revisions.
1525 """Obtain a segment of raw data corresponding to a range of revisions.
1526
1526
1527 Accepts the start and end revisions and an optional already-open
1527 Accepts the start and end revisions and an optional already-open
1528 file handle to be used for reading. If the file handle is read, its
1528 file handle to be used for reading. If the file handle is read, its
1529 seek position will not be preserved.
1529 seek position will not be preserved.
1530
1530
1531 Requests for data may be satisfied by a cache.
1531 Requests for data may be satisfied by a cache.
1532
1532
1533 Returns a 2-tuple of (offset, data) for the requested range of
1533 Returns a 2-tuple of (offset, data) for the requested range of
1534 revisions. Offset is the integer offset from the beginning of the
1534 revisions. Offset is the integer offset from the beginning of the
1535 revlog and data is a str or buffer of the raw byte data.
1535 revlog and data is a str or buffer of the raw byte data.
1536
1536
1537 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1537 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1538 to determine where each revision's data begins and ends.
1538 to determine where each revision's data begins and ends.
1539 """
1539 """
1540 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1540 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1541 # (functions are expensive).
1541 # (functions are expensive).
1542 index = self.index
1542 index = self.index
1543 istart = index[startrev]
1543 istart = index[startrev]
1544 start = int(istart[0] >> 16)
1544 start = int(istart[0] >> 16)
1545 if startrev == endrev:
1545 if startrev == endrev:
1546 end = start + istart[1]
1546 end = start + istart[1]
1547 else:
1547 else:
1548 iend = index[endrev]
1548 iend = index[endrev]
1549 end = int(iend[0] >> 16) + iend[1]
1549 end = int(iend[0] >> 16) + iend[1]
1550
1550
1551 if self._inline:
1551 if self._inline:
1552 start += (startrev + 1) * self._io.size
1552 start += (startrev + 1) * self._io.size
1553 end += (endrev + 1) * self._io.size
1553 end += (endrev + 1) * self._io.size
1554 length = end - start
1554 length = end - start
1555
1555
1556 return start, self._getsegment(start, length, df=df)
1556 return start, self._getsegment(start, length, df=df)
1557
1557
1558 def _chunk(self, rev, df=None):
1558 def _chunk(self, rev, df=None):
1559 """Obtain a single decompressed chunk for a revision.
1559 """Obtain a single decompressed chunk for a revision.
1560
1560
1561 Accepts an integer revision and an optional already-open file handle
1561 Accepts an integer revision and an optional already-open file handle
1562 to be used for reading. If used, the seek position of the file will not
1562 to be used for reading. If used, the seek position of the file will not
1563 be preserved.
1563 be preserved.
1564
1564
1565 Returns a str holding uncompressed data for the requested revision.
1565 Returns a str holding uncompressed data for the requested revision.
1566 """
1566 """
1567 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1567 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1568
1568
1569 def _chunks(self, revs, df=None, targetsize=None):
1569 def _chunks(self, revs, df=None, targetsize=None):
1570 """Obtain decompressed chunks for the specified revisions.
1570 """Obtain decompressed chunks for the specified revisions.
1571
1571
1572 Accepts an iterable of numeric revisions that are assumed to be in
1572 Accepts an iterable of numeric revisions that are assumed to be in
1573 ascending order. Also accepts an optional already-open file handle
1573 ascending order. Also accepts an optional already-open file handle
1574 to be used for reading. If used, the seek position of the file will
1574 to be used for reading. If used, the seek position of the file will
1575 not be preserved.
1575 not be preserved.
1576
1576
1577 This function is similar to calling ``self._chunk()`` multiple times,
1577 This function is similar to calling ``self._chunk()`` multiple times,
1578 but is faster.
1578 but is faster.
1579
1579
1580 Returns a list with decompressed data for each requested revision.
1580 Returns a list with decompressed data for each requested revision.
1581 """
1581 """
1582 if not revs:
1582 if not revs:
1583 return []
1583 return []
1584 start = self.start
1584 start = self.start
1585 length = self.length
1585 length = self.length
1586 inline = self._inline
1586 inline = self._inline
1587 iosize = self._io.size
1587 iosize = self._io.size
1588 buffer = util.buffer
1588 buffer = util.buffer
1589
1589
1590 l = []
1590 l = []
1591 ladd = l.append
1591 ladd = l.append
1592
1592
1593 if not self._withsparseread:
1593 if not self._withsparseread:
1594 slicedchunks = (revs,)
1594 slicedchunks = (revs,)
1595 else:
1595 else:
1596 slicedchunks = deltautil.slicechunk(
1596 slicedchunks = deltautil.slicechunk(
1597 self, revs, targetsize=targetsize
1597 self, revs, targetsize=targetsize
1598 )
1598 )
1599
1599
1600 for revschunk in slicedchunks:
1600 for revschunk in slicedchunks:
1601 firstrev = revschunk[0]
1601 firstrev = revschunk[0]
1602 # Skip trailing revisions with empty diff
1602 # Skip trailing revisions with empty diff
1603 for lastrev in revschunk[::-1]:
1603 for lastrev in revschunk[::-1]:
1604 if length(lastrev) != 0:
1604 if length(lastrev) != 0:
1605 break
1605 break
1606
1606
1607 try:
1607 try:
1608 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1608 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1609 except OverflowError:
1609 except OverflowError:
1610 # issue4215 - we can't cache a run of chunks greater than
1610 # issue4215 - we can't cache a run of chunks greater than
1611 # 2G on Windows
1611 # 2G on Windows
1612 return [self._chunk(rev, df=df) for rev in revschunk]
1612 return [self._chunk(rev, df=df) for rev in revschunk]
1613
1613
1614 decomp = self.decompress
1614 decomp = self.decompress
1615 for rev in revschunk:
1615 for rev in revschunk:
1616 chunkstart = start(rev)
1616 chunkstart = start(rev)
1617 if inline:
1617 if inline:
1618 chunkstart += (rev + 1) * iosize
1618 chunkstart += (rev + 1) * iosize
1619 chunklength = length(rev)
1619 chunklength = length(rev)
1620 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1620 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1621
1621
1622 return l
1622 return l
1623
1623
1624 def _chunkclear(self):
1624 def _chunkclear(self):
1625 """Clear the raw chunk cache."""
1625 """Clear the raw chunk cache."""
1626 self._chunkcache = (0, b'')
1626 self._chunkcache = (0, b'')
1627
1627
1628 def deltaparent(self, rev):
1628 def deltaparent(self, rev):
1629 """return deltaparent of the given revision"""
1629 """return deltaparent of the given revision"""
1630 base = self.index[rev][3]
1630 base = self.index[rev][3]
1631 if base == rev:
1631 if base == rev:
1632 return nullrev
1632 return nullrev
1633 elif self._generaldelta:
1633 elif self._generaldelta:
1634 return base
1634 return base
1635 else:
1635 else:
1636 return rev - 1
1636 return rev - 1
1637
1637
1638 def issnapshot(self, rev):
1638 def issnapshot(self, rev):
1639 """tells whether rev is a snapshot
1639 """tells whether rev is a snapshot
1640 """
1640 """
1641 if not self._sparserevlog:
1641 if not self._sparserevlog:
1642 return self.deltaparent(rev) == nullrev
1642 return self.deltaparent(rev) == nullrev
1643 elif util.safehasattr(self.index, b'issnapshot'):
1643 elif util.safehasattr(self.index, b'issnapshot'):
1644 # directly assign the method to cache the testing and access
1644 # directly assign the method to cache the testing and access
1645 self.issnapshot = self.index.issnapshot
1645 self.issnapshot = self.index.issnapshot
1646 return self.issnapshot(rev)
1646 return self.issnapshot(rev)
1647 if rev == nullrev:
1647 if rev == nullrev:
1648 return True
1648 return True
1649 entry = self.index[rev]
1649 entry = self.index[rev]
1650 base = entry[3]
1650 base = entry[3]
1651 if base == rev:
1651 if base == rev:
1652 return True
1652 return True
1653 if base == nullrev:
1653 if base == nullrev:
1654 return True
1654 return True
1655 p1 = entry[5]
1655 p1 = entry[5]
1656 p2 = entry[6]
1656 p2 = entry[6]
1657 if base == p1 or base == p2:
1657 if base == p1 or base == p2:
1658 return False
1658 return False
1659 return self.issnapshot(base)
1659 return self.issnapshot(base)
1660
1660
1661 def snapshotdepth(self, rev):
1661 def snapshotdepth(self, rev):
1662 """number of snapshot in the chain before this one"""
1662 """number of snapshot in the chain before this one"""
1663 if not self.issnapshot(rev):
1663 if not self.issnapshot(rev):
1664 raise error.ProgrammingError(b'revision %d not a snapshot')
1664 raise error.ProgrammingError(b'revision %d not a snapshot')
1665 return len(self._deltachain(rev)[0]) - 1
1665 return len(self._deltachain(rev)[0]) - 1
1666
1666
1667 def revdiff(self, rev1, rev2):
1667 def revdiff(self, rev1, rev2):
1668 """return or calculate a delta between two revisions
1668 """return or calculate a delta between two revisions
1669
1669
1670 The delta calculated is in binary form and is intended to be written to
1670 The delta calculated is in binary form and is intended to be written to
1671 revlog data directly. So this function needs raw revision data.
1671 revlog data directly. So this function needs raw revision data.
1672 """
1672 """
1673 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1673 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1674 return bytes(self._chunk(rev2))
1674 return bytes(self._chunk(rev2))
1675
1675
1676 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1676 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1677
1677
1678 def _processflags(self, text, flags, operation, raw=False):
1678 def _processflags(self, text, flags, operation, raw=False):
1679 """deprecated entry point to access flag processors"""
1679 """deprecated entry point to access flag processors"""
1680 msg = b'_processflag(...) use the specialized variant'
1680 msg = b'_processflag(...) use the specialized variant'
1681 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1681 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1682 if raw:
1682 if raw:
1683 return text, flagutil.processflagsraw(self, text, flags)
1683 return text, flagutil.processflagsraw(self, text, flags)
1684 elif operation == b'read':
1684 elif operation == b'read':
1685 return flagutil.processflagsread(self, text, flags)
1685 return flagutil.processflagsread(self, text, flags)
1686 else: # write operation
1686 else: # write operation
1687 return flagutil.processflagswrite(self, text, flags)
1687 return flagutil.processflagswrite(self, text, flags)
1688
1688
1689 def revision(self, nodeorrev, _df=None, raw=False):
1689 def revision(self, nodeorrev, _df=None, raw=False):
1690 """return an uncompressed revision of a given node or revision
1690 """return an uncompressed revision of a given node or revision
1691 number.
1691 number.
1692
1692
1693 _df - an existing file handle to read from. (internal-only)
1693 _df - an existing file handle to read from. (internal-only)
1694 raw - an optional argument specifying if the revision data is to be
1694 raw - an optional argument specifying if the revision data is to be
1695 treated as raw data when applying flag transforms. 'raw' should be set
1695 treated as raw data when applying flag transforms. 'raw' should be set
1696 to True when generating changegroups or in debug commands.
1696 to True when generating changegroups or in debug commands.
1697 """
1697 """
1698 if raw:
1698 if raw:
1699 msg = (
1699 msg = (
1700 b'revlog.revision(..., raw=True) is deprecated, '
1700 b'revlog.revision(..., raw=True) is deprecated, '
1701 b'use revlog.rawdata(...)'
1701 b'use revlog.rawdata(...)'
1702 )
1702 )
1703 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1703 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1704 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1704 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1705
1705
1706 def sidedata(self, nodeorrev, _df=None):
1706 def sidedata(self, nodeorrev, _df=None):
1707 """a map of extra data related to the changeset but not part of the hash
1707 """a map of extra data related to the changeset but not part of the hash
1708
1708
1709 This function currently return a dictionary. However, more advanced
1709 This function currently return a dictionary. However, more advanced
1710 mapping object will likely be used in the future for a more
1710 mapping object will likely be used in the future for a more
1711 efficient/lazy code.
1711 efficient/lazy code.
1712 """
1712 """
1713 return self._revisiondata(nodeorrev, _df)[1]
1713 return self._revisiondata(nodeorrev, _df)[1]
1714
1714
1715 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1715 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1716 # deal with <nodeorrev> argument type
1716 # deal with <nodeorrev> argument type
1717 if isinstance(nodeorrev, int):
1717 if isinstance(nodeorrev, int):
1718 rev = nodeorrev
1718 rev = nodeorrev
1719 node = self.node(rev)
1719 node = self.node(rev)
1720 else:
1720 else:
1721 node = nodeorrev
1721 node = nodeorrev
1722 rev = None
1722 rev = None
1723
1723
1724 # fast path the special `nullid` rev
1724 # fast path the special `nullid` rev
1725 if node == nullid:
1725 if node == nullid:
1726 return b"", {}
1726 return b"", {}
1727
1727
1728 # The text as stored inside the revlog. Might be the revision or might
1728 # The text as stored inside the revlog. Might be the revision or might
1729 # need to be processed to retrieve the revision.
1729 # need to be processed to retrieve the revision.
1730 rawtext = None
1730 rawtext = None
1731
1731
1732 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1732 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1733
1733
1734 if raw and validated:
1734 if raw and validated:
1735 # if we don't want to process the raw text and that raw
1735 # if we don't want to process the raw text and that raw
1736 # text is cached, we can exit early.
1736 # text is cached, we can exit early.
1737 return rawtext, {}
1737 return rawtext, {}
1738 if rev is None:
1738 if rev is None:
1739 rev = self.rev(node)
1739 rev = self.rev(node)
1740 # the revlog's flag for this revision
1740 # the revlog's flag for this revision
1741 # (usually alter its state or content)
1741 # (usually alter its state or content)
1742 flags = self.flags(rev)
1742 flags = self.flags(rev)
1743
1743
1744 if validated and flags == REVIDX_DEFAULT_FLAGS:
1744 if validated and flags == REVIDX_DEFAULT_FLAGS:
1745 # no extra flags set, no flag processor runs, text = rawtext
1745 # no extra flags set, no flag processor runs, text = rawtext
1746 return rawtext, {}
1746 return rawtext, {}
1747
1747
1748 sidedata = {}
1748 sidedata = {}
1749 if raw:
1749 if raw:
1750 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1750 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1751 text = rawtext
1751 text = rawtext
1752 else:
1752 else:
1753 try:
1753 try:
1754 r = flagutil.processflagsread(self, rawtext, flags)
1754 r = flagutil.processflagsread(self, rawtext, flags)
1755 except error.SidedataHashError as exc:
1755 except error.SidedataHashError as exc:
1756 msg = _(b"integrity check failed on %s:%s sidedata key %d")
1756 msg = _(b"integrity check failed on %s:%s sidedata key %d")
1757 msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey)
1757 msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey)
1758 raise error.RevlogError(msg)
1758 raise error.RevlogError(msg)
1759 text, validatehash, sidedata = r
1759 text, validatehash, sidedata = r
1760 if validatehash:
1760 if validatehash:
1761 self.checkhash(text, node, rev=rev)
1761 self.checkhash(text, node, rev=rev)
1762 if not validated:
1762 if not validated:
1763 self._revisioncache = (node, rev, rawtext)
1763 self._revisioncache = (node, rev, rawtext)
1764
1764
1765 return text, sidedata
1765 return text, sidedata
1766
1766
1767 def _rawtext(self, node, rev, _df=None):
1767 def _rawtext(self, node, rev, _df=None):
1768 """return the possibly unvalidated rawtext for a revision
1768 """return the possibly unvalidated rawtext for a revision
1769
1769
1770 returns (rev, rawtext, validated)
1770 returns (rev, rawtext, validated)
1771 """
1771 """
1772
1772
1773 # revision in the cache (could be useful to apply delta)
1773 # revision in the cache (could be useful to apply delta)
1774 cachedrev = None
1774 cachedrev = None
1775 # An intermediate text to apply deltas to
1775 # An intermediate text to apply deltas to
1776 basetext = None
1776 basetext = None
1777
1777
1778 # Check if we have the entry in cache
1778 # Check if we have the entry in cache
1779 # The cache entry looks like (node, rev, rawtext)
1779 # The cache entry looks like (node, rev, rawtext)
1780 if self._revisioncache:
1780 if self._revisioncache:
1781 if self._revisioncache[0] == node:
1781 if self._revisioncache[0] == node:
1782 return (rev, self._revisioncache[2], True)
1782 return (rev, self._revisioncache[2], True)
1783 cachedrev = self._revisioncache[1]
1783 cachedrev = self._revisioncache[1]
1784
1784
1785 if rev is None:
1785 if rev is None:
1786 rev = self.rev(node)
1786 rev = self.rev(node)
1787
1787
1788 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1788 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1789 if stopped:
1789 if stopped:
1790 basetext = self._revisioncache[2]
1790 basetext = self._revisioncache[2]
1791
1791
1792 # drop cache to save memory, the caller is expected to
1792 # drop cache to save memory, the caller is expected to
1793 # update self._revisioncache after validating the text
1793 # update self._revisioncache after validating the text
1794 self._revisioncache = None
1794 self._revisioncache = None
1795
1795
1796 targetsize = None
1796 targetsize = None
1797 rawsize = self.index[rev][2]
1797 rawsize = self.index[rev][2]
1798 if 0 <= rawsize:
1798 if 0 <= rawsize:
1799 targetsize = 4 * rawsize
1799 targetsize = 4 * rawsize
1800
1800
1801 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1801 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1802 if basetext is None:
1802 if basetext is None:
1803 basetext = bytes(bins[0])
1803 basetext = bytes(bins[0])
1804 bins = bins[1:]
1804 bins = bins[1:]
1805
1805
1806 rawtext = mdiff.patches(basetext, bins)
1806 rawtext = mdiff.patches(basetext, bins)
1807 del basetext # let us have a chance to free memory early
1807 del basetext # let us have a chance to free memory early
1808 return (rev, rawtext, False)
1808 return (rev, rawtext, False)
1809
1809
1810 def rawdata(self, nodeorrev, _df=None):
1810 def rawdata(self, nodeorrev, _df=None):
1811 """return an uncompressed raw data of a given node or revision number.
1811 """return an uncompressed raw data of a given node or revision number.
1812
1812
1813 _df - an existing file handle to read from. (internal-only)
1813 _df - an existing file handle to read from. (internal-only)
1814 """
1814 """
1815 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1815 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1816
1816
1817 def hash(self, text, p1, p2):
1817 def hash(self, text, p1, p2):
1818 """Compute a node hash.
1818 """Compute a node hash.
1819
1819
1820 Available as a function so that subclasses can replace the hash
1820 Available as a function so that subclasses can replace the hash
1821 as needed.
1821 as needed.
1822 """
1822 """
1823 return storageutil.hashrevisionsha1(text, p1, p2)
1823 return storageutil.hashrevisionsha1(text, p1, p2)
1824
1824
1825 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1825 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1826 """Check node hash integrity.
1826 """Check node hash integrity.
1827
1827
1828 Available as a function so that subclasses can extend hash mismatch
1828 Available as a function so that subclasses can extend hash mismatch
1829 behaviors as needed.
1829 behaviors as needed.
1830 """
1830 """
1831 try:
1831 try:
1832 if p1 is None and p2 is None:
1832 if p1 is None and p2 is None:
1833 p1, p2 = self.parents(node)
1833 p1, p2 = self.parents(node)
1834 if node != self.hash(text, p1, p2):
1834 if node != self.hash(text, p1, p2):
1835 # Clear the revision cache on hash failure. The revision cache
1835 # Clear the revision cache on hash failure. The revision cache
1836 # only stores the raw revision and clearing the cache does have
1836 # only stores the raw revision and clearing the cache does have
1837 # the side-effect that we won't have a cache hit when the raw
1837 # the side-effect that we won't have a cache hit when the raw
1838 # revision data is accessed. But this case should be rare and
1838 # revision data is accessed. But this case should be rare and
1839 # it is extra work to teach the cache about the hash
1839 # it is extra work to teach the cache about the hash
1840 # verification state.
1840 # verification state.
1841 if self._revisioncache and self._revisioncache[0] == node:
1841 if self._revisioncache and self._revisioncache[0] == node:
1842 self._revisioncache = None
1842 self._revisioncache = None
1843
1843
1844 revornode = rev
1844 revornode = rev
1845 if revornode is None:
1845 if revornode is None:
1846 revornode = templatefilters.short(hex(node))
1846 revornode = templatefilters.short(hex(node))
1847 raise error.RevlogError(
1847 raise error.RevlogError(
1848 _(b"integrity check failed on %s:%s")
1848 _(b"integrity check failed on %s:%s")
1849 % (self.indexfile, pycompat.bytestr(revornode))
1849 % (self.indexfile, pycompat.bytestr(revornode))
1850 )
1850 )
1851 except error.RevlogError:
1851 except error.RevlogError:
1852 if self._censorable and storageutil.iscensoredtext(text):
1852 if self._censorable and storageutil.iscensoredtext(text):
1853 raise error.CensoredNodeError(self.indexfile, node, text)
1853 raise error.CensoredNodeError(self.indexfile, node, text)
1854 raise
1854 raise
1855
1855
1856 def _enforceinlinesize(self, tr, fp=None):
1856 def _enforceinlinesize(self, tr, fp=None):
1857 """Check if the revlog is too big for inline and convert if so.
1857 """Check if the revlog is too big for inline and convert if so.
1858
1858
1859 This should be called after revisions are added to the revlog. If the
1859 This should be called after revisions are added to the revlog. If the
1860 revlog has grown too large to be an inline revlog, it will convert it
1860 revlog has grown too large to be an inline revlog, it will convert it
1861 to use multiple index and data files.
1861 to use multiple index and data files.
1862 """
1862 """
1863 tiprev = len(self) - 1
1863 tiprev = len(self) - 1
1864 if (
1864 if (
1865 not self._inline
1865 not self._inline
1866 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
1866 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
1867 ):
1867 ):
1868 return
1868 return
1869
1869
1870 trinfo = tr.find(self.indexfile)
1870 trinfo = tr.find(self.indexfile)
1871 if trinfo is None:
1871 if trinfo is None:
1872 raise error.RevlogError(
1872 raise error.RevlogError(
1873 _(b"%s not found in the transaction") % self.indexfile
1873 _(b"%s not found in the transaction") % self.indexfile
1874 )
1874 )
1875
1875
1876 trindex = trinfo[2]
1876 trindex = trinfo[2]
1877 if trindex is not None:
1877 if trindex is not None:
1878 dataoff = self.start(trindex)
1878 dataoff = self.start(trindex)
1879 else:
1879 else:
1880 # revlog was stripped at start of transaction, use all leftover data
1880 # revlog was stripped at start of transaction, use all leftover data
1881 trindex = len(self) - 1
1881 trindex = len(self) - 1
1882 dataoff = self.end(tiprev)
1882 dataoff = self.end(tiprev)
1883
1883
1884 tr.add(self.datafile, dataoff)
1884 tr.add(self.datafile, dataoff)
1885
1885
1886 if fp:
1886 if fp:
1887 fp.flush()
1887 fp.flush()
1888 fp.close()
1888 fp.close()
1889 # We can't use the cached file handle after close(). So prevent
1889 # We can't use the cached file handle after close(). So prevent
1890 # its usage.
1890 # its usage.
1891 self._writinghandles = None
1891 self._writinghandles = None
1892
1892
1893 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
1893 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
1894 for r in self:
1894 for r in self:
1895 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1895 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1896
1896
1897 with self._indexfp(b'w') as fp:
1897 with self._indexfp(b'w') as fp:
1898 self.version &= ~FLAG_INLINE_DATA
1898 self.version &= ~FLAG_INLINE_DATA
1899 self._inline = False
1899 self._inline = False
1900 io = self._io
1900 io = self._io
1901 for i in self:
1901 for i in self:
1902 e = io.packentry(self.index[i], self.node, self.version, i)
1902 e = io.packentry(self.index[i], self.node, self.version, i)
1903 fp.write(e)
1903 fp.write(e)
1904
1904
1905 # the temp file replace the real index when we exit the context
1905 # the temp file replace the real index when we exit the context
1906 # manager
1906 # manager
1907
1907
1908 tr.replace(self.indexfile, trindex * self._io.size)
1908 tr.replace(self.indexfile, trindex * self._io.size)
1909 self._chunkclear()
1909 self._chunkclear()
1910
1910
1911 def _nodeduplicatecallback(self, transaction, node):
1911 def _nodeduplicatecallback(self, transaction, node):
1912 """called when trying to add a node already stored.
1912 """called when trying to add a node already stored.
1913 """
1913 """
1914
1914
1915 def addrevision(
1915 def addrevision(
1916 self,
1916 self,
1917 text,
1917 text,
1918 transaction,
1918 transaction,
1919 link,
1919 link,
1920 p1,
1920 p1,
1921 p2,
1921 p2,
1922 cachedelta=None,
1922 cachedelta=None,
1923 node=None,
1923 node=None,
1924 flags=REVIDX_DEFAULT_FLAGS,
1924 flags=REVIDX_DEFAULT_FLAGS,
1925 deltacomputer=None,
1925 deltacomputer=None,
1926 sidedata=None,
1926 sidedata=None,
1927 ):
1927 ):
1928 """add a revision to the log
1928 """add a revision to the log
1929
1929
1930 text - the revision data to add
1930 text - the revision data to add
1931 transaction - the transaction object used for rollback
1931 transaction - the transaction object used for rollback
1932 link - the linkrev data to add
1932 link - the linkrev data to add
1933 p1, p2 - the parent nodeids of the revision
1933 p1, p2 - the parent nodeids of the revision
1934 cachedelta - an optional precomputed delta
1934 cachedelta - an optional precomputed delta
1935 node - nodeid of revision; typically node is not specified, and it is
1935 node - nodeid of revision; typically node is not specified, and it is
1936 computed by default as hash(text, p1, p2), however subclasses might
1936 computed by default as hash(text, p1, p2), however subclasses might
1937 use different hashing method (and override checkhash() in such case)
1937 use different hashing method (and override checkhash() in such case)
1938 flags - the known flags to set on the revision
1938 flags - the known flags to set on the revision
1939 deltacomputer - an optional deltacomputer instance shared between
1939 deltacomputer - an optional deltacomputer instance shared between
1940 multiple calls
1940 multiple calls
1941 """
1941 """
1942 if link == nullrev:
1942 if link == nullrev:
1943 raise error.RevlogError(
1943 raise error.RevlogError(
1944 _(b"attempted to add linkrev -1 to %s") % self.indexfile
1944 _(b"attempted to add linkrev -1 to %s") % self.indexfile
1945 )
1945 )
1946
1946
1947 if sidedata is None:
1947 if sidedata is None:
1948 sidedata = {}
1948 sidedata = {}
1949 flags = flags & ~REVIDX_SIDEDATA
1949 flags = flags & ~REVIDX_SIDEDATA
1950 elif not self.hassidedata:
1950 elif not self.hassidedata:
1951 raise error.ProgrammingError(
1951 raise error.ProgrammingError(
1952 _(b"trying to add sidedata to a revlog who don't support them")
1952 _(b"trying to add sidedata to a revlog who don't support them")
1953 )
1953 )
1954 else:
1954 else:
1955 flags |= REVIDX_SIDEDATA
1955 flags |= REVIDX_SIDEDATA
1956
1956
1957 if flags:
1957 if flags:
1958 node = node or self.hash(text, p1, p2)
1958 node = node or self.hash(text, p1, p2)
1959
1959
1960 rawtext, validatehash = flagutil.processflagswrite(
1960 rawtext, validatehash = flagutil.processflagswrite(
1961 self, text, flags, sidedata=sidedata
1961 self, text, flags, sidedata=sidedata
1962 )
1962 )
1963
1963
1964 # If the flag processor modifies the revision data, ignore any provided
1964 # If the flag processor modifies the revision data, ignore any provided
1965 # cachedelta.
1965 # cachedelta.
1966 if rawtext != text:
1966 if rawtext != text:
1967 cachedelta = None
1967 cachedelta = None
1968
1968
1969 if len(rawtext) > _maxentrysize:
1969 if len(rawtext) > _maxentrysize:
1970 raise error.RevlogError(
1970 raise error.RevlogError(
1971 _(
1971 _(
1972 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
1972 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
1973 )
1973 )
1974 % (self.indexfile, len(rawtext))
1974 % (self.indexfile, len(rawtext))
1975 )
1975 )
1976
1976
1977 node = node or self.hash(rawtext, p1, p2)
1977 node = node or self.hash(rawtext, p1, p2)
1978 if node in self.nodemap:
1978 if node in self.nodemap:
1979 return node
1979 return node
1980
1980
1981 if validatehash:
1981 if validatehash:
1982 self.checkhash(rawtext, node, p1=p1, p2=p2)
1982 self.checkhash(rawtext, node, p1=p1, p2=p2)
1983
1983
1984 return self.addrawrevision(
1984 return self.addrawrevision(
1985 rawtext,
1985 rawtext,
1986 transaction,
1986 transaction,
1987 link,
1987 link,
1988 p1,
1988 p1,
1989 p2,
1989 p2,
1990 node,
1990 node,
1991 flags,
1991 flags,
1992 cachedelta=cachedelta,
1992 cachedelta=cachedelta,
1993 deltacomputer=deltacomputer,
1993 deltacomputer=deltacomputer,
1994 )
1994 )
1995
1995
1996 def addrawrevision(
1996 def addrawrevision(
1997 self,
1997 self,
1998 rawtext,
1998 rawtext,
1999 transaction,
1999 transaction,
2000 link,
2000 link,
2001 p1,
2001 p1,
2002 p2,
2002 p2,
2003 node,
2003 node,
2004 flags,
2004 flags,
2005 cachedelta=None,
2005 cachedelta=None,
2006 deltacomputer=None,
2006 deltacomputer=None,
2007 ):
2007 ):
2008 """add a raw revision with known flags, node and parents
2008 """add a raw revision with known flags, node and parents
2009 useful when reusing a revision not stored in this revlog (ex: received
2009 useful when reusing a revision not stored in this revlog (ex: received
2010 over wire, or read from an external bundle).
2010 over wire, or read from an external bundle).
2011 """
2011 """
2012 dfh = None
2012 dfh = None
2013 if not self._inline:
2013 if not self._inline:
2014 dfh = self._datafp(b"a+")
2014 dfh = self._datafp(b"a+")
2015 ifh = self._indexfp(b"a+")
2015 ifh = self._indexfp(b"a+")
2016 try:
2016 try:
2017 return self._addrevision(
2017 return self._addrevision(
2018 node,
2018 node,
2019 rawtext,
2019 rawtext,
2020 transaction,
2020 transaction,
2021 link,
2021 link,
2022 p1,
2022 p1,
2023 p2,
2023 p2,
2024 flags,
2024 flags,
2025 cachedelta,
2025 cachedelta,
2026 ifh,
2026 ifh,
2027 dfh,
2027 dfh,
2028 deltacomputer=deltacomputer,
2028 deltacomputer=deltacomputer,
2029 )
2029 )
2030 finally:
2030 finally:
2031 if dfh:
2031 if dfh:
2032 dfh.close()
2032 dfh.close()
2033 ifh.close()
2033 ifh.close()
2034
2034
2035 def compress(self, data):
2035 def compress(self, data):
2036 """Generate a possibly-compressed representation of data."""
2036 """Generate a possibly-compressed representation of data."""
2037 if not data:
2037 if not data:
2038 return b'', data
2038 return b'', data
2039
2039
2040 compressed = self._compressor.compress(data)
2040 compressed = self._compressor.compress(data)
2041
2041
2042 if compressed:
2042 if compressed:
2043 # The revlog compressor added the header in the returned data.
2043 # The revlog compressor added the header in the returned data.
2044 return b'', compressed
2044 return b'', compressed
2045
2045
2046 if data[0:1] == b'\0':
2046 if data[0:1] == b'\0':
2047 return b'', data
2047 return b'', data
2048 return b'u', data
2048 return b'u', data
2049
2049
2050 def decompress(self, data):
2050 def decompress(self, data):
2051 """Decompress a revlog chunk.
2051 """Decompress a revlog chunk.
2052
2052
2053 The chunk is expected to begin with a header identifying the
2053 The chunk is expected to begin with a header identifying the
2054 format type so it can be routed to an appropriate decompressor.
2054 format type so it can be routed to an appropriate decompressor.
2055 """
2055 """
2056 if not data:
2056 if not data:
2057 return data
2057 return data
2058
2058
2059 # Revlogs are read much more frequently than they are written and many
2059 # Revlogs are read much more frequently than they are written and many
2060 # chunks only take microseconds to decompress, so performance is
2060 # chunks only take microseconds to decompress, so performance is
2061 # important here.
2061 # important here.
2062 #
2062 #
2063 # We can make a few assumptions about revlogs:
2063 # We can make a few assumptions about revlogs:
2064 #
2064 #
2065 # 1) the majority of chunks will be compressed (as opposed to inline
2065 # 1) the majority of chunks will be compressed (as opposed to inline
2066 # raw data).
2066 # raw data).
2067 # 2) decompressing *any* data will likely by at least 10x slower than
2067 # 2) decompressing *any* data will likely by at least 10x slower than
2068 # returning raw inline data.
2068 # returning raw inline data.
2069 # 3) we want to prioritize common and officially supported compression
2069 # 3) we want to prioritize common and officially supported compression
2070 # engines
2070 # engines
2071 #
2071 #
2072 # It follows that we want to optimize for "decompress compressed data
2072 # It follows that we want to optimize for "decompress compressed data
2073 # when encoded with common and officially supported compression engines"
2073 # when encoded with common and officially supported compression engines"
2074 # case over "raw data" and "data encoded by less common or non-official
2074 # case over "raw data" and "data encoded by less common or non-official
2075 # compression engines." That is why we have the inline lookup first
2075 # compression engines." That is why we have the inline lookup first
2076 # followed by the compengines lookup.
2076 # followed by the compengines lookup.
2077 #
2077 #
2078 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2078 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2079 # compressed chunks. And this matters for changelog and manifest reads.
2079 # compressed chunks. And this matters for changelog and manifest reads.
2080 t = data[0:1]
2080 t = data[0:1]
2081
2081
2082 if t == b'x':
2082 if t == b'x':
2083 try:
2083 try:
2084 return _zlibdecompress(data)
2084 return _zlibdecompress(data)
2085 except zlib.error as e:
2085 except zlib.error as e:
2086 raise error.RevlogError(
2086 raise error.RevlogError(
2087 _(b'revlog decompress error: %s')
2087 _(b'revlog decompress error: %s')
2088 % stringutil.forcebytestr(e)
2088 % stringutil.forcebytestr(e)
2089 )
2089 )
2090 # '\0' is more common than 'u' so it goes first.
2090 # '\0' is more common than 'u' so it goes first.
2091 elif t == b'\0':
2091 elif t == b'\0':
2092 return data
2092 return data
2093 elif t == b'u':
2093 elif t == b'u':
2094 return util.buffer(data, 1)
2094 return util.buffer(data, 1)
2095
2095
2096 try:
2096 try:
2097 compressor = self._decompressors[t]
2097 compressor = self._decompressors[t]
2098 except KeyError:
2098 except KeyError:
2099 try:
2099 try:
2100 engine = util.compengines.forrevlogheader(t)
2100 engine = util.compengines.forrevlogheader(t)
2101 compressor = engine.revlogcompressor(self._compengineopts)
2101 compressor = engine.revlogcompressor(self._compengineopts)
2102 self._decompressors[t] = compressor
2102 self._decompressors[t] = compressor
2103 except KeyError:
2103 except KeyError:
2104 raise error.RevlogError(_(b'unknown compression type %r') % t)
2104 raise error.RevlogError(_(b'unknown compression type %r') % t)
2105
2105
2106 return compressor.decompress(data)
2106 return compressor.decompress(data)
2107
2107
2108 def _addrevision(
2108 def _addrevision(
2109 self,
2109 self,
2110 node,
2110 node,
2111 rawtext,
2111 rawtext,
2112 transaction,
2112 transaction,
2113 link,
2113 link,
2114 p1,
2114 p1,
2115 p2,
2115 p2,
2116 flags,
2116 flags,
2117 cachedelta,
2117 cachedelta,
2118 ifh,
2118 ifh,
2119 dfh,
2119 dfh,
2120 alwayscache=False,
2120 alwayscache=False,
2121 deltacomputer=None,
2121 deltacomputer=None,
2122 ):
2122 ):
2123 """internal function to add revisions to the log
2123 """internal function to add revisions to the log
2124
2124
2125 see addrevision for argument descriptions.
2125 see addrevision for argument descriptions.
2126
2126
2127 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2127 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2128
2128
2129 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2129 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2130 be used.
2130 be used.
2131
2131
2132 invariants:
2132 invariants:
2133 - rawtext is optional (can be None); if not set, cachedelta must be set.
2133 - rawtext is optional (can be None); if not set, cachedelta must be set.
2134 if both are set, they must correspond to each other.
2134 if both are set, they must correspond to each other.
2135 """
2135 """
2136 if node == nullid:
2136 if node == nullid:
2137 raise error.RevlogError(
2137 raise error.RevlogError(
2138 _(b"%s: attempt to add null revision") % self.indexfile
2138 _(b"%s: attempt to add null revision") % self.indexfile
2139 )
2139 )
2140 if node == wdirid or node in wdirfilenodeids:
2140 if node == wdirid or node in wdirfilenodeids:
2141 raise error.RevlogError(
2141 raise error.RevlogError(
2142 _(b"%s: attempt to add wdir revision") % self.indexfile
2142 _(b"%s: attempt to add wdir revision") % self.indexfile
2143 )
2143 )
2144
2144
2145 if self._inline:
2145 if self._inline:
2146 fh = ifh
2146 fh = ifh
2147 else:
2147 else:
2148 fh = dfh
2148 fh = dfh
2149
2149
2150 btext = [rawtext]
2150 btext = [rawtext]
2151
2151
2152 curr = len(self)
2152 curr = len(self)
2153 prev = curr - 1
2153 prev = curr - 1
2154 offset = self.end(prev)
2154 offset = self.end(prev)
2155 p1r, p2r = self.rev(p1), self.rev(p2)
2155 p1r, p2r = self.rev(p1), self.rev(p2)
2156
2156
2157 # full versions are inserted when the needed deltas
2157 # full versions are inserted when the needed deltas
2158 # become comparable to the uncompressed text
2158 # become comparable to the uncompressed text
2159 if rawtext is None:
2159 if rawtext is None:
2160 # need rawtext size, before changed by flag processors, which is
2160 # need rawtext size, before changed by flag processors, which is
2161 # the non-raw size. use revlog explicitly to avoid filelog's extra
2161 # the non-raw size. use revlog explicitly to avoid filelog's extra
2162 # logic that might remove metadata size.
2162 # logic that might remove metadata size.
2163 textlen = mdiff.patchedsize(
2163 textlen = mdiff.patchedsize(
2164 revlog.size(self, cachedelta[0]), cachedelta[1]
2164 revlog.size(self, cachedelta[0]), cachedelta[1]
2165 )
2165 )
2166 else:
2166 else:
2167 textlen = len(rawtext)
2167 textlen = len(rawtext)
2168
2168
2169 if deltacomputer is None:
2169 if deltacomputer is None:
2170 deltacomputer = deltautil.deltacomputer(self)
2170 deltacomputer = deltautil.deltacomputer(self)
2171
2171
2172 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2172 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2173
2173
2174 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2174 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2175
2175
2176 e = (
2176 e = (
2177 offset_type(offset, flags),
2177 offset_type(offset, flags),
2178 deltainfo.deltalen,
2178 deltainfo.deltalen,
2179 textlen,
2179 textlen,
2180 deltainfo.base,
2180 deltainfo.base,
2181 link,
2181 link,
2182 p1r,
2182 p1r,
2183 p2r,
2183 p2r,
2184 node,
2184 node,
2185 )
2185 )
2186 self.index.append(e)
2186 self.index.append(e)
2187 self.nodemap[node] = curr
2187 self.nodemap[node] = curr
2188
2188
2189 # Reset the pure node cache start lookup offset to account for new
2189 # Reset the pure node cache start lookup offset to account for new
2190 # revision.
2190 # revision.
2191 if self._nodepos is not None:
2191 if self._nodepos is not None:
2192 self._nodepos = curr
2192 self._nodepos = curr
2193
2193
2194 entry = self._io.packentry(e, self.node, self.version, curr)
2194 entry = self._io.packentry(e, self.node, self.version, curr)
2195 self._writeentry(
2195 self._writeentry(
2196 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2196 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2197 )
2197 )
2198
2198
2199 rawtext = btext[0]
2199 rawtext = btext[0]
2200
2200
2201 if alwayscache and rawtext is None:
2201 if alwayscache and rawtext is None:
2202 rawtext = deltacomputer.buildtext(revinfo, fh)
2202 rawtext = deltacomputer.buildtext(revinfo, fh)
2203
2203
2204 if type(rawtext) == bytes: # only accept immutable objects
2204 if type(rawtext) == bytes: # only accept immutable objects
2205 self._revisioncache = (node, curr, rawtext)
2205 self._revisioncache = (node, curr, rawtext)
2206 self._chainbasecache[curr] = deltainfo.chainbase
2206 self._chainbasecache[curr] = deltainfo.chainbase
2207 return node
2207 return node
2208
2208
2209 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2209 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2210 # Files opened in a+ mode have inconsistent behavior on various
2210 # Files opened in a+ mode have inconsistent behavior on various
2211 # platforms. Windows requires that a file positioning call be made
2211 # platforms. Windows requires that a file positioning call be made
2212 # when the file handle transitions between reads and writes. See
2212 # when the file handle transitions between reads and writes. See
2213 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2213 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2214 # platforms, Python or the platform itself can be buggy. Some versions
2214 # platforms, Python or the platform itself can be buggy. Some versions
2215 # of Solaris have been observed to not append at the end of the file
2215 # of Solaris have been observed to not append at the end of the file
2216 # if the file was seeked to before the end. See issue4943 for more.
2216 # if the file was seeked to before the end. See issue4943 for more.
2217 #
2217 #
2218 # We work around this issue by inserting a seek() before writing.
2218 # We work around this issue by inserting a seek() before writing.
2219 # Note: This is likely not necessary on Python 3. However, because
2219 # Note: This is likely not necessary on Python 3. However, because
2220 # the file handle is reused for reads and may be seeked there, we need
2220 # the file handle is reused for reads and may be seeked there, we need
2221 # to be careful before changing this.
2221 # to be careful before changing this.
2222 ifh.seek(0, os.SEEK_END)
2222 ifh.seek(0, os.SEEK_END)
2223 if dfh:
2223 if dfh:
2224 dfh.seek(0, os.SEEK_END)
2224 dfh.seek(0, os.SEEK_END)
2225
2225
2226 curr = len(self) - 1
2226 curr = len(self) - 1
2227 if not self._inline:
2227 if not self._inline:
2228 transaction.add(self.datafile, offset)
2228 transaction.add(self.datafile, offset)
2229 transaction.add(self.indexfile, curr * len(entry))
2229 transaction.add(self.indexfile, curr * len(entry))
2230 if data[0]:
2230 if data[0]:
2231 dfh.write(data[0])
2231 dfh.write(data[0])
2232 dfh.write(data[1])
2232 dfh.write(data[1])
2233 ifh.write(entry)
2233 ifh.write(entry)
2234 else:
2234 else:
2235 offset += curr * self._io.size
2235 offset += curr * self._io.size
2236 transaction.add(self.indexfile, offset, curr)
2236 transaction.add(self.indexfile, offset, curr)
2237 ifh.write(entry)
2237 ifh.write(entry)
2238 ifh.write(data[0])
2238 ifh.write(data[0])
2239 ifh.write(data[1])
2239 ifh.write(data[1])
2240 self._enforceinlinesize(transaction, ifh)
2240 self._enforceinlinesize(transaction, ifh)
2241
2241
2242 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2242 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2243 """
2243 """
2244 add a delta group
2244 add a delta group
2245
2245
2246 given a set of deltas, add them to the revision log. the
2246 given a set of deltas, add them to the revision log. the
2247 first delta is against its parent, which should be in our
2247 first delta is against its parent, which should be in our
2248 log, the rest are against the previous delta.
2248 log, the rest are against the previous delta.
2249
2249
2250 If ``addrevisioncb`` is defined, it will be called with arguments of
2250 If ``addrevisioncb`` is defined, it will be called with arguments of
2251 this revlog and the node that was added.
2251 this revlog and the node that was added.
2252 """
2252 """
2253
2253
2254 if self._writinghandles:
2254 if self._writinghandles:
2255 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2255 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2256
2256
2257 nodes = []
2257 nodes = []
2258
2258
2259 r = len(self)
2259 r = len(self)
2260 end = 0
2260 end = 0
2261 if r:
2261 if r:
2262 end = self.end(r - 1)
2262 end = self.end(r - 1)
2263 ifh = self._indexfp(b"a+")
2263 ifh = self._indexfp(b"a+")
2264 isize = r * self._io.size
2264 isize = r * self._io.size
2265 if self._inline:
2265 if self._inline:
2266 transaction.add(self.indexfile, end + isize, r)
2266 transaction.add(self.indexfile, end + isize, r)
2267 dfh = None
2267 dfh = None
2268 else:
2268 else:
2269 transaction.add(self.indexfile, isize, r)
2269 transaction.add(self.indexfile, isize, r)
2270 transaction.add(self.datafile, end)
2270 transaction.add(self.datafile, end)
2271 dfh = self._datafp(b"a+")
2271 dfh = self._datafp(b"a+")
2272
2272
2273 def flush():
2273 def flush():
2274 if dfh:
2274 if dfh:
2275 dfh.flush()
2275 dfh.flush()
2276 ifh.flush()
2276 ifh.flush()
2277
2277
2278 self._writinghandles = (ifh, dfh)
2278 self._writinghandles = (ifh, dfh)
2279
2279
2280 try:
2280 try:
2281 deltacomputer = deltautil.deltacomputer(self)
2281 deltacomputer = deltautil.deltacomputer(self)
2282 # loop through our set of deltas
2282 # loop through our set of deltas
2283 for data in deltas:
2283 for data in deltas:
2284 node, p1, p2, linknode, deltabase, delta, flags = data
2284 node, p1, p2, linknode, deltabase, delta, flags = data
2285 link = linkmapper(linknode)
2285 link = linkmapper(linknode)
2286 flags = flags or REVIDX_DEFAULT_FLAGS
2286 flags = flags or REVIDX_DEFAULT_FLAGS
2287
2287
2288 nodes.append(node)
2288 nodes.append(node)
2289
2289
2290 if node in self.nodemap:
2290 if node in self.nodemap:
2291 self._nodeduplicatecallback(transaction, node)
2291 self._nodeduplicatecallback(transaction, node)
2292 # this can happen if two branches make the same change
2292 # this can happen if two branches make the same change
2293 continue
2293 continue
2294
2294
2295 for p in (p1, p2):
2295 for p in (p1, p2):
2296 if p not in self.nodemap:
2296 if p not in self.nodemap:
2297 raise error.LookupError(
2297 raise error.LookupError(
2298 p, self.indexfile, _(b'unknown parent')
2298 p, self.indexfile, _(b'unknown parent')
2299 )
2299 )
2300
2300
2301 if deltabase not in self.nodemap:
2301 if deltabase not in self.nodemap:
2302 raise error.LookupError(
2302 raise error.LookupError(
2303 deltabase, self.indexfile, _(b'unknown delta base')
2303 deltabase, self.indexfile, _(b'unknown delta base')
2304 )
2304 )
2305
2305
2306 baserev = self.rev(deltabase)
2306 baserev = self.rev(deltabase)
2307
2307
2308 if baserev != nullrev and self.iscensored(baserev):
2308 if baserev != nullrev and self.iscensored(baserev):
2309 # if base is censored, delta must be full replacement in a
2309 # if base is censored, delta must be full replacement in a
2310 # single patch operation
2310 # single patch operation
2311 hlen = struct.calcsize(b">lll")
2311 hlen = struct.calcsize(b">lll")
2312 oldlen = self.rawsize(baserev)
2312 oldlen = self.rawsize(baserev)
2313 newlen = len(delta) - hlen
2313 newlen = len(delta) - hlen
2314 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2314 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2315 raise error.CensoredBaseError(
2315 raise error.CensoredBaseError(
2316 self.indexfile, self.node(baserev)
2316 self.indexfile, self.node(baserev)
2317 )
2317 )
2318
2318
2319 if not flags and self._peek_iscensored(baserev, delta, flush):
2319 if not flags and self._peek_iscensored(baserev, delta, flush):
2320 flags |= REVIDX_ISCENSORED
2320 flags |= REVIDX_ISCENSORED
2321
2321
2322 # We assume consumers of addrevisioncb will want to retrieve
2322 # We assume consumers of addrevisioncb will want to retrieve
2323 # the added revision, which will require a call to
2323 # the added revision, which will require a call to
2324 # revision(). revision() will fast path if there is a cache
2324 # revision(). revision() will fast path if there is a cache
2325 # hit. So, we tell _addrevision() to always cache in this case.
2325 # hit. So, we tell _addrevision() to always cache in this case.
2326 # We're only using addgroup() in the context of changegroup
2326 # We're only using addgroup() in the context of changegroup
2327 # generation so the revision data can always be handled as raw
2327 # generation so the revision data can always be handled as raw
2328 # by the flagprocessor.
2328 # by the flagprocessor.
2329 self._addrevision(
2329 self._addrevision(
2330 node,
2330 node,
2331 None,
2331 None,
2332 transaction,
2332 transaction,
2333 link,
2333 link,
2334 p1,
2334 p1,
2335 p2,
2335 p2,
2336 flags,
2336 flags,
2337 (baserev, delta),
2337 (baserev, delta),
2338 ifh,
2338 ifh,
2339 dfh,
2339 dfh,
2340 alwayscache=bool(addrevisioncb),
2340 alwayscache=bool(addrevisioncb),
2341 deltacomputer=deltacomputer,
2341 deltacomputer=deltacomputer,
2342 )
2342 )
2343
2343
2344 if addrevisioncb:
2344 if addrevisioncb:
2345 addrevisioncb(self, node)
2345 addrevisioncb(self, node)
2346
2346
2347 if not dfh and not self._inline:
2347 if not dfh and not self._inline:
2348 # addrevision switched from inline to conventional
2348 # addrevision switched from inline to conventional
2349 # reopen the index
2349 # reopen the index
2350 ifh.close()
2350 ifh.close()
2351 dfh = self._datafp(b"a+")
2351 dfh = self._datafp(b"a+")
2352 ifh = self._indexfp(b"a+")
2352 ifh = self._indexfp(b"a+")
2353 self._writinghandles = (ifh, dfh)
2353 self._writinghandles = (ifh, dfh)
2354 finally:
2354 finally:
2355 self._writinghandles = None
2355 self._writinghandles = None
2356
2356
2357 if dfh:
2357 if dfh:
2358 dfh.close()
2358 dfh.close()
2359 ifh.close()
2359 ifh.close()
2360
2360
2361 return nodes
2361 return nodes
2362
2362
2363 def iscensored(self, rev):
2363 def iscensored(self, rev):
2364 """Check if a file revision is censored."""
2364 """Check if a file revision is censored."""
2365 if not self._censorable:
2365 if not self._censorable:
2366 return False
2366 return False
2367
2367
2368 return self.flags(rev) & REVIDX_ISCENSORED
2368 return self.flags(rev) & REVIDX_ISCENSORED
2369
2369
2370 def _peek_iscensored(self, baserev, delta, flush):
2370 def _peek_iscensored(self, baserev, delta, flush):
2371 """Quickly check if a delta produces a censored revision."""
2371 """Quickly check if a delta produces a censored revision."""
2372 if not self._censorable:
2372 if not self._censorable:
2373 return False
2373 return False
2374
2374
2375 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2375 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2376
2376
2377 def getstrippoint(self, minlink):
2377 def getstrippoint(self, minlink):
2378 """find the minimum rev that must be stripped to strip the linkrev
2378 """find the minimum rev that must be stripped to strip the linkrev
2379
2379
2380 Returns a tuple containing the minimum rev and a set of all revs that
2380 Returns a tuple containing the minimum rev and a set of all revs that
2381 have linkrevs that will be broken by this strip.
2381 have linkrevs that will be broken by this strip.
2382 """
2382 """
2383 return storageutil.resolvestripinfo(
2383 return storageutil.resolvestripinfo(
2384 minlink,
2384 minlink,
2385 len(self) - 1,
2385 len(self) - 1,
2386 self.headrevs(),
2386 self.headrevs(),
2387 self.linkrev,
2387 self.linkrev,
2388 self.parentrevs,
2388 self.parentrevs,
2389 )
2389 )
2390
2390
2391 def strip(self, minlink, transaction):
2391 def strip(self, minlink, transaction):
2392 """truncate the revlog on the first revision with a linkrev >= minlink
2392 """truncate the revlog on the first revision with a linkrev >= minlink
2393
2393
2394 This function is called when we're stripping revision minlink and
2394 This function is called when we're stripping revision minlink and
2395 its descendants from the repository.
2395 its descendants from the repository.
2396
2396
2397 We have to remove all revisions with linkrev >= minlink, because
2397 We have to remove all revisions with linkrev >= minlink, because
2398 the equivalent changelog revisions will be renumbered after the
2398 the equivalent changelog revisions will be renumbered after the
2399 strip.
2399 strip.
2400
2400
2401 So we truncate the revlog on the first of these revisions, and
2401 So we truncate the revlog on the first of these revisions, and
2402 trust that the caller has saved the revisions that shouldn't be
2402 trust that the caller has saved the revisions that shouldn't be
2403 removed and that it'll re-add them after this truncation.
2403 removed and that it'll re-add them after this truncation.
2404 """
2404 """
2405 if len(self) == 0:
2405 if len(self) == 0:
2406 return
2406 return
2407
2407
2408 rev, _ = self.getstrippoint(minlink)
2408 rev, _ = self.getstrippoint(minlink)
2409 if rev == len(self):
2409 if rev == len(self):
2410 return
2410 return
2411
2411
2412 # first truncate the files on disk
2412 # first truncate the files on disk
2413 end = self.start(rev)
2413 end = self.start(rev)
2414 if not self._inline:
2414 if not self._inline:
2415 transaction.add(self.datafile, end)
2415 transaction.add(self.datafile, end)
2416 end = rev * self._io.size
2416 end = rev * self._io.size
2417 else:
2417 else:
2418 end += rev * self._io.size
2418 end += rev * self._io.size
2419
2419
2420 transaction.add(self.indexfile, end)
2420 transaction.add(self.indexfile, end)
2421
2421
2422 # then reset internal state in memory to forget those revisions
2422 # then reset internal state in memory to forget those revisions
2423 self._revisioncache = None
2423 self._revisioncache = None
2424 self._chaininfocache = {}
2424 self._chaininfocache = {}
2425 self._chunkclear()
2425 self._chunkclear()
2426 for x in pycompat.xrange(rev, len(self)):
2426 for x in pycompat.xrange(rev, len(self)):
2427 del self.nodemap[self.node(x)]
2427 del self.nodemap[self.node(x)]
2428
2428
2429 del self.index[rev:-1]
2429 del self.index[rev:-1]
2430 self._nodepos = None
2430 self._nodepos = None
2431
2431
2432 def checksize(self):
2432 def checksize(self):
2433 """Check size of index and data files
2433 """Check size of index and data files
2434
2434
2435 return a (dd, di) tuple.
2435 return a (dd, di) tuple.
2436 - dd: extra bytes for the "data" file
2436 - dd: extra bytes for the "data" file
2437 - di: extra bytes for the "index" file
2437 - di: extra bytes for the "index" file
2438
2438
2439 A healthy revlog will return (0, 0).
2439 A healthy revlog will return (0, 0).
2440 """
2440 """
2441 expected = 0
2441 expected = 0
2442 if len(self):
2442 if len(self):
2443 expected = max(0, self.end(len(self) - 1))
2443 expected = max(0, self.end(len(self) - 1))
2444
2444
2445 try:
2445 try:
2446 with self._datafp() as f:
2446 with self._datafp() as f:
2447 f.seek(0, io.SEEK_END)
2447 f.seek(0, io.SEEK_END)
2448 actual = f.tell()
2448 actual = f.tell()
2449 dd = actual - expected
2449 dd = actual - expected
2450 except IOError as inst:
2450 except IOError as inst:
2451 if inst.errno != errno.ENOENT:
2451 if inst.errno != errno.ENOENT:
2452 raise
2452 raise
2453 dd = 0
2453 dd = 0
2454
2454
2455 try:
2455 try:
2456 f = self.opener(self.indexfile)
2456 f = self.opener(self.indexfile)
2457 f.seek(0, io.SEEK_END)
2457 f.seek(0, io.SEEK_END)
2458 actual = f.tell()
2458 actual = f.tell()
2459 f.close()
2459 f.close()
2460 s = self._io.size
2460 s = self._io.size
2461 i = max(0, actual // s)
2461 i = max(0, actual // s)
2462 di = actual - (i * s)
2462 di = actual - (i * s)
2463 if self._inline:
2463 if self._inline:
2464 databytes = 0
2464 databytes = 0
2465 for r in self:
2465 for r in self:
2466 databytes += max(0, self.length(r))
2466 databytes += max(0, self.length(r))
2467 dd = 0
2467 dd = 0
2468 di = actual - len(self) * s - databytes
2468 di = actual - len(self) * s - databytes
2469 except IOError as inst:
2469 except IOError as inst:
2470 if inst.errno != errno.ENOENT:
2470 if inst.errno != errno.ENOENT:
2471 raise
2471 raise
2472 di = 0
2472 di = 0
2473
2473
2474 return (dd, di)
2474 return (dd, di)
2475
2475
2476 def files(self):
2476 def files(self):
2477 res = [self.indexfile]
2477 res = [self.indexfile]
2478 if not self._inline:
2478 if not self._inline:
2479 res.append(self.datafile)
2479 res.append(self.datafile)
2480 return res
2480 return res
2481
2481
2482 def emitrevisions(
2482 def emitrevisions(
2483 self,
2483 self,
2484 nodes,
2484 nodes,
2485 nodesorder=None,
2485 nodesorder=None,
2486 revisiondata=False,
2486 revisiondata=False,
2487 assumehaveparentrevisions=False,
2487 assumehaveparentrevisions=False,
2488 deltamode=repository.CG_DELTAMODE_STD,
2488 deltamode=repository.CG_DELTAMODE_STD,
2489 ):
2489 ):
2490 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2490 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2491 raise error.ProgrammingError(
2491 raise error.ProgrammingError(
2492 b'unhandled value for nodesorder: %s' % nodesorder
2492 b'unhandled value for nodesorder: %s' % nodesorder
2493 )
2493 )
2494
2494
2495 if nodesorder is None and not self._generaldelta:
2495 if nodesorder is None and not self._generaldelta:
2496 nodesorder = b'storage'
2496 nodesorder = b'storage'
2497
2497
2498 if (
2498 if (
2499 not self._storedeltachains
2499 not self._storedeltachains
2500 and deltamode != repository.CG_DELTAMODE_PREV
2500 and deltamode != repository.CG_DELTAMODE_PREV
2501 ):
2501 ):
2502 deltamode = repository.CG_DELTAMODE_FULL
2502 deltamode = repository.CG_DELTAMODE_FULL
2503
2503
2504 return storageutil.emitrevisions(
2504 return storageutil.emitrevisions(
2505 self,
2505 self,
2506 nodes,
2506 nodes,
2507 nodesorder,
2507 nodesorder,
2508 revlogrevisiondelta,
2508 revlogrevisiondelta,
2509 deltaparentfn=self.deltaparent,
2509 deltaparentfn=self.deltaparent,
2510 candeltafn=self.candelta,
2510 candeltafn=self.candelta,
2511 rawsizefn=self.rawsize,
2511 rawsizefn=self.rawsize,
2512 revdifffn=self.revdiff,
2512 revdifffn=self.revdiff,
2513 flagsfn=self.flags,
2513 flagsfn=self.flags,
2514 deltamode=deltamode,
2514 deltamode=deltamode,
2515 revisiondata=revisiondata,
2515 revisiondata=revisiondata,
2516 assumehaveparentrevisions=assumehaveparentrevisions,
2516 assumehaveparentrevisions=assumehaveparentrevisions,
2517 )
2517 )
2518
2518
2519 DELTAREUSEALWAYS = b'always'
2519 DELTAREUSEALWAYS = b'always'
2520 DELTAREUSESAMEREVS = b'samerevs'
2520 DELTAREUSESAMEREVS = b'samerevs'
2521 DELTAREUSENEVER = b'never'
2521 DELTAREUSENEVER = b'never'
2522
2522
2523 DELTAREUSEFULLADD = b'fulladd'
2523 DELTAREUSEFULLADD = b'fulladd'
2524
2524
2525 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2525 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2526
2526
2527 def clone(
2527 def clone(
2528 self,
2528 self,
2529 tr,
2529 tr,
2530 destrevlog,
2530 destrevlog,
2531 addrevisioncb=None,
2531 addrevisioncb=None,
2532 deltareuse=DELTAREUSESAMEREVS,
2532 deltareuse=DELTAREUSESAMEREVS,
2533 forcedeltabothparents=None,
2533 forcedeltabothparents=None,
2534 sidedatacompanion=None,
2534 sidedatacompanion=None,
2535 ):
2535 ):
2536 """Copy this revlog to another, possibly with format changes.
2536 """Copy this revlog to another, possibly with format changes.
2537
2537
2538 The destination revlog will contain the same revisions and nodes.
2538 The destination revlog will contain the same revisions and nodes.
2539 However, it may not be bit-for-bit identical due to e.g. delta encoding
2539 However, it may not be bit-for-bit identical due to e.g. delta encoding
2540 differences.
2540 differences.
2541
2541
2542 The ``deltareuse`` argument control how deltas from the existing revlog
2542 The ``deltareuse`` argument control how deltas from the existing revlog
2543 are preserved in the destination revlog. The argument can have the
2543 are preserved in the destination revlog. The argument can have the
2544 following values:
2544 following values:
2545
2545
2546 DELTAREUSEALWAYS
2546 DELTAREUSEALWAYS
2547 Deltas will always be reused (if possible), even if the destination
2547 Deltas will always be reused (if possible), even if the destination
2548 revlog would not select the same revisions for the delta. This is the
2548 revlog would not select the same revisions for the delta. This is the
2549 fastest mode of operation.
2549 fastest mode of operation.
2550 DELTAREUSESAMEREVS
2550 DELTAREUSESAMEREVS
2551 Deltas will be reused if the destination revlog would pick the same
2551 Deltas will be reused if the destination revlog would pick the same
2552 revisions for the delta. This mode strikes a balance between speed
2552 revisions for the delta. This mode strikes a balance between speed
2553 and optimization.
2553 and optimization.
2554 DELTAREUSENEVER
2554 DELTAREUSENEVER
2555 Deltas will never be reused. This is the slowest mode of execution.
2555 Deltas will never be reused. This is the slowest mode of execution.
2556 This mode can be used to recompute deltas (e.g. if the diff/delta
2556 This mode can be used to recompute deltas (e.g. if the diff/delta
2557 algorithm changes).
2557 algorithm changes).
2558 DELTAREUSEFULLADD
2558 DELTAREUSEFULLADD
2559 Revision will be re-added as if their were new content. This is
2559 Revision will be re-added as if their were new content. This is
2560 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2560 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2561 eg: large file detection and handling.
2561 eg: large file detection and handling.
2562
2562
2563 Delta computation can be slow, so the choice of delta reuse policy can
2563 Delta computation can be slow, so the choice of delta reuse policy can
2564 significantly affect run time.
2564 significantly affect run time.
2565
2565
2566 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2566 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2567 two extremes. Deltas will be reused if they are appropriate. But if the
2567 two extremes. Deltas will be reused if they are appropriate. But if the
2568 delta could choose a better revision, it will do so. This means if you
2568 delta could choose a better revision, it will do so. This means if you
2569 are converting a non-generaldelta revlog to a generaldelta revlog,
2569 are converting a non-generaldelta revlog to a generaldelta revlog,
2570 deltas will be recomputed if the delta's parent isn't a parent of the
2570 deltas will be recomputed if the delta's parent isn't a parent of the
2571 revision.
2571 revision.
2572
2572
2573 In addition to the delta policy, the ``forcedeltabothparents``
2573 In addition to the delta policy, the ``forcedeltabothparents``
2574 argument controls whether to force compute deltas against both parents
2574 argument controls whether to force compute deltas against both parents
2575 for merges. By default, the current default is used.
2575 for merges. By default, the current default is used.
2576
2576
2577 If not None, the `sidedatacompanion` is callable that accept two
2577 If not None, the `sidedatacompanion` is callable that accept two
2578 arguments:
2578 arguments:
2579
2579
2580 (srcrevlog, rev)
2580 (srcrevlog, rev)
2581
2581
2582 and return a triplet that control changes to sidedata content from the
2582 and return a triplet that control changes to sidedata content from the
2583 old revision to the new clone result:
2583 old revision to the new clone result:
2584
2584
2585 (dropall, filterout, update)
2585 (dropall, filterout, update)
2586
2586
2587 * if `dropall` is True, all sidedata should be dropped
2587 * if `dropall` is True, all sidedata should be dropped
2588 * `filterout` is a set of sidedata keys that should be dropped
2588 * `filterout` is a set of sidedata keys that should be dropped
2589 * `update` is a mapping of additionnal/new key -> value
2589 * `update` is a mapping of additionnal/new key -> value
2590 """
2590 """
2591 if deltareuse not in self.DELTAREUSEALL:
2591 if deltareuse not in self.DELTAREUSEALL:
2592 raise ValueError(
2592 raise ValueError(
2593 _(b'value for deltareuse invalid: %s') % deltareuse
2593 _(b'value for deltareuse invalid: %s') % deltareuse
2594 )
2594 )
2595
2595
2596 if len(destrevlog):
2596 if len(destrevlog):
2597 raise ValueError(_(b'destination revlog is not empty'))
2597 raise ValueError(_(b'destination revlog is not empty'))
2598
2598
2599 if getattr(self, 'filteredrevs', None):
2599 if getattr(self, 'filteredrevs', None):
2600 raise ValueError(_(b'source revlog has filtered revisions'))
2600 raise ValueError(_(b'source revlog has filtered revisions'))
2601 if getattr(destrevlog, 'filteredrevs', None):
2601 if getattr(destrevlog, 'filteredrevs', None):
2602 raise ValueError(_(b'destination revlog has filtered revisions'))
2602 raise ValueError(_(b'destination revlog has filtered revisions'))
2603
2603
2604 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2604 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2605 # if possible.
2605 # if possible.
2606 oldlazydelta = destrevlog._lazydelta
2606 oldlazydelta = destrevlog._lazydelta
2607 oldlazydeltabase = destrevlog._lazydeltabase
2607 oldlazydeltabase = destrevlog._lazydeltabase
2608 oldamd = destrevlog._deltabothparents
2608 oldamd = destrevlog._deltabothparents
2609
2609
2610 try:
2610 try:
2611 if deltareuse == self.DELTAREUSEALWAYS:
2611 if deltareuse == self.DELTAREUSEALWAYS:
2612 destrevlog._lazydeltabase = True
2612 destrevlog._lazydeltabase = True
2613 destrevlog._lazydelta = True
2613 destrevlog._lazydelta = True
2614 elif deltareuse == self.DELTAREUSESAMEREVS:
2614 elif deltareuse == self.DELTAREUSESAMEREVS:
2615 destrevlog._lazydeltabase = False
2615 destrevlog._lazydeltabase = False
2616 destrevlog._lazydelta = True
2616 destrevlog._lazydelta = True
2617 elif deltareuse == self.DELTAREUSENEVER:
2617 elif deltareuse == self.DELTAREUSENEVER:
2618 destrevlog._lazydeltabase = False
2618 destrevlog._lazydeltabase = False
2619 destrevlog._lazydelta = False
2619 destrevlog._lazydelta = False
2620
2620
2621 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2621 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2622
2622
2623 self._clone(
2623 self._clone(
2624 tr,
2624 tr,
2625 destrevlog,
2625 destrevlog,
2626 addrevisioncb,
2626 addrevisioncb,
2627 deltareuse,
2627 deltareuse,
2628 forcedeltabothparents,
2628 forcedeltabothparents,
2629 sidedatacompanion,
2629 sidedatacompanion,
2630 )
2630 )
2631
2631
2632 finally:
2632 finally:
2633 destrevlog._lazydelta = oldlazydelta
2633 destrevlog._lazydelta = oldlazydelta
2634 destrevlog._lazydeltabase = oldlazydeltabase
2634 destrevlog._lazydeltabase = oldlazydeltabase
2635 destrevlog._deltabothparents = oldamd
2635 destrevlog._deltabothparents = oldamd
2636
2636
2637 def _clone(
2637 def _clone(
2638 self,
2638 self,
2639 tr,
2639 tr,
2640 destrevlog,
2640 destrevlog,
2641 addrevisioncb,
2641 addrevisioncb,
2642 deltareuse,
2642 deltareuse,
2643 forcedeltabothparents,
2643 forcedeltabothparents,
2644 sidedatacompanion,
2644 sidedatacompanion,
2645 ):
2645 ):
2646 """perform the core duty of `revlog.clone` after parameter processing"""
2646 """perform the core duty of `revlog.clone` after parameter processing"""
2647 deltacomputer = deltautil.deltacomputer(destrevlog)
2647 deltacomputer = deltautil.deltacomputer(destrevlog)
2648 index = self.index
2648 index = self.index
2649 for rev in self:
2649 for rev in self:
2650 entry = index[rev]
2650 entry = index[rev]
2651
2651
2652 # Some classes override linkrev to take filtered revs into
2652 # Some classes override linkrev to take filtered revs into
2653 # account. Use raw entry from index.
2653 # account. Use raw entry from index.
2654 flags = entry[0] & 0xFFFF
2654 flags = entry[0] & 0xFFFF
2655 linkrev = entry[4]
2655 linkrev = entry[4]
2656 p1 = index[entry[5]][7]
2656 p1 = index[entry[5]][7]
2657 p2 = index[entry[6]][7]
2657 p2 = index[entry[6]][7]
2658 node = entry[7]
2658 node = entry[7]
2659
2659
2660 sidedataactions = (False, [], {})
2660 sidedataactions = (False, [], {})
2661 if sidedatacompanion is not None:
2661 if sidedatacompanion is not None:
2662 sidedataactions = sidedatacompanion(self, rev)
2662 sidedataactions = sidedatacompanion(self, rev)
2663
2663
2664 # (Possibly) reuse the delta from the revlog if allowed and
2664 # (Possibly) reuse the delta from the revlog if allowed and
2665 # the revlog chunk is a delta.
2665 # the revlog chunk is a delta.
2666 cachedelta = None
2666 cachedelta = None
2667 rawtext = None
2667 rawtext = None
2668 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2668 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2669 dropall, filterout, update = sidedataactions
2669 dropall, filterout, update = sidedataactions
2670 text, sidedata = self._revisiondata(rev)
2670 text, sidedata = self._revisiondata(rev)
2671 if dropall:
2671 if dropall:
2672 sidedata = {}
2672 sidedata = {}
2673 for key in filterout:
2673 for key in filterout:
2674 sidedata.pop(key, None)
2674 sidedata.pop(key, None)
2675 sidedata.update(update)
2675 sidedata.update(update)
2676 if not sidedata:
2676 if not sidedata:
2677 sidedata = None
2677 sidedata = None
2678 destrevlog.addrevision(
2678 destrevlog.addrevision(
2679 text,
2679 text,
2680 tr,
2680 tr,
2681 linkrev,
2681 linkrev,
2682 p1,
2682 p1,
2683 p2,
2683 p2,
2684 cachedelta=cachedelta,
2684 cachedelta=cachedelta,
2685 node=node,
2685 node=node,
2686 flags=flags,
2686 flags=flags,
2687 deltacomputer=deltacomputer,
2687 deltacomputer=deltacomputer,
2688 sidedata=sidedata,
2688 sidedata=sidedata,
2689 )
2689 )
2690 else:
2690 else:
2691 if destrevlog._lazydelta:
2691 if destrevlog._lazydelta:
2692 dp = self.deltaparent(rev)
2692 dp = self.deltaparent(rev)
2693 if dp != nullrev:
2693 if dp != nullrev:
2694 cachedelta = (dp, bytes(self._chunk(rev)))
2694 cachedelta = (dp, bytes(self._chunk(rev)))
2695
2695
2696 if not cachedelta:
2696 if not cachedelta:
2697 rawtext = self.rawdata(rev)
2697 rawtext = self.rawdata(rev)
2698
2698
2699 ifh = destrevlog.opener(
2699 ifh = destrevlog.opener(
2700 destrevlog.indexfile, b'a+', checkambig=False
2700 destrevlog.indexfile, b'a+', checkambig=False
2701 )
2701 )
2702 dfh = None
2702 dfh = None
2703 if not destrevlog._inline:
2703 if not destrevlog._inline:
2704 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2704 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2705 try:
2705 try:
2706 destrevlog._addrevision(
2706 destrevlog._addrevision(
2707 node,
2707 node,
2708 rawtext,
2708 rawtext,
2709 tr,
2709 tr,
2710 linkrev,
2710 linkrev,
2711 p1,
2711 p1,
2712 p2,
2712 p2,
2713 flags,
2713 flags,
2714 cachedelta,
2714 cachedelta,
2715 ifh,
2715 ifh,
2716 dfh,
2716 dfh,
2717 deltacomputer=deltacomputer,
2717 deltacomputer=deltacomputer,
2718 )
2718 )
2719 finally:
2719 finally:
2720 if dfh:
2720 if dfh:
2721 dfh.close()
2721 dfh.close()
2722 ifh.close()
2722 ifh.close()
2723
2723
2724 if addrevisioncb:
2724 if addrevisioncb:
2725 addrevisioncb(self, rev, node)
2725 addrevisioncb(self, rev, node)
2726
2726
2727 def censorrevision(self, tr, censornode, tombstone=b''):
2727 def censorrevision(self, tr, censornode, tombstone=b''):
2728 if (self.version & 0xFFFF) == REVLOGV0:
2728 if (self.version & 0xFFFF) == REVLOGV0:
2729 raise error.RevlogError(
2729 raise error.RevlogError(
2730 _(b'cannot censor with version %d revlogs') % self.version
2730 _(b'cannot censor with version %d revlogs') % self.version
2731 )
2731 )
2732
2732
2733 censorrev = self.rev(censornode)
2733 censorrev = self.rev(censornode)
2734 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2734 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2735
2735
2736 if len(tombstone) > self.rawsize(censorrev):
2736 if len(tombstone) > self.rawsize(censorrev):
2737 raise error.Abort(
2737 raise error.Abort(
2738 _(b'censor tombstone must be no longer than censored data')
2738 _(b'censor tombstone must be no longer than censored data')
2739 )
2739 )
2740
2740
2741 # Rewriting the revlog in place is hard. Our strategy for censoring is
2741 # Rewriting the revlog in place is hard. Our strategy for censoring is
2742 # to create a new revlog, copy all revisions to it, then replace the
2742 # to create a new revlog, copy all revisions to it, then replace the
2743 # revlogs on transaction close.
2743 # revlogs on transaction close.
2744
2744
2745 newindexfile = self.indexfile + b'.tmpcensored'
2745 newindexfile = self.indexfile + b'.tmpcensored'
2746 newdatafile = self.datafile + b'.tmpcensored'
2746 newdatafile = self.datafile + b'.tmpcensored'
2747
2747
2748 # This is a bit dangerous. We could easily have a mismatch of state.
2748 # This is a bit dangerous. We could easily have a mismatch of state.
2749 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2749 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2750 newrl.version = self.version
2750 newrl.version = self.version
2751 newrl._generaldelta = self._generaldelta
2751 newrl._generaldelta = self._generaldelta
2752 newrl._io = self._io
2752 newrl._io = self._io
2753
2753
2754 for rev in self.revs():
2754 for rev in self.revs():
2755 node = self.node(rev)
2755 node = self.node(rev)
2756 p1, p2 = self.parents(node)
2756 p1, p2 = self.parents(node)
2757
2757
2758 if rev == censorrev:
2758 if rev == censorrev:
2759 newrl.addrawrevision(
2759 newrl.addrawrevision(
2760 tombstone,
2760 tombstone,
2761 tr,
2761 tr,
2762 self.linkrev(censorrev),
2762 self.linkrev(censorrev),
2763 p1,
2763 p1,
2764 p2,
2764 p2,
2765 censornode,
2765 censornode,
2766 REVIDX_ISCENSORED,
2766 REVIDX_ISCENSORED,
2767 )
2767 )
2768
2768
2769 if newrl.deltaparent(rev) != nullrev:
2769 if newrl.deltaparent(rev) != nullrev:
2770 raise error.Abort(
2770 raise error.Abort(
2771 _(
2771 _(
2772 b'censored revision stored as delta; '
2772 b'censored revision stored as delta; '
2773 b'cannot censor'
2773 b'cannot censor'
2774 ),
2774 ),
2775 hint=_(
2775 hint=_(
2776 b'censoring of revlogs is not '
2776 b'censoring of revlogs is not '
2777 b'fully implemented; please report '
2777 b'fully implemented; please report '
2778 b'this bug'
2778 b'this bug'
2779 ),
2779 ),
2780 )
2780 )
2781 continue
2781 continue
2782
2782
2783 if self.iscensored(rev):
2783 if self.iscensored(rev):
2784 if self.deltaparent(rev) != nullrev:
2784 if self.deltaparent(rev) != nullrev:
2785 raise error.Abort(
2785 raise error.Abort(
2786 _(
2786 _(
2787 b'cannot censor due to censored '
2787 b'cannot censor due to censored '
2788 b'revision having delta stored'
2788 b'revision having delta stored'
2789 )
2789 )
2790 )
2790 )
2791 rawtext = self._chunk(rev)
2791 rawtext = self._chunk(rev)
2792 else:
2792 else:
2793 rawtext = self.rawdata(rev)
2793 rawtext = self.rawdata(rev)
2794
2794
2795 newrl.addrawrevision(
2795 newrl.addrawrevision(
2796 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2796 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2797 )
2797 )
2798
2798
2799 tr.addbackup(self.indexfile, location=b'store')
2799 tr.addbackup(self.indexfile, location=b'store')
2800 if not self._inline:
2800 if not self._inline:
2801 tr.addbackup(self.datafile, location=b'store')
2801 tr.addbackup(self.datafile, location=b'store')
2802
2802
2803 self.opener.rename(newrl.indexfile, self.indexfile)
2803 self.opener.rename(newrl.indexfile, self.indexfile)
2804 if not self._inline:
2804 if not self._inline:
2805 self.opener.rename(newrl.datafile, self.datafile)
2805 self.opener.rename(newrl.datafile, self.datafile)
2806
2806
2807 self.clearcaches()
2807 self.clearcaches()
2808 self._loadindex()
2808 self._loadindex()
2809
2809
2810 def verifyintegrity(self, state):
2810 def verifyintegrity(self, state):
2811 """Verifies the integrity of the revlog.
2811 """Verifies the integrity of the revlog.
2812
2812
2813 Yields ``revlogproblem`` instances describing problems that are
2813 Yields ``revlogproblem`` instances describing problems that are
2814 found.
2814 found.
2815 """
2815 """
2816 dd, di = self.checksize()
2816 dd, di = self.checksize()
2817 if dd:
2817 if dd:
2818 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2818 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2819 if di:
2819 if di:
2820 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2820 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2821
2821
2822 version = self.version & 0xFFFF
2822 version = self.version & 0xFFFF
2823
2823
2824 # The verifier tells us what version revlog we should be.
2824 # The verifier tells us what version revlog we should be.
2825 if version != state[b'expectedversion']:
2825 if version != state[b'expectedversion']:
2826 yield revlogproblem(
2826 yield revlogproblem(
2827 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2827 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2828 % (self.indexfile, version, state[b'expectedversion'])
2828 % (self.indexfile, version, state[b'expectedversion'])
2829 )
2829 )
2830
2830
2831 state[b'skipread'] = set()
2831 state[b'skipread'] = set()
2832
2832
2833 for rev in self:
2833 for rev in self:
2834 node = self.node(rev)
2834 node = self.node(rev)
2835
2835
2836 # Verify contents. 4 cases to care about:
2836 # Verify contents. 4 cases to care about:
2837 #
2837 #
2838 # common: the most common case
2838 # common: the most common case
2839 # rename: with a rename
2839 # rename: with a rename
2840 # meta: file content starts with b'\1\n', the metadata
2840 # meta: file content starts with b'\1\n', the metadata
2841 # header defined in filelog.py, but without a rename
2841 # header defined in filelog.py, but without a rename
2842 # ext: content stored externally
2842 # ext: content stored externally
2843 #
2843 #
2844 # More formally, their differences are shown below:
2844 # More formally, their differences are shown below:
2845 #
2845 #
2846 # | common | rename | meta | ext
2846 # | common | rename | meta | ext
2847 # -------------------------------------------------------
2847 # -------------------------------------------------------
2848 # flags() | 0 | 0 | 0 | not 0
2848 # flags() | 0 | 0 | 0 | not 0
2849 # renamed() | False | True | False | ?
2849 # renamed() | False | True | False | ?
2850 # rawtext[0:2]=='\1\n'| False | True | True | ?
2850 # rawtext[0:2]=='\1\n'| False | True | True | ?
2851 #
2851 #
2852 # "rawtext" means the raw text stored in revlog data, which
2852 # "rawtext" means the raw text stored in revlog data, which
2853 # could be retrieved by "rawdata(rev)". "text"
2853 # could be retrieved by "rawdata(rev)". "text"
2854 # mentioned below is "revision(rev)".
2854 # mentioned below is "revision(rev)".
2855 #
2855 #
2856 # There are 3 different lengths stored physically:
2856 # There are 3 different lengths stored physically:
2857 # 1. L1: rawsize, stored in revlog index
2857 # 1. L1: rawsize, stored in revlog index
2858 # 2. L2: len(rawtext), stored in revlog data
2858 # 2. L2: len(rawtext), stored in revlog data
2859 # 3. L3: len(text), stored in revlog data if flags==0, or
2859 # 3. L3: len(text), stored in revlog data if flags==0, or
2860 # possibly somewhere else if flags!=0
2860 # possibly somewhere else if flags!=0
2861 #
2861 #
2862 # L1 should be equal to L2. L3 could be different from them.
2862 # L1 should be equal to L2. L3 could be different from them.
2863 # "text" may or may not affect commit hash depending on flag
2863 # "text" may or may not affect commit hash depending on flag
2864 # processors (see flagutil.addflagprocessor).
2864 # processors (see flagutil.addflagprocessor).
2865 #
2865 #
2866 # | common | rename | meta | ext
2866 # | common | rename | meta | ext
2867 # -------------------------------------------------
2867 # -------------------------------------------------
2868 # rawsize() | L1 | L1 | L1 | L1
2868 # rawsize() | L1 | L1 | L1 | L1
2869 # size() | L1 | L2-LM | L1(*) | L1 (?)
2869 # size() | L1 | L2-LM | L1(*) | L1 (?)
2870 # len(rawtext) | L2 | L2 | L2 | L2
2870 # len(rawtext) | L2 | L2 | L2 | L2
2871 # len(text) | L2 | L2 | L2 | L3
2871 # len(text) | L2 | L2 | L2 | L3
2872 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2872 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2873 #
2873 #
2874 # LM: length of metadata, depending on rawtext
2874 # LM: length of metadata, depending on rawtext
2875 # (*): not ideal, see comment in filelog.size
2875 # (*): not ideal, see comment in filelog.size
2876 # (?): could be "- len(meta)" if the resolved content has
2876 # (?): could be "- len(meta)" if the resolved content has
2877 # rename metadata
2877 # rename metadata
2878 #
2878 #
2879 # Checks needed to be done:
2879 # Checks needed to be done:
2880 # 1. length check: L1 == L2, in all cases.
2880 # 1. length check: L1 == L2, in all cases.
2881 # 2. hash check: depending on flag processor, we may need to
2881 # 2. hash check: depending on flag processor, we may need to
2882 # use either "text" (external), or "rawtext" (in revlog).
2882 # use either "text" (external), or "rawtext" (in revlog).
2883
2883
2884 try:
2884 try:
2885 skipflags = state.get(b'skipflags', 0)
2885 skipflags = state.get(b'skipflags', 0)
2886 if skipflags:
2886 if skipflags:
2887 skipflags &= self.flags(rev)
2887 skipflags &= self.flags(rev)
2888
2888
2889 if skipflags:
2889 if skipflags:
2890 state[b'skipread'].add(node)
2890 state[b'skipread'].add(node)
2891 else:
2891 else:
2892 # Side-effect: read content and verify hash.
2892 # Side-effect: read content and verify hash.
2893 self.revision(node)
2893 self.revision(node)
2894
2894
2895 l1 = self.rawsize(rev)
2895 l1 = self.rawsize(rev)
2896 l2 = len(self.rawdata(node))
2896 l2 = len(self.rawdata(node))
2897
2897
2898 if l1 != l2:
2898 if l1 != l2:
2899 yield revlogproblem(
2899 yield revlogproblem(
2900 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
2900 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
2901 node=node,
2901 node=node,
2902 )
2902 )
2903
2903
2904 except error.CensoredNodeError:
2904 except error.CensoredNodeError:
2905 if state[b'erroroncensored']:
2905 if state[b'erroroncensored']:
2906 yield revlogproblem(
2906 yield revlogproblem(
2907 error=_(b'censored file data'), node=node
2907 error=_(b'censored file data'), node=node
2908 )
2908 )
2909 state[b'skipread'].add(node)
2909 state[b'skipread'].add(node)
2910 except Exception as e:
2910 except Exception as e:
2911 yield revlogproblem(
2911 yield revlogproblem(
2912 error=_(b'unpacking %s: %s')
2912 error=_(b'unpacking %s: %s')
2913 % (short(node), stringutil.forcebytestr(e)),
2913 % (short(node), stringutil.forcebytestr(e)),
2914 node=node,
2914 node=node,
2915 )
2915 )
2916 state[b'skipread'].add(node)
2916 state[b'skipread'].add(node)
2917
2917
2918 def storageinfo(
2918 def storageinfo(
2919 self,
2919 self,
2920 exclusivefiles=False,
2920 exclusivefiles=False,
2921 sharedfiles=False,
2921 sharedfiles=False,
2922 revisionscount=False,
2922 revisionscount=False,
2923 trackedsize=False,
2923 trackedsize=False,
2924 storedsize=False,
2924 storedsize=False,
2925 ):
2925 ):
2926 d = {}
2926 d = {}
2927
2927
2928 if exclusivefiles:
2928 if exclusivefiles:
2929 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
2929 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
2930 if not self._inline:
2930 if not self._inline:
2931 d[b'exclusivefiles'].append((self.opener, self.datafile))
2931 d[b'exclusivefiles'].append((self.opener, self.datafile))
2932
2932
2933 if sharedfiles:
2933 if sharedfiles:
2934 d[b'sharedfiles'] = []
2934 d[b'sharedfiles'] = []
2935
2935
2936 if revisionscount:
2936 if revisionscount:
2937 d[b'revisionscount'] = len(self)
2937 d[b'revisionscount'] = len(self)
2938
2938
2939 if trackedsize:
2939 if trackedsize:
2940 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
2940 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
2941
2941
2942 if storedsize:
2942 if storedsize:
2943 d[b'storedsize'] = sum(
2943 d[b'storedsize'] = sum(
2944 self.opener.stat(path).st_size for path in self.files()
2944 self.opener.stat(path).st_size for path in self.files()
2945 )
2945 )
2946
2946
2947 return d
2947 return d
General Comments 0
You need to be logged in to leave comments. Login now