##// END OF EJS Templates
revlog: merge hash checking subfunctions...
Remi Chaintron -
r30584:be5b2098 default
parent child Browse files
Show More
@@ -1,1241 +1,1241 b''
1 # perf.py - performance test routines
1 # perf.py - performance test routines
2 '''helper extension to measure performance'''
2 '''helper extension to measure performance'''
3
3
4 # "historical portability" policy of perf.py:
4 # "historical portability" policy of perf.py:
5 #
5 #
6 # We have to do:
6 # We have to do:
7 # - make perf.py "loadable" with as wide Mercurial version as possible
7 # - make perf.py "loadable" with as wide Mercurial version as possible
8 # This doesn't mean that perf commands work correctly with that Mercurial.
8 # This doesn't mean that perf commands work correctly with that Mercurial.
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
9 # BTW, perf.py itself has been available since 1.1 (or eb240755386d).
10 # - make historical perf command work correctly with as wide Mercurial
10 # - make historical perf command work correctly with as wide Mercurial
11 # version as possible
11 # version as possible
12 #
12 #
13 # We have to do, if possible with reasonable cost:
13 # We have to do, if possible with reasonable cost:
14 # - make recent perf command for historical feature work correctly
14 # - make recent perf command for historical feature work correctly
15 # with early Mercurial
15 # with early Mercurial
16 #
16 #
17 # We don't have to do:
17 # We don't have to do:
18 # - make perf command for recent feature work correctly with early
18 # - make perf command for recent feature work correctly with early
19 # Mercurial
19 # Mercurial
20
20
21 from __future__ import absolute_import
21 from __future__ import absolute_import
22 import functools
22 import functools
23 import os
23 import os
24 import random
24 import random
25 import sys
25 import sys
26 import time
26 import time
27 from mercurial import (
27 from mercurial import (
28 bdiff,
28 bdiff,
29 changegroup,
29 changegroup,
30 cmdutil,
30 cmdutil,
31 commands,
31 commands,
32 copies,
32 copies,
33 error,
33 error,
34 extensions,
34 extensions,
35 mdiff,
35 mdiff,
36 merge,
36 merge,
37 revlog,
37 revlog,
38 util,
38 util,
39 )
39 )
40
40
41 # for "historical portability":
41 # for "historical portability":
42 # try to import modules separately (in dict order), and ignore
42 # try to import modules separately (in dict order), and ignore
43 # failure, because these aren't available with early Mercurial
43 # failure, because these aren't available with early Mercurial
44 try:
44 try:
45 from mercurial import branchmap # since 2.5 (or bcee63733aad)
45 from mercurial import branchmap # since 2.5 (or bcee63733aad)
46 except ImportError:
46 except ImportError:
47 pass
47 pass
48 try:
48 try:
49 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
49 from mercurial import obsolete # since 2.3 (or ad0d6c2b3279)
50 except ImportError:
50 except ImportError:
51 pass
51 pass
52 try:
52 try:
53 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
53 from mercurial import repoview # since 2.5 (or 3a6ddacb7198)
54 except ImportError:
54 except ImportError:
55 pass
55 pass
56 try:
56 try:
57 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
57 from mercurial import scmutil # since 1.9 (or 8b252e826c68)
58 except ImportError:
58 except ImportError:
59 pass
59 pass
60
60
61 # for "historical portability":
61 # for "historical portability":
62 # define util.safehasattr forcibly, because util.safehasattr has been
62 # define util.safehasattr forcibly, because util.safehasattr has been
63 # available since 1.9.3 (or 94b200a11cf7)
63 # available since 1.9.3 (or 94b200a11cf7)
64 _undefined = object()
64 _undefined = object()
65 def safehasattr(thing, attr):
65 def safehasattr(thing, attr):
66 return getattr(thing, attr, _undefined) is not _undefined
66 return getattr(thing, attr, _undefined) is not _undefined
67 setattr(util, 'safehasattr', safehasattr)
67 setattr(util, 'safehasattr', safehasattr)
68
68
69 # for "historical portability":
69 # for "historical portability":
70 # use locally defined empty option list, if formatteropts isn't
70 # use locally defined empty option list, if formatteropts isn't
71 # available, because commands.formatteropts has been available since
71 # available, because commands.formatteropts has been available since
72 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
72 # 3.2 (or 7a7eed5176a4), even though formatting itself has been
73 # available since 2.2 (or ae5f92e154d3)
73 # available since 2.2 (or ae5f92e154d3)
74 formatteropts = getattr(commands, "formatteropts", [])
74 formatteropts = getattr(commands, "formatteropts", [])
75
75
76 # for "historical portability":
76 # for "historical portability":
77 # use locally defined option list, if debugrevlogopts isn't available,
77 # use locally defined option list, if debugrevlogopts isn't available,
78 # because commands.debugrevlogopts has been available since 3.7 (or
78 # because commands.debugrevlogopts has been available since 3.7 (or
79 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
79 # 5606f7d0d063), even though cmdutil.openrevlog() has been available
80 # since 1.9 (or a79fea6b3e77).
80 # since 1.9 (or a79fea6b3e77).
81 revlogopts = getattr(commands, "debugrevlogopts", [
81 revlogopts = getattr(commands, "debugrevlogopts", [
82 ('c', 'changelog', False, ('open changelog')),
82 ('c', 'changelog', False, ('open changelog')),
83 ('m', 'manifest', False, ('open manifest')),
83 ('m', 'manifest', False, ('open manifest')),
84 ('', 'dir', False, ('open directory manifest')),
84 ('', 'dir', False, ('open directory manifest')),
85 ])
85 ])
86
86
87 cmdtable = {}
87 cmdtable = {}
88
88
89 # for "historical portability":
89 # for "historical portability":
90 # define parsealiases locally, because cmdutil.parsealiases has been
90 # define parsealiases locally, because cmdutil.parsealiases has been
91 # available since 1.5 (or 6252852b4332)
91 # available since 1.5 (or 6252852b4332)
92 def parsealiases(cmd):
92 def parsealiases(cmd):
93 return cmd.lstrip("^").split("|")
93 return cmd.lstrip("^").split("|")
94
94
95 if safehasattr(cmdutil, 'command'):
95 if safehasattr(cmdutil, 'command'):
96 import inspect
96 import inspect
97 command = cmdutil.command(cmdtable)
97 command = cmdutil.command(cmdtable)
98 if 'norepo' not in inspect.getargspec(command)[0]:
98 if 'norepo' not in inspect.getargspec(command)[0]:
99 # for "historical portability":
99 # for "historical portability":
100 # wrap original cmdutil.command, because "norepo" option has
100 # wrap original cmdutil.command, because "norepo" option has
101 # been available since 3.1 (or 75a96326cecb)
101 # been available since 3.1 (or 75a96326cecb)
102 _command = command
102 _command = command
103 def command(name, options=(), synopsis=None, norepo=False):
103 def command(name, options=(), synopsis=None, norepo=False):
104 if norepo:
104 if norepo:
105 commands.norepo += ' %s' % ' '.join(parsealiases(name))
105 commands.norepo += ' %s' % ' '.join(parsealiases(name))
106 return _command(name, list(options), synopsis)
106 return _command(name, list(options), synopsis)
107 else:
107 else:
108 # for "historical portability":
108 # for "historical portability":
109 # define "@command" annotation locally, because cmdutil.command
109 # define "@command" annotation locally, because cmdutil.command
110 # has been available since 1.9 (or 2daa5179e73f)
110 # has been available since 1.9 (or 2daa5179e73f)
111 def command(name, options=(), synopsis=None, norepo=False):
111 def command(name, options=(), synopsis=None, norepo=False):
112 def decorator(func):
112 def decorator(func):
113 if synopsis:
113 if synopsis:
114 cmdtable[name] = func, list(options), synopsis
114 cmdtable[name] = func, list(options), synopsis
115 else:
115 else:
116 cmdtable[name] = func, list(options)
116 cmdtable[name] = func, list(options)
117 if norepo:
117 if norepo:
118 commands.norepo += ' %s' % ' '.join(parsealiases(name))
118 commands.norepo += ' %s' % ' '.join(parsealiases(name))
119 return func
119 return func
120 return decorator
120 return decorator
121
121
122 def getlen(ui):
122 def getlen(ui):
123 if ui.configbool("perf", "stub"):
123 if ui.configbool("perf", "stub"):
124 return lambda x: 1
124 return lambda x: 1
125 return len
125 return len
126
126
127 def gettimer(ui, opts=None):
127 def gettimer(ui, opts=None):
128 """return a timer function and formatter: (timer, formatter)
128 """return a timer function and formatter: (timer, formatter)
129
129
130 This function exists to gather the creation of formatter in a single
130 This function exists to gather the creation of formatter in a single
131 place instead of duplicating it in all performance commands."""
131 place instead of duplicating it in all performance commands."""
132
132
133 # enforce an idle period before execution to counteract power management
133 # enforce an idle period before execution to counteract power management
134 # experimental config: perf.presleep
134 # experimental config: perf.presleep
135 time.sleep(getint(ui, "perf", "presleep", 1))
135 time.sleep(getint(ui, "perf", "presleep", 1))
136
136
137 if opts is None:
137 if opts is None:
138 opts = {}
138 opts = {}
139 # redirect all to stderr unless buffer api is in use
139 # redirect all to stderr unless buffer api is in use
140 if not ui._buffers:
140 if not ui._buffers:
141 ui = ui.copy()
141 ui = ui.copy()
142 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
142 uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
143 if uifout:
143 if uifout:
144 # for "historical portability":
144 # for "historical portability":
145 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
145 # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
146 uifout.set(ui.ferr)
146 uifout.set(ui.ferr)
147
147
148 # get a formatter
148 # get a formatter
149 uiformatter = getattr(ui, 'formatter', None)
149 uiformatter = getattr(ui, 'formatter', None)
150 if uiformatter:
150 if uiformatter:
151 fm = uiformatter('perf', opts)
151 fm = uiformatter('perf', opts)
152 else:
152 else:
153 # for "historical portability":
153 # for "historical portability":
154 # define formatter locally, because ui.formatter has been
154 # define formatter locally, because ui.formatter has been
155 # available since 2.2 (or ae5f92e154d3)
155 # available since 2.2 (or ae5f92e154d3)
156 from mercurial import node
156 from mercurial import node
157 class defaultformatter(object):
157 class defaultformatter(object):
158 """Minimized composition of baseformatter and plainformatter
158 """Minimized composition of baseformatter and plainformatter
159 """
159 """
160 def __init__(self, ui, topic, opts):
160 def __init__(self, ui, topic, opts):
161 self._ui = ui
161 self._ui = ui
162 if ui.debugflag:
162 if ui.debugflag:
163 self.hexfunc = node.hex
163 self.hexfunc = node.hex
164 else:
164 else:
165 self.hexfunc = node.short
165 self.hexfunc = node.short
166 def __nonzero__(self):
166 def __nonzero__(self):
167 return False
167 return False
168 def startitem(self):
168 def startitem(self):
169 pass
169 pass
170 def data(self, **data):
170 def data(self, **data):
171 pass
171 pass
172 def write(self, fields, deftext, *fielddata, **opts):
172 def write(self, fields, deftext, *fielddata, **opts):
173 self._ui.write(deftext % fielddata, **opts)
173 self._ui.write(deftext % fielddata, **opts)
174 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
174 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
175 if cond:
175 if cond:
176 self._ui.write(deftext % fielddata, **opts)
176 self._ui.write(deftext % fielddata, **opts)
177 def plain(self, text, **opts):
177 def plain(self, text, **opts):
178 self._ui.write(text, **opts)
178 self._ui.write(text, **opts)
179 def end(self):
179 def end(self):
180 pass
180 pass
181 fm = defaultformatter(ui, 'perf', opts)
181 fm = defaultformatter(ui, 'perf', opts)
182
182
183 # stub function, runs code only once instead of in a loop
183 # stub function, runs code only once instead of in a loop
184 # experimental config: perf.stub
184 # experimental config: perf.stub
185 if ui.configbool("perf", "stub"):
185 if ui.configbool("perf", "stub"):
186 return functools.partial(stub_timer, fm), fm
186 return functools.partial(stub_timer, fm), fm
187 return functools.partial(_timer, fm), fm
187 return functools.partial(_timer, fm), fm
188
188
189 def stub_timer(fm, func, title=None):
189 def stub_timer(fm, func, title=None):
190 func()
190 func()
191
191
192 def _timer(fm, func, title=None):
192 def _timer(fm, func, title=None):
193 results = []
193 results = []
194 begin = time.time()
194 begin = time.time()
195 count = 0
195 count = 0
196 while True:
196 while True:
197 ostart = os.times()
197 ostart = os.times()
198 cstart = time.time()
198 cstart = time.time()
199 r = func()
199 r = func()
200 cstop = time.time()
200 cstop = time.time()
201 ostop = os.times()
201 ostop = os.times()
202 count += 1
202 count += 1
203 a, b = ostart, ostop
203 a, b = ostart, ostop
204 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
204 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
205 if cstop - begin > 3 and count >= 100:
205 if cstop - begin > 3 and count >= 100:
206 break
206 break
207 if cstop - begin > 10 and count >= 3:
207 if cstop - begin > 10 and count >= 3:
208 break
208 break
209
209
210 fm.startitem()
210 fm.startitem()
211
211
212 if title:
212 if title:
213 fm.write('title', '! %s\n', title)
213 fm.write('title', '! %s\n', title)
214 if r:
214 if r:
215 fm.write('result', '! result: %s\n', r)
215 fm.write('result', '! result: %s\n', r)
216 m = min(results)
216 m = min(results)
217 fm.plain('!')
217 fm.plain('!')
218 fm.write('wall', ' wall %f', m[0])
218 fm.write('wall', ' wall %f', m[0])
219 fm.write('comb', ' comb %f', m[1] + m[2])
219 fm.write('comb', ' comb %f', m[1] + m[2])
220 fm.write('user', ' user %f', m[1])
220 fm.write('user', ' user %f', m[1])
221 fm.write('sys', ' sys %f', m[2])
221 fm.write('sys', ' sys %f', m[2])
222 fm.write('count', ' (best of %d)', count)
222 fm.write('count', ' (best of %d)', count)
223 fm.plain('\n')
223 fm.plain('\n')
224
224
225 # utilities for historical portability
225 # utilities for historical portability
226
226
227 def getint(ui, section, name, default):
227 def getint(ui, section, name, default):
228 # for "historical portability":
228 # for "historical portability":
229 # ui.configint has been available since 1.9 (or fa2b596db182)
229 # ui.configint has been available since 1.9 (or fa2b596db182)
230 v = ui.config(section, name, None)
230 v = ui.config(section, name, None)
231 if v is None:
231 if v is None:
232 return default
232 return default
233 try:
233 try:
234 return int(v)
234 return int(v)
235 except ValueError:
235 except ValueError:
236 raise error.ConfigError(("%s.%s is not an integer ('%s')")
236 raise error.ConfigError(("%s.%s is not an integer ('%s')")
237 % (section, name, v))
237 % (section, name, v))
238
238
239 def safeattrsetter(obj, name, ignoremissing=False):
239 def safeattrsetter(obj, name, ignoremissing=False):
240 """Ensure that 'obj' has 'name' attribute before subsequent setattr
240 """Ensure that 'obj' has 'name' attribute before subsequent setattr
241
241
242 This function is aborted, if 'obj' doesn't have 'name' attribute
242 This function is aborted, if 'obj' doesn't have 'name' attribute
243 at runtime. This avoids overlooking removal of an attribute, which
243 at runtime. This avoids overlooking removal of an attribute, which
244 breaks assumption of performance measurement, in the future.
244 breaks assumption of performance measurement, in the future.
245
245
246 This function returns the object to (1) assign a new value, and
246 This function returns the object to (1) assign a new value, and
247 (2) restore an original value to the attribute.
247 (2) restore an original value to the attribute.
248
248
249 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
249 If 'ignoremissing' is true, missing 'name' attribute doesn't cause
250 abortion, and this function returns None. This is useful to
250 abortion, and this function returns None. This is useful to
251 examine an attribute, which isn't ensured in all Mercurial
251 examine an attribute, which isn't ensured in all Mercurial
252 versions.
252 versions.
253 """
253 """
254 if not util.safehasattr(obj, name):
254 if not util.safehasattr(obj, name):
255 if ignoremissing:
255 if ignoremissing:
256 return None
256 return None
257 raise error.Abort(("missing attribute %s of %s might break assumption"
257 raise error.Abort(("missing attribute %s of %s might break assumption"
258 " of performance measurement") % (name, obj))
258 " of performance measurement") % (name, obj))
259
259
260 origvalue = getattr(obj, name)
260 origvalue = getattr(obj, name)
261 class attrutil(object):
261 class attrutil(object):
262 def set(self, newvalue):
262 def set(self, newvalue):
263 setattr(obj, name, newvalue)
263 setattr(obj, name, newvalue)
264 def restore(self):
264 def restore(self):
265 setattr(obj, name, origvalue)
265 setattr(obj, name, origvalue)
266
266
267 return attrutil()
267 return attrutil()
268
268
269 # utilities to examine each internal API changes
269 # utilities to examine each internal API changes
270
270
271 def getbranchmapsubsettable():
271 def getbranchmapsubsettable():
272 # for "historical portability":
272 # for "historical portability":
273 # subsettable is defined in:
273 # subsettable is defined in:
274 # - branchmap since 2.9 (or 175c6fd8cacc)
274 # - branchmap since 2.9 (or 175c6fd8cacc)
275 # - repoview since 2.5 (or 59a9f18d4587)
275 # - repoview since 2.5 (or 59a9f18d4587)
276 for mod in (branchmap, repoview):
276 for mod in (branchmap, repoview):
277 subsettable = getattr(mod, 'subsettable', None)
277 subsettable = getattr(mod, 'subsettable', None)
278 if subsettable:
278 if subsettable:
279 return subsettable
279 return subsettable
280
280
281 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
281 # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
282 # branchmap and repoview modules exist, but subsettable attribute
282 # branchmap and repoview modules exist, but subsettable attribute
283 # doesn't)
283 # doesn't)
284 raise error.Abort(("perfbranchmap not available with this Mercurial"),
284 raise error.Abort(("perfbranchmap not available with this Mercurial"),
285 hint="use 2.5 or later")
285 hint="use 2.5 or later")
286
286
287 def getsvfs(repo):
287 def getsvfs(repo):
288 """Return appropriate object to access files under .hg/store
288 """Return appropriate object to access files under .hg/store
289 """
289 """
290 # for "historical portability":
290 # for "historical portability":
291 # repo.svfs has been available since 2.3 (or 7034365089bf)
291 # repo.svfs has been available since 2.3 (or 7034365089bf)
292 svfs = getattr(repo, 'svfs', None)
292 svfs = getattr(repo, 'svfs', None)
293 if svfs:
293 if svfs:
294 return svfs
294 return svfs
295 else:
295 else:
296 return getattr(repo, 'sopener')
296 return getattr(repo, 'sopener')
297
297
298 def getvfs(repo):
298 def getvfs(repo):
299 """Return appropriate object to access files under .hg
299 """Return appropriate object to access files under .hg
300 """
300 """
301 # for "historical portability":
301 # for "historical portability":
302 # repo.vfs has been available since 2.3 (or 7034365089bf)
302 # repo.vfs has been available since 2.3 (or 7034365089bf)
303 vfs = getattr(repo, 'vfs', None)
303 vfs = getattr(repo, 'vfs', None)
304 if vfs:
304 if vfs:
305 return vfs
305 return vfs
306 else:
306 else:
307 return getattr(repo, 'opener')
307 return getattr(repo, 'opener')
308
308
309 def repocleartagscachefunc(repo):
309 def repocleartagscachefunc(repo):
310 """Return the function to clear tags cache according to repo internal API
310 """Return the function to clear tags cache according to repo internal API
311 """
311 """
312 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
312 if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
313 # in this case, setattr(repo, '_tagscache', None) or so isn't
313 # in this case, setattr(repo, '_tagscache', None) or so isn't
314 # correct way to clear tags cache, because existing code paths
314 # correct way to clear tags cache, because existing code paths
315 # expect _tagscache to be a structured object.
315 # expect _tagscache to be a structured object.
316 def clearcache():
316 def clearcache():
317 # _tagscache has been filteredpropertycache since 2.5 (or
317 # _tagscache has been filteredpropertycache since 2.5 (or
318 # 98c867ac1330), and delattr() can't work in such case
318 # 98c867ac1330), and delattr() can't work in such case
319 if '_tagscache' in vars(repo):
319 if '_tagscache' in vars(repo):
320 del repo.__dict__['_tagscache']
320 del repo.__dict__['_tagscache']
321 return clearcache
321 return clearcache
322
322
323 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
323 repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
324 if repotags: # since 1.4 (or 5614a628d173)
324 if repotags: # since 1.4 (or 5614a628d173)
325 return lambda : repotags.set(None)
325 return lambda : repotags.set(None)
326
326
327 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
327 repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
328 if repotagscache: # since 0.6 (or d7df759d0e97)
328 if repotagscache: # since 0.6 (or d7df759d0e97)
329 return lambda : repotagscache.set(None)
329 return lambda : repotagscache.set(None)
330
330
331 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
331 # Mercurial earlier than 0.6 (or d7df759d0e97) logically reaches
332 # this point, but it isn't so problematic, because:
332 # this point, but it isn't so problematic, because:
333 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
333 # - repo.tags of such Mercurial isn't "callable", and repo.tags()
334 # in perftags() causes failure soon
334 # in perftags() causes failure soon
335 # - perf.py itself has been available since 1.1 (or eb240755386d)
335 # - perf.py itself has been available since 1.1 (or eb240755386d)
336 raise error.Abort(("tags API of this hg command is unknown"))
336 raise error.Abort(("tags API of this hg command is unknown"))
337
337
338 # perf commands
338 # perf commands
339
339
340 @command('perfwalk', formatteropts)
340 @command('perfwalk', formatteropts)
341 def perfwalk(ui, repo, *pats, **opts):
341 def perfwalk(ui, repo, *pats, **opts):
342 timer, fm = gettimer(ui, opts)
342 timer, fm = gettimer(ui, opts)
343 try:
343 try:
344 m = scmutil.match(repo[None], pats, {})
344 m = scmutil.match(repo[None], pats, {})
345 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
345 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
346 except Exception:
346 except Exception:
347 try:
347 try:
348 m = scmutil.match(repo[None], pats, {})
348 m = scmutil.match(repo[None], pats, {})
349 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
349 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
350 except Exception:
350 except Exception:
351 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
351 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
352 fm.end()
352 fm.end()
353
353
354 @command('perfannotate', formatteropts)
354 @command('perfannotate', formatteropts)
355 def perfannotate(ui, repo, f, **opts):
355 def perfannotate(ui, repo, f, **opts):
356 timer, fm = gettimer(ui, opts)
356 timer, fm = gettimer(ui, opts)
357 fc = repo['.'][f]
357 fc = repo['.'][f]
358 timer(lambda: len(fc.annotate(True)))
358 timer(lambda: len(fc.annotate(True)))
359 fm.end()
359 fm.end()
360
360
361 @command('perfstatus',
361 @command('perfstatus',
362 [('u', 'unknown', False,
362 [('u', 'unknown', False,
363 'ask status to look for unknown files')] + formatteropts)
363 'ask status to look for unknown files')] + formatteropts)
364 def perfstatus(ui, repo, **opts):
364 def perfstatus(ui, repo, **opts):
365 #m = match.always(repo.root, repo.getcwd())
365 #m = match.always(repo.root, repo.getcwd())
366 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
366 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
367 # False))))
367 # False))))
368 timer, fm = gettimer(ui, opts)
368 timer, fm = gettimer(ui, opts)
369 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
369 timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
370 fm.end()
370 fm.end()
371
371
372 @command('perfaddremove', formatteropts)
372 @command('perfaddremove', formatteropts)
373 def perfaddremove(ui, repo, **opts):
373 def perfaddremove(ui, repo, **opts):
374 timer, fm = gettimer(ui, opts)
374 timer, fm = gettimer(ui, opts)
375 try:
375 try:
376 oldquiet = repo.ui.quiet
376 oldquiet = repo.ui.quiet
377 repo.ui.quiet = True
377 repo.ui.quiet = True
378 matcher = scmutil.match(repo[None])
378 matcher = scmutil.match(repo[None])
379 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
379 timer(lambda: scmutil.addremove(repo, matcher, "", dry_run=True))
380 finally:
380 finally:
381 repo.ui.quiet = oldquiet
381 repo.ui.quiet = oldquiet
382 fm.end()
382 fm.end()
383
383
384 def clearcaches(cl):
384 def clearcaches(cl):
385 # behave somewhat consistently across internal API changes
385 # behave somewhat consistently across internal API changes
386 if util.safehasattr(cl, 'clearcaches'):
386 if util.safehasattr(cl, 'clearcaches'):
387 cl.clearcaches()
387 cl.clearcaches()
388 elif util.safehasattr(cl, '_nodecache'):
388 elif util.safehasattr(cl, '_nodecache'):
389 from mercurial.node import nullid, nullrev
389 from mercurial.node import nullid, nullrev
390 cl._nodecache = {nullid: nullrev}
390 cl._nodecache = {nullid: nullrev}
391 cl._nodepos = None
391 cl._nodepos = None
392
392
393 @command('perfheads', formatteropts)
393 @command('perfheads', formatteropts)
394 def perfheads(ui, repo, **opts):
394 def perfheads(ui, repo, **opts):
395 timer, fm = gettimer(ui, opts)
395 timer, fm = gettimer(ui, opts)
396 cl = repo.changelog
396 cl = repo.changelog
397 def d():
397 def d():
398 len(cl.headrevs())
398 len(cl.headrevs())
399 clearcaches(cl)
399 clearcaches(cl)
400 timer(d)
400 timer(d)
401 fm.end()
401 fm.end()
402
402
403 @command('perftags', formatteropts)
403 @command('perftags', formatteropts)
404 def perftags(ui, repo, **opts):
404 def perftags(ui, repo, **opts):
405 import mercurial.changelog
405 import mercurial.changelog
406 import mercurial.manifest
406 import mercurial.manifest
407 timer, fm = gettimer(ui, opts)
407 timer, fm = gettimer(ui, opts)
408 svfs = getsvfs(repo)
408 svfs = getsvfs(repo)
409 repocleartagscache = repocleartagscachefunc(repo)
409 repocleartagscache = repocleartagscachefunc(repo)
410 def t():
410 def t():
411 repo.changelog = mercurial.changelog.changelog(svfs)
411 repo.changelog = mercurial.changelog.changelog(svfs)
412 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
412 repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
413 repocleartagscache()
413 repocleartagscache()
414 return len(repo.tags())
414 return len(repo.tags())
415 timer(t)
415 timer(t)
416 fm.end()
416 fm.end()
417
417
418 @command('perfancestors', formatteropts)
418 @command('perfancestors', formatteropts)
419 def perfancestors(ui, repo, **opts):
419 def perfancestors(ui, repo, **opts):
420 timer, fm = gettimer(ui, opts)
420 timer, fm = gettimer(ui, opts)
421 heads = repo.changelog.headrevs()
421 heads = repo.changelog.headrevs()
422 def d():
422 def d():
423 for a in repo.changelog.ancestors(heads):
423 for a in repo.changelog.ancestors(heads):
424 pass
424 pass
425 timer(d)
425 timer(d)
426 fm.end()
426 fm.end()
427
427
428 @command('perfancestorset', formatteropts)
428 @command('perfancestorset', formatteropts)
429 def perfancestorset(ui, repo, revset, **opts):
429 def perfancestorset(ui, repo, revset, **opts):
430 timer, fm = gettimer(ui, opts)
430 timer, fm = gettimer(ui, opts)
431 revs = repo.revs(revset)
431 revs = repo.revs(revset)
432 heads = repo.changelog.headrevs()
432 heads = repo.changelog.headrevs()
433 def d():
433 def d():
434 s = repo.changelog.ancestors(heads)
434 s = repo.changelog.ancestors(heads)
435 for rev in revs:
435 for rev in revs:
436 rev in s
436 rev in s
437 timer(d)
437 timer(d)
438 fm.end()
438 fm.end()
439
439
440 @command('perfchangegroupchangelog', formatteropts +
440 @command('perfchangegroupchangelog', formatteropts +
441 [('', 'version', '02', 'changegroup version'),
441 [('', 'version', '02', 'changegroup version'),
442 ('r', 'rev', '', 'revisions to add to changegroup')])
442 ('r', 'rev', '', 'revisions to add to changegroup')])
443 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
443 def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
444 """Benchmark producing a changelog group for a changegroup.
444 """Benchmark producing a changelog group for a changegroup.
445
445
446 This measures the time spent processing the changelog during a
446 This measures the time spent processing the changelog during a
447 bundle operation. This occurs during `hg bundle` and on a server
447 bundle operation. This occurs during `hg bundle` and on a server
448 processing a `getbundle` wire protocol request (handles clones
448 processing a `getbundle` wire protocol request (handles clones
449 and pull requests).
449 and pull requests).
450
450
451 By default, all revisions are added to the changegroup.
451 By default, all revisions are added to the changegroup.
452 """
452 """
453 cl = repo.changelog
453 cl = repo.changelog
454 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
454 revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
455 bundler = changegroup.getbundler(version, repo)
455 bundler = changegroup.getbundler(version, repo)
456
456
457 def lookup(node):
457 def lookup(node):
458 # The real bundler reads the revision in order to access the
458 # The real bundler reads the revision in order to access the
459 # manifest node and files list. Do that here.
459 # manifest node and files list. Do that here.
460 cl.read(node)
460 cl.read(node)
461 return node
461 return node
462
462
463 def d():
463 def d():
464 for chunk in bundler.group(revs, cl, lookup):
464 for chunk in bundler.group(revs, cl, lookup):
465 pass
465 pass
466
466
467 timer, fm = gettimer(ui, opts)
467 timer, fm = gettimer(ui, opts)
468 timer(d)
468 timer(d)
469 fm.end()
469 fm.end()
470
470
471 @command('perfdirs', formatteropts)
471 @command('perfdirs', formatteropts)
472 def perfdirs(ui, repo, **opts):
472 def perfdirs(ui, repo, **opts):
473 timer, fm = gettimer(ui, opts)
473 timer, fm = gettimer(ui, opts)
474 dirstate = repo.dirstate
474 dirstate = repo.dirstate
475 'a' in dirstate
475 'a' in dirstate
476 def d():
476 def d():
477 dirstate.dirs()
477 dirstate.dirs()
478 del dirstate._dirs
478 del dirstate._dirs
479 timer(d)
479 timer(d)
480 fm.end()
480 fm.end()
481
481
482 @command('perfdirstate', formatteropts)
482 @command('perfdirstate', formatteropts)
483 def perfdirstate(ui, repo, **opts):
483 def perfdirstate(ui, repo, **opts):
484 timer, fm = gettimer(ui, opts)
484 timer, fm = gettimer(ui, opts)
485 "a" in repo.dirstate
485 "a" in repo.dirstate
486 def d():
486 def d():
487 repo.dirstate.invalidate()
487 repo.dirstate.invalidate()
488 "a" in repo.dirstate
488 "a" in repo.dirstate
489 timer(d)
489 timer(d)
490 fm.end()
490 fm.end()
491
491
492 @command('perfdirstatedirs', formatteropts)
492 @command('perfdirstatedirs', formatteropts)
493 def perfdirstatedirs(ui, repo, **opts):
493 def perfdirstatedirs(ui, repo, **opts):
494 timer, fm = gettimer(ui, opts)
494 timer, fm = gettimer(ui, opts)
495 "a" in repo.dirstate
495 "a" in repo.dirstate
496 def d():
496 def d():
497 "a" in repo.dirstate._dirs
497 "a" in repo.dirstate._dirs
498 del repo.dirstate._dirs
498 del repo.dirstate._dirs
499 timer(d)
499 timer(d)
500 fm.end()
500 fm.end()
501
501
502 @command('perfdirstatefoldmap', formatteropts)
502 @command('perfdirstatefoldmap', formatteropts)
503 def perfdirstatefoldmap(ui, repo, **opts):
503 def perfdirstatefoldmap(ui, repo, **opts):
504 timer, fm = gettimer(ui, opts)
504 timer, fm = gettimer(ui, opts)
505 dirstate = repo.dirstate
505 dirstate = repo.dirstate
506 'a' in dirstate
506 'a' in dirstate
507 def d():
507 def d():
508 dirstate._filefoldmap.get('a')
508 dirstate._filefoldmap.get('a')
509 del dirstate._filefoldmap
509 del dirstate._filefoldmap
510 timer(d)
510 timer(d)
511 fm.end()
511 fm.end()
512
512
513 @command('perfdirfoldmap', formatteropts)
513 @command('perfdirfoldmap', formatteropts)
514 def perfdirfoldmap(ui, repo, **opts):
514 def perfdirfoldmap(ui, repo, **opts):
515 timer, fm = gettimer(ui, opts)
515 timer, fm = gettimer(ui, opts)
516 dirstate = repo.dirstate
516 dirstate = repo.dirstate
517 'a' in dirstate
517 'a' in dirstate
518 def d():
518 def d():
519 dirstate._dirfoldmap.get('a')
519 dirstate._dirfoldmap.get('a')
520 del dirstate._dirfoldmap
520 del dirstate._dirfoldmap
521 del dirstate._dirs
521 del dirstate._dirs
522 timer(d)
522 timer(d)
523 fm.end()
523 fm.end()
524
524
525 @command('perfdirstatewrite', formatteropts)
525 @command('perfdirstatewrite', formatteropts)
526 def perfdirstatewrite(ui, repo, **opts):
526 def perfdirstatewrite(ui, repo, **opts):
527 timer, fm = gettimer(ui, opts)
527 timer, fm = gettimer(ui, opts)
528 ds = repo.dirstate
528 ds = repo.dirstate
529 "a" in ds
529 "a" in ds
530 def d():
530 def d():
531 ds._dirty = True
531 ds._dirty = True
532 ds.write(repo.currenttransaction())
532 ds.write(repo.currenttransaction())
533 timer(d)
533 timer(d)
534 fm.end()
534 fm.end()
535
535
536 @command('perfmergecalculate',
536 @command('perfmergecalculate',
537 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
537 [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
538 def perfmergecalculate(ui, repo, rev, **opts):
538 def perfmergecalculate(ui, repo, rev, **opts):
539 timer, fm = gettimer(ui, opts)
539 timer, fm = gettimer(ui, opts)
540 wctx = repo[None]
540 wctx = repo[None]
541 rctx = scmutil.revsingle(repo, rev, rev)
541 rctx = scmutil.revsingle(repo, rev, rev)
542 ancestor = wctx.ancestor(rctx)
542 ancestor = wctx.ancestor(rctx)
543 # we don't want working dir files to be stat'd in the benchmark, so prime
543 # we don't want working dir files to be stat'd in the benchmark, so prime
544 # that cache
544 # that cache
545 wctx.dirty()
545 wctx.dirty()
546 def d():
546 def d():
547 # acceptremote is True because we don't want prompts in the middle of
547 # acceptremote is True because we don't want prompts in the middle of
548 # our benchmark
548 # our benchmark
549 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
549 merge.calculateupdates(repo, wctx, rctx, [ancestor], False, False,
550 acceptremote=True, followcopies=True)
550 acceptremote=True, followcopies=True)
551 timer(d)
551 timer(d)
552 fm.end()
552 fm.end()
553
553
554 @command('perfpathcopies', [], "REV REV")
554 @command('perfpathcopies', [], "REV REV")
555 def perfpathcopies(ui, repo, rev1, rev2, **opts):
555 def perfpathcopies(ui, repo, rev1, rev2, **opts):
556 timer, fm = gettimer(ui, opts)
556 timer, fm = gettimer(ui, opts)
557 ctx1 = scmutil.revsingle(repo, rev1, rev1)
557 ctx1 = scmutil.revsingle(repo, rev1, rev1)
558 ctx2 = scmutil.revsingle(repo, rev2, rev2)
558 ctx2 = scmutil.revsingle(repo, rev2, rev2)
559 def d():
559 def d():
560 copies.pathcopies(ctx1, ctx2)
560 copies.pathcopies(ctx1, ctx2)
561 timer(d)
561 timer(d)
562 fm.end()
562 fm.end()
563
563
564 @command('perfmanifest', [], 'REV')
564 @command('perfmanifest', [], 'REV')
565 def perfmanifest(ui, repo, rev, **opts):
565 def perfmanifest(ui, repo, rev, **opts):
566 timer, fm = gettimer(ui, opts)
566 timer, fm = gettimer(ui, opts)
567 ctx = scmutil.revsingle(repo, rev, rev)
567 ctx = scmutil.revsingle(repo, rev, rev)
568 t = ctx.manifestnode()
568 t = ctx.manifestnode()
569 def d():
569 def d():
570 repo.manifestlog.clearcaches()
570 repo.manifestlog.clearcaches()
571 repo.manifestlog[t].read()
571 repo.manifestlog[t].read()
572 timer(d)
572 timer(d)
573 fm.end()
573 fm.end()
574
574
575 @command('perfchangeset', formatteropts)
575 @command('perfchangeset', formatteropts)
576 def perfchangeset(ui, repo, rev, **opts):
576 def perfchangeset(ui, repo, rev, **opts):
577 timer, fm = gettimer(ui, opts)
577 timer, fm = gettimer(ui, opts)
578 n = repo[rev].node()
578 n = repo[rev].node()
579 def d():
579 def d():
580 repo.changelog.read(n)
580 repo.changelog.read(n)
581 #repo.changelog._cache = None
581 #repo.changelog._cache = None
582 timer(d)
582 timer(d)
583 fm.end()
583 fm.end()
584
584
585 @command('perfindex', formatteropts)
585 @command('perfindex', formatteropts)
586 def perfindex(ui, repo, **opts):
586 def perfindex(ui, repo, **opts):
587 import mercurial.revlog
587 import mercurial.revlog
588 timer, fm = gettimer(ui, opts)
588 timer, fm = gettimer(ui, opts)
589 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
589 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
590 n = repo["tip"].node()
590 n = repo["tip"].node()
591 svfs = getsvfs(repo)
591 svfs = getsvfs(repo)
592 def d():
592 def d():
593 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
593 cl = mercurial.revlog.revlog(svfs, "00changelog.i")
594 cl.rev(n)
594 cl.rev(n)
595 timer(d)
595 timer(d)
596 fm.end()
596 fm.end()
597
597
598 @command('perfstartup', formatteropts)
598 @command('perfstartup', formatteropts)
599 def perfstartup(ui, repo, **opts):
599 def perfstartup(ui, repo, **opts):
600 timer, fm = gettimer(ui, opts)
600 timer, fm = gettimer(ui, opts)
601 cmd = sys.argv[0]
601 cmd = sys.argv[0]
602 def d():
602 def d():
603 if os.name != 'nt':
603 if os.name != 'nt':
604 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
604 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
605 else:
605 else:
606 os.environ['HGRCPATH'] = ''
606 os.environ['HGRCPATH'] = ''
607 os.system("%s version -q > NUL" % cmd)
607 os.system("%s version -q > NUL" % cmd)
608 timer(d)
608 timer(d)
609 fm.end()
609 fm.end()
610
610
611 @command('perfparents', formatteropts)
611 @command('perfparents', formatteropts)
612 def perfparents(ui, repo, **opts):
612 def perfparents(ui, repo, **opts):
613 timer, fm = gettimer(ui, opts)
613 timer, fm = gettimer(ui, opts)
614 # control the number of commits perfparents iterates over
614 # control the number of commits perfparents iterates over
615 # experimental config: perf.parentscount
615 # experimental config: perf.parentscount
616 count = getint(ui, "perf", "parentscount", 1000)
616 count = getint(ui, "perf", "parentscount", 1000)
617 if len(repo.changelog) < count:
617 if len(repo.changelog) < count:
618 raise error.Abort("repo needs %d commits for this test" % count)
618 raise error.Abort("repo needs %d commits for this test" % count)
619 repo = repo.unfiltered()
619 repo = repo.unfiltered()
620 nl = [repo.changelog.node(i) for i in xrange(count)]
620 nl = [repo.changelog.node(i) for i in xrange(count)]
621 def d():
621 def d():
622 for n in nl:
622 for n in nl:
623 repo.changelog.parents(n)
623 repo.changelog.parents(n)
624 timer(d)
624 timer(d)
625 fm.end()
625 fm.end()
626
626
627 @command('perfctxfiles', formatteropts)
627 @command('perfctxfiles', formatteropts)
628 def perfctxfiles(ui, repo, x, **opts):
628 def perfctxfiles(ui, repo, x, **opts):
629 x = int(x)
629 x = int(x)
630 timer, fm = gettimer(ui, opts)
630 timer, fm = gettimer(ui, opts)
631 def d():
631 def d():
632 len(repo[x].files())
632 len(repo[x].files())
633 timer(d)
633 timer(d)
634 fm.end()
634 fm.end()
635
635
636 @command('perfrawfiles', formatteropts)
636 @command('perfrawfiles', formatteropts)
637 def perfrawfiles(ui, repo, x, **opts):
637 def perfrawfiles(ui, repo, x, **opts):
638 x = int(x)
638 x = int(x)
639 timer, fm = gettimer(ui, opts)
639 timer, fm = gettimer(ui, opts)
640 cl = repo.changelog
640 cl = repo.changelog
641 def d():
641 def d():
642 len(cl.read(x)[3])
642 len(cl.read(x)[3])
643 timer(d)
643 timer(d)
644 fm.end()
644 fm.end()
645
645
646 @command('perflookup', formatteropts)
646 @command('perflookup', formatteropts)
647 def perflookup(ui, repo, rev, **opts):
647 def perflookup(ui, repo, rev, **opts):
648 timer, fm = gettimer(ui, opts)
648 timer, fm = gettimer(ui, opts)
649 timer(lambda: len(repo.lookup(rev)))
649 timer(lambda: len(repo.lookup(rev)))
650 fm.end()
650 fm.end()
651
651
652 @command('perfrevrange', formatteropts)
652 @command('perfrevrange', formatteropts)
653 def perfrevrange(ui, repo, *specs, **opts):
653 def perfrevrange(ui, repo, *specs, **opts):
654 timer, fm = gettimer(ui, opts)
654 timer, fm = gettimer(ui, opts)
655 revrange = scmutil.revrange
655 revrange = scmutil.revrange
656 timer(lambda: len(revrange(repo, specs)))
656 timer(lambda: len(revrange(repo, specs)))
657 fm.end()
657 fm.end()
658
658
659 @command('perfnodelookup', formatteropts)
659 @command('perfnodelookup', formatteropts)
660 def perfnodelookup(ui, repo, rev, **opts):
660 def perfnodelookup(ui, repo, rev, **opts):
661 timer, fm = gettimer(ui, opts)
661 timer, fm = gettimer(ui, opts)
662 import mercurial.revlog
662 import mercurial.revlog
663 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
663 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
664 n = repo[rev].node()
664 n = repo[rev].node()
665 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
665 cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
666 def d():
666 def d():
667 cl.rev(n)
667 cl.rev(n)
668 clearcaches(cl)
668 clearcaches(cl)
669 timer(d)
669 timer(d)
670 fm.end()
670 fm.end()
671
671
672 @command('perflog',
672 @command('perflog',
673 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
673 [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
674 def perflog(ui, repo, rev=None, **opts):
674 def perflog(ui, repo, rev=None, **opts):
675 if rev is None:
675 if rev is None:
676 rev=[]
676 rev=[]
677 timer, fm = gettimer(ui, opts)
677 timer, fm = gettimer(ui, opts)
678 ui.pushbuffer()
678 ui.pushbuffer()
679 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
679 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
680 copies=opts.get('rename')))
680 copies=opts.get('rename')))
681 ui.popbuffer()
681 ui.popbuffer()
682 fm.end()
682 fm.end()
683
683
684 @command('perfmoonwalk', formatteropts)
684 @command('perfmoonwalk', formatteropts)
685 def perfmoonwalk(ui, repo, **opts):
685 def perfmoonwalk(ui, repo, **opts):
686 """benchmark walking the changelog backwards
686 """benchmark walking the changelog backwards
687
687
688 This also loads the changelog data for each revision in the changelog.
688 This also loads the changelog data for each revision in the changelog.
689 """
689 """
690 timer, fm = gettimer(ui, opts)
690 timer, fm = gettimer(ui, opts)
691 def moonwalk():
691 def moonwalk():
692 for i in xrange(len(repo), -1, -1):
692 for i in xrange(len(repo), -1, -1):
693 ctx = repo[i]
693 ctx = repo[i]
694 ctx.branch() # read changelog data (in addition to the index)
694 ctx.branch() # read changelog data (in addition to the index)
695 timer(moonwalk)
695 timer(moonwalk)
696 fm.end()
696 fm.end()
697
697
698 @command('perftemplating', formatteropts)
698 @command('perftemplating', formatteropts)
699 def perftemplating(ui, repo, rev=None, **opts):
699 def perftemplating(ui, repo, rev=None, **opts):
700 if rev is None:
700 if rev is None:
701 rev=[]
701 rev=[]
702 timer, fm = gettimer(ui, opts)
702 timer, fm = gettimer(ui, opts)
703 ui.pushbuffer()
703 ui.pushbuffer()
704 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
704 timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
705 template='{date|shortdate} [{rev}:{node|short}]'
705 template='{date|shortdate} [{rev}:{node|short}]'
706 ' {author|person}: {desc|firstline}\n'))
706 ' {author|person}: {desc|firstline}\n'))
707 ui.popbuffer()
707 ui.popbuffer()
708 fm.end()
708 fm.end()
709
709
710 @command('perfcca', formatteropts)
710 @command('perfcca', formatteropts)
711 def perfcca(ui, repo, **opts):
711 def perfcca(ui, repo, **opts):
712 timer, fm = gettimer(ui, opts)
712 timer, fm = gettimer(ui, opts)
713 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
713 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
714 fm.end()
714 fm.end()
715
715
716 @command('perffncacheload', formatteropts)
716 @command('perffncacheload', formatteropts)
717 def perffncacheload(ui, repo, **opts):
717 def perffncacheload(ui, repo, **opts):
718 timer, fm = gettimer(ui, opts)
718 timer, fm = gettimer(ui, opts)
719 s = repo.store
719 s = repo.store
720 def d():
720 def d():
721 s.fncache._load()
721 s.fncache._load()
722 timer(d)
722 timer(d)
723 fm.end()
723 fm.end()
724
724
725 @command('perffncachewrite', formatteropts)
725 @command('perffncachewrite', formatteropts)
726 def perffncachewrite(ui, repo, **opts):
726 def perffncachewrite(ui, repo, **opts):
727 timer, fm = gettimer(ui, opts)
727 timer, fm = gettimer(ui, opts)
728 s = repo.store
728 s = repo.store
729 s.fncache._load()
729 s.fncache._load()
730 lock = repo.lock()
730 lock = repo.lock()
731 tr = repo.transaction('perffncachewrite')
731 tr = repo.transaction('perffncachewrite')
732 def d():
732 def d():
733 s.fncache._dirty = True
733 s.fncache._dirty = True
734 s.fncache.write(tr)
734 s.fncache.write(tr)
735 timer(d)
735 timer(d)
736 tr.close()
736 tr.close()
737 lock.release()
737 lock.release()
738 fm.end()
738 fm.end()
739
739
740 @command('perffncacheencode', formatteropts)
740 @command('perffncacheencode', formatteropts)
741 def perffncacheencode(ui, repo, **opts):
741 def perffncacheencode(ui, repo, **opts):
742 timer, fm = gettimer(ui, opts)
742 timer, fm = gettimer(ui, opts)
743 s = repo.store
743 s = repo.store
744 s.fncache._load()
744 s.fncache._load()
745 def d():
745 def d():
746 for p in s.fncache.entries:
746 for p in s.fncache.entries:
747 s.encode(p)
747 s.encode(p)
748 timer(d)
748 timer(d)
749 fm.end()
749 fm.end()
750
750
751 @command('perfbdiff', revlogopts + formatteropts + [
751 @command('perfbdiff', revlogopts + formatteropts + [
752 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
752 ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
753 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
753 ('', 'alldata', False, 'test bdiffs for all associated revisions')],
754 '-c|-m|FILE REV')
754 '-c|-m|FILE REV')
755 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
755 def perfbdiff(ui, repo, file_, rev=None, count=None, **opts):
756 """benchmark a bdiff between revisions
756 """benchmark a bdiff between revisions
757
757
758 By default, benchmark a bdiff between its delta parent and itself.
758 By default, benchmark a bdiff between its delta parent and itself.
759
759
760 With ``--count``, benchmark bdiffs between delta parents and self for N
760 With ``--count``, benchmark bdiffs between delta parents and self for N
761 revisions starting at the specified revision.
761 revisions starting at the specified revision.
762
762
763 With ``--alldata``, assume the requested revision is a changeset and
763 With ``--alldata``, assume the requested revision is a changeset and
764 measure bdiffs for all changes related to that changeset (manifest
764 measure bdiffs for all changes related to that changeset (manifest
765 and filelogs).
765 and filelogs).
766 """
766 """
767 if opts['alldata']:
767 if opts['alldata']:
768 opts['changelog'] = True
768 opts['changelog'] = True
769
769
770 if opts.get('changelog') or opts.get('manifest'):
770 if opts.get('changelog') or opts.get('manifest'):
771 file_, rev = None, file_
771 file_, rev = None, file_
772 elif rev is None:
772 elif rev is None:
773 raise error.CommandError('perfbdiff', 'invalid arguments')
773 raise error.CommandError('perfbdiff', 'invalid arguments')
774
774
775 textpairs = []
775 textpairs = []
776
776
777 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
777 r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
778
778
779 startrev = r.rev(r.lookup(rev))
779 startrev = r.rev(r.lookup(rev))
780 for rev in range(startrev, min(startrev + count, len(r) - 1)):
780 for rev in range(startrev, min(startrev + count, len(r) - 1)):
781 if opts['alldata']:
781 if opts['alldata']:
782 # Load revisions associated with changeset.
782 # Load revisions associated with changeset.
783 ctx = repo[rev]
783 ctx = repo[rev]
784 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
784 mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
785 for pctx in ctx.parents():
785 for pctx in ctx.parents():
786 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
786 pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
787 textpairs.append((pman, mtext))
787 textpairs.append((pman, mtext))
788
788
789 # Load filelog revisions by iterating manifest delta.
789 # Load filelog revisions by iterating manifest delta.
790 man = ctx.manifest()
790 man = ctx.manifest()
791 pman = ctx.p1().manifest()
791 pman = ctx.p1().manifest()
792 for filename, change in pman.diff(man).items():
792 for filename, change in pman.diff(man).items():
793 fctx = repo.file(filename)
793 fctx = repo.file(filename)
794 f1 = fctx.revision(change[0][0] or -1)
794 f1 = fctx.revision(change[0][0] or -1)
795 f2 = fctx.revision(change[1][0] or -1)
795 f2 = fctx.revision(change[1][0] or -1)
796 textpairs.append((f1, f2))
796 textpairs.append((f1, f2))
797 else:
797 else:
798 dp = r.deltaparent(rev)
798 dp = r.deltaparent(rev)
799 textpairs.append((r.revision(dp), r.revision(rev)))
799 textpairs.append((r.revision(dp), r.revision(rev)))
800
800
801 def d():
801 def d():
802 for pair in textpairs:
802 for pair in textpairs:
803 bdiff.bdiff(*pair)
803 bdiff.bdiff(*pair)
804
804
805 timer, fm = gettimer(ui, opts)
805 timer, fm = gettimer(ui, opts)
806 timer(d)
806 timer(d)
807 fm.end()
807 fm.end()
808
808
809 @command('perfdiffwd', formatteropts)
809 @command('perfdiffwd', formatteropts)
810 def perfdiffwd(ui, repo, **opts):
810 def perfdiffwd(ui, repo, **opts):
811 """Profile diff of working directory changes"""
811 """Profile diff of working directory changes"""
812 timer, fm = gettimer(ui, opts)
812 timer, fm = gettimer(ui, opts)
813 options = {
813 options = {
814 'w': 'ignore_all_space',
814 'w': 'ignore_all_space',
815 'b': 'ignore_space_change',
815 'b': 'ignore_space_change',
816 'B': 'ignore_blank_lines',
816 'B': 'ignore_blank_lines',
817 }
817 }
818
818
819 for diffopt in ('', 'w', 'b', 'B', 'wB'):
819 for diffopt in ('', 'w', 'b', 'B', 'wB'):
820 opts = dict((options[c], '1') for c in diffopt)
820 opts = dict((options[c], '1') for c in diffopt)
821 def d():
821 def d():
822 ui.pushbuffer()
822 ui.pushbuffer()
823 commands.diff(ui, repo, **opts)
823 commands.diff(ui, repo, **opts)
824 ui.popbuffer()
824 ui.popbuffer()
825 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
825 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
826 timer(d, title)
826 timer(d, title)
827 fm.end()
827 fm.end()
828
828
829 @command('perfrevlog', revlogopts + formatteropts +
829 @command('perfrevlog', revlogopts + formatteropts +
830 [('d', 'dist', 100, 'distance between the revisions'),
830 [('d', 'dist', 100, 'distance between the revisions'),
831 ('s', 'startrev', 0, 'revision to start reading at'),
831 ('s', 'startrev', 0, 'revision to start reading at'),
832 ('', 'reverse', False, 'read in reverse')],
832 ('', 'reverse', False, 'read in reverse')],
833 '-c|-m|FILE')
833 '-c|-m|FILE')
834 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
834 def perfrevlog(ui, repo, file_=None, startrev=0, reverse=False, **opts):
835 """Benchmark reading a series of revisions from a revlog.
835 """Benchmark reading a series of revisions from a revlog.
836
836
837 By default, we read every ``-d/--dist`` revision from 0 to tip of
837 By default, we read every ``-d/--dist`` revision from 0 to tip of
838 the specified revlog.
838 the specified revlog.
839
839
840 The start revision can be defined via ``-s/--startrev``.
840 The start revision can be defined via ``-s/--startrev``.
841 """
841 """
842 timer, fm = gettimer(ui, opts)
842 timer, fm = gettimer(ui, opts)
843 _len = getlen(ui)
843 _len = getlen(ui)
844
844
845 def d():
845 def d():
846 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
846 r = cmdutil.openrevlog(repo, 'perfrevlog', file_, opts)
847
847
848 startrev = 0
848 startrev = 0
849 endrev = _len(r)
849 endrev = _len(r)
850 dist = opts['dist']
850 dist = opts['dist']
851
851
852 if reverse:
852 if reverse:
853 startrev, endrev = endrev, startrev
853 startrev, endrev = endrev, startrev
854 dist = -1 * dist
854 dist = -1 * dist
855
855
856 for x in xrange(startrev, endrev, dist):
856 for x in xrange(startrev, endrev, dist):
857 r.revision(r.node(x))
857 r.revision(r.node(x))
858
858
859 timer(d)
859 timer(d)
860 fm.end()
860 fm.end()
861
861
862 @command('perfrevlogchunks', revlogopts + formatteropts +
862 @command('perfrevlogchunks', revlogopts + formatteropts +
863 [('s', 'startrev', 0, 'revision to start at')],
863 [('s', 'startrev', 0, 'revision to start at')],
864 '-c|-m|FILE')
864 '-c|-m|FILE')
865 def perfrevlogchunks(ui, repo, file_=None, startrev=0, **opts):
865 def perfrevlogchunks(ui, repo, file_=None, startrev=0, **opts):
866 """Benchmark operations on revlog chunks.
866 """Benchmark operations on revlog chunks.
867
867
868 Logically, each revlog is a collection of fulltext revisions. However,
868 Logically, each revlog is a collection of fulltext revisions. However,
869 stored within each revlog are "chunks" of possibly compressed data. This
869 stored within each revlog are "chunks" of possibly compressed data. This
870 data needs to be read and decompressed or compressed and written.
870 data needs to be read and decompressed or compressed and written.
871
871
872 This command measures the time it takes to read+decompress and recompress
872 This command measures the time it takes to read+decompress and recompress
873 chunks in a revlog. It effectively isolates I/O and compression performance.
873 chunks in a revlog. It effectively isolates I/O and compression performance.
874 For measurements of higher-level operations like resolving revisions,
874 For measurements of higher-level operations like resolving revisions,
875 see ``perfrevlog`` and ``perfrevlogrevision``.
875 see ``perfrevlog`` and ``perfrevlogrevision``.
876 """
876 """
877 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
877 rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
878 revs = list(rl.revs(startrev, len(rl) - 1))
878 revs = list(rl.revs(startrev, len(rl) - 1))
879
879
880 def rlfh(rl):
880 def rlfh(rl):
881 if rl._inline:
881 if rl._inline:
882 return getsvfs(repo)(rl.indexfile)
882 return getsvfs(repo)(rl.indexfile)
883 else:
883 else:
884 return getsvfs(repo)(rl.datafile)
884 return getsvfs(repo)(rl.datafile)
885
885
886 def doread():
886 def doread():
887 rl.clearcaches()
887 rl.clearcaches()
888 for rev in revs:
888 for rev in revs:
889 rl._chunkraw(rev, rev)
889 rl._chunkraw(rev, rev)
890
890
891 def doreadcachedfh():
891 def doreadcachedfh():
892 rl.clearcaches()
892 rl.clearcaches()
893 fh = rlfh(rl)
893 fh = rlfh(rl)
894 for rev in revs:
894 for rev in revs:
895 rl._chunkraw(rev, rev, df=fh)
895 rl._chunkraw(rev, rev, df=fh)
896
896
897 def doreadbatch():
897 def doreadbatch():
898 rl.clearcaches()
898 rl.clearcaches()
899 rl._chunkraw(revs[0], revs[-1])
899 rl._chunkraw(revs[0], revs[-1])
900
900
901 def doreadbatchcachedfh():
901 def doreadbatchcachedfh():
902 rl.clearcaches()
902 rl.clearcaches()
903 fh = rlfh(rl)
903 fh = rlfh(rl)
904 rl._chunkraw(revs[0], revs[-1], df=fh)
904 rl._chunkraw(revs[0], revs[-1], df=fh)
905
905
906 def dochunk():
906 def dochunk():
907 rl.clearcaches()
907 rl.clearcaches()
908 fh = rlfh(rl)
908 fh = rlfh(rl)
909 for rev in revs:
909 for rev in revs:
910 rl._chunk(rev, df=fh)
910 rl._chunk(rev, df=fh)
911
911
912 chunks = [None]
912 chunks = [None]
913
913
914 def dochunkbatch():
914 def dochunkbatch():
915 rl.clearcaches()
915 rl.clearcaches()
916 fh = rlfh(rl)
916 fh = rlfh(rl)
917 # Save chunks as a side-effect.
917 # Save chunks as a side-effect.
918 chunks[0] = rl._chunks(revs, df=fh)
918 chunks[0] = rl._chunks(revs, df=fh)
919
919
920 def docompress():
920 def docompress():
921 rl.clearcaches()
921 rl.clearcaches()
922 for chunk in chunks[0]:
922 for chunk in chunks[0]:
923 rl.compress(chunk)
923 rl.compress(chunk)
924
924
925 benches = [
925 benches = [
926 (lambda: doread(), 'read'),
926 (lambda: doread(), 'read'),
927 (lambda: doreadcachedfh(), 'read w/ reused fd'),
927 (lambda: doreadcachedfh(), 'read w/ reused fd'),
928 (lambda: doreadbatch(), 'read batch'),
928 (lambda: doreadbatch(), 'read batch'),
929 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
929 (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
930 (lambda: dochunk(), 'chunk'),
930 (lambda: dochunk(), 'chunk'),
931 (lambda: dochunkbatch(), 'chunk batch'),
931 (lambda: dochunkbatch(), 'chunk batch'),
932 (lambda: docompress(), 'compress'),
932 (lambda: docompress(), 'compress'),
933 ]
933 ]
934
934
935 for fn, title in benches:
935 for fn, title in benches:
936 timer, fm = gettimer(ui, opts)
936 timer, fm = gettimer(ui, opts)
937 timer(fn, title=title)
937 timer(fn, title=title)
938 fm.end()
938 fm.end()
939
939
940 @command('perfrevlogrevision', revlogopts + formatteropts +
940 @command('perfrevlogrevision', revlogopts + formatteropts +
941 [('', 'cache', False, 'use caches instead of clearing')],
941 [('', 'cache', False, 'use caches instead of clearing')],
942 '-c|-m|FILE REV')
942 '-c|-m|FILE REV')
943 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
943 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
944 """Benchmark obtaining a revlog revision.
944 """Benchmark obtaining a revlog revision.
945
945
946 Obtaining a revlog revision consists of roughly the following steps:
946 Obtaining a revlog revision consists of roughly the following steps:
947
947
948 1. Compute the delta chain
948 1. Compute the delta chain
949 2. Obtain the raw chunks for that delta chain
949 2. Obtain the raw chunks for that delta chain
950 3. Decompress each raw chunk
950 3. Decompress each raw chunk
951 4. Apply binary patches to obtain fulltext
951 4. Apply binary patches to obtain fulltext
952 5. Verify hash of fulltext
952 5. Verify hash of fulltext
953
953
954 This command measures the time spent in each of these phases.
954 This command measures the time spent in each of these phases.
955 """
955 """
956 if opts.get('changelog') or opts.get('manifest'):
956 if opts.get('changelog') or opts.get('manifest'):
957 file_, rev = None, file_
957 file_, rev = None, file_
958 elif rev is None:
958 elif rev is None:
959 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
959 raise error.CommandError('perfrevlogrevision', 'invalid arguments')
960
960
961 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
961 r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
962 node = r.lookup(rev)
962 node = r.lookup(rev)
963 rev = r.rev(node)
963 rev = r.rev(node)
964
964
965 def dodeltachain(rev):
965 def dodeltachain(rev):
966 if not cache:
966 if not cache:
967 r.clearcaches()
967 r.clearcaches()
968 r._deltachain(rev)
968 r._deltachain(rev)
969
969
970 def doread(chain):
970 def doread(chain):
971 if not cache:
971 if not cache:
972 r.clearcaches()
972 r.clearcaches()
973 r._chunkraw(chain[0], chain[-1])
973 r._chunkraw(chain[0], chain[-1])
974
974
975 def dodecompress(data, chain):
975 def dodecompress(data, chain):
976 if not cache:
976 if not cache:
977 r.clearcaches()
977 r.clearcaches()
978
978
979 start = r.start
979 start = r.start
980 length = r.length
980 length = r.length
981 inline = r._inline
981 inline = r._inline
982 iosize = r._io.size
982 iosize = r._io.size
983 buffer = util.buffer
983 buffer = util.buffer
984 offset = start(chain[0])
984 offset = start(chain[0])
985
985
986 for rev in chain:
986 for rev in chain:
987 chunkstart = start(rev)
987 chunkstart = start(rev)
988 if inline:
988 if inline:
989 chunkstart += (rev + 1) * iosize
989 chunkstart += (rev + 1) * iosize
990 chunklength = length(rev)
990 chunklength = length(rev)
991 b = buffer(data, chunkstart - offset, chunklength)
991 b = buffer(data, chunkstart - offset, chunklength)
992 revlog.decompress(b)
992 revlog.decompress(b)
993
993
994 def dopatch(text, bins):
994 def dopatch(text, bins):
995 if not cache:
995 if not cache:
996 r.clearcaches()
996 r.clearcaches()
997 mdiff.patches(text, bins)
997 mdiff.patches(text, bins)
998
998
999 def dohash(text):
999 def dohash(text):
1000 if not cache:
1000 if not cache:
1001 r.clearcaches()
1001 r.clearcaches()
1002 r._checkhash(text, node, rev)
1002 r.checkhash(text, node, rev=rev)
1003
1003
1004 def dorevision():
1004 def dorevision():
1005 if not cache:
1005 if not cache:
1006 r.clearcaches()
1006 r.clearcaches()
1007 r.revision(node)
1007 r.revision(node)
1008
1008
1009 chain = r._deltachain(rev)[0]
1009 chain = r._deltachain(rev)[0]
1010 data = r._chunkraw(chain[0], chain[-1])[1]
1010 data = r._chunkraw(chain[0], chain[-1])[1]
1011 bins = r._chunks(chain)
1011 bins = r._chunks(chain)
1012 text = str(bins[0])
1012 text = str(bins[0])
1013 bins = bins[1:]
1013 bins = bins[1:]
1014 text = mdiff.patches(text, bins)
1014 text = mdiff.patches(text, bins)
1015
1015
1016 benches = [
1016 benches = [
1017 (lambda: dorevision(), 'full'),
1017 (lambda: dorevision(), 'full'),
1018 (lambda: dodeltachain(rev), 'deltachain'),
1018 (lambda: dodeltachain(rev), 'deltachain'),
1019 (lambda: doread(chain), 'read'),
1019 (lambda: doread(chain), 'read'),
1020 (lambda: dodecompress(data, chain), 'decompress'),
1020 (lambda: dodecompress(data, chain), 'decompress'),
1021 (lambda: dopatch(text, bins), 'patch'),
1021 (lambda: dopatch(text, bins), 'patch'),
1022 (lambda: dohash(text), 'hash'),
1022 (lambda: dohash(text), 'hash'),
1023 ]
1023 ]
1024
1024
1025 for fn, title in benches:
1025 for fn, title in benches:
1026 timer, fm = gettimer(ui, opts)
1026 timer, fm = gettimer(ui, opts)
1027 timer(fn, title=title)
1027 timer(fn, title=title)
1028 fm.end()
1028 fm.end()
1029
1029
1030 @command('perfrevset',
1030 @command('perfrevset',
1031 [('C', 'clear', False, 'clear volatile cache between each call.'),
1031 [('C', 'clear', False, 'clear volatile cache between each call.'),
1032 ('', 'contexts', False, 'obtain changectx for each revision')]
1032 ('', 'contexts', False, 'obtain changectx for each revision')]
1033 + formatteropts, "REVSET")
1033 + formatteropts, "REVSET")
1034 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1034 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
1035 """benchmark the execution time of a revset
1035 """benchmark the execution time of a revset
1036
1036
1037 Use the --clean option if need to evaluate the impact of build volatile
1037 Use the --clean option if need to evaluate the impact of build volatile
1038 revisions set cache on the revset execution. Volatile cache hold filtered
1038 revisions set cache on the revset execution. Volatile cache hold filtered
1039 and obsolete related cache."""
1039 and obsolete related cache."""
1040 timer, fm = gettimer(ui, opts)
1040 timer, fm = gettimer(ui, opts)
1041 def d():
1041 def d():
1042 if clear:
1042 if clear:
1043 repo.invalidatevolatilesets()
1043 repo.invalidatevolatilesets()
1044 if contexts:
1044 if contexts:
1045 for ctx in repo.set(expr): pass
1045 for ctx in repo.set(expr): pass
1046 else:
1046 else:
1047 for r in repo.revs(expr): pass
1047 for r in repo.revs(expr): pass
1048 timer(d)
1048 timer(d)
1049 fm.end()
1049 fm.end()
1050
1050
1051 @command('perfvolatilesets', formatteropts)
1051 @command('perfvolatilesets', formatteropts)
1052 def perfvolatilesets(ui, repo, *names, **opts):
1052 def perfvolatilesets(ui, repo, *names, **opts):
1053 """benchmark the computation of various volatile set
1053 """benchmark the computation of various volatile set
1054
1054
1055 Volatile set computes element related to filtering and obsolescence."""
1055 Volatile set computes element related to filtering and obsolescence."""
1056 timer, fm = gettimer(ui, opts)
1056 timer, fm = gettimer(ui, opts)
1057 repo = repo.unfiltered()
1057 repo = repo.unfiltered()
1058
1058
1059 def getobs(name):
1059 def getobs(name):
1060 def d():
1060 def d():
1061 repo.invalidatevolatilesets()
1061 repo.invalidatevolatilesets()
1062 obsolete.getrevs(repo, name)
1062 obsolete.getrevs(repo, name)
1063 return d
1063 return d
1064
1064
1065 allobs = sorted(obsolete.cachefuncs)
1065 allobs = sorted(obsolete.cachefuncs)
1066 if names:
1066 if names:
1067 allobs = [n for n in allobs if n in names]
1067 allobs = [n for n in allobs if n in names]
1068
1068
1069 for name in allobs:
1069 for name in allobs:
1070 timer(getobs(name), title=name)
1070 timer(getobs(name), title=name)
1071
1071
1072 def getfiltered(name):
1072 def getfiltered(name):
1073 def d():
1073 def d():
1074 repo.invalidatevolatilesets()
1074 repo.invalidatevolatilesets()
1075 repoview.filterrevs(repo, name)
1075 repoview.filterrevs(repo, name)
1076 return d
1076 return d
1077
1077
1078 allfilter = sorted(repoview.filtertable)
1078 allfilter = sorted(repoview.filtertable)
1079 if names:
1079 if names:
1080 allfilter = [n for n in allfilter if n in names]
1080 allfilter = [n for n in allfilter if n in names]
1081
1081
1082 for name in allfilter:
1082 for name in allfilter:
1083 timer(getfiltered(name), title=name)
1083 timer(getfiltered(name), title=name)
1084 fm.end()
1084 fm.end()
1085
1085
1086 @command('perfbranchmap',
1086 @command('perfbranchmap',
1087 [('f', 'full', False,
1087 [('f', 'full', False,
1088 'Includes build time of subset'),
1088 'Includes build time of subset'),
1089 ] + formatteropts)
1089 ] + formatteropts)
1090 def perfbranchmap(ui, repo, full=False, **opts):
1090 def perfbranchmap(ui, repo, full=False, **opts):
1091 """benchmark the update of a branchmap
1091 """benchmark the update of a branchmap
1092
1092
1093 This benchmarks the full repo.branchmap() call with read and write disabled
1093 This benchmarks the full repo.branchmap() call with read and write disabled
1094 """
1094 """
1095 timer, fm = gettimer(ui, opts)
1095 timer, fm = gettimer(ui, opts)
1096 def getbranchmap(filtername):
1096 def getbranchmap(filtername):
1097 """generate a benchmark function for the filtername"""
1097 """generate a benchmark function for the filtername"""
1098 if filtername is None:
1098 if filtername is None:
1099 view = repo
1099 view = repo
1100 else:
1100 else:
1101 view = repo.filtered(filtername)
1101 view = repo.filtered(filtername)
1102 def d():
1102 def d():
1103 if full:
1103 if full:
1104 view._branchcaches.clear()
1104 view._branchcaches.clear()
1105 else:
1105 else:
1106 view._branchcaches.pop(filtername, None)
1106 view._branchcaches.pop(filtername, None)
1107 view.branchmap()
1107 view.branchmap()
1108 return d
1108 return d
1109 # add filter in smaller subset to bigger subset
1109 # add filter in smaller subset to bigger subset
1110 possiblefilters = set(repoview.filtertable)
1110 possiblefilters = set(repoview.filtertable)
1111 subsettable = getbranchmapsubsettable()
1111 subsettable = getbranchmapsubsettable()
1112 allfilters = []
1112 allfilters = []
1113 while possiblefilters:
1113 while possiblefilters:
1114 for name in possiblefilters:
1114 for name in possiblefilters:
1115 subset = subsettable.get(name)
1115 subset = subsettable.get(name)
1116 if subset not in possiblefilters:
1116 if subset not in possiblefilters:
1117 break
1117 break
1118 else:
1118 else:
1119 assert False, 'subset cycle %s!' % possiblefilters
1119 assert False, 'subset cycle %s!' % possiblefilters
1120 allfilters.append(name)
1120 allfilters.append(name)
1121 possiblefilters.remove(name)
1121 possiblefilters.remove(name)
1122
1122
1123 # warm the cache
1123 # warm the cache
1124 if not full:
1124 if not full:
1125 for name in allfilters:
1125 for name in allfilters:
1126 repo.filtered(name).branchmap()
1126 repo.filtered(name).branchmap()
1127 # add unfiltered
1127 # add unfiltered
1128 allfilters.append(None)
1128 allfilters.append(None)
1129
1129
1130 branchcacheread = safeattrsetter(branchmap, 'read')
1130 branchcacheread = safeattrsetter(branchmap, 'read')
1131 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1131 branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
1132 branchcacheread.set(lambda repo: None)
1132 branchcacheread.set(lambda repo: None)
1133 branchcachewrite.set(lambda bc, repo: None)
1133 branchcachewrite.set(lambda bc, repo: None)
1134 try:
1134 try:
1135 for name in allfilters:
1135 for name in allfilters:
1136 timer(getbranchmap(name), title=str(name))
1136 timer(getbranchmap(name), title=str(name))
1137 finally:
1137 finally:
1138 branchcacheread.restore()
1138 branchcacheread.restore()
1139 branchcachewrite.restore()
1139 branchcachewrite.restore()
1140 fm.end()
1140 fm.end()
1141
1141
1142 @command('perfloadmarkers')
1142 @command('perfloadmarkers')
1143 def perfloadmarkers(ui, repo):
1143 def perfloadmarkers(ui, repo):
1144 """benchmark the time to parse the on-disk markers for a repo
1144 """benchmark the time to parse the on-disk markers for a repo
1145
1145
1146 Result is the number of markers in the repo."""
1146 Result is the number of markers in the repo."""
1147 timer, fm = gettimer(ui)
1147 timer, fm = gettimer(ui)
1148 svfs = getsvfs(repo)
1148 svfs = getsvfs(repo)
1149 timer(lambda: len(obsolete.obsstore(svfs)))
1149 timer(lambda: len(obsolete.obsstore(svfs)))
1150 fm.end()
1150 fm.end()
1151
1151
1152 @command('perflrucachedict', formatteropts +
1152 @command('perflrucachedict', formatteropts +
1153 [('', 'size', 4, 'size of cache'),
1153 [('', 'size', 4, 'size of cache'),
1154 ('', 'gets', 10000, 'number of key lookups'),
1154 ('', 'gets', 10000, 'number of key lookups'),
1155 ('', 'sets', 10000, 'number of key sets'),
1155 ('', 'sets', 10000, 'number of key sets'),
1156 ('', 'mixed', 10000, 'number of mixed mode operations'),
1156 ('', 'mixed', 10000, 'number of mixed mode operations'),
1157 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1157 ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
1158 norepo=True)
1158 norepo=True)
1159 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1159 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
1160 mixedgetfreq=50, **opts):
1160 mixedgetfreq=50, **opts):
1161 def doinit():
1161 def doinit():
1162 for i in xrange(10000):
1162 for i in xrange(10000):
1163 util.lrucachedict(size)
1163 util.lrucachedict(size)
1164
1164
1165 values = []
1165 values = []
1166 for i in xrange(size):
1166 for i in xrange(size):
1167 values.append(random.randint(0, sys.maxint))
1167 values.append(random.randint(0, sys.maxint))
1168
1168
1169 # Get mode fills the cache and tests raw lookup performance with no
1169 # Get mode fills the cache and tests raw lookup performance with no
1170 # eviction.
1170 # eviction.
1171 getseq = []
1171 getseq = []
1172 for i in xrange(gets):
1172 for i in xrange(gets):
1173 getseq.append(random.choice(values))
1173 getseq.append(random.choice(values))
1174
1174
1175 def dogets():
1175 def dogets():
1176 d = util.lrucachedict(size)
1176 d = util.lrucachedict(size)
1177 for v in values:
1177 for v in values:
1178 d[v] = v
1178 d[v] = v
1179 for key in getseq:
1179 for key in getseq:
1180 value = d[key]
1180 value = d[key]
1181 value # silence pyflakes warning
1181 value # silence pyflakes warning
1182
1182
1183 # Set mode tests insertion speed with cache eviction.
1183 # Set mode tests insertion speed with cache eviction.
1184 setseq = []
1184 setseq = []
1185 for i in xrange(sets):
1185 for i in xrange(sets):
1186 setseq.append(random.randint(0, sys.maxint))
1186 setseq.append(random.randint(0, sys.maxint))
1187
1187
1188 def dosets():
1188 def dosets():
1189 d = util.lrucachedict(size)
1189 d = util.lrucachedict(size)
1190 for v in setseq:
1190 for v in setseq:
1191 d[v] = v
1191 d[v] = v
1192
1192
1193 # Mixed mode randomly performs gets and sets with eviction.
1193 # Mixed mode randomly performs gets and sets with eviction.
1194 mixedops = []
1194 mixedops = []
1195 for i in xrange(mixed):
1195 for i in xrange(mixed):
1196 r = random.randint(0, 100)
1196 r = random.randint(0, 100)
1197 if r < mixedgetfreq:
1197 if r < mixedgetfreq:
1198 op = 0
1198 op = 0
1199 else:
1199 else:
1200 op = 1
1200 op = 1
1201
1201
1202 mixedops.append((op, random.randint(0, size * 2)))
1202 mixedops.append((op, random.randint(0, size * 2)))
1203
1203
1204 def domixed():
1204 def domixed():
1205 d = util.lrucachedict(size)
1205 d = util.lrucachedict(size)
1206
1206
1207 for op, v in mixedops:
1207 for op, v in mixedops:
1208 if op == 0:
1208 if op == 0:
1209 try:
1209 try:
1210 d[v]
1210 d[v]
1211 except KeyError:
1211 except KeyError:
1212 pass
1212 pass
1213 else:
1213 else:
1214 d[v] = v
1214 d[v] = v
1215
1215
1216 benches = [
1216 benches = [
1217 (doinit, 'init'),
1217 (doinit, 'init'),
1218 (dogets, 'gets'),
1218 (dogets, 'gets'),
1219 (dosets, 'sets'),
1219 (dosets, 'sets'),
1220 (domixed, 'mixed')
1220 (domixed, 'mixed')
1221 ]
1221 ]
1222
1222
1223 for fn, title in benches:
1223 for fn, title in benches:
1224 timer, fm = gettimer(ui, opts)
1224 timer, fm = gettimer(ui, opts)
1225 timer(fn, title=title)
1225 timer(fn, title=title)
1226 fm.end()
1226 fm.end()
1227
1227
1228 def uisetup(ui):
1228 def uisetup(ui):
1229 if (util.safehasattr(cmdutil, 'openrevlog') and
1229 if (util.safehasattr(cmdutil, 'openrevlog') and
1230 not util.safehasattr(commands, 'debugrevlogopts')):
1230 not util.safehasattr(commands, 'debugrevlogopts')):
1231 # for "historical portability":
1231 # for "historical portability":
1232 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1232 # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
1233 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1233 # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
1234 # openrevlog() should cause failure, because it has been
1234 # openrevlog() should cause failure, because it has been
1235 # available since 3.5 (or 49c583ca48c4).
1235 # available since 3.5 (or 49c583ca48c4).
1236 def openrevlog(orig, repo, cmd, file_, opts):
1236 def openrevlog(orig, repo, cmd, file_, opts):
1237 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1237 if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
1238 raise error.Abort("This version doesn't support --dir option",
1238 raise error.Abort("This version doesn't support --dir option",
1239 hint="use 3.5 or later")
1239 hint="use 3.5 or later")
1240 return orig(repo, cmd, file_, opts)
1240 return orig(repo, cmd, file_, opts)
1241 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
1241 extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
@@ -1,554 +1,554 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.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 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import os
16 import os
17 import shutil
17 import shutil
18 import tempfile
18 import tempfile
19
19
20 from .i18n import _
20 from .i18n import _
21 from .node import nullid
21 from .node import nullid
22
22
23 from . import (
23 from . import (
24 bundle2,
24 bundle2,
25 changegroup,
25 changegroup,
26 changelog,
26 changelog,
27 cmdutil,
27 cmdutil,
28 discovery,
28 discovery,
29 error,
29 error,
30 exchange,
30 exchange,
31 filelog,
31 filelog,
32 localrepo,
32 localrepo,
33 manifest,
33 manifest,
34 mdiff,
34 mdiff,
35 node as nodemod,
35 node as nodemod,
36 pathutil,
36 pathutil,
37 phases,
37 phases,
38 pycompat,
38 pycompat,
39 revlog,
39 revlog,
40 scmutil,
40 scmutil,
41 util,
41 util,
42 )
42 )
43
43
44 class bundlerevlog(revlog.revlog):
44 class bundlerevlog(revlog.revlog):
45 def __init__(self, opener, indexfile, bundle, linkmapper):
45 def __init__(self, opener, indexfile, bundle, linkmapper):
46 # How it works:
46 # How it works:
47 # To retrieve a revision, we need to know the offset of the revision in
47 # To retrieve a revision, we need to know the offset of the revision in
48 # the bundle (an unbundle object). We store this offset in the index
48 # the bundle (an unbundle object). We store this offset in the index
49 # (start). The base of the delta is stored in the base field.
49 # (start). The base of the delta is stored in the base field.
50 #
50 #
51 # To differentiate a rev in the bundle from a rev in the revlog, we
51 # To differentiate a rev in the bundle from a rev in the revlog, we
52 # check revision against repotiprev.
52 # check revision against repotiprev.
53 opener = scmutil.readonlyvfs(opener)
53 opener = scmutil.readonlyvfs(opener)
54 revlog.revlog.__init__(self, opener, indexfile)
54 revlog.revlog.__init__(self, opener, indexfile)
55 self.bundle = bundle
55 self.bundle = bundle
56 n = len(self)
56 n = len(self)
57 self.repotiprev = n - 1
57 self.repotiprev = n - 1
58 chain = None
58 chain = None
59 self.bundlerevs = set() # used by 'bundle()' revset expression
59 self.bundlerevs = set() # used by 'bundle()' revset expression
60 getchunk = lambda: bundle.deltachunk(chain)
60 getchunk = lambda: bundle.deltachunk(chain)
61 for chunkdata in iter(getchunk, {}):
61 for chunkdata in iter(getchunk, {}):
62 node = chunkdata['node']
62 node = chunkdata['node']
63 p1 = chunkdata['p1']
63 p1 = chunkdata['p1']
64 p2 = chunkdata['p2']
64 p2 = chunkdata['p2']
65 cs = chunkdata['cs']
65 cs = chunkdata['cs']
66 deltabase = chunkdata['deltabase']
66 deltabase = chunkdata['deltabase']
67 delta = chunkdata['delta']
67 delta = chunkdata['delta']
68
68
69 size = len(delta)
69 size = len(delta)
70 start = bundle.tell() - size
70 start = bundle.tell() - size
71
71
72 link = linkmapper(cs)
72 link = linkmapper(cs)
73 if node in self.nodemap:
73 if node in self.nodemap:
74 # this can happen if two branches make the same change
74 # this can happen if two branches make the same change
75 chain = node
75 chain = node
76 self.bundlerevs.add(self.nodemap[node])
76 self.bundlerevs.add(self.nodemap[node])
77 continue
77 continue
78
78
79 for p in (p1, p2):
79 for p in (p1, p2):
80 if p not in self.nodemap:
80 if p not in self.nodemap:
81 raise error.LookupError(p, self.indexfile,
81 raise error.LookupError(p, self.indexfile,
82 _("unknown parent"))
82 _("unknown parent"))
83
83
84 if deltabase not in self.nodemap:
84 if deltabase not in self.nodemap:
85 raise LookupError(deltabase, self.indexfile,
85 raise LookupError(deltabase, self.indexfile,
86 _('unknown delta base'))
86 _('unknown delta base'))
87
87
88 baserev = self.rev(deltabase)
88 baserev = self.rev(deltabase)
89 # start, size, full unc. size, base (unused), link, p1, p2, node
89 # start, size, full unc. size, base (unused), link, p1, p2, node
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
91 self.rev(p1), self.rev(p2), node)
91 self.rev(p1), self.rev(p2), node)
92 self.index.insert(-1, e)
92 self.index.insert(-1, e)
93 self.nodemap[node] = n
93 self.nodemap[node] = n
94 self.bundlerevs.add(n)
94 self.bundlerevs.add(n)
95 chain = node
95 chain = node
96 n += 1
96 n += 1
97
97
98 def _chunk(self, rev):
98 def _chunk(self, rev):
99 # Warning: in case of bundle, the diff is against what we stored as
99 # Warning: in case of bundle, the diff is against what we stored as
100 # delta base, not against rev - 1
100 # delta base, not against rev - 1
101 # XXX: could use some caching
101 # XXX: could use some caching
102 if rev <= self.repotiprev:
102 if rev <= self.repotiprev:
103 return revlog.revlog._chunk(self, rev)
103 return revlog.revlog._chunk(self, rev)
104 self.bundle.seek(self.start(rev))
104 self.bundle.seek(self.start(rev))
105 return self.bundle.read(self.length(rev))
105 return self.bundle.read(self.length(rev))
106
106
107 def revdiff(self, rev1, rev2):
107 def revdiff(self, rev1, rev2):
108 """return or calculate a delta between two revisions"""
108 """return or calculate a delta between two revisions"""
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
110 # hot path for bundle
110 # hot path for bundle
111 revb = self.index[rev2][3]
111 revb = self.index[rev2][3]
112 if revb == rev1:
112 if revb == rev1:
113 return self._chunk(rev2)
113 return self._chunk(rev2)
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
115 return revlog.revlog.revdiff(self, rev1, rev2)
115 return revlog.revlog.revdiff(self, rev1, rev2)
116
116
117 return mdiff.textdiff(self.revision(self.node(rev1)),
117 return mdiff.textdiff(self.revision(self.node(rev1)),
118 self.revision(self.node(rev2)))
118 self.revision(self.node(rev2)))
119
119
120 def revision(self, nodeorrev):
120 def revision(self, nodeorrev):
121 """return an uncompressed revision of a given node or revision
121 """return an uncompressed revision of a given node or revision
122 number.
122 number.
123 """
123 """
124 if isinstance(nodeorrev, int):
124 if isinstance(nodeorrev, int):
125 rev = nodeorrev
125 rev = nodeorrev
126 node = self.node(rev)
126 node = self.node(rev)
127 else:
127 else:
128 node = nodeorrev
128 node = nodeorrev
129 rev = self.rev(node)
129 rev = self.rev(node)
130
130
131 if node == nullid:
131 if node == nullid:
132 return ""
132 return ""
133
133
134 text = None
134 text = None
135 chain = []
135 chain = []
136 iterrev = rev
136 iterrev = rev
137 # reconstruct the revision if it is from a changegroup
137 # reconstruct the revision if it is from a changegroup
138 while iterrev > self.repotiprev:
138 while iterrev > self.repotiprev:
139 if self._cache and self._cache[1] == iterrev:
139 if self._cache and self._cache[1] == iterrev:
140 text = self._cache[2]
140 text = self._cache[2]
141 break
141 break
142 chain.append(iterrev)
142 chain.append(iterrev)
143 iterrev = self.index[iterrev][3]
143 iterrev = self.index[iterrev][3]
144 if text is None:
144 if text is None:
145 text = self.baserevision(iterrev)
145 text = self.baserevision(iterrev)
146
146
147 while chain:
147 while chain:
148 delta = self._chunk(chain.pop())
148 delta = self._chunk(chain.pop())
149 text = mdiff.patches(text, [delta])
149 text = mdiff.patches(text, [delta])
150
150
151 self._checkhash(text, node, rev)
151 self.checkhash(text, node, rev=rev)
152 self._cache = (node, rev, text)
152 self._cache = (node, rev, text)
153 return text
153 return text
154
154
155 def baserevision(self, nodeorrev):
155 def baserevision(self, nodeorrev):
156 # Revlog subclasses may override 'revision' method to modify format of
156 # Revlog subclasses may override 'revision' method to modify format of
157 # content retrieved from revlog. To use bundlerevlog with such class one
157 # content retrieved from revlog. To use bundlerevlog with such class one
158 # needs to override 'baserevision' and make more specific call here.
158 # needs to override 'baserevision' and make more specific call here.
159 return revlog.revlog.revision(self, nodeorrev)
159 return revlog.revlog.revision(self, nodeorrev)
160
160
161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
162 raise NotImplementedError
162 raise NotImplementedError
163 def addgroup(self, revs, linkmapper, transaction):
163 def addgroup(self, revs, linkmapper, transaction):
164 raise NotImplementedError
164 raise NotImplementedError
165 def strip(self, rev, minlink):
165 def strip(self, rev, minlink):
166 raise NotImplementedError
166 raise NotImplementedError
167 def checksize(self):
167 def checksize(self):
168 raise NotImplementedError
168 raise NotImplementedError
169
169
170 class bundlechangelog(bundlerevlog, changelog.changelog):
170 class bundlechangelog(bundlerevlog, changelog.changelog):
171 def __init__(self, opener, bundle):
171 def __init__(self, opener, bundle):
172 changelog.changelog.__init__(self, opener)
172 changelog.changelog.__init__(self, opener)
173 linkmapper = lambda x: x
173 linkmapper = lambda x: x
174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
175 linkmapper)
175 linkmapper)
176
176
177 def baserevision(self, nodeorrev):
177 def baserevision(self, nodeorrev):
178 # Although changelog doesn't override 'revision' method, some extensions
178 # Although changelog doesn't override 'revision' method, some extensions
179 # may replace this class with another that does. Same story with
179 # may replace this class with another that does. Same story with
180 # manifest and filelog classes.
180 # manifest and filelog classes.
181
181
182 # This bypasses filtering on changelog.node() and rev() because we need
182 # This bypasses filtering on changelog.node() and rev() because we need
183 # revision text of the bundle base even if it is hidden.
183 # revision text of the bundle base even if it is hidden.
184 oldfilter = self.filteredrevs
184 oldfilter = self.filteredrevs
185 try:
185 try:
186 self.filteredrevs = ()
186 self.filteredrevs = ()
187 return changelog.changelog.revision(self, nodeorrev)
187 return changelog.changelog.revision(self, nodeorrev)
188 finally:
188 finally:
189 self.filteredrevs = oldfilter
189 self.filteredrevs = oldfilter
190
190
191 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
191 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
192 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
192 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
193 manifest.manifestrevlog.__init__(self, opener, dir=dir)
193 manifest.manifestrevlog.__init__(self, opener, dir=dir)
194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
195 linkmapper)
195 linkmapper)
196 if dirlogstarts is None:
196 if dirlogstarts is None:
197 dirlogstarts = {}
197 dirlogstarts = {}
198 if self.bundle.version == "03":
198 if self.bundle.version == "03":
199 dirlogstarts = _getfilestarts(self.bundle)
199 dirlogstarts = _getfilestarts(self.bundle)
200 self._dirlogstarts = dirlogstarts
200 self._dirlogstarts = dirlogstarts
201 self._linkmapper = linkmapper
201 self._linkmapper = linkmapper
202
202
203 def baserevision(self, nodeorrev):
203 def baserevision(self, nodeorrev):
204 node = nodeorrev
204 node = nodeorrev
205 if isinstance(node, int):
205 if isinstance(node, int):
206 node = self.node(node)
206 node = self.node(node)
207
207
208 if node in self.fulltextcache:
208 if node in self.fulltextcache:
209 result = self.fulltextcache[node].tostring()
209 result = self.fulltextcache[node].tostring()
210 else:
210 else:
211 result = manifest.manifestrevlog.revision(self, nodeorrev)
211 result = manifest.manifestrevlog.revision(self, nodeorrev)
212 return result
212 return result
213
213
214 def dirlog(self, d):
214 def dirlog(self, d):
215 if d in self._dirlogstarts:
215 if d in self._dirlogstarts:
216 self.bundle.seek(self._dirlogstarts[d])
216 self.bundle.seek(self._dirlogstarts[d])
217 return bundlemanifest(
217 return bundlemanifest(
218 self.opener, self.bundle, self._linkmapper,
218 self.opener, self.bundle, self._linkmapper,
219 self._dirlogstarts, dir=d)
219 self._dirlogstarts, dir=d)
220 return super(bundlemanifest, self).dirlog(d)
220 return super(bundlemanifest, self).dirlog(d)
221
221
222 class bundlefilelog(bundlerevlog, filelog.filelog):
222 class bundlefilelog(bundlerevlog, filelog.filelog):
223 def __init__(self, opener, path, bundle, linkmapper):
223 def __init__(self, opener, path, bundle, linkmapper):
224 filelog.filelog.__init__(self, opener, path)
224 filelog.filelog.__init__(self, opener, path)
225 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
225 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
226 linkmapper)
226 linkmapper)
227
227
228 def baserevision(self, nodeorrev):
228 def baserevision(self, nodeorrev):
229 return filelog.filelog.revision(self, nodeorrev)
229 return filelog.filelog.revision(self, nodeorrev)
230
230
231 class bundlepeer(localrepo.localpeer):
231 class bundlepeer(localrepo.localpeer):
232 def canpush(self):
232 def canpush(self):
233 return False
233 return False
234
234
235 class bundlephasecache(phases.phasecache):
235 class bundlephasecache(phases.phasecache):
236 def __init__(self, *args, **kwargs):
236 def __init__(self, *args, **kwargs):
237 super(bundlephasecache, self).__init__(*args, **kwargs)
237 super(bundlephasecache, self).__init__(*args, **kwargs)
238 if util.safehasattr(self, 'opener'):
238 if util.safehasattr(self, 'opener'):
239 self.opener = scmutil.readonlyvfs(self.opener)
239 self.opener = scmutil.readonlyvfs(self.opener)
240
240
241 def write(self):
241 def write(self):
242 raise NotImplementedError
242 raise NotImplementedError
243
243
244 def _write(self, fp):
244 def _write(self, fp):
245 raise NotImplementedError
245 raise NotImplementedError
246
246
247 def _updateroots(self, phase, newroots, tr):
247 def _updateroots(self, phase, newroots, tr):
248 self.phaseroots[phase] = newroots
248 self.phaseroots[phase] = newroots
249 self.invalidate()
249 self.invalidate()
250 self.dirty = True
250 self.dirty = True
251
251
252 def _getfilestarts(bundle):
252 def _getfilestarts(bundle):
253 bundlefilespos = {}
253 bundlefilespos = {}
254 for chunkdata in iter(bundle.filelogheader, {}):
254 for chunkdata in iter(bundle.filelogheader, {}):
255 fname = chunkdata['filename']
255 fname = chunkdata['filename']
256 bundlefilespos[fname] = bundle.tell()
256 bundlefilespos[fname] = bundle.tell()
257 for chunk in iter(lambda: bundle.deltachunk(None), {}):
257 for chunk in iter(lambda: bundle.deltachunk(None), {}):
258 pass
258 pass
259 return bundlefilespos
259 return bundlefilespos
260
260
261 class bundlerepository(localrepo.localrepository):
261 class bundlerepository(localrepo.localrepository):
262 def __init__(self, ui, path, bundlename):
262 def __init__(self, ui, path, bundlename):
263 def _writetempbundle(read, suffix, header=''):
263 def _writetempbundle(read, suffix, header=''):
264 """Write a temporary file to disk
264 """Write a temporary file to disk
265
265
266 This is closure because we need to make sure this tracked by
266 This is closure because we need to make sure this tracked by
267 self.tempfile for cleanup purposes."""
267 self.tempfile for cleanup purposes."""
268 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
268 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
269 suffix=".hg10un")
269 suffix=".hg10un")
270 self.tempfile = temp
270 self.tempfile = temp
271
271
272 with os.fdopen(fdtemp, 'wb') as fptemp:
272 with os.fdopen(fdtemp, 'wb') as fptemp:
273 fptemp.write(header)
273 fptemp.write(header)
274 while True:
274 while True:
275 chunk = read(2**18)
275 chunk = read(2**18)
276 if not chunk:
276 if not chunk:
277 break
277 break
278 fptemp.write(chunk)
278 fptemp.write(chunk)
279
279
280 return self.vfs.open(self.tempfile, mode="rb")
280 return self.vfs.open(self.tempfile, mode="rb")
281 self._tempparent = None
281 self._tempparent = None
282 try:
282 try:
283 localrepo.localrepository.__init__(self, ui, path)
283 localrepo.localrepository.__init__(self, ui, path)
284 except error.RepoError:
284 except error.RepoError:
285 self._tempparent = tempfile.mkdtemp()
285 self._tempparent = tempfile.mkdtemp()
286 localrepo.instance(ui, self._tempparent, 1)
286 localrepo.instance(ui, self._tempparent, 1)
287 localrepo.localrepository.__init__(self, ui, self._tempparent)
287 localrepo.localrepository.__init__(self, ui, self._tempparent)
288 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
288 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
289
289
290 if path:
290 if path:
291 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
291 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
292 else:
292 else:
293 self._url = 'bundle:' + bundlename
293 self._url = 'bundle:' + bundlename
294
294
295 self.tempfile = None
295 self.tempfile = None
296 f = util.posixfile(bundlename, "rb")
296 f = util.posixfile(bundlename, "rb")
297 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
297 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
298
298
299 if isinstance(self.bundle, bundle2.unbundle20):
299 if isinstance(self.bundle, bundle2.unbundle20):
300 cgstream = None
300 cgstream = None
301 for part in self.bundle.iterparts():
301 for part in self.bundle.iterparts():
302 if part.type == 'changegroup':
302 if part.type == 'changegroup':
303 if cgstream is not None:
303 if cgstream is not None:
304 raise NotImplementedError("can't process "
304 raise NotImplementedError("can't process "
305 "multiple changegroups")
305 "multiple changegroups")
306 cgstream = part
306 cgstream = part
307 version = part.params.get('version', '01')
307 version = part.params.get('version', '01')
308 legalcgvers = changegroup.supportedincomingversions(self)
308 legalcgvers = changegroup.supportedincomingversions(self)
309 if version not in legalcgvers:
309 if version not in legalcgvers:
310 msg = _('Unsupported changegroup version: %s')
310 msg = _('Unsupported changegroup version: %s')
311 raise error.Abort(msg % version)
311 raise error.Abort(msg % version)
312 if self.bundle.compressed():
312 if self.bundle.compressed():
313 cgstream = _writetempbundle(part.read,
313 cgstream = _writetempbundle(part.read,
314 ".cg%sun" % version)
314 ".cg%sun" % version)
315
315
316 if cgstream is None:
316 if cgstream is None:
317 raise error.Abort(_('No changegroups found'))
317 raise error.Abort(_('No changegroups found'))
318 cgstream.seek(0)
318 cgstream.seek(0)
319
319
320 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
320 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
321
321
322 elif self.bundle.compressed():
322 elif self.bundle.compressed():
323 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
323 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
324 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
324 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
325 bundlename,
325 bundlename,
326 self.vfs)
326 self.vfs)
327
327
328 # dict with the mapping 'filename' -> position in the bundle
328 # dict with the mapping 'filename' -> position in the bundle
329 self.bundlefilespos = {}
329 self.bundlefilespos = {}
330
330
331 self.firstnewrev = self.changelog.repotiprev + 1
331 self.firstnewrev = self.changelog.repotiprev + 1
332 phases.retractboundary(self, None, phases.draft,
332 phases.retractboundary(self, None, phases.draft,
333 [ctx.node() for ctx in self[self.firstnewrev:]])
333 [ctx.node() for ctx in self[self.firstnewrev:]])
334
334
335 @localrepo.unfilteredpropertycache
335 @localrepo.unfilteredpropertycache
336 def _phasecache(self):
336 def _phasecache(self):
337 return bundlephasecache(self, self._phasedefaults)
337 return bundlephasecache(self, self._phasedefaults)
338
338
339 @localrepo.unfilteredpropertycache
339 @localrepo.unfilteredpropertycache
340 def changelog(self):
340 def changelog(self):
341 # consume the header if it exists
341 # consume the header if it exists
342 self.bundle.changelogheader()
342 self.bundle.changelogheader()
343 c = bundlechangelog(self.svfs, self.bundle)
343 c = bundlechangelog(self.svfs, self.bundle)
344 self.manstart = self.bundle.tell()
344 self.manstart = self.bundle.tell()
345 return c
345 return c
346
346
347 def _constructmanifest(self):
347 def _constructmanifest(self):
348 self.bundle.seek(self.manstart)
348 self.bundle.seek(self.manstart)
349 # consume the header if it exists
349 # consume the header if it exists
350 self.bundle.manifestheader()
350 self.bundle.manifestheader()
351 linkmapper = self.unfiltered().changelog.rev
351 linkmapper = self.unfiltered().changelog.rev
352 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
352 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
353 self.filestart = self.bundle.tell()
353 self.filestart = self.bundle.tell()
354 return m
354 return m
355
355
356 @localrepo.unfilteredpropertycache
356 @localrepo.unfilteredpropertycache
357 def manstart(self):
357 def manstart(self):
358 self.changelog
358 self.changelog
359 return self.manstart
359 return self.manstart
360
360
361 @localrepo.unfilteredpropertycache
361 @localrepo.unfilteredpropertycache
362 def filestart(self):
362 def filestart(self):
363 self.manifestlog
363 self.manifestlog
364 return self.filestart
364 return self.filestart
365
365
366 def url(self):
366 def url(self):
367 return self._url
367 return self._url
368
368
369 def file(self, f):
369 def file(self, f):
370 if not self.bundlefilespos:
370 if not self.bundlefilespos:
371 self.bundle.seek(self.filestart)
371 self.bundle.seek(self.filestart)
372 self.bundlefilespos = _getfilestarts(self.bundle)
372 self.bundlefilespos = _getfilestarts(self.bundle)
373
373
374 if f in self.bundlefilespos:
374 if f in self.bundlefilespos:
375 self.bundle.seek(self.bundlefilespos[f])
375 self.bundle.seek(self.bundlefilespos[f])
376 linkmapper = self.unfiltered().changelog.rev
376 linkmapper = self.unfiltered().changelog.rev
377 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
377 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
378 else:
378 else:
379 return filelog.filelog(self.svfs, f)
379 return filelog.filelog(self.svfs, f)
380
380
381 def close(self):
381 def close(self):
382 """Close assigned bundle file immediately."""
382 """Close assigned bundle file immediately."""
383 self.bundlefile.close()
383 self.bundlefile.close()
384 if self.tempfile is not None:
384 if self.tempfile is not None:
385 self.vfs.unlink(self.tempfile)
385 self.vfs.unlink(self.tempfile)
386 if self._tempparent:
386 if self._tempparent:
387 shutil.rmtree(self._tempparent, True)
387 shutil.rmtree(self._tempparent, True)
388
388
389 def cancopy(self):
389 def cancopy(self):
390 return False
390 return False
391
391
392 def peer(self):
392 def peer(self):
393 return bundlepeer(self)
393 return bundlepeer(self)
394
394
395 def getcwd(self):
395 def getcwd(self):
396 return pycompat.getcwd() # always outside the repo
396 return pycompat.getcwd() # always outside the repo
397
397
398 # Check if parents exist in localrepo before setting
398 # Check if parents exist in localrepo before setting
399 def setparents(self, p1, p2=nullid):
399 def setparents(self, p1, p2=nullid):
400 p1rev = self.changelog.rev(p1)
400 p1rev = self.changelog.rev(p1)
401 p2rev = self.changelog.rev(p2)
401 p2rev = self.changelog.rev(p2)
402 msg = _("setting parent to node %s that only exists in the bundle\n")
402 msg = _("setting parent to node %s that only exists in the bundle\n")
403 if self.changelog.repotiprev < p1rev:
403 if self.changelog.repotiprev < p1rev:
404 self.ui.warn(msg % nodemod.hex(p1))
404 self.ui.warn(msg % nodemod.hex(p1))
405 if self.changelog.repotiprev < p2rev:
405 if self.changelog.repotiprev < p2rev:
406 self.ui.warn(msg % nodemod.hex(p2))
406 self.ui.warn(msg % nodemod.hex(p2))
407 return super(bundlerepository, self).setparents(p1, p2)
407 return super(bundlerepository, self).setparents(p1, p2)
408
408
409 def instance(ui, path, create):
409 def instance(ui, path, create):
410 if create:
410 if create:
411 raise error.Abort(_('cannot create new bundle repository'))
411 raise error.Abort(_('cannot create new bundle repository'))
412 # internal config: bundle.mainreporoot
412 # internal config: bundle.mainreporoot
413 parentpath = ui.config("bundle", "mainreporoot", "")
413 parentpath = ui.config("bundle", "mainreporoot", "")
414 if not parentpath:
414 if not parentpath:
415 # try to find the correct path to the working directory repo
415 # try to find the correct path to the working directory repo
416 parentpath = cmdutil.findrepo(pycompat.getcwd())
416 parentpath = cmdutil.findrepo(pycompat.getcwd())
417 if parentpath is None:
417 if parentpath is None:
418 parentpath = ''
418 parentpath = ''
419 if parentpath:
419 if parentpath:
420 # Try to make the full path relative so we get a nice, short URL.
420 # Try to make the full path relative so we get a nice, short URL.
421 # In particular, we don't want temp dir names in test outputs.
421 # In particular, we don't want temp dir names in test outputs.
422 cwd = pycompat.getcwd()
422 cwd = pycompat.getcwd()
423 if parentpath == cwd:
423 if parentpath == cwd:
424 parentpath = ''
424 parentpath = ''
425 else:
425 else:
426 cwd = pathutil.normasprefix(cwd)
426 cwd = pathutil.normasprefix(cwd)
427 if parentpath.startswith(cwd):
427 if parentpath.startswith(cwd):
428 parentpath = parentpath[len(cwd):]
428 parentpath = parentpath[len(cwd):]
429 u = util.url(path)
429 u = util.url(path)
430 path = u.localpath()
430 path = u.localpath()
431 if u.scheme == 'bundle':
431 if u.scheme == 'bundle':
432 s = path.split("+", 1)
432 s = path.split("+", 1)
433 if len(s) == 1:
433 if len(s) == 1:
434 repopath, bundlename = parentpath, s[0]
434 repopath, bundlename = parentpath, s[0]
435 else:
435 else:
436 repopath, bundlename = s
436 repopath, bundlename = s
437 else:
437 else:
438 repopath, bundlename = parentpath, path
438 repopath, bundlename = parentpath, path
439 return bundlerepository(ui, repopath, bundlename)
439 return bundlerepository(ui, repopath, bundlename)
440
440
441 class bundletransactionmanager(object):
441 class bundletransactionmanager(object):
442 def transaction(self):
442 def transaction(self):
443 return None
443 return None
444
444
445 def close(self):
445 def close(self):
446 raise NotImplementedError
446 raise NotImplementedError
447
447
448 def release(self):
448 def release(self):
449 raise NotImplementedError
449 raise NotImplementedError
450
450
451 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
451 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
452 force=False):
452 force=False):
453 '''obtains a bundle of changes incoming from other
453 '''obtains a bundle of changes incoming from other
454
454
455 "onlyheads" restricts the returned changes to those reachable from the
455 "onlyheads" restricts the returned changes to those reachable from the
456 specified heads.
456 specified heads.
457 "bundlename", if given, stores the bundle to this file path permanently;
457 "bundlename", if given, stores the bundle to this file path permanently;
458 otherwise it's stored to a temp file and gets deleted again when you call
458 otherwise it's stored to a temp file and gets deleted again when you call
459 the returned "cleanupfn".
459 the returned "cleanupfn".
460 "force" indicates whether to proceed on unrelated repos.
460 "force" indicates whether to proceed on unrelated repos.
461
461
462 Returns a tuple (local, csets, cleanupfn):
462 Returns a tuple (local, csets, cleanupfn):
463
463
464 "local" is a local repo from which to obtain the actual incoming
464 "local" is a local repo from which to obtain the actual incoming
465 changesets; it is a bundlerepo for the obtained bundle when the
465 changesets; it is a bundlerepo for the obtained bundle when the
466 original "other" is remote.
466 original "other" is remote.
467 "csets" lists the incoming changeset node ids.
467 "csets" lists the incoming changeset node ids.
468 "cleanupfn" must be called without arguments when you're done processing
468 "cleanupfn" must be called without arguments when you're done processing
469 the changes; it closes both the original "other" and the one returned
469 the changes; it closes both the original "other" and the one returned
470 here.
470 here.
471 '''
471 '''
472 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
472 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
473 force=force)
473 force=force)
474 common, incoming, rheads = tmp
474 common, incoming, rheads = tmp
475 if not incoming:
475 if not incoming:
476 try:
476 try:
477 if bundlename:
477 if bundlename:
478 os.unlink(bundlename)
478 os.unlink(bundlename)
479 except OSError:
479 except OSError:
480 pass
480 pass
481 return repo, [], other.close
481 return repo, [], other.close
482
482
483 commonset = set(common)
483 commonset = set(common)
484 rheads = [x for x in rheads if x not in commonset]
484 rheads = [x for x in rheads if x not in commonset]
485
485
486 bundle = None
486 bundle = None
487 bundlerepo = None
487 bundlerepo = None
488 localrepo = other.local()
488 localrepo = other.local()
489 if bundlename or not localrepo:
489 if bundlename or not localrepo:
490 # create a bundle (uncompressed if other repo is not local)
490 # create a bundle (uncompressed if other repo is not local)
491
491
492 # developer config: devel.legacy.exchange
492 # developer config: devel.legacy.exchange
493 legexc = ui.configlist('devel', 'legacy.exchange')
493 legexc = ui.configlist('devel', 'legacy.exchange')
494 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
494 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
495 canbundle2 = (not forcebundle1
495 canbundle2 = (not forcebundle1
496 and other.capable('getbundle')
496 and other.capable('getbundle')
497 and other.capable('bundle2'))
497 and other.capable('bundle2'))
498 if canbundle2:
498 if canbundle2:
499 kwargs = {}
499 kwargs = {}
500 kwargs['common'] = common
500 kwargs['common'] = common
501 kwargs['heads'] = rheads
501 kwargs['heads'] = rheads
502 kwargs['bundlecaps'] = exchange.caps20to10(repo)
502 kwargs['bundlecaps'] = exchange.caps20to10(repo)
503 kwargs['cg'] = True
503 kwargs['cg'] = True
504 b2 = other.getbundle('incoming', **kwargs)
504 b2 = other.getbundle('incoming', **kwargs)
505 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
505 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
506 bundlename)
506 bundlename)
507 else:
507 else:
508 if other.capable('getbundle'):
508 if other.capable('getbundle'):
509 cg = other.getbundle('incoming', common=common, heads=rheads)
509 cg = other.getbundle('incoming', common=common, heads=rheads)
510 elif onlyheads is None and not other.capable('changegroupsubset'):
510 elif onlyheads is None and not other.capable('changegroupsubset'):
511 # compat with older servers when pulling all remote heads
511 # compat with older servers when pulling all remote heads
512 cg = other.changegroup(incoming, "incoming")
512 cg = other.changegroup(incoming, "incoming")
513 rheads = None
513 rheads = None
514 else:
514 else:
515 cg = other.changegroupsubset(incoming, rheads, 'incoming')
515 cg = other.changegroupsubset(incoming, rheads, 'incoming')
516 if localrepo:
516 if localrepo:
517 bundletype = "HG10BZ"
517 bundletype = "HG10BZ"
518 else:
518 else:
519 bundletype = "HG10UN"
519 bundletype = "HG10UN"
520 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
520 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
521 bundletype)
521 bundletype)
522 # keep written bundle?
522 # keep written bundle?
523 if bundlename:
523 if bundlename:
524 bundle = None
524 bundle = None
525 if not localrepo:
525 if not localrepo:
526 # use the created uncompressed bundlerepo
526 # use the created uncompressed bundlerepo
527 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
527 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
528 fname)
528 fname)
529 # this repo contains local and other now, so filter out local again
529 # this repo contains local and other now, so filter out local again
530 common = repo.heads()
530 common = repo.heads()
531 if localrepo:
531 if localrepo:
532 # Part of common may be remotely filtered
532 # Part of common may be remotely filtered
533 # So use an unfiltered version
533 # So use an unfiltered version
534 # The discovery process probably need cleanup to avoid that
534 # The discovery process probably need cleanup to avoid that
535 localrepo = localrepo.unfiltered()
535 localrepo = localrepo.unfiltered()
536
536
537 csets = localrepo.changelog.findmissing(common, rheads)
537 csets = localrepo.changelog.findmissing(common, rheads)
538
538
539 if bundlerepo:
539 if bundlerepo:
540 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
540 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
541 remotephases = other.listkeys('phases')
541 remotephases = other.listkeys('phases')
542
542
543 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
543 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
544 pullop.trmanager = bundletransactionmanager()
544 pullop.trmanager = bundletransactionmanager()
545 exchange._pullapplyphases(pullop, remotephases)
545 exchange._pullapplyphases(pullop, remotephases)
546
546
547 def cleanup():
547 def cleanup():
548 if bundlerepo:
548 if bundlerepo:
549 bundlerepo.close()
549 bundlerepo.close()
550 if bundle:
550 if bundle:
551 os.unlink(bundle)
551 os.unlink(bundle)
552 other.close()
552 other.close()
553
553
554 return (localrepo, csets, cleanup)
554 return (localrepo, csets, cleanup)
@@ -1,137 +1,137 b''
1 # filelog.py - file history class for mercurial
1 # filelog.py - file history class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11 import struct
11 import struct
12
12
13 from . import (
13 from . import (
14 error,
14 error,
15 mdiff,
15 mdiff,
16 revlog,
16 revlog,
17 )
17 )
18
18
19 _mdre = re.compile('\1\n')
19 _mdre = re.compile('\1\n')
20 def parsemeta(text):
20 def parsemeta(text):
21 """return (metadatadict, keylist, metadatasize)"""
21 """return (metadatadict, keylist, metadatasize)"""
22 # text can be buffer, so we can't use .startswith or .index
22 # text can be buffer, so we can't use .startswith or .index
23 if text[:2] != '\1\n':
23 if text[:2] != '\1\n':
24 return None, None
24 return None, None
25 s = _mdre.search(text, 2).start()
25 s = _mdre.search(text, 2).start()
26 mtext = text[2:s]
26 mtext = text[2:s]
27 meta = {}
27 meta = {}
28 for l in mtext.splitlines():
28 for l in mtext.splitlines():
29 k, v = l.split(": ", 1)
29 k, v = l.split(": ", 1)
30 meta[k] = v
30 meta[k] = v
31 return meta, (s + 2)
31 return meta, (s + 2)
32
32
33 def packmeta(meta, text):
33 def packmeta(meta, text):
34 keys = sorted(meta.iterkeys())
34 keys = sorted(meta.iterkeys())
35 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
35 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
36 return "\1\n%s\1\n%s" % (metatext, text)
36 return "\1\n%s\1\n%s" % (metatext, text)
37
37
38 def _censoredtext(text):
38 def _censoredtext(text):
39 m, offs = parsemeta(text)
39 m, offs = parsemeta(text)
40 return m and "censored" in m
40 return m and "censored" in m
41
41
42 class filelog(revlog.revlog):
42 class filelog(revlog.revlog):
43 def __init__(self, opener, path):
43 def __init__(self, opener, path):
44 super(filelog, self).__init__(opener,
44 super(filelog, self).__init__(opener,
45 "/".join(("data", path + ".i")))
45 "/".join(("data", path + ".i")))
46
46
47 def read(self, node):
47 def read(self, node):
48 t = self.revision(node)
48 t = self.revision(node)
49 if not t.startswith('\1\n'):
49 if not t.startswith('\1\n'):
50 return t
50 return t
51 s = t.index('\1\n', 2)
51 s = t.index('\1\n', 2)
52 return t[s + 2:]
52 return t[s + 2:]
53
53
54 def add(self, text, meta, transaction, link, p1=None, p2=None):
54 def add(self, text, meta, transaction, link, p1=None, p2=None):
55 if meta or text.startswith('\1\n'):
55 if meta or text.startswith('\1\n'):
56 text = packmeta(meta, text)
56 text = packmeta(meta, text)
57 return self.addrevision(text, transaction, link, p1, p2)
57 return self.addrevision(text, transaction, link, p1, p2)
58
58
59 def renamed(self, node):
59 def renamed(self, node):
60 if self.parents(node)[0] != revlog.nullid:
60 if self.parents(node)[0] != revlog.nullid:
61 return False
61 return False
62 t = self.revision(node)
62 t = self.revision(node)
63 m = parsemeta(t)[0]
63 m = parsemeta(t)[0]
64 if m and "copy" in m:
64 if m and "copy" in m:
65 return (m["copy"], revlog.bin(m["copyrev"]))
65 return (m["copy"], revlog.bin(m["copyrev"]))
66 return False
66 return False
67
67
68 def size(self, rev):
68 def size(self, rev):
69 """return the size of a given revision"""
69 """return the size of a given revision"""
70
70
71 # for revisions with renames, we have to go the slow way
71 # for revisions with renames, we have to go the slow way
72 node = self.node(rev)
72 node = self.node(rev)
73 if self.renamed(node):
73 if self.renamed(node):
74 return len(self.read(node))
74 return len(self.read(node))
75 if self.iscensored(rev):
75 if self.iscensored(rev):
76 return 0
76 return 0
77
77
78 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
78 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
79 return super(filelog, self).size(rev)
79 return super(filelog, self).size(rev)
80
80
81 def cmp(self, node, text):
81 def cmp(self, node, text):
82 """compare text with a given file revision
82 """compare text with a given file revision
83
83
84 returns True if text is different than what is stored.
84 returns True if text is different than what is stored.
85 """
85 """
86
86
87 t = text
87 t = text
88 if text.startswith('\1\n'):
88 if text.startswith('\1\n'):
89 t = '\1\n\1\n' + text
89 t = '\1\n\1\n' + text
90
90
91 samehashes = not super(filelog, self).cmp(node, t)
91 samehashes = not super(filelog, self).cmp(node, t)
92 if samehashes:
92 if samehashes:
93 return False
93 return False
94
94
95 # censored files compare against the empty file
95 # censored files compare against the empty file
96 if self.iscensored(self.rev(node)):
96 if self.iscensored(self.rev(node)):
97 return text != ''
97 return text != ''
98
98
99 # renaming a file produces a different hash, even if the data
99 # renaming a file produces a different hash, even if the data
100 # remains unchanged. Check if it's the case (slow):
100 # remains unchanged. Check if it's the case (slow):
101 if self.renamed(node):
101 if self.renamed(node):
102 t2 = self.read(node)
102 t2 = self.read(node)
103 return t2 != text
103 return t2 != text
104
104
105 return True
105 return True
106
106
107 def checkhash(self, text, p1, p2, node, rev=None):
107 def checkhash(self, text, node, p1=None, p2=None, rev=None):
108 try:
108 try:
109 super(filelog, self).checkhash(text, p1, p2, node, rev=rev)
109 super(filelog, self).checkhash(text, node, p1=p1, p2=p2, rev=rev)
110 except error.RevlogError:
110 except error.RevlogError:
111 if _censoredtext(text):
111 if _censoredtext(text):
112 raise error.CensoredNodeError(self.indexfile, node, text)
112 raise error.CensoredNodeError(self.indexfile, node, text)
113 raise
113 raise
114
114
115 def iscensored(self, rev):
115 def iscensored(self, rev):
116 """Check if a file revision is censored."""
116 """Check if a file revision is censored."""
117 return self.flags(rev) & revlog.REVIDX_ISCENSORED
117 return self.flags(rev) & revlog.REVIDX_ISCENSORED
118
118
119 def _peek_iscensored(self, baserev, delta, flush):
119 def _peek_iscensored(self, baserev, delta, flush):
120 """Quickly check if a delta produces a censored revision."""
120 """Quickly check if a delta produces a censored revision."""
121 # Fragile heuristic: unless new file meta keys are added alphabetically
121 # Fragile heuristic: unless new file meta keys are added alphabetically
122 # preceding "censored", all censored revisions are prefixed by
122 # preceding "censored", all censored revisions are prefixed by
123 # "\1\ncensored:". A delta producing such a censored revision must be a
123 # "\1\ncensored:". A delta producing such a censored revision must be a
124 # full-replacement delta, so we inspect the first and only patch in the
124 # full-replacement delta, so we inspect the first and only patch in the
125 # delta for this prefix.
125 # delta for this prefix.
126 hlen = struct.calcsize(">lll")
126 hlen = struct.calcsize(">lll")
127 if len(delta) <= hlen:
127 if len(delta) <= hlen:
128 return False
128 return False
129
129
130 oldlen = self.rawsize(baserev)
130 oldlen = self.rawsize(baserev)
131 newlen = len(delta) - hlen
131 newlen = len(delta) - hlen
132 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
132 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
133 return False
133 return False
134
134
135 add = "\1\ncensored:"
135 add = "\1\ncensored:"
136 addlen = len(add)
136 addlen = len(add)
137 return newlen >= addlen and delta[hlen:hlen + addlen] == add
137 return newlen >= addlen and delta[hlen:hlen + addlen] == add
@@ -1,1822 +1,1822 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 errno
17 import errno
18 import hashlib
18 import hashlib
19 import os
19 import os
20 import struct
20 import struct
21 import zlib
21 import zlib
22
22
23 # import stuff from node for others to import from revlog
23 # import stuff from node for others to import from revlog
24 from .node import (
24 from .node import (
25 bin,
25 bin,
26 hex,
26 hex,
27 nullid,
27 nullid,
28 nullrev,
28 nullrev,
29 )
29 )
30 from .i18n import _
30 from .i18n import _
31 from . import (
31 from . import (
32 ancestor,
32 ancestor,
33 error,
33 error,
34 mdiff,
34 mdiff,
35 parsers,
35 parsers,
36 templatefilters,
36 templatefilters,
37 util,
37 util,
38 )
38 )
39
39
40 _pack = struct.pack
40 _pack = struct.pack
41 _unpack = struct.unpack
41 _unpack = struct.unpack
42 _compress = zlib.compress
42 _compress = zlib.compress
43 _decompress = zlib.decompress
43 _decompress = zlib.decompress
44
44
45 # revlog header flags
45 # revlog header flags
46 REVLOGV0 = 0
46 REVLOGV0 = 0
47 REVLOGNG = 1
47 REVLOGNG = 1
48 REVLOGNGINLINEDATA = (1 << 16)
48 REVLOGNGINLINEDATA = (1 << 16)
49 REVLOGGENERALDELTA = (1 << 17)
49 REVLOGGENERALDELTA = (1 << 17)
50 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
50 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
51 REVLOG_DEFAULT_FORMAT = REVLOGNG
51 REVLOG_DEFAULT_FORMAT = REVLOGNG
52 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
52 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
53 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
53 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
54
54
55 # revlog index flags
55 # revlog index flags
56 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
56 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
57 REVIDX_DEFAULT_FLAGS = 0
57 REVIDX_DEFAULT_FLAGS = 0
58 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
58 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
59
59
60 # max size of revlog with inline data
60 # max size of revlog with inline data
61 _maxinline = 131072
61 _maxinline = 131072
62 _chunksize = 1048576
62 _chunksize = 1048576
63
63
64 RevlogError = error.RevlogError
64 RevlogError = error.RevlogError
65 LookupError = error.LookupError
65 LookupError = error.LookupError
66 CensoredNodeError = error.CensoredNodeError
66 CensoredNodeError = error.CensoredNodeError
67
67
68 def getoffset(q):
68 def getoffset(q):
69 return int(q >> 16)
69 return int(q >> 16)
70
70
71 def gettype(q):
71 def gettype(q):
72 return int(q & 0xFFFF)
72 return int(q & 0xFFFF)
73
73
74 def offset_type(offset, type):
74 def offset_type(offset, type):
75 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
75 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
76 raise ValueError('unknown revlog index flags')
76 raise ValueError('unknown revlog index flags')
77 return long(long(offset) << 16 | type)
77 return long(long(offset) << 16 | type)
78
78
79 _nullhash = hashlib.sha1(nullid)
79 _nullhash = hashlib.sha1(nullid)
80
80
81 def hash(text, p1, p2):
81 def hash(text, p1, p2):
82 """generate a hash from the given text and its parent hashes
82 """generate a hash from the given text and its parent hashes
83
83
84 This hash combines both the current file contents and its history
84 This hash combines both the current file contents and its history
85 in a manner that makes it easy to distinguish nodes with the same
85 in a manner that makes it easy to distinguish nodes with the same
86 content in the revision graph.
86 content in the revision graph.
87 """
87 """
88 # As of now, if one of the parent node is null, p2 is null
88 # As of now, if one of the parent node is null, p2 is null
89 if p2 == nullid:
89 if p2 == nullid:
90 # deep copy of a hash is faster than creating one
90 # deep copy of a hash is faster than creating one
91 s = _nullhash.copy()
91 s = _nullhash.copy()
92 s.update(p1)
92 s.update(p1)
93 else:
93 else:
94 # none of the parent nodes are nullid
94 # none of the parent nodes are nullid
95 l = [p1, p2]
95 l = [p1, p2]
96 l.sort()
96 l.sort()
97 s = hashlib.sha1(l[0])
97 s = hashlib.sha1(l[0])
98 s.update(l[1])
98 s.update(l[1])
99 s.update(text)
99 s.update(text)
100 return s.digest()
100 return s.digest()
101
101
102 def decompress(bin):
102 def decompress(bin):
103 """ decompress the given input """
103 """ decompress the given input """
104 if not bin:
104 if not bin:
105 return bin
105 return bin
106 t = bin[0]
106 t = bin[0]
107 if t == '\0':
107 if t == '\0':
108 return bin
108 return bin
109 if t == 'x':
109 if t == 'x':
110 try:
110 try:
111 return _decompress(bin)
111 return _decompress(bin)
112 except zlib.error as e:
112 except zlib.error as e:
113 raise RevlogError(_("revlog decompress error: %s") % str(e))
113 raise RevlogError(_("revlog decompress error: %s") % str(e))
114 if t == 'u':
114 if t == 'u':
115 return util.buffer(bin, 1)
115 return util.buffer(bin, 1)
116 raise RevlogError(_("unknown compression type %r") % t)
116 raise RevlogError(_("unknown compression type %r") % t)
117
117
118 # index v0:
118 # index v0:
119 # 4 bytes: offset
119 # 4 bytes: offset
120 # 4 bytes: compressed length
120 # 4 bytes: compressed length
121 # 4 bytes: base rev
121 # 4 bytes: base rev
122 # 4 bytes: link rev
122 # 4 bytes: link rev
123 # 20 bytes: parent 1 nodeid
123 # 20 bytes: parent 1 nodeid
124 # 20 bytes: parent 2 nodeid
124 # 20 bytes: parent 2 nodeid
125 # 20 bytes: nodeid
125 # 20 bytes: nodeid
126 indexformatv0 = ">4l20s20s20s"
126 indexformatv0 = ">4l20s20s20s"
127
127
128 class revlogoldio(object):
128 class revlogoldio(object):
129 def __init__(self):
129 def __init__(self):
130 self.size = struct.calcsize(indexformatv0)
130 self.size = struct.calcsize(indexformatv0)
131
131
132 def parseindex(self, data, inline):
132 def parseindex(self, data, inline):
133 s = self.size
133 s = self.size
134 index = []
134 index = []
135 nodemap = {nullid: nullrev}
135 nodemap = {nullid: nullrev}
136 n = off = 0
136 n = off = 0
137 l = len(data)
137 l = len(data)
138 while off + s <= l:
138 while off + s <= l:
139 cur = data[off:off + s]
139 cur = data[off:off + s]
140 off += s
140 off += s
141 e = _unpack(indexformatv0, cur)
141 e = _unpack(indexformatv0, cur)
142 # transform to revlogv1 format
142 # transform to revlogv1 format
143 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
143 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
144 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
144 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
145 index.append(e2)
145 index.append(e2)
146 nodemap[e[6]] = n
146 nodemap[e[6]] = n
147 n += 1
147 n += 1
148
148
149 # add the magic null revision at -1
149 # add the magic null revision at -1
150 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
150 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
151
151
152 return index, nodemap, None
152 return index, nodemap, None
153
153
154 def packentry(self, entry, node, version, rev):
154 def packentry(self, entry, node, version, rev):
155 if gettype(entry[0]):
155 if gettype(entry[0]):
156 raise RevlogError(_("index entry flags need RevlogNG"))
156 raise RevlogError(_("index entry flags need RevlogNG"))
157 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
157 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
158 node(entry[5]), node(entry[6]), entry[7])
158 node(entry[5]), node(entry[6]), entry[7])
159 return _pack(indexformatv0, *e2)
159 return _pack(indexformatv0, *e2)
160
160
161 # index ng:
161 # index ng:
162 # 6 bytes: offset
162 # 6 bytes: offset
163 # 2 bytes: flags
163 # 2 bytes: flags
164 # 4 bytes: compressed length
164 # 4 bytes: compressed length
165 # 4 bytes: uncompressed length
165 # 4 bytes: uncompressed length
166 # 4 bytes: base rev
166 # 4 bytes: base rev
167 # 4 bytes: link rev
167 # 4 bytes: link rev
168 # 4 bytes: parent 1 rev
168 # 4 bytes: parent 1 rev
169 # 4 bytes: parent 2 rev
169 # 4 bytes: parent 2 rev
170 # 32 bytes: nodeid
170 # 32 bytes: nodeid
171 indexformatng = ">Qiiiiii20s12x"
171 indexformatng = ">Qiiiiii20s12x"
172 versionformat = ">I"
172 versionformat = ">I"
173
173
174 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
174 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
175 # signed integer)
175 # signed integer)
176 _maxentrysize = 0x7fffffff
176 _maxentrysize = 0x7fffffff
177
177
178 class revlogio(object):
178 class revlogio(object):
179 def __init__(self):
179 def __init__(self):
180 self.size = struct.calcsize(indexformatng)
180 self.size = struct.calcsize(indexformatng)
181
181
182 def parseindex(self, data, inline):
182 def parseindex(self, data, inline):
183 # call the C implementation to parse the index data
183 # call the C implementation to parse the index data
184 index, cache = parsers.parse_index2(data, inline)
184 index, cache = parsers.parse_index2(data, inline)
185 return index, getattr(index, 'nodemap', None), cache
185 return index, getattr(index, 'nodemap', None), cache
186
186
187 def packentry(self, entry, node, version, rev):
187 def packentry(self, entry, node, version, rev):
188 p = _pack(indexformatng, *entry)
188 p = _pack(indexformatng, *entry)
189 if rev == 0:
189 if rev == 0:
190 p = _pack(versionformat, version) + p[4:]
190 p = _pack(versionformat, version) + p[4:]
191 return p
191 return p
192
192
193 class revlog(object):
193 class revlog(object):
194 """
194 """
195 the underlying revision storage object
195 the underlying revision storage object
196
196
197 A revlog consists of two parts, an index and the revision data.
197 A revlog consists of two parts, an index and the revision data.
198
198
199 The index is a file with a fixed record size containing
199 The index is a file with a fixed record size containing
200 information on each revision, including its nodeid (hash), the
200 information on each revision, including its nodeid (hash), the
201 nodeids of its parents, the position and offset of its data within
201 nodeids of its parents, the position and offset of its data within
202 the data file, and the revision it's based on. Finally, each entry
202 the data file, and the revision it's based on. Finally, each entry
203 contains a linkrev entry that can serve as a pointer to external
203 contains a linkrev entry that can serve as a pointer to external
204 data.
204 data.
205
205
206 The revision data itself is a linear collection of data chunks.
206 The revision data itself is a linear collection of data chunks.
207 Each chunk represents a revision and is usually represented as a
207 Each chunk represents a revision and is usually represented as a
208 delta against the previous chunk. To bound lookup time, runs of
208 delta against the previous chunk. To bound lookup time, runs of
209 deltas are limited to about 2 times the length of the original
209 deltas are limited to about 2 times the length of the original
210 version data. This makes retrieval of a version proportional to
210 version data. This makes retrieval of a version proportional to
211 its size, or O(1) relative to the number of revisions.
211 its size, or O(1) relative to the number of revisions.
212
212
213 Both pieces of the revlog are written to in an append-only
213 Both pieces of the revlog are written to in an append-only
214 fashion, which means we never need to rewrite a file to insert or
214 fashion, which means we never need to rewrite a file to insert or
215 remove data, and can use some simple techniques to avoid the need
215 remove data, and can use some simple techniques to avoid the need
216 for locking while reading.
216 for locking while reading.
217
217
218 If checkambig, indexfile is opened with checkambig=True at
218 If checkambig, indexfile is opened with checkambig=True at
219 writing, to avoid file stat ambiguity.
219 writing, to avoid file stat ambiguity.
220 """
220 """
221 def __init__(self, opener, indexfile, checkambig=False):
221 def __init__(self, opener, indexfile, checkambig=False):
222 """
222 """
223 create a revlog object
223 create a revlog object
224
224
225 opener is a function that abstracts the file opening operation
225 opener is a function that abstracts the file opening operation
226 and can be used to implement COW semantics or the like.
226 and can be used to implement COW semantics or the like.
227 """
227 """
228 self.indexfile = indexfile
228 self.indexfile = indexfile
229 self.datafile = indexfile[:-2] + ".d"
229 self.datafile = indexfile[:-2] + ".d"
230 self.opener = opener
230 self.opener = opener
231 # When True, indexfile is opened with checkambig=True at writing, to
231 # When True, indexfile is opened with checkambig=True at writing, to
232 # avoid file stat ambiguity.
232 # avoid file stat ambiguity.
233 self._checkambig = checkambig
233 self._checkambig = checkambig
234 # 3-tuple of (node, rev, text) for a raw revision.
234 # 3-tuple of (node, rev, text) for a raw revision.
235 self._cache = None
235 self._cache = None
236 # Maps rev to chain base rev.
236 # Maps rev to chain base rev.
237 self._chainbasecache = util.lrucachedict(100)
237 self._chainbasecache = util.lrucachedict(100)
238 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
238 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
239 self._chunkcache = (0, '')
239 self._chunkcache = (0, '')
240 # How much data to read and cache into the raw revlog data cache.
240 # How much data to read and cache into the raw revlog data cache.
241 self._chunkcachesize = 65536
241 self._chunkcachesize = 65536
242 self._maxchainlen = None
242 self._maxchainlen = None
243 self._aggressivemergedeltas = False
243 self._aggressivemergedeltas = False
244 self.index = []
244 self.index = []
245 # Mapping of partial identifiers to full nodes.
245 # Mapping of partial identifiers to full nodes.
246 self._pcache = {}
246 self._pcache = {}
247 # Mapping of revision integer to full node.
247 # Mapping of revision integer to full node.
248 self._nodecache = {nullid: nullrev}
248 self._nodecache = {nullid: nullrev}
249 self._nodepos = None
249 self._nodepos = None
250
250
251 v = REVLOG_DEFAULT_VERSION
251 v = REVLOG_DEFAULT_VERSION
252 opts = getattr(opener, 'options', None)
252 opts = getattr(opener, 'options', None)
253 if opts is not None:
253 if opts is not None:
254 if 'revlogv1' in opts:
254 if 'revlogv1' in opts:
255 if 'generaldelta' in opts:
255 if 'generaldelta' in opts:
256 v |= REVLOGGENERALDELTA
256 v |= REVLOGGENERALDELTA
257 else:
257 else:
258 v = 0
258 v = 0
259 if 'chunkcachesize' in opts:
259 if 'chunkcachesize' in opts:
260 self._chunkcachesize = opts['chunkcachesize']
260 self._chunkcachesize = opts['chunkcachesize']
261 if 'maxchainlen' in opts:
261 if 'maxchainlen' in opts:
262 self._maxchainlen = opts['maxchainlen']
262 self._maxchainlen = opts['maxchainlen']
263 if 'aggressivemergedeltas' in opts:
263 if 'aggressivemergedeltas' in opts:
264 self._aggressivemergedeltas = opts['aggressivemergedeltas']
264 self._aggressivemergedeltas = opts['aggressivemergedeltas']
265 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
265 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
266
266
267 if self._chunkcachesize <= 0:
267 if self._chunkcachesize <= 0:
268 raise RevlogError(_('revlog chunk cache size %r is not greater '
268 raise RevlogError(_('revlog chunk cache size %r is not greater '
269 'than 0') % self._chunkcachesize)
269 'than 0') % self._chunkcachesize)
270 elif self._chunkcachesize & (self._chunkcachesize - 1):
270 elif self._chunkcachesize & (self._chunkcachesize - 1):
271 raise RevlogError(_('revlog chunk cache size %r is not a power '
271 raise RevlogError(_('revlog chunk cache size %r is not a power '
272 'of 2') % self._chunkcachesize)
272 'of 2') % self._chunkcachesize)
273
273
274 indexdata = ''
274 indexdata = ''
275 self._initempty = True
275 self._initempty = True
276 try:
276 try:
277 f = self.opener(self.indexfile)
277 f = self.opener(self.indexfile)
278 indexdata = f.read()
278 indexdata = f.read()
279 f.close()
279 f.close()
280 if len(indexdata) > 0:
280 if len(indexdata) > 0:
281 v = struct.unpack(versionformat, indexdata[:4])[0]
281 v = struct.unpack(versionformat, indexdata[:4])[0]
282 self._initempty = False
282 self._initempty = False
283 except IOError as inst:
283 except IOError as inst:
284 if inst.errno != errno.ENOENT:
284 if inst.errno != errno.ENOENT:
285 raise
285 raise
286
286
287 self.version = v
287 self.version = v
288 self._inline = v & REVLOGNGINLINEDATA
288 self._inline = v & REVLOGNGINLINEDATA
289 self._generaldelta = v & REVLOGGENERALDELTA
289 self._generaldelta = v & REVLOGGENERALDELTA
290 flags = v & ~0xFFFF
290 flags = v & ~0xFFFF
291 fmt = v & 0xFFFF
291 fmt = v & 0xFFFF
292 if fmt == REVLOGV0 and flags:
292 if fmt == REVLOGV0 and flags:
293 raise RevlogError(_("index %s unknown flags %#04x for format v0")
293 raise RevlogError(_("index %s unknown flags %#04x for format v0")
294 % (self.indexfile, flags >> 16))
294 % (self.indexfile, flags >> 16))
295 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
295 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
296 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
296 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
297 % (self.indexfile, flags >> 16))
297 % (self.indexfile, flags >> 16))
298 elif fmt > REVLOGNG:
298 elif fmt > REVLOGNG:
299 raise RevlogError(_("index %s unknown format %d")
299 raise RevlogError(_("index %s unknown format %d")
300 % (self.indexfile, fmt))
300 % (self.indexfile, fmt))
301
301
302 self.storedeltachains = True
302 self.storedeltachains = True
303
303
304 self._io = revlogio()
304 self._io = revlogio()
305 if self.version == REVLOGV0:
305 if self.version == REVLOGV0:
306 self._io = revlogoldio()
306 self._io = revlogoldio()
307 try:
307 try:
308 d = self._io.parseindex(indexdata, self._inline)
308 d = self._io.parseindex(indexdata, self._inline)
309 except (ValueError, IndexError):
309 except (ValueError, IndexError):
310 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
310 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
311 self.index, nodemap, self._chunkcache = d
311 self.index, nodemap, self._chunkcache = d
312 if nodemap is not None:
312 if nodemap is not None:
313 self.nodemap = self._nodecache = nodemap
313 self.nodemap = self._nodecache = nodemap
314 if not self._chunkcache:
314 if not self._chunkcache:
315 self._chunkclear()
315 self._chunkclear()
316 # revnum -> (chain-length, sum-delta-length)
316 # revnum -> (chain-length, sum-delta-length)
317 self._chaininfocache = {}
317 self._chaininfocache = {}
318
318
319 def tip(self):
319 def tip(self):
320 return self.node(len(self.index) - 2)
320 return self.node(len(self.index) - 2)
321 def __contains__(self, rev):
321 def __contains__(self, rev):
322 return 0 <= rev < len(self)
322 return 0 <= rev < len(self)
323 def __len__(self):
323 def __len__(self):
324 return len(self.index) - 1
324 return len(self.index) - 1
325 def __iter__(self):
325 def __iter__(self):
326 return iter(xrange(len(self)))
326 return iter(xrange(len(self)))
327 def revs(self, start=0, stop=None):
327 def revs(self, start=0, stop=None):
328 """iterate over all rev in this revlog (from start to stop)"""
328 """iterate over all rev in this revlog (from start to stop)"""
329 step = 1
329 step = 1
330 if stop is not None:
330 if stop is not None:
331 if start > stop:
331 if start > stop:
332 step = -1
332 step = -1
333 stop += step
333 stop += step
334 else:
334 else:
335 stop = len(self)
335 stop = len(self)
336 return xrange(start, stop, step)
336 return xrange(start, stop, step)
337
337
338 @util.propertycache
338 @util.propertycache
339 def nodemap(self):
339 def nodemap(self):
340 self.rev(self.node(0))
340 self.rev(self.node(0))
341 return self._nodecache
341 return self._nodecache
342
342
343 def hasnode(self, node):
343 def hasnode(self, node):
344 try:
344 try:
345 self.rev(node)
345 self.rev(node)
346 return True
346 return True
347 except KeyError:
347 except KeyError:
348 return False
348 return False
349
349
350 def clearcaches(self):
350 def clearcaches(self):
351 self._cache = None
351 self._cache = None
352 self._chainbasecache.clear()
352 self._chainbasecache.clear()
353 self._chunkcache = (0, '')
353 self._chunkcache = (0, '')
354 self._pcache = {}
354 self._pcache = {}
355
355
356 try:
356 try:
357 self._nodecache.clearcaches()
357 self._nodecache.clearcaches()
358 except AttributeError:
358 except AttributeError:
359 self._nodecache = {nullid: nullrev}
359 self._nodecache = {nullid: nullrev}
360 self._nodepos = None
360 self._nodepos = None
361
361
362 def rev(self, node):
362 def rev(self, node):
363 try:
363 try:
364 return self._nodecache[node]
364 return self._nodecache[node]
365 except TypeError:
365 except TypeError:
366 raise
366 raise
367 except RevlogError:
367 except RevlogError:
368 # parsers.c radix tree lookup failed
368 # parsers.c radix tree lookup failed
369 raise LookupError(node, self.indexfile, _('no node'))
369 raise LookupError(node, self.indexfile, _('no node'))
370 except KeyError:
370 except KeyError:
371 # pure python cache lookup failed
371 # pure python cache lookup failed
372 n = self._nodecache
372 n = self._nodecache
373 i = self.index
373 i = self.index
374 p = self._nodepos
374 p = self._nodepos
375 if p is None:
375 if p is None:
376 p = len(i) - 2
376 p = len(i) - 2
377 for r in xrange(p, -1, -1):
377 for r in xrange(p, -1, -1):
378 v = i[r][7]
378 v = i[r][7]
379 n[v] = r
379 n[v] = r
380 if v == node:
380 if v == node:
381 self._nodepos = r - 1
381 self._nodepos = r - 1
382 return r
382 return r
383 raise LookupError(node, self.indexfile, _('no node'))
383 raise LookupError(node, self.indexfile, _('no node'))
384
384
385 # Accessors for index entries.
385 # Accessors for index entries.
386
386
387 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
387 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
388 # are flags.
388 # are flags.
389 def start(self, rev):
389 def start(self, rev):
390 return int(self.index[rev][0] >> 16)
390 return int(self.index[rev][0] >> 16)
391
391
392 def flags(self, rev):
392 def flags(self, rev):
393 return self.index[rev][0] & 0xFFFF
393 return self.index[rev][0] & 0xFFFF
394
394
395 def length(self, rev):
395 def length(self, rev):
396 return self.index[rev][1]
396 return self.index[rev][1]
397
397
398 def rawsize(self, rev):
398 def rawsize(self, rev):
399 """return the length of the uncompressed text for a given revision"""
399 """return the length of the uncompressed text for a given revision"""
400 l = self.index[rev][2]
400 l = self.index[rev][2]
401 if l >= 0:
401 if l >= 0:
402 return l
402 return l
403
403
404 t = self.revision(self.node(rev))
404 t = self.revision(self.node(rev))
405 return len(t)
405 return len(t)
406 size = rawsize
406 size = rawsize
407
407
408 def chainbase(self, rev):
408 def chainbase(self, rev):
409 base = self._chainbasecache.get(rev)
409 base = self._chainbasecache.get(rev)
410 if base is not None:
410 if base is not None:
411 return base
411 return base
412
412
413 index = self.index
413 index = self.index
414 base = index[rev][3]
414 base = index[rev][3]
415 while base != rev:
415 while base != rev:
416 rev = base
416 rev = base
417 base = index[rev][3]
417 base = index[rev][3]
418
418
419 self._chainbasecache[rev] = base
419 self._chainbasecache[rev] = base
420 return base
420 return base
421
421
422 def linkrev(self, rev):
422 def linkrev(self, rev):
423 return self.index[rev][4]
423 return self.index[rev][4]
424
424
425 def parentrevs(self, rev):
425 def parentrevs(self, rev):
426 return self.index[rev][5:7]
426 return self.index[rev][5:7]
427
427
428 def node(self, rev):
428 def node(self, rev):
429 return self.index[rev][7]
429 return self.index[rev][7]
430
430
431 # Derived from index values.
431 # Derived from index values.
432
432
433 def end(self, rev):
433 def end(self, rev):
434 return self.start(rev) + self.length(rev)
434 return self.start(rev) + self.length(rev)
435
435
436 def parents(self, node):
436 def parents(self, node):
437 i = self.index
437 i = self.index
438 d = i[self.rev(node)]
438 d = i[self.rev(node)]
439 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
439 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
440
440
441 def chainlen(self, rev):
441 def chainlen(self, rev):
442 return self._chaininfo(rev)[0]
442 return self._chaininfo(rev)[0]
443
443
444 def _chaininfo(self, rev):
444 def _chaininfo(self, rev):
445 chaininfocache = self._chaininfocache
445 chaininfocache = self._chaininfocache
446 if rev in chaininfocache:
446 if rev in chaininfocache:
447 return chaininfocache[rev]
447 return chaininfocache[rev]
448 index = self.index
448 index = self.index
449 generaldelta = self._generaldelta
449 generaldelta = self._generaldelta
450 iterrev = rev
450 iterrev = rev
451 e = index[iterrev]
451 e = index[iterrev]
452 clen = 0
452 clen = 0
453 compresseddeltalen = 0
453 compresseddeltalen = 0
454 while iterrev != e[3]:
454 while iterrev != e[3]:
455 clen += 1
455 clen += 1
456 compresseddeltalen += e[1]
456 compresseddeltalen += e[1]
457 if generaldelta:
457 if generaldelta:
458 iterrev = e[3]
458 iterrev = e[3]
459 else:
459 else:
460 iterrev -= 1
460 iterrev -= 1
461 if iterrev in chaininfocache:
461 if iterrev in chaininfocache:
462 t = chaininfocache[iterrev]
462 t = chaininfocache[iterrev]
463 clen += t[0]
463 clen += t[0]
464 compresseddeltalen += t[1]
464 compresseddeltalen += t[1]
465 break
465 break
466 e = index[iterrev]
466 e = index[iterrev]
467 else:
467 else:
468 # Add text length of base since decompressing that also takes
468 # Add text length of base since decompressing that also takes
469 # work. For cache hits the length is already included.
469 # work. For cache hits the length is already included.
470 compresseddeltalen += e[1]
470 compresseddeltalen += e[1]
471 r = (clen, compresseddeltalen)
471 r = (clen, compresseddeltalen)
472 chaininfocache[rev] = r
472 chaininfocache[rev] = r
473 return r
473 return r
474
474
475 def _deltachain(self, rev, stoprev=None):
475 def _deltachain(self, rev, stoprev=None):
476 """Obtain the delta chain for a revision.
476 """Obtain the delta chain for a revision.
477
477
478 ``stoprev`` specifies a revision to stop at. If not specified, we
478 ``stoprev`` specifies a revision to stop at. If not specified, we
479 stop at the base of the chain.
479 stop at the base of the chain.
480
480
481 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
481 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
482 revs in ascending order and ``stopped`` is a bool indicating whether
482 revs in ascending order and ``stopped`` is a bool indicating whether
483 ``stoprev`` was hit.
483 ``stoprev`` was hit.
484 """
484 """
485 chain = []
485 chain = []
486
486
487 # Alias to prevent attribute lookup in tight loop.
487 # Alias to prevent attribute lookup in tight loop.
488 index = self.index
488 index = self.index
489 generaldelta = self._generaldelta
489 generaldelta = self._generaldelta
490
490
491 iterrev = rev
491 iterrev = rev
492 e = index[iterrev]
492 e = index[iterrev]
493 while iterrev != e[3] and iterrev != stoprev:
493 while iterrev != e[3] and iterrev != stoprev:
494 chain.append(iterrev)
494 chain.append(iterrev)
495 if generaldelta:
495 if generaldelta:
496 iterrev = e[3]
496 iterrev = e[3]
497 else:
497 else:
498 iterrev -= 1
498 iterrev -= 1
499 e = index[iterrev]
499 e = index[iterrev]
500
500
501 if iterrev == stoprev:
501 if iterrev == stoprev:
502 stopped = True
502 stopped = True
503 else:
503 else:
504 chain.append(iterrev)
504 chain.append(iterrev)
505 stopped = False
505 stopped = False
506
506
507 chain.reverse()
507 chain.reverse()
508 return chain, stopped
508 return chain, stopped
509
509
510 def ancestors(self, revs, stoprev=0, inclusive=False):
510 def ancestors(self, revs, stoprev=0, inclusive=False):
511 """Generate the ancestors of 'revs' in reverse topological order.
511 """Generate the ancestors of 'revs' in reverse topological order.
512 Does not generate revs lower than stoprev.
512 Does not generate revs lower than stoprev.
513
513
514 See the documentation for ancestor.lazyancestors for more details."""
514 See the documentation for ancestor.lazyancestors for more details."""
515
515
516 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
516 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
517 inclusive=inclusive)
517 inclusive=inclusive)
518
518
519 def descendants(self, revs):
519 def descendants(self, revs):
520 """Generate the descendants of 'revs' in revision order.
520 """Generate the descendants of 'revs' in revision order.
521
521
522 Yield a sequence of revision numbers starting with a child of
522 Yield a sequence of revision numbers starting with a child of
523 some rev in revs, i.e., each revision is *not* considered a
523 some rev in revs, i.e., each revision is *not* considered a
524 descendant of itself. Results are ordered by revision number (a
524 descendant of itself. Results are ordered by revision number (a
525 topological sort)."""
525 topological sort)."""
526 first = min(revs)
526 first = min(revs)
527 if first == nullrev:
527 if first == nullrev:
528 for i in self:
528 for i in self:
529 yield i
529 yield i
530 return
530 return
531
531
532 seen = set(revs)
532 seen = set(revs)
533 for i in self.revs(start=first + 1):
533 for i in self.revs(start=first + 1):
534 for x in self.parentrevs(i):
534 for x in self.parentrevs(i):
535 if x != nullrev and x in seen:
535 if x != nullrev and x in seen:
536 seen.add(i)
536 seen.add(i)
537 yield i
537 yield i
538 break
538 break
539
539
540 def findcommonmissing(self, common=None, heads=None):
540 def findcommonmissing(self, common=None, heads=None):
541 """Return a tuple of the ancestors of common and the ancestors of heads
541 """Return a tuple of the ancestors of common and the ancestors of heads
542 that are not ancestors of common. In revset terminology, we return the
542 that are not ancestors of common. In revset terminology, we return the
543 tuple:
543 tuple:
544
544
545 ::common, (::heads) - (::common)
545 ::common, (::heads) - (::common)
546
546
547 The list is sorted by revision number, meaning it is
547 The list is sorted by revision number, meaning it is
548 topologically sorted.
548 topologically sorted.
549
549
550 'heads' and 'common' are both lists of node IDs. If heads is
550 'heads' and 'common' are both lists of node IDs. If heads is
551 not supplied, uses all of the revlog's heads. If common is not
551 not supplied, uses all of the revlog's heads. If common is not
552 supplied, uses nullid."""
552 supplied, uses nullid."""
553 if common is None:
553 if common is None:
554 common = [nullid]
554 common = [nullid]
555 if heads is None:
555 if heads is None:
556 heads = self.heads()
556 heads = self.heads()
557
557
558 common = [self.rev(n) for n in common]
558 common = [self.rev(n) for n in common]
559 heads = [self.rev(n) for n in heads]
559 heads = [self.rev(n) for n in heads]
560
560
561 # we want the ancestors, but inclusive
561 # we want the ancestors, but inclusive
562 class lazyset(object):
562 class lazyset(object):
563 def __init__(self, lazyvalues):
563 def __init__(self, lazyvalues):
564 self.addedvalues = set()
564 self.addedvalues = set()
565 self.lazyvalues = lazyvalues
565 self.lazyvalues = lazyvalues
566
566
567 def __contains__(self, value):
567 def __contains__(self, value):
568 return value in self.addedvalues or value in self.lazyvalues
568 return value in self.addedvalues or value in self.lazyvalues
569
569
570 def __iter__(self):
570 def __iter__(self):
571 added = self.addedvalues
571 added = self.addedvalues
572 for r in added:
572 for r in added:
573 yield r
573 yield r
574 for r in self.lazyvalues:
574 for r in self.lazyvalues:
575 if not r in added:
575 if not r in added:
576 yield r
576 yield r
577
577
578 def add(self, value):
578 def add(self, value):
579 self.addedvalues.add(value)
579 self.addedvalues.add(value)
580
580
581 def update(self, values):
581 def update(self, values):
582 self.addedvalues.update(values)
582 self.addedvalues.update(values)
583
583
584 has = lazyset(self.ancestors(common))
584 has = lazyset(self.ancestors(common))
585 has.add(nullrev)
585 has.add(nullrev)
586 has.update(common)
586 has.update(common)
587
587
588 # take all ancestors from heads that aren't in has
588 # take all ancestors from heads that aren't in has
589 missing = set()
589 missing = set()
590 visit = collections.deque(r for r in heads if r not in has)
590 visit = collections.deque(r for r in heads if r not in has)
591 while visit:
591 while visit:
592 r = visit.popleft()
592 r = visit.popleft()
593 if r in missing:
593 if r in missing:
594 continue
594 continue
595 else:
595 else:
596 missing.add(r)
596 missing.add(r)
597 for p in self.parentrevs(r):
597 for p in self.parentrevs(r):
598 if p not in has:
598 if p not in has:
599 visit.append(p)
599 visit.append(p)
600 missing = list(missing)
600 missing = list(missing)
601 missing.sort()
601 missing.sort()
602 return has, [self.node(miss) for miss in missing]
602 return has, [self.node(miss) for miss in missing]
603
603
604 def incrementalmissingrevs(self, common=None):
604 def incrementalmissingrevs(self, common=None):
605 """Return an object that can be used to incrementally compute the
605 """Return an object that can be used to incrementally compute the
606 revision numbers of the ancestors of arbitrary sets that are not
606 revision numbers of the ancestors of arbitrary sets that are not
607 ancestors of common. This is an ancestor.incrementalmissingancestors
607 ancestors of common. This is an ancestor.incrementalmissingancestors
608 object.
608 object.
609
609
610 'common' is a list of revision numbers. If common is not supplied, uses
610 'common' is a list of revision numbers. If common is not supplied, uses
611 nullrev.
611 nullrev.
612 """
612 """
613 if common is None:
613 if common is None:
614 common = [nullrev]
614 common = [nullrev]
615
615
616 return ancestor.incrementalmissingancestors(self.parentrevs, common)
616 return ancestor.incrementalmissingancestors(self.parentrevs, common)
617
617
618 def findmissingrevs(self, common=None, heads=None):
618 def findmissingrevs(self, common=None, heads=None):
619 """Return the revision numbers of the ancestors of heads that
619 """Return the revision numbers of the ancestors of heads that
620 are not ancestors of common.
620 are not ancestors of common.
621
621
622 More specifically, return a list of revision numbers corresponding to
622 More specifically, return a list of revision numbers corresponding to
623 nodes N such that every N satisfies the following constraints:
623 nodes N such that every N satisfies the following constraints:
624
624
625 1. N is an ancestor of some node in 'heads'
625 1. N is an ancestor of some node in 'heads'
626 2. N is not an ancestor of any node in 'common'
626 2. N is not an ancestor of any node in 'common'
627
627
628 The list is sorted by revision number, meaning it is
628 The list is sorted by revision number, meaning it is
629 topologically sorted.
629 topologically sorted.
630
630
631 'heads' and 'common' are both lists of revision numbers. If heads is
631 'heads' and 'common' are both lists of revision numbers. If heads is
632 not supplied, uses all of the revlog's heads. If common is not
632 not supplied, uses all of the revlog's heads. If common is not
633 supplied, uses nullid."""
633 supplied, uses nullid."""
634 if common is None:
634 if common is None:
635 common = [nullrev]
635 common = [nullrev]
636 if heads is None:
636 if heads is None:
637 heads = self.headrevs()
637 heads = self.headrevs()
638
638
639 inc = self.incrementalmissingrevs(common=common)
639 inc = self.incrementalmissingrevs(common=common)
640 return inc.missingancestors(heads)
640 return inc.missingancestors(heads)
641
641
642 def findmissing(self, common=None, heads=None):
642 def findmissing(self, common=None, heads=None):
643 """Return the ancestors of heads that are not ancestors of common.
643 """Return the ancestors of heads that are not ancestors of common.
644
644
645 More specifically, return a list of nodes N such that every N
645 More specifically, return a list of nodes N such that every N
646 satisfies the following constraints:
646 satisfies the following constraints:
647
647
648 1. N is an ancestor of some node in 'heads'
648 1. N is an ancestor of some node in 'heads'
649 2. N is not an ancestor of any node in 'common'
649 2. N is not an ancestor of any node in 'common'
650
650
651 The list is sorted by revision number, meaning it is
651 The list is sorted by revision number, meaning it is
652 topologically sorted.
652 topologically sorted.
653
653
654 'heads' and 'common' are both lists of node IDs. If heads is
654 'heads' and 'common' are both lists of node IDs. If heads is
655 not supplied, uses all of the revlog's heads. If common is not
655 not supplied, uses all of the revlog's heads. If common is not
656 supplied, uses nullid."""
656 supplied, uses nullid."""
657 if common is None:
657 if common is None:
658 common = [nullid]
658 common = [nullid]
659 if heads is None:
659 if heads is None:
660 heads = self.heads()
660 heads = self.heads()
661
661
662 common = [self.rev(n) for n in common]
662 common = [self.rev(n) for n in common]
663 heads = [self.rev(n) for n in heads]
663 heads = [self.rev(n) for n in heads]
664
664
665 inc = self.incrementalmissingrevs(common=common)
665 inc = self.incrementalmissingrevs(common=common)
666 return [self.node(r) for r in inc.missingancestors(heads)]
666 return [self.node(r) for r in inc.missingancestors(heads)]
667
667
668 def nodesbetween(self, roots=None, heads=None):
668 def nodesbetween(self, roots=None, heads=None):
669 """Return a topological path from 'roots' to 'heads'.
669 """Return a topological path from 'roots' to 'heads'.
670
670
671 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
671 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
672 topologically sorted list of all nodes N that satisfy both of
672 topologically sorted list of all nodes N that satisfy both of
673 these constraints:
673 these constraints:
674
674
675 1. N is a descendant of some node in 'roots'
675 1. N is a descendant of some node in 'roots'
676 2. N is an ancestor of some node in 'heads'
676 2. N is an ancestor of some node in 'heads'
677
677
678 Every node is considered to be both a descendant and an ancestor
678 Every node is considered to be both a descendant and an ancestor
679 of itself, so every reachable node in 'roots' and 'heads' will be
679 of itself, so every reachable node in 'roots' and 'heads' will be
680 included in 'nodes'.
680 included in 'nodes'.
681
681
682 'outroots' is the list of reachable nodes in 'roots', i.e., the
682 'outroots' is the list of reachable nodes in 'roots', i.e., the
683 subset of 'roots' that is returned in 'nodes'. Likewise,
683 subset of 'roots' that is returned in 'nodes'. Likewise,
684 'outheads' is the subset of 'heads' that is also in 'nodes'.
684 'outheads' is the subset of 'heads' that is also in 'nodes'.
685
685
686 'roots' and 'heads' are both lists of node IDs. If 'roots' is
686 'roots' and 'heads' are both lists of node IDs. If 'roots' is
687 unspecified, uses nullid as the only root. If 'heads' is
687 unspecified, uses nullid as the only root. If 'heads' is
688 unspecified, uses list of all of the revlog's heads."""
688 unspecified, uses list of all of the revlog's heads."""
689 nonodes = ([], [], [])
689 nonodes = ([], [], [])
690 if roots is not None:
690 if roots is not None:
691 roots = list(roots)
691 roots = list(roots)
692 if not roots:
692 if not roots:
693 return nonodes
693 return nonodes
694 lowestrev = min([self.rev(n) for n in roots])
694 lowestrev = min([self.rev(n) for n in roots])
695 else:
695 else:
696 roots = [nullid] # Everybody's a descendant of nullid
696 roots = [nullid] # Everybody's a descendant of nullid
697 lowestrev = nullrev
697 lowestrev = nullrev
698 if (lowestrev == nullrev) and (heads is None):
698 if (lowestrev == nullrev) and (heads is None):
699 # We want _all_ the nodes!
699 # We want _all_ the nodes!
700 return ([self.node(r) for r in self], [nullid], list(self.heads()))
700 return ([self.node(r) for r in self], [nullid], list(self.heads()))
701 if heads is None:
701 if heads is None:
702 # All nodes are ancestors, so the latest ancestor is the last
702 # All nodes are ancestors, so the latest ancestor is the last
703 # node.
703 # node.
704 highestrev = len(self) - 1
704 highestrev = len(self) - 1
705 # Set ancestors to None to signal that every node is an ancestor.
705 # Set ancestors to None to signal that every node is an ancestor.
706 ancestors = None
706 ancestors = None
707 # Set heads to an empty dictionary for later discovery of heads
707 # Set heads to an empty dictionary for later discovery of heads
708 heads = {}
708 heads = {}
709 else:
709 else:
710 heads = list(heads)
710 heads = list(heads)
711 if not heads:
711 if not heads:
712 return nonodes
712 return nonodes
713 ancestors = set()
713 ancestors = set()
714 # Turn heads into a dictionary so we can remove 'fake' heads.
714 # Turn heads into a dictionary so we can remove 'fake' heads.
715 # Also, later we will be using it to filter out the heads we can't
715 # Also, later we will be using it to filter out the heads we can't
716 # find from roots.
716 # find from roots.
717 heads = dict.fromkeys(heads, False)
717 heads = dict.fromkeys(heads, False)
718 # Start at the top and keep marking parents until we're done.
718 # Start at the top and keep marking parents until we're done.
719 nodestotag = set(heads)
719 nodestotag = set(heads)
720 # Remember where the top was so we can use it as a limit later.
720 # Remember where the top was so we can use it as a limit later.
721 highestrev = max([self.rev(n) for n in nodestotag])
721 highestrev = max([self.rev(n) for n in nodestotag])
722 while nodestotag:
722 while nodestotag:
723 # grab a node to tag
723 # grab a node to tag
724 n = nodestotag.pop()
724 n = nodestotag.pop()
725 # Never tag nullid
725 # Never tag nullid
726 if n == nullid:
726 if n == nullid:
727 continue
727 continue
728 # A node's revision number represents its place in a
728 # A node's revision number represents its place in a
729 # topologically sorted list of nodes.
729 # topologically sorted list of nodes.
730 r = self.rev(n)
730 r = self.rev(n)
731 if r >= lowestrev:
731 if r >= lowestrev:
732 if n not in ancestors:
732 if n not in ancestors:
733 # If we are possibly a descendant of one of the roots
733 # If we are possibly a descendant of one of the roots
734 # and we haven't already been marked as an ancestor
734 # and we haven't already been marked as an ancestor
735 ancestors.add(n) # Mark as ancestor
735 ancestors.add(n) # Mark as ancestor
736 # Add non-nullid parents to list of nodes to tag.
736 # Add non-nullid parents to list of nodes to tag.
737 nodestotag.update([p for p in self.parents(n) if
737 nodestotag.update([p for p in self.parents(n) if
738 p != nullid])
738 p != nullid])
739 elif n in heads: # We've seen it before, is it a fake head?
739 elif n in heads: # We've seen it before, is it a fake head?
740 # So it is, real heads should not be the ancestors of
740 # So it is, real heads should not be the ancestors of
741 # any other heads.
741 # any other heads.
742 heads.pop(n)
742 heads.pop(n)
743 if not ancestors:
743 if not ancestors:
744 return nonodes
744 return nonodes
745 # Now that we have our set of ancestors, we want to remove any
745 # Now that we have our set of ancestors, we want to remove any
746 # roots that are not ancestors.
746 # roots that are not ancestors.
747
747
748 # If one of the roots was nullid, everything is included anyway.
748 # If one of the roots was nullid, everything is included anyway.
749 if lowestrev > nullrev:
749 if lowestrev > nullrev:
750 # But, since we weren't, let's recompute the lowest rev to not
750 # But, since we weren't, let's recompute the lowest rev to not
751 # include roots that aren't ancestors.
751 # include roots that aren't ancestors.
752
752
753 # Filter out roots that aren't ancestors of heads
753 # Filter out roots that aren't ancestors of heads
754 roots = [root for root in roots if root in ancestors]
754 roots = [root for root in roots if root in ancestors]
755 # Recompute the lowest revision
755 # Recompute the lowest revision
756 if roots:
756 if roots:
757 lowestrev = min([self.rev(root) for root in roots])
757 lowestrev = min([self.rev(root) for root in roots])
758 else:
758 else:
759 # No more roots? Return empty list
759 # No more roots? Return empty list
760 return nonodes
760 return nonodes
761 else:
761 else:
762 # We are descending from nullid, and don't need to care about
762 # We are descending from nullid, and don't need to care about
763 # any other roots.
763 # any other roots.
764 lowestrev = nullrev
764 lowestrev = nullrev
765 roots = [nullid]
765 roots = [nullid]
766 # Transform our roots list into a set.
766 # Transform our roots list into a set.
767 descendants = set(roots)
767 descendants = set(roots)
768 # Also, keep the original roots so we can filter out roots that aren't
768 # Also, keep the original roots so we can filter out roots that aren't
769 # 'real' roots (i.e. are descended from other roots).
769 # 'real' roots (i.e. are descended from other roots).
770 roots = descendants.copy()
770 roots = descendants.copy()
771 # Our topologically sorted list of output nodes.
771 # Our topologically sorted list of output nodes.
772 orderedout = []
772 orderedout = []
773 # Don't start at nullid since we don't want nullid in our output list,
773 # Don't start at nullid since we don't want nullid in our output list,
774 # and if nullid shows up in descendants, empty parents will look like
774 # and if nullid shows up in descendants, empty parents will look like
775 # they're descendants.
775 # they're descendants.
776 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
776 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
777 n = self.node(r)
777 n = self.node(r)
778 isdescendant = False
778 isdescendant = False
779 if lowestrev == nullrev: # Everybody is a descendant of nullid
779 if lowestrev == nullrev: # Everybody is a descendant of nullid
780 isdescendant = True
780 isdescendant = True
781 elif n in descendants:
781 elif n in descendants:
782 # n is already a descendant
782 # n is already a descendant
783 isdescendant = True
783 isdescendant = True
784 # This check only needs to be done here because all the roots
784 # This check only needs to be done here because all the roots
785 # will start being marked is descendants before the loop.
785 # will start being marked is descendants before the loop.
786 if n in roots:
786 if n in roots:
787 # If n was a root, check if it's a 'real' root.
787 # If n was a root, check if it's a 'real' root.
788 p = tuple(self.parents(n))
788 p = tuple(self.parents(n))
789 # If any of its parents are descendants, it's not a root.
789 # If any of its parents are descendants, it's not a root.
790 if (p[0] in descendants) or (p[1] in descendants):
790 if (p[0] in descendants) or (p[1] in descendants):
791 roots.remove(n)
791 roots.remove(n)
792 else:
792 else:
793 p = tuple(self.parents(n))
793 p = tuple(self.parents(n))
794 # A node is a descendant if either of its parents are
794 # A node is a descendant if either of its parents are
795 # descendants. (We seeded the dependents list with the roots
795 # descendants. (We seeded the dependents list with the roots
796 # up there, remember?)
796 # up there, remember?)
797 if (p[0] in descendants) or (p[1] in descendants):
797 if (p[0] in descendants) or (p[1] in descendants):
798 descendants.add(n)
798 descendants.add(n)
799 isdescendant = True
799 isdescendant = True
800 if isdescendant and ((ancestors is None) or (n in ancestors)):
800 if isdescendant and ((ancestors is None) or (n in ancestors)):
801 # Only include nodes that are both descendants and ancestors.
801 # Only include nodes that are both descendants and ancestors.
802 orderedout.append(n)
802 orderedout.append(n)
803 if (ancestors is not None) and (n in heads):
803 if (ancestors is not None) and (n in heads):
804 # We're trying to figure out which heads are reachable
804 # We're trying to figure out which heads are reachable
805 # from roots.
805 # from roots.
806 # Mark this head as having been reached
806 # Mark this head as having been reached
807 heads[n] = True
807 heads[n] = True
808 elif ancestors is None:
808 elif ancestors is None:
809 # Otherwise, we're trying to discover the heads.
809 # Otherwise, we're trying to discover the heads.
810 # Assume this is a head because if it isn't, the next step
810 # Assume this is a head because if it isn't, the next step
811 # will eventually remove it.
811 # will eventually remove it.
812 heads[n] = True
812 heads[n] = True
813 # But, obviously its parents aren't.
813 # But, obviously its parents aren't.
814 for p in self.parents(n):
814 for p in self.parents(n):
815 heads.pop(p, None)
815 heads.pop(p, None)
816 heads = [head for head, flag in heads.iteritems() if flag]
816 heads = [head for head, flag in heads.iteritems() if flag]
817 roots = list(roots)
817 roots = list(roots)
818 assert orderedout
818 assert orderedout
819 assert roots
819 assert roots
820 assert heads
820 assert heads
821 return (orderedout, roots, heads)
821 return (orderedout, roots, heads)
822
822
823 def headrevs(self):
823 def headrevs(self):
824 try:
824 try:
825 return self.index.headrevs()
825 return self.index.headrevs()
826 except AttributeError:
826 except AttributeError:
827 return self._headrevs()
827 return self._headrevs()
828
828
829 def computephases(self, roots):
829 def computephases(self, roots):
830 return self.index.computephasesmapsets(roots)
830 return self.index.computephasesmapsets(roots)
831
831
832 def _headrevs(self):
832 def _headrevs(self):
833 count = len(self)
833 count = len(self)
834 if not count:
834 if not count:
835 return [nullrev]
835 return [nullrev]
836 # we won't iter over filtered rev so nobody is a head at start
836 # we won't iter over filtered rev so nobody is a head at start
837 ishead = [0] * (count + 1)
837 ishead = [0] * (count + 1)
838 index = self.index
838 index = self.index
839 for r in self:
839 for r in self:
840 ishead[r] = 1 # I may be an head
840 ishead[r] = 1 # I may be an head
841 e = index[r]
841 e = index[r]
842 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
842 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
843 return [r for r, val in enumerate(ishead) if val]
843 return [r for r, val in enumerate(ishead) if val]
844
844
845 def heads(self, start=None, stop=None):
845 def heads(self, start=None, stop=None):
846 """return the list of all nodes that have no children
846 """return the list of all nodes that have no children
847
847
848 if start is specified, only heads that are descendants of
848 if start is specified, only heads that are descendants of
849 start will be returned
849 start will be returned
850 if stop is specified, it will consider all the revs from stop
850 if stop is specified, it will consider all the revs from stop
851 as if they had no children
851 as if they had no children
852 """
852 """
853 if start is None and stop is None:
853 if start is None and stop is None:
854 if not len(self):
854 if not len(self):
855 return [nullid]
855 return [nullid]
856 return [self.node(r) for r in self.headrevs()]
856 return [self.node(r) for r in self.headrevs()]
857
857
858 if start is None:
858 if start is None:
859 start = nullid
859 start = nullid
860 if stop is None:
860 if stop is None:
861 stop = []
861 stop = []
862 stoprevs = set([self.rev(n) for n in stop])
862 stoprevs = set([self.rev(n) for n in stop])
863 startrev = self.rev(start)
863 startrev = self.rev(start)
864 reachable = set((startrev,))
864 reachable = set((startrev,))
865 heads = set((startrev,))
865 heads = set((startrev,))
866
866
867 parentrevs = self.parentrevs
867 parentrevs = self.parentrevs
868 for r in self.revs(start=startrev + 1):
868 for r in self.revs(start=startrev + 1):
869 for p in parentrevs(r):
869 for p in parentrevs(r):
870 if p in reachable:
870 if p in reachable:
871 if r not in stoprevs:
871 if r not in stoprevs:
872 reachable.add(r)
872 reachable.add(r)
873 heads.add(r)
873 heads.add(r)
874 if p in heads and p not in stoprevs:
874 if p in heads and p not in stoprevs:
875 heads.remove(p)
875 heads.remove(p)
876
876
877 return [self.node(r) for r in heads]
877 return [self.node(r) for r in heads]
878
878
879 def children(self, node):
879 def children(self, node):
880 """find the children of a given node"""
880 """find the children of a given node"""
881 c = []
881 c = []
882 p = self.rev(node)
882 p = self.rev(node)
883 for r in self.revs(start=p + 1):
883 for r in self.revs(start=p + 1):
884 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
884 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
885 if prevs:
885 if prevs:
886 for pr in prevs:
886 for pr in prevs:
887 if pr == p:
887 if pr == p:
888 c.append(self.node(r))
888 c.append(self.node(r))
889 elif p == nullrev:
889 elif p == nullrev:
890 c.append(self.node(r))
890 c.append(self.node(r))
891 return c
891 return c
892
892
893 def descendant(self, start, end):
893 def descendant(self, start, end):
894 if start == nullrev:
894 if start == nullrev:
895 return True
895 return True
896 for i in self.descendants([start]):
896 for i in self.descendants([start]):
897 if i == end:
897 if i == end:
898 return True
898 return True
899 elif i > end:
899 elif i > end:
900 break
900 break
901 return False
901 return False
902
902
903 def commonancestorsheads(self, a, b):
903 def commonancestorsheads(self, a, b):
904 """calculate all the heads of the common ancestors of nodes a and b"""
904 """calculate all the heads of the common ancestors of nodes a and b"""
905 a, b = self.rev(a), self.rev(b)
905 a, b = self.rev(a), self.rev(b)
906 try:
906 try:
907 ancs = self.index.commonancestorsheads(a, b)
907 ancs = self.index.commonancestorsheads(a, b)
908 except (AttributeError, OverflowError): # C implementation failed
908 except (AttributeError, OverflowError): # C implementation failed
909 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
909 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
910 return map(self.node, ancs)
910 return map(self.node, ancs)
911
911
912 def isancestor(self, a, b):
912 def isancestor(self, a, b):
913 """return True if node a is an ancestor of node b
913 """return True if node a is an ancestor of node b
914
914
915 The implementation of this is trivial but the use of
915 The implementation of this is trivial but the use of
916 commonancestorsheads is not."""
916 commonancestorsheads is not."""
917 return a in self.commonancestorsheads(a, b)
917 return a in self.commonancestorsheads(a, b)
918
918
919 def ancestor(self, a, b):
919 def ancestor(self, a, b):
920 """calculate the "best" common ancestor of nodes a and b"""
920 """calculate the "best" common ancestor of nodes a and b"""
921
921
922 a, b = self.rev(a), self.rev(b)
922 a, b = self.rev(a), self.rev(b)
923 try:
923 try:
924 ancs = self.index.ancestors(a, b)
924 ancs = self.index.ancestors(a, b)
925 except (AttributeError, OverflowError):
925 except (AttributeError, OverflowError):
926 ancs = ancestor.ancestors(self.parentrevs, a, b)
926 ancs = ancestor.ancestors(self.parentrevs, a, b)
927 if ancs:
927 if ancs:
928 # choose a consistent winner when there's a tie
928 # choose a consistent winner when there's a tie
929 return min(map(self.node, ancs))
929 return min(map(self.node, ancs))
930 return nullid
930 return nullid
931
931
932 def _match(self, id):
932 def _match(self, id):
933 if isinstance(id, int):
933 if isinstance(id, int):
934 # rev
934 # rev
935 return self.node(id)
935 return self.node(id)
936 if len(id) == 20:
936 if len(id) == 20:
937 # possibly a binary node
937 # possibly a binary node
938 # odds of a binary node being all hex in ASCII are 1 in 10**25
938 # odds of a binary node being all hex in ASCII are 1 in 10**25
939 try:
939 try:
940 node = id
940 node = id
941 self.rev(node) # quick search the index
941 self.rev(node) # quick search the index
942 return node
942 return node
943 except LookupError:
943 except LookupError:
944 pass # may be partial hex id
944 pass # may be partial hex id
945 try:
945 try:
946 # str(rev)
946 # str(rev)
947 rev = int(id)
947 rev = int(id)
948 if str(rev) != id:
948 if str(rev) != id:
949 raise ValueError
949 raise ValueError
950 if rev < 0:
950 if rev < 0:
951 rev = len(self) + rev
951 rev = len(self) + rev
952 if rev < 0 or rev >= len(self):
952 if rev < 0 or rev >= len(self):
953 raise ValueError
953 raise ValueError
954 return self.node(rev)
954 return self.node(rev)
955 except (ValueError, OverflowError):
955 except (ValueError, OverflowError):
956 pass
956 pass
957 if len(id) == 40:
957 if len(id) == 40:
958 try:
958 try:
959 # a full hex nodeid?
959 # a full hex nodeid?
960 node = bin(id)
960 node = bin(id)
961 self.rev(node)
961 self.rev(node)
962 return node
962 return node
963 except (TypeError, LookupError):
963 except (TypeError, LookupError):
964 pass
964 pass
965
965
966 def _partialmatch(self, id):
966 def _partialmatch(self, id):
967 try:
967 try:
968 partial = self.index.partialmatch(id)
968 partial = self.index.partialmatch(id)
969 if partial and self.hasnode(partial):
969 if partial and self.hasnode(partial):
970 return partial
970 return partial
971 return None
971 return None
972 except RevlogError:
972 except RevlogError:
973 # parsers.c radix tree lookup gave multiple matches
973 # parsers.c radix tree lookup gave multiple matches
974 # fast path: for unfiltered changelog, radix tree is accurate
974 # fast path: for unfiltered changelog, radix tree is accurate
975 if not getattr(self, 'filteredrevs', None):
975 if not getattr(self, 'filteredrevs', None):
976 raise LookupError(id, self.indexfile,
976 raise LookupError(id, self.indexfile,
977 _('ambiguous identifier'))
977 _('ambiguous identifier'))
978 # fall through to slow path that filters hidden revisions
978 # fall through to slow path that filters hidden revisions
979 except (AttributeError, ValueError):
979 except (AttributeError, ValueError):
980 # we are pure python, or key was too short to search radix tree
980 # we are pure python, or key was too short to search radix tree
981 pass
981 pass
982
982
983 if id in self._pcache:
983 if id in self._pcache:
984 return self._pcache[id]
984 return self._pcache[id]
985
985
986 if len(id) < 40:
986 if len(id) < 40:
987 try:
987 try:
988 # hex(node)[:...]
988 # hex(node)[:...]
989 l = len(id) // 2 # grab an even number of digits
989 l = len(id) // 2 # grab an even number of digits
990 prefix = bin(id[:l * 2])
990 prefix = bin(id[:l * 2])
991 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
991 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
992 nl = [n for n in nl if hex(n).startswith(id) and
992 nl = [n for n in nl if hex(n).startswith(id) and
993 self.hasnode(n)]
993 self.hasnode(n)]
994 if len(nl) > 0:
994 if len(nl) > 0:
995 if len(nl) == 1:
995 if len(nl) == 1:
996 self._pcache[id] = nl[0]
996 self._pcache[id] = nl[0]
997 return nl[0]
997 return nl[0]
998 raise LookupError(id, self.indexfile,
998 raise LookupError(id, self.indexfile,
999 _('ambiguous identifier'))
999 _('ambiguous identifier'))
1000 return None
1000 return None
1001 except TypeError:
1001 except TypeError:
1002 pass
1002 pass
1003
1003
1004 def lookup(self, id):
1004 def lookup(self, id):
1005 """locate a node based on:
1005 """locate a node based on:
1006 - revision number or str(revision number)
1006 - revision number or str(revision number)
1007 - nodeid or subset of hex nodeid
1007 - nodeid or subset of hex nodeid
1008 """
1008 """
1009 n = self._match(id)
1009 n = self._match(id)
1010 if n is not None:
1010 if n is not None:
1011 return n
1011 return n
1012 n = self._partialmatch(id)
1012 n = self._partialmatch(id)
1013 if n:
1013 if n:
1014 return n
1014 return n
1015
1015
1016 raise LookupError(id, self.indexfile, _('no match found'))
1016 raise LookupError(id, self.indexfile, _('no match found'))
1017
1017
1018 def cmp(self, node, text):
1018 def cmp(self, node, text):
1019 """compare text with a given file revision
1019 """compare text with a given file revision
1020
1020
1021 returns True if text is different than what is stored.
1021 returns True if text is different than what is stored.
1022 """
1022 """
1023 p1, p2 = self.parents(node)
1023 p1, p2 = self.parents(node)
1024 return hash(text, p1, p2) != node
1024 return hash(text, p1, p2) != node
1025
1025
1026 def _addchunk(self, offset, data):
1026 def _addchunk(self, offset, data):
1027 """Add a segment to the revlog cache.
1027 """Add a segment to the revlog cache.
1028
1028
1029 Accepts an absolute offset and the data that is at that location.
1029 Accepts an absolute offset and the data that is at that location.
1030 """
1030 """
1031 o, d = self._chunkcache
1031 o, d = self._chunkcache
1032 # try to add to existing cache
1032 # try to add to existing cache
1033 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1033 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1034 self._chunkcache = o, d + data
1034 self._chunkcache = o, d + data
1035 else:
1035 else:
1036 self._chunkcache = offset, data
1036 self._chunkcache = offset, data
1037
1037
1038 def _loadchunk(self, offset, length, df=None):
1038 def _loadchunk(self, offset, length, df=None):
1039 """Load a segment of raw data from the revlog.
1039 """Load a segment of raw data from the revlog.
1040
1040
1041 Accepts an absolute offset, length to read, and an optional existing
1041 Accepts an absolute offset, length to read, and an optional existing
1042 file handle to read from.
1042 file handle to read from.
1043
1043
1044 If an existing file handle is passed, it will be seeked and the
1044 If an existing file handle is passed, it will be seeked and the
1045 original seek position will NOT be restored.
1045 original seek position will NOT be restored.
1046
1046
1047 Returns a str or buffer of raw byte data.
1047 Returns a str or buffer of raw byte data.
1048 """
1048 """
1049 if df is not None:
1049 if df is not None:
1050 closehandle = False
1050 closehandle = False
1051 else:
1051 else:
1052 if self._inline:
1052 if self._inline:
1053 df = self.opener(self.indexfile)
1053 df = self.opener(self.indexfile)
1054 else:
1054 else:
1055 df = self.opener(self.datafile)
1055 df = self.opener(self.datafile)
1056 closehandle = True
1056 closehandle = True
1057
1057
1058 # Cache data both forward and backward around the requested
1058 # Cache data both forward and backward around the requested
1059 # data, in a fixed size window. This helps speed up operations
1059 # data, in a fixed size window. This helps speed up operations
1060 # involving reading the revlog backwards.
1060 # involving reading the revlog backwards.
1061 cachesize = self._chunkcachesize
1061 cachesize = self._chunkcachesize
1062 realoffset = offset & ~(cachesize - 1)
1062 realoffset = offset & ~(cachesize - 1)
1063 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1063 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1064 - realoffset)
1064 - realoffset)
1065 df.seek(realoffset)
1065 df.seek(realoffset)
1066 d = df.read(reallength)
1066 d = df.read(reallength)
1067 if closehandle:
1067 if closehandle:
1068 df.close()
1068 df.close()
1069 self._addchunk(realoffset, d)
1069 self._addchunk(realoffset, d)
1070 if offset != realoffset or reallength != length:
1070 if offset != realoffset or reallength != length:
1071 return util.buffer(d, offset - realoffset, length)
1071 return util.buffer(d, offset - realoffset, length)
1072 return d
1072 return d
1073
1073
1074 def _getchunk(self, offset, length, df=None):
1074 def _getchunk(self, offset, length, df=None):
1075 """Obtain a segment of raw data from the revlog.
1075 """Obtain a segment of raw data from the revlog.
1076
1076
1077 Accepts an absolute offset, length of bytes to obtain, and an
1077 Accepts an absolute offset, length of bytes to obtain, and an
1078 optional file handle to the already-opened revlog. If the file
1078 optional file handle to the already-opened revlog. If the file
1079 handle is used, it's original seek position will not be preserved.
1079 handle is used, it's original seek position will not be preserved.
1080
1080
1081 Requests for data may be returned from a cache.
1081 Requests for data may be returned from a cache.
1082
1082
1083 Returns a str or a buffer instance of raw byte data.
1083 Returns a str or a buffer instance of raw byte data.
1084 """
1084 """
1085 o, d = self._chunkcache
1085 o, d = self._chunkcache
1086 l = len(d)
1086 l = len(d)
1087
1087
1088 # is it in the cache?
1088 # is it in the cache?
1089 cachestart = offset - o
1089 cachestart = offset - o
1090 cacheend = cachestart + length
1090 cacheend = cachestart + length
1091 if cachestart >= 0 and cacheend <= l:
1091 if cachestart >= 0 and cacheend <= l:
1092 if cachestart == 0 and cacheend == l:
1092 if cachestart == 0 and cacheend == l:
1093 return d # avoid a copy
1093 return d # avoid a copy
1094 return util.buffer(d, cachestart, cacheend - cachestart)
1094 return util.buffer(d, cachestart, cacheend - cachestart)
1095
1095
1096 return self._loadchunk(offset, length, df=df)
1096 return self._loadchunk(offset, length, df=df)
1097
1097
1098 def _chunkraw(self, startrev, endrev, df=None):
1098 def _chunkraw(self, startrev, endrev, df=None):
1099 """Obtain a segment of raw data corresponding to a range of revisions.
1099 """Obtain a segment of raw data corresponding to a range of revisions.
1100
1100
1101 Accepts the start and end revisions and an optional already-open
1101 Accepts the start and end revisions and an optional already-open
1102 file handle to be used for reading. If the file handle is read, its
1102 file handle to be used for reading. If the file handle is read, its
1103 seek position will not be preserved.
1103 seek position will not be preserved.
1104
1104
1105 Requests for data may be satisfied by a cache.
1105 Requests for data may be satisfied by a cache.
1106
1106
1107 Returns a 2-tuple of (offset, data) for the requested range of
1107 Returns a 2-tuple of (offset, data) for the requested range of
1108 revisions. Offset is the integer offset from the beginning of the
1108 revisions. Offset is the integer offset from the beginning of the
1109 revlog and data is a str or buffer of the raw byte data.
1109 revlog and data is a str or buffer of the raw byte data.
1110
1110
1111 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1111 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1112 to determine where each revision's data begins and ends.
1112 to determine where each revision's data begins and ends.
1113 """
1113 """
1114 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1114 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1115 # (functions are expensive).
1115 # (functions are expensive).
1116 index = self.index
1116 index = self.index
1117 istart = index[startrev]
1117 istart = index[startrev]
1118 start = int(istart[0] >> 16)
1118 start = int(istart[0] >> 16)
1119 if startrev == endrev:
1119 if startrev == endrev:
1120 end = start + istart[1]
1120 end = start + istart[1]
1121 else:
1121 else:
1122 iend = index[endrev]
1122 iend = index[endrev]
1123 end = int(iend[0] >> 16) + iend[1]
1123 end = int(iend[0] >> 16) + iend[1]
1124
1124
1125 if self._inline:
1125 if self._inline:
1126 start += (startrev + 1) * self._io.size
1126 start += (startrev + 1) * self._io.size
1127 end += (endrev + 1) * self._io.size
1127 end += (endrev + 1) * self._io.size
1128 length = end - start
1128 length = end - start
1129
1129
1130 return start, self._getchunk(start, length, df=df)
1130 return start, self._getchunk(start, length, df=df)
1131
1131
1132 def _chunk(self, rev, df=None):
1132 def _chunk(self, rev, df=None):
1133 """Obtain a single decompressed chunk for a revision.
1133 """Obtain a single decompressed chunk for a revision.
1134
1134
1135 Accepts an integer revision and an optional already-open file handle
1135 Accepts an integer revision and an optional already-open file handle
1136 to be used for reading. If used, the seek position of the file will not
1136 to be used for reading. If used, the seek position of the file will not
1137 be preserved.
1137 be preserved.
1138
1138
1139 Returns a str holding uncompressed data for the requested revision.
1139 Returns a str holding uncompressed data for the requested revision.
1140 """
1140 """
1141 return decompress(self._chunkraw(rev, rev, df=df)[1])
1141 return decompress(self._chunkraw(rev, rev, df=df)[1])
1142
1142
1143 def _chunks(self, revs, df=None):
1143 def _chunks(self, revs, df=None):
1144 """Obtain decompressed chunks for the specified revisions.
1144 """Obtain decompressed chunks for the specified revisions.
1145
1145
1146 Accepts an iterable of numeric revisions that are assumed to be in
1146 Accepts an iterable of numeric revisions that are assumed to be in
1147 ascending order. Also accepts an optional already-open file handle
1147 ascending order. Also accepts an optional already-open file handle
1148 to be used for reading. If used, the seek position of the file will
1148 to be used for reading. If used, the seek position of the file will
1149 not be preserved.
1149 not be preserved.
1150
1150
1151 This function is similar to calling ``self._chunk()`` multiple times,
1151 This function is similar to calling ``self._chunk()`` multiple times,
1152 but is faster.
1152 but is faster.
1153
1153
1154 Returns a list with decompressed data for each requested revision.
1154 Returns a list with decompressed data for each requested revision.
1155 """
1155 """
1156 if not revs:
1156 if not revs:
1157 return []
1157 return []
1158 start = self.start
1158 start = self.start
1159 length = self.length
1159 length = self.length
1160 inline = self._inline
1160 inline = self._inline
1161 iosize = self._io.size
1161 iosize = self._io.size
1162 buffer = util.buffer
1162 buffer = util.buffer
1163
1163
1164 l = []
1164 l = []
1165 ladd = l.append
1165 ladd = l.append
1166
1166
1167 try:
1167 try:
1168 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1168 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1169 except OverflowError:
1169 except OverflowError:
1170 # issue4215 - we can't cache a run of chunks greater than
1170 # issue4215 - we can't cache a run of chunks greater than
1171 # 2G on Windows
1171 # 2G on Windows
1172 return [self._chunk(rev, df=df) for rev in revs]
1172 return [self._chunk(rev, df=df) for rev in revs]
1173
1173
1174 for rev in revs:
1174 for rev in revs:
1175 chunkstart = start(rev)
1175 chunkstart = start(rev)
1176 if inline:
1176 if inline:
1177 chunkstart += (rev + 1) * iosize
1177 chunkstart += (rev + 1) * iosize
1178 chunklength = length(rev)
1178 chunklength = length(rev)
1179 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1179 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1180
1180
1181 return l
1181 return l
1182
1182
1183 def _chunkclear(self):
1183 def _chunkclear(self):
1184 """Clear the raw chunk cache."""
1184 """Clear the raw chunk cache."""
1185 self._chunkcache = (0, '')
1185 self._chunkcache = (0, '')
1186
1186
1187 def deltaparent(self, rev):
1187 def deltaparent(self, rev):
1188 """return deltaparent of the given revision"""
1188 """return deltaparent of the given revision"""
1189 base = self.index[rev][3]
1189 base = self.index[rev][3]
1190 if base == rev:
1190 if base == rev:
1191 return nullrev
1191 return nullrev
1192 elif self._generaldelta:
1192 elif self._generaldelta:
1193 return base
1193 return base
1194 else:
1194 else:
1195 return rev - 1
1195 return rev - 1
1196
1196
1197 def revdiff(self, rev1, rev2):
1197 def revdiff(self, rev1, rev2):
1198 """return or calculate a delta between two revisions"""
1198 """return or calculate a delta between two revisions"""
1199 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1199 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1200 return str(self._chunk(rev2))
1200 return str(self._chunk(rev2))
1201
1201
1202 return mdiff.textdiff(self.revision(rev1),
1202 return mdiff.textdiff(self.revision(rev1),
1203 self.revision(rev2))
1203 self.revision(rev2))
1204
1204
1205 def revision(self, nodeorrev, _df=None):
1205 def revision(self, nodeorrev, _df=None):
1206 """return an uncompressed revision of a given node or revision
1206 """return an uncompressed revision of a given node or revision
1207 number.
1207 number.
1208
1208
1209 _df is an existing file handle to read from. It is meant to only be
1209 _df is an existing file handle to read from. It is meant to only be
1210 used internally.
1210 used internally.
1211 """
1211 """
1212 if isinstance(nodeorrev, int):
1212 if isinstance(nodeorrev, int):
1213 rev = nodeorrev
1213 rev = nodeorrev
1214 node = self.node(rev)
1214 node = self.node(rev)
1215 else:
1215 else:
1216 node = nodeorrev
1216 node = nodeorrev
1217 rev = None
1217 rev = None
1218
1218
1219 cachedrev = None
1219 cachedrev = None
1220 if node == nullid:
1220 if node == nullid:
1221 return ""
1221 return ""
1222 if self._cache:
1222 if self._cache:
1223 if self._cache[0] == node:
1223 if self._cache[0] == node:
1224 return self._cache[2]
1224 return self._cache[2]
1225 cachedrev = self._cache[1]
1225 cachedrev = self._cache[1]
1226
1226
1227 # look up what we need to read
1227 # look up what we need to read
1228 text = None
1228 text = None
1229 if rev is None:
1229 if rev is None:
1230 rev = self.rev(node)
1230 rev = self.rev(node)
1231
1231
1232 # check rev flags
1232 # check rev flags
1233 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1233 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1234 raise RevlogError(_('incompatible revision flag %x') %
1234 raise RevlogError(_('incompatible revision flag %x') %
1235 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1235 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1236
1236
1237 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1237 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1238 if stopped:
1238 if stopped:
1239 text = self._cache[2]
1239 text = self._cache[2]
1240
1240
1241 # drop cache to save memory
1241 # drop cache to save memory
1242 self._cache = None
1242 self._cache = None
1243
1243
1244 bins = self._chunks(chain, df=_df)
1244 bins = self._chunks(chain, df=_df)
1245 if text is None:
1245 if text is None:
1246 text = str(bins[0])
1246 text = str(bins[0])
1247 bins = bins[1:]
1247 bins = bins[1:]
1248
1248
1249 text = mdiff.patches(text, bins)
1249 text = mdiff.patches(text, bins)
1250
1250 self.checkhash(text, node, rev=rev)
1251 text = self._checkhash(text, node, rev)
1252
1253 self._cache = (node, rev, text)
1251 self._cache = (node, rev, text)
1254 return text
1252 return text
1255
1253
1256 def hash(self, text, p1, p2):
1254 def hash(self, text, p1, p2):
1257 """Compute a node hash.
1255 """Compute a node hash.
1258
1256
1259 Available as a function so that subclasses can replace the hash
1257 Available as a function so that subclasses can replace the hash
1260 as needed.
1258 as needed.
1261 """
1259 """
1262 return hash(text, p1, p2)
1260 return hash(text, p1, p2)
1263
1261
1264 def _checkhash(self, text, node, rev):
1262 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1265 p1, p2 = self.parents(node)
1263 """Check node hash integrity.
1266 self.checkhash(text, p1, p2, node, rev)
1267 return text
1268
1264
1269 def checkhash(self, text, p1, p2, node, rev=None):
1265 Available as a function so that subclasses can extend hash mismatch
1266 behaviors as needed.
1267 """
1268 if p1 is None and p2 is None:
1269 p1, p2 = self.parents(node)
1270 if node != self.hash(text, p1, p2):
1270 if node != self.hash(text, p1, p2):
1271 revornode = rev
1271 revornode = rev
1272 if revornode is None:
1272 if revornode is None:
1273 revornode = templatefilters.short(hex(node))
1273 revornode = templatefilters.short(hex(node))
1274 raise RevlogError(_("integrity check failed on %s:%s")
1274 raise RevlogError(_("integrity check failed on %s:%s")
1275 % (self.indexfile, revornode))
1275 % (self.indexfile, revornode))
1276
1276
1277 def checkinlinesize(self, tr, fp=None):
1277 def checkinlinesize(self, tr, fp=None):
1278 """Check if the revlog is too big for inline and convert if so.
1278 """Check if the revlog is too big for inline and convert if so.
1279
1279
1280 This should be called after revisions are added to the revlog. If the
1280 This should be called after revisions are added to the revlog. If the
1281 revlog has grown too large to be an inline revlog, it will convert it
1281 revlog has grown too large to be an inline revlog, it will convert it
1282 to use multiple index and data files.
1282 to use multiple index and data files.
1283 """
1283 """
1284 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1284 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1285 return
1285 return
1286
1286
1287 trinfo = tr.find(self.indexfile)
1287 trinfo = tr.find(self.indexfile)
1288 if trinfo is None:
1288 if trinfo is None:
1289 raise RevlogError(_("%s not found in the transaction")
1289 raise RevlogError(_("%s not found in the transaction")
1290 % self.indexfile)
1290 % self.indexfile)
1291
1291
1292 trindex = trinfo[2]
1292 trindex = trinfo[2]
1293 if trindex is not None:
1293 if trindex is not None:
1294 dataoff = self.start(trindex)
1294 dataoff = self.start(trindex)
1295 else:
1295 else:
1296 # revlog was stripped at start of transaction, use all leftover data
1296 # revlog was stripped at start of transaction, use all leftover data
1297 trindex = len(self) - 1
1297 trindex = len(self) - 1
1298 dataoff = self.end(-2)
1298 dataoff = self.end(-2)
1299
1299
1300 tr.add(self.datafile, dataoff)
1300 tr.add(self.datafile, dataoff)
1301
1301
1302 if fp:
1302 if fp:
1303 fp.flush()
1303 fp.flush()
1304 fp.close()
1304 fp.close()
1305
1305
1306 df = self.opener(self.datafile, 'w')
1306 df = self.opener(self.datafile, 'w')
1307 try:
1307 try:
1308 for r in self:
1308 for r in self:
1309 df.write(self._chunkraw(r, r)[1])
1309 df.write(self._chunkraw(r, r)[1])
1310 finally:
1310 finally:
1311 df.close()
1311 df.close()
1312
1312
1313 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1313 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1314 checkambig=self._checkambig)
1314 checkambig=self._checkambig)
1315 self.version &= ~(REVLOGNGINLINEDATA)
1315 self.version &= ~(REVLOGNGINLINEDATA)
1316 self._inline = False
1316 self._inline = False
1317 for i in self:
1317 for i in self:
1318 e = self._io.packentry(self.index[i], self.node, self.version, i)
1318 e = self._io.packentry(self.index[i], self.node, self.version, i)
1319 fp.write(e)
1319 fp.write(e)
1320
1320
1321 # if we don't call close, the temp file will never replace the
1321 # if we don't call close, the temp file will never replace the
1322 # real index
1322 # real index
1323 fp.close()
1323 fp.close()
1324
1324
1325 tr.replace(self.indexfile, trindex * self._io.size)
1325 tr.replace(self.indexfile, trindex * self._io.size)
1326 self._chunkclear()
1326 self._chunkclear()
1327
1327
1328 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1328 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1329 node=None):
1329 node=None):
1330 """add a revision to the log
1330 """add a revision to the log
1331
1331
1332 text - the revision data to add
1332 text - the revision data to add
1333 transaction - the transaction object used for rollback
1333 transaction - the transaction object used for rollback
1334 link - the linkrev data to add
1334 link - the linkrev data to add
1335 p1, p2 - the parent nodeids of the revision
1335 p1, p2 - the parent nodeids of the revision
1336 cachedelta - an optional precomputed delta
1336 cachedelta - an optional precomputed delta
1337 node - nodeid of revision; typically node is not specified, and it is
1337 node - nodeid of revision; typically node is not specified, and it is
1338 computed by default as hash(text, p1, p2), however subclasses might
1338 computed by default as hash(text, p1, p2), however subclasses might
1339 use different hashing method (and override checkhash() in such case)
1339 use different hashing method (and override checkhash() in such case)
1340 """
1340 """
1341 if link == nullrev:
1341 if link == nullrev:
1342 raise RevlogError(_("attempted to add linkrev -1 to %s")
1342 raise RevlogError(_("attempted to add linkrev -1 to %s")
1343 % self.indexfile)
1343 % self.indexfile)
1344
1344
1345 if len(text) > _maxentrysize:
1345 if len(text) > _maxentrysize:
1346 raise RevlogError(
1346 raise RevlogError(
1347 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1347 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1348 % (self.indexfile, len(text)))
1348 % (self.indexfile, len(text)))
1349
1349
1350 node = node or self.hash(text, p1, p2)
1350 node = node or self.hash(text, p1, p2)
1351 if node in self.nodemap:
1351 if node in self.nodemap:
1352 return node
1352 return node
1353
1353
1354 dfh = None
1354 dfh = None
1355 if not self._inline:
1355 if not self._inline:
1356 dfh = self.opener(self.datafile, "a+")
1356 dfh = self.opener(self.datafile, "a+")
1357 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1357 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1358 try:
1358 try:
1359 return self._addrevision(node, text, transaction, link, p1, p2,
1359 return self._addrevision(node, text, transaction, link, p1, p2,
1360 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1360 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1361 finally:
1361 finally:
1362 if dfh:
1362 if dfh:
1363 dfh.close()
1363 dfh.close()
1364 ifh.close()
1364 ifh.close()
1365
1365
1366 def compress(self, text):
1366 def compress(self, text):
1367 """ generate a possibly-compressed representation of text """
1367 """ generate a possibly-compressed representation of text """
1368 if not text:
1368 if not text:
1369 return ("", text)
1369 return ("", text)
1370 l = len(text)
1370 l = len(text)
1371 bin = None
1371 bin = None
1372 if l < 44:
1372 if l < 44:
1373 pass
1373 pass
1374 elif l > 1000000:
1374 elif l > 1000000:
1375 # zlib makes an internal copy, thus doubling memory usage for
1375 # zlib makes an internal copy, thus doubling memory usage for
1376 # large files, so lets do this in pieces
1376 # large files, so lets do this in pieces
1377 z = zlib.compressobj()
1377 z = zlib.compressobj()
1378 p = []
1378 p = []
1379 pos = 0
1379 pos = 0
1380 while pos < l:
1380 while pos < l:
1381 pos2 = pos + 2**20
1381 pos2 = pos + 2**20
1382 p.append(z.compress(text[pos:pos2]))
1382 p.append(z.compress(text[pos:pos2]))
1383 pos = pos2
1383 pos = pos2
1384 p.append(z.flush())
1384 p.append(z.flush())
1385 if sum(map(len, p)) < l:
1385 if sum(map(len, p)) < l:
1386 bin = "".join(p)
1386 bin = "".join(p)
1387 else:
1387 else:
1388 bin = _compress(text)
1388 bin = _compress(text)
1389 if bin is None or len(bin) > l:
1389 if bin is None or len(bin) > l:
1390 if text[0] == '\0':
1390 if text[0] == '\0':
1391 return ("", text)
1391 return ("", text)
1392 return ('u', text)
1392 return ('u', text)
1393 return ("", bin)
1393 return ("", bin)
1394
1394
1395 def _isgooddelta(self, d, textlen):
1395 def _isgooddelta(self, d, textlen):
1396 """Returns True if the given delta is good. Good means that it is within
1396 """Returns True if the given delta is good. Good means that it is within
1397 the disk span, disk size, and chain length bounds that we know to be
1397 the disk span, disk size, and chain length bounds that we know to be
1398 performant."""
1398 performant."""
1399 if d is None:
1399 if d is None:
1400 return False
1400 return False
1401
1401
1402 # - 'dist' is the distance from the base revision -- bounding it limits
1402 # - 'dist' is the distance from the base revision -- bounding it limits
1403 # the amount of I/O we need to do.
1403 # the amount of I/O we need to do.
1404 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1404 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1405 # to apply -- bounding it limits the amount of CPU we consume.
1405 # to apply -- bounding it limits the amount of CPU we consume.
1406 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1406 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1407 if (dist > textlen * 4 or l > textlen or
1407 if (dist > textlen * 4 or l > textlen or
1408 compresseddeltalen > textlen * 2 or
1408 compresseddeltalen > textlen * 2 or
1409 (self._maxchainlen and chainlen > self._maxchainlen)):
1409 (self._maxchainlen and chainlen > self._maxchainlen)):
1410 return False
1410 return False
1411
1411
1412 return True
1412 return True
1413
1413
1414 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1414 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1415 cachedelta, ifh, dfh, alwayscache=False):
1415 cachedelta, ifh, dfh, alwayscache=False):
1416 """internal function to add revisions to the log
1416 """internal function to add revisions to the log
1417
1417
1418 see addrevision for argument descriptions.
1418 see addrevision for argument descriptions.
1419 invariants:
1419 invariants:
1420 - text is optional (can be None); if not set, cachedelta must be set.
1420 - text is optional (can be None); if not set, cachedelta must be set.
1421 if both are set, they must correspond to each other.
1421 if both are set, they must correspond to each other.
1422 """
1422 """
1423 btext = [text]
1423 btext = [text]
1424 def buildtext():
1424 def buildtext():
1425 if btext[0] is not None:
1425 if btext[0] is not None:
1426 return btext[0]
1426 return btext[0]
1427 baserev = cachedelta[0]
1427 baserev = cachedelta[0]
1428 delta = cachedelta[1]
1428 delta = cachedelta[1]
1429 # special case deltas which replace entire base; no need to decode
1429 # special case deltas which replace entire base; no need to decode
1430 # base revision. this neatly avoids censored bases, which throw when
1430 # base revision. this neatly avoids censored bases, which throw when
1431 # they're decoded.
1431 # they're decoded.
1432 hlen = struct.calcsize(">lll")
1432 hlen = struct.calcsize(">lll")
1433 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1433 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1434 len(delta) - hlen):
1434 len(delta) - hlen):
1435 btext[0] = delta[hlen:]
1435 btext[0] = delta[hlen:]
1436 else:
1436 else:
1437 if self._inline:
1437 if self._inline:
1438 fh = ifh
1438 fh = ifh
1439 else:
1439 else:
1440 fh = dfh
1440 fh = dfh
1441 basetext = self.revision(self.node(baserev), _df=fh)
1441 basetext = self.revision(self.node(baserev), _df=fh)
1442 btext[0] = mdiff.patch(basetext, delta)
1442 btext[0] = mdiff.patch(basetext, delta)
1443 try:
1443 try:
1444 self.checkhash(btext[0], p1, p2, node)
1444 self.checkhash(btext[0], node, p1=p1, p2=p2)
1445 if flags & REVIDX_ISCENSORED:
1445 if flags & REVIDX_ISCENSORED:
1446 raise RevlogError(_('node %s is not censored') % node)
1446 raise RevlogError(_('node %s is not censored') % node)
1447 except CensoredNodeError:
1447 except CensoredNodeError:
1448 # must pass the censored index flag to add censored revisions
1448 # must pass the censored index flag to add censored revisions
1449 if not flags & REVIDX_ISCENSORED:
1449 if not flags & REVIDX_ISCENSORED:
1450 raise
1450 raise
1451 return btext[0]
1451 return btext[0]
1452
1452
1453 def builddelta(rev):
1453 def builddelta(rev):
1454 # can we use the cached delta?
1454 # can we use the cached delta?
1455 if cachedelta and cachedelta[0] == rev:
1455 if cachedelta and cachedelta[0] == rev:
1456 delta = cachedelta[1]
1456 delta = cachedelta[1]
1457 else:
1457 else:
1458 t = buildtext()
1458 t = buildtext()
1459 if self.iscensored(rev):
1459 if self.iscensored(rev):
1460 # deltas based on a censored revision must replace the
1460 # deltas based on a censored revision must replace the
1461 # full content in one patch, so delta works everywhere
1461 # full content in one patch, so delta works everywhere
1462 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1462 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1463 delta = header + t
1463 delta = header + t
1464 else:
1464 else:
1465 if self._inline:
1465 if self._inline:
1466 fh = ifh
1466 fh = ifh
1467 else:
1467 else:
1468 fh = dfh
1468 fh = dfh
1469 ptext = self.revision(self.node(rev), _df=fh)
1469 ptext = self.revision(self.node(rev), _df=fh)
1470 delta = mdiff.textdiff(ptext, t)
1470 delta = mdiff.textdiff(ptext, t)
1471 header, data = self.compress(delta)
1471 header, data = self.compress(delta)
1472 deltalen = len(header) + len(data)
1472 deltalen = len(header) + len(data)
1473 chainbase = self.chainbase(rev)
1473 chainbase = self.chainbase(rev)
1474 dist = deltalen + offset - self.start(chainbase)
1474 dist = deltalen + offset - self.start(chainbase)
1475 if self._generaldelta:
1475 if self._generaldelta:
1476 base = rev
1476 base = rev
1477 else:
1477 else:
1478 base = chainbase
1478 base = chainbase
1479 chainlen, compresseddeltalen = self._chaininfo(rev)
1479 chainlen, compresseddeltalen = self._chaininfo(rev)
1480 chainlen += 1
1480 chainlen += 1
1481 compresseddeltalen += deltalen
1481 compresseddeltalen += deltalen
1482 return (dist, deltalen, (header, data), base,
1482 return (dist, deltalen, (header, data), base,
1483 chainbase, chainlen, compresseddeltalen)
1483 chainbase, chainlen, compresseddeltalen)
1484
1484
1485 curr = len(self)
1485 curr = len(self)
1486 prev = curr - 1
1486 prev = curr - 1
1487 offset = self.end(prev)
1487 offset = self.end(prev)
1488 delta = None
1488 delta = None
1489 p1r, p2r = self.rev(p1), self.rev(p2)
1489 p1r, p2r = self.rev(p1), self.rev(p2)
1490
1490
1491 # full versions are inserted when the needed deltas
1491 # full versions are inserted when the needed deltas
1492 # become comparable to the uncompressed text
1492 # become comparable to the uncompressed text
1493 if text is None:
1493 if text is None:
1494 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1494 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1495 cachedelta[1])
1495 cachedelta[1])
1496 else:
1496 else:
1497 textlen = len(text)
1497 textlen = len(text)
1498
1498
1499 # should we try to build a delta?
1499 # should we try to build a delta?
1500 if prev != nullrev and self.storedeltachains:
1500 if prev != nullrev and self.storedeltachains:
1501 tested = set()
1501 tested = set()
1502 # This condition is true most of the time when processing
1502 # This condition is true most of the time when processing
1503 # changegroup data into a generaldelta repo. The only time it
1503 # changegroup data into a generaldelta repo. The only time it
1504 # isn't true is if this is the first revision in a delta chain
1504 # isn't true is if this is the first revision in a delta chain
1505 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1505 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1506 if cachedelta and self._generaldelta and self._lazydeltabase:
1506 if cachedelta and self._generaldelta and self._lazydeltabase:
1507 # Assume what we received from the server is a good choice
1507 # Assume what we received from the server is a good choice
1508 # build delta will reuse the cache
1508 # build delta will reuse the cache
1509 candidatedelta = builddelta(cachedelta[0])
1509 candidatedelta = builddelta(cachedelta[0])
1510 tested.add(cachedelta[0])
1510 tested.add(cachedelta[0])
1511 if self._isgooddelta(candidatedelta, textlen):
1511 if self._isgooddelta(candidatedelta, textlen):
1512 delta = candidatedelta
1512 delta = candidatedelta
1513 if delta is None and self._generaldelta:
1513 if delta is None and self._generaldelta:
1514 # exclude already lazy tested base if any
1514 # exclude already lazy tested base if any
1515 parents = [p for p in (p1r, p2r)
1515 parents = [p for p in (p1r, p2r)
1516 if p != nullrev and p not in tested]
1516 if p != nullrev and p not in tested]
1517 if parents and not self._aggressivemergedeltas:
1517 if parents and not self._aggressivemergedeltas:
1518 # Pick whichever parent is closer to us (to minimize the
1518 # Pick whichever parent is closer to us (to minimize the
1519 # chance of having to build a fulltext).
1519 # chance of having to build a fulltext).
1520 parents = [max(parents)]
1520 parents = [max(parents)]
1521 tested.update(parents)
1521 tested.update(parents)
1522 pdeltas = []
1522 pdeltas = []
1523 for p in parents:
1523 for p in parents:
1524 pd = builddelta(p)
1524 pd = builddelta(p)
1525 if self._isgooddelta(pd, textlen):
1525 if self._isgooddelta(pd, textlen):
1526 pdeltas.append(pd)
1526 pdeltas.append(pd)
1527 if pdeltas:
1527 if pdeltas:
1528 delta = min(pdeltas, key=lambda x: x[1])
1528 delta = min(pdeltas, key=lambda x: x[1])
1529 if delta is None and prev not in tested:
1529 if delta is None and prev not in tested:
1530 # other approach failed try against prev to hopefully save us a
1530 # other approach failed try against prev to hopefully save us a
1531 # fulltext.
1531 # fulltext.
1532 candidatedelta = builddelta(prev)
1532 candidatedelta = builddelta(prev)
1533 if self._isgooddelta(candidatedelta, textlen):
1533 if self._isgooddelta(candidatedelta, textlen):
1534 delta = candidatedelta
1534 delta = candidatedelta
1535 if delta is not None:
1535 if delta is not None:
1536 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1536 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1537 else:
1537 else:
1538 text = buildtext()
1538 text = buildtext()
1539 data = self.compress(text)
1539 data = self.compress(text)
1540 l = len(data[1]) + len(data[0])
1540 l = len(data[1]) + len(data[0])
1541 base = chainbase = curr
1541 base = chainbase = curr
1542
1542
1543 e = (offset_type(offset, flags), l, textlen,
1543 e = (offset_type(offset, flags), l, textlen,
1544 base, link, p1r, p2r, node)
1544 base, link, p1r, p2r, node)
1545 self.index.insert(-1, e)
1545 self.index.insert(-1, e)
1546 self.nodemap[node] = curr
1546 self.nodemap[node] = curr
1547
1547
1548 entry = self._io.packentry(e, self.node, self.version, curr)
1548 entry = self._io.packentry(e, self.node, self.version, curr)
1549 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1549 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1550
1550
1551 if alwayscache and text is None:
1551 if alwayscache and text is None:
1552 text = buildtext()
1552 text = buildtext()
1553
1553
1554 if type(text) == str: # only accept immutable objects
1554 if type(text) == str: # only accept immutable objects
1555 self._cache = (node, curr, text)
1555 self._cache = (node, curr, text)
1556 self._chainbasecache[curr] = chainbase
1556 self._chainbasecache[curr] = chainbase
1557 return node
1557 return node
1558
1558
1559 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1559 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1560 # Files opened in a+ mode have inconsistent behavior on various
1560 # Files opened in a+ mode have inconsistent behavior on various
1561 # platforms. Windows requires that a file positioning call be made
1561 # platforms. Windows requires that a file positioning call be made
1562 # when the file handle transitions between reads and writes. See
1562 # when the file handle transitions between reads and writes. See
1563 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1563 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1564 # platforms, Python or the platform itself can be buggy. Some versions
1564 # platforms, Python or the platform itself can be buggy. Some versions
1565 # of Solaris have been observed to not append at the end of the file
1565 # of Solaris have been observed to not append at the end of the file
1566 # if the file was seeked to before the end. See issue4943 for more.
1566 # if the file was seeked to before the end. See issue4943 for more.
1567 #
1567 #
1568 # We work around this issue by inserting a seek() before writing.
1568 # We work around this issue by inserting a seek() before writing.
1569 # Note: This is likely not necessary on Python 3.
1569 # Note: This is likely not necessary on Python 3.
1570 ifh.seek(0, os.SEEK_END)
1570 ifh.seek(0, os.SEEK_END)
1571 if dfh:
1571 if dfh:
1572 dfh.seek(0, os.SEEK_END)
1572 dfh.seek(0, os.SEEK_END)
1573
1573
1574 curr = len(self) - 1
1574 curr = len(self) - 1
1575 if not self._inline:
1575 if not self._inline:
1576 transaction.add(self.datafile, offset)
1576 transaction.add(self.datafile, offset)
1577 transaction.add(self.indexfile, curr * len(entry))
1577 transaction.add(self.indexfile, curr * len(entry))
1578 if data[0]:
1578 if data[0]:
1579 dfh.write(data[0])
1579 dfh.write(data[0])
1580 dfh.write(data[1])
1580 dfh.write(data[1])
1581 ifh.write(entry)
1581 ifh.write(entry)
1582 else:
1582 else:
1583 offset += curr * self._io.size
1583 offset += curr * self._io.size
1584 transaction.add(self.indexfile, offset, curr)
1584 transaction.add(self.indexfile, offset, curr)
1585 ifh.write(entry)
1585 ifh.write(entry)
1586 ifh.write(data[0])
1586 ifh.write(data[0])
1587 ifh.write(data[1])
1587 ifh.write(data[1])
1588 self.checkinlinesize(transaction, ifh)
1588 self.checkinlinesize(transaction, ifh)
1589
1589
1590 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1590 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1591 """
1591 """
1592 add a delta group
1592 add a delta group
1593
1593
1594 given a set of deltas, add them to the revision log. the
1594 given a set of deltas, add them to the revision log. the
1595 first delta is against its parent, which should be in our
1595 first delta is against its parent, which should be in our
1596 log, the rest are against the previous delta.
1596 log, the rest are against the previous delta.
1597
1597
1598 If ``addrevisioncb`` is defined, it will be called with arguments of
1598 If ``addrevisioncb`` is defined, it will be called with arguments of
1599 this revlog and the node that was added.
1599 this revlog and the node that was added.
1600 """
1600 """
1601
1601
1602 # track the base of the current delta log
1602 # track the base of the current delta log
1603 content = []
1603 content = []
1604 node = None
1604 node = None
1605
1605
1606 r = len(self)
1606 r = len(self)
1607 end = 0
1607 end = 0
1608 if r:
1608 if r:
1609 end = self.end(r - 1)
1609 end = self.end(r - 1)
1610 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1610 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1611 isize = r * self._io.size
1611 isize = r * self._io.size
1612 if self._inline:
1612 if self._inline:
1613 transaction.add(self.indexfile, end + isize, r)
1613 transaction.add(self.indexfile, end + isize, r)
1614 dfh = None
1614 dfh = None
1615 else:
1615 else:
1616 transaction.add(self.indexfile, isize, r)
1616 transaction.add(self.indexfile, isize, r)
1617 transaction.add(self.datafile, end)
1617 transaction.add(self.datafile, end)
1618 dfh = self.opener(self.datafile, "a+")
1618 dfh = self.opener(self.datafile, "a+")
1619 def flush():
1619 def flush():
1620 if dfh:
1620 if dfh:
1621 dfh.flush()
1621 dfh.flush()
1622 ifh.flush()
1622 ifh.flush()
1623 try:
1623 try:
1624 # loop through our set of deltas
1624 # loop through our set of deltas
1625 chain = None
1625 chain = None
1626 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1626 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1627 node = chunkdata['node']
1627 node = chunkdata['node']
1628 p1 = chunkdata['p1']
1628 p1 = chunkdata['p1']
1629 p2 = chunkdata['p2']
1629 p2 = chunkdata['p2']
1630 cs = chunkdata['cs']
1630 cs = chunkdata['cs']
1631 deltabase = chunkdata['deltabase']
1631 deltabase = chunkdata['deltabase']
1632 delta = chunkdata['delta']
1632 delta = chunkdata['delta']
1633 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1633 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1634
1634
1635 content.append(node)
1635 content.append(node)
1636
1636
1637 link = linkmapper(cs)
1637 link = linkmapper(cs)
1638 if node in self.nodemap:
1638 if node in self.nodemap:
1639 # this can happen if two branches make the same change
1639 # this can happen if two branches make the same change
1640 chain = node
1640 chain = node
1641 continue
1641 continue
1642
1642
1643 for p in (p1, p2):
1643 for p in (p1, p2):
1644 if p not in self.nodemap:
1644 if p not in self.nodemap:
1645 raise LookupError(p, self.indexfile,
1645 raise LookupError(p, self.indexfile,
1646 _('unknown parent'))
1646 _('unknown parent'))
1647
1647
1648 if deltabase not in self.nodemap:
1648 if deltabase not in self.nodemap:
1649 raise LookupError(deltabase, self.indexfile,
1649 raise LookupError(deltabase, self.indexfile,
1650 _('unknown delta base'))
1650 _('unknown delta base'))
1651
1651
1652 baserev = self.rev(deltabase)
1652 baserev = self.rev(deltabase)
1653
1653
1654 if baserev != nullrev and self.iscensored(baserev):
1654 if baserev != nullrev and self.iscensored(baserev):
1655 # if base is censored, delta must be full replacement in a
1655 # if base is censored, delta must be full replacement in a
1656 # single patch operation
1656 # single patch operation
1657 hlen = struct.calcsize(">lll")
1657 hlen = struct.calcsize(">lll")
1658 oldlen = self.rawsize(baserev)
1658 oldlen = self.rawsize(baserev)
1659 newlen = len(delta) - hlen
1659 newlen = len(delta) - hlen
1660 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1660 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1661 raise error.CensoredBaseError(self.indexfile,
1661 raise error.CensoredBaseError(self.indexfile,
1662 self.node(baserev))
1662 self.node(baserev))
1663
1663
1664 if not flags and self._peek_iscensored(baserev, delta, flush):
1664 if not flags and self._peek_iscensored(baserev, delta, flush):
1665 flags |= REVIDX_ISCENSORED
1665 flags |= REVIDX_ISCENSORED
1666
1666
1667 # We assume consumers of addrevisioncb will want to retrieve
1667 # We assume consumers of addrevisioncb will want to retrieve
1668 # the added revision, which will require a call to
1668 # the added revision, which will require a call to
1669 # revision(). revision() will fast path if there is a cache
1669 # revision(). revision() will fast path if there is a cache
1670 # hit. So, we tell _addrevision() to always cache in this case.
1670 # hit. So, we tell _addrevision() to always cache in this case.
1671 chain = self._addrevision(node, None, transaction, link,
1671 chain = self._addrevision(node, None, transaction, link,
1672 p1, p2, flags, (baserev, delta),
1672 p1, p2, flags, (baserev, delta),
1673 ifh, dfh,
1673 ifh, dfh,
1674 alwayscache=bool(addrevisioncb))
1674 alwayscache=bool(addrevisioncb))
1675
1675
1676 if addrevisioncb:
1676 if addrevisioncb:
1677 addrevisioncb(self, chain)
1677 addrevisioncb(self, chain)
1678
1678
1679 if not dfh and not self._inline:
1679 if not dfh and not self._inline:
1680 # addrevision switched from inline to conventional
1680 # addrevision switched from inline to conventional
1681 # reopen the index
1681 # reopen the index
1682 ifh.close()
1682 ifh.close()
1683 dfh = self.opener(self.datafile, "a+")
1683 dfh = self.opener(self.datafile, "a+")
1684 ifh = self.opener(self.indexfile, "a+",
1684 ifh = self.opener(self.indexfile, "a+",
1685 checkambig=self._checkambig)
1685 checkambig=self._checkambig)
1686 finally:
1686 finally:
1687 if dfh:
1687 if dfh:
1688 dfh.close()
1688 dfh.close()
1689 ifh.close()
1689 ifh.close()
1690
1690
1691 return content
1691 return content
1692
1692
1693 def iscensored(self, rev):
1693 def iscensored(self, rev):
1694 """Check if a file revision is censored."""
1694 """Check if a file revision is censored."""
1695 return False
1695 return False
1696
1696
1697 def _peek_iscensored(self, baserev, delta, flush):
1697 def _peek_iscensored(self, baserev, delta, flush):
1698 """Quickly check if a delta produces a censored revision."""
1698 """Quickly check if a delta produces a censored revision."""
1699 return False
1699 return False
1700
1700
1701 def getstrippoint(self, minlink):
1701 def getstrippoint(self, minlink):
1702 """find the minimum rev that must be stripped to strip the linkrev
1702 """find the minimum rev that must be stripped to strip the linkrev
1703
1703
1704 Returns a tuple containing the minimum rev and a set of all revs that
1704 Returns a tuple containing the minimum rev and a set of all revs that
1705 have linkrevs that will be broken by this strip.
1705 have linkrevs that will be broken by this strip.
1706 """
1706 """
1707 brokenrevs = set()
1707 brokenrevs = set()
1708 strippoint = len(self)
1708 strippoint = len(self)
1709
1709
1710 heads = {}
1710 heads = {}
1711 futurelargelinkrevs = set()
1711 futurelargelinkrevs = set()
1712 for head in self.headrevs():
1712 for head in self.headrevs():
1713 headlinkrev = self.linkrev(head)
1713 headlinkrev = self.linkrev(head)
1714 heads[head] = headlinkrev
1714 heads[head] = headlinkrev
1715 if headlinkrev >= minlink:
1715 if headlinkrev >= minlink:
1716 futurelargelinkrevs.add(headlinkrev)
1716 futurelargelinkrevs.add(headlinkrev)
1717
1717
1718 # This algorithm involves walking down the rev graph, starting at the
1718 # This algorithm involves walking down the rev graph, starting at the
1719 # heads. Since the revs are topologically sorted according to linkrev,
1719 # heads. Since the revs are topologically sorted according to linkrev,
1720 # once all head linkrevs are below the minlink, we know there are
1720 # once all head linkrevs are below the minlink, we know there are
1721 # no more revs that could have a linkrev greater than minlink.
1721 # no more revs that could have a linkrev greater than minlink.
1722 # So we can stop walking.
1722 # So we can stop walking.
1723 while futurelargelinkrevs:
1723 while futurelargelinkrevs:
1724 strippoint -= 1
1724 strippoint -= 1
1725 linkrev = heads.pop(strippoint)
1725 linkrev = heads.pop(strippoint)
1726
1726
1727 if linkrev < minlink:
1727 if linkrev < minlink:
1728 brokenrevs.add(strippoint)
1728 brokenrevs.add(strippoint)
1729 else:
1729 else:
1730 futurelargelinkrevs.remove(linkrev)
1730 futurelargelinkrevs.remove(linkrev)
1731
1731
1732 for p in self.parentrevs(strippoint):
1732 for p in self.parentrevs(strippoint):
1733 if p != nullrev:
1733 if p != nullrev:
1734 plinkrev = self.linkrev(p)
1734 plinkrev = self.linkrev(p)
1735 heads[p] = plinkrev
1735 heads[p] = plinkrev
1736 if plinkrev >= minlink:
1736 if plinkrev >= minlink:
1737 futurelargelinkrevs.add(plinkrev)
1737 futurelargelinkrevs.add(plinkrev)
1738
1738
1739 return strippoint, brokenrevs
1739 return strippoint, brokenrevs
1740
1740
1741 def strip(self, minlink, transaction):
1741 def strip(self, minlink, transaction):
1742 """truncate the revlog on the first revision with a linkrev >= minlink
1742 """truncate the revlog on the first revision with a linkrev >= minlink
1743
1743
1744 This function is called when we're stripping revision minlink and
1744 This function is called when we're stripping revision minlink and
1745 its descendants from the repository.
1745 its descendants from the repository.
1746
1746
1747 We have to remove all revisions with linkrev >= minlink, because
1747 We have to remove all revisions with linkrev >= minlink, because
1748 the equivalent changelog revisions will be renumbered after the
1748 the equivalent changelog revisions will be renumbered after the
1749 strip.
1749 strip.
1750
1750
1751 So we truncate the revlog on the first of these revisions, and
1751 So we truncate the revlog on the first of these revisions, and
1752 trust that the caller has saved the revisions that shouldn't be
1752 trust that the caller has saved the revisions that shouldn't be
1753 removed and that it'll re-add them after this truncation.
1753 removed and that it'll re-add them after this truncation.
1754 """
1754 """
1755 if len(self) == 0:
1755 if len(self) == 0:
1756 return
1756 return
1757
1757
1758 rev, _ = self.getstrippoint(minlink)
1758 rev, _ = self.getstrippoint(minlink)
1759 if rev == len(self):
1759 if rev == len(self):
1760 return
1760 return
1761
1761
1762 # first truncate the files on disk
1762 # first truncate the files on disk
1763 end = self.start(rev)
1763 end = self.start(rev)
1764 if not self._inline:
1764 if not self._inline:
1765 transaction.add(self.datafile, end)
1765 transaction.add(self.datafile, end)
1766 end = rev * self._io.size
1766 end = rev * self._io.size
1767 else:
1767 else:
1768 end += rev * self._io.size
1768 end += rev * self._io.size
1769
1769
1770 transaction.add(self.indexfile, end)
1770 transaction.add(self.indexfile, end)
1771
1771
1772 # then reset internal state in memory to forget those revisions
1772 # then reset internal state in memory to forget those revisions
1773 self._cache = None
1773 self._cache = None
1774 self._chaininfocache = {}
1774 self._chaininfocache = {}
1775 self._chunkclear()
1775 self._chunkclear()
1776 for x in xrange(rev, len(self)):
1776 for x in xrange(rev, len(self)):
1777 del self.nodemap[self.node(x)]
1777 del self.nodemap[self.node(x)]
1778
1778
1779 del self.index[rev:-1]
1779 del self.index[rev:-1]
1780
1780
1781 def checksize(self):
1781 def checksize(self):
1782 expected = 0
1782 expected = 0
1783 if len(self):
1783 if len(self):
1784 expected = max(0, self.end(len(self) - 1))
1784 expected = max(0, self.end(len(self) - 1))
1785
1785
1786 try:
1786 try:
1787 f = self.opener(self.datafile)
1787 f = self.opener(self.datafile)
1788 f.seek(0, 2)
1788 f.seek(0, 2)
1789 actual = f.tell()
1789 actual = f.tell()
1790 f.close()
1790 f.close()
1791 dd = actual - expected
1791 dd = actual - expected
1792 except IOError as inst:
1792 except IOError as inst:
1793 if inst.errno != errno.ENOENT:
1793 if inst.errno != errno.ENOENT:
1794 raise
1794 raise
1795 dd = 0
1795 dd = 0
1796
1796
1797 try:
1797 try:
1798 f = self.opener(self.indexfile)
1798 f = self.opener(self.indexfile)
1799 f.seek(0, 2)
1799 f.seek(0, 2)
1800 actual = f.tell()
1800 actual = f.tell()
1801 f.close()
1801 f.close()
1802 s = self._io.size
1802 s = self._io.size
1803 i = max(0, actual // s)
1803 i = max(0, actual // s)
1804 di = actual - (i * s)
1804 di = actual - (i * s)
1805 if self._inline:
1805 if self._inline:
1806 databytes = 0
1806 databytes = 0
1807 for r in self:
1807 for r in self:
1808 databytes += max(0, self.length(r))
1808 databytes += max(0, self.length(r))
1809 dd = 0
1809 dd = 0
1810 di = actual - len(self) * s - databytes
1810 di = actual - len(self) * s - databytes
1811 except IOError as inst:
1811 except IOError as inst:
1812 if inst.errno != errno.ENOENT:
1812 if inst.errno != errno.ENOENT:
1813 raise
1813 raise
1814 di = 0
1814 di = 0
1815
1815
1816 return (dd, di)
1816 return (dd, di)
1817
1817
1818 def files(self):
1818 def files(self):
1819 res = [self.indexfile]
1819 res = [self.indexfile]
1820 if not self._inline:
1820 if not self._inline:
1821 res.append(self.datafile)
1821 res.append(self.datafile)
1822 return res
1822 return res
General Comments 0
You need to be logged in to leave comments. Login now