##// END OF EJS Templates
revlog: split functionality related to deltas computation in a new module...
Boris Feld -
r39366:655b5b46 default
parent child Browse files
Show More
@@ -1,3361 +1,3365 b''
1 # debugcommands.py - command processing for debug* commands
1 # debugcommands.py - command processing for debug* commands
2 #
2 #
3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2016 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 codecs
10 import codecs
11 import collections
11 import collections
12 import difflib
12 import difflib
13 import errno
13 import errno
14 import operator
14 import operator
15 import os
15 import os
16 import random
16 import random
17 import re
17 import re
18 import socket
18 import socket
19 import ssl
19 import ssl
20 import stat
20 import stat
21 import string
21 import string
22 import subprocess
22 import subprocess
23 import sys
23 import sys
24 import time
24 import time
25
25
26 from .i18n import _
26 from .i18n import _
27 from .node import (
27 from .node import (
28 bin,
28 bin,
29 hex,
29 hex,
30 nullhex,
30 nullhex,
31 nullid,
31 nullid,
32 nullrev,
32 nullrev,
33 short,
33 short,
34 )
34 )
35 from .thirdparty import (
35 from .thirdparty import (
36 cbor,
36 cbor,
37 )
37 )
38 from . import (
38 from . import (
39 bundle2,
39 bundle2,
40 changegroup,
40 changegroup,
41 cmdutil,
41 cmdutil,
42 color,
42 color,
43 context,
43 context,
44 dagparser,
44 dagparser,
45 encoding,
45 encoding,
46 error,
46 error,
47 exchange,
47 exchange,
48 extensions,
48 extensions,
49 filemerge,
49 filemerge,
50 filesetlang,
50 filesetlang,
51 formatter,
51 formatter,
52 hg,
52 hg,
53 httppeer,
53 httppeer,
54 localrepo,
54 localrepo,
55 lock as lockmod,
55 lock as lockmod,
56 logcmdutil,
56 logcmdutil,
57 merge as mergemod,
57 merge as mergemod,
58 obsolete,
58 obsolete,
59 obsutil,
59 obsutil,
60 phases,
60 phases,
61 policy,
61 policy,
62 pvec,
62 pvec,
63 pycompat,
63 pycompat,
64 registrar,
64 registrar,
65 repair,
65 repair,
66 revlog,
66 revlog,
67 revset,
67 revset,
68 revsetlang,
68 revsetlang,
69 scmutil,
69 scmutil,
70 setdiscovery,
70 setdiscovery,
71 simplemerge,
71 simplemerge,
72 sshpeer,
72 sshpeer,
73 sslutil,
73 sslutil,
74 streamclone,
74 streamclone,
75 templater,
75 templater,
76 treediscovery,
76 treediscovery,
77 upgrade,
77 upgrade,
78 url as urlmod,
78 url as urlmod,
79 util,
79 util,
80 vfs as vfsmod,
80 vfs as vfsmod,
81 wireprotoframing,
81 wireprotoframing,
82 wireprotoserver,
82 wireprotoserver,
83 wireprotov2peer,
83 wireprotov2peer,
84 )
84 )
85 from .utils import (
85 from .utils import (
86 dateutil,
86 dateutil,
87 procutil,
87 procutil,
88 stringutil,
88 stringutil,
89 )
89 )
90
90
91 from .revlogutils import (
92 deltas as deltautil
93 )
94
91 release = lockmod.release
95 release = lockmod.release
92
96
93 command = registrar.command()
97 command = registrar.command()
94
98
95 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
99 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
96 def debugancestor(ui, repo, *args):
100 def debugancestor(ui, repo, *args):
97 """find the ancestor revision of two revisions in a given index"""
101 """find the ancestor revision of two revisions in a given index"""
98 if len(args) == 3:
102 if len(args) == 3:
99 index, rev1, rev2 = args
103 index, rev1, rev2 = args
100 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
104 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
101 lookup = r.lookup
105 lookup = r.lookup
102 elif len(args) == 2:
106 elif len(args) == 2:
103 if not repo:
107 if not repo:
104 raise error.Abort(_('there is no Mercurial repository here '
108 raise error.Abort(_('there is no Mercurial repository here '
105 '(.hg not found)'))
109 '(.hg not found)'))
106 rev1, rev2 = args
110 rev1, rev2 = args
107 r = repo.changelog
111 r = repo.changelog
108 lookup = repo.lookup
112 lookup = repo.lookup
109 else:
113 else:
110 raise error.Abort(_('either two or three arguments required'))
114 raise error.Abort(_('either two or three arguments required'))
111 a = r.ancestor(lookup(rev1), lookup(rev2))
115 a = r.ancestor(lookup(rev1), lookup(rev2))
112 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
116 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
113
117
114 @command('debugapplystreamclonebundle', [], 'FILE')
118 @command('debugapplystreamclonebundle', [], 'FILE')
115 def debugapplystreamclonebundle(ui, repo, fname):
119 def debugapplystreamclonebundle(ui, repo, fname):
116 """apply a stream clone bundle file"""
120 """apply a stream clone bundle file"""
117 f = hg.openpath(ui, fname)
121 f = hg.openpath(ui, fname)
118 gen = exchange.readbundle(ui, f, fname)
122 gen = exchange.readbundle(ui, f, fname)
119 gen.apply(repo)
123 gen.apply(repo)
120
124
121 @command('debugbuilddag',
125 @command('debugbuilddag',
122 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
126 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
123 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
127 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
124 ('n', 'new-file', None, _('add new file at each rev'))],
128 ('n', 'new-file', None, _('add new file at each rev'))],
125 _('[OPTION]... [TEXT]'))
129 _('[OPTION]... [TEXT]'))
126 def debugbuilddag(ui, repo, text=None,
130 def debugbuilddag(ui, repo, text=None,
127 mergeable_file=False,
131 mergeable_file=False,
128 overwritten_file=False,
132 overwritten_file=False,
129 new_file=False):
133 new_file=False):
130 """builds a repo with a given DAG from scratch in the current empty repo
134 """builds a repo with a given DAG from scratch in the current empty repo
131
135
132 The description of the DAG is read from stdin if not given on the
136 The description of the DAG is read from stdin if not given on the
133 command line.
137 command line.
134
138
135 Elements:
139 Elements:
136
140
137 - "+n" is a linear run of n nodes based on the current default parent
141 - "+n" is a linear run of n nodes based on the current default parent
138 - "." is a single node based on the current default parent
142 - "." is a single node based on the current default parent
139 - "$" resets the default parent to null (implied at the start);
143 - "$" resets the default parent to null (implied at the start);
140 otherwise the default parent is always the last node created
144 otherwise the default parent is always the last node created
141 - "<p" sets the default parent to the backref p
145 - "<p" sets the default parent to the backref p
142 - "*p" is a fork at parent p, which is a backref
146 - "*p" is a fork at parent p, which is a backref
143 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
147 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
144 - "/p2" is a merge of the preceding node and p2
148 - "/p2" is a merge of the preceding node and p2
145 - ":tag" defines a local tag for the preceding node
149 - ":tag" defines a local tag for the preceding node
146 - "@branch" sets the named branch for subsequent nodes
150 - "@branch" sets the named branch for subsequent nodes
147 - "#...\\n" is a comment up to the end of the line
151 - "#...\\n" is a comment up to the end of the line
148
152
149 Whitespace between the above elements is ignored.
153 Whitespace between the above elements is ignored.
150
154
151 A backref is either
155 A backref is either
152
156
153 - a number n, which references the node curr-n, where curr is the current
157 - a number n, which references the node curr-n, where curr is the current
154 node, or
158 node, or
155 - the name of a local tag you placed earlier using ":tag", or
159 - the name of a local tag you placed earlier using ":tag", or
156 - empty to denote the default parent.
160 - empty to denote the default parent.
157
161
158 All string valued-elements are either strictly alphanumeric, or must
162 All string valued-elements are either strictly alphanumeric, or must
159 be enclosed in double quotes ("..."), with "\\" as escape character.
163 be enclosed in double quotes ("..."), with "\\" as escape character.
160 """
164 """
161
165
162 if text is None:
166 if text is None:
163 ui.status(_("reading DAG from stdin\n"))
167 ui.status(_("reading DAG from stdin\n"))
164 text = ui.fin.read()
168 text = ui.fin.read()
165
169
166 cl = repo.changelog
170 cl = repo.changelog
167 if len(cl) > 0:
171 if len(cl) > 0:
168 raise error.Abort(_('repository is not empty'))
172 raise error.Abort(_('repository is not empty'))
169
173
170 # determine number of revs in DAG
174 # determine number of revs in DAG
171 total = 0
175 total = 0
172 for type, data in dagparser.parsedag(text):
176 for type, data in dagparser.parsedag(text):
173 if type == 'n':
177 if type == 'n':
174 total += 1
178 total += 1
175
179
176 if mergeable_file:
180 if mergeable_file:
177 linesperrev = 2
181 linesperrev = 2
178 # make a file with k lines per rev
182 # make a file with k lines per rev
179 initialmergedlines = ['%d' % i
183 initialmergedlines = ['%d' % i
180 for i in pycompat.xrange(0, total * linesperrev)]
184 for i in pycompat.xrange(0, total * linesperrev)]
181 initialmergedlines.append("")
185 initialmergedlines.append("")
182
186
183 tags = []
187 tags = []
184 progress = ui.makeprogress(_('building'), unit=_('revisions'),
188 progress = ui.makeprogress(_('building'), unit=_('revisions'),
185 total=total)
189 total=total)
186 with progress, repo.wlock(), repo.lock(), repo.transaction("builddag"):
190 with progress, repo.wlock(), repo.lock(), repo.transaction("builddag"):
187 at = -1
191 at = -1
188 atbranch = 'default'
192 atbranch = 'default'
189 nodeids = []
193 nodeids = []
190 id = 0
194 id = 0
191 progress.update(id)
195 progress.update(id)
192 for type, data in dagparser.parsedag(text):
196 for type, data in dagparser.parsedag(text):
193 if type == 'n':
197 if type == 'n':
194 ui.note(('node %s\n' % pycompat.bytestr(data)))
198 ui.note(('node %s\n' % pycompat.bytestr(data)))
195 id, ps = data
199 id, ps = data
196
200
197 files = []
201 files = []
198 filecontent = {}
202 filecontent = {}
199
203
200 p2 = None
204 p2 = None
201 if mergeable_file:
205 if mergeable_file:
202 fn = "mf"
206 fn = "mf"
203 p1 = repo[ps[0]]
207 p1 = repo[ps[0]]
204 if len(ps) > 1:
208 if len(ps) > 1:
205 p2 = repo[ps[1]]
209 p2 = repo[ps[1]]
206 pa = p1.ancestor(p2)
210 pa = p1.ancestor(p2)
207 base, local, other = [x[fn].data() for x in (pa, p1,
211 base, local, other = [x[fn].data() for x in (pa, p1,
208 p2)]
212 p2)]
209 m3 = simplemerge.Merge3Text(base, local, other)
213 m3 = simplemerge.Merge3Text(base, local, other)
210 ml = [l.strip() for l in m3.merge_lines()]
214 ml = [l.strip() for l in m3.merge_lines()]
211 ml.append("")
215 ml.append("")
212 elif at > 0:
216 elif at > 0:
213 ml = p1[fn].data().split("\n")
217 ml = p1[fn].data().split("\n")
214 else:
218 else:
215 ml = initialmergedlines
219 ml = initialmergedlines
216 ml[id * linesperrev] += " r%i" % id
220 ml[id * linesperrev] += " r%i" % id
217 mergedtext = "\n".join(ml)
221 mergedtext = "\n".join(ml)
218 files.append(fn)
222 files.append(fn)
219 filecontent[fn] = mergedtext
223 filecontent[fn] = mergedtext
220
224
221 if overwritten_file:
225 if overwritten_file:
222 fn = "of"
226 fn = "of"
223 files.append(fn)
227 files.append(fn)
224 filecontent[fn] = "r%i\n" % id
228 filecontent[fn] = "r%i\n" % id
225
229
226 if new_file:
230 if new_file:
227 fn = "nf%i" % id
231 fn = "nf%i" % id
228 files.append(fn)
232 files.append(fn)
229 filecontent[fn] = "r%i\n" % id
233 filecontent[fn] = "r%i\n" % id
230 if len(ps) > 1:
234 if len(ps) > 1:
231 if not p2:
235 if not p2:
232 p2 = repo[ps[1]]
236 p2 = repo[ps[1]]
233 for fn in p2:
237 for fn in p2:
234 if fn.startswith("nf"):
238 if fn.startswith("nf"):
235 files.append(fn)
239 files.append(fn)
236 filecontent[fn] = p2[fn].data()
240 filecontent[fn] = p2[fn].data()
237
241
238 def fctxfn(repo, cx, path):
242 def fctxfn(repo, cx, path):
239 if path in filecontent:
243 if path in filecontent:
240 return context.memfilectx(repo, cx, path,
244 return context.memfilectx(repo, cx, path,
241 filecontent[path])
245 filecontent[path])
242 return None
246 return None
243
247
244 if len(ps) == 0 or ps[0] < 0:
248 if len(ps) == 0 or ps[0] < 0:
245 pars = [None, None]
249 pars = [None, None]
246 elif len(ps) == 1:
250 elif len(ps) == 1:
247 pars = [nodeids[ps[0]], None]
251 pars = [nodeids[ps[0]], None]
248 else:
252 else:
249 pars = [nodeids[p] for p in ps]
253 pars = [nodeids[p] for p in ps]
250 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
254 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
251 date=(id, 0),
255 date=(id, 0),
252 user="debugbuilddag",
256 user="debugbuilddag",
253 extra={'branch': atbranch})
257 extra={'branch': atbranch})
254 nodeid = repo.commitctx(cx)
258 nodeid = repo.commitctx(cx)
255 nodeids.append(nodeid)
259 nodeids.append(nodeid)
256 at = id
260 at = id
257 elif type == 'l':
261 elif type == 'l':
258 id, name = data
262 id, name = data
259 ui.note(('tag %s\n' % name))
263 ui.note(('tag %s\n' % name))
260 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
264 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
261 elif type == 'a':
265 elif type == 'a':
262 ui.note(('branch %s\n' % data))
266 ui.note(('branch %s\n' % data))
263 atbranch = data
267 atbranch = data
264 progress.update(id)
268 progress.update(id)
265
269
266 if tags:
270 if tags:
267 repo.vfs.write("localtags", "".join(tags))
271 repo.vfs.write("localtags", "".join(tags))
268
272
269 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
273 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
270 indent_string = ' ' * indent
274 indent_string = ' ' * indent
271 if all:
275 if all:
272 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
276 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
273 % indent_string)
277 % indent_string)
274
278
275 def showchunks(named):
279 def showchunks(named):
276 ui.write("\n%s%s\n" % (indent_string, named))
280 ui.write("\n%s%s\n" % (indent_string, named))
277 for deltadata in gen.deltaiter():
281 for deltadata in gen.deltaiter():
278 node, p1, p2, cs, deltabase, delta, flags = deltadata
282 node, p1, p2, cs, deltabase, delta, flags = deltadata
279 ui.write("%s%s %s %s %s %s %d\n" %
283 ui.write("%s%s %s %s %s %s %d\n" %
280 (indent_string, hex(node), hex(p1), hex(p2),
284 (indent_string, hex(node), hex(p1), hex(p2),
281 hex(cs), hex(deltabase), len(delta)))
285 hex(cs), hex(deltabase), len(delta)))
282
286
283 chunkdata = gen.changelogheader()
287 chunkdata = gen.changelogheader()
284 showchunks("changelog")
288 showchunks("changelog")
285 chunkdata = gen.manifestheader()
289 chunkdata = gen.manifestheader()
286 showchunks("manifest")
290 showchunks("manifest")
287 for chunkdata in iter(gen.filelogheader, {}):
291 for chunkdata in iter(gen.filelogheader, {}):
288 fname = chunkdata['filename']
292 fname = chunkdata['filename']
289 showchunks(fname)
293 showchunks(fname)
290 else:
294 else:
291 if isinstance(gen, bundle2.unbundle20):
295 if isinstance(gen, bundle2.unbundle20):
292 raise error.Abort(_('use debugbundle2 for this file'))
296 raise error.Abort(_('use debugbundle2 for this file'))
293 chunkdata = gen.changelogheader()
297 chunkdata = gen.changelogheader()
294 for deltadata in gen.deltaiter():
298 for deltadata in gen.deltaiter():
295 node, p1, p2, cs, deltabase, delta, flags = deltadata
299 node, p1, p2, cs, deltabase, delta, flags = deltadata
296 ui.write("%s%s\n" % (indent_string, hex(node)))
300 ui.write("%s%s\n" % (indent_string, hex(node)))
297
301
298 def _debugobsmarkers(ui, part, indent=0, **opts):
302 def _debugobsmarkers(ui, part, indent=0, **opts):
299 """display version and markers contained in 'data'"""
303 """display version and markers contained in 'data'"""
300 opts = pycompat.byteskwargs(opts)
304 opts = pycompat.byteskwargs(opts)
301 data = part.read()
305 data = part.read()
302 indent_string = ' ' * indent
306 indent_string = ' ' * indent
303 try:
307 try:
304 version, markers = obsolete._readmarkers(data)
308 version, markers = obsolete._readmarkers(data)
305 except error.UnknownVersion as exc:
309 except error.UnknownVersion as exc:
306 msg = "%sunsupported version: %s (%d bytes)\n"
310 msg = "%sunsupported version: %s (%d bytes)\n"
307 msg %= indent_string, exc.version, len(data)
311 msg %= indent_string, exc.version, len(data)
308 ui.write(msg)
312 ui.write(msg)
309 else:
313 else:
310 msg = "%sversion: %d (%d bytes)\n"
314 msg = "%sversion: %d (%d bytes)\n"
311 msg %= indent_string, version, len(data)
315 msg %= indent_string, version, len(data)
312 ui.write(msg)
316 ui.write(msg)
313 fm = ui.formatter('debugobsolete', opts)
317 fm = ui.formatter('debugobsolete', opts)
314 for rawmarker in sorted(markers):
318 for rawmarker in sorted(markers):
315 m = obsutil.marker(None, rawmarker)
319 m = obsutil.marker(None, rawmarker)
316 fm.startitem()
320 fm.startitem()
317 fm.plain(indent_string)
321 fm.plain(indent_string)
318 cmdutil.showmarker(fm, m)
322 cmdutil.showmarker(fm, m)
319 fm.end()
323 fm.end()
320
324
321 def _debugphaseheads(ui, data, indent=0):
325 def _debugphaseheads(ui, data, indent=0):
322 """display version and markers contained in 'data'"""
326 """display version and markers contained in 'data'"""
323 indent_string = ' ' * indent
327 indent_string = ' ' * indent
324 headsbyphase = phases.binarydecode(data)
328 headsbyphase = phases.binarydecode(data)
325 for phase in phases.allphases:
329 for phase in phases.allphases:
326 for head in headsbyphase[phase]:
330 for head in headsbyphase[phase]:
327 ui.write(indent_string)
331 ui.write(indent_string)
328 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
332 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
329
333
330 def _quasirepr(thing):
334 def _quasirepr(thing):
331 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
335 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
332 return '{%s}' % (
336 return '{%s}' % (
333 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
337 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
334 return pycompat.bytestr(repr(thing))
338 return pycompat.bytestr(repr(thing))
335
339
336 def _debugbundle2(ui, gen, all=None, **opts):
340 def _debugbundle2(ui, gen, all=None, **opts):
337 """lists the contents of a bundle2"""
341 """lists the contents of a bundle2"""
338 if not isinstance(gen, bundle2.unbundle20):
342 if not isinstance(gen, bundle2.unbundle20):
339 raise error.Abort(_('not a bundle2 file'))
343 raise error.Abort(_('not a bundle2 file'))
340 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
344 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
341 parttypes = opts.get(r'part_type', [])
345 parttypes = opts.get(r'part_type', [])
342 for part in gen.iterparts():
346 for part in gen.iterparts():
343 if parttypes and part.type not in parttypes:
347 if parttypes and part.type not in parttypes:
344 continue
348 continue
345 msg = '%s -- %s (mandatory: %r)\n'
349 msg = '%s -- %s (mandatory: %r)\n'
346 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
350 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
347 if part.type == 'changegroup':
351 if part.type == 'changegroup':
348 version = part.params.get('version', '01')
352 version = part.params.get('version', '01')
349 cg = changegroup.getunbundler(version, part, 'UN')
353 cg = changegroup.getunbundler(version, part, 'UN')
350 if not ui.quiet:
354 if not ui.quiet:
351 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
355 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
352 if part.type == 'obsmarkers':
356 if part.type == 'obsmarkers':
353 if not ui.quiet:
357 if not ui.quiet:
354 _debugobsmarkers(ui, part, indent=4, **opts)
358 _debugobsmarkers(ui, part, indent=4, **opts)
355 if part.type == 'phase-heads':
359 if part.type == 'phase-heads':
356 if not ui.quiet:
360 if not ui.quiet:
357 _debugphaseheads(ui, part, indent=4)
361 _debugphaseheads(ui, part, indent=4)
358
362
359 @command('debugbundle',
363 @command('debugbundle',
360 [('a', 'all', None, _('show all details')),
364 [('a', 'all', None, _('show all details')),
361 ('', 'part-type', [], _('show only the named part type')),
365 ('', 'part-type', [], _('show only the named part type')),
362 ('', 'spec', None, _('print the bundlespec of the bundle'))],
366 ('', 'spec', None, _('print the bundlespec of the bundle'))],
363 _('FILE'),
367 _('FILE'),
364 norepo=True)
368 norepo=True)
365 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
369 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
366 """lists the contents of a bundle"""
370 """lists the contents of a bundle"""
367 with hg.openpath(ui, bundlepath) as f:
371 with hg.openpath(ui, bundlepath) as f:
368 if spec:
372 if spec:
369 spec = exchange.getbundlespec(ui, f)
373 spec = exchange.getbundlespec(ui, f)
370 ui.write('%s\n' % spec)
374 ui.write('%s\n' % spec)
371 return
375 return
372
376
373 gen = exchange.readbundle(ui, f, bundlepath)
377 gen = exchange.readbundle(ui, f, bundlepath)
374 if isinstance(gen, bundle2.unbundle20):
378 if isinstance(gen, bundle2.unbundle20):
375 return _debugbundle2(ui, gen, all=all, **opts)
379 return _debugbundle2(ui, gen, all=all, **opts)
376 _debugchangegroup(ui, gen, all=all, **opts)
380 _debugchangegroup(ui, gen, all=all, **opts)
377
381
378 @command('debugcapabilities',
382 @command('debugcapabilities',
379 [], _('PATH'),
383 [], _('PATH'),
380 norepo=True)
384 norepo=True)
381 def debugcapabilities(ui, path, **opts):
385 def debugcapabilities(ui, path, **opts):
382 """lists the capabilities of a remote peer"""
386 """lists the capabilities of a remote peer"""
383 opts = pycompat.byteskwargs(opts)
387 opts = pycompat.byteskwargs(opts)
384 peer = hg.peer(ui, opts, path)
388 peer = hg.peer(ui, opts, path)
385 caps = peer.capabilities()
389 caps = peer.capabilities()
386 ui.write(('Main capabilities:\n'))
390 ui.write(('Main capabilities:\n'))
387 for c in sorted(caps):
391 for c in sorted(caps):
388 ui.write((' %s\n') % c)
392 ui.write((' %s\n') % c)
389 b2caps = bundle2.bundle2caps(peer)
393 b2caps = bundle2.bundle2caps(peer)
390 if b2caps:
394 if b2caps:
391 ui.write(('Bundle2 capabilities:\n'))
395 ui.write(('Bundle2 capabilities:\n'))
392 for key, values in sorted(b2caps.iteritems()):
396 for key, values in sorted(b2caps.iteritems()):
393 ui.write((' %s\n') % key)
397 ui.write((' %s\n') % key)
394 for v in values:
398 for v in values:
395 ui.write((' %s\n') % v)
399 ui.write((' %s\n') % v)
396
400
397 @command('debugcheckstate', [], '')
401 @command('debugcheckstate', [], '')
398 def debugcheckstate(ui, repo):
402 def debugcheckstate(ui, repo):
399 """validate the correctness of the current dirstate"""
403 """validate the correctness of the current dirstate"""
400 parent1, parent2 = repo.dirstate.parents()
404 parent1, parent2 = repo.dirstate.parents()
401 m1 = repo[parent1].manifest()
405 m1 = repo[parent1].manifest()
402 m2 = repo[parent2].manifest()
406 m2 = repo[parent2].manifest()
403 errors = 0
407 errors = 0
404 for f in repo.dirstate:
408 for f in repo.dirstate:
405 state = repo.dirstate[f]
409 state = repo.dirstate[f]
406 if state in "nr" and f not in m1:
410 if state in "nr" and f not in m1:
407 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
411 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
408 errors += 1
412 errors += 1
409 if state in "a" and f in m1:
413 if state in "a" and f in m1:
410 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
414 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
411 errors += 1
415 errors += 1
412 if state in "m" and f not in m1 and f not in m2:
416 if state in "m" and f not in m1 and f not in m2:
413 ui.warn(_("%s in state %s, but not in either manifest\n") %
417 ui.warn(_("%s in state %s, but not in either manifest\n") %
414 (f, state))
418 (f, state))
415 errors += 1
419 errors += 1
416 for f in m1:
420 for f in m1:
417 state = repo.dirstate[f]
421 state = repo.dirstate[f]
418 if state not in "nrm":
422 if state not in "nrm":
419 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
423 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
420 errors += 1
424 errors += 1
421 if errors:
425 if errors:
422 error = _(".hg/dirstate inconsistent with current parent's manifest")
426 error = _(".hg/dirstate inconsistent with current parent's manifest")
423 raise error.Abort(error)
427 raise error.Abort(error)
424
428
425 @command('debugcolor',
429 @command('debugcolor',
426 [('', 'style', None, _('show all configured styles'))],
430 [('', 'style', None, _('show all configured styles'))],
427 'hg debugcolor')
431 'hg debugcolor')
428 def debugcolor(ui, repo, **opts):
432 def debugcolor(ui, repo, **opts):
429 """show available color, effects or style"""
433 """show available color, effects or style"""
430 ui.write(('color mode: %s\n') % stringutil.pprint(ui._colormode))
434 ui.write(('color mode: %s\n') % stringutil.pprint(ui._colormode))
431 if opts.get(r'style'):
435 if opts.get(r'style'):
432 return _debugdisplaystyle(ui)
436 return _debugdisplaystyle(ui)
433 else:
437 else:
434 return _debugdisplaycolor(ui)
438 return _debugdisplaycolor(ui)
435
439
436 def _debugdisplaycolor(ui):
440 def _debugdisplaycolor(ui):
437 ui = ui.copy()
441 ui = ui.copy()
438 ui._styles.clear()
442 ui._styles.clear()
439 for effect in color._activeeffects(ui).keys():
443 for effect in color._activeeffects(ui).keys():
440 ui._styles[effect] = effect
444 ui._styles[effect] = effect
441 if ui._terminfoparams:
445 if ui._terminfoparams:
442 for k, v in ui.configitems('color'):
446 for k, v in ui.configitems('color'):
443 if k.startswith('color.'):
447 if k.startswith('color.'):
444 ui._styles[k] = k[6:]
448 ui._styles[k] = k[6:]
445 elif k.startswith('terminfo.'):
449 elif k.startswith('terminfo.'):
446 ui._styles[k] = k[9:]
450 ui._styles[k] = k[9:]
447 ui.write(_('available colors:\n'))
451 ui.write(_('available colors:\n'))
448 # sort label with a '_' after the other to group '_background' entry.
452 # sort label with a '_' after the other to group '_background' entry.
449 items = sorted(ui._styles.items(),
453 items = sorted(ui._styles.items(),
450 key=lambda i: ('_' in i[0], i[0], i[1]))
454 key=lambda i: ('_' in i[0], i[0], i[1]))
451 for colorname, label in items:
455 for colorname, label in items:
452 ui.write(('%s\n') % colorname, label=label)
456 ui.write(('%s\n') % colorname, label=label)
453
457
454 def _debugdisplaystyle(ui):
458 def _debugdisplaystyle(ui):
455 ui.write(_('available style:\n'))
459 ui.write(_('available style:\n'))
456 if not ui._styles:
460 if not ui._styles:
457 return
461 return
458 width = max(len(s) for s in ui._styles)
462 width = max(len(s) for s in ui._styles)
459 for label, effects in sorted(ui._styles.items()):
463 for label, effects in sorted(ui._styles.items()):
460 ui.write('%s' % label, label=label)
464 ui.write('%s' % label, label=label)
461 if effects:
465 if effects:
462 # 50
466 # 50
463 ui.write(': ')
467 ui.write(': ')
464 ui.write(' ' * (max(0, width - len(label))))
468 ui.write(' ' * (max(0, width - len(label))))
465 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
469 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
466 ui.write('\n')
470 ui.write('\n')
467
471
468 @command('debugcreatestreamclonebundle', [], 'FILE')
472 @command('debugcreatestreamclonebundle', [], 'FILE')
469 def debugcreatestreamclonebundle(ui, repo, fname):
473 def debugcreatestreamclonebundle(ui, repo, fname):
470 """create a stream clone bundle file
474 """create a stream clone bundle file
471
475
472 Stream bundles are special bundles that are essentially archives of
476 Stream bundles are special bundles that are essentially archives of
473 revlog files. They are commonly used for cloning very quickly.
477 revlog files. They are commonly used for cloning very quickly.
474 """
478 """
475 # TODO we may want to turn this into an abort when this functionality
479 # TODO we may want to turn this into an abort when this functionality
476 # is moved into `hg bundle`.
480 # is moved into `hg bundle`.
477 if phases.hassecret(repo):
481 if phases.hassecret(repo):
478 ui.warn(_('(warning: stream clone bundle will contain secret '
482 ui.warn(_('(warning: stream clone bundle will contain secret '
479 'revisions)\n'))
483 'revisions)\n'))
480
484
481 requirements, gen = streamclone.generatebundlev1(repo)
485 requirements, gen = streamclone.generatebundlev1(repo)
482 changegroup.writechunks(ui, gen, fname)
486 changegroup.writechunks(ui, gen, fname)
483
487
484 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
488 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
485
489
486 @command('debugdag',
490 @command('debugdag',
487 [('t', 'tags', None, _('use tags as labels')),
491 [('t', 'tags', None, _('use tags as labels')),
488 ('b', 'branches', None, _('annotate with branch names')),
492 ('b', 'branches', None, _('annotate with branch names')),
489 ('', 'dots', None, _('use dots for runs')),
493 ('', 'dots', None, _('use dots for runs')),
490 ('s', 'spaces', None, _('separate elements by spaces'))],
494 ('s', 'spaces', None, _('separate elements by spaces'))],
491 _('[OPTION]... [FILE [REV]...]'),
495 _('[OPTION]... [FILE [REV]...]'),
492 optionalrepo=True)
496 optionalrepo=True)
493 def debugdag(ui, repo, file_=None, *revs, **opts):
497 def debugdag(ui, repo, file_=None, *revs, **opts):
494 """format the changelog or an index DAG as a concise textual description
498 """format the changelog or an index DAG as a concise textual description
495
499
496 If you pass a revlog index, the revlog's DAG is emitted. If you list
500 If you pass a revlog index, the revlog's DAG is emitted. If you list
497 revision numbers, they get labeled in the output as rN.
501 revision numbers, they get labeled in the output as rN.
498
502
499 Otherwise, the changelog DAG of the current repo is emitted.
503 Otherwise, the changelog DAG of the current repo is emitted.
500 """
504 """
501 spaces = opts.get(r'spaces')
505 spaces = opts.get(r'spaces')
502 dots = opts.get(r'dots')
506 dots = opts.get(r'dots')
503 if file_:
507 if file_:
504 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
508 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
505 file_)
509 file_)
506 revs = set((int(r) for r in revs))
510 revs = set((int(r) for r in revs))
507 def events():
511 def events():
508 for r in rlog:
512 for r in rlog:
509 yield 'n', (r, list(p for p in rlog.parentrevs(r)
513 yield 'n', (r, list(p for p in rlog.parentrevs(r)
510 if p != -1))
514 if p != -1))
511 if r in revs:
515 if r in revs:
512 yield 'l', (r, "r%i" % r)
516 yield 'l', (r, "r%i" % r)
513 elif repo:
517 elif repo:
514 cl = repo.changelog
518 cl = repo.changelog
515 tags = opts.get(r'tags')
519 tags = opts.get(r'tags')
516 branches = opts.get(r'branches')
520 branches = opts.get(r'branches')
517 if tags:
521 if tags:
518 labels = {}
522 labels = {}
519 for l, n in repo.tags().items():
523 for l, n in repo.tags().items():
520 labels.setdefault(cl.rev(n), []).append(l)
524 labels.setdefault(cl.rev(n), []).append(l)
521 def events():
525 def events():
522 b = "default"
526 b = "default"
523 for r in cl:
527 for r in cl:
524 if branches:
528 if branches:
525 newb = cl.read(cl.node(r))[5]['branch']
529 newb = cl.read(cl.node(r))[5]['branch']
526 if newb != b:
530 if newb != b:
527 yield 'a', newb
531 yield 'a', newb
528 b = newb
532 b = newb
529 yield 'n', (r, list(p for p in cl.parentrevs(r)
533 yield 'n', (r, list(p for p in cl.parentrevs(r)
530 if p != -1))
534 if p != -1))
531 if tags:
535 if tags:
532 ls = labels.get(r)
536 ls = labels.get(r)
533 if ls:
537 if ls:
534 for l in ls:
538 for l in ls:
535 yield 'l', (r, l)
539 yield 'l', (r, l)
536 else:
540 else:
537 raise error.Abort(_('need repo for changelog dag'))
541 raise error.Abort(_('need repo for changelog dag'))
538
542
539 for line in dagparser.dagtextlines(events(),
543 for line in dagparser.dagtextlines(events(),
540 addspaces=spaces,
544 addspaces=spaces,
541 wraplabels=True,
545 wraplabels=True,
542 wrapannotations=True,
546 wrapannotations=True,
543 wrapnonlinear=dots,
547 wrapnonlinear=dots,
544 usedots=dots,
548 usedots=dots,
545 maxlinewidth=70):
549 maxlinewidth=70):
546 ui.write(line)
550 ui.write(line)
547 ui.write("\n")
551 ui.write("\n")
548
552
549 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
553 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
550 def debugdata(ui, repo, file_, rev=None, **opts):
554 def debugdata(ui, repo, file_, rev=None, **opts):
551 """dump the contents of a data file revision"""
555 """dump the contents of a data file revision"""
552 opts = pycompat.byteskwargs(opts)
556 opts = pycompat.byteskwargs(opts)
553 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
557 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
554 if rev is not None:
558 if rev is not None:
555 raise error.CommandError('debugdata', _('invalid arguments'))
559 raise error.CommandError('debugdata', _('invalid arguments'))
556 file_, rev = None, file_
560 file_, rev = None, file_
557 elif rev is None:
561 elif rev is None:
558 raise error.CommandError('debugdata', _('invalid arguments'))
562 raise error.CommandError('debugdata', _('invalid arguments'))
559 r = cmdutil.openstorage(repo, 'debugdata', file_, opts)
563 r = cmdutil.openstorage(repo, 'debugdata', file_, opts)
560 try:
564 try:
561 ui.write(r.revision(r.lookup(rev), raw=True))
565 ui.write(r.revision(r.lookup(rev), raw=True))
562 except KeyError:
566 except KeyError:
563 raise error.Abort(_('invalid revision identifier %s') % rev)
567 raise error.Abort(_('invalid revision identifier %s') % rev)
564
568
565 @command('debugdate',
569 @command('debugdate',
566 [('e', 'extended', None, _('try extended date formats'))],
570 [('e', 'extended', None, _('try extended date formats'))],
567 _('[-e] DATE [RANGE]'),
571 _('[-e] DATE [RANGE]'),
568 norepo=True, optionalrepo=True)
572 norepo=True, optionalrepo=True)
569 def debugdate(ui, date, range=None, **opts):
573 def debugdate(ui, date, range=None, **opts):
570 """parse and display a date"""
574 """parse and display a date"""
571 if opts[r"extended"]:
575 if opts[r"extended"]:
572 d = dateutil.parsedate(date, util.extendeddateformats)
576 d = dateutil.parsedate(date, util.extendeddateformats)
573 else:
577 else:
574 d = dateutil.parsedate(date)
578 d = dateutil.parsedate(date)
575 ui.write(("internal: %d %d\n") % d)
579 ui.write(("internal: %d %d\n") % d)
576 ui.write(("standard: %s\n") % dateutil.datestr(d))
580 ui.write(("standard: %s\n") % dateutil.datestr(d))
577 if range:
581 if range:
578 m = dateutil.matchdate(range)
582 m = dateutil.matchdate(range)
579 ui.write(("match: %s\n") % m(d[0]))
583 ui.write(("match: %s\n") % m(d[0]))
580
584
581 @command('debugdeltachain',
585 @command('debugdeltachain',
582 cmdutil.debugrevlogopts + cmdutil.formatteropts,
586 cmdutil.debugrevlogopts + cmdutil.formatteropts,
583 _('-c|-m|FILE'),
587 _('-c|-m|FILE'),
584 optionalrepo=True)
588 optionalrepo=True)
585 def debugdeltachain(ui, repo, file_=None, **opts):
589 def debugdeltachain(ui, repo, file_=None, **opts):
586 """dump information about delta chains in a revlog
590 """dump information about delta chains in a revlog
587
591
588 Output can be templatized. Available template keywords are:
592 Output can be templatized. Available template keywords are:
589
593
590 :``rev``: revision number
594 :``rev``: revision number
591 :``chainid``: delta chain identifier (numbered by unique base)
595 :``chainid``: delta chain identifier (numbered by unique base)
592 :``chainlen``: delta chain length to this revision
596 :``chainlen``: delta chain length to this revision
593 :``prevrev``: previous revision in delta chain
597 :``prevrev``: previous revision in delta chain
594 :``deltatype``: role of delta / how it was computed
598 :``deltatype``: role of delta / how it was computed
595 :``compsize``: compressed size of revision
599 :``compsize``: compressed size of revision
596 :``uncompsize``: uncompressed size of revision
600 :``uncompsize``: uncompressed size of revision
597 :``chainsize``: total size of compressed revisions in chain
601 :``chainsize``: total size of compressed revisions in chain
598 :``chainratio``: total chain size divided by uncompressed revision size
602 :``chainratio``: total chain size divided by uncompressed revision size
599 (new delta chains typically start at ratio 2.00)
603 (new delta chains typically start at ratio 2.00)
600 :``lindist``: linear distance from base revision in delta chain to end
604 :``lindist``: linear distance from base revision in delta chain to end
601 of this revision
605 of this revision
602 :``extradist``: total size of revisions not part of this delta chain from
606 :``extradist``: total size of revisions not part of this delta chain from
603 base of delta chain to end of this revision; a measurement
607 base of delta chain to end of this revision; a measurement
604 of how much extra data we need to read/seek across to read
608 of how much extra data we need to read/seek across to read
605 the delta chain for this revision
609 the delta chain for this revision
606 :``extraratio``: extradist divided by chainsize; another representation of
610 :``extraratio``: extradist divided by chainsize; another representation of
607 how much unrelated data is needed to load this delta chain
611 how much unrelated data is needed to load this delta chain
608
612
609 If the repository is configured to use the sparse read, additional keywords
613 If the repository is configured to use the sparse read, additional keywords
610 are available:
614 are available:
611
615
612 :``readsize``: total size of data read from the disk for a revision
616 :``readsize``: total size of data read from the disk for a revision
613 (sum of the sizes of all the blocks)
617 (sum of the sizes of all the blocks)
614 :``largestblock``: size of the largest block of data read from the disk
618 :``largestblock``: size of the largest block of data read from the disk
615 :``readdensity``: density of useful bytes in the data read from the disk
619 :``readdensity``: density of useful bytes in the data read from the disk
616 :``srchunks``: in how many data hunks the whole revision would be read
620 :``srchunks``: in how many data hunks the whole revision would be read
617
621
618 The sparse read can be enabled with experimental.sparse-read = True
622 The sparse read can be enabled with experimental.sparse-read = True
619 """
623 """
620 opts = pycompat.byteskwargs(opts)
624 opts = pycompat.byteskwargs(opts)
621 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
625 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
622 index = r.index
626 index = r.index
623 start = r.start
627 start = r.start
624 length = r.length
628 length = r.length
625 generaldelta = r.version & revlog.FLAG_GENERALDELTA
629 generaldelta = r.version & revlog.FLAG_GENERALDELTA
626 withsparseread = getattr(r, '_withsparseread', False)
630 withsparseread = getattr(r, '_withsparseread', False)
627
631
628 def revinfo(rev):
632 def revinfo(rev):
629 e = index[rev]
633 e = index[rev]
630 compsize = e[1]
634 compsize = e[1]
631 uncompsize = e[2]
635 uncompsize = e[2]
632 chainsize = 0
636 chainsize = 0
633
637
634 if generaldelta:
638 if generaldelta:
635 if e[3] == e[5]:
639 if e[3] == e[5]:
636 deltatype = 'p1'
640 deltatype = 'p1'
637 elif e[3] == e[6]:
641 elif e[3] == e[6]:
638 deltatype = 'p2'
642 deltatype = 'p2'
639 elif e[3] == rev - 1:
643 elif e[3] == rev - 1:
640 deltatype = 'prev'
644 deltatype = 'prev'
641 elif e[3] == rev:
645 elif e[3] == rev:
642 deltatype = 'base'
646 deltatype = 'base'
643 else:
647 else:
644 deltatype = 'other'
648 deltatype = 'other'
645 else:
649 else:
646 if e[3] == rev:
650 if e[3] == rev:
647 deltatype = 'base'
651 deltatype = 'base'
648 else:
652 else:
649 deltatype = 'prev'
653 deltatype = 'prev'
650
654
651 chain = r._deltachain(rev)[0]
655 chain = r._deltachain(rev)[0]
652 for iterrev in chain:
656 for iterrev in chain:
653 e = index[iterrev]
657 e = index[iterrev]
654 chainsize += e[1]
658 chainsize += e[1]
655
659
656 return compsize, uncompsize, deltatype, chain, chainsize
660 return compsize, uncompsize, deltatype, chain, chainsize
657
661
658 fm = ui.formatter('debugdeltachain', opts)
662 fm = ui.formatter('debugdeltachain', opts)
659
663
660 fm.plain(' rev chain# chainlen prev delta '
664 fm.plain(' rev chain# chainlen prev delta '
661 'size rawsize chainsize ratio lindist extradist '
665 'size rawsize chainsize ratio lindist extradist '
662 'extraratio')
666 'extraratio')
663 if withsparseread:
667 if withsparseread:
664 fm.plain(' readsize largestblk rddensity srchunks')
668 fm.plain(' readsize largestblk rddensity srchunks')
665 fm.plain('\n')
669 fm.plain('\n')
666
670
667 chainbases = {}
671 chainbases = {}
668 for rev in r:
672 for rev in r:
669 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
673 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
670 chainbase = chain[0]
674 chainbase = chain[0]
671 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
675 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
672 basestart = start(chainbase)
676 basestart = start(chainbase)
673 revstart = start(rev)
677 revstart = start(rev)
674 lineardist = revstart + comp - basestart
678 lineardist = revstart + comp - basestart
675 extradist = lineardist - chainsize
679 extradist = lineardist - chainsize
676 try:
680 try:
677 prevrev = chain[-2]
681 prevrev = chain[-2]
678 except IndexError:
682 except IndexError:
679 prevrev = -1
683 prevrev = -1
680
684
681 if uncomp != 0:
685 if uncomp != 0:
682 chainratio = float(chainsize) / float(uncomp)
686 chainratio = float(chainsize) / float(uncomp)
683 else:
687 else:
684 chainratio = chainsize
688 chainratio = chainsize
685
689
686 if chainsize != 0:
690 if chainsize != 0:
687 extraratio = float(extradist) / float(chainsize)
691 extraratio = float(extradist) / float(chainsize)
688 else:
692 else:
689 extraratio = extradist
693 extraratio = extradist
690
694
691 fm.startitem()
695 fm.startitem()
692 fm.write('rev chainid chainlen prevrev deltatype compsize '
696 fm.write('rev chainid chainlen prevrev deltatype compsize '
693 'uncompsize chainsize chainratio lindist extradist '
697 'uncompsize chainsize chainratio lindist extradist '
694 'extraratio',
698 'extraratio',
695 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
699 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
696 rev, chainid, len(chain), prevrev, deltatype, comp,
700 rev, chainid, len(chain), prevrev, deltatype, comp,
697 uncomp, chainsize, chainratio, lineardist, extradist,
701 uncomp, chainsize, chainratio, lineardist, extradist,
698 extraratio,
702 extraratio,
699 rev=rev, chainid=chainid, chainlen=len(chain),
703 rev=rev, chainid=chainid, chainlen=len(chain),
700 prevrev=prevrev, deltatype=deltatype, compsize=comp,
704 prevrev=prevrev, deltatype=deltatype, compsize=comp,
701 uncompsize=uncomp, chainsize=chainsize,
705 uncompsize=uncomp, chainsize=chainsize,
702 chainratio=chainratio, lindist=lineardist,
706 chainratio=chainratio, lindist=lineardist,
703 extradist=extradist, extraratio=extraratio)
707 extradist=extradist, extraratio=extraratio)
704 if withsparseread:
708 if withsparseread:
705 readsize = 0
709 readsize = 0
706 largestblock = 0
710 largestblock = 0
707 srchunks = 0
711 srchunks = 0
708
712
709 for revschunk in revlog._slicechunk(r, chain):
713 for revschunk in deltautil.slicechunk(r, chain):
710 srchunks += 1
714 srchunks += 1
711 blkend = start(revschunk[-1]) + length(revschunk[-1])
715 blkend = start(revschunk[-1]) + length(revschunk[-1])
712 blksize = blkend - start(revschunk[0])
716 blksize = blkend - start(revschunk[0])
713
717
714 readsize += blksize
718 readsize += blksize
715 if largestblock < blksize:
719 if largestblock < blksize:
716 largestblock = blksize
720 largestblock = blksize
717
721
718 if readsize:
722 if readsize:
719 readdensity = float(chainsize) / float(readsize)
723 readdensity = float(chainsize) / float(readsize)
720 else:
724 else:
721 readdensity = 1
725 readdensity = 1
722
726
723 fm.write('readsize largestblock readdensity srchunks',
727 fm.write('readsize largestblock readdensity srchunks',
724 ' %10d %10d %9.5f %8d',
728 ' %10d %10d %9.5f %8d',
725 readsize, largestblock, readdensity, srchunks,
729 readsize, largestblock, readdensity, srchunks,
726 readsize=readsize, largestblock=largestblock,
730 readsize=readsize, largestblock=largestblock,
727 readdensity=readdensity, srchunks=srchunks)
731 readdensity=readdensity, srchunks=srchunks)
728
732
729 fm.plain('\n')
733 fm.plain('\n')
730
734
731 fm.end()
735 fm.end()
732
736
733 @command('debugdirstate|debugstate',
737 @command('debugdirstate|debugstate',
734 [('', 'nodates', None, _('do not display the saved mtime')),
738 [('', 'nodates', None, _('do not display the saved mtime')),
735 ('', 'datesort', None, _('sort by saved mtime'))],
739 ('', 'datesort', None, _('sort by saved mtime'))],
736 _('[OPTION]...'))
740 _('[OPTION]...'))
737 def debugstate(ui, repo, **opts):
741 def debugstate(ui, repo, **opts):
738 """show the contents of the current dirstate"""
742 """show the contents of the current dirstate"""
739
743
740 nodates = opts.get(r'nodates')
744 nodates = opts.get(r'nodates')
741 datesort = opts.get(r'datesort')
745 datesort = opts.get(r'datesort')
742
746
743 timestr = ""
747 timestr = ""
744 if datesort:
748 if datesort:
745 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
749 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
746 else:
750 else:
747 keyfunc = None # sort by filename
751 keyfunc = None # sort by filename
748 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
752 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
749 if ent[3] == -1:
753 if ent[3] == -1:
750 timestr = 'unset '
754 timestr = 'unset '
751 elif nodates:
755 elif nodates:
752 timestr = 'set '
756 timestr = 'set '
753 else:
757 else:
754 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
758 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
755 time.localtime(ent[3]))
759 time.localtime(ent[3]))
756 timestr = encoding.strtolocal(timestr)
760 timestr = encoding.strtolocal(timestr)
757 if ent[1] & 0o20000:
761 if ent[1] & 0o20000:
758 mode = 'lnk'
762 mode = 'lnk'
759 else:
763 else:
760 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
764 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
761 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
765 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
762 for f in repo.dirstate.copies():
766 for f in repo.dirstate.copies():
763 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
767 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
764
768
765 @command('debugdiscovery',
769 @command('debugdiscovery',
766 [('', 'old', None, _('use old-style discovery')),
770 [('', 'old', None, _('use old-style discovery')),
767 ('', 'nonheads', None,
771 ('', 'nonheads', None,
768 _('use old-style discovery with non-heads included')),
772 _('use old-style discovery with non-heads included')),
769 ('', 'rev', [], 'restrict discovery to this set of revs'),
773 ('', 'rev', [], 'restrict discovery to this set of revs'),
770 ] + cmdutil.remoteopts,
774 ] + cmdutil.remoteopts,
771 _('[--rev REV] [OTHER]'))
775 _('[--rev REV] [OTHER]'))
772 def debugdiscovery(ui, repo, remoteurl="default", **opts):
776 def debugdiscovery(ui, repo, remoteurl="default", **opts):
773 """runs the changeset discovery protocol in isolation"""
777 """runs the changeset discovery protocol in isolation"""
774 opts = pycompat.byteskwargs(opts)
778 opts = pycompat.byteskwargs(opts)
775 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
779 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
776 remote = hg.peer(repo, opts, remoteurl)
780 remote = hg.peer(repo, opts, remoteurl)
777 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
781 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
778
782
779 # make sure tests are repeatable
783 # make sure tests are repeatable
780 random.seed(12323)
784 random.seed(12323)
781
785
782 def doit(pushedrevs, remoteheads, remote=remote):
786 def doit(pushedrevs, remoteheads, remote=remote):
783 if opts.get('old'):
787 if opts.get('old'):
784 if not util.safehasattr(remote, 'branches'):
788 if not util.safehasattr(remote, 'branches'):
785 # enable in-client legacy support
789 # enable in-client legacy support
786 remote = localrepo.locallegacypeer(remote.local())
790 remote = localrepo.locallegacypeer(remote.local())
787 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
791 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
788 force=True)
792 force=True)
789 common = set(common)
793 common = set(common)
790 if not opts.get('nonheads'):
794 if not opts.get('nonheads'):
791 ui.write(("unpruned common: %s\n") %
795 ui.write(("unpruned common: %s\n") %
792 " ".join(sorted(short(n) for n in common)))
796 " ".join(sorted(short(n) for n in common)))
793
797
794 clnode = repo.changelog.node
798 clnode = repo.changelog.node
795 common = repo.revs('heads(::%ln)', common)
799 common = repo.revs('heads(::%ln)', common)
796 common = {clnode(r) for r in common}
800 common = {clnode(r) for r in common}
797 else:
801 else:
798 nodes = None
802 nodes = None
799 if pushedrevs:
803 if pushedrevs:
800 revs = scmutil.revrange(repo, pushedrevs)
804 revs = scmutil.revrange(repo, pushedrevs)
801 nodes = [repo[r].node() for r in revs]
805 nodes = [repo[r].node() for r in revs]
802 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
806 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
803 ancestorsof=nodes)
807 ancestorsof=nodes)
804 common = set(common)
808 common = set(common)
805 rheads = set(hds)
809 rheads = set(hds)
806 lheads = set(repo.heads())
810 lheads = set(repo.heads())
807 ui.write(("common heads: %s\n") %
811 ui.write(("common heads: %s\n") %
808 " ".join(sorted(short(n) for n in common)))
812 " ".join(sorted(short(n) for n in common)))
809 if lheads <= common:
813 if lheads <= common:
810 ui.write(("local is subset\n"))
814 ui.write(("local is subset\n"))
811 elif rheads <= common:
815 elif rheads <= common:
812 ui.write(("remote is subset\n"))
816 ui.write(("remote is subset\n"))
813
817
814 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
818 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
815 localrevs = opts['rev']
819 localrevs = opts['rev']
816 doit(localrevs, remoterevs)
820 doit(localrevs, remoterevs)
817
821
818 _chunksize = 4 << 10
822 _chunksize = 4 << 10
819
823
820 @command('debugdownload',
824 @command('debugdownload',
821 [
825 [
822 ('o', 'output', '', _('path')),
826 ('o', 'output', '', _('path')),
823 ],
827 ],
824 optionalrepo=True)
828 optionalrepo=True)
825 def debugdownload(ui, repo, url, output=None, **opts):
829 def debugdownload(ui, repo, url, output=None, **opts):
826 """download a resource using Mercurial logic and config
830 """download a resource using Mercurial logic and config
827 """
831 """
828 fh = urlmod.open(ui, url, output)
832 fh = urlmod.open(ui, url, output)
829
833
830 dest = ui
834 dest = ui
831 if output:
835 if output:
832 dest = open(output, "wb", _chunksize)
836 dest = open(output, "wb", _chunksize)
833 try:
837 try:
834 data = fh.read(_chunksize)
838 data = fh.read(_chunksize)
835 while data:
839 while data:
836 dest.write(data)
840 dest.write(data)
837 data = fh.read(_chunksize)
841 data = fh.read(_chunksize)
838 finally:
842 finally:
839 if output:
843 if output:
840 dest.close()
844 dest.close()
841
845
842 @command('debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
846 @command('debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
843 def debugextensions(ui, repo, **opts):
847 def debugextensions(ui, repo, **opts):
844 '''show information about active extensions'''
848 '''show information about active extensions'''
845 opts = pycompat.byteskwargs(opts)
849 opts = pycompat.byteskwargs(opts)
846 exts = extensions.extensions(ui)
850 exts = extensions.extensions(ui)
847 hgver = util.version()
851 hgver = util.version()
848 fm = ui.formatter('debugextensions', opts)
852 fm = ui.formatter('debugextensions', opts)
849 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
853 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
850 isinternal = extensions.ismoduleinternal(extmod)
854 isinternal = extensions.ismoduleinternal(extmod)
851 extsource = pycompat.fsencode(extmod.__file__)
855 extsource = pycompat.fsencode(extmod.__file__)
852 if isinternal:
856 if isinternal:
853 exttestedwith = [] # never expose magic string to users
857 exttestedwith = [] # never expose magic string to users
854 else:
858 else:
855 exttestedwith = getattr(extmod, 'testedwith', '').split()
859 exttestedwith = getattr(extmod, 'testedwith', '').split()
856 extbuglink = getattr(extmod, 'buglink', None)
860 extbuglink = getattr(extmod, 'buglink', None)
857
861
858 fm.startitem()
862 fm.startitem()
859
863
860 if ui.quiet or ui.verbose:
864 if ui.quiet or ui.verbose:
861 fm.write('name', '%s\n', extname)
865 fm.write('name', '%s\n', extname)
862 else:
866 else:
863 fm.write('name', '%s', extname)
867 fm.write('name', '%s', extname)
864 if isinternal or hgver in exttestedwith:
868 if isinternal or hgver in exttestedwith:
865 fm.plain('\n')
869 fm.plain('\n')
866 elif not exttestedwith:
870 elif not exttestedwith:
867 fm.plain(_(' (untested!)\n'))
871 fm.plain(_(' (untested!)\n'))
868 else:
872 else:
869 lasttestedversion = exttestedwith[-1]
873 lasttestedversion = exttestedwith[-1]
870 fm.plain(' (%s!)\n' % lasttestedversion)
874 fm.plain(' (%s!)\n' % lasttestedversion)
871
875
872 fm.condwrite(ui.verbose and extsource, 'source',
876 fm.condwrite(ui.verbose and extsource, 'source',
873 _(' location: %s\n'), extsource or "")
877 _(' location: %s\n'), extsource or "")
874
878
875 if ui.verbose:
879 if ui.verbose:
876 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
880 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
877 fm.data(bundled=isinternal)
881 fm.data(bundled=isinternal)
878
882
879 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
883 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
880 _(' tested with: %s\n'),
884 _(' tested with: %s\n'),
881 fm.formatlist(exttestedwith, name='ver'))
885 fm.formatlist(exttestedwith, name='ver'))
882
886
883 fm.condwrite(ui.verbose and extbuglink, 'buglink',
887 fm.condwrite(ui.verbose and extbuglink, 'buglink',
884 _(' bug reporting: %s\n'), extbuglink or "")
888 _(' bug reporting: %s\n'), extbuglink or "")
885
889
886 fm.end()
890 fm.end()
887
891
888 @command('debugfileset',
892 @command('debugfileset',
889 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV')),
893 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV')),
890 ('', 'all-files', False,
894 ('', 'all-files', False,
891 _('test files from all revisions and working directory')),
895 _('test files from all revisions and working directory')),
892 ('s', 'show-matcher', None,
896 ('s', 'show-matcher', None,
893 _('print internal representation of matcher')),
897 _('print internal representation of matcher')),
894 ('p', 'show-stage', [],
898 ('p', 'show-stage', [],
895 _('print parsed tree at the given stage'), _('NAME'))],
899 _('print parsed tree at the given stage'), _('NAME'))],
896 _('[-r REV] [--all-files] [OPTION]... FILESPEC'))
900 _('[-r REV] [--all-files] [OPTION]... FILESPEC'))
897 def debugfileset(ui, repo, expr, **opts):
901 def debugfileset(ui, repo, expr, **opts):
898 '''parse and apply a fileset specification'''
902 '''parse and apply a fileset specification'''
899 from . import fileset
903 from . import fileset
900 fileset.symbols # force import of fileset so we have predicates to optimize
904 fileset.symbols # force import of fileset so we have predicates to optimize
901 opts = pycompat.byteskwargs(opts)
905 opts = pycompat.byteskwargs(opts)
902 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
906 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
903
907
904 stages = [
908 stages = [
905 ('parsed', pycompat.identity),
909 ('parsed', pycompat.identity),
906 ('analyzed', filesetlang.analyze),
910 ('analyzed', filesetlang.analyze),
907 ('optimized', filesetlang.optimize),
911 ('optimized', filesetlang.optimize),
908 ]
912 ]
909 stagenames = set(n for n, f in stages)
913 stagenames = set(n for n, f in stages)
910
914
911 showalways = set()
915 showalways = set()
912 if ui.verbose and not opts['show_stage']:
916 if ui.verbose and not opts['show_stage']:
913 # show parsed tree by --verbose (deprecated)
917 # show parsed tree by --verbose (deprecated)
914 showalways.add('parsed')
918 showalways.add('parsed')
915 if opts['show_stage'] == ['all']:
919 if opts['show_stage'] == ['all']:
916 showalways.update(stagenames)
920 showalways.update(stagenames)
917 else:
921 else:
918 for n in opts['show_stage']:
922 for n in opts['show_stage']:
919 if n not in stagenames:
923 if n not in stagenames:
920 raise error.Abort(_('invalid stage name: %s') % n)
924 raise error.Abort(_('invalid stage name: %s') % n)
921 showalways.update(opts['show_stage'])
925 showalways.update(opts['show_stage'])
922
926
923 tree = filesetlang.parse(expr)
927 tree = filesetlang.parse(expr)
924 for n, f in stages:
928 for n, f in stages:
925 tree = f(tree)
929 tree = f(tree)
926 if n in showalways:
930 if n in showalways:
927 if opts['show_stage'] or n != 'parsed':
931 if opts['show_stage'] or n != 'parsed':
928 ui.write(("* %s:\n") % n)
932 ui.write(("* %s:\n") % n)
929 ui.write(filesetlang.prettyformat(tree), "\n")
933 ui.write(filesetlang.prettyformat(tree), "\n")
930
934
931 files = set()
935 files = set()
932 if opts['all_files']:
936 if opts['all_files']:
933 for r in repo:
937 for r in repo:
934 c = repo[r]
938 c = repo[r]
935 files.update(c.files())
939 files.update(c.files())
936 files.update(c.substate)
940 files.update(c.substate)
937 if opts['all_files'] or ctx.rev() is None:
941 if opts['all_files'] or ctx.rev() is None:
938 wctx = repo[None]
942 wctx = repo[None]
939 files.update(repo.dirstate.walk(scmutil.matchall(repo),
943 files.update(repo.dirstate.walk(scmutil.matchall(repo),
940 subrepos=list(wctx.substate),
944 subrepos=list(wctx.substate),
941 unknown=True, ignored=True))
945 unknown=True, ignored=True))
942 files.update(wctx.substate)
946 files.update(wctx.substate)
943 else:
947 else:
944 files.update(ctx.files())
948 files.update(ctx.files())
945 files.update(ctx.substate)
949 files.update(ctx.substate)
946
950
947 m = ctx.matchfileset(expr)
951 m = ctx.matchfileset(expr)
948 if opts['show_matcher'] or (opts['show_matcher'] is None and ui.verbose):
952 if opts['show_matcher'] or (opts['show_matcher'] is None and ui.verbose):
949 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
953 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
950 for f in sorted(files):
954 for f in sorted(files):
951 if not m(f):
955 if not m(f):
952 continue
956 continue
953 ui.write("%s\n" % f)
957 ui.write("%s\n" % f)
954
958
955 @command('debugformat',
959 @command('debugformat',
956 [] + cmdutil.formatteropts)
960 [] + cmdutil.formatteropts)
957 def debugformat(ui, repo, **opts):
961 def debugformat(ui, repo, **opts):
958 """display format information about the current repository
962 """display format information about the current repository
959
963
960 Use --verbose to get extra information about current config value and
964 Use --verbose to get extra information about current config value and
961 Mercurial default."""
965 Mercurial default."""
962 opts = pycompat.byteskwargs(opts)
966 opts = pycompat.byteskwargs(opts)
963 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
967 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
964 maxvariantlength = max(len('format-variant'), maxvariantlength)
968 maxvariantlength = max(len('format-variant'), maxvariantlength)
965
969
966 def makeformatname(name):
970 def makeformatname(name):
967 return '%s:' + (' ' * (maxvariantlength - len(name)))
971 return '%s:' + (' ' * (maxvariantlength - len(name)))
968
972
969 fm = ui.formatter('debugformat', opts)
973 fm = ui.formatter('debugformat', opts)
970 if fm.isplain():
974 if fm.isplain():
971 def formatvalue(value):
975 def formatvalue(value):
972 if util.safehasattr(value, 'startswith'):
976 if util.safehasattr(value, 'startswith'):
973 return value
977 return value
974 if value:
978 if value:
975 return 'yes'
979 return 'yes'
976 else:
980 else:
977 return 'no'
981 return 'no'
978 else:
982 else:
979 formatvalue = pycompat.identity
983 formatvalue = pycompat.identity
980
984
981 fm.plain('format-variant')
985 fm.plain('format-variant')
982 fm.plain(' ' * (maxvariantlength - len('format-variant')))
986 fm.plain(' ' * (maxvariantlength - len('format-variant')))
983 fm.plain(' repo')
987 fm.plain(' repo')
984 if ui.verbose:
988 if ui.verbose:
985 fm.plain(' config default')
989 fm.plain(' config default')
986 fm.plain('\n')
990 fm.plain('\n')
987 for fv in upgrade.allformatvariant:
991 for fv in upgrade.allformatvariant:
988 fm.startitem()
992 fm.startitem()
989 repovalue = fv.fromrepo(repo)
993 repovalue = fv.fromrepo(repo)
990 configvalue = fv.fromconfig(repo)
994 configvalue = fv.fromconfig(repo)
991
995
992 if repovalue != configvalue:
996 if repovalue != configvalue:
993 namelabel = 'formatvariant.name.mismatchconfig'
997 namelabel = 'formatvariant.name.mismatchconfig'
994 repolabel = 'formatvariant.repo.mismatchconfig'
998 repolabel = 'formatvariant.repo.mismatchconfig'
995 elif repovalue != fv.default:
999 elif repovalue != fv.default:
996 namelabel = 'formatvariant.name.mismatchdefault'
1000 namelabel = 'formatvariant.name.mismatchdefault'
997 repolabel = 'formatvariant.repo.mismatchdefault'
1001 repolabel = 'formatvariant.repo.mismatchdefault'
998 else:
1002 else:
999 namelabel = 'formatvariant.name.uptodate'
1003 namelabel = 'formatvariant.name.uptodate'
1000 repolabel = 'formatvariant.repo.uptodate'
1004 repolabel = 'formatvariant.repo.uptodate'
1001
1005
1002 fm.write('name', makeformatname(fv.name), fv.name,
1006 fm.write('name', makeformatname(fv.name), fv.name,
1003 label=namelabel)
1007 label=namelabel)
1004 fm.write('repo', ' %3s', formatvalue(repovalue),
1008 fm.write('repo', ' %3s', formatvalue(repovalue),
1005 label=repolabel)
1009 label=repolabel)
1006 if fv.default != configvalue:
1010 if fv.default != configvalue:
1007 configlabel = 'formatvariant.config.special'
1011 configlabel = 'formatvariant.config.special'
1008 else:
1012 else:
1009 configlabel = 'formatvariant.config.default'
1013 configlabel = 'formatvariant.config.default'
1010 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
1014 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
1011 label=configlabel)
1015 label=configlabel)
1012 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
1016 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
1013 label='formatvariant.default')
1017 label='formatvariant.default')
1014 fm.plain('\n')
1018 fm.plain('\n')
1015 fm.end()
1019 fm.end()
1016
1020
1017 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
1021 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
1018 def debugfsinfo(ui, path="."):
1022 def debugfsinfo(ui, path="."):
1019 """show information detected about current filesystem"""
1023 """show information detected about current filesystem"""
1020 ui.write(('path: %s\n') % path)
1024 ui.write(('path: %s\n') % path)
1021 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
1025 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
1022 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1026 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1023 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
1027 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
1024 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1028 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1025 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
1029 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
1026 casesensitive = '(unknown)'
1030 casesensitive = '(unknown)'
1027 try:
1031 try:
1028 with pycompat.namedtempfile(prefix='.debugfsinfo', dir=path) as f:
1032 with pycompat.namedtempfile(prefix='.debugfsinfo', dir=path) as f:
1029 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
1033 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
1030 except OSError:
1034 except OSError:
1031 pass
1035 pass
1032 ui.write(('case-sensitive: %s\n') % casesensitive)
1036 ui.write(('case-sensitive: %s\n') % casesensitive)
1033
1037
1034 @command('debuggetbundle',
1038 @command('debuggetbundle',
1035 [('H', 'head', [], _('id of head node'), _('ID')),
1039 [('H', 'head', [], _('id of head node'), _('ID')),
1036 ('C', 'common', [], _('id of common node'), _('ID')),
1040 ('C', 'common', [], _('id of common node'), _('ID')),
1037 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1041 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1038 _('REPO FILE [-H|-C ID]...'),
1042 _('REPO FILE [-H|-C ID]...'),
1039 norepo=True)
1043 norepo=True)
1040 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1044 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1041 """retrieves a bundle from a repo
1045 """retrieves a bundle from a repo
1042
1046
1043 Every ID must be a full-length hex node id string. Saves the bundle to the
1047 Every ID must be a full-length hex node id string. Saves the bundle to the
1044 given file.
1048 given file.
1045 """
1049 """
1046 opts = pycompat.byteskwargs(opts)
1050 opts = pycompat.byteskwargs(opts)
1047 repo = hg.peer(ui, opts, repopath)
1051 repo = hg.peer(ui, opts, repopath)
1048 if not repo.capable('getbundle'):
1052 if not repo.capable('getbundle'):
1049 raise error.Abort("getbundle() not supported by target repository")
1053 raise error.Abort("getbundle() not supported by target repository")
1050 args = {}
1054 args = {}
1051 if common:
1055 if common:
1052 args[r'common'] = [bin(s) for s in common]
1056 args[r'common'] = [bin(s) for s in common]
1053 if head:
1057 if head:
1054 args[r'heads'] = [bin(s) for s in head]
1058 args[r'heads'] = [bin(s) for s in head]
1055 # TODO: get desired bundlecaps from command line.
1059 # TODO: get desired bundlecaps from command line.
1056 args[r'bundlecaps'] = None
1060 args[r'bundlecaps'] = None
1057 bundle = repo.getbundle('debug', **args)
1061 bundle = repo.getbundle('debug', **args)
1058
1062
1059 bundletype = opts.get('type', 'bzip2').lower()
1063 bundletype = opts.get('type', 'bzip2').lower()
1060 btypes = {'none': 'HG10UN',
1064 btypes = {'none': 'HG10UN',
1061 'bzip2': 'HG10BZ',
1065 'bzip2': 'HG10BZ',
1062 'gzip': 'HG10GZ',
1066 'gzip': 'HG10GZ',
1063 'bundle2': 'HG20'}
1067 'bundle2': 'HG20'}
1064 bundletype = btypes.get(bundletype)
1068 bundletype = btypes.get(bundletype)
1065 if bundletype not in bundle2.bundletypes:
1069 if bundletype not in bundle2.bundletypes:
1066 raise error.Abort(_('unknown bundle type specified with --type'))
1070 raise error.Abort(_('unknown bundle type specified with --type'))
1067 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1071 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1068
1072
1069 @command('debugignore', [], '[FILE]')
1073 @command('debugignore', [], '[FILE]')
1070 def debugignore(ui, repo, *files, **opts):
1074 def debugignore(ui, repo, *files, **opts):
1071 """display the combined ignore pattern and information about ignored files
1075 """display the combined ignore pattern and information about ignored files
1072
1076
1073 With no argument display the combined ignore pattern.
1077 With no argument display the combined ignore pattern.
1074
1078
1075 Given space separated file names, shows if the given file is ignored and
1079 Given space separated file names, shows if the given file is ignored and
1076 if so, show the ignore rule (file and line number) that matched it.
1080 if so, show the ignore rule (file and line number) that matched it.
1077 """
1081 """
1078 ignore = repo.dirstate._ignore
1082 ignore = repo.dirstate._ignore
1079 if not files:
1083 if not files:
1080 # Show all the patterns
1084 # Show all the patterns
1081 ui.write("%s\n" % pycompat.byterepr(ignore))
1085 ui.write("%s\n" % pycompat.byterepr(ignore))
1082 else:
1086 else:
1083 m = scmutil.match(repo[None], pats=files)
1087 m = scmutil.match(repo[None], pats=files)
1084 for f in m.files():
1088 for f in m.files():
1085 nf = util.normpath(f)
1089 nf = util.normpath(f)
1086 ignored = None
1090 ignored = None
1087 ignoredata = None
1091 ignoredata = None
1088 if nf != '.':
1092 if nf != '.':
1089 if ignore(nf):
1093 if ignore(nf):
1090 ignored = nf
1094 ignored = nf
1091 ignoredata = repo.dirstate._ignorefileandline(nf)
1095 ignoredata = repo.dirstate._ignorefileandline(nf)
1092 else:
1096 else:
1093 for p in util.finddirs(nf):
1097 for p in util.finddirs(nf):
1094 if ignore(p):
1098 if ignore(p):
1095 ignored = p
1099 ignored = p
1096 ignoredata = repo.dirstate._ignorefileandline(p)
1100 ignoredata = repo.dirstate._ignorefileandline(p)
1097 break
1101 break
1098 if ignored:
1102 if ignored:
1099 if ignored == nf:
1103 if ignored == nf:
1100 ui.write(_("%s is ignored\n") % m.uipath(f))
1104 ui.write(_("%s is ignored\n") % m.uipath(f))
1101 else:
1105 else:
1102 ui.write(_("%s is ignored because of "
1106 ui.write(_("%s is ignored because of "
1103 "containing folder %s\n")
1107 "containing folder %s\n")
1104 % (m.uipath(f), ignored))
1108 % (m.uipath(f), ignored))
1105 ignorefile, lineno, line = ignoredata
1109 ignorefile, lineno, line = ignoredata
1106 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1110 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1107 % (ignorefile, lineno, line))
1111 % (ignorefile, lineno, line))
1108 else:
1112 else:
1109 ui.write(_("%s is not ignored\n") % m.uipath(f))
1113 ui.write(_("%s is not ignored\n") % m.uipath(f))
1110
1114
1111 @command('debugindex', cmdutil.debugrevlogopts + cmdutil.formatteropts,
1115 @command('debugindex', cmdutil.debugrevlogopts + cmdutil.formatteropts,
1112 _('-c|-m|FILE'))
1116 _('-c|-m|FILE'))
1113 def debugindex(ui, repo, file_=None, **opts):
1117 def debugindex(ui, repo, file_=None, **opts):
1114 """dump index data for a storage primitive"""
1118 """dump index data for a storage primitive"""
1115 opts = pycompat.byteskwargs(opts)
1119 opts = pycompat.byteskwargs(opts)
1116 store = cmdutil.openstorage(repo, 'debugindex', file_, opts)
1120 store = cmdutil.openstorage(repo, 'debugindex', file_, opts)
1117
1121
1118 if ui.debugflag:
1122 if ui.debugflag:
1119 shortfn = hex
1123 shortfn = hex
1120 else:
1124 else:
1121 shortfn = short
1125 shortfn = short
1122
1126
1123 idlen = 12
1127 idlen = 12
1124 for i in store:
1128 for i in store:
1125 idlen = len(shortfn(store.node(i)))
1129 idlen = len(shortfn(store.node(i)))
1126 break
1130 break
1127
1131
1128 fm = ui.formatter('debugindex', opts)
1132 fm = ui.formatter('debugindex', opts)
1129 fm.plain(b' rev linkrev %s %s p2\n' % (
1133 fm.plain(b' rev linkrev %s %s p2\n' % (
1130 b'nodeid'.ljust(idlen),
1134 b'nodeid'.ljust(idlen),
1131 b'p1'.ljust(idlen)))
1135 b'p1'.ljust(idlen)))
1132
1136
1133 for rev in store:
1137 for rev in store:
1134 node = store.node(rev)
1138 node = store.node(rev)
1135 parents = store.parents(node)
1139 parents = store.parents(node)
1136
1140
1137 fm.startitem()
1141 fm.startitem()
1138 fm.write(b'rev', b'%6d ', rev)
1142 fm.write(b'rev', b'%6d ', rev)
1139 fm.write(b'linkrev', '%7d ', store.linkrev(rev))
1143 fm.write(b'linkrev', '%7d ', store.linkrev(rev))
1140 fm.write(b'node', '%s ', shortfn(node))
1144 fm.write(b'node', '%s ', shortfn(node))
1141 fm.write(b'p1', '%s ', shortfn(parents[0]))
1145 fm.write(b'p1', '%s ', shortfn(parents[0]))
1142 fm.write(b'p2', '%s', shortfn(parents[1]))
1146 fm.write(b'p2', '%s', shortfn(parents[1]))
1143 fm.plain(b'\n')
1147 fm.plain(b'\n')
1144
1148
1145 fm.end()
1149 fm.end()
1146
1150
1147 @command('debugindexdot', cmdutil.debugrevlogopts,
1151 @command('debugindexdot', cmdutil.debugrevlogopts,
1148 _('-c|-m|FILE'), optionalrepo=True)
1152 _('-c|-m|FILE'), optionalrepo=True)
1149 def debugindexdot(ui, repo, file_=None, **opts):
1153 def debugindexdot(ui, repo, file_=None, **opts):
1150 """dump an index DAG as a graphviz dot file"""
1154 """dump an index DAG as a graphviz dot file"""
1151 opts = pycompat.byteskwargs(opts)
1155 opts = pycompat.byteskwargs(opts)
1152 r = cmdutil.openstorage(repo, 'debugindexdot', file_, opts)
1156 r = cmdutil.openstorage(repo, 'debugindexdot', file_, opts)
1153 ui.write(("digraph G {\n"))
1157 ui.write(("digraph G {\n"))
1154 for i in r:
1158 for i in r:
1155 node = r.node(i)
1159 node = r.node(i)
1156 pp = r.parents(node)
1160 pp = r.parents(node)
1157 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1161 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1158 if pp[1] != nullid:
1162 if pp[1] != nullid:
1159 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1163 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1160 ui.write("}\n")
1164 ui.write("}\n")
1161
1165
1162 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1166 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1163 def debuginstall(ui, **opts):
1167 def debuginstall(ui, **opts):
1164 '''test Mercurial installation
1168 '''test Mercurial installation
1165
1169
1166 Returns 0 on success.
1170 Returns 0 on success.
1167 '''
1171 '''
1168 opts = pycompat.byteskwargs(opts)
1172 opts = pycompat.byteskwargs(opts)
1169
1173
1170 def writetemp(contents):
1174 def writetemp(contents):
1171 (fd, name) = pycompat.mkstemp(prefix="hg-debuginstall-")
1175 (fd, name) = pycompat.mkstemp(prefix="hg-debuginstall-")
1172 f = os.fdopen(fd, r"wb")
1176 f = os.fdopen(fd, r"wb")
1173 f.write(contents)
1177 f.write(contents)
1174 f.close()
1178 f.close()
1175 return name
1179 return name
1176
1180
1177 problems = 0
1181 problems = 0
1178
1182
1179 fm = ui.formatter('debuginstall', opts)
1183 fm = ui.formatter('debuginstall', opts)
1180 fm.startitem()
1184 fm.startitem()
1181
1185
1182 # encoding
1186 # encoding
1183 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1187 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1184 err = None
1188 err = None
1185 try:
1189 try:
1186 codecs.lookup(pycompat.sysstr(encoding.encoding))
1190 codecs.lookup(pycompat.sysstr(encoding.encoding))
1187 except LookupError as inst:
1191 except LookupError as inst:
1188 err = stringutil.forcebytestr(inst)
1192 err = stringutil.forcebytestr(inst)
1189 problems += 1
1193 problems += 1
1190 fm.condwrite(err, 'encodingerror', _(" %s\n"
1194 fm.condwrite(err, 'encodingerror', _(" %s\n"
1191 " (check that your locale is properly set)\n"), err)
1195 " (check that your locale is properly set)\n"), err)
1192
1196
1193 # Python
1197 # Python
1194 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1198 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1195 pycompat.sysexecutable)
1199 pycompat.sysexecutable)
1196 fm.write('pythonver', _("checking Python version (%s)\n"),
1200 fm.write('pythonver', _("checking Python version (%s)\n"),
1197 ("%d.%d.%d" % sys.version_info[:3]))
1201 ("%d.%d.%d" % sys.version_info[:3]))
1198 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1202 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1199 os.path.dirname(pycompat.fsencode(os.__file__)))
1203 os.path.dirname(pycompat.fsencode(os.__file__)))
1200
1204
1201 security = set(sslutil.supportedprotocols)
1205 security = set(sslutil.supportedprotocols)
1202 if sslutil.hassni:
1206 if sslutil.hassni:
1203 security.add('sni')
1207 security.add('sni')
1204
1208
1205 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1209 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1206 fm.formatlist(sorted(security), name='protocol',
1210 fm.formatlist(sorted(security), name='protocol',
1207 fmt='%s', sep=','))
1211 fmt='%s', sep=','))
1208
1212
1209 # These are warnings, not errors. So don't increment problem count. This
1213 # These are warnings, not errors. So don't increment problem count. This
1210 # may change in the future.
1214 # may change in the future.
1211 if 'tls1.2' not in security:
1215 if 'tls1.2' not in security:
1212 fm.plain(_(' TLS 1.2 not supported by Python install; '
1216 fm.plain(_(' TLS 1.2 not supported by Python install; '
1213 'network connections lack modern security\n'))
1217 'network connections lack modern security\n'))
1214 if 'sni' not in security:
1218 if 'sni' not in security:
1215 fm.plain(_(' SNI not supported by Python install; may have '
1219 fm.plain(_(' SNI not supported by Python install; may have '
1216 'connectivity issues with some servers\n'))
1220 'connectivity issues with some servers\n'))
1217
1221
1218 # TODO print CA cert info
1222 # TODO print CA cert info
1219
1223
1220 # hg version
1224 # hg version
1221 hgver = util.version()
1225 hgver = util.version()
1222 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1226 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1223 hgver.split('+')[0])
1227 hgver.split('+')[0])
1224 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1228 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1225 '+'.join(hgver.split('+')[1:]))
1229 '+'.join(hgver.split('+')[1:]))
1226
1230
1227 # compiled modules
1231 # compiled modules
1228 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1232 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1229 policy.policy)
1233 policy.policy)
1230 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1234 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1231 os.path.dirname(pycompat.fsencode(__file__)))
1235 os.path.dirname(pycompat.fsencode(__file__)))
1232
1236
1233 if policy.policy in ('c', 'allow'):
1237 if policy.policy in ('c', 'allow'):
1234 err = None
1238 err = None
1235 try:
1239 try:
1236 from .cext import (
1240 from .cext import (
1237 base85,
1241 base85,
1238 bdiff,
1242 bdiff,
1239 mpatch,
1243 mpatch,
1240 osutil,
1244 osutil,
1241 )
1245 )
1242 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1246 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1243 except Exception as inst:
1247 except Exception as inst:
1244 err = stringutil.forcebytestr(inst)
1248 err = stringutil.forcebytestr(inst)
1245 problems += 1
1249 problems += 1
1246 fm.condwrite(err, 'extensionserror', " %s\n", err)
1250 fm.condwrite(err, 'extensionserror', " %s\n", err)
1247
1251
1248 compengines = util.compengines._engines.values()
1252 compengines = util.compengines._engines.values()
1249 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1253 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1250 fm.formatlist(sorted(e.name() for e in compengines),
1254 fm.formatlist(sorted(e.name() for e in compengines),
1251 name='compengine', fmt='%s', sep=', '))
1255 name='compengine', fmt='%s', sep=', '))
1252 fm.write('compenginesavail', _('checking available compression engines '
1256 fm.write('compenginesavail', _('checking available compression engines '
1253 '(%s)\n'),
1257 '(%s)\n'),
1254 fm.formatlist(sorted(e.name() for e in compengines
1258 fm.formatlist(sorted(e.name() for e in compengines
1255 if e.available()),
1259 if e.available()),
1256 name='compengine', fmt='%s', sep=', '))
1260 name='compengine', fmt='%s', sep=', '))
1257 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1261 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1258 fm.write('compenginesserver', _('checking available compression engines '
1262 fm.write('compenginesserver', _('checking available compression engines '
1259 'for wire protocol (%s)\n'),
1263 'for wire protocol (%s)\n'),
1260 fm.formatlist([e.name() for e in wirecompengines
1264 fm.formatlist([e.name() for e in wirecompengines
1261 if e.wireprotosupport()],
1265 if e.wireprotosupport()],
1262 name='compengine', fmt='%s', sep=', '))
1266 name='compengine', fmt='%s', sep=', '))
1263 re2 = 'missing'
1267 re2 = 'missing'
1264 if util._re2:
1268 if util._re2:
1265 re2 = 'available'
1269 re2 = 'available'
1266 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1270 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1267 fm.data(re2=bool(util._re2))
1271 fm.data(re2=bool(util._re2))
1268
1272
1269 # templates
1273 # templates
1270 p = templater.templatepaths()
1274 p = templater.templatepaths()
1271 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1275 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1272 fm.condwrite(not p, '', _(" no template directories found\n"))
1276 fm.condwrite(not p, '', _(" no template directories found\n"))
1273 if p:
1277 if p:
1274 m = templater.templatepath("map-cmdline.default")
1278 m = templater.templatepath("map-cmdline.default")
1275 if m:
1279 if m:
1276 # template found, check if it is working
1280 # template found, check if it is working
1277 err = None
1281 err = None
1278 try:
1282 try:
1279 templater.templater.frommapfile(m)
1283 templater.templater.frommapfile(m)
1280 except Exception as inst:
1284 except Exception as inst:
1281 err = stringutil.forcebytestr(inst)
1285 err = stringutil.forcebytestr(inst)
1282 p = None
1286 p = None
1283 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1287 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1284 else:
1288 else:
1285 p = None
1289 p = None
1286 fm.condwrite(p, 'defaulttemplate',
1290 fm.condwrite(p, 'defaulttemplate',
1287 _("checking default template (%s)\n"), m)
1291 _("checking default template (%s)\n"), m)
1288 fm.condwrite(not m, 'defaulttemplatenotfound',
1292 fm.condwrite(not m, 'defaulttemplatenotfound',
1289 _(" template '%s' not found\n"), "default")
1293 _(" template '%s' not found\n"), "default")
1290 if not p:
1294 if not p:
1291 problems += 1
1295 problems += 1
1292 fm.condwrite(not p, '',
1296 fm.condwrite(not p, '',
1293 _(" (templates seem to have been installed incorrectly)\n"))
1297 _(" (templates seem to have been installed incorrectly)\n"))
1294
1298
1295 # editor
1299 # editor
1296 editor = ui.geteditor()
1300 editor = ui.geteditor()
1297 editor = util.expandpath(editor)
1301 editor = util.expandpath(editor)
1298 editorbin = procutil.shellsplit(editor)[0]
1302 editorbin = procutil.shellsplit(editor)[0]
1299 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1303 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1300 cmdpath = procutil.findexe(editorbin)
1304 cmdpath = procutil.findexe(editorbin)
1301 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1305 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1302 _(" No commit editor set and can't find %s in PATH\n"
1306 _(" No commit editor set and can't find %s in PATH\n"
1303 " (specify a commit editor in your configuration"
1307 " (specify a commit editor in your configuration"
1304 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1308 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1305 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1309 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1306 _(" Can't find editor '%s' in PATH\n"
1310 _(" Can't find editor '%s' in PATH\n"
1307 " (specify a commit editor in your configuration"
1311 " (specify a commit editor in your configuration"
1308 " file)\n"), not cmdpath and editorbin)
1312 " file)\n"), not cmdpath and editorbin)
1309 if not cmdpath and editor != 'vi':
1313 if not cmdpath and editor != 'vi':
1310 problems += 1
1314 problems += 1
1311
1315
1312 # check username
1316 # check username
1313 username = None
1317 username = None
1314 err = None
1318 err = None
1315 try:
1319 try:
1316 username = ui.username()
1320 username = ui.username()
1317 except error.Abort as e:
1321 except error.Abort as e:
1318 err = stringutil.forcebytestr(e)
1322 err = stringutil.forcebytestr(e)
1319 problems += 1
1323 problems += 1
1320
1324
1321 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1325 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1322 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1326 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1323 " (specify a username in your configuration file)\n"), err)
1327 " (specify a username in your configuration file)\n"), err)
1324
1328
1325 fm.condwrite(not problems, '',
1329 fm.condwrite(not problems, '',
1326 _("no problems detected\n"))
1330 _("no problems detected\n"))
1327 if not problems:
1331 if not problems:
1328 fm.data(problems=problems)
1332 fm.data(problems=problems)
1329 fm.condwrite(problems, 'problems',
1333 fm.condwrite(problems, 'problems',
1330 _("%d problems detected,"
1334 _("%d problems detected,"
1331 " please check your install!\n"), problems)
1335 " please check your install!\n"), problems)
1332 fm.end()
1336 fm.end()
1333
1337
1334 return problems
1338 return problems
1335
1339
1336 @command('debugknown', [], _('REPO ID...'), norepo=True)
1340 @command('debugknown', [], _('REPO ID...'), norepo=True)
1337 def debugknown(ui, repopath, *ids, **opts):
1341 def debugknown(ui, repopath, *ids, **opts):
1338 """test whether node ids are known to a repo
1342 """test whether node ids are known to a repo
1339
1343
1340 Every ID must be a full-length hex node id string. Returns a list of 0s
1344 Every ID must be a full-length hex node id string. Returns a list of 0s
1341 and 1s indicating unknown/known.
1345 and 1s indicating unknown/known.
1342 """
1346 """
1343 opts = pycompat.byteskwargs(opts)
1347 opts = pycompat.byteskwargs(opts)
1344 repo = hg.peer(ui, opts, repopath)
1348 repo = hg.peer(ui, opts, repopath)
1345 if not repo.capable('known'):
1349 if not repo.capable('known'):
1346 raise error.Abort("known() not supported by target repository")
1350 raise error.Abort("known() not supported by target repository")
1347 flags = repo.known([bin(s) for s in ids])
1351 flags = repo.known([bin(s) for s in ids])
1348 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1352 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1349
1353
1350 @command('debuglabelcomplete', [], _('LABEL...'))
1354 @command('debuglabelcomplete', [], _('LABEL...'))
1351 def debuglabelcomplete(ui, repo, *args):
1355 def debuglabelcomplete(ui, repo, *args):
1352 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1356 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1353 debugnamecomplete(ui, repo, *args)
1357 debugnamecomplete(ui, repo, *args)
1354
1358
1355 @command('debuglocks',
1359 @command('debuglocks',
1356 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1360 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1357 ('W', 'force-wlock', None,
1361 ('W', 'force-wlock', None,
1358 _('free the working state lock (DANGEROUS)')),
1362 _('free the working state lock (DANGEROUS)')),
1359 ('s', 'set-lock', None, _('set the store lock until stopped')),
1363 ('s', 'set-lock', None, _('set the store lock until stopped')),
1360 ('S', 'set-wlock', None,
1364 ('S', 'set-wlock', None,
1361 _('set the working state lock until stopped'))],
1365 _('set the working state lock until stopped'))],
1362 _('[OPTION]...'))
1366 _('[OPTION]...'))
1363 def debuglocks(ui, repo, **opts):
1367 def debuglocks(ui, repo, **opts):
1364 """show or modify state of locks
1368 """show or modify state of locks
1365
1369
1366 By default, this command will show which locks are held. This
1370 By default, this command will show which locks are held. This
1367 includes the user and process holding the lock, the amount of time
1371 includes the user and process holding the lock, the amount of time
1368 the lock has been held, and the machine name where the process is
1372 the lock has been held, and the machine name where the process is
1369 running if it's not local.
1373 running if it's not local.
1370
1374
1371 Locks protect the integrity of Mercurial's data, so should be
1375 Locks protect the integrity of Mercurial's data, so should be
1372 treated with care. System crashes or other interruptions may cause
1376 treated with care. System crashes or other interruptions may cause
1373 locks to not be properly released, though Mercurial will usually
1377 locks to not be properly released, though Mercurial will usually
1374 detect and remove such stale locks automatically.
1378 detect and remove such stale locks automatically.
1375
1379
1376 However, detecting stale locks may not always be possible (for
1380 However, detecting stale locks may not always be possible (for
1377 instance, on a shared filesystem). Removing locks may also be
1381 instance, on a shared filesystem). Removing locks may also be
1378 blocked by filesystem permissions.
1382 blocked by filesystem permissions.
1379
1383
1380 Setting a lock will prevent other commands from changing the data.
1384 Setting a lock will prevent other commands from changing the data.
1381 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1385 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1382 The set locks are removed when the command exits.
1386 The set locks are removed when the command exits.
1383
1387
1384 Returns 0 if no locks are held.
1388 Returns 0 if no locks are held.
1385
1389
1386 """
1390 """
1387
1391
1388 if opts.get(r'force_lock'):
1392 if opts.get(r'force_lock'):
1389 repo.svfs.unlink('lock')
1393 repo.svfs.unlink('lock')
1390 if opts.get(r'force_wlock'):
1394 if opts.get(r'force_wlock'):
1391 repo.vfs.unlink('wlock')
1395 repo.vfs.unlink('wlock')
1392 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1396 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1393 return 0
1397 return 0
1394
1398
1395 locks = []
1399 locks = []
1396 try:
1400 try:
1397 if opts.get(r'set_wlock'):
1401 if opts.get(r'set_wlock'):
1398 try:
1402 try:
1399 locks.append(repo.wlock(False))
1403 locks.append(repo.wlock(False))
1400 except error.LockHeld:
1404 except error.LockHeld:
1401 raise error.Abort(_('wlock is already held'))
1405 raise error.Abort(_('wlock is already held'))
1402 if opts.get(r'set_lock'):
1406 if opts.get(r'set_lock'):
1403 try:
1407 try:
1404 locks.append(repo.lock(False))
1408 locks.append(repo.lock(False))
1405 except error.LockHeld:
1409 except error.LockHeld:
1406 raise error.Abort(_('lock is already held'))
1410 raise error.Abort(_('lock is already held'))
1407 if len(locks):
1411 if len(locks):
1408 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1412 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1409 return 0
1413 return 0
1410 finally:
1414 finally:
1411 release(*locks)
1415 release(*locks)
1412
1416
1413 now = time.time()
1417 now = time.time()
1414 held = 0
1418 held = 0
1415
1419
1416 def report(vfs, name, method):
1420 def report(vfs, name, method):
1417 # this causes stale locks to get reaped for more accurate reporting
1421 # this causes stale locks to get reaped for more accurate reporting
1418 try:
1422 try:
1419 l = method(False)
1423 l = method(False)
1420 except error.LockHeld:
1424 except error.LockHeld:
1421 l = None
1425 l = None
1422
1426
1423 if l:
1427 if l:
1424 l.release()
1428 l.release()
1425 else:
1429 else:
1426 try:
1430 try:
1427 st = vfs.lstat(name)
1431 st = vfs.lstat(name)
1428 age = now - st[stat.ST_MTIME]
1432 age = now - st[stat.ST_MTIME]
1429 user = util.username(st.st_uid)
1433 user = util.username(st.st_uid)
1430 locker = vfs.readlock(name)
1434 locker = vfs.readlock(name)
1431 if ":" in locker:
1435 if ":" in locker:
1432 host, pid = locker.split(':')
1436 host, pid = locker.split(':')
1433 if host == socket.gethostname():
1437 if host == socket.gethostname():
1434 locker = 'user %s, process %s' % (user, pid)
1438 locker = 'user %s, process %s' % (user, pid)
1435 else:
1439 else:
1436 locker = 'user %s, process %s, host %s' \
1440 locker = 'user %s, process %s, host %s' \
1437 % (user, pid, host)
1441 % (user, pid, host)
1438 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1442 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1439 return 1
1443 return 1
1440 except OSError as e:
1444 except OSError as e:
1441 if e.errno != errno.ENOENT:
1445 if e.errno != errno.ENOENT:
1442 raise
1446 raise
1443
1447
1444 ui.write(("%-6s free\n") % (name + ":"))
1448 ui.write(("%-6s free\n") % (name + ":"))
1445 return 0
1449 return 0
1446
1450
1447 held += report(repo.svfs, "lock", repo.lock)
1451 held += report(repo.svfs, "lock", repo.lock)
1448 held += report(repo.vfs, "wlock", repo.wlock)
1452 held += report(repo.vfs, "wlock", repo.wlock)
1449
1453
1450 return held
1454 return held
1451
1455
1452 @command('debugmanifestfulltextcache', [
1456 @command('debugmanifestfulltextcache', [
1453 ('', 'clear', False, _('clear the cache')),
1457 ('', 'clear', False, _('clear the cache')),
1454 ('a', 'add', '', _('add the given manifest node to the cache'),
1458 ('a', 'add', '', _('add the given manifest node to the cache'),
1455 _('NODE'))
1459 _('NODE'))
1456 ], '')
1460 ], '')
1457 def debugmanifestfulltextcache(ui, repo, add=None, **opts):
1461 def debugmanifestfulltextcache(ui, repo, add=None, **opts):
1458 """show, clear or amend the contents of the manifest fulltext cache"""
1462 """show, clear or amend the contents of the manifest fulltext cache"""
1459 with repo.lock():
1463 with repo.lock():
1460 r = repo.manifestlog.getstorage(b'')
1464 r = repo.manifestlog.getstorage(b'')
1461 try:
1465 try:
1462 cache = r._fulltextcache
1466 cache = r._fulltextcache
1463 except AttributeError:
1467 except AttributeError:
1464 ui.warn(_(
1468 ui.warn(_(
1465 "Current revlog implementation doesn't appear to have a "
1469 "Current revlog implementation doesn't appear to have a "
1466 'manifest fulltext cache\n'))
1470 'manifest fulltext cache\n'))
1467 return
1471 return
1468
1472
1469 if opts.get(r'clear'):
1473 if opts.get(r'clear'):
1470 cache.clear()
1474 cache.clear()
1471
1475
1472 if add:
1476 if add:
1473 try:
1477 try:
1474 manifest = repo.manifestlog[r.lookup(add)]
1478 manifest = repo.manifestlog[r.lookup(add)]
1475 except error.LookupError as e:
1479 except error.LookupError as e:
1476 raise error.Abort(e, hint="Check your manifest node id")
1480 raise error.Abort(e, hint="Check your manifest node id")
1477 manifest.read() # stores revisision in cache too
1481 manifest.read() # stores revisision in cache too
1478
1482
1479 if not len(cache):
1483 if not len(cache):
1480 ui.write(_('Cache empty'))
1484 ui.write(_('Cache empty'))
1481 else:
1485 else:
1482 ui.write(
1486 ui.write(
1483 _('Cache contains %d manifest entries, in order of most to '
1487 _('Cache contains %d manifest entries, in order of most to '
1484 'least recent:\n') % (len(cache),))
1488 'least recent:\n') % (len(cache),))
1485 totalsize = 0
1489 totalsize = 0
1486 for nodeid in cache:
1490 for nodeid in cache:
1487 # Use cache.get to not update the LRU order
1491 # Use cache.get to not update the LRU order
1488 data = cache.get(nodeid)
1492 data = cache.get(nodeid)
1489 size = len(data)
1493 size = len(data)
1490 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
1494 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
1491 ui.write(_('id: %s, size %s\n') % (
1495 ui.write(_('id: %s, size %s\n') % (
1492 hex(nodeid), util.bytecount(size)))
1496 hex(nodeid), util.bytecount(size)))
1493 ondisk = cache._opener.stat('manifestfulltextcache').st_size
1497 ondisk = cache._opener.stat('manifestfulltextcache').st_size
1494 ui.write(
1498 ui.write(
1495 _('Total cache data size %s, on-disk %s\n') % (
1499 _('Total cache data size %s, on-disk %s\n') % (
1496 util.bytecount(totalsize), util.bytecount(ondisk))
1500 util.bytecount(totalsize), util.bytecount(ondisk))
1497 )
1501 )
1498
1502
1499 @command('debugmergestate', [], '')
1503 @command('debugmergestate', [], '')
1500 def debugmergestate(ui, repo, *args):
1504 def debugmergestate(ui, repo, *args):
1501 """print merge state
1505 """print merge state
1502
1506
1503 Use --verbose to print out information about whether v1 or v2 merge state
1507 Use --verbose to print out information about whether v1 or v2 merge state
1504 was chosen."""
1508 was chosen."""
1505 def _hashornull(h):
1509 def _hashornull(h):
1506 if h == nullhex:
1510 if h == nullhex:
1507 return 'null'
1511 return 'null'
1508 else:
1512 else:
1509 return h
1513 return h
1510
1514
1511 def printrecords(version):
1515 def printrecords(version):
1512 ui.write(('* version %d records\n') % version)
1516 ui.write(('* version %d records\n') % version)
1513 if version == 1:
1517 if version == 1:
1514 records = v1records
1518 records = v1records
1515 else:
1519 else:
1516 records = v2records
1520 records = v2records
1517
1521
1518 for rtype, record in records:
1522 for rtype, record in records:
1519 # pretty print some record types
1523 # pretty print some record types
1520 if rtype == 'L':
1524 if rtype == 'L':
1521 ui.write(('local: %s\n') % record)
1525 ui.write(('local: %s\n') % record)
1522 elif rtype == 'O':
1526 elif rtype == 'O':
1523 ui.write(('other: %s\n') % record)
1527 ui.write(('other: %s\n') % record)
1524 elif rtype == 'm':
1528 elif rtype == 'm':
1525 driver, mdstate = record.split('\0', 1)
1529 driver, mdstate = record.split('\0', 1)
1526 ui.write(('merge driver: %s (state "%s")\n')
1530 ui.write(('merge driver: %s (state "%s")\n')
1527 % (driver, mdstate))
1531 % (driver, mdstate))
1528 elif rtype in 'FDC':
1532 elif rtype in 'FDC':
1529 r = record.split('\0')
1533 r = record.split('\0')
1530 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1534 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1531 if version == 1:
1535 if version == 1:
1532 onode = 'not stored in v1 format'
1536 onode = 'not stored in v1 format'
1533 flags = r[7]
1537 flags = r[7]
1534 else:
1538 else:
1535 onode, flags = r[7:9]
1539 onode, flags = r[7:9]
1536 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1540 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1537 % (f, rtype, state, _hashornull(hash)))
1541 % (f, rtype, state, _hashornull(hash)))
1538 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1542 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1539 ui.write((' ancestor path: %s (node %s)\n')
1543 ui.write((' ancestor path: %s (node %s)\n')
1540 % (afile, _hashornull(anode)))
1544 % (afile, _hashornull(anode)))
1541 ui.write((' other path: %s (node %s)\n')
1545 ui.write((' other path: %s (node %s)\n')
1542 % (ofile, _hashornull(onode)))
1546 % (ofile, _hashornull(onode)))
1543 elif rtype == 'f':
1547 elif rtype == 'f':
1544 filename, rawextras = record.split('\0', 1)
1548 filename, rawextras = record.split('\0', 1)
1545 extras = rawextras.split('\0')
1549 extras = rawextras.split('\0')
1546 i = 0
1550 i = 0
1547 extrastrings = []
1551 extrastrings = []
1548 while i < len(extras):
1552 while i < len(extras):
1549 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1553 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1550 i += 2
1554 i += 2
1551
1555
1552 ui.write(('file extras: %s (%s)\n')
1556 ui.write(('file extras: %s (%s)\n')
1553 % (filename, ', '.join(extrastrings)))
1557 % (filename, ', '.join(extrastrings)))
1554 elif rtype == 'l':
1558 elif rtype == 'l':
1555 labels = record.split('\0', 2)
1559 labels = record.split('\0', 2)
1556 labels = [l for l in labels if len(l) > 0]
1560 labels = [l for l in labels if len(l) > 0]
1557 ui.write(('labels:\n'))
1561 ui.write(('labels:\n'))
1558 ui.write((' local: %s\n' % labels[0]))
1562 ui.write((' local: %s\n' % labels[0]))
1559 ui.write((' other: %s\n' % labels[1]))
1563 ui.write((' other: %s\n' % labels[1]))
1560 if len(labels) > 2:
1564 if len(labels) > 2:
1561 ui.write((' base: %s\n' % labels[2]))
1565 ui.write((' base: %s\n' % labels[2]))
1562 else:
1566 else:
1563 ui.write(('unrecognized entry: %s\t%s\n')
1567 ui.write(('unrecognized entry: %s\t%s\n')
1564 % (rtype, record.replace('\0', '\t')))
1568 % (rtype, record.replace('\0', '\t')))
1565
1569
1566 # Avoid mergestate.read() since it may raise an exception for unsupported
1570 # Avoid mergestate.read() since it may raise an exception for unsupported
1567 # merge state records. We shouldn't be doing this, but this is OK since this
1571 # merge state records. We shouldn't be doing this, but this is OK since this
1568 # command is pretty low-level.
1572 # command is pretty low-level.
1569 ms = mergemod.mergestate(repo)
1573 ms = mergemod.mergestate(repo)
1570
1574
1571 # sort so that reasonable information is on top
1575 # sort so that reasonable information is on top
1572 v1records = ms._readrecordsv1()
1576 v1records = ms._readrecordsv1()
1573 v2records = ms._readrecordsv2()
1577 v2records = ms._readrecordsv2()
1574 order = 'LOml'
1578 order = 'LOml'
1575 def key(r):
1579 def key(r):
1576 idx = order.find(r[0])
1580 idx = order.find(r[0])
1577 if idx == -1:
1581 if idx == -1:
1578 return (1, r[1])
1582 return (1, r[1])
1579 else:
1583 else:
1580 return (0, idx)
1584 return (0, idx)
1581 v1records.sort(key=key)
1585 v1records.sort(key=key)
1582 v2records.sort(key=key)
1586 v2records.sort(key=key)
1583
1587
1584 if not v1records and not v2records:
1588 if not v1records and not v2records:
1585 ui.write(('no merge state found\n'))
1589 ui.write(('no merge state found\n'))
1586 elif not v2records:
1590 elif not v2records:
1587 ui.note(('no version 2 merge state\n'))
1591 ui.note(('no version 2 merge state\n'))
1588 printrecords(1)
1592 printrecords(1)
1589 elif ms._v1v2match(v1records, v2records):
1593 elif ms._v1v2match(v1records, v2records):
1590 ui.note(('v1 and v2 states match: using v2\n'))
1594 ui.note(('v1 and v2 states match: using v2\n'))
1591 printrecords(2)
1595 printrecords(2)
1592 else:
1596 else:
1593 ui.note(('v1 and v2 states mismatch: using v1\n'))
1597 ui.note(('v1 and v2 states mismatch: using v1\n'))
1594 printrecords(1)
1598 printrecords(1)
1595 if ui.verbose:
1599 if ui.verbose:
1596 printrecords(2)
1600 printrecords(2)
1597
1601
1598 @command('debugnamecomplete', [], _('NAME...'))
1602 @command('debugnamecomplete', [], _('NAME...'))
1599 def debugnamecomplete(ui, repo, *args):
1603 def debugnamecomplete(ui, repo, *args):
1600 '''complete "names" - tags, open branch names, bookmark names'''
1604 '''complete "names" - tags, open branch names, bookmark names'''
1601
1605
1602 names = set()
1606 names = set()
1603 # since we previously only listed open branches, we will handle that
1607 # since we previously only listed open branches, we will handle that
1604 # specially (after this for loop)
1608 # specially (after this for loop)
1605 for name, ns in repo.names.iteritems():
1609 for name, ns in repo.names.iteritems():
1606 if name != 'branches':
1610 if name != 'branches':
1607 names.update(ns.listnames(repo))
1611 names.update(ns.listnames(repo))
1608 names.update(tag for (tag, heads, tip, closed)
1612 names.update(tag for (tag, heads, tip, closed)
1609 in repo.branchmap().iterbranches() if not closed)
1613 in repo.branchmap().iterbranches() if not closed)
1610 completions = set()
1614 completions = set()
1611 if not args:
1615 if not args:
1612 args = ['']
1616 args = ['']
1613 for a in args:
1617 for a in args:
1614 completions.update(n for n in names if n.startswith(a))
1618 completions.update(n for n in names if n.startswith(a))
1615 ui.write('\n'.join(sorted(completions)))
1619 ui.write('\n'.join(sorted(completions)))
1616 ui.write('\n')
1620 ui.write('\n')
1617
1621
1618 @command('debugobsolete',
1622 @command('debugobsolete',
1619 [('', 'flags', 0, _('markers flag')),
1623 [('', 'flags', 0, _('markers flag')),
1620 ('', 'record-parents', False,
1624 ('', 'record-parents', False,
1621 _('record parent information for the precursor')),
1625 _('record parent information for the precursor')),
1622 ('r', 'rev', [], _('display markers relevant to REV')),
1626 ('r', 'rev', [], _('display markers relevant to REV')),
1623 ('', 'exclusive', False, _('restrict display to markers only '
1627 ('', 'exclusive', False, _('restrict display to markers only '
1624 'relevant to REV')),
1628 'relevant to REV')),
1625 ('', 'index', False, _('display index of the marker')),
1629 ('', 'index', False, _('display index of the marker')),
1626 ('', 'delete', [], _('delete markers specified by indices')),
1630 ('', 'delete', [], _('delete markers specified by indices')),
1627 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1631 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1628 _('[OBSOLETED [REPLACEMENT ...]]'))
1632 _('[OBSOLETED [REPLACEMENT ...]]'))
1629 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1633 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1630 """create arbitrary obsolete marker
1634 """create arbitrary obsolete marker
1631
1635
1632 With no arguments, displays the list of obsolescence markers."""
1636 With no arguments, displays the list of obsolescence markers."""
1633
1637
1634 opts = pycompat.byteskwargs(opts)
1638 opts = pycompat.byteskwargs(opts)
1635
1639
1636 def parsenodeid(s):
1640 def parsenodeid(s):
1637 try:
1641 try:
1638 # We do not use revsingle/revrange functions here to accept
1642 # We do not use revsingle/revrange functions here to accept
1639 # arbitrary node identifiers, possibly not present in the
1643 # arbitrary node identifiers, possibly not present in the
1640 # local repository.
1644 # local repository.
1641 n = bin(s)
1645 n = bin(s)
1642 if len(n) != len(nullid):
1646 if len(n) != len(nullid):
1643 raise TypeError()
1647 raise TypeError()
1644 return n
1648 return n
1645 except TypeError:
1649 except TypeError:
1646 raise error.Abort('changeset references must be full hexadecimal '
1650 raise error.Abort('changeset references must be full hexadecimal '
1647 'node identifiers')
1651 'node identifiers')
1648
1652
1649 if opts.get('delete'):
1653 if opts.get('delete'):
1650 indices = []
1654 indices = []
1651 for v in opts.get('delete'):
1655 for v in opts.get('delete'):
1652 try:
1656 try:
1653 indices.append(int(v))
1657 indices.append(int(v))
1654 except ValueError:
1658 except ValueError:
1655 raise error.Abort(_('invalid index value: %r') % v,
1659 raise error.Abort(_('invalid index value: %r') % v,
1656 hint=_('use integers for indices'))
1660 hint=_('use integers for indices'))
1657
1661
1658 if repo.currenttransaction():
1662 if repo.currenttransaction():
1659 raise error.Abort(_('cannot delete obsmarkers in the middle '
1663 raise error.Abort(_('cannot delete obsmarkers in the middle '
1660 'of transaction.'))
1664 'of transaction.'))
1661
1665
1662 with repo.lock():
1666 with repo.lock():
1663 n = repair.deleteobsmarkers(repo.obsstore, indices)
1667 n = repair.deleteobsmarkers(repo.obsstore, indices)
1664 ui.write(_('deleted %i obsolescence markers\n') % n)
1668 ui.write(_('deleted %i obsolescence markers\n') % n)
1665
1669
1666 return
1670 return
1667
1671
1668 if precursor is not None:
1672 if precursor is not None:
1669 if opts['rev']:
1673 if opts['rev']:
1670 raise error.Abort('cannot select revision when creating marker')
1674 raise error.Abort('cannot select revision when creating marker')
1671 metadata = {}
1675 metadata = {}
1672 metadata['user'] = encoding.fromlocal(opts['user'] or ui.username())
1676 metadata['user'] = encoding.fromlocal(opts['user'] or ui.username())
1673 succs = tuple(parsenodeid(succ) for succ in successors)
1677 succs = tuple(parsenodeid(succ) for succ in successors)
1674 l = repo.lock()
1678 l = repo.lock()
1675 try:
1679 try:
1676 tr = repo.transaction('debugobsolete')
1680 tr = repo.transaction('debugobsolete')
1677 try:
1681 try:
1678 date = opts.get('date')
1682 date = opts.get('date')
1679 if date:
1683 if date:
1680 date = dateutil.parsedate(date)
1684 date = dateutil.parsedate(date)
1681 else:
1685 else:
1682 date = None
1686 date = None
1683 prec = parsenodeid(precursor)
1687 prec = parsenodeid(precursor)
1684 parents = None
1688 parents = None
1685 if opts['record_parents']:
1689 if opts['record_parents']:
1686 if prec not in repo.unfiltered():
1690 if prec not in repo.unfiltered():
1687 raise error.Abort('cannot used --record-parents on '
1691 raise error.Abort('cannot used --record-parents on '
1688 'unknown changesets')
1692 'unknown changesets')
1689 parents = repo.unfiltered()[prec].parents()
1693 parents = repo.unfiltered()[prec].parents()
1690 parents = tuple(p.node() for p in parents)
1694 parents = tuple(p.node() for p in parents)
1691 repo.obsstore.create(tr, prec, succs, opts['flags'],
1695 repo.obsstore.create(tr, prec, succs, opts['flags'],
1692 parents=parents, date=date,
1696 parents=parents, date=date,
1693 metadata=metadata, ui=ui)
1697 metadata=metadata, ui=ui)
1694 tr.close()
1698 tr.close()
1695 except ValueError as exc:
1699 except ValueError as exc:
1696 raise error.Abort(_('bad obsmarker input: %s') %
1700 raise error.Abort(_('bad obsmarker input: %s') %
1697 pycompat.bytestr(exc))
1701 pycompat.bytestr(exc))
1698 finally:
1702 finally:
1699 tr.release()
1703 tr.release()
1700 finally:
1704 finally:
1701 l.release()
1705 l.release()
1702 else:
1706 else:
1703 if opts['rev']:
1707 if opts['rev']:
1704 revs = scmutil.revrange(repo, opts['rev'])
1708 revs = scmutil.revrange(repo, opts['rev'])
1705 nodes = [repo[r].node() for r in revs]
1709 nodes = [repo[r].node() for r in revs]
1706 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1710 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1707 exclusive=opts['exclusive']))
1711 exclusive=opts['exclusive']))
1708 markers.sort(key=lambda x: x._data)
1712 markers.sort(key=lambda x: x._data)
1709 else:
1713 else:
1710 markers = obsutil.getmarkers(repo)
1714 markers = obsutil.getmarkers(repo)
1711
1715
1712 markerstoiter = markers
1716 markerstoiter = markers
1713 isrelevant = lambda m: True
1717 isrelevant = lambda m: True
1714 if opts.get('rev') and opts.get('index'):
1718 if opts.get('rev') and opts.get('index'):
1715 markerstoiter = obsutil.getmarkers(repo)
1719 markerstoiter = obsutil.getmarkers(repo)
1716 markerset = set(markers)
1720 markerset = set(markers)
1717 isrelevant = lambda m: m in markerset
1721 isrelevant = lambda m: m in markerset
1718
1722
1719 fm = ui.formatter('debugobsolete', opts)
1723 fm = ui.formatter('debugobsolete', opts)
1720 for i, m in enumerate(markerstoiter):
1724 for i, m in enumerate(markerstoiter):
1721 if not isrelevant(m):
1725 if not isrelevant(m):
1722 # marker can be irrelevant when we're iterating over a set
1726 # marker can be irrelevant when we're iterating over a set
1723 # of markers (markerstoiter) which is bigger than the set
1727 # of markers (markerstoiter) which is bigger than the set
1724 # of markers we want to display (markers)
1728 # of markers we want to display (markers)
1725 # this can happen if both --index and --rev options are
1729 # this can happen if both --index and --rev options are
1726 # provided and thus we need to iterate over all of the markers
1730 # provided and thus we need to iterate over all of the markers
1727 # to get the correct indices, but only display the ones that
1731 # to get the correct indices, but only display the ones that
1728 # are relevant to --rev value
1732 # are relevant to --rev value
1729 continue
1733 continue
1730 fm.startitem()
1734 fm.startitem()
1731 ind = i if opts.get('index') else None
1735 ind = i if opts.get('index') else None
1732 cmdutil.showmarker(fm, m, index=ind)
1736 cmdutil.showmarker(fm, m, index=ind)
1733 fm.end()
1737 fm.end()
1734
1738
1735 @command('debugpathcomplete',
1739 @command('debugpathcomplete',
1736 [('f', 'full', None, _('complete an entire path')),
1740 [('f', 'full', None, _('complete an entire path')),
1737 ('n', 'normal', None, _('show only normal files')),
1741 ('n', 'normal', None, _('show only normal files')),
1738 ('a', 'added', None, _('show only added files')),
1742 ('a', 'added', None, _('show only added files')),
1739 ('r', 'removed', None, _('show only removed files'))],
1743 ('r', 'removed', None, _('show only removed files'))],
1740 _('FILESPEC...'))
1744 _('FILESPEC...'))
1741 def debugpathcomplete(ui, repo, *specs, **opts):
1745 def debugpathcomplete(ui, repo, *specs, **opts):
1742 '''complete part or all of a tracked path
1746 '''complete part or all of a tracked path
1743
1747
1744 This command supports shells that offer path name completion. It
1748 This command supports shells that offer path name completion. It
1745 currently completes only files already known to the dirstate.
1749 currently completes only files already known to the dirstate.
1746
1750
1747 Completion extends only to the next path segment unless
1751 Completion extends only to the next path segment unless
1748 --full is specified, in which case entire paths are used.'''
1752 --full is specified, in which case entire paths are used.'''
1749
1753
1750 def complete(path, acceptable):
1754 def complete(path, acceptable):
1751 dirstate = repo.dirstate
1755 dirstate = repo.dirstate
1752 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1756 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1753 rootdir = repo.root + pycompat.ossep
1757 rootdir = repo.root + pycompat.ossep
1754 if spec != repo.root and not spec.startswith(rootdir):
1758 if spec != repo.root and not spec.startswith(rootdir):
1755 return [], []
1759 return [], []
1756 if os.path.isdir(spec):
1760 if os.path.isdir(spec):
1757 spec += '/'
1761 spec += '/'
1758 spec = spec[len(rootdir):]
1762 spec = spec[len(rootdir):]
1759 fixpaths = pycompat.ossep != '/'
1763 fixpaths = pycompat.ossep != '/'
1760 if fixpaths:
1764 if fixpaths:
1761 spec = spec.replace(pycompat.ossep, '/')
1765 spec = spec.replace(pycompat.ossep, '/')
1762 speclen = len(spec)
1766 speclen = len(spec)
1763 fullpaths = opts[r'full']
1767 fullpaths = opts[r'full']
1764 files, dirs = set(), set()
1768 files, dirs = set(), set()
1765 adddir, addfile = dirs.add, files.add
1769 adddir, addfile = dirs.add, files.add
1766 for f, st in dirstate.iteritems():
1770 for f, st in dirstate.iteritems():
1767 if f.startswith(spec) and st[0] in acceptable:
1771 if f.startswith(spec) and st[0] in acceptable:
1768 if fixpaths:
1772 if fixpaths:
1769 f = f.replace('/', pycompat.ossep)
1773 f = f.replace('/', pycompat.ossep)
1770 if fullpaths:
1774 if fullpaths:
1771 addfile(f)
1775 addfile(f)
1772 continue
1776 continue
1773 s = f.find(pycompat.ossep, speclen)
1777 s = f.find(pycompat.ossep, speclen)
1774 if s >= 0:
1778 if s >= 0:
1775 adddir(f[:s])
1779 adddir(f[:s])
1776 else:
1780 else:
1777 addfile(f)
1781 addfile(f)
1778 return files, dirs
1782 return files, dirs
1779
1783
1780 acceptable = ''
1784 acceptable = ''
1781 if opts[r'normal']:
1785 if opts[r'normal']:
1782 acceptable += 'nm'
1786 acceptable += 'nm'
1783 if opts[r'added']:
1787 if opts[r'added']:
1784 acceptable += 'a'
1788 acceptable += 'a'
1785 if opts[r'removed']:
1789 if opts[r'removed']:
1786 acceptable += 'r'
1790 acceptable += 'r'
1787 cwd = repo.getcwd()
1791 cwd = repo.getcwd()
1788 if not specs:
1792 if not specs:
1789 specs = ['.']
1793 specs = ['.']
1790
1794
1791 files, dirs = set(), set()
1795 files, dirs = set(), set()
1792 for spec in specs:
1796 for spec in specs:
1793 f, d = complete(spec, acceptable or 'nmar')
1797 f, d = complete(spec, acceptable or 'nmar')
1794 files.update(f)
1798 files.update(f)
1795 dirs.update(d)
1799 dirs.update(d)
1796 files.update(dirs)
1800 files.update(dirs)
1797 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1801 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1798 ui.write('\n')
1802 ui.write('\n')
1799
1803
1800 @command('debugpeer', [], _('PATH'), norepo=True)
1804 @command('debugpeer', [], _('PATH'), norepo=True)
1801 def debugpeer(ui, path):
1805 def debugpeer(ui, path):
1802 """establish a connection to a peer repository"""
1806 """establish a connection to a peer repository"""
1803 # Always enable peer request logging. Requires --debug to display
1807 # Always enable peer request logging. Requires --debug to display
1804 # though.
1808 # though.
1805 overrides = {
1809 overrides = {
1806 ('devel', 'debug.peer-request'): True,
1810 ('devel', 'debug.peer-request'): True,
1807 }
1811 }
1808
1812
1809 with ui.configoverride(overrides):
1813 with ui.configoverride(overrides):
1810 peer = hg.peer(ui, {}, path)
1814 peer = hg.peer(ui, {}, path)
1811
1815
1812 local = peer.local() is not None
1816 local = peer.local() is not None
1813 canpush = peer.canpush()
1817 canpush = peer.canpush()
1814
1818
1815 ui.write(_('url: %s\n') % peer.url())
1819 ui.write(_('url: %s\n') % peer.url())
1816 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1820 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1817 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1821 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1818
1822
1819 @command('debugpickmergetool',
1823 @command('debugpickmergetool',
1820 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1824 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1821 ('', 'changedelete', None, _('emulate merging change and delete')),
1825 ('', 'changedelete', None, _('emulate merging change and delete')),
1822 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1826 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1823 _('[PATTERN]...'),
1827 _('[PATTERN]...'),
1824 inferrepo=True)
1828 inferrepo=True)
1825 def debugpickmergetool(ui, repo, *pats, **opts):
1829 def debugpickmergetool(ui, repo, *pats, **opts):
1826 """examine which merge tool is chosen for specified file
1830 """examine which merge tool is chosen for specified file
1827
1831
1828 As described in :hg:`help merge-tools`, Mercurial examines
1832 As described in :hg:`help merge-tools`, Mercurial examines
1829 configurations below in this order to decide which merge tool is
1833 configurations below in this order to decide which merge tool is
1830 chosen for specified file.
1834 chosen for specified file.
1831
1835
1832 1. ``--tool`` option
1836 1. ``--tool`` option
1833 2. ``HGMERGE`` environment variable
1837 2. ``HGMERGE`` environment variable
1834 3. configurations in ``merge-patterns`` section
1838 3. configurations in ``merge-patterns`` section
1835 4. configuration of ``ui.merge``
1839 4. configuration of ``ui.merge``
1836 5. configurations in ``merge-tools`` section
1840 5. configurations in ``merge-tools`` section
1837 6. ``hgmerge`` tool (for historical reason only)
1841 6. ``hgmerge`` tool (for historical reason only)
1838 7. default tool for fallback (``:merge`` or ``:prompt``)
1842 7. default tool for fallback (``:merge`` or ``:prompt``)
1839
1843
1840 This command writes out examination result in the style below::
1844 This command writes out examination result in the style below::
1841
1845
1842 FILE = MERGETOOL
1846 FILE = MERGETOOL
1843
1847
1844 By default, all files known in the first parent context of the
1848 By default, all files known in the first parent context of the
1845 working directory are examined. Use file patterns and/or -I/-X
1849 working directory are examined. Use file patterns and/or -I/-X
1846 options to limit target files. -r/--rev is also useful to examine
1850 options to limit target files. -r/--rev is also useful to examine
1847 files in another context without actual updating to it.
1851 files in another context without actual updating to it.
1848
1852
1849 With --debug, this command shows warning messages while matching
1853 With --debug, this command shows warning messages while matching
1850 against ``merge-patterns`` and so on, too. It is recommended to
1854 against ``merge-patterns`` and so on, too. It is recommended to
1851 use this option with explicit file patterns and/or -I/-X options,
1855 use this option with explicit file patterns and/or -I/-X options,
1852 because this option increases amount of output per file according
1856 because this option increases amount of output per file according
1853 to configurations in hgrc.
1857 to configurations in hgrc.
1854
1858
1855 With -v/--verbose, this command shows configurations below at
1859 With -v/--verbose, this command shows configurations below at
1856 first (only if specified).
1860 first (only if specified).
1857
1861
1858 - ``--tool`` option
1862 - ``--tool`` option
1859 - ``HGMERGE`` environment variable
1863 - ``HGMERGE`` environment variable
1860 - configuration of ``ui.merge``
1864 - configuration of ``ui.merge``
1861
1865
1862 If merge tool is chosen before matching against
1866 If merge tool is chosen before matching against
1863 ``merge-patterns``, this command can't show any helpful
1867 ``merge-patterns``, this command can't show any helpful
1864 information, even with --debug. In such case, information above is
1868 information, even with --debug. In such case, information above is
1865 useful to know why a merge tool is chosen.
1869 useful to know why a merge tool is chosen.
1866 """
1870 """
1867 opts = pycompat.byteskwargs(opts)
1871 opts = pycompat.byteskwargs(opts)
1868 overrides = {}
1872 overrides = {}
1869 if opts['tool']:
1873 if opts['tool']:
1870 overrides[('ui', 'forcemerge')] = opts['tool']
1874 overrides[('ui', 'forcemerge')] = opts['tool']
1871 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1875 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1872
1876
1873 with ui.configoverride(overrides, 'debugmergepatterns'):
1877 with ui.configoverride(overrides, 'debugmergepatterns'):
1874 hgmerge = encoding.environ.get("HGMERGE")
1878 hgmerge = encoding.environ.get("HGMERGE")
1875 if hgmerge is not None:
1879 if hgmerge is not None:
1876 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1880 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1877 uimerge = ui.config("ui", "merge")
1881 uimerge = ui.config("ui", "merge")
1878 if uimerge:
1882 if uimerge:
1879 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1883 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1880
1884
1881 ctx = scmutil.revsingle(repo, opts.get('rev'))
1885 ctx = scmutil.revsingle(repo, opts.get('rev'))
1882 m = scmutil.match(ctx, pats, opts)
1886 m = scmutil.match(ctx, pats, opts)
1883 changedelete = opts['changedelete']
1887 changedelete = opts['changedelete']
1884 for path in ctx.walk(m):
1888 for path in ctx.walk(m):
1885 fctx = ctx[path]
1889 fctx = ctx[path]
1886 try:
1890 try:
1887 if not ui.debugflag:
1891 if not ui.debugflag:
1888 ui.pushbuffer(error=True)
1892 ui.pushbuffer(error=True)
1889 tool, toolpath = filemerge._picktool(repo, ui, path,
1893 tool, toolpath = filemerge._picktool(repo, ui, path,
1890 fctx.isbinary(),
1894 fctx.isbinary(),
1891 'l' in fctx.flags(),
1895 'l' in fctx.flags(),
1892 changedelete)
1896 changedelete)
1893 finally:
1897 finally:
1894 if not ui.debugflag:
1898 if not ui.debugflag:
1895 ui.popbuffer()
1899 ui.popbuffer()
1896 ui.write(('%s = %s\n') % (path, tool))
1900 ui.write(('%s = %s\n') % (path, tool))
1897
1901
1898 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1902 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1899 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1903 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1900 '''access the pushkey key/value protocol
1904 '''access the pushkey key/value protocol
1901
1905
1902 With two args, list the keys in the given namespace.
1906 With two args, list the keys in the given namespace.
1903
1907
1904 With five args, set a key to new if it currently is set to old.
1908 With five args, set a key to new if it currently is set to old.
1905 Reports success or failure.
1909 Reports success or failure.
1906 '''
1910 '''
1907
1911
1908 target = hg.peer(ui, {}, repopath)
1912 target = hg.peer(ui, {}, repopath)
1909 if keyinfo:
1913 if keyinfo:
1910 key, old, new = keyinfo
1914 key, old, new = keyinfo
1911 with target.commandexecutor() as e:
1915 with target.commandexecutor() as e:
1912 r = e.callcommand('pushkey', {
1916 r = e.callcommand('pushkey', {
1913 'namespace': namespace,
1917 'namespace': namespace,
1914 'key': key,
1918 'key': key,
1915 'old': old,
1919 'old': old,
1916 'new': new,
1920 'new': new,
1917 }).result()
1921 }).result()
1918
1922
1919 ui.status(pycompat.bytestr(r) + '\n')
1923 ui.status(pycompat.bytestr(r) + '\n')
1920 return not r
1924 return not r
1921 else:
1925 else:
1922 for k, v in sorted(target.listkeys(namespace).iteritems()):
1926 for k, v in sorted(target.listkeys(namespace).iteritems()):
1923 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
1927 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
1924 stringutil.escapestr(v)))
1928 stringutil.escapestr(v)))
1925
1929
1926 @command('debugpvec', [], _('A B'))
1930 @command('debugpvec', [], _('A B'))
1927 def debugpvec(ui, repo, a, b=None):
1931 def debugpvec(ui, repo, a, b=None):
1928 ca = scmutil.revsingle(repo, a)
1932 ca = scmutil.revsingle(repo, a)
1929 cb = scmutil.revsingle(repo, b)
1933 cb = scmutil.revsingle(repo, b)
1930 pa = pvec.ctxpvec(ca)
1934 pa = pvec.ctxpvec(ca)
1931 pb = pvec.ctxpvec(cb)
1935 pb = pvec.ctxpvec(cb)
1932 if pa == pb:
1936 if pa == pb:
1933 rel = "="
1937 rel = "="
1934 elif pa > pb:
1938 elif pa > pb:
1935 rel = ">"
1939 rel = ">"
1936 elif pa < pb:
1940 elif pa < pb:
1937 rel = "<"
1941 rel = "<"
1938 elif pa | pb:
1942 elif pa | pb:
1939 rel = "|"
1943 rel = "|"
1940 ui.write(_("a: %s\n") % pa)
1944 ui.write(_("a: %s\n") % pa)
1941 ui.write(_("b: %s\n") % pb)
1945 ui.write(_("b: %s\n") % pb)
1942 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1946 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1943 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1947 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1944 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1948 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1945 pa.distance(pb), rel))
1949 pa.distance(pb), rel))
1946
1950
1947 @command('debugrebuilddirstate|debugrebuildstate',
1951 @command('debugrebuilddirstate|debugrebuildstate',
1948 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1952 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1949 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1953 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1950 'the working copy parent')),
1954 'the working copy parent')),
1951 ],
1955 ],
1952 _('[-r REV]'))
1956 _('[-r REV]'))
1953 def debugrebuilddirstate(ui, repo, rev, **opts):
1957 def debugrebuilddirstate(ui, repo, rev, **opts):
1954 """rebuild the dirstate as it would look like for the given revision
1958 """rebuild the dirstate as it would look like for the given revision
1955
1959
1956 If no revision is specified the first current parent will be used.
1960 If no revision is specified the first current parent will be used.
1957
1961
1958 The dirstate will be set to the files of the given revision.
1962 The dirstate will be set to the files of the given revision.
1959 The actual working directory content or existing dirstate
1963 The actual working directory content or existing dirstate
1960 information such as adds or removes is not considered.
1964 information such as adds or removes is not considered.
1961
1965
1962 ``minimal`` will only rebuild the dirstate status for files that claim to be
1966 ``minimal`` will only rebuild the dirstate status for files that claim to be
1963 tracked but are not in the parent manifest, or that exist in the parent
1967 tracked but are not in the parent manifest, or that exist in the parent
1964 manifest but are not in the dirstate. It will not change adds, removes, or
1968 manifest but are not in the dirstate. It will not change adds, removes, or
1965 modified files that are in the working copy parent.
1969 modified files that are in the working copy parent.
1966
1970
1967 One use of this command is to make the next :hg:`status` invocation
1971 One use of this command is to make the next :hg:`status` invocation
1968 check the actual file content.
1972 check the actual file content.
1969 """
1973 """
1970 ctx = scmutil.revsingle(repo, rev)
1974 ctx = scmutil.revsingle(repo, rev)
1971 with repo.wlock():
1975 with repo.wlock():
1972 dirstate = repo.dirstate
1976 dirstate = repo.dirstate
1973 changedfiles = None
1977 changedfiles = None
1974 # See command doc for what minimal does.
1978 # See command doc for what minimal does.
1975 if opts.get(r'minimal'):
1979 if opts.get(r'minimal'):
1976 manifestfiles = set(ctx.manifest().keys())
1980 manifestfiles = set(ctx.manifest().keys())
1977 dirstatefiles = set(dirstate)
1981 dirstatefiles = set(dirstate)
1978 manifestonly = manifestfiles - dirstatefiles
1982 manifestonly = manifestfiles - dirstatefiles
1979 dsonly = dirstatefiles - manifestfiles
1983 dsonly = dirstatefiles - manifestfiles
1980 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1984 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1981 changedfiles = manifestonly | dsnotadded
1985 changedfiles = manifestonly | dsnotadded
1982
1986
1983 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1987 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1984
1988
1985 @command('debugrebuildfncache', [], '')
1989 @command('debugrebuildfncache', [], '')
1986 def debugrebuildfncache(ui, repo):
1990 def debugrebuildfncache(ui, repo):
1987 """rebuild the fncache file"""
1991 """rebuild the fncache file"""
1988 repair.rebuildfncache(ui, repo)
1992 repair.rebuildfncache(ui, repo)
1989
1993
1990 @command('debugrename',
1994 @command('debugrename',
1991 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1995 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1992 _('[-r REV] FILE'))
1996 _('[-r REV] FILE'))
1993 def debugrename(ui, repo, file1, *pats, **opts):
1997 def debugrename(ui, repo, file1, *pats, **opts):
1994 """dump rename information"""
1998 """dump rename information"""
1995
1999
1996 opts = pycompat.byteskwargs(opts)
2000 opts = pycompat.byteskwargs(opts)
1997 ctx = scmutil.revsingle(repo, opts.get('rev'))
2001 ctx = scmutil.revsingle(repo, opts.get('rev'))
1998 m = scmutil.match(ctx, (file1,) + pats, opts)
2002 m = scmutil.match(ctx, (file1,) + pats, opts)
1999 for abs in ctx.walk(m):
2003 for abs in ctx.walk(m):
2000 fctx = ctx[abs]
2004 fctx = ctx[abs]
2001 o = fctx.filelog().renamed(fctx.filenode())
2005 o = fctx.filelog().renamed(fctx.filenode())
2002 rel = m.rel(abs)
2006 rel = m.rel(abs)
2003 if o:
2007 if o:
2004 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2008 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2005 else:
2009 else:
2006 ui.write(_("%s not renamed\n") % rel)
2010 ui.write(_("%s not renamed\n") % rel)
2007
2011
2008 @command('debugrevlog', cmdutil.debugrevlogopts +
2012 @command('debugrevlog', cmdutil.debugrevlogopts +
2009 [('d', 'dump', False, _('dump index data'))],
2013 [('d', 'dump', False, _('dump index data'))],
2010 _('-c|-m|FILE'),
2014 _('-c|-m|FILE'),
2011 optionalrepo=True)
2015 optionalrepo=True)
2012 def debugrevlog(ui, repo, file_=None, **opts):
2016 def debugrevlog(ui, repo, file_=None, **opts):
2013 """show data and statistics about a revlog"""
2017 """show data and statistics about a revlog"""
2014 opts = pycompat.byteskwargs(opts)
2018 opts = pycompat.byteskwargs(opts)
2015 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2019 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2016
2020
2017 if opts.get("dump"):
2021 if opts.get("dump"):
2018 numrevs = len(r)
2022 numrevs = len(r)
2019 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
2023 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
2020 " rawsize totalsize compression heads chainlen\n"))
2024 " rawsize totalsize compression heads chainlen\n"))
2021 ts = 0
2025 ts = 0
2022 heads = set()
2026 heads = set()
2023
2027
2024 for rev in pycompat.xrange(numrevs):
2028 for rev in pycompat.xrange(numrevs):
2025 dbase = r.deltaparent(rev)
2029 dbase = r.deltaparent(rev)
2026 if dbase == -1:
2030 if dbase == -1:
2027 dbase = rev
2031 dbase = rev
2028 cbase = r.chainbase(rev)
2032 cbase = r.chainbase(rev)
2029 clen = r.chainlen(rev)
2033 clen = r.chainlen(rev)
2030 p1, p2 = r.parentrevs(rev)
2034 p1, p2 = r.parentrevs(rev)
2031 rs = r.rawsize(rev)
2035 rs = r.rawsize(rev)
2032 ts = ts + rs
2036 ts = ts + rs
2033 heads -= set(r.parentrevs(rev))
2037 heads -= set(r.parentrevs(rev))
2034 heads.add(rev)
2038 heads.add(rev)
2035 try:
2039 try:
2036 compression = ts / r.end(rev)
2040 compression = ts / r.end(rev)
2037 except ZeroDivisionError:
2041 except ZeroDivisionError:
2038 compression = 0
2042 compression = 0
2039 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2043 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2040 "%11d %5d %8d\n" %
2044 "%11d %5d %8d\n" %
2041 (rev, p1, p2, r.start(rev), r.end(rev),
2045 (rev, p1, p2, r.start(rev), r.end(rev),
2042 r.start(dbase), r.start(cbase),
2046 r.start(dbase), r.start(cbase),
2043 r.start(p1), r.start(p2),
2047 r.start(p1), r.start(p2),
2044 rs, ts, compression, len(heads), clen))
2048 rs, ts, compression, len(heads), clen))
2045 return 0
2049 return 0
2046
2050
2047 v = r.version
2051 v = r.version
2048 format = v & 0xFFFF
2052 format = v & 0xFFFF
2049 flags = []
2053 flags = []
2050 gdelta = False
2054 gdelta = False
2051 if v & revlog.FLAG_INLINE_DATA:
2055 if v & revlog.FLAG_INLINE_DATA:
2052 flags.append('inline')
2056 flags.append('inline')
2053 if v & revlog.FLAG_GENERALDELTA:
2057 if v & revlog.FLAG_GENERALDELTA:
2054 gdelta = True
2058 gdelta = True
2055 flags.append('generaldelta')
2059 flags.append('generaldelta')
2056 if not flags:
2060 if not flags:
2057 flags = ['(none)']
2061 flags = ['(none)']
2058
2062
2059 ### tracks merge vs single parent
2063 ### tracks merge vs single parent
2060 nummerges = 0
2064 nummerges = 0
2061
2065
2062 ### tracks ways the "delta" are build
2066 ### tracks ways the "delta" are build
2063 # nodelta
2067 # nodelta
2064 numempty = 0
2068 numempty = 0
2065 numemptytext = 0
2069 numemptytext = 0
2066 numemptydelta = 0
2070 numemptydelta = 0
2067 # full file content
2071 # full file content
2068 numfull = 0
2072 numfull = 0
2069 # intermediate snapshot against a prior snapshot
2073 # intermediate snapshot against a prior snapshot
2070 numsemi = 0
2074 numsemi = 0
2071 # snapshot count per depth
2075 # snapshot count per depth
2072 numsnapdepth = collections.defaultdict(lambda: 0)
2076 numsnapdepth = collections.defaultdict(lambda: 0)
2073 # delta against previous revision
2077 # delta against previous revision
2074 numprev = 0
2078 numprev = 0
2075 # delta against first or second parent (not prev)
2079 # delta against first or second parent (not prev)
2076 nump1 = 0
2080 nump1 = 0
2077 nump2 = 0
2081 nump2 = 0
2078 # delta against neither prev nor parents
2082 # delta against neither prev nor parents
2079 numother = 0
2083 numother = 0
2080 # delta against prev that are also first or second parent
2084 # delta against prev that are also first or second parent
2081 # (details of `numprev`)
2085 # (details of `numprev`)
2082 nump1prev = 0
2086 nump1prev = 0
2083 nump2prev = 0
2087 nump2prev = 0
2084
2088
2085 # data about delta chain of each revs
2089 # data about delta chain of each revs
2086 chainlengths = []
2090 chainlengths = []
2087 chainbases = []
2091 chainbases = []
2088 chainspans = []
2092 chainspans = []
2089
2093
2090 # data about each revision
2094 # data about each revision
2091 datasize = [None, 0, 0]
2095 datasize = [None, 0, 0]
2092 fullsize = [None, 0, 0]
2096 fullsize = [None, 0, 0]
2093 semisize = [None, 0, 0]
2097 semisize = [None, 0, 0]
2094 # snapshot count per depth
2098 # snapshot count per depth
2095 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2099 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2096 deltasize = [None, 0, 0]
2100 deltasize = [None, 0, 0]
2097 chunktypecounts = {}
2101 chunktypecounts = {}
2098 chunktypesizes = {}
2102 chunktypesizes = {}
2099
2103
2100 def addsize(size, l):
2104 def addsize(size, l):
2101 if l[0] is None or size < l[0]:
2105 if l[0] is None or size < l[0]:
2102 l[0] = size
2106 l[0] = size
2103 if size > l[1]:
2107 if size > l[1]:
2104 l[1] = size
2108 l[1] = size
2105 l[2] += size
2109 l[2] += size
2106
2110
2107 numrevs = len(r)
2111 numrevs = len(r)
2108 for rev in pycompat.xrange(numrevs):
2112 for rev in pycompat.xrange(numrevs):
2109 p1, p2 = r.parentrevs(rev)
2113 p1, p2 = r.parentrevs(rev)
2110 delta = r.deltaparent(rev)
2114 delta = r.deltaparent(rev)
2111 if format > 0:
2115 if format > 0:
2112 addsize(r.rawsize(rev), datasize)
2116 addsize(r.rawsize(rev), datasize)
2113 if p2 != nullrev:
2117 if p2 != nullrev:
2114 nummerges += 1
2118 nummerges += 1
2115 size = r.length(rev)
2119 size = r.length(rev)
2116 if delta == nullrev:
2120 if delta == nullrev:
2117 chainlengths.append(0)
2121 chainlengths.append(0)
2118 chainbases.append(r.start(rev))
2122 chainbases.append(r.start(rev))
2119 chainspans.append(size)
2123 chainspans.append(size)
2120 if size == 0:
2124 if size == 0:
2121 numempty += 1
2125 numempty += 1
2122 numemptytext += 1
2126 numemptytext += 1
2123 else:
2127 else:
2124 numfull += 1
2128 numfull += 1
2125 numsnapdepth[0] += 1
2129 numsnapdepth[0] += 1
2126 addsize(size, fullsize)
2130 addsize(size, fullsize)
2127 addsize(size, snapsizedepth[0])
2131 addsize(size, snapsizedepth[0])
2128 else:
2132 else:
2129 chainlengths.append(chainlengths[delta] + 1)
2133 chainlengths.append(chainlengths[delta] + 1)
2130 baseaddr = chainbases[delta]
2134 baseaddr = chainbases[delta]
2131 revaddr = r.start(rev)
2135 revaddr = r.start(rev)
2132 chainbases.append(baseaddr)
2136 chainbases.append(baseaddr)
2133 chainspans.append((revaddr - baseaddr) + size)
2137 chainspans.append((revaddr - baseaddr) + size)
2134 if size == 0:
2138 if size == 0:
2135 numempty += 1
2139 numempty += 1
2136 numemptydelta += 1
2140 numemptydelta += 1
2137 elif r.issnapshot(rev):
2141 elif r.issnapshot(rev):
2138 addsize(size, semisize)
2142 addsize(size, semisize)
2139 numsemi += 1
2143 numsemi += 1
2140 depth = r.snapshotdepth(rev)
2144 depth = r.snapshotdepth(rev)
2141 numsnapdepth[depth] += 1
2145 numsnapdepth[depth] += 1
2142 addsize(size, snapsizedepth[depth])
2146 addsize(size, snapsizedepth[depth])
2143 else:
2147 else:
2144 addsize(size, deltasize)
2148 addsize(size, deltasize)
2145 if delta == rev - 1:
2149 if delta == rev - 1:
2146 numprev += 1
2150 numprev += 1
2147 if delta == p1:
2151 if delta == p1:
2148 nump1prev += 1
2152 nump1prev += 1
2149 elif delta == p2:
2153 elif delta == p2:
2150 nump2prev += 1
2154 nump2prev += 1
2151 elif delta == p1:
2155 elif delta == p1:
2152 nump1 += 1
2156 nump1 += 1
2153 elif delta == p2:
2157 elif delta == p2:
2154 nump2 += 1
2158 nump2 += 1
2155 elif delta != nullrev:
2159 elif delta != nullrev:
2156 numother += 1
2160 numother += 1
2157
2161
2158 # Obtain data on the raw chunks in the revlog.
2162 # Obtain data on the raw chunks in the revlog.
2159 if util.safehasattr(r, '_getsegmentforrevs'):
2163 if util.safehasattr(r, '_getsegmentforrevs'):
2160 segment = r._getsegmentforrevs(rev, rev)[1]
2164 segment = r._getsegmentforrevs(rev, rev)[1]
2161 else:
2165 else:
2162 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2166 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2163 if segment:
2167 if segment:
2164 chunktype = bytes(segment[0:1])
2168 chunktype = bytes(segment[0:1])
2165 else:
2169 else:
2166 chunktype = 'empty'
2170 chunktype = 'empty'
2167
2171
2168 if chunktype not in chunktypecounts:
2172 if chunktype not in chunktypecounts:
2169 chunktypecounts[chunktype] = 0
2173 chunktypecounts[chunktype] = 0
2170 chunktypesizes[chunktype] = 0
2174 chunktypesizes[chunktype] = 0
2171
2175
2172 chunktypecounts[chunktype] += 1
2176 chunktypecounts[chunktype] += 1
2173 chunktypesizes[chunktype] += size
2177 chunktypesizes[chunktype] += size
2174
2178
2175 # Adjust size min value for empty cases
2179 # Adjust size min value for empty cases
2176 for size in (datasize, fullsize, semisize, deltasize):
2180 for size in (datasize, fullsize, semisize, deltasize):
2177 if size[0] is None:
2181 if size[0] is None:
2178 size[0] = 0
2182 size[0] = 0
2179
2183
2180 numdeltas = numrevs - numfull - numempty - numsemi
2184 numdeltas = numrevs - numfull - numempty - numsemi
2181 numoprev = numprev - nump1prev - nump2prev
2185 numoprev = numprev - nump1prev - nump2prev
2182 totalrawsize = datasize[2]
2186 totalrawsize = datasize[2]
2183 datasize[2] /= numrevs
2187 datasize[2] /= numrevs
2184 fulltotal = fullsize[2]
2188 fulltotal = fullsize[2]
2185 fullsize[2] /= numfull
2189 fullsize[2] /= numfull
2186 semitotal = semisize[2]
2190 semitotal = semisize[2]
2187 snaptotal = {}
2191 snaptotal = {}
2188 if 0 < numsemi:
2192 if 0 < numsemi:
2189 semisize[2] /= numsemi
2193 semisize[2] /= numsemi
2190 for depth in snapsizedepth:
2194 for depth in snapsizedepth:
2191 snaptotal[depth] = snapsizedepth[depth][2]
2195 snaptotal[depth] = snapsizedepth[depth][2]
2192 snapsizedepth[depth][2] /= numsnapdepth[depth]
2196 snapsizedepth[depth][2] /= numsnapdepth[depth]
2193
2197
2194 deltatotal = deltasize[2]
2198 deltatotal = deltasize[2]
2195 if numdeltas > 0:
2199 if numdeltas > 0:
2196 deltasize[2] /= numdeltas
2200 deltasize[2] /= numdeltas
2197 totalsize = fulltotal + semitotal + deltatotal
2201 totalsize = fulltotal + semitotal + deltatotal
2198 avgchainlen = sum(chainlengths) / numrevs
2202 avgchainlen = sum(chainlengths) / numrevs
2199 maxchainlen = max(chainlengths)
2203 maxchainlen = max(chainlengths)
2200 maxchainspan = max(chainspans)
2204 maxchainspan = max(chainspans)
2201 compratio = 1
2205 compratio = 1
2202 if totalsize:
2206 if totalsize:
2203 compratio = totalrawsize / totalsize
2207 compratio = totalrawsize / totalsize
2204
2208
2205 basedfmtstr = '%%%dd\n'
2209 basedfmtstr = '%%%dd\n'
2206 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2210 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2207
2211
2208 def dfmtstr(max):
2212 def dfmtstr(max):
2209 return basedfmtstr % len(str(max))
2213 return basedfmtstr % len(str(max))
2210 def pcfmtstr(max, padding=0):
2214 def pcfmtstr(max, padding=0):
2211 return basepcfmtstr % (len(str(max)), ' ' * padding)
2215 return basepcfmtstr % (len(str(max)), ' ' * padding)
2212
2216
2213 def pcfmt(value, total):
2217 def pcfmt(value, total):
2214 if total:
2218 if total:
2215 return (value, 100 * float(value) / total)
2219 return (value, 100 * float(value) / total)
2216 else:
2220 else:
2217 return value, 100.0
2221 return value, 100.0
2218
2222
2219 ui.write(('format : %d\n') % format)
2223 ui.write(('format : %d\n') % format)
2220 ui.write(('flags : %s\n') % ', '.join(flags))
2224 ui.write(('flags : %s\n') % ', '.join(flags))
2221
2225
2222 ui.write('\n')
2226 ui.write('\n')
2223 fmt = pcfmtstr(totalsize)
2227 fmt = pcfmtstr(totalsize)
2224 fmt2 = dfmtstr(totalsize)
2228 fmt2 = dfmtstr(totalsize)
2225 ui.write(('revisions : ') + fmt2 % numrevs)
2229 ui.write(('revisions : ') + fmt2 % numrevs)
2226 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2230 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2227 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2231 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2228 ui.write(('revisions : ') + fmt2 % numrevs)
2232 ui.write(('revisions : ') + fmt2 % numrevs)
2229 ui.write((' empty : ') + fmt % pcfmt(numempty, numrevs))
2233 ui.write((' empty : ') + fmt % pcfmt(numempty, numrevs))
2230 ui.write((' text : ')
2234 ui.write((' text : ')
2231 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta))
2235 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta))
2232 ui.write((' delta : ')
2236 ui.write((' delta : ')
2233 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta))
2237 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta))
2234 ui.write((' snapshot : ') + fmt % pcfmt(numfull + numsemi, numrevs))
2238 ui.write((' snapshot : ') + fmt % pcfmt(numfull + numsemi, numrevs))
2235 for depth in sorted(numsnapdepth):
2239 for depth in sorted(numsnapdepth):
2236 ui.write((' lvl-%-3d : ' % depth)
2240 ui.write((' lvl-%-3d : ' % depth)
2237 + fmt % pcfmt(numsnapdepth[depth], numrevs))
2241 + fmt % pcfmt(numsnapdepth[depth], numrevs))
2238 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2242 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2239 ui.write(('revision size : ') + fmt2 % totalsize)
2243 ui.write(('revision size : ') + fmt2 % totalsize)
2240 ui.write((' snapshot : ')
2244 ui.write((' snapshot : ')
2241 + fmt % pcfmt(fulltotal + semitotal, totalsize))
2245 + fmt % pcfmt(fulltotal + semitotal, totalsize))
2242 for depth in sorted(numsnapdepth):
2246 for depth in sorted(numsnapdepth):
2243 ui.write((' lvl-%-3d : ' % depth)
2247 ui.write((' lvl-%-3d : ' % depth)
2244 + fmt % pcfmt(snaptotal[depth], totalsize))
2248 + fmt % pcfmt(snaptotal[depth], totalsize))
2245 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2249 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2246
2250
2247 def fmtchunktype(chunktype):
2251 def fmtchunktype(chunktype):
2248 if chunktype == 'empty':
2252 if chunktype == 'empty':
2249 return ' %s : ' % chunktype
2253 return ' %s : ' % chunktype
2250 elif chunktype in pycompat.bytestr(string.ascii_letters):
2254 elif chunktype in pycompat.bytestr(string.ascii_letters):
2251 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2255 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2252 else:
2256 else:
2253 return ' 0x%s : ' % hex(chunktype)
2257 return ' 0x%s : ' % hex(chunktype)
2254
2258
2255 ui.write('\n')
2259 ui.write('\n')
2256 ui.write(('chunks : ') + fmt2 % numrevs)
2260 ui.write(('chunks : ') + fmt2 % numrevs)
2257 for chunktype in sorted(chunktypecounts):
2261 for chunktype in sorted(chunktypecounts):
2258 ui.write(fmtchunktype(chunktype))
2262 ui.write(fmtchunktype(chunktype))
2259 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2263 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2260 ui.write(('chunks size : ') + fmt2 % totalsize)
2264 ui.write(('chunks size : ') + fmt2 % totalsize)
2261 for chunktype in sorted(chunktypecounts):
2265 for chunktype in sorted(chunktypecounts):
2262 ui.write(fmtchunktype(chunktype))
2266 ui.write(fmtchunktype(chunktype))
2263 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2267 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2264
2268
2265 ui.write('\n')
2269 ui.write('\n')
2266 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2270 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2267 ui.write(('avg chain length : ') + fmt % avgchainlen)
2271 ui.write(('avg chain length : ') + fmt % avgchainlen)
2268 ui.write(('max chain length : ') + fmt % maxchainlen)
2272 ui.write(('max chain length : ') + fmt % maxchainlen)
2269 ui.write(('max chain reach : ') + fmt % maxchainspan)
2273 ui.write(('max chain reach : ') + fmt % maxchainspan)
2270 ui.write(('compression ratio : ') + fmt % compratio)
2274 ui.write(('compression ratio : ') + fmt % compratio)
2271
2275
2272 if format > 0:
2276 if format > 0:
2273 ui.write('\n')
2277 ui.write('\n')
2274 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2278 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2275 % tuple(datasize))
2279 % tuple(datasize))
2276 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2280 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2277 % tuple(fullsize))
2281 % tuple(fullsize))
2278 ui.write(('inter-snapshot size (min/max/avg) : %d / %d / %d\n')
2282 ui.write(('inter-snapshot size (min/max/avg) : %d / %d / %d\n')
2279 % tuple(semisize))
2283 % tuple(semisize))
2280 for depth in sorted(snapsizedepth):
2284 for depth in sorted(snapsizedepth):
2281 if depth == 0:
2285 if depth == 0:
2282 continue
2286 continue
2283 ui.write((' level-%-3d (min/max/avg) : %d / %d / %d\n')
2287 ui.write((' level-%-3d (min/max/avg) : %d / %d / %d\n')
2284 % ((depth,) + tuple(snapsizedepth[depth])))
2288 % ((depth,) + tuple(snapsizedepth[depth])))
2285 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2289 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2286 % tuple(deltasize))
2290 % tuple(deltasize))
2287
2291
2288 if numdeltas > 0:
2292 if numdeltas > 0:
2289 ui.write('\n')
2293 ui.write('\n')
2290 fmt = pcfmtstr(numdeltas)
2294 fmt = pcfmtstr(numdeltas)
2291 fmt2 = pcfmtstr(numdeltas, 4)
2295 fmt2 = pcfmtstr(numdeltas, 4)
2292 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2296 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2293 if numprev > 0:
2297 if numprev > 0:
2294 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2298 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2295 numprev))
2299 numprev))
2296 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2300 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2297 numprev))
2301 numprev))
2298 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2302 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2299 numprev))
2303 numprev))
2300 if gdelta:
2304 if gdelta:
2301 ui.write(('deltas against p1 : ')
2305 ui.write(('deltas against p1 : ')
2302 + fmt % pcfmt(nump1, numdeltas))
2306 + fmt % pcfmt(nump1, numdeltas))
2303 ui.write(('deltas against p2 : ')
2307 ui.write(('deltas against p2 : ')
2304 + fmt % pcfmt(nump2, numdeltas))
2308 + fmt % pcfmt(nump2, numdeltas))
2305 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2309 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2306 numdeltas))
2310 numdeltas))
2307
2311
2308 @command('debugrevlogindex', cmdutil.debugrevlogopts +
2312 @command('debugrevlogindex', cmdutil.debugrevlogopts +
2309 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2313 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2310 _('[-f FORMAT] -c|-m|FILE'),
2314 _('[-f FORMAT] -c|-m|FILE'),
2311 optionalrepo=True)
2315 optionalrepo=True)
2312 def debugrevlogindex(ui, repo, file_=None, **opts):
2316 def debugrevlogindex(ui, repo, file_=None, **opts):
2313 """dump the contents of a revlog index"""
2317 """dump the contents of a revlog index"""
2314 opts = pycompat.byteskwargs(opts)
2318 opts = pycompat.byteskwargs(opts)
2315 r = cmdutil.openrevlog(repo, 'debugrevlogindex', file_, opts)
2319 r = cmdutil.openrevlog(repo, 'debugrevlogindex', file_, opts)
2316 format = opts.get('format', 0)
2320 format = opts.get('format', 0)
2317 if format not in (0, 1):
2321 if format not in (0, 1):
2318 raise error.Abort(_("unknown format %d") % format)
2322 raise error.Abort(_("unknown format %d") % format)
2319
2323
2320 if ui.debugflag:
2324 if ui.debugflag:
2321 shortfn = hex
2325 shortfn = hex
2322 else:
2326 else:
2323 shortfn = short
2327 shortfn = short
2324
2328
2325 # There might not be anything in r, so have a sane default
2329 # There might not be anything in r, so have a sane default
2326 idlen = 12
2330 idlen = 12
2327 for i in r:
2331 for i in r:
2328 idlen = len(shortfn(r.node(i)))
2332 idlen = len(shortfn(r.node(i)))
2329 break
2333 break
2330
2334
2331 if format == 0:
2335 if format == 0:
2332 if ui.verbose:
2336 if ui.verbose:
2333 ui.write((" rev offset length linkrev"
2337 ui.write((" rev offset length linkrev"
2334 " %s %s p2\n") % ("nodeid".ljust(idlen),
2338 " %s %s p2\n") % ("nodeid".ljust(idlen),
2335 "p1".ljust(idlen)))
2339 "p1".ljust(idlen)))
2336 else:
2340 else:
2337 ui.write((" rev linkrev %s %s p2\n") % (
2341 ui.write((" rev linkrev %s %s p2\n") % (
2338 "nodeid".ljust(idlen), "p1".ljust(idlen)))
2342 "nodeid".ljust(idlen), "p1".ljust(idlen)))
2339 elif format == 1:
2343 elif format == 1:
2340 if ui.verbose:
2344 if ui.verbose:
2341 ui.write((" rev flag offset length size link p1"
2345 ui.write((" rev flag offset length size link p1"
2342 " p2 %s\n") % "nodeid".rjust(idlen))
2346 " p2 %s\n") % "nodeid".rjust(idlen))
2343 else:
2347 else:
2344 ui.write((" rev flag size link p1 p2 %s\n") %
2348 ui.write((" rev flag size link p1 p2 %s\n") %
2345 "nodeid".rjust(idlen))
2349 "nodeid".rjust(idlen))
2346
2350
2347 for i in r:
2351 for i in r:
2348 node = r.node(i)
2352 node = r.node(i)
2349 if format == 0:
2353 if format == 0:
2350 try:
2354 try:
2351 pp = r.parents(node)
2355 pp = r.parents(node)
2352 except Exception:
2356 except Exception:
2353 pp = [nullid, nullid]
2357 pp = [nullid, nullid]
2354 if ui.verbose:
2358 if ui.verbose:
2355 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
2359 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
2356 i, r.start(i), r.length(i), r.linkrev(i),
2360 i, r.start(i), r.length(i), r.linkrev(i),
2357 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2361 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2358 else:
2362 else:
2359 ui.write("% 6d % 7d %s %s %s\n" % (
2363 ui.write("% 6d % 7d %s %s %s\n" % (
2360 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
2364 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
2361 shortfn(pp[1])))
2365 shortfn(pp[1])))
2362 elif format == 1:
2366 elif format == 1:
2363 pr = r.parentrevs(i)
2367 pr = r.parentrevs(i)
2364 if ui.verbose:
2368 if ui.verbose:
2365 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
2369 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
2366 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2370 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2367 r.linkrev(i), pr[0], pr[1], shortfn(node)))
2371 r.linkrev(i), pr[0], pr[1], shortfn(node)))
2368 else:
2372 else:
2369 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
2373 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
2370 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
2374 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
2371 shortfn(node)))
2375 shortfn(node)))
2372
2376
2373 @command('debugrevspec',
2377 @command('debugrevspec',
2374 [('', 'optimize', None,
2378 [('', 'optimize', None,
2375 _('print parsed tree after optimizing (DEPRECATED)')),
2379 _('print parsed tree after optimizing (DEPRECATED)')),
2376 ('', 'show-revs', True, _('print list of result revisions (default)')),
2380 ('', 'show-revs', True, _('print list of result revisions (default)')),
2377 ('s', 'show-set', None, _('print internal representation of result set')),
2381 ('s', 'show-set', None, _('print internal representation of result set')),
2378 ('p', 'show-stage', [],
2382 ('p', 'show-stage', [],
2379 _('print parsed tree at the given stage'), _('NAME')),
2383 _('print parsed tree at the given stage'), _('NAME')),
2380 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2384 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2381 ('', 'verify-optimized', False, _('verify optimized result')),
2385 ('', 'verify-optimized', False, _('verify optimized result')),
2382 ],
2386 ],
2383 ('REVSPEC'))
2387 ('REVSPEC'))
2384 def debugrevspec(ui, repo, expr, **opts):
2388 def debugrevspec(ui, repo, expr, **opts):
2385 """parse and apply a revision specification
2389 """parse and apply a revision specification
2386
2390
2387 Use -p/--show-stage option to print the parsed tree at the given stages.
2391 Use -p/--show-stage option to print the parsed tree at the given stages.
2388 Use -p all to print tree at every stage.
2392 Use -p all to print tree at every stage.
2389
2393
2390 Use --no-show-revs option with -s or -p to print only the set
2394 Use --no-show-revs option with -s or -p to print only the set
2391 representation or the parsed tree respectively.
2395 representation or the parsed tree respectively.
2392
2396
2393 Use --verify-optimized to compare the optimized result with the unoptimized
2397 Use --verify-optimized to compare the optimized result with the unoptimized
2394 one. Returns 1 if the optimized result differs.
2398 one. Returns 1 if the optimized result differs.
2395 """
2399 """
2396 opts = pycompat.byteskwargs(opts)
2400 opts = pycompat.byteskwargs(opts)
2397 aliases = ui.configitems('revsetalias')
2401 aliases = ui.configitems('revsetalias')
2398 stages = [
2402 stages = [
2399 ('parsed', lambda tree: tree),
2403 ('parsed', lambda tree: tree),
2400 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2404 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2401 ui.warn)),
2405 ui.warn)),
2402 ('concatenated', revsetlang.foldconcat),
2406 ('concatenated', revsetlang.foldconcat),
2403 ('analyzed', revsetlang.analyze),
2407 ('analyzed', revsetlang.analyze),
2404 ('optimized', revsetlang.optimize),
2408 ('optimized', revsetlang.optimize),
2405 ]
2409 ]
2406 if opts['no_optimized']:
2410 if opts['no_optimized']:
2407 stages = stages[:-1]
2411 stages = stages[:-1]
2408 if opts['verify_optimized'] and opts['no_optimized']:
2412 if opts['verify_optimized'] and opts['no_optimized']:
2409 raise error.Abort(_('cannot use --verify-optimized with '
2413 raise error.Abort(_('cannot use --verify-optimized with '
2410 '--no-optimized'))
2414 '--no-optimized'))
2411 stagenames = set(n for n, f in stages)
2415 stagenames = set(n for n, f in stages)
2412
2416
2413 showalways = set()
2417 showalways = set()
2414 showchanged = set()
2418 showchanged = set()
2415 if ui.verbose and not opts['show_stage']:
2419 if ui.verbose and not opts['show_stage']:
2416 # show parsed tree by --verbose (deprecated)
2420 # show parsed tree by --verbose (deprecated)
2417 showalways.add('parsed')
2421 showalways.add('parsed')
2418 showchanged.update(['expanded', 'concatenated'])
2422 showchanged.update(['expanded', 'concatenated'])
2419 if opts['optimize']:
2423 if opts['optimize']:
2420 showalways.add('optimized')
2424 showalways.add('optimized')
2421 if opts['show_stage'] and opts['optimize']:
2425 if opts['show_stage'] and opts['optimize']:
2422 raise error.Abort(_('cannot use --optimize with --show-stage'))
2426 raise error.Abort(_('cannot use --optimize with --show-stage'))
2423 if opts['show_stage'] == ['all']:
2427 if opts['show_stage'] == ['all']:
2424 showalways.update(stagenames)
2428 showalways.update(stagenames)
2425 else:
2429 else:
2426 for n in opts['show_stage']:
2430 for n in opts['show_stage']:
2427 if n not in stagenames:
2431 if n not in stagenames:
2428 raise error.Abort(_('invalid stage name: %s') % n)
2432 raise error.Abort(_('invalid stage name: %s') % n)
2429 showalways.update(opts['show_stage'])
2433 showalways.update(opts['show_stage'])
2430
2434
2431 treebystage = {}
2435 treebystage = {}
2432 printedtree = None
2436 printedtree = None
2433 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
2437 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
2434 for n, f in stages:
2438 for n, f in stages:
2435 treebystage[n] = tree = f(tree)
2439 treebystage[n] = tree = f(tree)
2436 if n in showalways or (n in showchanged and tree != printedtree):
2440 if n in showalways or (n in showchanged and tree != printedtree):
2437 if opts['show_stage'] or n != 'parsed':
2441 if opts['show_stage'] or n != 'parsed':
2438 ui.write(("* %s:\n") % n)
2442 ui.write(("* %s:\n") % n)
2439 ui.write(revsetlang.prettyformat(tree), "\n")
2443 ui.write(revsetlang.prettyformat(tree), "\n")
2440 printedtree = tree
2444 printedtree = tree
2441
2445
2442 if opts['verify_optimized']:
2446 if opts['verify_optimized']:
2443 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2447 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2444 brevs = revset.makematcher(treebystage['optimized'])(repo)
2448 brevs = revset.makematcher(treebystage['optimized'])(repo)
2445 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2449 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2446 ui.write(("* analyzed set:\n"), stringutil.prettyrepr(arevs), "\n")
2450 ui.write(("* analyzed set:\n"), stringutil.prettyrepr(arevs), "\n")
2447 ui.write(("* optimized set:\n"), stringutil.prettyrepr(brevs), "\n")
2451 ui.write(("* optimized set:\n"), stringutil.prettyrepr(brevs), "\n")
2448 arevs = list(arevs)
2452 arevs = list(arevs)
2449 brevs = list(brevs)
2453 brevs = list(brevs)
2450 if arevs == brevs:
2454 if arevs == brevs:
2451 return 0
2455 return 0
2452 ui.write(('--- analyzed\n'), label='diff.file_a')
2456 ui.write(('--- analyzed\n'), label='diff.file_a')
2453 ui.write(('+++ optimized\n'), label='diff.file_b')
2457 ui.write(('+++ optimized\n'), label='diff.file_b')
2454 sm = difflib.SequenceMatcher(None, arevs, brevs)
2458 sm = difflib.SequenceMatcher(None, arevs, brevs)
2455 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2459 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2456 if tag in ('delete', 'replace'):
2460 if tag in ('delete', 'replace'):
2457 for c in arevs[alo:ahi]:
2461 for c in arevs[alo:ahi]:
2458 ui.write('-%s\n' % c, label='diff.deleted')
2462 ui.write('-%s\n' % c, label='diff.deleted')
2459 if tag in ('insert', 'replace'):
2463 if tag in ('insert', 'replace'):
2460 for c in brevs[blo:bhi]:
2464 for c in brevs[blo:bhi]:
2461 ui.write('+%s\n' % c, label='diff.inserted')
2465 ui.write('+%s\n' % c, label='diff.inserted')
2462 if tag == 'equal':
2466 if tag == 'equal':
2463 for c in arevs[alo:ahi]:
2467 for c in arevs[alo:ahi]:
2464 ui.write(' %s\n' % c)
2468 ui.write(' %s\n' % c)
2465 return 1
2469 return 1
2466
2470
2467 func = revset.makematcher(tree)
2471 func = revset.makematcher(tree)
2468 revs = func(repo)
2472 revs = func(repo)
2469 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2473 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2470 ui.write(("* set:\n"), stringutil.prettyrepr(revs), "\n")
2474 ui.write(("* set:\n"), stringutil.prettyrepr(revs), "\n")
2471 if not opts['show_revs']:
2475 if not opts['show_revs']:
2472 return
2476 return
2473 for c in revs:
2477 for c in revs:
2474 ui.write("%d\n" % c)
2478 ui.write("%d\n" % c)
2475
2479
2476 @command('debugserve', [
2480 @command('debugserve', [
2477 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2481 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2478 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2482 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2479 ('', 'logiofile', '', _('file to log server I/O to')),
2483 ('', 'logiofile', '', _('file to log server I/O to')),
2480 ], '')
2484 ], '')
2481 def debugserve(ui, repo, **opts):
2485 def debugserve(ui, repo, **opts):
2482 """run a server with advanced settings
2486 """run a server with advanced settings
2483
2487
2484 This command is similar to :hg:`serve`. It exists partially as a
2488 This command is similar to :hg:`serve`. It exists partially as a
2485 workaround to the fact that ``hg serve --stdio`` must have specific
2489 workaround to the fact that ``hg serve --stdio`` must have specific
2486 arguments for security reasons.
2490 arguments for security reasons.
2487 """
2491 """
2488 opts = pycompat.byteskwargs(opts)
2492 opts = pycompat.byteskwargs(opts)
2489
2493
2490 if not opts['sshstdio']:
2494 if not opts['sshstdio']:
2491 raise error.Abort(_('only --sshstdio is currently supported'))
2495 raise error.Abort(_('only --sshstdio is currently supported'))
2492
2496
2493 logfh = None
2497 logfh = None
2494
2498
2495 if opts['logiofd'] and opts['logiofile']:
2499 if opts['logiofd'] and opts['logiofile']:
2496 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2500 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2497
2501
2498 if opts['logiofd']:
2502 if opts['logiofd']:
2499 # Line buffered because output is line based.
2503 # Line buffered because output is line based.
2500 try:
2504 try:
2501 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2505 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2502 except OSError as e:
2506 except OSError as e:
2503 if e.errno != errno.ESPIPE:
2507 if e.errno != errno.ESPIPE:
2504 raise
2508 raise
2505 # can't seek a pipe, so `ab` mode fails on py3
2509 # can't seek a pipe, so `ab` mode fails on py3
2506 logfh = os.fdopen(int(opts['logiofd']), r'wb', 1)
2510 logfh = os.fdopen(int(opts['logiofd']), r'wb', 1)
2507 elif opts['logiofile']:
2511 elif opts['logiofile']:
2508 logfh = open(opts['logiofile'], 'ab', 1)
2512 logfh = open(opts['logiofile'], 'ab', 1)
2509
2513
2510 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2514 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2511 s.serve_forever()
2515 s.serve_forever()
2512
2516
2513 @command('debugsetparents', [], _('REV1 [REV2]'))
2517 @command('debugsetparents', [], _('REV1 [REV2]'))
2514 def debugsetparents(ui, repo, rev1, rev2=None):
2518 def debugsetparents(ui, repo, rev1, rev2=None):
2515 """manually set the parents of the current working directory
2519 """manually set the parents of the current working directory
2516
2520
2517 This is useful for writing repository conversion tools, but should
2521 This is useful for writing repository conversion tools, but should
2518 be used with care. For example, neither the working directory nor the
2522 be used with care. For example, neither the working directory nor the
2519 dirstate is updated, so file status may be incorrect after running this
2523 dirstate is updated, so file status may be incorrect after running this
2520 command.
2524 command.
2521
2525
2522 Returns 0 on success.
2526 Returns 0 on success.
2523 """
2527 """
2524
2528
2525 node1 = scmutil.revsingle(repo, rev1).node()
2529 node1 = scmutil.revsingle(repo, rev1).node()
2526 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2530 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2527
2531
2528 with repo.wlock():
2532 with repo.wlock():
2529 repo.setparents(node1, node2)
2533 repo.setparents(node1, node2)
2530
2534
2531 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2535 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2532 def debugssl(ui, repo, source=None, **opts):
2536 def debugssl(ui, repo, source=None, **opts):
2533 '''test a secure connection to a server
2537 '''test a secure connection to a server
2534
2538
2535 This builds the certificate chain for the server on Windows, installing the
2539 This builds the certificate chain for the server on Windows, installing the
2536 missing intermediates and trusted root via Windows Update if necessary. It
2540 missing intermediates and trusted root via Windows Update if necessary. It
2537 does nothing on other platforms.
2541 does nothing on other platforms.
2538
2542
2539 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2543 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2540 that server is used. See :hg:`help urls` for more information.
2544 that server is used. See :hg:`help urls` for more information.
2541
2545
2542 If the update succeeds, retry the original operation. Otherwise, the cause
2546 If the update succeeds, retry the original operation. Otherwise, the cause
2543 of the SSL error is likely another issue.
2547 of the SSL error is likely another issue.
2544 '''
2548 '''
2545 if not pycompat.iswindows:
2549 if not pycompat.iswindows:
2546 raise error.Abort(_('certificate chain building is only possible on '
2550 raise error.Abort(_('certificate chain building is only possible on '
2547 'Windows'))
2551 'Windows'))
2548
2552
2549 if not source:
2553 if not source:
2550 if not repo:
2554 if not repo:
2551 raise error.Abort(_("there is no Mercurial repository here, and no "
2555 raise error.Abort(_("there is no Mercurial repository here, and no "
2552 "server specified"))
2556 "server specified"))
2553 source = "default"
2557 source = "default"
2554
2558
2555 source, branches = hg.parseurl(ui.expandpath(source))
2559 source, branches = hg.parseurl(ui.expandpath(source))
2556 url = util.url(source)
2560 url = util.url(source)
2557 addr = None
2561 addr = None
2558
2562
2559 defaultport = {'https': 443, 'ssh': 22}
2563 defaultport = {'https': 443, 'ssh': 22}
2560 if url.scheme in defaultport:
2564 if url.scheme in defaultport:
2561 try:
2565 try:
2562 addr = (url.host, int(url.port or defaultport[url.scheme]))
2566 addr = (url.host, int(url.port or defaultport[url.scheme]))
2563 except ValueError:
2567 except ValueError:
2564 raise error.Abort(_("malformed port number in URL"))
2568 raise error.Abort(_("malformed port number in URL"))
2565 else:
2569 else:
2566 raise error.Abort(_("only https and ssh connections are supported"))
2570 raise error.Abort(_("only https and ssh connections are supported"))
2567
2571
2568 from . import win32
2572 from . import win32
2569
2573
2570 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2574 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2571 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2575 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2572
2576
2573 try:
2577 try:
2574 s.connect(addr)
2578 s.connect(addr)
2575 cert = s.getpeercert(True)
2579 cert = s.getpeercert(True)
2576
2580
2577 ui.status(_('checking the certificate chain for %s\n') % url.host)
2581 ui.status(_('checking the certificate chain for %s\n') % url.host)
2578
2582
2579 complete = win32.checkcertificatechain(cert, build=False)
2583 complete = win32.checkcertificatechain(cert, build=False)
2580
2584
2581 if not complete:
2585 if not complete:
2582 ui.status(_('certificate chain is incomplete, updating... '))
2586 ui.status(_('certificate chain is incomplete, updating... '))
2583
2587
2584 if not win32.checkcertificatechain(cert):
2588 if not win32.checkcertificatechain(cert):
2585 ui.status(_('failed.\n'))
2589 ui.status(_('failed.\n'))
2586 else:
2590 else:
2587 ui.status(_('done.\n'))
2591 ui.status(_('done.\n'))
2588 else:
2592 else:
2589 ui.status(_('full certificate chain is available\n'))
2593 ui.status(_('full certificate chain is available\n'))
2590 finally:
2594 finally:
2591 s.close()
2595 s.close()
2592
2596
2593 @command('debugsub',
2597 @command('debugsub',
2594 [('r', 'rev', '',
2598 [('r', 'rev', '',
2595 _('revision to check'), _('REV'))],
2599 _('revision to check'), _('REV'))],
2596 _('[-r REV] [REV]'))
2600 _('[-r REV] [REV]'))
2597 def debugsub(ui, repo, rev=None):
2601 def debugsub(ui, repo, rev=None):
2598 ctx = scmutil.revsingle(repo, rev, None)
2602 ctx = scmutil.revsingle(repo, rev, None)
2599 for k, v in sorted(ctx.substate.items()):
2603 for k, v in sorted(ctx.substate.items()):
2600 ui.write(('path %s\n') % k)
2604 ui.write(('path %s\n') % k)
2601 ui.write((' source %s\n') % v[0])
2605 ui.write((' source %s\n') % v[0])
2602 ui.write((' revision %s\n') % v[1])
2606 ui.write((' revision %s\n') % v[1])
2603
2607
2604 @command('debugsuccessorssets',
2608 @command('debugsuccessorssets',
2605 [('', 'closest', False, _('return closest successors sets only'))],
2609 [('', 'closest', False, _('return closest successors sets only'))],
2606 _('[REV]'))
2610 _('[REV]'))
2607 def debugsuccessorssets(ui, repo, *revs, **opts):
2611 def debugsuccessorssets(ui, repo, *revs, **opts):
2608 """show set of successors for revision
2612 """show set of successors for revision
2609
2613
2610 A successors set of changeset A is a consistent group of revisions that
2614 A successors set of changeset A is a consistent group of revisions that
2611 succeed A. It contains non-obsolete changesets only unless closests
2615 succeed A. It contains non-obsolete changesets only unless closests
2612 successors set is set.
2616 successors set is set.
2613
2617
2614 In most cases a changeset A has a single successors set containing a single
2618 In most cases a changeset A has a single successors set containing a single
2615 successor (changeset A replaced by A').
2619 successor (changeset A replaced by A').
2616
2620
2617 A changeset that is made obsolete with no successors are called "pruned".
2621 A changeset that is made obsolete with no successors are called "pruned".
2618 Such changesets have no successors sets at all.
2622 Such changesets have no successors sets at all.
2619
2623
2620 A changeset that has been "split" will have a successors set containing
2624 A changeset that has been "split" will have a successors set containing
2621 more than one successor.
2625 more than one successor.
2622
2626
2623 A changeset that has been rewritten in multiple different ways is called
2627 A changeset that has been rewritten in multiple different ways is called
2624 "divergent". Such changesets have multiple successor sets (each of which
2628 "divergent". Such changesets have multiple successor sets (each of which
2625 may also be split, i.e. have multiple successors).
2629 may also be split, i.e. have multiple successors).
2626
2630
2627 Results are displayed as follows::
2631 Results are displayed as follows::
2628
2632
2629 <rev1>
2633 <rev1>
2630 <successors-1A>
2634 <successors-1A>
2631 <rev2>
2635 <rev2>
2632 <successors-2A>
2636 <successors-2A>
2633 <successors-2B1> <successors-2B2> <successors-2B3>
2637 <successors-2B1> <successors-2B2> <successors-2B3>
2634
2638
2635 Here rev2 has two possible (i.e. divergent) successors sets. The first
2639 Here rev2 has two possible (i.e. divergent) successors sets. The first
2636 holds one element, whereas the second holds three (i.e. the changeset has
2640 holds one element, whereas the second holds three (i.e. the changeset has
2637 been split).
2641 been split).
2638 """
2642 """
2639 # passed to successorssets caching computation from one call to another
2643 # passed to successorssets caching computation from one call to another
2640 cache = {}
2644 cache = {}
2641 ctx2str = bytes
2645 ctx2str = bytes
2642 node2str = short
2646 node2str = short
2643 for rev in scmutil.revrange(repo, revs):
2647 for rev in scmutil.revrange(repo, revs):
2644 ctx = repo[rev]
2648 ctx = repo[rev]
2645 ui.write('%s\n'% ctx2str(ctx))
2649 ui.write('%s\n'% ctx2str(ctx))
2646 for succsset in obsutil.successorssets(repo, ctx.node(),
2650 for succsset in obsutil.successorssets(repo, ctx.node(),
2647 closest=opts[r'closest'],
2651 closest=opts[r'closest'],
2648 cache=cache):
2652 cache=cache):
2649 if succsset:
2653 if succsset:
2650 ui.write(' ')
2654 ui.write(' ')
2651 ui.write(node2str(succsset[0]))
2655 ui.write(node2str(succsset[0]))
2652 for node in succsset[1:]:
2656 for node in succsset[1:]:
2653 ui.write(' ')
2657 ui.write(' ')
2654 ui.write(node2str(node))
2658 ui.write(node2str(node))
2655 ui.write('\n')
2659 ui.write('\n')
2656
2660
2657 @command('debugtemplate',
2661 @command('debugtemplate',
2658 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2662 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2659 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2663 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2660 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2664 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2661 optionalrepo=True)
2665 optionalrepo=True)
2662 def debugtemplate(ui, repo, tmpl, **opts):
2666 def debugtemplate(ui, repo, tmpl, **opts):
2663 """parse and apply a template
2667 """parse and apply a template
2664
2668
2665 If -r/--rev is given, the template is processed as a log template and
2669 If -r/--rev is given, the template is processed as a log template and
2666 applied to the given changesets. Otherwise, it is processed as a generic
2670 applied to the given changesets. Otherwise, it is processed as a generic
2667 template.
2671 template.
2668
2672
2669 Use --verbose to print the parsed tree.
2673 Use --verbose to print the parsed tree.
2670 """
2674 """
2671 revs = None
2675 revs = None
2672 if opts[r'rev']:
2676 if opts[r'rev']:
2673 if repo is None:
2677 if repo is None:
2674 raise error.RepoError(_('there is no Mercurial repository here '
2678 raise error.RepoError(_('there is no Mercurial repository here '
2675 '(.hg not found)'))
2679 '(.hg not found)'))
2676 revs = scmutil.revrange(repo, opts[r'rev'])
2680 revs = scmutil.revrange(repo, opts[r'rev'])
2677
2681
2678 props = {}
2682 props = {}
2679 for d in opts[r'define']:
2683 for d in opts[r'define']:
2680 try:
2684 try:
2681 k, v = (e.strip() for e in d.split('=', 1))
2685 k, v = (e.strip() for e in d.split('=', 1))
2682 if not k or k == 'ui':
2686 if not k or k == 'ui':
2683 raise ValueError
2687 raise ValueError
2684 props[k] = v
2688 props[k] = v
2685 except ValueError:
2689 except ValueError:
2686 raise error.Abort(_('malformed keyword definition: %s') % d)
2690 raise error.Abort(_('malformed keyword definition: %s') % d)
2687
2691
2688 if ui.verbose:
2692 if ui.verbose:
2689 aliases = ui.configitems('templatealias')
2693 aliases = ui.configitems('templatealias')
2690 tree = templater.parse(tmpl)
2694 tree = templater.parse(tmpl)
2691 ui.note(templater.prettyformat(tree), '\n')
2695 ui.note(templater.prettyformat(tree), '\n')
2692 newtree = templater.expandaliases(tree, aliases)
2696 newtree = templater.expandaliases(tree, aliases)
2693 if newtree != tree:
2697 if newtree != tree:
2694 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2698 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2695
2699
2696 if revs is None:
2700 if revs is None:
2697 tres = formatter.templateresources(ui, repo)
2701 tres = formatter.templateresources(ui, repo)
2698 t = formatter.maketemplater(ui, tmpl, resources=tres)
2702 t = formatter.maketemplater(ui, tmpl, resources=tres)
2699 if ui.verbose:
2703 if ui.verbose:
2700 kwds, funcs = t.symbolsuseddefault()
2704 kwds, funcs = t.symbolsuseddefault()
2701 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2705 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2702 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2706 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2703 ui.write(t.renderdefault(props))
2707 ui.write(t.renderdefault(props))
2704 else:
2708 else:
2705 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2709 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2706 if ui.verbose:
2710 if ui.verbose:
2707 kwds, funcs = displayer.t.symbolsuseddefault()
2711 kwds, funcs = displayer.t.symbolsuseddefault()
2708 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2712 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2709 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2713 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2710 for r in revs:
2714 for r in revs:
2711 displayer.show(repo[r], **pycompat.strkwargs(props))
2715 displayer.show(repo[r], **pycompat.strkwargs(props))
2712 displayer.close()
2716 displayer.close()
2713
2717
2714 @command('debuguigetpass', [
2718 @command('debuguigetpass', [
2715 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2719 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2716 ], _('[-p TEXT]'), norepo=True)
2720 ], _('[-p TEXT]'), norepo=True)
2717 def debuguigetpass(ui, prompt=''):
2721 def debuguigetpass(ui, prompt=''):
2718 """show prompt to type password"""
2722 """show prompt to type password"""
2719 r = ui.getpass(prompt)
2723 r = ui.getpass(prompt)
2720 ui.write(('respose: %s\n') % r)
2724 ui.write(('respose: %s\n') % r)
2721
2725
2722 @command('debuguiprompt', [
2726 @command('debuguiprompt', [
2723 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2727 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2724 ], _('[-p TEXT]'), norepo=True)
2728 ], _('[-p TEXT]'), norepo=True)
2725 def debuguiprompt(ui, prompt=''):
2729 def debuguiprompt(ui, prompt=''):
2726 """show plain prompt"""
2730 """show plain prompt"""
2727 r = ui.prompt(prompt)
2731 r = ui.prompt(prompt)
2728 ui.write(('response: %s\n') % r)
2732 ui.write(('response: %s\n') % r)
2729
2733
2730 @command('debugupdatecaches', [])
2734 @command('debugupdatecaches', [])
2731 def debugupdatecaches(ui, repo, *pats, **opts):
2735 def debugupdatecaches(ui, repo, *pats, **opts):
2732 """warm all known caches in the repository"""
2736 """warm all known caches in the repository"""
2733 with repo.wlock(), repo.lock():
2737 with repo.wlock(), repo.lock():
2734 repo.updatecaches(full=True)
2738 repo.updatecaches(full=True)
2735
2739
2736 @command('debugupgraderepo', [
2740 @command('debugupgraderepo', [
2737 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2741 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2738 ('', 'run', False, _('performs an upgrade')),
2742 ('', 'run', False, _('performs an upgrade')),
2739 ])
2743 ])
2740 def debugupgraderepo(ui, repo, run=False, optimize=None):
2744 def debugupgraderepo(ui, repo, run=False, optimize=None):
2741 """upgrade a repository to use different features
2745 """upgrade a repository to use different features
2742
2746
2743 If no arguments are specified, the repository is evaluated for upgrade
2747 If no arguments are specified, the repository is evaluated for upgrade
2744 and a list of problems and potential optimizations is printed.
2748 and a list of problems and potential optimizations is printed.
2745
2749
2746 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2750 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2747 can be influenced via additional arguments. More details will be provided
2751 can be influenced via additional arguments. More details will be provided
2748 by the command output when run without ``--run``.
2752 by the command output when run without ``--run``.
2749
2753
2750 During the upgrade, the repository will be locked and no writes will be
2754 During the upgrade, the repository will be locked and no writes will be
2751 allowed.
2755 allowed.
2752
2756
2753 At the end of the upgrade, the repository may not be readable while new
2757 At the end of the upgrade, the repository may not be readable while new
2754 repository data is swapped in. This window will be as long as it takes to
2758 repository data is swapped in. This window will be as long as it takes to
2755 rename some directories inside the ``.hg`` directory. On most machines, this
2759 rename some directories inside the ``.hg`` directory. On most machines, this
2756 should complete almost instantaneously and the chances of a consumer being
2760 should complete almost instantaneously and the chances of a consumer being
2757 unable to access the repository should be low.
2761 unable to access the repository should be low.
2758 """
2762 """
2759 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2763 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2760
2764
2761 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2765 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2762 inferrepo=True)
2766 inferrepo=True)
2763 def debugwalk(ui, repo, *pats, **opts):
2767 def debugwalk(ui, repo, *pats, **opts):
2764 """show how files match on given patterns"""
2768 """show how files match on given patterns"""
2765 opts = pycompat.byteskwargs(opts)
2769 opts = pycompat.byteskwargs(opts)
2766 m = scmutil.match(repo[None], pats, opts)
2770 m = scmutil.match(repo[None], pats, opts)
2767 if ui.verbose:
2771 if ui.verbose:
2768 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
2772 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
2769 items = list(repo[None].walk(m))
2773 items = list(repo[None].walk(m))
2770 if not items:
2774 if not items:
2771 return
2775 return
2772 f = lambda fn: fn
2776 f = lambda fn: fn
2773 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2777 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2774 f = lambda fn: util.normpath(fn)
2778 f = lambda fn: util.normpath(fn)
2775 fmt = 'f %%-%ds %%-%ds %%s' % (
2779 fmt = 'f %%-%ds %%-%ds %%s' % (
2776 max([len(abs) for abs in items]),
2780 max([len(abs) for abs in items]),
2777 max([len(m.rel(abs)) for abs in items]))
2781 max([len(m.rel(abs)) for abs in items]))
2778 for abs in items:
2782 for abs in items:
2779 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2783 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2780 ui.write("%s\n" % line.rstrip())
2784 ui.write("%s\n" % line.rstrip())
2781
2785
2782 @command('debugwhyunstable', [], _('REV'))
2786 @command('debugwhyunstable', [], _('REV'))
2783 def debugwhyunstable(ui, repo, rev):
2787 def debugwhyunstable(ui, repo, rev):
2784 """explain instabilities of a changeset"""
2788 """explain instabilities of a changeset"""
2785 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
2789 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
2786 dnodes = ''
2790 dnodes = ''
2787 if entry.get('divergentnodes'):
2791 if entry.get('divergentnodes'):
2788 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2792 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2789 for ctx in entry['divergentnodes']) + ' '
2793 for ctx in entry['divergentnodes']) + ' '
2790 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2794 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2791 entry['reason'], entry['node']))
2795 entry['reason'], entry['node']))
2792
2796
2793 @command('debugwireargs',
2797 @command('debugwireargs',
2794 [('', 'three', '', 'three'),
2798 [('', 'three', '', 'three'),
2795 ('', 'four', '', 'four'),
2799 ('', 'four', '', 'four'),
2796 ('', 'five', '', 'five'),
2800 ('', 'five', '', 'five'),
2797 ] + cmdutil.remoteopts,
2801 ] + cmdutil.remoteopts,
2798 _('REPO [OPTIONS]... [ONE [TWO]]'),
2802 _('REPO [OPTIONS]... [ONE [TWO]]'),
2799 norepo=True)
2803 norepo=True)
2800 def debugwireargs(ui, repopath, *vals, **opts):
2804 def debugwireargs(ui, repopath, *vals, **opts):
2801 opts = pycompat.byteskwargs(opts)
2805 opts = pycompat.byteskwargs(opts)
2802 repo = hg.peer(ui, opts, repopath)
2806 repo = hg.peer(ui, opts, repopath)
2803 for opt in cmdutil.remoteopts:
2807 for opt in cmdutil.remoteopts:
2804 del opts[opt[1]]
2808 del opts[opt[1]]
2805 args = {}
2809 args = {}
2806 for k, v in opts.iteritems():
2810 for k, v in opts.iteritems():
2807 if v:
2811 if v:
2808 args[k] = v
2812 args[k] = v
2809 args = pycompat.strkwargs(args)
2813 args = pycompat.strkwargs(args)
2810 # run twice to check that we don't mess up the stream for the next command
2814 # run twice to check that we don't mess up the stream for the next command
2811 res1 = repo.debugwireargs(*vals, **args)
2815 res1 = repo.debugwireargs(*vals, **args)
2812 res2 = repo.debugwireargs(*vals, **args)
2816 res2 = repo.debugwireargs(*vals, **args)
2813 ui.write("%s\n" % res1)
2817 ui.write("%s\n" % res1)
2814 if res1 != res2:
2818 if res1 != res2:
2815 ui.warn("%s\n" % res2)
2819 ui.warn("%s\n" % res2)
2816
2820
2817 def _parsewirelangblocks(fh):
2821 def _parsewirelangblocks(fh):
2818 activeaction = None
2822 activeaction = None
2819 blocklines = []
2823 blocklines = []
2820
2824
2821 for line in fh:
2825 for line in fh:
2822 line = line.rstrip()
2826 line = line.rstrip()
2823 if not line:
2827 if not line:
2824 continue
2828 continue
2825
2829
2826 if line.startswith(b'#'):
2830 if line.startswith(b'#'):
2827 continue
2831 continue
2828
2832
2829 if not line.startswith(b' '):
2833 if not line.startswith(b' '):
2830 # New block. Flush previous one.
2834 # New block. Flush previous one.
2831 if activeaction:
2835 if activeaction:
2832 yield activeaction, blocklines
2836 yield activeaction, blocklines
2833
2837
2834 activeaction = line
2838 activeaction = line
2835 blocklines = []
2839 blocklines = []
2836 continue
2840 continue
2837
2841
2838 # Else we start with an indent.
2842 # Else we start with an indent.
2839
2843
2840 if not activeaction:
2844 if not activeaction:
2841 raise error.Abort(_('indented line outside of block'))
2845 raise error.Abort(_('indented line outside of block'))
2842
2846
2843 blocklines.append(line)
2847 blocklines.append(line)
2844
2848
2845 # Flush last block.
2849 # Flush last block.
2846 if activeaction:
2850 if activeaction:
2847 yield activeaction, blocklines
2851 yield activeaction, blocklines
2848
2852
2849 @command('debugwireproto',
2853 @command('debugwireproto',
2850 [
2854 [
2851 ('', 'localssh', False, _('start an SSH server for this repo')),
2855 ('', 'localssh', False, _('start an SSH server for this repo')),
2852 ('', 'peer', '', _('construct a specific version of the peer')),
2856 ('', 'peer', '', _('construct a specific version of the peer')),
2853 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2857 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2854 ('', 'nologhandshake', False,
2858 ('', 'nologhandshake', False,
2855 _('do not log I/O related to the peer handshake')),
2859 _('do not log I/O related to the peer handshake')),
2856 ] + cmdutil.remoteopts,
2860 ] + cmdutil.remoteopts,
2857 _('[PATH]'),
2861 _('[PATH]'),
2858 optionalrepo=True)
2862 optionalrepo=True)
2859 def debugwireproto(ui, repo, path=None, **opts):
2863 def debugwireproto(ui, repo, path=None, **opts):
2860 """send wire protocol commands to a server
2864 """send wire protocol commands to a server
2861
2865
2862 This command can be used to issue wire protocol commands to remote
2866 This command can be used to issue wire protocol commands to remote
2863 peers and to debug the raw data being exchanged.
2867 peers and to debug the raw data being exchanged.
2864
2868
2865 ``--localssh`` will start an SSH server against the current repository
2869 ``--localssh`` will start an SSH server against the current repository
2866 and connect to that. By default, the connection will perform a handshake
2870 and connect to that. By default, the connection will perform a handshake
2867 and establish an appropriate peer instance.
2871 and establish an appropriate peer instance.
2868
2872
2869 ``--peer`` can be used to bypass the handshake protocol and construct a
2873 ``--peer`` can be used to bypass the handshake protocol and construct a
2870 peer instance using the specified class type. Valid values are ``raw``,
2874 peer instance using the specified class type. Valid values are ``raw``,
2871 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2875 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2872 raw data payloads and don't support higher-level command actions.
2876 raw data payloads and don't support higher-level command actions.
2873
2877
2874 ``--noreadstderr`` can be used to disable automatic reading from stderr
2878 ``--noreadstderr`` can be used to disable automatic reading from stderr
2875 of the peer (for SSH connections only). Disabling automatic reading of
2879 of the peer (for SSH connections only). Disabling automatic reading of
2876 stderr is useful for making output more deterministic.
2880 stderr is useful for making output more deterministic.
2877
2881
2878 Commands are issued via a mini language which is specified via stdin.
2882 Commands are issued via a mini language which is specified via stdin.
2879 The language consists of individual actions to perform. An action is
2883 The language consists of individual actions to perform. An action is
2880 defined by a block. A block is defined as a line with no leading
2884 defined by a block. A block is defined as a line with no leading
2881 space followed by 0 or more lines with leading space. Blocks are
2885 space followed by 0 or more lines with leading space. Blocks are
2882 effectively a high-level command with additional metadata.
2886 effectively a high-level command with additional metadata.
2883
2887
2884 Lines beginning with ``#`` are ignored.
2888 Lines beginning with ``#`` are ignored.
2885
2889
2886 The following sections denote available actions.
2890 The following sections denote available actions.
2887
2891
2888 raw
2892 raw
2889 ---
2893 ---
2890
2894
2891 Send raw data to the server.
2895 Send raw data to the server.
2892
2896
2893 The block payload contains the raw data to send as one atomic send
2897 The block payload contains the raw data to send as one atomic send
2894 operation. The data may not actually be delivered in a single system
2898 operation. The data may not actually be delivered in a single system
2895 call: it depends on the abilities of the transport being used.
2899 call: it depends on the abilities of the transport being used.
2896
2900
2897 Each line in the block is de-indented and concatenated. Then, that
2901 Each line in the block is de-indented and concatenated. Then, that
2898 value is evaluated as a Python b'' literal. This allows the use of
2902 value is evaluated as a Python b'' literal. This allows the use of
2899 backslash escaping, etc.
2903 backslash escaping, etc.
2900
2904
2901 raw+
2905 raw+
2902 ----
2906 ----
2903
2907
2904 Behaves like ``raw`` except flushes output afterwards.
2908 Behaves like ``raw`` except flushes output afterwards.
2905
2909
2906 command <X>
2910 command <X>
2907 -----------
2911 -----------
2908
2912
2909 Send a request to run a named command, whose name follows the ``command``
2913 Send a request to run a named command, whose name follows the ``command``
2910 string.
2914 string.
2911
2915
2912 Arguments to the command are defined as lines in this block. The format of
2916 Arguments to the command are defined as lines in this block. The format of
2913 each line is ``<key> <value>``. e.g.::
2917 each line is ``<key> <value>``. e.g.::
2914
2918
2915 command listkeys
2919 command listkeys
2916 namespace bookmarks
2920 namespace bookmarks
2917
2921
2918 If the value begins with ``eval:``, it will be interpreted as a Python
2922 If the value begins with ``eval:``, it will be interpreted as a Python
2919 literal expression. Otherwise values are interpreted as Python b'' literals.
2923 literal expression. Otherwise values are interpreted as Python b'' literals.
2920 This allows sending complex types and encoding special byte sequences via
2924 This allows sending complex types and encoding special byte sequences via
2921 backslash escaping.
2925 backslash escaping.
2922
2926
2923 The following arguments have special meaning:
2927 The following arguments have special meaning:
2924
2928
2925 ``PUSHFILE``
2929 ``PUSHFILE``
2926 When defined, the *push* mechanism of the peer will be used instead
2930 When defined, the *push* mechanism of the peer will be used instead
2927 of the static request-response mechanism and the content of the
2931 of the static request-response mechanism and the content of the
2928 file specified in the value of this argument will be sent as the
2932 file specified in the value of this argument will be sent as the
2929 command payload.
2933 command payload.
2930
2934
2931 This can be used to submit a local bundle file to the remote.
2935 This can be used to submit a local bundle file to the remote.
2932
2936
2933 batchbegin
2937 batchbegin
2934 ----------
2938 ----------
2935
2939
2936 Instruct the peer to begin a batched send.
2940 Instruct the peer to begin a batched send.
2937
2941
2938 All ``command`` blocks are queued for execution until the next
2942 All ``command`` blocks are queued for execution until the next
2939 ``batchsubmit`` block.
2943 ``batchsubmit`` block.
2940
2944
2941 batchsubmit
2945 batchsubmit
2942 -----------
2946 -----------
2943
2947
2944 Submit previously queued ``command`` blocks as a batch request.
2948 Submit previously queued ``command`` blocks as a batch request.
2945
2949
2946 This action MUST be paired with a ``batchbegin`` action.
2950 This action MUST be paired with a ``batchbegin`` action.
2947
2951
2948 httprequest <method> <path>
2952 httprequest <method> <path>
2949 ---------------------------
2953 ---------------------------
2950
2954
2951 (HTTP peer only)
2955 (HTTP peer only)
2952
2956
2953 Send an HTTP request to the peer.
2957 Send an HTTP request to the peer.
2954
2958
2955 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2959 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2956
2960
2957 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2961 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2958 headers to add to the request. e.g. ``Accept: foo``.
2962 headers to add to the request. e.g. ``Accept: foo``.
2959
2963
2960 The following arguments are special:
2964 The following arguments are special:
2961
2965
2962 ``BODYFILE``
2966 ``BODYFILE``
2963 The content of the file defined as the value to this argument will be
2967 The content of the file defined as the value to this argument will be
2964 transferred verbatim as the HTTP request body.
2968 transferred verbatim as the HTTP request body.
2965
2969
2966 ``frame <type> <flags> <payload>``
2970 ``frame <type> <flags> <payload>``
2967 Send a unified protocol frame as part of the request body.
2971 Send a unified protocol frame as part of the request body.
2968
2972
2969 All frames will be collected and sent as the body to the HTTP
2973 All frames will be collected and sent as the body to the HTTP
2970 request.
2974 request.
2971
2975
2972 close
2976 close
2973 -----
2977 -----
2974
2978
2975 Close the connection to the server.
2979 Close the connection to the server.
2976
2980
2977 flush
2981 flush
2978 -----
2982 -----
2979
2983
2980 Flush data written to the server.
2984 Flush data written to the server.
2981
2985
2982 readavailable
2986 readavailable
2983 -------------
2987 -------------
2984
2988
2985 Close the write end of the connection and read all available data from
2989 Close the write end of the connection and read all available data from
2986 the server.
2990 the server.
2987
2991
2988 If the connection to the server encompasses multiple pipes, we poll both
2992 If the connection to the server encompasses multiple pipes, we poll both
2989 pipes and read available data.
2993 pipes and read available data.
2990
2994
2991 readline
2995 readline
2992 --------
2996 --------
2993
2997
2994 Read a line of output from the server. If there are multiple output
2998 Read a line of output from the server. If there are multiple output
2995 pipes, reads only the main pipe.
2999 pipes, reads only the main pipe.
2996
3000
2997 ereadline
3001 ereadline
2998 ---------
3002 ---------
2999
3003
3000 Like ``readline``, but read from the stderr pipe, if available.
3004 Like ``readline``, but read from the stderr pipe, if available.
3001
3005
3002 read <X>
3006 read <X>
3003 --------
3007 --------
3004
3008
3005 ``read()`` N bytes from the server's main output pipe.
3009 ``read()`` N bytes from the server's main output pipe.
3006
3010
3007 eread <X>
3011 eread <X>
3008 ---------
3012 ---------
3009
3013
3010 ``read()`` N bytes from the server's stderr pipe, if available.
3014 ``read()`` N bytes from the server's stderr pipe, if available.
3011
3015
3012 Specifying Unified Frame-Based Protocol Frames
3016 Specifying Unified Frame-Based Protocol Frames
3013 ----------------------------------------------
3017 ----------------------------------------------
3014
3018
3015 It is possible to emit a *Unified Frame-Based Protocol* by using special
3019 It is possible to emit a *Unified Frame-Based Protocol* by using special
3016 syntax.
3020 syntax.
3017
3021
3018 A frame is composed as a type, flags, and payload. These can be parsed
3022 A frame is composed as a type, flags, and payload. These can be parsed
3019 from a string of the form:
3023 from a string of the form:
3020
3024
3021 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
3025 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
3022
3026
3023 ``request-id`` and ``stream-id`` are integers defining the request and
3027 ``request-id`` and ``stream-id`` are integers defining the request and
3024 stream identifiers.
3028 stream identifiers.
3025
3029
3026 ``type`` can be an integer value for the frame type or the string name
3030 ``type`` can be an integer value for the frame type or the string name
3027 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
3031 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
3028 ``command-name``.
3032 ``command-name``.
3029
3033
3030 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
3034 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
3031 components. Each component (and there can be just one) can be an integer
3035 components. Each component (and there can be just one) can be an integer
3032 or a flag name for stream flags or frame flags, respectively. Values are
3036 or a flag name for stream flags or frame flags, respectively. Values are
3033 resolved to integers and then bitwise OR'd together.
3037 resolved to integers and then bitwise OR'd together.
3034
3038
3035 ``payload`` represents the raw frame payload. If it begins with
3039 ``payload`` represents the raw frame payload. If it begins with
3036 ``cbor:``, the following string is evaluated as Python code and the
3040 ``cbor:``, the following string is evaluated as Python code and the
3037 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
3041 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
3038 as a Python byte string literal.
3042 as a Python byte string literal.
3039 """
3043 """
3040 opts = pycompat.byteskwargs(opts)
3044 opts = pycompat.byteskwargs(opts)
3041
3045
3042 if opts['localssh'] and not repo:
3046 if opts['localssh'] and not repo:
3043 raise error.Abort(_('--localssh requires a repository'))
3047 raise error.Abort(_('--localssh requires a repository'))
3044
3048
3045 if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'):
3049 if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'):
3046 raise error.Abort(_('invalid value for --peer'),
3050 raise error.Abort(_('invalid value for --peer'),
3047 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
3051 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
3048
3052
3049 if path and opts['localssh']:
3053 if path and opts['localssh']:
3050 raise error.Abort(_('cannot specify --localssh with an explicit '
3054 raise error.Abort(_('cannot specify --localssh with an explicit '
3051 'path'))
3055 'path'))
3052
3056
3053 if ui.interactive():
3057 if ui.interactive():
3054 ui.write(_('(waiting for commands on stdin)\n'))
3058 ui.write(_('(waiting for commands on stdin)\n'))
3055
3059
3056 blocks = list(_parsewirelangblocks(ui.fin))
3060 blocks = list(_parsewirelangblocks(ui.fin))
3057
3061
3058 proc = None
3062 proc = None
3059 stdin = None
3063 stdin = None
3060 stdout = None
3064 stdout = None
3061 stderr = None
3065 stderr = None
3062 opener = None
3066 opener = None
3063
3067
3064 if opts['localssh']:
3068 if opts['localssh']:
3065 # We start the SSH server in its own process so there is process
3069 # We start the SSH server in its own process so there is process
3066 # separation. This prevents a whole class of potential bugs around
3070 # separation. This prevents a whole class of potential bugs around
3067 # shared state from interfering with server operation.
3071 # shared state from interfering with server operation.
3068 args = procutil.hgcmd() + [
3072 args = procutil.hgcmd() + [
3069 '-R', repo.root,
3073 '-R', repo.root,
3070 'debugserve', '--sshstdio',
3074 'debugserve', '--sshstdio',
3071 ]
3075 ]
3072 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
3076 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
3073 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
3077 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
3074 bufsize=0)
3078 bufsize=0)
3075
3079
3076 stdin = proc.stdin
3080 stdin = proc.stdin
3077 stdout = proc.stdout
3081 stdout = proc.stdout
3078 stderr = proc.stderr
3082 stderr = proc.stderr
3079
3083
3080 # We turn the pipes into observers so we can log I/O.
3084 # We turn the pipes into observers so we can log I/O.
3081 if ui.verbose or opts['peer'] == 'raw':
3085 if ui.verbose or opts['peer'] == 'raw':
3082 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
3086 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
3083 logdata=True)
3087 logdata=True)
3084 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
3088 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
3085 logdata=True)
3089 logdata=True)
3086 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
3090 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
3087 logdata=True)
3091 logdata=True)
3088
3092
3089 # --localssh also implies the peer connection settings.
3093 # --localssh also implies the peer connection settings.
3090
3094
3091 url = 'ssh://localserver'
3095 url = 'ssh://localserver'
3092 autoreadstderr = not opts['noreadstderr']
3096 autoreadstderr = not opts['noreadstderr']
3093
3097
3094 if opts['peer'] == 'ssh1':
3098 if opts['peer'] == 'ssh1':
3095 ui.write(_('creating ssh peer for wire protocol version 1\n'))
3099 ui.write(_('creating ssh peer for wire protocol version 1\n'))
3096 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
3100 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
3097 None, autoreadstderr=autoreadstderr)
3101 None, autoreadstderr=autoreadstderr)
3098 elif opts['peer'] == 'ssh2':
3102 elif opts['peer'] == 'ssh2':
3099 ui.write(_('creating ssh peer for wire protocol version 2\n'))
3103 ui.write(_('creating ssh peer for wire protocol version 2\n'))
3100 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
3104 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
3101 None, autoreadstderr=autoreadstderr)
3105 None, autoreadstderr=autoreadstderr)
3102 elif opts['peer'] == 'raw':
3106 elif opts['peer'] == 'raw':
3103 ui.write(_('using raw connection to peer\n'))
3107 ui.write(_('using raw connection to peer\n'))
3104 peer = None
3108 peer = None
3105 else:
3109 else:
3106 ui.write(_('creating ssh peer from handshake results\n'))
3110 ui.write(_('creating ssh peer from handshake results\n'))
3107 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
3111 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
3108 autoreadstderr=autoreadstderr)
3112 autoreadstderr=autoreadstderr)
3109
3113
3110 elif path:
3114 elif path:
3111 # We bypass hg.peer() so we can proxy the sockets.
3115 # We bypass hg.peer() so we can proxy the sockets.
3112 # TODO consider not doing this because we skip
3116 # TODO consider not doing this because we skip
3113 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
3117 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
3114 u = util.url(path)
3118 u = util.url(path)
3115 if u.scheme != 'http':
3119 if u.scheme != 'http':
3116 raise error.Abort(_('only http:// paths are currently supported'))
3120 raise error.Abort(_('only http:// paths are currently supported'))
3117
3121
3118 url, authinfo = u.authinfo()
3122 url, authinfo = u.authinfo()
3119 openerargs = {
3123 openerargs = {
3120 r'useragent': b'Mercurial debugwireproto',
3124 r'useragent': b'Mercurial debugwireproto',
3121 }
3125 }
3122
3126
3123 # Turn pipes/sockets into observers so we can log I/O.
3127 # Turn pipes/sockets into observers so we can log I/O.
3124 if ui.verbose:
3128 if ui.verbose:
3125 openerargs.update({
3129 openerargs.update({
3126 r'loggingfh': ui,
3130 r'loggingfh': ui,
3127 r'loggingname': b's',
3131 r'loggingname': b's',
3128 r'loggingopts': {
3132 r'loggingopts': {
3129 r'logdata': True,
3133 r'logdata': True,
3130 r'logdataapis': False,
3134 r'logdataapis': False,
3131 },
3135 },
3132 })
3136 })
3133
3137
3134 if ui.debugflag:
3138 if ui.debugflag:
3135 openerargs[r'loggingopts'][r'logdataapis'] = True
3139 openerargs[r'loggingopts'][r'logdataapis'] = True
3136
3140
3137 # Don't send default headers when in raw mode. This allows us to
3141 # Don't send default headers when in raw mode. This allows us to
3138 # bypass most of the behavior of our URL handling code so we can
3142 # bypass most of the behavior of our URL handling code so we can
3139 # have near complete control over what's sent on the wire.
3143 # have near complete control over what's sent on the wire.
3140 if opts['peer'] == 'raw':
3144 if opts['peer'] == 'raw':
3141 openerargs[r'sendaccept'] = False
3145 openerargs[r'sendaccept'] = False
3142
3146
3143 opener = urlmod.opener(ui, authinfo, **openerargs)
3147 opener = urlmod.opener(ui, authinfo, **openerargs)
3144
3148
3145 if opts['peer'] == 'http2':
3149 if opts['peer'] == 'http2':
3146 ui.write(_('creating http peer for wire protocol version 2\n'))
3150 ui.write(_('creating http peer for wire protocol version 2\n'))
3147 # We go through makepeer() because we need an API descriptor for
3151 # We go through makepeer() because we need an API descriptor for
3148 # the peer instance to be useful.
3152 # the peer instance to be useful.
3149 with ui.configoverride({
3153 with ui.configoverride({
3150 ('experimental', 'httppeer.advertise-v2'): True}):
3154 ('experimental', 'httppeer.advertise-v2'): True}):
3151 if opts['nologhandshake']:
3155 if opts['nologhandshake']:
3152 ui.pushbuffer()
3156 ui.pushbuffer()
3153
3157
3154 peer = httppeer.makepeer(ui, path, opener=opener)
3158 peer = httppeer.makepeer(ui, path, opener=opener)
3155
3159
3156 if opts['nologhandshake']:
3160 if opts['nologhandshake']:
3157 ui.popbuffer()
3161 ui.popbuffer()
3158
3162
3159 if not isinstance(peer, httppeer.httpv2peer):
3163 if not isinstance(peer, httppeer.httpv2peer):
3160 raise error.Abort(_('could not instantiate HTTP peer for '
3164 raise error.Abort(_('could not instantiate HTTP peer for '
3161 'wire protocol version 2'),
3165 'wire protocol version 2'),
3162 hint=_('the server may not have the feature '
3166 hint=_('the server may not have the feature '
3163 'enabled or is not allowing this '
3167 'enabled or is not allowing this '
3164 'client version'))
3168 'client version'))
3165
3169
3166 elif opts['peer'] == 'raw':
3170 elif opts['peer'] == 'raw':
3167 ui.write(_('using raw connection to peer\n'))
3171 ui.write(_('using raw connection to peer\n'))
3168 peer = None
3172 peer = None
3169 elif opts['peer']:
3173 elif opts['peer']:
3170 raise error.Abort(_('--peer %s not supported with HTTP peers') %
3174 raise error.Abort(_('--peer %s not supported with HTTP peers') %
3171 opts['peer'])
3175 opts['peer'])
3172 else:
3176 else:
3173 peer = httppeer.makepeer(ui, path, opener=opener)
3177 peer = httppeer.makepeer(ui, path, opener=opener)
3174
3178
3175 # We /could/ populate stdin/stdout with sock.makefile()...
3179 # We /could/ populate stdin/stdout with sock.makefile()...
3176 else:
3180 else:
3177 raise error.Abort(_('unsupported connection configuration'))
3181 raise error.Abort(_('unsupported connection configuration'))
3178
3182
3179 batchedcommands = None
3183 batchedcommands = None
3180
3184
3181 # Now perform actions based on the parsed wire language instructions.
3185 # Now perform actions based on the parsed wire language instructions.
3182 for action, lines in blocks:
3186 for action, lines in blocks:
3183 if action in ('raw', 'raw+'):
3187 if action in ('raw', 'raw+'):
3184 if not stdin:
3188 if not stdin:
3185 raise error.Abort(_('cannot call raw/raw+ on this peer'))
3189 raise error.Abort(_('cannot call raw/raw+ on this peer'))
3186
3190
3187 # Concatenate the data together.
3191 # Concatenate the data together.
3188 data = ''.join(l.lstrip() for l in lines)
3192 data = ''.join(l.lstrip() for l in lines)
3189 data = stringutil.unescapestr(data)
3193 data = stringutil.unescapestr(data)
3190 stdin.write(data)
3194 stdin.write(data)
3191
3195
3192 if action == 'raw+':
3196 if action == 'raw+':
3193 stdin.flush()
3197 stdin.flush()
3194 elif action == 'flush':
3198 elif action == 'flush':
3195 if not stdin:
3199 if not stdin:
3196 raise error.Abort(_('cannot call flush on this peer'))
3200 raise error.Abort(_('cannot call flush on this peer'))
3197 stdin.flush()
3201 stdin.flush()
3198 elif action.startswith('command'):
3202 elif action.startswith('command'):
3199 if not peer:
3203 if not peer:
3200 raise error.Abort(_('cannot send commands unless peer instance '
3204 raise error.Abort(_('cannot send commands unless peer instance '
3201 'is available'))
3205 'is available'))
3202
3206
3203 command = action.split(' ', 1)[1]
3207 command = action.split(' ', 1)[1]
3204
3208
3205 args = {}
3209 args = {}
3206 for line in lines:
3210 for line in lines:
3207 # We need to allow empty values.
3211 # We need to allow empty values.
3208 fields = line.lstrip().split(' ', 1)
3212 fields = line.lstrip().split(' ', 1)
3209 if len(fields) == 1:
3213 if len(fields) == 1:
3210 key = fields[0]
3214 key = fields[0]
3211 value = ''
3215 value = ''
3212 else:
3216 else:
3213 key, value = fields
3217 key, value = fields
3214
3218
3215 if value.startswith('eval:'):
3219 if value.startswith('eval:'):
3216 value = stringutil.evalpythonliteral(value[5:])
3220 value = stringutil.evalpythonliteral(value[5:])
3217 else:
3221 else:
3218 value = stringutil.unescapestr(value)
3222 value = stringutil.unescapestr(value)
3219
3223
3220 args[key] = value
3224 args[key] = value
3221
3225
3222 if batchedcommands is not None:
3226 if batchedcommands is not None:
3223 batchedcommands.append((command, args))
3227 batchedcommands.append((command, args))
3224 continue
3228 continue
3225
3229
3226 ui.status(_('sending %s command\n') % command)
3230 ui.status(_('sending %s command\n') % command)
3227
3231
3228 if 'PUSHFILE' in args:
3232 if 'PUSHFILE' in args:
3229 with open(args['PUSHFILE'], r'rb') as fh:
3233 with open(args['PUSHFILE'], r'rb') as fh:
3230 del args['PUSHFILE']
3234 del args['PUSHFILE']
3231 res, output = peer._callpush(command, fh,
3235 res, output = peer._callpush(command, fh,
3232 **pycompat.strkwargs(args))
3236 **pycompat.strkwargs(args))
3233 ui.status(_('result: %s\n') % stringutil.escapestr(res))
3237 ui.status(_('result: %s\n') % stringutil.escapestr(res))
3234 ui.status(_('remote output: %s\n') %
3238 ui.status(_('remote output: %s\n') %
3235 stringutil.escapestr(output))
3239 stringutil.escapestr(output))
3236 else:
3240 else:
3237 with peer.commandexecutor() as e:
3241 with peer.commandexecutor() as e:
3238 res = e.callcommand(command, args).result()
3242 res = e.callcommand(command, args).result()
3239
3243
3240 if isinstance(res, wireprotov2peer.commandresponse):
3244 if isinstance(res, wireprotov2peer.commandresponse):
3241 val = list(res.cborobjects())
3245 val = list(res.cborobjects())
3242 ui.status(_('response: %s\n') %
3246 ui.status(_('response: %s\n') %
3243 stringutil.pprint(val, bprefix=True))
3247 stringutil.pprint(val, bprefix=True))
3244
3248
3245 else:
3249 else:
3246 ui.status(_('response: %s\n') %
3250 ui.status(_('response: %s\n') %
3247 stringutil.pprint(res, bprefix=True))
3251 stringutil.pprint(res, bprefix=True))
3248
3252
3249 elif action == 'batchbegin':
3253 elif action == 'batchbegin':
3250 if batchedcommands is not None:
3254 if batchedcommands is not None:
3251 raise error.Abort(_('nested batchbegin not allowed'))
3255 raise error.Abort(_('nested batchbegin not allowed'))
3252
3256
3253 batchedcommands = []
3257 batchedcommands = []
3254 elif action == 'batchsubmit':
3258 elif action == 'batchsubmit':
3255 # There is a batching API we could go through. But it would be
3259 # There is a batching API we could go through. But it would be
3256 # difficult to normalize requests into function calls. It is easier
3260 # difficult to normalize requests into function calls. It is easier
3257 # to bypass this layer and normalize to commands + args.
3261 # to bypass this layer and normalize to commands + args.
3258 ui.status(_('sending batch with %d sub-commands\n') %
3262 ui.status(_('sending batch with %d sub-commands\n') %
3259 len(batchedcommands))
3263 len(batchedcommands))
3260 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
3264 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
3261 ui.status(_('response #%d: %s\n') %
3265 ui.status(_('response #%d: %s\n') %
3262 (i, stringutil.escapestr(chunk)))
3266 (i, stringutil.escapestr(chunk)))
3263
3267
3264 batchedcommands = None
3268 batchedcommands = None
3265
3269
3266 elif action.startswith('httprequest '):
3270 elif action.startswith('httprequest '):
3267 if not opener:
3271 if not opener:
3268 raise error.Abort(_('cannot use httprequest without an HTTP '
3272 raise error.Abort(_('cannot use httprequest without an HTTP '
3269 'peer'))
3273 'peer'))
3270
3274
3271 request = action.split(' ', 2)
3275 request = action.split(' ', 2)
3272 if len(request) != 3:
3276 if len(request) != 3:
3273 raise error.Abort(_('invalid httprequest: expected format is '
3277 raise error.Abort(_('invalid httprequest: expected format is '
3274 '"httprequest <method> <path>'))
3278 '"httprequest <method> <path>'))
3275
3279
3276 method, httppath = request[1:]
3280 method, httppath = request[1:]
3277 headers = {}
3281 headers = {}
3278 body = None
3282 body = None
3279 frames = []
3283 frames = []
3280 for line in lines:
3284 for line in lines:
3281 line = line.lstrip()
3285 line = line.lstrip()
3282 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3286 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3283 if m:
3287 if m:
3284 headers[m.group(1)] = m.group(2)
3288 headers[m.group(1)] = m.group(2)
3285 continue
3289 continue
3286
3290
3287 if line.startswith(b'BODYFILE '):
3291 if line.startswith(b'BODYFILE '):
3288 with open(line.split(b' ', 1), 'rb') as fh:
3292 with open(line.split(b' ', 1), 'rb') as fh:
3289 body = fh.read()
3293 body = fh.read()
3290 elif line.startswith(b'frame '):
3294 elif line.startswith(b'frame '):
3291 frame = wireprotoframing.makeframefromhumanstring(
3295 frame = wireprotoframing.makeframefromhumanstring(
3292 line[len(b'frame '):])
3296 line[len(b'frame '):])
3293
3297
3294 frames.append(frame)
3298 frames.append(frame)
3295 else:
3299 else:
3296 raise error.Abort(_('unknown argument to httprequest: %s') %
3300 raise error.Abort(_('unknown argument to httprequest: %s') %
3297 line)
3301 line)
3298
3302
3299 url = path + httppath
3303 url = path + httppath
3300
3304
3301 if frames:
3305 if frames:
3302 body = b''.join(bytes(f) for f in frames)
3306 body = b''.join(bytes(f) for f in frames)
3303
3307
3304 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3308 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3305
3309
3306 # urllib.Request insists on using has_data() as a proxy for
3310 # urllib.Request insists on using has_data() as a proxy for
3307 # determining the request method. Override that to use our
3311 # determining the request method. Override that to use our
3308 # explicitly requested method.
3312 # explicitly requested method.
3309 req.get_method = lambda: pycompat.sysstr(method)
3313 req.get_method = lambda: pycompat.sysstr(method)
3310
3314
3311 try:
3315 try:
3312 res = opener.open(req)
3316 res = opener.open(req)
3313 body = res.read()
3317 body = res.read()
3314 except util.urlerr.urlerror as e:
3318 except util.urlerr.urlerror as e:
3315 # read() method must be called, but only exists in Python 2
3319 # read() method must be called, but only exists in Python 2
3316 getattr(e, 'read', lambda: None)()
3320 getattr(e, 'read', lambda: None)()
3317 continue
3321 continue
3318
3322
3319 if res.headers.get('Content-Type') == 'application/mercurial-cbor':
3323 if res.headers.get('Content-Type') == 'application/mercurial-cbor':
3320 ui.write(_('cbor> %s\n') %
3324 ui.write(_('cbor> %s\n') %
3321 stringutil.pprint(cbor.loads(body), bprefix=True))
3325 stringutil.pprint(cbor.loads(body), bprefix=True))
3322
3326
3323 elif action == 'close':
3327 elif action == 'close':
3324 peer.close()
3328 peer.close()
3325 elif action == 'readavailable':
3329 elif action == 'readavailable':
3326 if not stdout or not stderr:
3330 if not stdout or not stderr:
3327 raise error.Abort(_('readavailable not available on this peer'))
3331 raise error.Abort(_('readavailable not available on this peer'))
3328
3332
3329 stdin.close()
3333 stdin.close()
3330 stdout.read()
3334 stdout.read()
3331 stderr.read()
3335 stderr.read()
3332
3336
3333 elif action == 'readline':
3337 elif action == 'readline':
3334 if not stdout:
3338 if not stdout:
3335 raise error.Abort(_('readline not available on this peer'))
3339 raise error.Abort(_('readline not available on this peer'))
3336 stdout.readline()
3340 stdout.readline()
3337 elif action == 'ereadline':
3341 elif action == 'ereadline':
3338 if not stderr:
3342 if not stderr:
3339 raise error.Abort(_('ereadline not available on this peer'))
3343 raise error.Abort(_('ereadline not available on this peer'))
3340 stderr.readline()
3344 stderr.readline()
3341 elif action.startswith('read '):
3345 elif action.startswith('read '):
3342 count = int(action.split(' ', 1)[1])
3346 count = int(action.split(' ', 1)[1])
3343 if not stdout:
3347 if not stdout:
3344 raise error.Abort(_('read not available on this peer'))
3348 raise error.Abort(_('read not available on this peer'))
3345 stdout.read(count)
3349 stdout.read(count)
3346 elif action.startswith('eread '):
3350 elif action.startswith('eread '):
3347 count = int(action.split(' ', 1)[1])
3351 count = int(action.split(' ', 1)[1])
3348 if not stderr:
3352 if not stderr:
3349 raise error.Abort(_('eread not available on this peer'))
3353 raise error.Abort(_('eread not available on this peer'))
3350 stderr.read(count)
3354 stderr.read(count)
3351 else:
3355 else:
3352 raise error.Abort(_('unknown action: %s') % action)
3356 raise error.Abort(_('unknown action: %s') % action)
3353
3357
3354 if batchedcommands is not None:
3358 if batchedcommands is not None:
3355 raise error.Abort(_('unclosed "batchbegin" request'))
3359 raise error.Abort(_('unclosed "batchbegin" request'))
3356
3360
3357 if peer:
3361 if peer:
3358 peer.close()
3362 peer.close()
3359
3363
3360 if proc:
3364 if proc:
3361 proc.kill()
3365 proc.kill()
This diff has been collapsed as it changes many lines, (709 lines changed) Show them Hide them
@@ -1,3180 +1,2489 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import contextlib
17 import contextlib
18 import errno
18 import errno
19 import hashlib
19 import hashlib
20 import heapq
21 import os
20 import os
22 import re
21 import re
23 import struct
22 import struct
24 import zlib
23 import zlib
25
24
26 # import stuff from node for others to import from revlog
25 # import stuff from node for others to import from revlog
27 from .node import (
26 from .node import (
28 bin,
27 bin,
29 hex,
28 hex,
30 nullhex,
29 nullhex,
31 nullid,
30 nullid,
32 nullrev,
31 nullrev,
33 wdirfilenodeids,
32 wdirfilenodeids,
34 wdirhex,
33 wdirhex,
35 wdirid,
34 wdirid,
36 wdirrev,
35 wdirrev,
37 )
36 )
38 from .i18n import _
37 from .i18n import _
39 from .revlogutils.constants import (
38 from .revlogutils.constants import (
40 FLAG_GENERALDELTA,
39 FLAG_GENERALDELTA,
41 FLAG_INLINE_DATA,
40 FLAG_INLINE_DATA,
42 LIMIT_DELTA2TEXT,
43 REVIDX_DEFAULT_FLAGS,
41 REVIDX_DEFAULT_FLAGS,
44 REVIDX_ELLIPSIS,
42 REVIDX_ELLIPSIS,
45 REVIDX_EXTSTORED,
43 REVIDX_EXTSTORED,
46 REVIDX_FLAGS_ORDER,
44 REVIDX_FLAGS_ORDER,
47 REVIDX_ISCENSORED,
45 REVIDX_ISCENSORED,
48 REVIDX_KNOWN_FLAGS,
46 REVIDX_KNOWN_FLAGS,
49 REVIDX_RAWTEXT_CHANGING_FLAGS,
47 REVIDX_RAWTEXT_CHANGING_FLAGS,
50 REVLOGV0,
48 REVLOGV0,
51 REVLOGV1,
49 REVLOGV1,
52 REVLOGV1_FLAGS,
50 REVLOGV1_FLAGS,
53 REVLOGV2,
51 REVLOGV2,
54 REVLOGV2_FLAGS,
52 REVLOGV2_FLAGS,
55 REVLOG_DEFAULT_FLAGS,
53 REVLOG_DEFAULT_FLAGS,
56 REVLOG_DEFAULT_FORMAT,
54 REVLOG_DEFAULT_FORMAT,
57 REVLOG_DEFAULT_VERSION,
55 REVLOG_DEFAULT_VERSION,
58 )
56 )
59 from .thirdparty import (
57 from .thirdparty import (
60 attr,
58 attr,
61 )
59 )
62 from . import (
60 from . import (
63 ancestor,
61 ancestor,
64 error,
62 error,
65 mdiff,
63 mdiff,
66 policy,
64 policy,
67 pycompat,
65 pycompat,
68 repository,
66 repository,
69 templatefilters,
67 templatefilters,
70 util,
68 util,
71 )
69 )
70 from .revlogutils import (
71 deltas as deltautil,
72 )
72 from .utils import (
73 from .utils import (
73 interfaceutil,
74 interfaceutil,
74 stringutil,
75 stringutil,
75 )
76 )
76
77
77 # blanked usage of all the name to prevent pyflakes constraints
78 # blanked usage of all the name to prevent pyflakes constraints
78 # We need these name available in the module for extensions.
79 # We need these name available in the module for extensions.
79 REVLOGV0
80 REVLOGV0
80 REVLOGV1
81 REVLOGV1
81 REVLOGV2
82 REVLOGV2
82 FLAG_INLINE_DATA
83 FLAG_INLINE_DATA
83 FLAG_GENERALDELTA
84 FLAG_GENERALDELTA
84 REVLOG_DEFAULT_FLAGS
85 REVLOG_DEFAULT_FLAGS
85 REVLOG_DEFAULT_FORMAT
86 REVLOG_DEFAULT_FORMAT
86 REVLOG_DEFAULT_VERSION
87 REVLOG_DEFAULT_VERSION
87 REVLOGV1_FLAGS
88 REVLOGV1_FLAGS
88 REVLOGV2_FLAGS
89 REVLOGV2_FLAGS
89 REVIDX_ISCENSORED
90 REVIDX_ISCENSORED
90 REVIDX_ELLIPSIS
91 REVIDX_ELLIPSIS
91 REVIDX_EXTSTORED
92 REVIDX_EXTSTORED
92 REVIDX_DEFAULT_FLAGS
93 REVIDX_DEFAULT_FLAGS
93 REVIDX_FLAGS_ORDER
94 REVIDX_FLAGS_ORDER
94 REVIDX_KNOWN_FLAGS
95 REVIDX_KNOWN_FLAGS
95 REVIDX_RAWTEXT_CHANGING_FLAGS
96 REVIDX_RAWTEXT_CHANGING_FLAGS
96
97
97 parsers = policy.importmod(r'parsers')
98 parsers = policy.importmod(r'parsers')
98
99
99 # Aliased for performance.
100 # Aliased for performance.
100 _zlibdecompress = zlib.decompress
101 _zlibdecompress = zlib.decompress
101
102
102 # max size of revlog with inline data
103 # max size of revlog with inline data
103 _maxinline = 131072
104 _maxinline = 131072
104 _chunksize = 1048576
105 _chunksize = 1048576
105
106
106 RevlogError = error.RevlogError
107 RevlogError = error.RevlogError
107 LookupError = error.LookupError
108 LookupError = error.LookupError
108 AmbiguousPrefixLookupError = error.AmbiguousPrefixLookupError
109 AmbiguousPrefixLookupError = error.AmbiguousPrefixLookupError
109 CensoredNodeError = error.CensoredNodeError
110 CensoredNodeError = error.CensoredNodeError
110 ProgrammingError = error.ProgrammingError
111 ProgrammingError = error.ProgrammingError
111
112
112 # Store flag processors (cf. 'addflagprocessor()' to register)
113 # Store flag processors (cf. 'addflagprocessor()' to register)
113 _flagprocessors = {
114 _flagprocessors = {
114 REVIDX_ISCENSORED: None,
115 REVIDX_ISCENSORED: None,
115 }
116 }
116
117
117 _mdre = re.compile('\1\n')
118 _mdre = re.compile('\1\n')
118 def parsemeta(text):
119 def parsemeta(text):
119 """return (metadatadict, metadatasize)"""
120 """return (metadatadict, metadatasize)"""
120 # text can be buffer, so we can't use .startswith or .index
121 # text can be buffer, so we can't use .startswith or .index
121 if text[:2] != '\1\n':
122 if text[:2] != '\1\n':
122 return None, None
123 return None, None
123 s = _mdre.search(text, 2).start()
124 s = _mdre.search(text, 2).start()
124 mtext = text[2:s]
125 mtext = text[2:s]
125 meta = {}
126 meta = {}
126 for l in mtext.splitlines():
127 for l in mtext.splitlines():
127 k, v = l.split(": ", 1)
128 k, v = l.split(": ", 1)
128 meta[k] = v
129 meta[k] = v
129 return meta, (s + 2)
130 return meta, (s + 2)
130
131
131 def packmeta(meta, text):
132 def packmeta(meta, text):
132 keys = sorted(meta)
133 keys = sorted(meta)
133 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
134 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
134 return "\1\n%s\1\n%s" % (metatext, text)
135 return "\1\n%s\1\n%s" % (metatext, text)
135
136
136 def _censoredtext(text):
137 def _censoredtext(text):
137 m, offs = parsemeta(text)
138 m, offs = parsemeta(text)
138 return m and "censored" in m
139 return m and "censored" in m
139
140
140 def addflagprocessor(flag, processor):
141 def addflagprocessor(flag, processor):
141 """Register a flag processor on a revision data flag.
142 """Register a flag processor on a revision data flag.
142
143
143 Invariant:
144 Invariant:
144 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
145 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
145 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
146 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
146 - Only one flag processor can be registered on a specific flag.
147 - Only one flag processor can be registered on a specific flag.
147 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
148 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
148 following signatures:
149 following signatures:
149 - (read) f(self, rawtext) -> text, bool
150 - (read) f(self, rawtext) -> text, bool
150 - (write) f(self, text) -> rawtext, bool
151 - (write) f(self, text) -> rawtext, bool
151 - (raw) f(self, rawtext) -> bool
152 - (raw) f(self, rawtext) -> bool
152 "text" is presented to the user. "rawtext" is stored in revlog data, not
153 "text" is presented to the user. "rawtext" is stored in revlog data, not
153 directly visible to the user.
154 directly visible to the user.
154 The boolean returned by these transforms is used to determine whether
155 The boolean returned by these transforms is used to determine whether
155 the returned text can be used for hash integrity checking. For example,
156 the returned text can be used for hash integrity checking. For example,
156 if "write" returns False, then "text" is used to generate hash. If
157 if "write" returns False, then "text" is used to generate hash. If
157 "write" returns True, that basically means "rawtext" returned by "write"
158 "write" returns True, that basically means "rawtext" returned by "write"
158 should be used to generate hash. Usually, "write" and "read" return
159 should be used to generate hash. Usually, "write" and "read" return
159 different booleans. And "raw" returns a same boolean as "write".
160 different booleans. And "raw" returns a same boolean as "write".
160
161
161 Note: The 'raw' transform is used for changegroup generation and in some
162 Note: The 'raw' transform is used for changegroup generation and in some
162 debug commands. In this case the transform only indicates whether the
163 debug commands. In this case the transform only indicates whether the
163 contents can be used for hash integrity checks.
164 contents can be used for hash integrity checks.
164 """
165 """
165 if not flag & REVIDX_KNOWN_FLAGS:
166 if not flag & REVIDX_KNOWN_FLAGS:
166 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
167 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
167 raise ProgrammingError(msg)
168 raise ProgrammingError(msg)
168 if flag not in REVIDX_FLAGS_ORDER:
169 if flag not in REVIDX_FLAGS_ORDER:
169 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
170 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
170 raise ProgrammingError(msg)
171 raise ProgrammingError(msg)
171 if flag in _flagprocessors:
172 if flag in _flagprocessors:
172 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
173 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
173 raise error.Abort(msg)
174 raise error.Abort(msg)
174 _flagprocessors[flag] = processor
175 _flagprocessors[flag] = processor
175
176
176 def getoffset(q):
177 def getoffset(q):
177 return int(q >> 16)
178 return int(q >> 16)
178
179
179 def gettype(q):
180 def gettype(q):
180 return int(q & 0xFFFF)
181 return int(q & 0xFFFF)
181
182
182 def offset_type(offset, type):
183 def offset_type(offset, type):
183 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
184 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
184 raise ValueError('unknown revlog index flags')
185 raise ValueError('unknown revlog index flags')
185 return int(int(offset) << 16 | type)
186 return int(int(offset) << 16 | type)
186
187
187 _nullhash = hashlib.sha1(nullid)
188 _nullhash = hashlib.sha1(nullid)
188
189
189 def hash(text, p1, p2):
190 def hash(text, p1, p2):
190 """generate a hash from the given text and its parent hashes
191 """generate a hash from the given text and its parent hashes
191
192
192 This hash combines both the current file contents and its history
193 This hash combines both the current file contents and its history
193 in a manner that makes it easy to distinguish nodes with the same
194 in a manner that makes it easy to distinguish nodes with the same
194 content in the revision graph.
195 content in the revision graph.
195 """
196 """
196 # As of now, if one of the parent node is null, p2 is null
197 # As of now, if one of the parent node is null, p2 is null
197 if p2 == nullid:
198 if p2 == nullid:
198 # deep copy of a hash is faster than creating one
199 # deep copy of a hash is faster than creating one
199 s = _nullhash.copy()
200 s = _nullhash.copy()
200 s.update(p1)
201 s.update(p1)
201 else:
202 else:
202 # none of the parent nodes are nullid
203 # none of the parent nodes are nullid
203 if p1 < p2:
204 if p1 < p2:
204 a = p1
205 a = p1
205 b = p2
206 b = p2
206 else:
207 else:
207 a = p2
208 a = p2
208 b = p1
209 b = p1
209 s = hashlib.sha1(a)
210 s = hashlib.sha1(a)
210 s.update(b)
211 s.update(b)
211 s.update(text)
212 s.update(text)
212 return s.digest()
213 return s.digest()
213
214
214 class _testrevlog(object):
215 """minimalist fake revlog to use in doctests"""
216
217 def __init__(self, data, density=0.5, mingap=0):
218 """data is an list of revision payload boundaries"""
219 self._data = data
220 self._srdensitythreshold = density
221 self._srmingapsize = mingap
222
223 def start(self, rev):
224 if rev == 0:
225 return 0
226 return self._data[rev - 1]
227
228 def end(self, rev):
229 return self._data[rev]
230
231 def length(self, rev):
232 return self.end(rev) - self.start(rev)
233
234 def __len__(self):
235 return len(self._data)
236
237 def _trimchunk(revlog, revs, startidx, endidx=None):
238 """returns revs[startidx:endidx] without empty trailing revs
239
240 Doctest Setup
241 >>> revlog = _testrevlog([
242 ... 5, #0
243 ... 10, #1
244 ... 12, #2
245 ... 12, #3 (empty)
246 ... 17, #4
247 ... 21, #5
248 ... 21, #6 (empty)
249 ... ])
250
251 Contiguous cases:
252 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
253 [0, 1, 2, 3, 4, 5]
254 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
255 [0, 1, 2, 3, 4]
256 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
257 [0, 1, 2]
258 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
259 [2]
260 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
261 [3, 4, 5]
262 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
263 [3, 4]
264
265 Discontiguous cases:
266 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
267 [1, 3, 5]
268 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
269 [1]
270 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
271 [3, 5]
272 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
273 [3, 5]
274 """
275 length = revlog.length
276
277 if endidx is None:
278 endidx = len(revs)
279
280 # If we have a non-emtpy delta candidate, there are nothing to trim
281 if revs[endidx - 1] < len(revlog):
282 # Trim empty revs at the end, except the very first revision of a chain
283 while (endidx > 1
284 and endidx > startidx
285 and length(revs[endidx - 1]) == 0):
286 endidx -= 1
287
288 return revs[startidx:endidx]
289
290 def _segmentspan(revlog, revs, deltainfo=None):
291 """Get the byte span of a segment of revisions
292
293 revs is a sorted array of revision numbers
294
295 >>> revlog = _testrevlog([
296 ... 5, #0
297 ... 10, #1
298 ... 12, #2
299 ... 12, #3 (empty)
300 ... 17, #4
301 ... ])
302
303 >>> _segmentspan(revlog, [0, 1, 2, 3, 4])
304 17
305 >>> _segmentspan(revlog, [0, 4])
306 17
307 >>> _segmentspan(revlog, [3, 4])
308 5
309 >>> _segmentspan(revlog, [1, 2, 3,])
310 7
311 >>> _segmentspan(revlog, [1, 3])
312 7
313 """
314 if not revs:
315 return 0
316 if deltainfo is not None and len(revlog) <= revs[-1]:
317 if len(revs) == 1:
318 return deltainfo.deltalen
319 offset = revlog.end(len(revlog) - 1)
320 end = deltainfo.deltalen + offset
321 else:
322 end = revlog.end(revs[-1])
323 return end - revlog.start(revs[0])
324
325 def _slicechunk(revlog, revs, deltainfo=None, targetsize=None):
326 """slice revs to reduce the amount of unrelated data to be read from disk.
327
328 ``revs`` is sliced into groups that should be read in one time.
329 Assume that revs are sorted.
330
331 The initial chunk is sliced until the overall density (payload/chunks-span
332 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
333 `revlog._srmingapsize` is skipped.
334
335 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
336 For consistency with other slicing choice, this limit won't go lower than
337 `revlog._srmingapsize`.
338
339 If individual revisions chunk are larger than this limit, they will still
340 be raised individually.
341
342 >>> revlog = _testrevlog([
343 ... 5, #00 (5)
344 ... 10, #01 (5)
345 ... 12, #02 (2)
346 ... 12, #03 (empty)
347 ... 27, #04 (15)
348 ... 31, #05 (4)
349 ... 31, #06 (empty)
350 ... 42, #07 (11)
351 ... 47, #08 (5)
352 ... 47, #09 (empty)
353 ... 48, #10 (1)
354 ... 51, #11 (3)
355 ... 74, #12 (23)
356 ... 85, #13 (11)
357 ... 86, #14 (1)
358 ... 91, #15 (5)
359 ... ])
360
361 >>> list(_slicechunk(revlog, list(range(16))))
362 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
363 >>> list(_slicechunk(revlog, [0, 15]))
364 [[0], [15]]
365 >>> list(_slicechunk(revlog, [0, 11, 15]))
366 [[0], [11], [15]]
367 >>> list(_slicechunk(revlog, [0, 11, 13, 15]))
368 [[0], [11, 13, 15]]
369 >>> list(_slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
370 [[1, 2], [5, 8, 10, 11], [14]]
371
372 Slicing with a maximum chunk size
373 >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
374 [[0], [11], [13], [15]]
375 >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
376 [[0], [11], [13, 15]]
377 """
378 if targetsize is not None:
379 targetsize = max(targetsize, revlog._srmingapsize)
380 # targetsize should not be specified when evaluating delta candidates:
381 # * targetsize is used to ensure we stay within specification when reading,
382 # * deltainfo is used to pick are good delta chain when writing.
383 if not (deltainfo is None or targetsize is None):
384 msg = 'cannot use `targetsize` with a `deltainfo`'
385 raise error.ProgrammingError(msg)
386 for chunk in _slicechunktodensity(revlog, revs,
387 deltainfo,
388 revlog._srdensitythreshold,
389 revlog._srmingapsize):
390 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
391 yield subchunk
392
393 def _slicechunktosize(revlog, revs, targetsize=None):
394 """slice revs to match the target size
395
396 This is intended to be used on chunk that density slicing selected by that
397 are still too large compared to the read garantee of revlog. This might
398 happens when "minimal gap size" interrupted the slicing or when chain are
399 built in a way that create large blocks next to each other.
400
401 >>> revlog = _testrevlog([
402 ... 3, #0 (3)
403 ... 5, #1 (2)
404 ... 6, #2 (1)
405 ... 8, #3 (2)
406 ... 8, #4 (empty)
407 ... 11, #5 (3)
408 ... 12, #6 (1)
409 ... 13, #7 (1)
410 ... 14, #8 (1)
411 ... ])
412
413 Cases where chunk is already small enough
414 >>> list(_slicechunktosize(revlog, [0], 3))
415 [[0]]
416 >>> list(_slicechunktosize(revlog, [6, 7], 3))
417 [[6, 7]]
418 >>> list(_slicechunktosize(revlog, [0], None))
419 [[0]]
420 >>> list(_slicechunktosize(revlog, [6, 7], None))
421 [[6, 7]]
422
423 cases where we need actual slicing
424 >>> list(_slicechunktosize(revlog, [0, 1], 3))
425 [[0], [1]]
426 >>> list(_slicechunktosize(revlog, [1, 3], 3))
427 [[1], [3]]
428 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
429 [[1, 2], [3]]
430 >>> list(_slicechunktosize(revlog, [3, 5], 3))
431 [[3], [5]]
432 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
433 [[3], [5]]
434 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
435 [[5], [6, 7, 8]]
436 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
437 [[0], [1, 2], [3], [5], [6, 7, 8]]
438
439 Case with too large individual chunk (must return valid chunk)
440 >>> list(_slicechunktosize(revlog, [0, 1], 2))
441 [[0], [1]]
442 >>> list(_slicechunktosize(revlog, [1, 3], 1))
443 [[1], [3]]
444 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
445 [[3], [5]]
446 """
447 assert targetsize is None or 0 <= targetsize
448 if targetsize is None or _segmentspan(revlog, revs) <= targetsize:
449 yield revs
450 return
451
452 startrevidx = 0
453 startdata = revlog.start(revs[0])
454 endrevidx = 0
455 iterrevs = enumerate(revs)
456 next(iterrevs) # skip first rev.
457 for idx, r in iterrevs:
458 span = revlog.end(r) - startdata
459 if span <= targetsize:
460 endrevidx = idx
461 else:
462 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
463 if chunk:
464 yield chunk
465 startrevidx = idx
466 startdata = revlog.start(r)
467 endrevidx = idx
468 yield _trimchunk(revlog, revs, startrevidx)
469
470 def _slicechunktodensity(revlog, revs, deltainfo=None, targetdensity=0.5,
471 mingapsize=0):
472 """slice revs to reduce the amount of unrelated data to be read from disk.
473
474 ``revs`` is sliced into groups that should be read in one time.
475 Assume that revs are sorted.
476
477 ``deltainfo`` is a _deltainfo instance of a revision that we would append
478 to the top of the revlog.
479
480 The initial chunk is sliced until the overall density (payload/chunks-span
481 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
482 skipped.
483
484 >>> revlog = _testrevlog([
485 ... 5, #00 (5)
486 ... 10, #01 (5)
487 ... 12, #02 (2)
488 ... 12, #03 (empty)
489 ... 27, #04 (15)
490 ... 31, #05 (4)
491 ... 31, #06 (empty)
492 ... 42, #07 (11)
493 ... 47, #08 (5)
494 ... 47, #09 (empty)
495 ... 48, #10 (1)
496 ... 51, #11 (3)
497 ... 74, #12 (23)
498 ... 85, #13 (11)
499 ... 86, #14 (1)
500 ... 91, #15 (5)
501 ... ])
502
503 >>> list(_slicechunktodensity(revlog, list(range(16))))
504 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
505 >>> list(_slicechunktodensity(revlog, [0, 15]))
506 [[0], [15]]
507 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
508 [[0], [11], [15]]
509 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
510 [[0], [11, 13, 15]]
511 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
512 [[1, 2], [5, 8, 10, 11], [14]]
513 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
514 ... mingapsize=20))
515 [[1, 2, 3, 5, 8, 10, 11], [14]]
516 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
517 ... targetdensity=0.95))
518 [[1, 2], [5], [8, 10, 11], [14]]
519 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
520 ... targetdensity=0.95, mingapsize=12))
521 [[1, 2], [5, 8, 10, 11], [14]]
522 """
523 start = revlog.start
524 length = revlog.length
525
526 if len(revs) <= 1:
527 yield revs
528 return
529
530 nextrev = len(revlog)
531 nextoffset = revlog.end(nextrev - 1)
532
533 if deltainfo is None:
534 deltachainspan = _segmentspan(revlog, revs)
535 chainpayload = sum(length(r) for r in revs)
536 else:
537 deltachainspan = deltainfo.distance
538 chainpayload = deltainfo.compresseddeltalen
539
540 if deltachainspan < mingapsize:
541 yield revs
542 return
543
544 readdata = deltachainspan
545
546 if deltachainspan:
547 density = chainpayload / float(deltachainspan)
548 else:
549 density = 1.0
550
551 if density >= targetdensity:
552 yield revs
553 return
554
555 if deltainfo is not None and deltainfo.deltalen:
556 revs = list(revs)
557 revs.append(nextrev)
558
559 # Store the gaps in a heap to have them sorted by decreasing size
560 gapsheap = []
561 heapq.heapify(gapsheap)
562 prevend = None
563 for i, rev in enumerate(revs):
564 if rev < nextrev:
565 revstart = start(rev)
566 revlen = length(rev)
567 else:
568 revstart = nextoffset
569 revlen = deltainfo.deltalen
570
571 # Skip empty revisions to form larger holes
572 if revlen == 0:
573 continue
574
575 if prevend is not None:
576 gapsize = revstart - prevend
577 # only consider holes that are large enough
578 if gapsize > mingapsize:
579 heapq.heappush(gapsheap, (-gapsize, i))
580
581 prevend = revstart + revlen
582
583 # Collect the indices of the largest holes until the density is acceptable
584 indicesheap = []
585 heapq.heapify(indicesheap)
586 while gapsheap and density < targetdensity:
587 oppgapsize, gapidx = heapq.heappop(gapsheap)
588
589 heapq.heappush(indicesheap, gapidx)
590
591 # the gap sizes are stored as negatives to be sorted decreasingly
592 # by the heap
593 readdata -= (-oppgapsize)
594 if readdata > 0:
595 density = chainpayload / float(readdata)
596 else:
597 density = 1.0
598
599 # Cut the revs at collected indices
600 previdx = 0
601 while indicesheap:
602 idx = heapq.heappop(indicesheap)
603
604 chunk = _trimchunk(revlog, revs, previdx, idx)
605 if chunk:
606 yield chunk
607
608 previdx = idx
609
610 chunk = _trimchunk(revlog, revs, previdx)
611 if chunk:
612 yield chunk
613
614 @attr.s(slots=True, frozen=True)
615 class _deltainfo(object):
616 distance = attr.ib()
617 deltalen = attr.ib()
618 data = attr.ib()
619 base = attr.ib()
620 chainbase = attr.ib()
621 chainlen = attr.ib()
622 compresseddeltalen = attr.ib()
623 snapshotdepth = attr.ib()
624
625 class _deltacomputer(object):
626 def __init__(self, revlog):
627 self.revlog = revlog
628
629 def _getcandidaterevs(self, p1, p2, cachedelta):
630 """
631 Provides revisions that present an interest to be diffed against,
632 grouped by level of easiness.
633 """
634 revlog = self.revlog
635 gdelta = revlog._generaldelta
636 curr = len(revlog)
637 prev = curr - 1
638 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
639
640 # should we try to build a delta?
641 if prev != nullrev and revlog._storedeltachains:
642 tested = set()
643 # This condition is true most of the time when processing
644 # changegroup data into a generaldelta repo. The only time it
645 # isn't true is if this is the first revision in a delta chain
646 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
647 if cachedelta and gdelta and revlog._lazydeltabase:
648 # Assume what we received from the server is a good choice
649 # build delta will reuse the cache
650 yield (cachedelta[0],)
651 tested.add(cachedelta[0])
652
653 if gdelta:
654 # exclude already lazy tested base if any
655 parents = [p for p in (p1r, p2r)
656 if p != nullrev and p not in tested]
657
658 if not revlog._deltabothparents and len(parents) == 2:
659 parents.sort()
660 # To minimize the chance of having to build a fulltext,
661 # pick first whichever parent is closest to us (max rev)
662 yield (parents[1],)
663 # then the other one (min rev) if the first did not fit
664 yield (parents[0],)
665 tested.update(parents)
666 elif len(parents) > 0:
667 # Test all parents (1 or 2), and keep the best candidate
668 yield parents
669 tested.update(parents)
670
671 if prev not in tested:
672 # other approach failed try against prev to hopefully save us a
673 # fulltext.
674 yield (prev,)
675 tested.add(prev)
676
677 def buildtext(self, revinfo, fh):
678 """Builds a fulltext version of a revision
679
680 revinfo: _revisioninfo instance that contains all needed info
681 fh: file handle to either the .i or the .d revlog file,
682 depending on whether it is inlined or not
683 """
684 btext = revinfo.btext
685 if btext[0] is not None:
686 return btext[0]
687
688 revlog = self.revlog
689 cachedelta = revinfo.cachedelta
690 flags = revinfo.flags
691 node = revinfo.node
692
693 baserev = cachedelta[0]
694 delta = cachedelta[1]
695 # special case deltas which replace entire base; no need to decode
696 # base revision. this neatly avoids censored bases, which throw when
697 # they're decoded.
698 hlen = struct.calcsize(">lll")
699 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
700 len(delta) - hlen):
701 btext[0] = delta[hlen:]
702 else:
703 # deltabase is rawtext before changed by flag processors, which is
704 # equivalent to non-raw text
705 basetext = revlog.revision(baserev, _df=fh, raw=False)
706 btext[0] = mdiff.patch(basetext, delta)
707
708 try:
709 res = revlog._processflags(btext[0], flags, 'read', raw=True)
710 btext[0], validatehash = res
711 if validatehash:
712 revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
713 if flags & REVIDX_ISCENSORED:
714 raise RevlogError(_('node %s is not censored') % node)
715 except CensoredNodeError:
716 # must pass the censored index flag to add censored revisions
717 if not flags & REVIDX_ISCENSORED:
718 raise
719 return btext[0]
720
721 def _builddeltadiff(self, base, revinfo, fh):
722 revlog = self.revlog
723 t = self.buildtext(revinfo, fh)
724 if revlog.iscensored(base):
725 # deltas based on a censored revision must replace the
726 # full content in one patch, so delta works everywhere
727 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
728 delta = header + t
729 else:
730 ptext = revlog.revision(base, _df=fh, raw=True)
731 delta = mdiff.textdiff(ptext, t)
732
733 return delta
734
735 def _builddeltainfo(self, revinfo, base, fh):
736 # can we use the cached delta?
737 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
738 delta = revinfo.cachedelta[1]
739 else:
740 delta = self._builddeltadiff(base, revinfo, fh)
741 revlog = self.revlog
742 header, data = revlog.compress(delta)
743 deltalen = len(header) + len(data)
744 chainbase = revlog.chainbase(base)
745 offset = revlog.end(len(revlog) - 1)
746 dist = deltalen + offset - revlog.start(chainbase)
747 if revlog._generaldelta:
748 deltabase = base
749 else:
750 deltabase = chainbase
751 chainlen, compresseddeltalen = revlog._chaininfo(base)
752 chainlen += 1
753 compresseddeltalen += deltalen
754
755 revlog = self.revlog
756 snapshotdepth = None
757 if deltabase == nullrev:
758 snapshotdepth = 0
759 elif revlog._sparserevlog and revlog.issnapshot(deltabase):
760 # A delta chain should always be one full snapshot,
761 # zero or more semi-snapshots, and zero or more deltas
762 p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
763 if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
764 snapshotdepth = len(revlog._deltachain(deltabase)[0])
765
766 return _deltainfo(dist, deltalen, (header, data), deltabase,
767 chainbase, chainlen, compresseddeltalen,
768 snapshotdepth)
769
770 def finddeltainfo(self, revinfo, fh):
771 """Find an acceptable delta against a candidate revision
772
773 revinfo: information about the revision (instance of _revisioninfo)
774 fh: file handle to either the .i or the .d revlog file,
775 depending on whether it is inlined or not
776
777 Returns the first acceptable candidate revision, as ordered by
778 _getcandidaterevs
779 """
780 if not revinfo.textlen:
781 return None # empty file do not need delta
782
783 cachedelta = revinfo.cachedelta
784 p1 = revinfo.p1
785 p2 = revinfo.p2
786 revlog = self.revlog
787
788 deltalength = self.revlog.length
789 deltaparent = self.revlog.deltaparent
790
791 deltainfo = None
792 deltas_limit = revinfo.textlen * LIMIT_DELTA2TEXT
793 for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
794 # filter out delta base that will never produce good delta
795 candidaterevs = [r for r in candidaterevs
796 if self.revlog.length(r) <= deltas_limit]
797 nominateddeltas = []
798 for candidaterev in candidaterevs:
799 # skip over empty delta (no need to include them in a chain)
800 while candidaterev != nullrev and not deltalength(candidaterev):
801 candidaterev = deltaparent(candidaterev)
802 # no need to try a delta against nullid, this will be handled
803 # by fulltext later.
804 if candidaterev == nullrev:
805 continue
806 # no delta for rawtext-changing revs (see "candelta" for why)
807 if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
808 continue
809 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
810 if revlog._isgooddeltainfo(candidatedelta, revinfo):
811 nominateddeltas.append(candidatedelta)
812 if nominateddeltas:
813 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
814 break
815
816 return deltainfo
817
818 @attr.s(slots=True, frozen=True)
215 @attr.s(slots=True, frozen=True)
819 class _revisioninfo(object):
216 class _revisioninfo(object):
820 """Information about a revision that allows building its fulltext
217 """Information about a revision that allows building its fulltext
821 node: expected hash of the revision
218 node: expected hash of the revision
822 p1, p2: parent revs of the revision
219 p1, p2: parent revs of the revision
823 btext: built text cache consisting of a one-element list
220 btext: built text cache consisting of a one-element list
824 cachedelta: (baserev, uncompressed_delta) or None
221 cachedelta: (baserev, uncompressed_delta) or None
825 flags: flags associated to the revision storage
222 flags: flags associated to the revision storage
826
223
827 One of btext[0] or cachedelta must be set.
224 One of btext[0] or cachedelta must be set.
828 """
225 """
829 node = attr.ib()
226 node = attr.ib()
830 p1 = attr.ib()
227 p1 = attr.ib()
831 p2 = attr.ib()
228 p2 = attr.ib()
832 btext = attr.ib()
229 btext = attr.ib()
833 textlen = attr.ib()
230 textlen = attr.ib()
834 cachedelta = attr.ib()
231 cachedelta = attr.ib()
835 flags = attr.ib()
232 flags = attr.ib()
836
233
837 @interfaceutil.implementer(repository.irevisiondelta)
234 @interfaceutil.implementer(repository.irevisiondelta)
838 @attr.s(slots=True, frozen=True)
235 @attr.s(slots=True, frozen=True)
839 class revlogrevisiondelta(object):
236 class revlogrevisiondelta(object):
840 node = attr.ib()
237 node = attr.ib()
841 p1node = attr.ib()
238 p1node = attr.ib()
842 p2node = attr.ib()
239 p2node = attr.ib()
843 basenode = attr.ib()
240 basenode = attr.ib()
844 linknode = attr.ib()
241 linknode = attr.ib()
845 flags = attr.ib()
242 flags = attr.ib()
846 baserevisionsize = attr.ib()
243 baserevisionsize = attr.ib()
847 revision = attr.ib()
244 revision = attr.ib()
848 delta = attr.ib()
245 delta = attr.ib()
849
246
850 # index v0:
247 # index v0:
851 # 4 bytes: offset
248 # 4 bytes: offset
852 # 4 bytes: compressed length
249 # 4 bytes: compressed length
853 # 4 bytes: base rev
250 # 4 bytes: base rev
854 # 4 bytes: link rev
251 # 4 bytes: link rev
855 # 20 bytes: parent 1 nodeid
252 # 20 bytes: parent 1 nodeid
856 # 20 bytes: parent 2 nodeid
253 # 20 bytes: parent 2 nodeid
857 # 20 bytes: nodeid
254 # 20 bytes: nodeid
858 indexformatv0 = struct.Struct(">4l20s20s20s")
255 indexformatv0 = struct.Struct(">4l20s20s20s")
859 indexformatv0_pack = indexformatv0.pack
256 indexformatv0_pack = indexformatv0.pack
860 indexformatv0_unpack = indexformatv0.unpack
257 indexformatv0_unpack = indexformatv0.unpack
861
258
862 class revlogoldindex(list):
259 class revlogoldindex(list):
863 def __getitem__(self, i):
260 def __getitem__(self, i):
864 if i == -1:
261 if i == -1:
865 return (0, 0, 0, -1, -1, -1, -1, nullid)
262 return (0, 0, 0, -1, -1, -1, -1, nullid)
866 return list.__getitem__(self, i)
263 return list.__getitem__(self, i)
867
264
868 class revlogoldio(object):
265 class revlogoldio(object):
869 def __init__(self):
266 def __init__(self):
870 self.size = indexformatv0.size
267 self.size = indexformatv0.size
871
268
872 def parseindex(self, data, inline):
269 def parseindex(self, data, inline):
873 s = self.size
270 s = self.size
874 index = []
271 index = []
875 nodemap = {nullid: nullrev}
272 nodemap = {nullid: nullrev}
876 n = off = 0
273 n = off = 0
877 l = len(data)
274 l = len(data)
878 while off + s <= l:
275 while off + s <= l:
879 cur = data[off:off + s]
276 cur = data[off:off + s]
880 off += s
277 off += s
881 e = indexformatv0_unpack(cur)
278 e = indexformatv0_unpack(cur)
882 # transform to revlogv1 format
279 # transform to revlogv1 format
883 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
280 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
884 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
281 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
885 index.append(e2)
282 index.append(e2)
886 nodemap[e[6]] = n
283 nodemap[e[6]] = n
887 n += 1
284 n += 1
888
285
889 return revlogoldindex(index), nodemap, None
286 return revlogoldindex(index), nodemap, None
890
287
891 def packentry(self, entry, node, version, rev):
288 def packentry(self, entry, node, version, rev):
892 if gettype(entry[0]):
289 if gettype(entry[0]):
893 raise RevlogError(_('index entry flags need revlog version 1'))
290 raise RevlogError(_('index entry flags need revlog version 1'))
894 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
291 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
895 node(entry[5]), node(entry[6]), entry[7])
292 node(entry[5]), node(entry[6]), entry[7])
896 return indexformatv0_pack(*e2)
293 return indexformatv0_pack(*e2)
897
294
898 # index ng:
295 # index ng:
899 # 6 bytes: offset
296 # 6 bytes: offset
900 # 2 bytes: flags
297 # 2 bytes: flags
901 # 4 bytes: compressed length
298 # 4 bytes: compressed length
902 # 4 bytes: uncompressed length
299 # 4 bytes: uncompressed length
903 # 4 bytes: base rev
300 # 4 bytes: base rev
904 # 4 bytes: link rev
301 # 4 bytes: link rev
905 # 4 bytes: parent 1 rev
302 # 4 bytes: parent 1 rev
906 # 4 bytes: parent 2 rev
303 # 4 bytes: parent 2 rev
907 # 32 bytes: nodeid
304 # 32 bytes: nodeid
908 indexformatng = struct.Struct(">Qiiiiii20s12x")
305 indexformatng = struct.Struct(">Qiiiiii20s12x")
909 indexformatng_pack = indexformatng.pack
306 indexformatng_pack = indexformatng.pack
910 versionformat = struct.Struct(">I")
307 versionformat = struct.Struct(">I")
911 versionformat_pack = versionformat.pack
308 versionformat_pack = versionformat.pack
912 versionformat_unpack = versionformat.unpack
309 versionformat_unpack = versionformat.unpack
913
310
914 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
311 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
915 # signed integer)
312 # signed integer)
916 _maxentrysize = 0x7fffffff
313 _maxentrysize = 0x7fffffff
917
314
918 class revlogio(object):
315 class revlogio(object):
919 def __init__(self):
316 def __init__(self):
920 self.size = indexformatng.size
317 self.size = indexformatng.size
921
318
922 def parseindex(self, data, inline):
319 def parseindex(self, data, inline):
923 # call the C implementation to parse the index data
320 # call the C implementation to parse the index data
924 index, cache = parsers.parse_index2(data, inline)
321 index, cache = parsers.parse_index2(data, inline)
925 return index, getattr(index, 'nodemap', None), cache
322 return index, getattr(index, 'nodemap', None), cache
926
323
927 def packentry(self, entry, node, version, rev):
324 def packentry(self, entry, node, version, rev):
928 p = indexformatng_pack(*entry)
325 p = indexformatng_pack(*entry)
929 if rev == 0:
326 if rev == 0:
930 p = versionformat_pack(version) + p[4:]
327 p = versionformat_pack(version) + p[4:]
931 return p
328 return p
932
329
933 class revlog(object):
330 class revlog(object):
934 """
331 """
935 the underlying revision storage object
332 the underlying revision storage object
936
333
937 A revlog consists of two parts, an index and the revision data.
334 A revlog consists of two parts, an index and the revision data.
938
335
939 The index is a file with a fixed record size containing
336 The index is a file with a fixed record size containing
940 information on each revision, including its nodeid (hash), the
337 information on each revision, including its nodeid (hash), the
941 nodeids of its parents, the position and offset of its data within
338 nodeids of its parents, the position and offset of its data within
942 the data file, and the revision it's based on. Finally, each entry
339 the data file, and the revision it's based on. Finally, each entry
943 contains a linkrev entry that can serve as a pointer to external
340 contains a linkrev entry that can serve as a pointer to external
944 data.
341 data.
945
342
946 The revision data itself is a linear collection of data chunks.
343 The revision data itself is a linear collection of data chunks.
947 Each chunk represents a revision and is usually represented as a
344 Each chunk represents a revision and is usually represented as a
948 delta against the previous chunk. To bound lookup time, runs of
345 delta against the previous chunk. To bound lookup time, runs of
949 deltas are limited to about 2 times the length of the original
346 deltas are limited to about 2 times the length of the original
950 version data. This makes retrieval of a version proportional to
347 version data. This makes retrieval of a version proportional to
951 its size, or O(1) relative to the number of revisions.
348 its size, or O(1) relative to the number of revisions.
952
349
953 Both pieces of the revlog are written to in an append-only
350 Both pieces of the revlog are written to in an append-only
954 fashion, which means we never need to rewrite a file to insert or
351 fashion, which means we never need to rewrite a file to insert or
955 remove data, and can use some simple techniques to avoid the need
352 remove data, and can use some simple techniques to avoid the need
956 for locking while reading.
353 for locking while reading.
957
354
958 If checkambig, indexfile is opened with checkambig=True at
355 If checkambig, indexfile is opened with checkambig=True at
959 writing, to avoid file stat ambiguity.
356 writing, to avoid file stat ambiguity.
960
357
961 If mmaplargeindex is True, and an mmapindexthreshold is set, the
358 If mmaplargeindex is True, and an mmapindexthreshold is set, the
962 index will be mmapped rather than read if it is larger than the
359 index will be mmapped rather than read if it is larger than the
963 configured threshold.
360 configured threshold.
964
361
965 If censorable is True, the revlog can have censored revisions.
362 If censorable is True, the revlog can have censored revisions.
966 """
363 """
967 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
364 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
968 mmaplargeindex=False, censorable=False):
365 mmaplargeindex=False, censorable=False):
969 """
366 """
970 create a revlog object
367 create a revlog object
971
368
972 opener is a function that abstracts the file opening operation
369 opener is a function that abstracts the file opening operation
973 and can be used to implement COW semantics or the like.
370 and can be used to implement COW semantics or the like.
974 """
371 """
975 self.indexfile = indexfile
372 self.indexfile = indexfile
976 self.datafile = datafile or (indexfile[:-2] + ".d")
373 self.datafile = datafile or (indexfile[:-2] + ".d")
977 self.opener = opener
374 self.opener = opener
978 # When True, indexfile is opened with checkambig=True at writing, to
375 # When True, indexfile is opened with checkambig=True at writing, to
979 # avoid file stat ambiguity.
376 # avoid file stat ambiguity.
980 self._checkambig = checkambig
377 self._checkambig = checkambig
981 self._censorable = censorable
378 self._censorable = censorable
982 # 3-tuple of (node, rev, text) for a raw revision.
379 # 3-tuple of (node, rev, text) for a raw revision.
983 self._cache = None
380 self._cache = None
984 # Maps rev to chain base rev.
381 # Maps rev to chain base rev.
985 self._chainbasecache = util.lrucachedict(100)
382 self._chainbasecache = util.lrucachedict(100)
986 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
383 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
987 self._chunkcache = (0, '')
384 self._chunkcache = (0, '')
988 # How much data to read and cache into the raw revlog data cache.
385 # How much data to read and cache into the raw revlog data cache.
989 self._chunkcachesize = 65536
386 self._chunkcachesize = 65536
990 self._maxchainlen = None
387 self._maxchainlen = None
991 self._deltabothparents = True
388 self._deltabothparents = True
992 self.index = []
389 self.index = []
993 # Mapping of partial identifiers to full nodes.
390 # Mapping of partial identifiers to full nodes.
994 self._pcache = {}
391 self._pcache = {}
995 # Mapping of revision integer to full node.
392 # Mapping of revision integer to full node.
996 self._nodecache = {nullid: nullrev}
393 self._nodecache = {nullid: nullrev}
997 self._nodepos = None
394 self._nodepos = None
998 self._compengine = 'zlib'
395 self._compengine = 'zlib'
999 self._maxdeltachainspan = -1
396 self._maxdeltachainspan = -1
1000 self._withsparseread = False
397 self._withsparseread = False
1001 self._sparserevlog = False
398 self._sparserevlog = False
1002 self._srdensitythreshold = 0.50
399 self._srdensitythreshold = 0.50
1003 self._srmingapsize = 262144
400 self._srmingapsize = 262144
1004
401
1005 mmapindexthreshold = None
402 mmapindexthreshold = None
1006 v = REVLOG_DEFAULT_VERSION
403 v = REVLOG_DEFAULT_VERSION
1007 opts = getattr(opener, 'options', None)
404 opts = getattr(opener, 'options', None)
1008 if opts is not None:
405 if opts is not None:
1009 if 'revlogv2' in opts:
406 if 'revlogv2' in opts:
1010 # version 2 revlogs always use generaldelta.
407 # version 2 revlogs always use generaldelta.
1011 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
408 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
1012 elif 'revlogv1' in opts:
409 elif 'revlogv1' in opts:
1013 if 'generaldelta' in opts:
410 if 'generaldelta' in opts:
1014 v |= FLAG_GENERALDELTA
411 v |= FLAG_GENERALDELTA
1015 else:
412 else:
1016 v = 0
413 v = 0
1017 if 'chunkcachesize' in opts:
414 if 'chunkcachesize' in opts:
1018 self._chunkcachesize = opts['chunkcachesize']
415 self._chunkcachesize = opts['chunkcachesize']
1019 if 'maxchainlen' in opts:
416 if 'maxchainlen' in opts:
1020 self._maxchainlen = opts['maxchainlen']
417 self._maxchainlen = opts['maxchainlen']
1021 if 'deltabothparents' in opts:
418 if 'deltabothparents' in opts:
1022 self._deltabothparents = opts['deltabothparents']
419 self._deltabothparents = opts['deltabothparents']
1023 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
420 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
1024 if 'compengine' in opts:
421 if 'compengine' in opts:
1025 self._compengine = opts['compengine']
422 self._compengine = opts['compengine']
1026 if 'maxdeltachainspan' in opts:
423 if 'maxdeltachainspan' in opts:
1027 self._maxdeltachainspan = opts['maxdeltachainspan']
424 self._maxdeltachainspan = opts['maxdeltachainspan']
1028 if mmaplargeindex and 'mmapindexthreshold' in opts:
425 if mmaplargeindex and 'mmapindexthreshold' in opts:
1029 mmapindexthreshold = opts['mmapindexthreshold']
426 mmapindexthreshold = opts['mmapindexthreshold']
1030 self._sparserevlog = bool(opts.get('sparse-revlog', False))
427 self._sparserevlog = bool(opts.get('sparse-revlog', False))
1031 withsparseread = bool(opts.get('with-sparse-read', False))
428 withsparseread = bool(opts.get('with-sparse-read', False))
1032 # sparse-revlog forces sparse-read
429 # sparse-revlog forces sparse-read
1033 self._withsparseread = self._sparserevlog or withsparseread
430 self._withsparseread = self._sparserevlog or withsparseread
1034 if 'sparse-read-density-threshold' in opts:
431 if 'sparse-read-density-threshold' in opts:
1035 self._srdensitythreshold = opts['sparse-read-density-threshold']
432 self._srdensitythreshold = opts['sparse-read-density-threshold']
1036 if 'sparse-read-min-gap-size' in opts:
433 if 'sparse-read-min-gap-size' in opts:
1037 self._srmingapsize = opts['sparse-read-min-gap-size']
434 self._srmingapsize = opts['sparse-read-min-gap-size']
1038
435
1039 if self._chunkcachesize <= 0:
436 if self._chunkcachesize <= 0:
1040 raise RevlogError(_('revlog chunk cache size %r is not greater '
437 raise RevlogError(_('revlog chunk cache size %r is not greater '
1041 'than 0') % self._chunkcachesize)
438 'than 0') % self._chunkcachesize)
1042 elif self._chunkcachesize & (self._chunkcachesize - 1):
439 elif self._chunkcachesize & (self._chunkcachesize - 1):
1043 raise RevlogError(_('revlog chunk cache size %r is not a power '
440 raise RevlogError(_('revlog chunk cache size %r is not a power '
1044 'of 2') % self._chunkcachesize)
441 'of 2') % self._chunkcachesize)
1045
442
1046 indexdata = ''
443 indexdata = ''
1047 self._initempty = True
444 self._initempty = True
1048 try:
445 try:
1049 with self._indexfp() as f:
446 with self._indexfp() as f:
1050 if (mmapindexthreshold is not None and
447 if (mmapindexthreshold is not None and
1051 self.opener.fstat(f).st_size >= mmapindexthreshold):
448 self.opener.fstat(f).st_size >= mmapindexthreshold):
1052 indexdata = util.buffer(util.mmapread(f))
449 indexdata = util.buffer(util.mmapread(f))
1053 else:
450 else:
1054 indexdata = f.read()
451 indexdata = f.read()
1055 if len(indexdata) > 0:
452 if len(indexdata) > 0:
1056 v = versionformat_unpack(indexdata[:4])[0]
453 v = versionformat_unpack(indexdata[:4])[0]
1057 self._initempty = False
454 self._initempty = False
1058 except IOError as inst:
455 except IOError as inst:
1059 if inst.errno != errno.ENOENT:
456 if inst.errno != errno.ENOENT:
1060 raise
457 raise
1061
458
1062 self.version = v
459 self.version = v
1063 self._inline = v & FLAG_INLINE_DATA
460 self._inline = v & FLAG_INLINE_DATA
1064 self._generaldelta = v & FLAG_GENERALDELTA
461 self._generaldelta = v & FLAG_GENERALDELTA
1065 flags = v & ~0xFFFF
462 flags = v & ~0xFFFF
1066 fmt = v & 0xFFFF
463 fmt = v & 0xFFFF
1067 if fmt == REVLOGV0:
464 if fmt == REVLOGV0:
1068 if flags:
465 if flags:
1069 raise RevlogError(_('unknown flags (%#04x) in version %d '
466 raise RevlogError(_('unknown flags (%#04x) in version %d '
1070 'revlog %s') %
467 'revlog %s') %
1071 (flags >> 16, fmt, self.indexfile))
468 (flags >> 16, fmt, self.indexfile))
1072 elif fmt == REVLOGV1:
469 elif fmt == REVLOGV1:
1073 if flags & ~REVLOGV1_FLAGS:
470 if flags & ~REVLOGV1_FLAGS:
1074 raise RevlogError(_('unknown flags (%#04x) in version %d '
471 raise RevlogError(_('unknown flags (%#04x) in version %d '
1075 'revlog %s') %
472 'revlog %s') %
1076 (flags >> 16, fmt, self.indexfile))
473 (flags >> 16, fmt, self.indexfile))
1077 elif fmt == REVLOGV2:
474 elif fmt == REVLOGV2:
1078 if flags & ~REVLOGV2_FLAGS:
475 if flags & ~REVLOGV2_FLAGS:
1079 raise RevlogError(_('unknown flags (%#04x) in version %d '
476 raise RevlogError(_('unknown flags (%#04x) in version %d '
1080 'revlog %s') %
477 'revlog %s') %
1081 (flags >> 16, fmt, self.indexfile))
478 (flags >> 16, fmt, self.indexfile))
1082 else:
479 else:
1083 raise RevlogError(_('unknown version (%d) in revlog %s') %
480 raise RevlogError(_('unknown version (%d) in revlog %s') %
1084 (fmt, self.indexfile))
481 (fmt, self.indexfile))
1085
482
1086 self._storedeltachains = True
483 self._storedeltachains = True
1087
484
1088 self._io = revlogio()
485 self._io = revlogio()
1089 if self.version == REVLOGV0:
486 if self.version == REVLOGV0:
1090 self._io = revlogoldio()
487 self._io = revlogoldio()
1091 try:
488 try:
1092 d = self._io.parseindex(indexdata, self._inline)
489 d = self._io.parseindex(indexdata, self._inline)
1093 except (ValueError, IndexError):
490 except (ValueError, IndexError):
1094 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
491 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
1095 self.index, nodemap, self._chunkcache = d
492 self.index, nodemap, self._chunkcache = d
1096 if nodemap is not None:
493 if nodemap is not None:
1097 self.nodemap = self._nodecache = nodemap
494 self.nodemap = self._nodecache = nodemap
1098 if not self._chunkcache:
495 if not self._chunkcache:
1099 self._chunkclear()
496 self._chunkclear()
1100 # revnum -> (chain-length, sum-delta-length)
497 # revnum -> (chain-length, sum-delta-length)
1101 self._chaininfocache = {}
498 self._chaininfocache = {}
1102 # revlog header -> revlog compressor
499 # revlog header -> revlog compressor
1103 self._decompressors = {}
500 self._decompressors = {}
1104
501
1105 @util.propertycache
502 @util.propertycache
1106 def _compressor(self):
503 def _compressor(self):
1107 return util.compengines[self._compengine].revlogcompressor()
504 return util.compengines[self._compengine].revlogcompressor()
1108
505
1109 def _indexfp(self, mode='r'):
506 def _indexfp(self, mode='r'):
1110 """file object for the revlog's index file"""
507 """file object for the revlog's index file"""
1111 args = {r'mode': mode}
508 args = {r'mode': mode}
1112 if mode != 'r':
509 if mode != 'r':
1113 args[r'checkambig'] = self._checkambig
510 args[r'checkambig'] = self._checkambig
1114 if mode == 'w':
511 if mode == 'w':
1115 args[r'atomictemp'] = True
512 args[r'atomictemp'] = True
1116 return self.opener(self.indexfile, **args)
513 return self.opener(self.indexfile, **args)
1117
514
1118 def _datafp(self, mode='r'):
515 def _datafp(self, mode='r'):
1119 """file object for the revlog's data file"""
516 """file object for the revlog's data file"""
1120 return self.opener(self.datafile, mode=mode)
517 return self.opener(self.datafile, mode=mode)
1121
518
1122 @contextlib.contextmanager
519 @contextlib.contextmanager
1123 def _datareadfp(self, existingfp=None):
520 def _datareadfp(self, existingfp=None):
1124 """file object suitable to read data"""
521 """file object suitable to read data"""
1125 if existingfp is not None:
522 if existingfp is not None:
1126 yield existingfp
523 yield existingfp
1127 else:
524 else:
1128 if self._inline:
525 if self._inline:
1129 func = self._indexfp
526 func = self._indexfp
1130 else:
527 else:
1131 func = self._datafp
528 func = self._datafp
1132 with func() as fp:
529 with func() as fp:
1133 yield fp
530 yield fp
1134
531
1135 def tip(self):
532 def tip(self):
1136 return self.node(len(self.index) - 1)
533 return self.node(len(self.index) - 1)
1137 def __contains__(self, rev):
534 def __contains__(self, rev):
1138 return 0 <= rev < len(self)
535 return 0 <= rev < len(self)
1139 def __len__(self):
536 def __len__(self):
1140 return len(self.index)
537 return len(self.index)
1141 def __iter__(self):
538 def __iter__(self):
1142 return iter(pycompat.xrange(len(self)))
539 return iter(pycompat.xrange(len(self)))
1143 def revs(self, start=0, stop=None):
540 def revs(self, start=0, stop=None):
1144 """iterate over all rev in this revlog (from start to stop)"""
541 """iterate over all rev in this revlog (from start to stop)"""
1145 step = 1
542 step = 1
1146 length = len(self)
543 length = len(self)
1147 if stop is not None:
544 if stop is not None:
1148 if start > stop:
545 if start > stop:
1149 step = -1
546 step = -1
1150 stop += step
547 stop += step
1151 if stop > length:
548 if stop > length:
1152 stop = length
549 stop = length
1153 else:
550 else:
1154 stop = length
551 stop = length
1155 return pycompat.xrange(start, stop, step)
552 return pycompat.xrange(start, stop, step)
1156
553
1157 @util.propertycache
554 @util.propertycache
1158 def nodemap(self):
555 def nodemap(self):
1159 if self.index:
556 if self.index:
1160 # populate mapping down to the initial node
557 # populate mapping down to the initial node
1161 node0 = self.index[0][7] # get around changelog filtering
558 node0 = self.index[0][7] # get around changelog filtering
1162 self.rev(node0)
559 self.rev(node0)
1163 return self._nodecache
560 return self._nodecache
1164
561
1165 def hasnode(self, node):
562 def hasnode(self, node):
1166 try:
563 try:
1167 self.rev(node)
564 self.rev(node)
1168 return True
565 return True
1169 except KeyError:
566 except KeyError:
1170 return False
567 return False
1171
568
1172 def candelta(self, baserev, rev):
569 def candelta(self, baserev, rev):
1173 """whether two revisions (baserev, rev) can be delta-ed or not"""
570 """whether two revisions (baserev, rev) can be delta-ed or not"""
1174 # Disable delta if either rev requires a content-changing flag
571 # Disable delta if either rev requires a content-changing flag
1175 # processor (ex. LFS). This is because such flag processor can alter
572 # processor (ex. LFS). This is because such flag processor can alter
1176 # the rawtext content that the delta will be based on, and two clients
573 # the rawtext content that the delta will be based on, and two clients
1177 # could have a same revlog node with different flags (i.e. different
574 # could have a same revlog node with different flags (i.e. different
1178 # rawtext contents) and the delta could be incompatible.
575 # rawtext contents) and the delta could be incompatible.
1179 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
576 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
1180 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
577 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
1181 return False
578 return False
1182 return True
579 return True
1183
580
1184 def clearcaches(self):
581 def clearcaches(self):
1185 self._cache = None
582 self._cache = None
1186 self._chainbasecache.clear()
583 self._chainbasecache.clear()
1187 self._chunkcache = (0, '')
584 self._chunkcache = (0, '')
1188 self._pcache = {}
585 self._pcache = {}
1189
586
1190 try:
587 try:
1191 self._nodecache.clearcaches()
588 self._nodecache.clearcaches()
1192 except AttributeError:
589 except AttributeError:
1193 self._nodecache = {nullid: nullrev}
590 self._nodecache = {nullid: nullrev}
1194 self._nodepos = None
591 self._nodepos = None
1195
592
1196 def rev(self, node):
593 def rev(self, node):
1197 try:
594 try:
1198 return self._nodecache[node]
595 return self._nodecache[node]
1199 except TypeError:
596 except TypeError:
1200 raise
597 raise
1201 except RevlogError:
598 except RevlogError:
1202 # parsers.c radix tree lookup failed
599 # parsers.c radix tree lookup failed
1203 if node == wdirid or node in wdirfilenodeids:
600 if node == wdirid or node in wdirfilenodeids:
1204 raise error.WdirUnsupported
601 raise error.WdirUnsupported
1205 raise LookupError(node, self.indexfile, _('no node'))
602 raise LookupError(node, self.indexfile, _('no node'))
1206 except KeyError:
603 except KeyError:
1207 # pure python cache lookup failed
604 # pure python cache lookup failed
1208 n = self._nodecache
605 n = self._nodecache
1209 i = self.index
606 i = self.index
1210 p = self._nodepos
607 p = self._nodepos
1211 if p is None:
608 if p is None:
1212 p = len(i) - 1
609 p = len(i) - 1
1213 else:
610 else:
1214 assert p < len(i)
611 assert p < len(i)
1215 for r in pycompat.xrange(p, -1, -1):
612 for r in pycompat.xrange(p, -1, -1):
1216 v = i[r][7]
613 v = i[r][7]
1217 n[v] = r
614 n[v] = r
1218 if v == node:
615 if v == node:
1219 self._nodepos = r - 1
616 self._nodepos = r - 1
1220 return r
617 return r
1221 if node == wdirid or node in wdirfilenodeids:
618 if node == wdirid or node in wdirfilenodeids:
1222 raise error.WdirUnsupported
619 raise error.WdirUnsupported
1223 raise LookupError(node, self.indexfile, _('no node'))
620 raise LookupError(node, self.indexfile, _('no node'))
1224
621
1225 # Accessors for index entries.
622 # Accessors for index entries.
1226
623
1227 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
624 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1228 # are flags.
625 # are flags.
1229 def start(self, rev):
626 def start(self, rev):
1230 return int(self.index[rev][0] >> 16)
627 return int(self.index[rev][0] >> 16)
1231
628
1232 def flags(self, rev):
629 def flags(self, rev):
1233 return self.index[rev][0] & 0xFFFF
630 return self.index[rev][0] & 0xFFFF
1234
631
1235 def length(self, rev):
632 def length(self, rev):
1236 return self.index[rev][1]
633 return self.index[rev][1]
1237
634
1238 def rawsize(self, rev):
635 def rawsize(self, rev):
1239 """return the length of the uncompressed text for a given revision"""
636 """return the length of the uncompressed text for a given revision"""
1240 l = self.index[rev][2]
637 l = self.index[rev][2]
1241 if l >= 0:
638 if l >= 0:
1242 return l
639 return l
1243
640
1244 t = self.revision(rev, raw=True)
641 t = self.revision(rev, raw=True)
1245 return len(t)
642 return len(t)
1246
643
1247 def size(self, rev):
644 def size(self, rev):
1248 """length of non-raw text (processed by a "read" flag processor)"""
645 """length of non-raw text (processed by a "read" flag processor)"""
1249 # fast path: if no "read" flag processor could change the content,
646 # fast path: if no "read" flag processor could change the content,
1250 # size is rawsize. note: ELLIPSIS is known to not change the content.
647 # size is rawsize. note: ELLIPSIS is known to not change the content.
1251 flags = self.flags(rev)
648 flags = self.flags(rev)
1252 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
649 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1253 return self.rawsize(rev)
650 return self.rawsize(rev)
1254
651
1255 return len(self.revision(rev, raw=False))
652 return len(self.revision(rev, raw=False))
1256
653
1257 def chainbase(self, rev):
654 def chainbase(self, rev):
1258 base = self._chainbasecache.get(rev)
655 base = self._chainbasecache.get(rev)
1259 if base is not None:
656 if base is not None:
1260 return base
657 return base
1261
658
1262 index = self.index
659 index = self.index
1263 iterrev = rev
660 iterrev = rev
1264 base = index[iterrev][3]
661 base = index[iterrev][3]
1265 while base != iterrev:
662 while base != iterrev:
1266 iterrev = base
663 iterrev = base
1267 base = index[iterrev][3]
664 base = index[iterrev][3]
1268
665
1269 self._chainbasecache[rev] = base
666 self._chainbasecache[rev] = base
1270 return base
667 return base
1271
668
1272 def linkrev(self, rev):
669 def linkrev(self, rev):
1273 return self.index[rev][4]
670 return self.index[rev][4]
1274
671
1275 def parentrevs(self, rev):
672 def parentrevs(self, rev):
1276 try:
673 try:
1277 entry = self.index[rev]
674 entry = self.index[rev]
1278 except IndexError:
675 except IndexError:
1279 if rev == wdirrev:
676 if rev == wdirrev:
1280 raise error.WdirUnsupported
677 raise error.WdirUnsupported
1281 raise
678 raise
1282
679
1283 return entry[5], entry[6]
680 return entry[5], entry[6]
1284
681
1285 def node(self, rev):
682 def node(self, rev):
1286 try:
683 try:
1287 return self.index[rev][7]
684 return self.index[rev][7]
1288 except IndexError:
685 except IndexError:
1289 if rev == wdirrev:
686 if rev == wdirrev:
1290 raise error.WdirUnsupported
687 raise error.WdirUnsupported
1291 raise
688 raise
1292
689
1293 # Derived from index values.
690 # Derived from index values.
1294
691
1295 def end(self, rev):
692 def end(self, rev):
1296 return self.start(rev) + self.length(rev)
693 return self.start(rev) + self.length(rev)
1297
694
1298 def parents(self, node):
695 def parents(self, node):
1299 i = self.index
696 i = self.index
1300 d = i[self.rev(node)]
697 d = i[self.rev(node)]
1301 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
698 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
1302
699
1303 def chainlen(self, rev):
700 def chainlen(self, rev):
1304 return self._chaininfo(rev)[0]
701 return self._chaininfo(rev)[0]
1305
702
1306 def _chaininfo(self, rev):
703 def _chaininfo(self, rev):
1307 chaininfocache = self._chaininfocache
704 chaininfocache = self._chaininfocache
1308 if rev in chaininfocache:
705 if rev in chaininfocache:
1309 return chaininfocache[rev]
706 return chaininfocache[rev]
1310 index = self.index
707 index = self.index
1311 generaldelta = self._generaldelta
708 generaldelta = self._generaldelta
1312 iterrev = rev
709 iterrev = rev
1313 e = index[iterrev]
710 e = index[iterrev]
1314 clen = 0
711 clen = 0
1315 compresseddeltalen = 0
712 compresseddeltalen = 0
1316 while iterrev != e[3]:
713 while iterrev != e[3]:
1317 clen += 1
714 clen += 1
1318 compresseddeltalen += e[1]
715 compresseddeltalen += e[1]
1319 if generaldelta:
716 if generaldelta:
1320 iterrev = e[3]
717 iterrev = e[3]
1321 else:
718 else:
1322 iterrev -= 1
719 iterrev -= 1
1323 if iterrev in chaininfocache:
720 if iterrev in chaininfocache:
1324 t = chaininfocache[iterrev]
721 t = chaininfocache[iterrev]
1325 clen += t[0]
722 clen += t[0]
1326 compresseddeltalen += t[1]
723 compresseddeltalen += t[1]
1327 break
724 break
1328 e = index[iterrev]
725 e = index[iterrev]
1329 else:
726 else:
1330 # Add text length of base since decompressing that also takes
727 # Add text length of base since decompressing that also takes
1331 # work. For cache hits the length is already included.
728 # work. For cache hits the length is already included.
1332 compresseddeltalen += e[1]
729 compresseddeltalen += e[1]
1333 r = (clen, compresseddeltalen)
730 r = (clen, compresseddeltalen)
1334 chaininfocache[rev] = r
731 chaininfocache[rev] = r
1335 return r
732 return r
1336
733
1337 def _deltachain(self, rev, stoprev=None):
734 def _deltachain(self, rev, stoprev=None):
1338 """Obtain the delta chain for a revision.
735 """Obtain the delta chain for a revision.
1339
736
1340 ``stoprev`` specifies a revision to stop at. If not specified, we
737 ``stoprev`` specifies a revision to stop at. If not specified, we
1341 stop at the base of the chain.
738 stop at the base of the chain.
1342
739
1343 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
740 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1344 revs in ascending order and ``stopped`` is a bool indicating whether
741 revs in ascending order and ``stopped`` is a bool indicating whether
1345 ``stoprev`` was hit.
742 ``stoprev`` was hit.
1346 """
743 """
1347 # Try C implementation.
744 # Try C implementation.
1348 try:
745 try:
1349 return self.index.deltachain(rev, stoprev, self._generaldelta)
746 return self.index.deltachain(rev, stoprev, self._generaldelta)
1350 except AttributeError:
747 except AttributeError:
1351 pass
748 pass
1352
749
1353 chain = []
750 chain = []
1354
751
1355 # Alias to prevent attribute lookup in tight loop.
752 # Alias to prevent attribute lookup in tight loop.
1356 index = self.index
753 index = self.index
1357 generaldelta = self._generaldelta
754 generaldelta = self._generaldelta
1358
755
1359 iterrev = rev
756 iterrev = rev
1360 e = index[iterrev]
757 e = index[iterrev]
1361 while iterrev != e[3] and iterrev != stoprev:
758 while iterrev != e[3] and iterrev != stoprev:
1362 chain.append(iterrev)
759 chain.append(iterrev)
1363 if generaldelta:
760 if generaldelta:
1364 iterrev = e[3]
761 iterrev = e[3]
1365 else:
762 else:
1366 iterrev -= 1
763 iterrev -= 1
1367 e = index[iterrev]
764 e = index[iterrev]
1368
765
1369 if iterrev == stoprev:
766 if iterrev == stoprev:
1370 stopped = True
767 stopped = True
1371 else:
768 else:
1372 chain.append(iterrev)
769 chain.append(iterrev)
1373 stopped = False
770 stopped = False
1374
771
1375 chain.reverse()
772 chain.reverse()
1376 return chain, stopped
773 return chain, stopped
1377
774
1378 def ancestors(self, revs, stoprev=0, inclusive=False):
775 def ancestors(self, revs, stoprev=0, inclusive=False):
1379 """Generate the ancestors of 'revs' in reverse topological order.
776 """Generate the ancestors of 'revs' in reverse topological order.
1380 Does not generate revs lower than stoprev.
777 Does not generate revs lower than stoprev.
1381
778
1382 See the documentation for ancestor.lazyancestors for more details."""
779 See the documentation for ancestor.lazyancestors for more details."""
1383
780
1384 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
781 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
1385 inclusive=inclusive)
782 inclusive=inclusive)
1386
783
1387 def descendants(self, revs):
784 def descendants(self, revs):
1388 """Generate the descendants of 'revs' in revision order.
785 """Generate the descendants of 'revs' in revision order.
1389
786
1390 Yield a sequence of revision numbers starting with a child of
787 Yield a sequence of revision numbers starting with a child of
1391 some rev in revs, i.e., each revision is *not* considered a
788 some rev in revs, i.e., each revision is *not* considered a
1392 descendant of itself. Results are ordered by revision number (a
789 descendant of itself. Results are ordered by revision number (a
1393 topological sort)."""
790 topological sort)."""
1394 first = min(revs)
791 first = min(revs)
1395 if first == nullrev:
792 if first == nullrev:
1396 for i in self:
793 for i in self:
1397 yield i
794 yield i
1398 return
795 return
1399
796
1400 seen = set(revs)
797 seen = set(revs)
1401 for i in self.revs(start=first + 1):
798 for i in self.revs(start=first + 1):
1402 for x in self.parentrevs(i):
799 for x in self.parentrevs(i):
1403 if x != nullrev and x in seen:
800 if x != nullrev and x in seen:
1404 seen.add(i)
801 seen.add(i)
1405 yield i
802 yield i
1406 break
803 break
1407
804
1408 def findcommonmissing(self, common=None, heads=None):
805 def findcommonmissing(self, common=None, heads=None):
1409 """Return a tuple of the ancestors of common and the ancestors of heads
806 """Return a tuple of the ancestors of common and the ancestors of heads
1410 that are not ancestors of common. In revset terminology, we return the
807 that are not ancestors of common. In revset terminology, we return the
1411 tuple:
808 tuple:
1412
809
1413 ::common, (::heads) - (::common)
810 ::common, (::heads) - (::common)
1414
811
1415 The list is sorted by revision number, meaning it is
812 The list is sorted by revision number, meaning it is
1416 topologically sorted.
813 topologically sorted.
1417
814
1418 'heads' and 'common' are both lists of node IDs. If heads is
815 'heads' and 'common' are both lists of node IDs. If heads is
1419 not supplied, uses all of the revlog's heads. If common is not
816 not supplied, uses all of the revlog's heads. If common is not
1420 supplied, uses nullid."""
817 supplied, uses nullid."""
1421 if common is None:
818 if common is None:
1422 common = [nullid]
819 common = [nullid]
1423 if heads is None:
820 if heads is None:
1424 heads = self.heads()
821 heads = self.heads()
1425
822
1426 common = [self.rev(n) for n in common]
823 common = [self.rev(n) for n in common]
1427 heads = [self.rev(n) for n in heads]
824 heads = [self.rev(n) for n in heads]
1428
825
1429 # we want the ancestors, but inclusive
826 # we want the ancestors, but inclusive
1430 class lazyset(object):
827 class lazyset(object):
1431 def __init__(self, lazyvalues):
828 def __init__(self, lazyvalues):
1432 self.addedvalues = set()
829 self.addedvalues = set()
1433 self.lazyvalues = lazyvalues
830 self.lazyvalues = lazyvalues
1434
831
1435 def __contains__(self, value):
832 def __contains__(self, value):
1436 return value in self.addedvalues or value in self.lazyvalues
833 return value in self.addedvalues or value in self.lazyvalues
1437
834
1438 def __iter__(self):
835 def __iter__(self):
1439 added = self.addedvalues
836 added = self.addedvalues
1440 for r in added:
837 for r in added:
1441 yield r
838 yield r
1442 for r in self.lazyvalues:
839 for r in self.lazyvalues:
1443 if not r in added:
840 if not r in added:
1444 yield r
841 yield r
1445
842
1446 def add(self, value):
843 def add(self, value):
1447 self.addedvalues.add(value)
844 self.addedvalues.add(value)
1448
845
1449 def update(self, values):
846 def update(self, values):
1450 self.addedvalues.update(values)
847 self.addedvalues.update(values)
1451
848
1452 has = lazyset(self.ancestors(common))
849 has = lazyset(self.ancestors(common))
1453 has.add(nullrev)
850 has.add(nullrev)
1454 has.update(common)
851 has.update(common)
1455
852
1456 # take all ancestors from heads that aren't in has
853 # take all ancestors from heads that aren't in has
1457 missing = set()
854 missing = set()
1458 visit = collections.deque(r for r in heads if r not in has)
855 visit = collections.deque(r for r in heads if r not in has)
1459 while visit:
856 while visit:
1460 r = visit.popleft()
857 r = visit.popleft()
1461 if r in missing:
858 if r in missing:
1462 continue
859 continue
1463 else:
860 else:
1464 missing.add(r)
861 missing.add(r)
1465 for p in self.parentrevs(r):
862 for p in self.parentrevs(r):
1466 if p not in has:
863 if p not in has:
1467 visit.append(p)
864 visit.append(p)
1468 missing = list(missing)
865 missing = list(missing)
1469 missing.sort()
866 missing.sort()
1470 return has, [self.node(miss) for miss in missing]
867 return has, [self.node(miss) for miss in missing]
1471
868
1472 def incrementalmissingrevs(self, common=None):
869 def incrementalmissingrevs(self, common=None):
1473 """Return an object that can be used to incrementally compute the
870 """Return an object that can be used to incrementally compute the
1474 revision numbers of the ancestors of arbitrary sets that are not
871 revision numbers of the ancestors of arbitrary sets that are not
1475 ancestors of common. This is an ancestor.incrementalmissingancestors
872 ancestors of common. This is an ancestor.incrementalmissingancestors
1476 object.
873 object.
1477
874
1478 'common' is a list of revision numbers. If common is not supplied, uses
875 'common' is a list of revision numbers. If common is not supplied, uses
1479 nullrev.
876 nullrev.
1480 """
877 """
1481 if common is None:
878 if common is None:
1482 common = [nullrev]
879 common = [nullrev]
1483
880
1484 return ancestor.incrementalmissingancestors(self.parentrevs, common)
881 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1485
882
1486 def findmissingrevs(self, common=None, heads=None):
883 def findmissingrevs(self, common=None, heads=None):
1487 """Return the revision numbers of the ancestors of heads that
884 """Return the revision numbers of the ancestors of heads that
1488 are not ancestors of common.
885 are not ancestors of common.
1489
886
1490 More specifically, return a list of revision numbers corresponding to
887 More specifically, return a list of revision numbers corresponding to
1491 nodes N such that every N satisfies the following constraints:
888 nodes N such that every N satisfies the following constraints:
1492
889
1493 1. N is an ancestor of some node in 'heads'
890 1. N is an ancestor of some node in 'heads'
1494 2. N is not an ancestor of any node in 'common'
891 2. N is not an ancestor of any node in 'common'
1495
892
1496 The list is sorted by revision number, meaning it is
893 The list is sorted by revision number, meaning it is
1497 topologically sorted.
894 topologically sorted.
1498
895
1499 'heads' and 'common' are both lists of revision numbers. If heads is
896 'heads' and 'common' are both lists of revision numbers. If heads is
1500 not supplied, uses all of the revlog's heads. If common is not
897 not supplied, uses all of the revlog's heads. If common is not
1501 supplied, uses nullid."""
898 supplied, uses nullid."""
1502 if common is None:
899 if common is None:
1503 common = [nullrev]
900 common = [nullrev]
1504 if heads is None:
901 if heads is None:
1505 heads = self.headrevs()
902 heads = self.headrevs()
1506
903
1507 inc = self.incrementalmissingrevs(common=common)
904 inc = self.incrementalmissingrevs(common=common)
1508 return inc.missingancestors(heads)
905 return inc.missingancestors(heads)
1509
906
1510 def findmissing(self, common=None, heads=None):
907 def findmissing(self, common=None, heads=None):
1511 """Return the ancestors of heads that are not ancestors of common.
908 """Return the ancestors of heads that are not ancestors of common.
1512
909
1513 More specifically, return a list of nodes N such that every N
910 More specifically, return a list of nodes N such that every N
1514 satisfies the following constraints:
911 satisfies the following constraints:
1515
912
1516 1. N is an ancestor of some node in 'heads'
913 1. N is an ancestor of some node in 'heads'
1517 2. N is not an ancestor of any node in 'common'
914 2. N is not an ancestor of any node in 'common'
1518
915
1519 The list is sorted by revision number, meaning it is
916 The list is sorted by revision number, meaning it is
1520 topologically sorted.
917 topologically sorted.
1521
918
1522 'heads' and 'common' are both lists of node IDs. If heads is
919 'heads' and 'common' are both lists of node IDs. If heads is
1523 not supplied, uses all of the revlog's heads. If common is not
920 not supplied, uses all of the revlog's heads. If common is not
1524 supplied, uses nullid."""
921 supplied, uses nullid."""
1525 if common is None:
922 if common is None:
1526 common = [nullid]
923 common = [nullid]
1527 if heads is None:
924 if heads is None:
1528 heads = self.heads()
925 heads = self.heads()
1529
926
1530 common = [self.rev(n) for n in common]
927 common = [self.rev(n) for n in common]
1531 heads = [self.rev(n) for n in heads]
928 heads = [self.rev(n) for n in heads]
1532
929
1533 inc = self.incrementalmissingrevs(common=common)
930 inc = self.incrementalmissingrevs(common=common)
1534 return [self.node(r) for r in inc.missingancestors(heads)]
931 return [self.node(r) for r in inc.missingancestors(heads)]
1535
932
1536 def nodesbetween(self, roots=None, heads=None):
933 def nodesbetween(self, roots=None, heads=None):
1537 """Return a topological path from 'roots' to 'heads'.
934 """Return a topological path from 'roots' to 'heads'.
1538
935
1539 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
936 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1540 topologically sorted list of all nodes N that satisfy both of
937 topologically sorted list of all nodes N that satisfy both of
1541 these constraints:
938 these constraints:
1542
939
1543 1. N is a descendant of some node in 'roots'
940 1. N is a descendant of some node in 'roots'
1544 2. N is an ancestor of some node in 'heads'
941 2. N is an ancestor of some node in 'heads'
1545
942
1546 Every node is considered to be both a descendant and an ancestor
943 Every node is considered to be both a descendant and an ancestor
1547 of itself, so every reachable node in 'roots' and 'heads' will be
944 of itself, so every reachable node in 'roots' and 'heads' will be
1548 included in 'nodes'.
945 included in 'nodes'.
1549
946
1550 'outroots' is the list of reachable nodes in 'roots', i.e., the
947 'outroots' is the list of reachable nodes in 'roots', i.e., the
1551 subset of 'roots' that is returned in 'nodes'. Likewise,
948 subset of 'roots' that is returned in 'nodes'. Likewise,
1552 'outheads' is the subset of 'heads' that is also in 'nodes'.
949 'outheads' is the subset of 'heads' that is also in 'nodes'.
1553
950
1554 'roots' and 'heads' are both lists of node IDs. If 'roots' is
951 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1555 unspecified, uses nullid as the only root. If 'heads' is
952 unspecified, uses nullid as the only root. If 'heads' is
1556 unspecified, uses list of all of the revlog's heads."""
953 unspecified, uses list of all of the revlog's heads."""
1557 nonodes = ([], [], [])
954 nonodes = ([], [], [])
1558 if roots is not None:
955 if roots is not None:
1559 roots = list(roots)
956 roots = list(roots)
1560 if not roots:
957 if not roots:
1561 return nonodes
958 return nonodes
1562 lowestrev = min([self.rev(n) for n in roots])
959 lowestrev = min([self.rev(n) for n in roots])
1563 else:
960 else:
1564 roots = [nullid] # Everybody's a descendant of nullid
961 roots = [nullid] # Everybody's a descendant of nullid
1565 lowestrev = nullrev
962 lowestrev = nullrev
1566 if (lowestrev == nullrev) and (heads is None):
963 if (lowestrev == nullrev) and (heads is None):
1567 # We want _all_ the nodes!
964 # We want _all_ the nodes!
1568 return ([self.node(r) for r in self], [nullid], list(self.heads()))
965 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1569 if heads is None:
966 if heads is None:
1570 # All nodes are ancestors, so the latest ancestor is the last
967 # All nodes are ancestors, so the latest ancestor is the last
1571 # node.
968 # node.
1572 highestrev = len(self) - 1
969 highestrev = len(self) - 1
1573 # Set ancestors to None to signal that every node is an ancestor.
970 # Set ancestors to None to signal that every node is an ancestor.
1574 ancestors = None
971 ancestors = None
1575 # Set heads to an empty dictionary for later discovery of heads
972 # Set heads to an empty dictionary for later discovery of heads
1576 heads = {}
973 heads = {}
1577 else:
974 else:
1578 heads = list(heads)
975 heads = list(heads)
1579 if not heads:
976 if not heads:
1580 return nonodes
977 return nonodes
1581 ancestors = set()
978 ancestors = set()
1582 # Turn heads into a dictionary so we can remove 'fake' heads.
979 # Turn heads into a dictionary so we can remove 'fake' heads.
1583 # Also, later we will be using it to filter out the heads we can't
980 # Also, later we will be using it to filter out the heads we can't
1584 # find from roots.
981 # find from roots.
1585 heads = dict.fromkeys(heads, False)
982 heads = dict.fromkeys(heads, False)
1586 # Start at the top and keep marking parents until we're done.
983 # Start at the top and keep marking parents until we're done.
1587 nodestotag = set(heads)
984 nodestotag = set(heads)
1588 # Remember where the top was so we can use it as a limit later.
985 # Remember where the top was so we can use it as a limit later.
1589 highestrev = max([self.rev(n) for n in nodestotag])
986 highestrev = max([self.rev(n) for n in nodestotag])
1590 while nodestotag:
987 while nodestotag:
1591 # grab a node to tag
988 # grab a node to tag
1592 n = nodestotag.pop()
989 n = nodestotag.pop()
1593 # Never tag nullid
990 # Never tag nullid
1594 if n == nullid:
991 if n == nullid:
1595 continue
992 continue
1596 # A node's revision number represents its place in a
993 # A node's revision number represents its place in a
1597 # topologically sorted list of nodes.
994 # topologically sorted list of nodes.
1598 r = self.rev(n)
995 r = self.rev(n)
1599 if r >= lowestrev:
996 if r >= lowestrev:
1600 if n not in ancestors:
997 if n not in ancestors:
1601 # If we are possibly a descendant of one of the roots
998 # If we are possibly a descendant of one of the roots
1602 # and we haven't already been marked as an ancestor
999 # and we haven't already been marked as an ancestor
1603 ancestors.add(n) # Mark as ancestor
1000 ancestors.add(n) # Mark as ancestor
1604 # Add non-nullid parents to list of nodes to tag.
1001 # Add non-nullid parents to list of nodes to tag.
1605 nodestotag.update([p for p in self.parents(n) if
1002 nodestotag.update([p for p in self.parents(n) if
1606 p != nullid])
1003 p != nullid])
1607 elif n in heads: # We've seen it before, is it a fake head?
1004 elif n in heads: # We've seen it before, is it a fake head?
1608 # So it is, real heads should not be the ancestors of
1005 # So it is, real heads should not be the ancestors of
1609 # any other heads.
1006 # any other heads.
1610 heads.pop(n)
1007 heads.pop(n)
1611 if not ancestors:
1008 if not ancestors:
1612 return nonodes
1009 return nonodes
1613 # Now that we have our set of ancestors, we want to remove any
1010 # Now that we have our set of ancestors, we want to remove any
1614 # roots that are not ancestors.
1011 # roots that are not ancestors.
1615
1012
1616 # If one of the roots was nullid, everything is included anyway.
1013 # If one of the roots was nullid, everything is included anyway.
1617 if lowestrev > nullrev:
1014 if lowestrev > nullrev:
1618 # But, since we weren't, let's recompute the lowest rev to not
1015 # But, since we weren't, let's recompute the lowest rev to not
1619 # include roots that aren't ancestors.
1016 # include roots that aren't ancestors.
1620
1017
1621 # Filter out roots that aren't ancestors of heads
1018 # Filter out roots that aren't ancestors of heads
1622 roots = [root for root in roots if root in ancestors]
1019 roots = [root for root in roots if root in ancestors]
1623 # Recompute the lowest revision
1020 # Recompute the lowest revision
1624 if roots:
1021 if roots:
1625 lowestrev = min([self.rev(root) for root in roots])
1022 lowestrev = min([self.rev(root) for root in roots])
1626 else:
1023 else:
1627 # No more roots? Return empty list
1024 # No more roots? Return empty list
1628 return nonodes
1025 return nonodes
1629 else:
1026 else:
1630 # We are descending from nullid, and don't need to care about
1027 # We are descending from nullid, and don't need to care about
1631 # any other roots.
1028 # any other roots.
1632 lowestrev = nullrev
1029 lowestrev = nullrev
1633 roots = [nullid]
1030 roots = [nullid]
1634 # Transform our roots list into a set.
1031 # Transform our roots list into a set.
1635 descendants = set(roots)
1032 descendants = set(roots)
1636 # Also, keep the original roots so we can filter out roots that aren't
1033 # Also, keep the original roots so we can filter out roots that aren't
1637 # 'real' roots (i.e. are descended from other roots).
1034 # 'real' roots (i.e. are descended from other roots).
1638 roots = descendants.copy()
1035 roots = descendants.copy()
1639 # Our topologically sorted list of output nodes.
1036 # Our topologically sorted list of output nodes.
1640 orderedout = []
1037 orderedout = []
1641 # Don't start at nullid since we don't want nullid in our output list,
1038 # Don't start at nullid since we don't want nullid in our output list,
1642 # and if nullid shows up in descendants, empty parents will look like
1039 # and if nullid shows up in descendants, empty parents will look like
1643 # they're descendants.
1040 # they're descendants.
1644 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1041 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1645 n = self.node(r)
1042 n = self.node(r)
1646 isdescendant = False
1043 isdescendant = False
1647 if lowestrev == nullrev: # Everybody is a descendant of nullid
1044 if lowestrev == nullrev: # Everybody is a descendant of nullid
1648 isdescendant = True
1045 isdescendant = True
1649 elif n in descendants:
1046 elif n in descendants:
1650 # n is already a descendant
1047 # n is already a descendant
1651 isdescendant = True
1048 isdescendant = True
1652 # This check only needs to be done here because all the roots
1049 # This check only needs to be done here because all the roots
1653 # will start being marked is descendants before the loop.
1050 # will start being marked is descendants before the loop.
1654 if n in roots:
1051 if n in roots:
1655 # If n was a root, check if it's a 'real' root.
1052 # If n was a root, check if it's a 'real' root.
1656 p = tuple(self.parents(n))
1053 p = tuple(self.parents(n))
1657 # If any of its parents are descendants, it's not a root.
1054 # If any of its parents are descendants, it's not a root.
1658 if (p[0] in descendants) or (p[1] in descendants):
1055 if (p[0] in descendants) or (p[1] in descendants):
1659 roots.remove(n)
1056 roots.remove(n)
1660 else:
1057 else:
1661 p = tuple(self.parents(n))
1058 p = tuple(self.parents(n))
1662 # A node is a descendant if either of its parents are
1059 # A node is a descendant if either of its parents are
1663 # descendants. (We seeded the dependents list with the roots
1060 # descendants. (We seeded the dependents list with the roots
1664 # up there, remember?)
1061 # up there, remember?)
1665 if (p[0] in descendants) or (p[1] in descendants):
1062 if (p[0] in descendants) or (p[1] in descendants):
1666 descendants.add(n)
1063 descendants.add(n)
1667 isdescendant = True
1064 isdescendant = True
1668 if isdescendant and ((ancestors is None) or (n in ancestors)):
1065 if isdescendant and ((ancestors is None) or (n in ancestors)):
1669 # Only include nodes that are both descendants and ancestors.
1066 # Only include nodes that are both descendants and ancestors.
1670 orderedout.append(n)
1067 orderedout.append(n)
1671 if (ancestors is not None) and (n in heads):
1068 if (ancestors is not None) and (n in heads):
1672 # We're trying to figure out which heads are reachable
1069 # We're trying to figure out which heads are reachable
1673 # from roots.
1070 # from roots.
1674 # Mark this head as having been reached
1071 # Mark this head as having been reached
1675 heads[n] = True
1072 heads[n] = True
1676 elif ancestors is None:
1073 elif ancestors is None:
1677 # Otherwise, we're trying to discover the heads.
1074 # Otherwise, we're trying to discover the heads.
1678 # Assume this is a head because if it isn't, the next step
1075 # Assume this is a head because if it isn't, the next step
1679 # will eventually remove it.
1076 # will eventually remove it.
1680 heads[n] = True
1077 heads[n] = True
1681 # But, obviously its parents aren't.
1078 # But, obviously its parents aren't.
1682 for p in self.parents(n):
1079 for p in self.parents(n):
1683 heads.pop(p, None)
1080 heads.pop(p, None)
1684 heads = [head for head, flag in heads.iteritems() if flag]
1081 heads = [head for head, flag in heads.iteritems() if flag]
1685 roots = list(roots)
1082 roots = list(roots)
1686 assert orderedout
1083 assert orderedout
1687 assert roots
1084 assert roots
1688 assert heads
1085 assert heads
1689 return (orderedout, roots, heads)
1086 return (orderedout, roots, heads)
1690
1087
1691 def headrevs(self):
1088 def headrevs(self):
1692 try:
1089 try:
1693 return self.index.headrevs()
1090 return self.index.headrevs()
1694 except AttributeError:
1091 except AttributeError:
1695 return self._headrevs()
1092 return self._headrevs()
1696
1093
1697 def computephases(self, roots):
1094 def computephases(self, roots):
1698 return self.index.computephasesmapsets(roots)
1095 return self.index.computephasesmapsets(roots)
1699
1096
1700 def _headrevs(self):
1097 def _headrevs(self):
1701 count = len(self)
1098 count = len(self)
1702 if not count:
1099 if not count:
1703 return [nullrev]
1100 return [nullrev]
1704 # we won't iter over filtered rev so nobody is a head at start
1101 # we won't iter over filtered rev so nobody is a head at start
1705 ishead = [0] * (count + 1)
1102 ishead = [0] * (count + 1)
1706 index = self.index
1103 index = self.index
1707 for r in self:
1104 for r in self:
1708 ishead[r] = 1 # I may be an head
1105 ishead[r] = 1 # I may be an head
1709 e = index[r]
1106 e = index[r]
1710 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1107 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1711 return [r for r, val in enumerate(ishead) if val]
1108 return [r for r, val in enumerate(ishead) if val]
1712
1109
1713 def heads(self, start=None, stop=None):
1110 def heads(self, start=None, stop=None):
1714 """return the list of all nodes that have no children
1111 """return the list of all nodes that have no children
1715
1112
1716 if start is specified, only heads that are descendants of
1113 if start is specified, only heads that are descendants of
1717 start will be returned
1114 start will be returned
1718 if stop is specified, it will consider all the revs from stop
1115 if stop is specified, it will consider all the revs from stop
1719 as if they had no children
1116 as if they had no children
1720 """
1117 """
1721 if start is None and stop is None:
1118 if start is None and stop is None:
1722 if not len(self):
1119 if not len(self):
1723 return [nullid]
1120 return [nullid]
1724 return [self.node(r) for r in self.headrevs()]
1121 return [self.node(r) for r in self.headrevs()]
1725
1122
1726 if start is None:
1123 if start is None:
1727 start = nullid
1124 start = nullid
1728 if stop is None:
1125 if stop is None:
1729 stop = []
1126 stop = []
1730 stoprevs = set([self.rev(n) for n in stop])
1127 stoprevs = set([self.rev(n) for n in stop])
1731 startrev = self.rev(start)
1128 startrev = self.rev(start)
1732 reachable = {startrev}
1129 reachable = {startrev}
1733 heads = {startrev}
1130 heads = {startrev}
1734
1131
1735 parentrevs = self.parentrevs
1132 parentrevs = self.parentrevs
1736 for r in self.revs(start=startrev + 1):
1133 for r in self.revs(start=startrev + 1):
1737 for p in parentrevs(r):
1134 for p in parentrevs(r):
1738 if p in reachable:
1135 if p in reachable:
1739 if r not in stoprevs:
1136 if r not in stoprevs:
1740 reachable.add(r)
1137 reachable.add(r)
1741 heads.add(r)
1138 heads.add(r)
1742 if p in heads and p not in stoprevs:
1139 if p in heads and p not in stoprevs:
1743 heads.remove(p)
1140 heads.remove(p)
1744
1141
1745 return [self.node(r) for r in heads]
1142 return [self.node(r) for r in heads]
1746
1143
1747 def children(self, node):
1144 def children(self, node):
1748 """find the children of a given node"""
1145 """find the children of a given node"""
1749 c = []
1146 c = []
1750 p = self.rev(node)
1147 p = self.rev(node)
1751 for r in self.revs(start=p + 1):
1148 for r in self.revs(start=p + 1):
1752 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1149 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1753 if prevs:
1150 if prevs:
1754 for pr in prevs:
1151 for pr in prevs:
1755 if pr == p:
1152 if pr == p:
1756 c.append(self.node(r))
1153 c.append(self.node(r))
1757 elif p == nullrev:
1154 elif p == nullrev:
1758 c.append(self.node(r))
1155 c.append(self.node(r))
1759 return c
1156 return c
1760
1157
1761 def commonancestorsheads(self, a, b):
1158 def commonancestorsheads(self, a, b):
1762 """calculate all the heads of the common ancestors of nodes a and b"""
1159 """calculate all the heads of the common ancestors of nodes a and b"""
1763 a, b = self.rev(a), self.rev(b)
1160 a, b = self.rev(a), self.rev(b)
1764 ancs = self._commonancestorsheads(a, b)
1161 ancs = self._commonancestorsheads(a, b)
1765 return pycompat.maplist(self.node, ancs)
1162 return pycompat.maplist(self.node, ancs)
1766
1163
1767 def _commonancestorsheads(self, *revs):
1164 def _commonancestorsheads(self, *revs):
1768 """calculate all the heads of the common ancestors of revs"""
1165 """calculate all the heads of the common ancestors of revs"""
1769 try:
1166 try:
1770 ancs = self.index.commonancestorsheads(*revs)
1167 ancs = self.index.commonancestorsheads(*revs)
1771 except (AttributeError, OverflowError): # C implementation failed
1168 except (AttributeError, OverflowError): # C implementation failed
1772 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1169 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1773 return ancs
1170 return ancs
1774
1171
1775 def isancestor(self, a, b):
1172 def isancestor(self, a, b):
1776 """return True if node a is an ancestor of node b
1173 """return True if node a is an ancestor of node b
1777
1174
1778 A revision is considered an ancestor of itself."""
1175 A revision is considered an ancestor of itself."""
1779 a, b = self.rev(a), self.rev(b)
1176 a, b = self.rev(a), self.rev(b)
1780 return self.isancestorrev(a, b)
1177 return self.isancestorrev(a, b)
1781
1178
1782 def isancestorrev(self, a, b):
1179 def isancestorrev(self, a, b):
1783 """return True if revision a is an ancestor of revision b
1180 """return True if revision a is an ancestor of revision b
1784
1181
1785 A revision is considered an ancestor of itself.
1182 A revision is considered an ancestor of itself.
1786
1183
1787 The implementation of this is trivial but the use of
1184 The implementation of this is trivial but the use of
1788 commonancestorsheads is not."""
1185 commonancestorsheads is not."""
1789 if a == nullrev:
1186 if a == nullrev:
1790 return True
1187 return True
1791 elif a == b:
1188 elif a == b:
1792 return True
1189 return True
1793 elif a > b:
1190 elif a > b:
1794 return False
1191 return False
1795 return a in self._commonancestorsheads(a, b)
1192 return a in self._commonancestorsheads(a, b)
1796
1193
1797 def ancestor(self, a, b):
1194 def ancestor(self, a, b):
1798 """calculate the "best" common ancestor of nodes a and b"""
1195 """calculate the "best" common ancestor of nodes a and b"""
1799
1196
1800 a, b = self.rev(a), self.rev(b)
1197 a, b = self.rev(a), self.rev(b)
1801 try:
1198 try:
1802 ancs = self.index.ancestors(a, b)
1199 ancs = self.index.ancestors(a, b)
1803 except (AttributeError, OverflowError):
1200 except (AttributeError, OverflowError):
1804 ancs = ancestor.ancestors(self.parentrevs, a, b)
1201 ancs = ancestor.ancestors(self.parentrevs, a, b)
1805 if ancs:
1202 if ancs:
1806 # choose a consistent winner when there's a tie
1203 # choose a consistent winner when there's a tie
1807 return min(map(self.node, ancs))
1204 return min(map(self.node, ancs))
1808 return nullid
1205 return nullid
1809
1206
1810 def _match(self, id):
1207 def _match(self, id):
1811 if isinstance(id, int):
1208 if isinstance(id, int):
1812 # rev
1209 # rev
1813 return self.node(id)
1210 return self.node(id)
1814 if len(id) == 20:
1211 if len(id) == 20:
1815 # possibly a binary node
1212 # possibly a binary node
1816 # odds of a binary node being all hex in ASCII are 1 in 10**25
1213 # odds of a binary node being all hex in ASCII are 1 in 10**25
1817 try:
1214 try:
1818 node = id
1215 node = id
1819 self.rev(node) # quick search the index
1216 self.rev(node) # quick search the index
1820 return node
1217 return node
1821 except LookupError:
1218 except LookupError:
1822 pass # may be partial hex id
1219 pass # may be partial hex id
1823 try:
1220 try:
1824 # str(rev)
1221 # str(rev)
1825 rev = int(id)
1222 rev = int(id)
1826 if "%d" % rev != id:
1223 if "%d" % rev != id:
1827 raise ValueError
1224 raise ValueError
1828 if rev < 0:
1225 if rev < 0:
1829 rev = len(self) + rev
1226 rev = len(self) + rev
1830 if rev < 0 or rev >= len(self):
1227 if rev < 0 or rev >= len(self):
1831 raise ValueError
1228 raise ValueError
1832 return self.node(rev)
1229 return self.node(rev)
1833 except (ValueError, OverflowError):
1230 except (ValueError, OverflowError):
1834 pass
1231 pass
1835 if len(id) == 40:
1232 if len(id) == 40:
1836 try:
1233 try:
1837 # a full hex nodeid?
1234 # a full hex nodeid?
1838 node = bin(id)
1235 node = bin(id)
1839 self.rev(node)
1236 self.rev(node)
1840 return node
1237 return node
1841 except (TypeError, LookupError):
1238 except (TypeError, LookupError):
1842 pass
1239 pass
1843
1240
1844 def _partialmatch(self, id):
1241 def _partialmatch(self, id):
1845 # we don't care wdirfilenodeids as they should be always full hash
1242 # we don't care wdirfilenodeids as they should be always full hash
1846 maybewdir = wdirhex.startswith(id)
1243 maybewdir = wdirhex.startswith(id)
1847 try:
1244 try:
1848 partial = self.index.partialmatch(id)
1245 partial = self.index.partialmatch(id)
1849 if partial and self.hasnode(partial):
1246 if partial and self.hasnode(partial):
1850 if maybewdir:
1247 if maybewdir:
1851 # single 'ff...' match in radix tree, ambiguous with wdir
1248 # single 'ff...' match in radix tree, ambiguous with wdir
1852 raise RevlogError
1249 raise RevlogError
1853 return partial
1250 return partial
1854 if maybewdir:
1251 if maybewdir:
1855 # no 'ff...' match in radix tree, wdir identified
1252 # no 'ff...' match in radix tree, wdir identified
1856 raise error.WdirUnsupported
1253 raise error.WdirUnsupported
1857 return None
1254 return None
1858 except RevlogError:
1255 except RevlogError:
1859 # parsers.c radix tree lookup gave multiple matches
1256 # parsers.c radix tree lookup gave multiple matches
1860 # fast path: for unfiltered changelog, radix tree is accurate
1257 # fast path: for unfiltered changelog, radix tree is accurate
1861 if not getattr(self, 'filteredrevs', None):
1258 if not getattr(self, 'filteredrevs', None):
1862 raise AmbiguousPrefixLookupError(id, self.indexfile,
1259 raise AmbiguousPrefixLookupError(id, self.indexfile,
1863 _('ambiguous identifier'))
1260 _('ambiguous identifier'))
1864 # fall through to slow path that filters hidden revisions
1261 # fall through to slow path that filters hidden revisions
1865 except (AttributeError, ValueError):
1262 except (AttributeError, ValueError):
1866 # we are pure python, or key was too short to search radix tree
1263 # we are pure python, or key was too short to search radix tree
1867 pass
1264 pass
1868
1265
1869 if id in self._pcache:
1266 if id in self._pcache:
1870 return self._pcache[id]
1267 return self._pcache[id]
1871
1268
1872 if len(id) <= 40:
1269 if len(id) <= 40:
1873 try:
1270 try:
1874 # hex(node)[:...]
1271 # hex(node)[:...]
1875 l = len(id) // 2 # grab an even number of digits
1272 l = len(id) // 2 # grab an even number of digits
1876 prefix = bin(id[:l * 2])
1273 prefix = bin(id[:l * 2])
1877 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1274 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1878 nl = [n for n in nl if hex(n).startswith(id) and
1275 nl = [n for n in nl if hex(n).startswith(id) and
1879 self.hasnode(n)]
1276 self.hasnode(n)]
1880 if nullhex.startswith(id):
1277 if nullhex.startswith(id):
1881 nl.append(nullid)
1278 nl.append(nullid)
1882 if len(nl) > 0:
1279 if len(nl) > 0:
1883 if len(nl) == 1 and not maybewdir:
1280 if len(nl) == 1 and not maybewdir:
1884 self._pcache[id] = nl[0]
1281 self._pcache[id] = nl[0]
1885 return nl[0]
1282 return nl[0]
1886 raise AmbiguousPrefixLookupError(id, self.indexfile,
1283 raise AmbiguousPrefixLookupError(id, self.indexfile,
1887 _('ambiguous identifier'))
1284 _('ambiguous identifier'))
1888 if maybewdir:
1285 if maybewdir:
1889 raise error.WdirUnsupported
1286 raise error.WdirUnsupported
1890 return None
1287 return None
1891 except TypeError:
1288 except TypeError:
1892 pass
1289 pass
1893
1290
1894 def lookup(self, id):
1291 def lookup(self, id):
1895 """locate a node based on:
1292 """locate a node based on:
1896 - revision number or str(revision number)
1293 - revision number or str(revision number)
1897 - nodeid or subset of hex nodeid
1294 - nodeid or subset of hex nodeid
1898 """
1295 """
1899 n = self._match(id)
1296 n = self._match(id)
1900 if n is not None:
1297 if n is not None:
1901 return n
1298 return n
1902 n = self._partialmatch(id)
1299 n = self._partialmatch(id)
1903 if n:
1300 if n:
1904 return n
1301 return n
1905
1302
1906 raise LookupError(id, self.indexfile, _('no match found'))
1303 raise LookupError(id, self.indexfile, _('no match found'))
1907
1304
1908 def shortest(self, node, minlength=1):
1305 def shortest(self, node, minlength=1):
1909 """Find the shortest unambiguous prefix that matches node."""
1306 """Find the shortest unambiguous prefix that matches node."""
1910 def isvalid(prefix):
1307 def isvalid(prefix):
1911 try:
1308 try:
1912 node = self._partialmatch(prefix)
1309 node = self._partialmatch(prefix)
1913 except error.RevlogError:
1310 except error.RevlogError:
1914 return False
1311 return False
1915 except error.WdirUnsupported:
1312 except error.WdirUnsupported:
1916 # single 'ff...' match
1313 # single 'ff...' match
1917 return True
1314 return True
1918 if node is None:
1315 if node is None:
1919 raise LookupError(node, self.indexfile, _('no node'))
1316 raise LookupError(node, self.indexfile, _('no node'))
1920 return True
1317 return True
1921
1318
1922 def maybewdir(prefix):
1319 def maybewdir(prefix):
1923 return all(c == 'f' for c in prefix)
1320 return all(c == 'f' for c in prefix)
1924
1321
1925 hexnode = hex(node)
1322 hexnode = hex(node)
1926
1323
1927 def disambiguate(hexnode, minlength):
1324 def disambiguate(hexnode, minlength):
1928 """Disambiguate against wdirid."""
1325 """Disambiguate against wdirid."""
1929 for length in range(minlength, 41):
1326 for length in range(minlength, 41):
1930 prefix = hexnode[:length]
1327 prefix = hexnode[:length]
1931 if not maybewdir(prefix):
1328 if not maybewdir(prefix):
1932 return prefix
1329 return prefix
1933
1330
1934 if not getattr(self, 'filteredrevs', None):
1331 if not getattr(self, 'filteredrevs', None):
1935 try:
1332 try:
1936 length = max(self.index.shortest(node), minlength)
1333 length = max(self.index.shortest(node), minlength)
1937 return disambiguate(hexnode, length)
1334 return disambiguate(hexnode, length)
1938 except RevlogError:
1335 except RevlogError:
1939 if node != wdirid:
1336 if node != wdirid:
1940 raise LookupError(node, self.indexfile, _('no node'))
1337 raise LookupError(node, self.indexfile, _('no node'))
1941 except AttributeError:
1338 except AttributeError:
1942 # Fall through to pure code
1339 # Fall through to pure code
1943 pass
1340 pass
1944
1341
1945 if node == wdirid:
1342 if node == wdirid:
1946 for length in range(minlength, 41):
1343 for length in range(minlength, 41):
1947 prefix = hexnode[:length]
1344 prefix = hexnode[:length]
1948 if isvalid(prefix):
1345 if isvalid(prefix):
1949 return prefix
1346 return prefix
1950
1347
1951 for length in range(minlength, 41):
1348 for length in range(minlength, 41):
1952 prefix = hexnode[:length]
1349 prefix = hexnode[:length]
1953 if isvalid(prefix):
1350 if isvalid(prefix):
1954 return disambiguate(hexnode, length)
1351 return disambiguate(hexnode, length)
1955
1352
1956 def cmp(self, node, text):
1353 def cmp(self, node, text):
1957 """compare text with a given file revision
1354 """compare text with a given file revision
1958
1355
1959 returns True if text is different than what is stored.
1356 returns True if text is different than what is stored.
1960 """
1357 """
1961 p1, p2 = self.parents(node)
1358 p1, p2 = self.parents(node)
1962 return hash(text, p1, p2) != node
1359 return hash(text, p1, p2) != node
1963
1360
1964 def _cachesegment(self, offset, data):
1361 def _cachesegment(self, offset, data):
1965 """Add a segment to the revlog cache.
1362 """Add a segment to the revlog cache.
1966
1363
1967 Accepts an absolute offset and the data that is at that location.
1364 Accepts an absolute offset and the data that is at that location.
1968 """
1365 """
1969 o, d = self._chunkcache
1366 o, d = self._chunkcache
1970 # try to add to existing cache
1367 # try to add to existing cache
1971 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1368 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1972 self._chunkcache = o, d + data
1369 self._chunkcache = o, d + data
1973 else:
1370 else:
1974 self._chunkcache = offset, data
1371 self._chunkcache = offset, data
1975
1372
1976 def _readsegment(self, offset, length, df=None):
1373 def _readsegment(self, offset, length, df=None):
1977 """Load a segment of raw data from the revlog.
1374 """Load a segment of raw data from the revlog.
1978
1375
1979 Accepts an absolute offset, length to read, and an optional existing
1376 Accepts an absolute offset, length to read, and an optional existing
1980 file handle to read from.
1377 file handle to read from.
1981
1378
1982 If an existing file handle is passed, it will be seeked and the
1379 If an existing file handle is passed, it will be seeked and the
1983 original seek position will NOT be restored.
1380 original seek position will NOT be restored.
1984
1381
1985 Returns a str or buffer of raw byte data.
1382 Returns a str or buffer of raw byte data.
1986 """
1383 """
1987 # Cache data both forward and backward around the requested
1384 # Cache data both forward and backward around the requested
1988 # data, in a fixed size window. This helps speed up operations
1385 # data, in a fixed size window. This helps speed up operations
1989 # involving reading the revlog backwards.
1386 # involving reading the revlog backwards.
1990 cachesize = self._chunkcachesize
1387 cachesize = self._chunkcachesize
1991 realoffset = offset & ~(cachesize - 1)
1388 realoffset = offset & ~(cachesize - 1)
1992 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1389 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1993 - realoffset)
1390 - realoffset)
1994 with self._datareadfp(df) as df:
1391 with self._datareadfp(df) as df:
1995 df.seek(realoffset)
1392 df.seek(realoffset)
1996 d = df.read(reallength)
1393 d = df.read(reallength)
1997 self._cachesegment(realoffset, d)
1394 self._cachesegment(realoffset, d)
1998 if offset != realoffset or reallength != length:
1395 if offset != realoffset or reallength != length:
1999 return util.buffer(d, offset - realoffset, length)
1396 return util.buffer(d, offset - realoffset, length)
2000 return d
1397 return d
2001
1398
2002 def _getsegment(self, offset, length, df=None):
1399 def _getsegment(self, offset, length, df=None):
2003 """Obtain a segment of raw data from the revlog.
1400 """Obtain a segment of raw data from the revlog.
2004
1401
2005 Accepts an absolute offset, length of bytes to obtain, and an
1402 Accepts an absolute offset, length of bytes to obtain, and an
2006 optional file handle to the already-opened revlog. If the file
1403 optional file handle to the already-opened revlog. If the file
2007 handle is used, it's original seek position will not be preserved.
1404 handle is used, it's original seek position will not be preserved.
2008
1405
2009 Requests for data may be returned from a cache.
1406 Requests for data may be returned from a cache.
2010
1407
2011 Returns a str or a buffer instance of raw byte data.
1408 Returns a str or a buffer instance of raw byte data.
2012 """
1409 """
2013 o, d = self._chunkcache
1410 o, d = self._chunkcache
2014 l = len(d)
1411 l = len(d)
2015
1412
2016 # is it in the cache?
1413 # is it in the cache?
2017 cachestart = offset - o
1414 cachestart = offset - o
2018 cacheend = cachestart + length
1415 cacheend = cachestart + length
2019 if cachestart >= 0 and cacheend <= l:
1416 if cachestart >= 0 and cacheend <= l:
2020 if cachestart == 0 and cacheend == l:
1417 if cachestart == 0 and cacheend == l:
2021 return d # avoid a copy
1418 return d # avoid a copy
2022 return util.buffer(d, cachestart, cacheend - cachestart)
1419 return util.buffer(d, cachestart, cacheend - cachestart)
2023
1420
2024 return self._readsegment(offset, length, df=df)
1421 return self._readsegment(offset, length, df=df)
2025
1422
2026 def _getsegmentforrevs(self, startrev, endrev, df=None):
1423 def _getsegmentforrevs(self, startrev, endrev, df=None):
2027 """Obtain a segment of raw data corresponding to a range of revisions.
1424 """Obtain a segment of raw data corresponding to a range of revisions.
2028
1425
2029 Accepts the start and end revisions and an optional already-open
1426 Accepts the start and end revisions and an optional already-open
2030 file handle to be used for reading. If the file handle is read, its
1427 file handle to be used for reading. If the file handle is read, its
2031 seek position will not be preserved.
1428 seek position will not be preserved.
2032
1429
2033 Requests for data may be satisfied by a cache.
1430 Requests for data may be satisfied by a cache.
2034
1431
2035 Returns a 2-tuple of (offset, data) for the requested range of
1432 Returns a 2-tuple of (offset, data) for the requested range of
2036 revisions. Offset is the integer offset from the beginning of the
1433 revisions. Offset is the integer offset from the beginning of the
2037 revlog and data is a str or buffer of the raw byte data.
1434 revlog and data is a str or buffer of the raw byte data.
2038
1435
2039 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1436 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
2040 to determine where each revision's data begins and ends.
1437 to determine where each revision's data begins and ends.
2041 """
1438 """
2042 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1439 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
2043 # (functions are expensive).
1440 # (functions are expensive).
2044 index = self.index
1441 index = self.index
2045 istart = index[startrev]
1442 istart = index[startrev]
2046 start = int(istart[0] >> 16)
1443 start = int(istart[0] >> 16)
2047 if startrev == endrev:
1444 if startrev == endrev:
2048 end = start + istart[1]
1445 end = start + istart[1]
2049 else:
1446 else:
2050 iend = index[endrev]
1447 iend = index[endrev]
2051 end = int(iend[0] >> 16) + iend[1]
1448 end = int(iend[0] >> 16) + iend[1]
2052
1449
2053 if self._inline:
1450 if self._inline:
2054 start += (startrev + 1) * self._io.size
1451 start += (startrev + 1) * self._io.size
2055 end += (endrev + 1) * self._io.size
1452 end += (endrev + 1) * self._io.size
2056 length = end - start
1453 length = end - start
2057
1454
2058 return start, self._getsegment(start, length, df=df)
1455 return start, self._getsegment(start, length, df=df)
2059
1456
2060 def _chunk(self, rev, df=None):
1457 def _chunk(self, rev, df=None):
2061 """Obtain a single decompressed chunk for a revision.
1458 """Obtain a single decompressed chunk for a revision.
2062
1459
2063 Accepts an integer revision and an optional already-open file handle
1460 Accepts an integer revision and an optional already-open file handle
2064 to be used for reading. If used, the seek position of the file will not
1461 to be used for reading. If used, the seek position of the file will not
2065 be preserved.
1462 be preserved.
2066
1463
2067 Returns a str holding uncompressed data for the requested revision.
1464 Returns a str holding uncompressed data for the requested revision.
2068 """
1465 """
2069 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1466 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
2070
1467
2071 def _chunks(self, revs, df=None, targetsize=None):
1468 def _chunks(self, revs, df=None, targetsize=None):
2072 """Obtain decompressed chunks for the specified revisions.
1469 """Obtain decompressed chunks for the specified revisions.
2073
1470
2074 Accepts an iterable of numeric revisions that are assumed to be in
1471 Accepts an iterable of numeric revisions that are assumed to be in
2075 ascending order. Also accepts an optional already-open file handle
1472 ascending order. Also accepts an optional already-open file handle
2076 to be used for reading. If used, the seek position of the file will
1473 to be used for reading. If used, the seek position of the file will
2077 not be preserved.
1474 not be preserved.
2078
1475
2079 This function is similar to calling ``self._chunk()`` multiple times,
1476 This function is similar to calling ``self._chunk()`` multiple times,
2080 but is faster.
1477 but is faster.
2081
1478
2082 Returns a list with decompressed data for each requested revision.
1479 Returns a list with decompressed data for each requested revision.
2083 """
1480 """
2084 if not revs:
1481 if not revs:
2085 return []
1482 return []
2086 start = self.start
1483 start = self.start
2087 length = self.length
1484 length = self.length
2088 inline = self._inline
1485 inline = self._inline
2089 iosize = self._io.size
1486 iosize = self._io.size
2090 buffer = util.buffer
1487 buffer = util.buffer
2091
1488
2092 l = []
1489 l = []
2093 ladd = l.append
1490 ladd = l.append
2094
1491
2095 if not self._withsparseread:
1492 if not self._withsparseread:
2096 slicedchunks = (revs,)
1493 slicedchunks = (revs,)
2097 else:
1494 else:
2098 slicedchunks = _slicechunk(self, revs, targetsize=targetsize)
1495 slicedchunks = deltautil.slicechunk(self, revs,
1496 targetsize=targetsize)
2099
1497
2100 for revschunk in slicedchunks:
1498 for revschunk in slicedchunks:
2101 firstrev = revschunk[0]
1499 firstrev = revschunk[0]
2102 # Skip trailing revisions with empty diff
1500 # Skip trailing revisions with empty diff
2103 for lastrev in revschunk[::-1]:
1501 for lastrev in revschunk[::-1]:
2104 if length(lastrev) != 0:
1502 if length(lastrev) != 0:
2105 break
1503 break
2106
1504
2107 try:
1505 try:
2108 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1506 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
2109 except OverflowError:
1507 except OverflowError:
2110 # issue4215 - we can't cache a run of chunks greater than
1508 # issue4215 - we can't cache a run of chunks greater than
2111 # 2G on Windows
1509 # 2G on Windows
2112 return [self._chunk(rev, df=df) for rev in revschunk]
1510 return [self._chunk(rev, df=df) for rev in revschunk]
2113
1511
2114 decomp = self.decompress
1512 decomp = self.decompress
2115 for rev in revschunk:
1513 for rev in revschunk:
2116 chunkstart = start(rev)
1514 chunkstart = start(rev)
2117 if inline:
1515 if inline:
2118 chunkstart += (rev + 1) * iosize
1516 chunkstart += (rev + 1) * iosize
2119 chunklength = length(rev)
1517 chunklength = length(rev)
2120 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1518 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
2121
1519
2122 return l
1520 return l
2123
1521
2124 def _chunkclear(self):
1522 def _chunkclear(self):
2125 """Clear the raw chunk cache."""
1523 """Clear the raw chunk cache."""
2126 self._chunkcache = (0, '')
1524 self._chunkcache = (0, '')
2127
1525
2128 def deltaparent(self, rev):
1526 def deltaparent(self, rev):
2129 """return deltaparent of the given revision"""
1527 """return deltaparent of the given revision"""
2130 base = self.index[rev][3]
1528 base = self.index[rev][3]
2131 if base == rev:
1529 if base == rev:
2132 return nullrev
1530 return nullrev
2133 elif self._generaldelta:
1531 elif self._generaldelta:
2134 return base
1532 return base
2135 else:
1533 else:
2136 return rev - 1
1534 return rev - 1
2137
1535
2138 def issnapshot(self, rev):
1536 def issnapshot(self, rev):
2139 """tells whether rev is a snapshot
1537 """tells whether rev is a snapshot
2140 """
1538 """
2141 if rev == nullrev:
1539 if rev == nullrev:
2142 return True
1540 return True
2143 deltap = self.deltaparent(rev)
1541 deltap = self.deltaparent(rev)
2144 if deltap == nullrev:
1542 if deltap == nullrev:
2145 return True
1543 return True
2146 p1, p2 = self.parentrevs(rev)
1544 p1, p2 = self.parentrevs(rev)
2147 if deltap in (p1, p2):
1545 if deltap in (p1, p2):
2148 return False
1546 return False
2149 return self.issnapshot(deltap)
1547 return self.issnapshot(deltap)
2150
1548
2151 def snapshotdepth(self, rev):
1549 def snapshotdepth(self, rev):
2152 """number of snapshot in the chain before this one"""
1550 """number of snapshot in the chain before this one"""
2153 if not self.issnapshot(rev):
1551 if not self.issnapshot(rev):
2154 raise ProgrammingError('revision %d not a snapshot')
1552 raise ProgrammingError('revision %d not a snapshot')
2155 return len(self._deltachain(rev)[0]) - 1
1553 return len(self._deltachain(rev)[0]) - 1
2156
1554
2157 def revdiff(self, rev1, rev2):
1555 def revdiff(self, rev1, rev2):
2158 """return or calculate a delta between two revisions
1556 """return or calculate a delta between two revisions
2159
1557
2160 The delta calculated is in binary form and is intended to be written to
1558 The delta calculated is in binary form and is intended to be written to
2161 revlog data directly. So this function needs raw revision data.
1559 revlog data directly. So this function needs raw revision data.
2162 """
1560 """
2163 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1561 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2164 return bytes(self._chunk(rev2))
1562 return bytes(self._chunk(rev2))
2165
1563
2166 return mdiff.textdiff(self.revision(rev1, raw=True),
1564 return mdiff.textdiff(self.revision(rev1, raw=True),
2167 self.revision(rev2, raw=True))
1565 self.revision(rev2, raw=True))
2168
1566
2169 def revision(self, nodeorrev, _df=None, raw=False):
1567 def revision(self, nodeorrev, _df=None, raw=False):
2170 """return an uncompressed revision of a given node or revision
1568 """return an uncompressed revision of a given node or revision
2171 number.
1569 number.
2172
1570
2173 _df - an existing file handle to read from. (internal-only)
1571 _df - an existing file handle to read from. (internal-only)
2174 raw - an optional argument specifying if the revision data is to be
1572 raw - an optional argument specifying if the revision data is to be
2175 treated as raw data when applying flag transforms. 'raw' should be set
1573 treated as raw data when applying flag transforms. 'raw' should be set
2176 to True when generating changegroups or in debug commands.
1574 to True when generating changegroups or in debug commands.
2177 """
1575 """
2178 if isinstance(nodeorrev, int):
1576 if isinstance(nodeorrev, int):
2179 rev = nodeorrev
1577 rev = nodeorrev
2180 node = self.node(rev)
1578 node = self.node(rev)
2181 else:
1579 else:
2182 node = nodeorrev
1580 node = nodeorrev
2183 rev = None
1581 rev = None
2184
1582
2185 cachedrev = None
1583 cachedrev = None
2186 flags = None
1584 flags = None
2187 rawtext = None
1585 rawtext = None
2188 if node == nullid:
1586 if node == nullid:
2189 return ""
1587 return ""
2190 if self._cache:
1588 if self._cache:
2191 if self._cache[0] == node:
1589 if self._cache[0] == node:
2192 # _cache only stores rawtext
1590 # _cache only stores rawtext
2193 if raw:
1591 if raw:
2194 return self._cache[2]
1592 return self._cache[2]
2195 # duplicated, but good for perf
1593 # duplicated, but good for perf
2196 if rev is None:
1594 if rev is None:
2197 rev = self.rev(node)
1595 rev = self.rev(node)
2198 if flags is None:
1596 if flags is None:
2199 flags = self.flags(rev)
1597 flags = self.flags(rev)
2200 # no extra flags set, no flag processor runs, text = rawtext
1598 # no extra flags set, no flag processor runs, text = rawtext
2201 if flags == REVIDX_DEFAULT_FLAGS:
1599 if flags == REVIDX_DEFAULT_FLAGS:
2202 return self._cache[2]
1600 return self._cache[2]
2203 # rawtext is reusable. need to run flag processor
1601 # rawtext is reusable. need to run flag processor
2204 rawtext = self._cache[2]
1602 rawtext = self._cache[2]
2205
1603
2206 cachedrev = self._cache[1]
1604 cachedrev = self._cache[1]
2207
1605
2208 # look up what we need to read
1606 # look up what we need to read
2209 if rawtext is None:
1607 if rawtext is None:
2210 if rev is None:
1608 if rev is None:
2211 rev = self.rev(node)
1609 rev = self.rev(node)
2212
1610
2213 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1611 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2214 if stopped:
1612 if stopped:
2215 rawtext = self._cache[2]
1613 rawtext = self._cache[2]
2216
1614
2217 # drop cache to save memory
1615 # drop cache to save memory
2218 self._cache = None
1616 self._cache = None
2219
1617
2220 targetsize = None
1618 targetsize = None
2221 rawsize = self.index[rev][2]
1619 rawsize = self.index[rev][2]
2222 if 0 <= rawsize:
1620 if 0 <= rawsize:
2223 targetsize = 4 * rawsize
1621 targetsize = 4 * rawsize
2224
1622
2225 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1623 bins = self._chunks(chain, df=_df, targetsize=targetsize)
2226 if rawtext is None:
1624 if rawtext is None:
2227 rawtext = bytes(bins[0])
1625 rawtext = bytes(bins[0])
2228 bins = bins[1:]
1626 bins = bins[1:]
2229
1627
2230 rawtext = mdiff.patches(rawtext, bins)
1628 rawtext = mdiff.patches(rawtext, bins)
2231 self._cache = (node, rev, rawtext)
1629 self._cache = (node, rev, rawtext)
2232
1630
2233 if flags is None:
1631 if flags is None:
2234 if rev is None:
1632 if rev is None:
2235 rev = self.rev(node)
1633 rev = self.rev(node)
2236 flags = self.flags(rev)
1634 flags = self.flags(rev)
2237
1635
2238 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1636 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
2239 if validatehash:
1637 if validatehash:
2240 self.checkhash(text, node, rev=rev)
1638 self.checkhash(text, node, rev=rev)
2241
1639
2242 return text
1640 return text
2243
1641
2244 def hash(self, text, p1, p2):
1642 def hash(self, text, p1, p2):
2245 """Compute a node hash.
1643 """Compute a node hash.
2246
1644
2247 Available as a function so that subclasses can replace the hash
1645 Available as a function so that subclasses can replace the hash
2248 as needed.
1646 as needed.
2249 """
1647 """
2250 return hash(text, p1, p2)
1648 return hash(text, p1, p2)
2251
1649
2252 def _processflags(self, text, flags, operation, raw=False):
1650 def _processflags(self, text, flags, operation, raw=False):
2253 """Inspect revision data flags and applies transforms defined by
1651 """Inspect revision data flags and applies transforms defined by
2254 registered flag processors.
1652 registered flag processors.
2255
1653
2256 ``text`` - the revision data to process
1654 ``text`` - the revision data to process
2257 ``flags`` - the revision flags
1655 ``flags`` - the revision flags
2258 ``operation`` - the operation being performed (read or write)
1656 ``operation`` - the operation being performed (read or write)
2259 ``raw`` - an optional argument describing if the raw transform should be
1657 ``raw`` - an optional argument describing if the raw transform should be
2260 applied.
1658 applied.
2261
1659
2262 This method processes the flags in the order (or reverse order if
1660 This method processes the flags in the order (or reverse order if
2263 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1661 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
2264 flag processors registered for present flags. The order of flags defined
1662 flag processors registered for present flags. The order of flags defined
2265 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1663 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
2266
1664
2267 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1665 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
2268 processed text and ``validatehash`` is a bool indicating whether the
1666 processed text and ``validatehash`` is a bool indicating whether the
2269 returned text should be checked for hash integrity.
1667 returned text should be checked for hash integrity.
2270
1668
2271 Note: If the ``raw`` argument is set, it has precedence over the
1669 Note: If the ``raw`` argument is set, it has precedence over the
2272 operation and will only update the value of ``validatehash``.
1670 operation and will only update the value of ``validatehash``.
2273 """
1671 """
2274 # fast path: no flag processors will run
1672 # fast path: no flag processors will run
2275 if flags == 0:
1673 if flags == 0:
2276 return text, True
1674 return text, True
2277 if not operation in ('read', 'write'):
1675 if not operation in ('read', 'write'):
2278 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
1676 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
2279 # Check all flags are known.
1677 # Check all flags are known.
2280 if flags & ~REVIDX_KNOWN_FLAGS:
1678 if flags & ~REVIDX_KNOWN_FLAGS:
2281 raise RevlogError(_("incompatible revision flag '%#x'") %
1679 raise RevlogError(_("incompatible revision flag '%#x'") %
2282 (flags & ~REVIDX_KNOWN_FLAGS))
1680 (flags & ~REVIDX_KNOWN_FLAGS))
2283 validatehash = True
1681 validatehash = True
2284 # Depending on the operation (read or write), the order might be
1682 # Depending on the operation (read or write), the order might be
2285 # reversed due to non-commutative transforms.
1683 # reversed due to non-commutative transforms.
2286 orderedflags = REVIDX_FLAGS_ORDER
1684 orderedflags = REVIDX_FLAGS_ORDER
2287 if operation == 'write':
1685 if operation == 'write':
2288 orderedflags = reversed(orderedflags)
1686 orderedflags = reversed(orderedflags)
2289
1687
2290 for flag in orderedflags:
1688 for flag in orderedflags:
2291 # If a flagprocessor has been registered for a known flag, apply the
1689 # If a flagprocessor has been registered for a known flag, apply the
2292 # related operation transform and update result tuple.
1690 # related operation transform and update result tuple.
2293 if flag & flags:
1691 if flag & flags:
2294 vhash = True
1692 vhash = True
2295
1693
2296 if flag not in _flagprocessors:
1694 if flag not in _flagprocessors:
2297 message = _("missing processor for flag '%#x'") % (flag)
1695 message = _("missing processor for flag '%#x'") % (flag)
2298 raise RevlogError(message)
1696 raise RevlogError(message)
2299
1697
2300 processor = _flagprocessors[flag]
1698 processor = _flagprocessors[flag]
2301 if processor is not None:
1699 if processor is not None:
2302 readtransform, writetransform, rawtransform = processor
1700 readtransform, writetransform, rawtransform = processor
2303
1701
2304 if raw:
1702 if raw:
2305 vhash = rawtransform(self, text)
1703 vhash = rawtransform(self, text)
2306 elif operation == 'read':
1704 elif operation == 'read':
2307 text, vhash = readtransform(self, text)
1705 text, vhash = readtransform(self, text)
2308 else: # write operation
1706 else: # write operation
2309 text, vhash = writetransform(self, text)
1707 text, vhash = writetransform(self, text)
2310 validatehash = validatehash and vhash
1708 validatehash = validatehash and vhash
2311
1709
2312 return text, validatehash
1710 return text, validatehash
2313
1711
2314 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1712 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2315 """Check node hash integrity.
1713 """Check node hash integrity.
2316
1714
2317 Available as a function so that subclasses can extend hash mismatch
1715 Available as a function so that subclasses can extend hash mismatch
2318 behaviors as needed.
1716 behaviors as needed.
2319 """
1717 """
2320 try:
1718 try:
2321 if p1 is None and p2 is None:
1719 if p1 is None and p2 is None:
2322 p1, p2 = self.parents(node)
1720 p1, p2 = self.parents(node)
2323 if node != self.hash(text, p1, p2):
1721 if node != self.hash(text, p1, p2):
2324 revornode = rev
1722 revornode = rev
2325 if revornode is None:
1723 if revornode is None:
2326 revornode = templatefilters.short(hex(node))
1724 revornode = templatefilters.short(hex(node))
2327 raise RevlogError(_("integrity check failed on %s:%s")
1725 raise RevlogError(_("integrity check failed on %s:%s")
2328 % (self.indexfile, pycompat.bytestr(revornode)))
1726 % (self.indexfile, pycompat.bytestr(revornode)))
2329 except RevlogError:
1727 except RevlogError:
2330 if self._censorable and _censoredtext(text):
1728 if self._censorable and _censoredtext(text):
2331 raise error.CensoredNodeError(self.indexfile, node, text)
1729 raise error.CensoredNodeError(self.indexfile, node, text)
2332 raise
1730 raise
2333
1731
2334 def _enforceinlinesize(self, tr, fp=None):
1732 def _enforceinlinesize(self, tr, fp=None):
2335 """Check if the revlog is too big for inline and convert if so.
1733 """Check if the revlog is too big for inline and convert if so.
2336
1734
2337 This should be called after revisions are added to the revlog. If the
1735 This should be called after revisions are added to the revlog. If the
2338 revlog has grown too large to be an inline revlog, it will convert it
1736 revlog has grown too large to be an inline revlog, it will convert it
2339 to use multiple index and data files.
1737 to use multiple index and data files.
2340 """
1738 """
2341 tiprev = len(self) - 1
1739 tiprev = len(self) - 1
2342 if (not self._inline or
1740 if (not self._inline or
2343 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1741 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
2344 return
1742 return
2345
1743
2346 trinfo = tr.find(self.indexfile)
1744 trinfo = tr.find(self.indexfile)
2347 if trinfo is None:
1745 if trinfo is None:
2348 raise RevlogError(_("%s not found in the transaction")
1746 raise RevlogError(_("%s not found in the transaction")
2349 % self.indexfile)
1747 % self.indexfile)
2350
1748
2351 trindex = trinfo[2]
1749 trindex = trinfo[2]
2352 if trindex is not None:
1750 if trindex is not None:
2353 dataoff = self.start(trindex)
1751 dataoff = self.start(trindex)
2354 else:
1752 else:
2355 # revlog was stripped at start of transaction, use all leftover data
1753 # revlog was stripped at start of transaction, use all leftover data
2356 trindex = len(self) - 1
1754 trindex = len(self) - 1
2357 dataoff = self.end(tiprev)
1755 dataoff = self.end(tiprev)
2358
1756
2359 tr.add(self.datafile, dataoff)
1757 tr.add(self.datafile, dataoff)
2360
1758
2361 if fp:
1759 if fp:
2362 fp.flush()
1760 fp.flush()
2363 fp.close()
1761 fp.close()
2364
1762
2365 with self._datafp('w') as df:
1763 with self._datafp('w') as df:
2366 for r in self:
1764 for r in self:
2367 df.write(self._getsegmentforrevs(r, r)[1])
1765 df.write(self._getsegmentforrevs(r, r)[1])
2368
1766
2369 with self._indexfp('w') as fp:
1767 with self._indexfp('w') as fp:
2370 self.version &= ~FLAG_INLINE_DATA
1768 self.version &= ~FLAG_INLINE_DATA
2371 self._inline = False
1769 self._inline = False
2372 io = self._io
1770 io = self._io
2373 for i in self:
1771 for i in self:
2374 e = io.packentry(self.index[i], self.node, self.version, i)
1772 e = io.packentry(self.index[i], self.node, self.version, i)
2375 fp.write(e)
1773 fp.write(e)
2376
1774
2377 # the temp file replace the real index when we exit the context
1775 # the temp file replace the real index when we exit the context
2378 # manager
1776 # manager
2379
1777
2380 tr.replace(self.indexfile, trindex * self._io.size)
1778 tr.replace(self.indexfile, trindex * self._io.size)
2381 self._chunkclear()
1779 self._chunkclear()
2382
1780
2383 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1781 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
2384 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1782 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
2385 """add a revision to the log
1783 """add a revision to the log
2386
1784
2387 text - the revision data to add
1785 text - the revision data to add
2388 transaction - the transaction object used for rollback
1786 transaction - the transaction object used for rollback
2389 link - the linkrev data to add
1787 link - the linkrev data to add
2390 p1, p2 - the parent nodeids of the revision
1788 p1, p2 - the parent nodeids of the revision
2391 cachedelta - an optional precomputed delta
1789 cachedelta - an optional precomputed delta
2392 node - nodeid of revision; typically node is not specified, and it is
1790 node - nodeid of revision; typically node is not specified, and it is
2393 computed by default as hash(text, p1, p2), however subclasses might
1791 computed by default as hash(text, p1, p2), however subclasses might
2394 use different hashing method (and override checkhash() in such case)
1792 use different hashing method (and override checkhash() in such case)
2395 flags - the known flags to set on the revision
1793 flags - the known flags to set on the revision
2396 deltacomputer - an optional _deltacomputer instance shared between
1794 deltacomputer - an optional deltacomputer instance shared between
2397 multiple calls
1795 multiple calls
2398 """
1796 """
2399 if link == nullrev:
1797 if link == nullrev:
2400 raise RevlogError(_("attempted to add linkrev -1 to %s")
1798 raise RevlogError(_("attempted to add linkrev -1 to %s")
2401 % self.indexfile)
1799 % self.indexfile)
2402
1800
2403 if flags:
1801 if flags:
2404 node = node or self.hash(text, p1, p2)
1802 node = node or self.hash(text, p1, p2)
2405
1803
2406 rawtext, validatehash = self._processflags(text, flags, 'write')
1804 rawtext, validatehash = self._processflags(text, flags, 'write')
2407
1805
2408 # If the flag processor modifies the revision data, ignore any provided
1806 # If the flag processor modifies the revision data, ignore any provided
2409 # cachedelta.
1807 # cachedelta.
2410 if rawtext != text:
1808 if rawtext != text:
2411 cachedelta = None
1809 cachedelta = None
2412
1810
2413 if len(rawtext) > _maxentrysize:
1811 if len(rawtext) > _maxentrysize:
2414 raise RevlogError(
1812 raise RevlogError(
2415 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1813 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
2416 % (self.indexfile, len(rawtext)))
1814 % (self.indexfile, len(rawtext)))
2417
1815
2418 node = node or self.hash(rawtext, p1, p2)
1816 node = node or self.hash(rawtext, p1, p2)
2419 if node in self.nodemap:
1817 if node in self.nodemap:
2420 return node
1818 return node
2421
1819
2422 if validatehash:
1820 if validatehash:
2423 self.checkhash(rawtext, node, p1=p1, p2=p2)
1821 self.checkhash(rawtext, node, p1=p1, p2=p2)
2424
1822
2425 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1823 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
2426 flags, cachedelta=cachedelta,
1824 flags, cachedelta=cachedelta,
2427 deltacomputer=deltacomputer)
1825 deltacomputer=deltacomputer)
2428
1826
2429 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1827 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
2430 cachedelta=None, deltacomputer=None):
1828 cachedelta=None, deltacomputer=None):
2431 """add a raw revision with known flags, node and parents
1829 """add a raw revision with known flags, node and parents
2432 useful when reusing a revision not stored in this revlog (ex: received
1830 useful when reusing a revision not stored in this revlog (ex: received
2433 over wire, or read from an external bundle).
1831 over wire, or read from an external bundle).
2434 """
1832 """
2435 dfh = None
1833 dfh = None
2436 if not self._inline:
1834 if not self._inline:
2437 dfh = self._datafp("a+")
1835 dfh = self._datafp("a+")
2438 ifh = self._indexfp("a+")
1836 ifh = self._indexfp("a+")
2439 try:
1837 try:
2440 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1838 return self._addrevision(node, rawtext, transaction, link, p1, p2,
2441 flags, cachedelta, ifh, dfh,
1839 flags, cachedelta, ifh, dfh,
2442 deltacomputer=deltacomputer)
1840 deltacomputer=deltacomputer)
2443 finally:
1841 finally:
2444 if dfh:
1842 if dfh:
2445 dfh.close()
1843 dfh.close()
2446 ifh.close()
1844 ifh.close()
2447
1845
2448 def compress(self, data):
1846 def compress(self, data):
2449 """Generate a possibly-compressed representation of data."""
1847 """Generate a possibly-compressed representation of data."""
2450 if not data:
1848 if not data:
2451 return '', data
1849 return '', data
2452
1850
2453 compressed = self._compressor.compress(data)
1851 compressed = self._compressor.compress(data)
2454
1852
2455 if compressed:
1853 if compressed:
2456 # The revlog compressor added the header in the returned data.
1854 # The revlog compressor added the header in the returned data.
2457 return '', compressed
1855 return '', compressed
2458
1856
2459 if data[0:1] == '\0':
1857 if data[0:1] == '\0':
2460 return '', data
1858 return '', data
2461 return 'u', data
1859 return 'u', data
2462
1860
2463 def decompress(self, data):
1861 def decompress(self, data):
2464 """Decompress a revlog chunk.
1862 """Decompress a revlog chunk.
2465
1863
2466 The chunk is expected to begin with a header identifying the
1864 The chunk is expected to begin with a header identifying the
2467 format type so it can be routed to an appropriate decompressor.
1865 format type so it can be routed to an appropriate decompressor.
2468 """
1866 """
2469 if not data:
1867 if not data:
2470 return data
1868 return data
2471
1869
2472 # Revlogs are read much more frequently than they are written and many
1870 # Revlogs are read much more frequently than they are written and many
2473 # chunks only take microseconds to decompress, so performance is
1871 # chunks only take microseconds to decompress, so performance is
2474 # important here.
1872 # important here.
2475 #
1873 #
2476 # We can make a few assumptions about revlogs:
1874 # We can make a few assumptions about revlogs:
2477 #
1875 #
2478 # 1) the majority of chunks will be compressed (as opposed to inline
1876 # 1) the majority of chunks will be compressed (as opposed to inline
2479 # raw data).
1877 # raw data).
2480 # 2) decompressing *any* data will likely by at least 10x slower than
1878 # 2) decompressing *any* data will likely by at least 10x slower than
2481 # returning raw inline data.
1879 # returning raw inline data.
2482 # 3) we want to prioritize common and officially supported compression
1880 # 3) we want to prioritize common and officially supported compression
2483 # engines
1881 # engines
2484 #
1882 #
2485 # It follows that we want to optimize for "decompress compressed data
1883 # It follows that we want to optimize for "decompress compressed data
2486 # when encoded with common and officially supported compression engines"
1884 # when encoded with common and officially supported compression engines"
2487 # case over "raw data" and "data encoded by less common or non-official
1885 # case over "raw data" and "data encoded by less common or non-official
2488 # compression engines." That is why we have the inline lookup first
1886 # compression engines." That is why we have the inline lookup first
2489 # followed by the compengines lookup.
1887 # followed by the compengines lookup.
2490 #
1888 #
2491 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1889 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2492 # compressed chunks. And this matters for changelog and manifest reads.
1890 # compressed chunks. And this matters for changelog and manifest reads.
2493 t = data[0:1]
1891 t = data[0:1]
2494
1892
2495 if t == 'x':
1893 if t == 'x':
2496 try:
1894 try:
2497 return _zlibdecompress(data)
1895 return _zlibdecompress(data)
2498 except zlib.error as e:
1896 except zlib.error as e:
2499 raise RevlogError(_('revlog decompress error: %s') %
1897 raise RevlogError(_('revlog decompress error: %s') %
2500 stringutil.forcebytestr(e))
1898 stringutil.forcebytestr(e))
2501 # '\0' is more common than 'u' so it goes first.
1899 # '\0' is more common than 'u' so it goes first.
2502 elif t == '\0':
1900 elif t == '\0':
2503 return data
1901 return data
2504 elif t == 'u':
1902 elif t == 'u':
2505 return util.buffer(data, 1)
1903 return util.buffer(data, 1)
2506
1904
2507 try:
1905 try:
2508 compressor = self._decompressors[t]
1906 compressor = self._decompressors[t]
2509 except KeyError:
1907 except KeyError:
2510 try:
1908 try:
2511 engine = util.compengines.forrevlogheader(t)
1909 engine = util.compengines.forrevlogheader(t)
2512 compressor = engine.revlogcompressor()
1910 compressor = engine.revlogcompressor()
2513 self._decompressors[t] = compressor
1911 self._decompressors[t] = compressor
2514 except KeyError:
1912 except KeyError:
2515 raise RevlogError(_('unknown compression type %r') % t)
1913 raise RevlogError(_('unknown compression type %r') % t)
2516
1914
2517 return compressor.decompress(data)
1915 return compressor.decompress(data)
2518
1916
2519 def _isgooddeltainfo(self, deltainfo, revinfo):
2520 """Returns True if the given delta is good. Good means that it is within
2521 the disk span, disk size, and chain length bounds that we know to be
2522 performant."""
2523 if deltainfo is None:
2524 return False
2525
2526 # - 'deltainfo.distance' is the distance from the base revision --
2527 # bounding it limits the amount of I/O we need to do.
2528 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
2529 # deltas we need to apply -- bounding it limits the amount of CPU
2530 # we consume.
2531
2532 if self._sparserevlog:
2533 # As sparse-read will be used, we can consider that the distance,
2534 # instead of being the span of the whole chunk,
2535 # is the span of the largest read chunk
2536 base = deltainfo.base
2537
2538 if base != nullrev:
2539 deltachain = self._deltachain(base)[0]
2540 else:
2541 deltachain = []
2542
2543 # search for the first non-snapshot revision
2544 for idx, r in enumerate(deltachain):
2545 if not self.issnapshot(r):
2546 break
2547 deltachain = deltachain[idx:]
2548 chunks = _slicechunk(self, deltachain, deltainfo)
2549 all_span = [_segmentspan(self, revs, deltainfo) for revs in chunks]
2550 distance = max(all_span)
2551 else:
2552 distance = deltainfo.distance
2553
2554 textlen = revinfo.textlen
2555 defaultmax = textlen * 4
2556 maxdist = self._maxdeltachainspan
2557 if not maxdist:
2558 maxdist = distance # ensure the conditional pass
2559 maxdist = max(maxdist, defaultmax)
2560 if self._sparserevlog and maxdist < self._srmingapsize:
2561 # In multiple place, we are ignoring irrelevant data range below a
2562 # certain size. Be also apply this tradeoff here and relax span
2563 # constraint for small enought content.
2564 maxdist = self._srmingapsize
2565
2566 # Bad delta from read span:
2567 #
2568 # If the span of data read is larger than the maximum allowed.
2569 if maxdist < distance:
2570 return False
2571
2572 # Bad delta from new delta size:
2573 #
2574 # If the delta size is larger than the target text, storing the
2575 # delta will be inefficient.
2576 if textlen < deltainfo.deltalen:
2577 return False
2578
2579 # Bad delta from cumulated payload size:
2580 #
2581 # If the sum of delta get larger than K * target text length.
2582 if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
2583 return False
2584
2585 # Bad delta from chain length:
2586 #
2587 # If the number of delta in the chain gets too high.
2588 if self._maxchainlen and self._maxchainlen < deltainfo.chainlen:
2589 return False
2590
2591 # bad delta from intermediate snapshot size limit
2592 #
2593 # If an intermediate snapshot size is higher than the limit. The
2594 # limit exist to prevent endless chain of intermediate delta to be
2595 # created.
2596 if (deltainfo.snapshotdepth is not None and
2597 (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen):
2598 return False
2599
2600 # bad delta if new intermediate snapshot is larger than the previous
2601 # snapshot
2602 if (deltainfo.snapshotdepth
2603 and self.length(deltainfo.base) < deltainfo.deltalen):
2604 return False
2605
2606 return True
2607
2608 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
1917 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2609 cachedelta, ifh, dfh, alwayscache=False,
1918 cachedelta, ifh, dfh, alwayscache=False,
2610 deltacomputer=None):
1919 deltacomputer=None):
2611 """internal function to add revisions to the log
1920 """internal function to add revisions to the log
2612
1921
2613 see addrevision for argument descriptions.
1922 see addrevision for argument descriptions.
2614
1923
2615 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
1924 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2616
1925
2617 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
1926 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2618 be used.
1927 be used.
2619
1928
2620 invariants:
1929 invariants:
2621 - rawtext is optional (can be None); if not set, cachedelta must be set.
1930 - rawtext is optional (can be None); if not set, cachedelta must be set.
2622 if both are set, they must correspond to each other.
1931 if both are set, they must correspond to each other.
2623 """
1932 """
2624 if node == nullid:
1933 if node == nullid:
2625 raise RevlogError(_("%s: attempt to add null revision") %
1934 raise RevlogError(_("%s: attempt to add null revision") %
2626 (self.indexfile))
1935 (self.indexfile))
2627 if node == wdirid or node in wdirfilenodeids:
1936 if node == wdirid or node in wdirfilenodeids:
2628 raise RevlogError(_("%s: attempt to add wdir revision") %
1937 raise RevlogError(_("%s: attempt to add wdir revision") %
2629 (self.indexfile))
1938 (self.indexfile))
2630
1939
2631 if self._inline:
1940 if self._inline:
2632 fh = ifh
1941 fh = ifh
2633 else:
1942 else:
2634 fh = dfh
1943 fh = dfh
2635
1944
2636 btext = [rawtext]
1945 btext = [rawtext]
2637
1946
2638 curr = len(self)
1947 curr = len(self)
2639 prev = curr - 1
1948 prev = curr - 1
2640 offset = self.end(prev)
1949 offset = self.end(prev)
2641 p1r, p2r = self.rev(p1), self.rev(p2)
1950 p1r, p2r = self.rev(p1), self.rev(p2)
2642
1951
2643 # full versions are inserted when the needed deltas
1952 # full versions are inserted when the needed deltas
2644 # become comparable to the uncompressed text
1953 # become comparable to the uncompressed text
2645 if rawtext is None:
1954 if rawtext is None:
2646 # need rawtext size, before changed by flag processors, which is
1955 # need rawtext size, before changed by flag processors, which is
2647 # the non-raw size. use revlog explicitly to avoid filelog's extra
1956 # the non-raw size. use revlog explicitly to avoid filelog's extra
2648 # logic that might remove metadata size.
1957 # logic that might remove metadata size.
2649 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
1958 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2650 cachedelta[1])
1959 cachedelta[1])
2651 else:
1960 else:
2652 textlen = len(rawtext)
1961 textlen = len(rawtext)
2653
1962
2654 if deltacomputer is None:
1963 if deltacomputer is None:
2655 deltacomputer = _deltacomputer(self)
1964 deltacomputer = deltautil.deltacomputer(self)
2656
1965
2657 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
1966 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2658
1967
2659 # no delta for flag processor revision (see "candelta" for why)
1968 # no delta for flag processor revision (see "candelta" for why)
2660 # not calling candelta since only one revision needs test, also to
1969 # not calling candelta since only one revision needs test, also to
2661 # avoid overhead fetching flags again.
1970 # avoid overhead fetching flags again.
2662 if flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
1971 if flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
2663 deltainfo = None
1972 deltainfo = None
2664 else:
1973 else:
2665 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
1974 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2666
1975
2667 if deltainfo is not None:
1976 if deltainfo is not None:
2668 base = deltainfo.base
1977 base = deltainfo.base
2669 chainbase = deltainfo.chainbase
1978 chainbase = deltainfo.chainbase
2670 data = deltainfo.data
1979 data = deltainfo.data
2671 l = deltainfo.deltalen
1980 l = deltainfo.deltalen
2672 else:
1981 else:
2673 rawtext = deltacomputer.buildtext(revinfo, fh)
1982 rawtext = deltacomputer.buildtext(revinfo, fh)
2674 data = self.compress(rawtext)
1983 data = self.compress(rawtext)
2675 l = len(data[1]) + len(data[0])
1984 l = len(data[1]) + len(data[0])
2676 base = chainbase = curr
1985 base = chainbase = curr
2677
1986
2678 e = (offset_type(offset, flags), l, textlen,
1987 e = (offset_type(offset, flags), l, textlen,
2679 base, link, p1r, p2r, node)
1988 base, link, p1r, p2r, node)
2680 self.index.append(e)
1989 self.index.append(e)
2681 self.nodemap[node] = curr
1990 self.nodemap[node] = curr
2682
1991
2683 entry = self._io.packentry(e, self.node, self.version, curr)
1992 entry = self._io.packentry(e, self.node, self.version, curr)
2684 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1993 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
2685
1994
2686 if alwayscache and rawtext is None:
1995 if alwayscache and rawtext is None:
2687 rawtext = deltacomputer.buildtext(revinfo, fh)
1996 rawtext = deltacomputer.buildtext(revinfo, fh)
2688
1997
2689 if type(rawtext) == bytes: # only accept immutable objects
1998 if type(rawtext) == bytes: # only accept immutable objects
2690 self._cache = (node, curr, rawtext)
1999 self._cache = (node, curr, rawtext)
2691 self._chainbasecache[curr] = chainbase
2000 self._chainbasecache[curr] = chainbase
2692 return node
2001 return node
2693
2002
2694 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2003 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2695 # Files opened in a+ mode have inconsistent behavior on various
2004 # Files opened in a+ mode have inconsistent behavior on various
2696 # platforms. Windows requires that a file positioning call be made
2005 # platforms. Windows requires that a file positioning call be made
2697 # when the file handle transitions between reads and writes. See
2006 # when the file handle transitions between reads and writes. See
2698 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2007 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2699 # platforms, Python or the platform itself can be buggy. Some versions
2008 # platforms, Python or the platform itself can be buggy. Some versions
2700 # of Solaris have been observed to not append at the end of the file
2009 # of Solaris have been observed to not append at the end of the file
2701 # if the file was seeked to before the end. See issue4943 for more.
2010 # if the file was seeked to before the end. See issue4943 for more.
2702 #
2011 #
2703 # We work around this issue by inserting a seek() before writing.
2012 # We work around this issue by inserting a seek() before writing.
2704 # Note: This is likely not necessary on Python 3.
2013 # Note: This is likely not necessary on Python 3.
2705 ifh.seek(0, os.SEEK_END)
2014 ifh.seek(0, os.SEEK_END)
2706 if dfh:
2015 if dfh:
2707 dfh.seek(0, os.SEEK_END)
2016 dfh.seek(0, os.SEEK_END)
2708
2017
2709 curr = len(self) - 1
2018 curr = len(self) - 1
2710 if not self._inline:
2019 if not self._inline:
2711 transaction.add(self.datafile, offset)
2020 transaction.add(self.datafile, offset)
2712 transaction.add(self.indexfile, curr * len(entry))
2021 transaction.add(self.indexfile, curr * len(entry))
2713 if data[0]:
2022 if data[0]:
2714 dfh.write(data[0])
2023 dfh.write(data[0])
2715 dfh.write(data[1])
2024 dfh.write(data[1])
2716 ifh.write(entry)
2025 ifh.write(entry)
2717 else:
2026 else:
2718 offset += curr * self._io.size
2027 offset += curr * self._io.size
2719 transaction.add(self.indexfile, offset, curr)
2028 transaction.add(self.indexfile, offset, curr)
2720 ifh.write(entry)
2029 ifh.write(entry)
2721 ifh.write(data[0])
2030 ifh.write(data[0])
2722 ifh.write(data[1])
2031 ifh.write(data[1])
2723 self._enforceinlinesize(transaction, ifh)
2032 self._enforceinlinesize(transaction, ifh)
2724
2033
2725 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2034 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2726 """
2035 """
2727 add a delta group
2036 add a delta group
2728
2037
2729 given a set of deltas, add them to the revision log. the
2038 given a set of deltas, add them to the revision log. the
2730 first delta is against its parent, which should be in our
2039 first delta is against its parent, which should be in our
2731 log, the rest are against the previous delta.
2040 log, the rest are against the previous delta.
2732
2041
2733 If ``addrevisioncb`` is defined, it will be called with arguments of
2042 If ``addrevisioncb`` is defined, it will be called with arguments of
2734 this revlog and the node that was added.
2043 this revlog and the node that was added.
2735 """
2044 """
2736
2045
2737 nodes = []
2046 nodes = []
2738
2047
2739 r = len(self)
2048 r = len(self)
2740 end = 0
2049 end = 0
2741 if r:
2050 if r:
2742 end = self.end(r - 1)
2051 end = self.end(r - 1)
2743 ifh = self._indexfp("a+")
2052 ifh = self._indexfp("a+")
2744 isize = r * self._io.size
2053 isize = r * self._io.size
2745 if self._inline:
2054 if self._inline:
2746 transaction.add(self.indexfile, end + isize, r)
2055 transaction.add(self.indexfile, end + isize, r)
2747 dfh = None
2056 dfh = None
2748 else:
2057 else:
2749 transaction.add(self.indexfile, isize, r)
2058 transaction.add(self.indexfile, isize, r)
2750 transaction.add(self.datafile, end)
2059 transaction.add(self.datafile, end)
2751 dfh = self._datafp("a+")
2060 dfh = self._datafp("a+")
2752 def flush():
2061 def flush():
2753 if dfh:
2062 if dfh:
2754 dfh.flush()
2063 dfh.flush()
2755 ifh.flush()
2064 ifh.flush()
2756 try:
2065 try:
2757 deltacomputer = _deltacomputer(self)
2066 deltacomputer = deltautil.deltacomputer(self)
2758 # loop through our set of deltas
2067 # loop through our set of deltas
2759 for data in deltas:
2068 for data in deltas:
2760 node, p1, p2, linknode, deltabase, delta, flags = data
2069 node, p1, p2, linknode, deltabase, delta, flags = data
2761 link = linkmapper(linknode)
2070 link = linkmapper(linknode)
2762 flags = flags or REVIDX_DEFAULT_FLAGS
2071 flags = flags or REVIDX_DEFAULT_FLAGS
2763
2072
2764 nodes.append(node)
2073 nodes.append(node)
2765
2074
2766 if node in self.nodemap:
2075 if node in self.nodemap:
2767 # this can happen if two branches make the same change
2076 # this can happen if two branches make the same change
2768 continue
2077 continue
2769
2078
2770 for p in (p1, p2):
2079 for p in (p1, p2):
2771 if p not in self.nodemap:
2080 if p not in self.nodemap:
2772 raise LookupError(p, self.indexfile,
2081 raise LookupError(p, self.indexfile,
2773 _('unknown parent'))
2082 _('unknown parent'))
2774
2083
2775 if deltabase not in self.nodemap:
2084 if deltabase not in self.nodemap:
2776 raise LookupError(deltabase, self.indexfile,
2085 raise LookupError(deltabase, self.indexfile,
2777 _('unknown delta base'))
2086 _('unknown delta base'))
2778
2087
2779 baserev = self.rev(deltabase)
2088 baserev = self.rev(deltabase)
2780
2089
2781 if baserev != nullrev and self.iscensored(baserev):
2090 if baserev != nullrev and self.iscensored(baserev):
2782 # if base is censored, delta must be full replacement in a
2091 # if base is censored, delta must be full replacement in a
2783 # single patch operation
2092 # single patch operation
2784 hlen = struct.calcsize(">lll")
2093 hlen = struct.calcsize(">lll")
2785 oldlen = self.rawsize(baserev)
2094 oldlen = self.rawsize(baserev)
2786 newlen = len(delta) - hlen
2095 newlen = len(delta) - hlen
2787 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2096 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2788 raise error.CensoredBaseError(self.indexfile,
2097 raise error.CensoredBaseError(self.indexfile,
2789 self.node(baserev))
2098 self.node(baserev))
2790
2099
2791 if not flags and self._peek_iscensored(baserev, delta, flush):
2100 if not flags and self._peek_iscensored(baserev, delta, flush):
2792 flags |= REVIDX_ISCENSORED
2101 flags |= REVIDX_ISCENSORED
2793
2102
2794 # We assume consumers of addrevisioncb will want to retrieve
2103 # We assume consumers of addrevisioncb will want to retrieve
2795 # the added revision, which will require a call to
2104 # the added revision, which will require a call to
2796 # revision(). revision() will fast path if there is a cache
2105 # revision(). revision() will fast path if there is a cache
2797 # hit. So, we tell _addrevision() to always cache in this case.
2106 # hit. So, we tell _addrevision() to always cache in this case.
2798 # We're only using addgroup() in the context of changegroup
2107 # We're only using addgroup() in the context of changegroup
2799 # generation so the revision data can always be handled as raw
2108 # generation so the revision data can always be handled as raw
2800 # by the flagprocessor.
2109 # by the flagprocessor.
2801 self._addrevision(node, None, transaction, link,
2110 self._addrevision(node, None, transaction, link,
2802 p1, p2, flags, (baserev, delta),
2111 p1, p2, flags, (baserev, delta),
2803 ifh, dfh,
2112 ifh, dfh,
2804 alwayscache=bool(addrevisioncb),
2113 alwayscache=bool(addrevisioncb),
2805 deltacomputer=deltacomputer)
2114 deltacomputer=deltacomputer)
2806
2115
2807 if addrevisioncb:
2116 if addrevisioncb:
2808 addrevisioncb(self, node)
2117 addrevisioncb(self, node)
2809
2118
2810 if not dfh and not self._inline:
2119 if not dfh and not self._inline:
2811 # addrevision switched from inline to conventional
2120 # addrevision switched from inline to conventional
2812 # reopen the index
2121 # reopen the index
2813 ifh.close()
2122 ifh.close()
2814 dfh = self._datafp("a+")
2123 dfh = self._datafp("a+")
2815 ifh = self._indexfp("a+")
2124 ifh = self._indexfp("a+")
2816 finally:
2125 finally:
2817 if dfh:
2126 if dfh:
2818 dfh.close()
2127 dfh.close()
2819 ifh.close()
2128 ifh.close()
2820
2129
2821 return nodes
2130 return nodes
2822
2131
2823 def iscensored(self, rev):
2132 def iscensored(self, rev):
2824 """Check if a file revision is censored."""
2133 """Check if a file revision is censored."""
2825 if not self._censorable:
2134 if not self._censorable:
2826 return False
2135 return False
2827
2136
2828 return self.flags(rev) & REVIDX_ISCENSORED
2137 return self.flags(rev) & REVIDX_ISCENSORED
2829
2138
2830 def _peek_iscensored(self, baserev, delta, flush):
2139 def _peek_iscensored(self, baserev, delta, flush):
2831 """Quickly check if a delta produces a censored revision."""
2140 """Quickly check if a delta produces a censored revision."""
2832 if not self._censorable:
2141 if not self._censorable:
2833 return False
2142 return False
2834
2143
2835 # Fragile heuristic: unless new file meta keys are added alphabetically
2144 # Fragile heuristic: unless new file meta keys are added alphabetically
2836 # preceding "censored", all censored revisions are prefixed by
2145 # preceding "censored", all censored revisions are prefixed by
2837 # "\1\ncensored:". A delta producing such a censored revision must be a
2146 # "\1\ncensored:". A delta producing such a censored revision must be a
2838 # full-replacement delta, so we inspect the first and only patch in the
2147 # full-replacement delta, so we inspect the first and only patch in the
2839 # delta for this prefix.
2148 # delta for this prefix.
2840 hlen = struct.calcsize(">lll")
2149 hlen = struct.calcsize(">lll")
2841 if len(delta) <= hlen:
2150 if len(delta) <= hlen:
2842 return False
2151 return False
2843
2152
2844 oldlen = self.rawsize(baserev)
2153 oldlen = self.rawsize(baserev)
2845 newlen = len(delta) - hlen
2154 newlen = len(delta) - hlen
2846 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2155 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2847 return False
2156 return False
2848
2157
2849 add = "\1\ncensored:"
2158 add = "\1\ncensored:"
2850 addlen = len(add)
2159 addlen = len(add)
2851 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2160 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2852
2161
2853 def getstrippoint(self, minlink):
2162 def getstrippoint(self, minlink):
2854 """find the minimum rev that must be stripped to strip the linkrev
2163 """find the minimum rev that must be stripped to strip the linkrev
2855
2164
2856 Returns a tuple containing the minimum rev and a set of all revs that
2165 Returns a tuple containing the minimum rev and a set of all revs that
2857 have linkrevs that will be broken by this strip.
2166 have linkrevs that will be broken by this strip.
2858 """
2167 """
2859 brokenrevs = set()
2168 brokenrevs = set()
2860 strippoint = len(self)
2169 strippoint = len(self)
2861
2170
2862 heads = {}
2171 heads = {}
2863 futurelargelinkrevs = set()
2172 futurelargelinkrevs = set()
2864 for head in self.headrevs():
2173 for head in self.headrevs():
2865 headlinkrev = self.linkrev(head)
2174 headlinkrev = self.linkrev(head)
2866 heads[head] = headlinkrev
2175 heads[head] = headlinkrev
2867 if headlinkrev >= minlink:
2176 if headlinkrev >= minlink:
2868 futurelargelinkrevs.add(headlinkrev)
2177 futurelargelinkrevs.add(headlinkrev)
2869
2178
2870 # This algorithm involves walking down the rev graph, starting at the
2179 # This algorithm involves walking down the rev graph, starting at the
2871 # heads. Since the revs are topologically sorted according to linkrev,
2180 # heads. Since the revs are topologically sorted according to linkrev,
2872 # once all head linkrevs are below the minlink, we know there are
2181 # once all head linkrevs are below the minlink, we know there are
2873 # no more revs that could have a linkrev greater than minlink.
2182 # no more revs that could have a linkrev greater than minlink.
2874 # So we can stop walking.
2183 # So we can stop walking.
2875 while futurelargelinkrevs:
2184 while futurelargelinkrevs:
2876 strippoint -= 1
2185 strippoint -= 1
2877 linkrev = heads.pop(strippoint)
2186 linkrev = heads.pop(strippoint)
2878
2187
2879 if linkrev < minlink:
2188 if linkrev < minlink:
2880 brokenrevs.add(strippoint)
2189 brokenrevs.add(strippoint)
2881 else:
2190 else:
2882 futurelargelinkrevs.remove(linkrev)
2191 futurelargelinkrevs.remove(linkrev)
2883
2192
2884 for p in self.parentrevs(strippoint):
2193 for p in self.parentrevs(strippoint):
2885 if p != nullrev:
2194 if p != nullrev:
2886 plinkrev = self.linkrev(p)
2195 plinkrev = self.linkrev(p)
2887 heads[p] = plinkrev
2196 heads[p] = plinkrev
2888 if plinkrev >= minlink:
2197 if plinkrev >= minlink:
2889 futurelargelinkrevs.add(plinkrev)
2198 futurelargelinkrevs.add(plinkrev)
2890
2199
2891 return strippoint, brokenrevs
2200 return strippoint, brokenrevs
2892
2201
2893 def strip(self, minlink, transaction):
2202 def strip(self, minlink, transaction):
2894 """truncate the revlog on the first revision with a linkrev >= minlink
2203 """truncate the revlog on the first revision with a linkrev >= minlink
2895
2204
2896 This function is called when we're stripping revision minlink and
2205 This function is called when we're stripping revision minlink and
2897 its descendants from the repository.
2206 its descendants from the repository.
2898
2207
2899 We have to remove all revisions with linkrev >= minlink, because
2208 We have to remove all revisions with linkrev >= minlink, because
2900 the equivalent changelog revisions will be renumbered after the
2209 the equivalent changelog revisions will be renumbered after the
2901 strip.
2210 strip.
2902
2211
2903 So we truncate the revlog on the first of these revisions, and
2212 So we truncate the revlog on the first of these revisions, and
2904 trust that the caller has saved the revisions that shouldn't be
2213 trust that the caller has saved the revisions that shouldn't be
2905 removed and that it'll re-add them after this truncation.
2214 removed and that it'll re-add them after this truncation.
2906 """
2215 """
2907 if len(self) == 0:
2216 if len(self) == 0:
2908 return
2217 return
2909
2218
2910 rev, _ = self.getstrippoint(minlink)
2219 rev, _ = self.getstrippoint(minlink)
2911 if rev == len(self):
2220 if rev == len(self):
2912 return
2221 return
2913
2222
2914 # first truncate the files on disk
2223 # first truncate the files on disk
2915 end = self.start(rev)
2224 end = self.start(rev)
2916 if not self._inline:
2225 if not self._inline:
2917 transaction.add(self.datafile, end)
2226 transaction.add(self.datafile, end)
2918 end = rev * self._io.size
2227 end = rev * self._io.size
2919 else:
2228 else:
2920 end += rev * self._io.size
2229 end += rev * self._io.size
2921
2230
2922 transaction.add(self.indexfile, end)
2231 transaction.add(self.indexfile, end)
2923
2232
2924 # then reset internal state in memory to forget those revisions
2233 # then reset internal state in memory to forget those revisions
2925 self._cache = None
2234 self._cache = None
2926 self._chaininfocache = {}
2235 self._chaininfocache = {}
2927 self._chunkclear()
2236 self._chunkclear()
2928 for x in pycompat.xrange(rev, len(self)):
2237 for x in pycompat.xrange(rev, len(self)):
2929 del self.nodemap[self.node(x)]
2238 del self.nodemap[self.node(x)]
2930
2239
2931 del self.index[rev:-1]
2240 del self.index[rev:-1]
2932 self._nodepos = None
2241 self._nodepos = None
2933
2242
2934 def checksize(self):
2243 def checksize(self):
2935 expected = 0
2244 expected = 0
2936 if len(self):
2245 if len(self):
2937 expected = max(0, self.end(len(self) - 1))
2246 expected = max(0, self.end(len(self) - 1))
2938
2247
2939 try:
2248 try:
2940 with self._datafp() as f:
2249 with self._datafp() as f:
2941 f.seek(0, 2)
2250 f.seek(0, 2)
2942 actual = f.tell()
2251 actual = f.tell()
2943 dd = actual - expected
2252 dd = actual - expected
2944 except IOError as inst:
2253 except IOError as inst:
2945 if inst.errno != errno.ENOENT:
2254 if inst.errno != errno.ENOENT:
2946 raise
2255 raise
2947 dd = 0
2256 dd = 0
2948
2257
2949 try:
2258 try:
2950 f = self.opener(self.indexfile)
2259 f = self.opener(self.indexfile)
2951 f.seek(0, 2)
2260 f.seek(0, 2)
2952 actual = f.tell()
2261 actual = f.tell()
2953 f.close()
2262 f.close()
2954 s = self._io.size
2263 s = self._io.size
2955 i = max(0, actual // s)
2264 i = max(0, actual // s)
2956 di = actual - (i * s)
2265 di = actual - (i * s)
2957 if self._inline:
2266 if self._inline:
2958 databytes = 0
2267 databytes = 0
2959 for r in self:
2268 for r in self:
2960 databytes += max(0, self.length(r))
2269 databytes += max(0, self.length(r))
2961 dd = 0
2270 dd = 0
2962 di = actual - len(self) * s - databytes
2271 di = actual - len(self) * s - databytes
2963 except IOError as inst:
2272 except IOError as inst:
2964 if inst.errno != errno.ENOENT:
2273 if inst.errno != errno.ENOENT:
2965 raise
2274 raise
2966 di = 0
2275 di = 0
2967
2276
2968 return (dd, di)
2277 return (dd, di)
2969
2278
2970 def files(self):
2279 def files(self):
2971 res = [self.indexfile]
2280 res = [self.indexfile]
2972 if not self._inline:
2281 if not self._inline:
2973 res.append(self.datafile)
2282 res.append(self.datafile)
2974 return res
2283 return res
2975
2284
2976 def emitrevisiondeltas(self, requests):
2285 def emitrevisiondeltas(self, requests):
2977 frev = self.rev
2286 frev = self.rev
2978
2287
2979 prevrev = None
2288 prevrev = None
2980 for request in requests:
2289 for request in requests:
2981 node = request.node
2290 node = request.node
2982 rev = frev(node)
2291 rev = frev(node)
2983
2292
2984 if prevrev is None:
2293 if prevrev is None:
2985 prevrev = self.index[rev][5]
2294 prevrev = self.index[rev][5]
2986
2295
2987 # Requesting a full revision.
2296 # Requesting a full revision.
2988 if request.basenode == nullid:
2297 if request.basenode == nullid:
2989 baserev = nullrev
2298 baserev = nullrev
2990 # Requesting an explicit revision.
2299 # Requesting an explicit revision.
2991 elif request.basenode is not None:
2300 elif request.basenode is not None:
2992 baserev = frev(request.basenode)
2301 baserev = frev(request.basenode)
2993 # Allowing us to choose.
2302 # Allowing us to choose.
2994 else:
2303 else:
2995 p1rev, p2rev = self.parentrevs(rev)
2304 p1rev, p2rev = self.parentrevs(rev)
2996 deltaparentrev = self.deltaparent(rev)
2305 deltaparentrev = self.deltaparent(rev)
2997
2306
2998 # Avoid sending full revisions when delta parent is null. Pick
2307 # Avoid sending full revisions when delta parent is null. Pick
2999 # prev in that case. It's tempting to pick p1 in this case, as
2308 # prev in that case. It's tempting to pick p1 in this case, as
3000 # p1 will be smaller in the common case. However, computing a
2309 # p1 will be smaller in the common case. However, computing a
3001 # delta against p1 may require resolving the raw text of p1,
2310 # delta against p1 may require resolving the raw text of p1,
3002 # which could be expensive. The revlog caches should have prev
2311 # which could be expensive. The revlog caches should have prev
3003 # cached, meaning less CPU for delta generation. There is
2312 # cached, meaning less CPU for delta generation. There is
3004 # likely room to add a flag and/or config option to control this
2313 # likely room to add a flag and/or config option to control this
3005 # behavior.
2314 # behavior.
3006 if deltaparentrev == nullrev and self._storedeltachains:
2315 if deltaparentrev == nullrev and self._storedeltachains:
3007 baserev = prevrev
2316 baserev = prevrev
3008
2317
3009 # Revlog is configured to use full snapshot for a reason.
2318 # Revlog is configured to use full snapshot for a reason.
3010 # Stick to full snapshot.
2319 # Stick to full snapshot.
3011 elif deltaparentrev == nullrev:
2320 elif deltaparentrev == nullrev:
3012 baserev = nullrev
2321 baserev = nullrev
3013
2322
3014 # Pick previous when we can't be sure the base is available
2323 # Pick previous when we can't be sure the base is available
3015 # on consumer.
2324 # on consumer.
3016 elif deltaparentrev not in (p1rev, p2rev, prevrev):
2325 elif deltaparentrev not in (p1rev, p2rev, prevrev):
3017 baserev = prevrev
2326 baserev = prevrev
3018 else:
2327 else:
3019 baserev = deltaparentrev
2328 baserev = deltaparentrev
3020
2329
3021 if baserev != nullrev and not self.candelta(baserev, rev):
2330 if baserev != nullrev and not self.candelta(baserev, rev):
3022 baserev = nullrev
2331 baserev = nullrev
3023
2332
3024 revision = None
2333 revision = None
3025 delta = None
2334 delta = None
3026 baserevisionsize = None
2335 baserevisionsize = None
3027
2336
3028 if self.iscensored(baserev) or self.iscensored(rev):
2337 if self.iscensored(baserev) or self.iscensored(rev):
3029 try:
2338 try:
3030 revision = self.revision(node, raw=True)
2339 revision = self.revision(node, raw=True)
3031 except error.CensoredNodeError as e:
2340 except error.CensoredNodeError as e:
3032 revision = e.tombstone
2341 revision = e.tombstone
3033
2342
3034 if baserev != nullrev:
2343 if baserev != nullrev:
3035 baserevisionsize = self.rawsize(baserev)
2344 baserevisionsize = self.rawsize(baserev)
3036
2345
3037 elif baserev == nullrev:
2346 elif baserev == nullrev:
3038 revision = self.revision(node, raw=True)
2347 revision = self.revision(node, raw=True)
3039 else:
2348 else:
3040 delta = self.revdiff(baserev, rev)
2349 delta = self.revdiff(baserev, rev)
3041
2350
3042 extraflags = REVIDX_ELLIPSIS if request.ellipsis else 0
2351 extraflags = REVIDX_ELLIPSIS if request.ellipsis else 0
3043
2352
3044 yield revlogrevisiondelta(
2353 yield revlogrevisiondelta(
3045 node=node,
2354 node=node,
3046 p1node=request.p1node,
2355 p1node=request.p1node,
3047 p2node=request.p2node,
2356 p2node=request.p2node,
3048 linknode=request.linknode,
2357 linknode=request.linknode,
3049 basenode=self.node(baserev),
2358 basenode=self.node(baserev),
3050 flags=self.flags(rev) | extraflags,
2359 flags=self.flags(rev) | extraflags,
3051 baserevisionsize=baserevisionsize,
2360 baserevisionsize=baserevisionsize,
3052 revision=revision,
2361 revision=revision,
3053 delta=delta)
2362 delta=delta)
3054
2363
3055 prevrev = rev
2364 prevrev = rev
3056
2365
3057 DELTAREUSEALWAYS = 'always'
2366 DELTAREUSEALWAYS = 'always'
3058 DELTAREUSESAMEREVS = 'samerevs'
2367 DELTAREUSESAMEREVS = 'samerevs'
3059 DELTAREUSENEVER = 'never'
2368 DELTAREUSENEVER = 'never'
3060
2369
3061 DELTAREUSEFULLADD = 'fulladd'
2370 DELTAREUSEFULLADD = 'fulladd'
3062
2371
3063 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2372 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
3064
2373
3065 def clone(self, tr, destrevlog, addrevisioncb=None,
2374 def clone(self, tr, destrevlog, addrevisioncb=None,
3066 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
2375 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
3067 """Copy this revlog to another, possibly with format changes.
2376 """Copy this revlog to another, possibly with format changes.
3068
2377
3069 The destination revlog will contain the same revisions and nodes.
2378 The destination revlog will contain the same revisions and nodes.
3070 However, it may not be bit-for-bit identical due to e.g. delta encoding
2379 However, it may not be bit-for-bit identical due to e.g. delta encoding
3071 differences.
2380 differences.
3072
2381
3073 The ``deltareuse`` argument control how deltas from the existing revlog
2382 The ``deltareuse`` argument control how deltas from the existing revlog
3074 are preserved in the destination revlog. The argument can have the
2383 are preserved in the destination revlog. The argument can have the
3075 following values:
2384 following values:
3076
2385
3077 DELTAREUSEALWAYS
2386 DELTAREUSEALWAYS
3078 Deltas will always be reused (if possible), even if the destination
2387 Deltas will always be reused (if possible), even if the destination
3079 revlog would not select the same revisions for the delta. This is the
2388 revlog would not select the same revisions for the delta. This is the
3080 fastest mode of operation.
2389 fastest mode of operation.
3081 DELTAREUSESAMEREVS
2390 DELTAREUSESAMEREVS
3082 Deltas will be reused if the destination revlog would pick the same
2391 Deltas will be reused if the destination revlog would pick the same
3083 revisions for the delta. This mode strikes a balance between speed
2392 revisions for the delta. This mode strikes a balance between speed
3084 and optimization.
2393 and optimization.
3085 DELTAREUSENEVER
2394 DELTAREUSENEVER
3086 Deltas will never be reused. This is the slowest mode of execution.
2395 Deltas will never be reused. This is the slowest mode of execution.
3087 This mode can be used to recompute deltas (e.g. if the diff/delta
2396 This mode can be used to recompute deltas (e.g. if the diff/delta
3088 algorithm changes).
2397 algorithm changes).
3089
2398
3090 Delta computation can be slow, so the choice of delta reuse policy can
2399 Delta computation can be slow, so the choice of delta reuse policy can
3091 significantly affect run time.
2400 significantly affect run time.
3092
2401
3093 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2402 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3094 two extremes. Deltas will be reused if they are appropriate. But if the
2403 two extremes. Deltas will be reused if they are appropriate. But if the
3095 delta could choose a better revision, it will do so. This means if you
2404 delta could choose a better revision, it will do so. This means if you
3096 are converting a non-generaldelta revlog to a generaldelta revlog,
2405 are converting a non-generaldelta revlog to a generaldelta revlog,
3097 deltas will be recomputed if the delta's parent isn't a parent of the
2406 deltas will be recomputed if the delta's parent isn't a parent of the
3098 revision.
2407 revision.
3099
2408
3100 In addition to the delta policy, the ``deltabothparents`` argument
2409 In addition to the delta policy, the ``deltabothparents`` argument
3101 controls whether to compute deltas against both parents for merges.
2410 controls whether to compute deltas against both parents for merges.
3102 By default, the current default is used.
2411 By default, the current default is used.
3103 """
2412 """
3104 if deltareuse not in self.DELTAREUSEALL:
2413 if deltareuse not in self.DELTAREUSEALL:
3105 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2414 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
3106
2415
3107 if len(destrevlog):
2416 if len(destrevlog):
3108 raise ValueError(_('destination revlog is not empty'))
2417 raise ValueError(_('destination revlog is not empty'))
3109
2418
3110 if getattr(self, 'filteredrevs', None):
2419 if getattr(self, 'filteredrevs', None):
3111 raise ValueError(_('source revlog has filtered revisions'))
2420 raise ValueError(_('source revlog has filtered revisions'))
3112 if getattr(destrevlog, 'filteredrevs', None):
2421 if getattr(destrevlog, 'filteredrevs', None):
3113 raise ValueError(_('destination revlog has filtered revisions'))
2422 raise ValueError(_('destination revlog has filtered revisions'))
3114
2423
3115 # lazydeltabase controls whether to reuse a cached delta, if possible.
2424 # lazydeltabase controls whether to reuse a cached delta, if possible.
3116 oldlazydeltabase = destrevlog._lazydeltabase
2425 oldlazydeltabase = destrevlog._lazydeltabase
3117 oldamd = destrevlog._deltabothparents
2426 oldamd = destrevlog._deltabothparents
3118
2427
3119 try:
2428 try:
3120 if deltareuse == self.DELTAREUSEALWAYS:
2429 if deltareuse == self.DELTAREUSEALWAYS:
3121 destrevlog._lazydeltabase = True
2430 destrevlog._lazydeltabase = True
3122 elif deltareuse == self.DELTAREUSESAMEREVS:
2431 elif deltareuse == self.DELTAREUSESAMEREVS:
3123 destrevlog._lazydeltabase = False
2432 destrevlog._lazydeltabase = False
3124
2433
3125 destrevlog._deltabothparents = deltabothparents or oldamd
2434 destrevlog._deltabothparents = deltabothparents or oldamd
3126
2435
3127 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2436 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
3128 self.DELTAREUSESAMEREVS)
2437 self.DELTAREUSESAMEREVS)
3129
2438
3130 deltacomputer = _deltacomputer(destrevlog)
2439 deltacomputer = deltautil.deltacomputer(destrevlog)
3131 index = self.index
2440 index = self.index
3132 for rev in self:
2441 for rev in self:
3133 entry = index[rev]
2442 entry = index[rev]
3134
2443
3135 # Some classes override linkrev to take filtered revs into
2444 # Some classes override linkrev to take filtered revs into
3136 # account. Use raw entry from index.
2445 # account. Use raw entry from index.
3137 flags = entry[0] & 0xffff
2446 flags = entry[0] & 0xffff
3138 linkrev = entry[4]
2447 linkrev = entry[4]
3139 p1 = index[entry[5]][7]
2448 p1 = index[entry[5]][7]
3140 p2 = index[entry[6]][7]
2449 p2 = index[entry[6]][7]
3141 node = entry[7]
2450 node = entry[7]
3142
2451
3143 # (Possibly) reuse the delta from the revlog if allowed and
2452 # (Possibly) reuse the delta from the revlog if allowed and
3144 # the revlog chunk is a delta.
2453 # the revlog chunk is a delta.
3145 cachedelta = None
2454 cachedelta = None
3146 rawtext = None
2455 rawtext = None
3147 if populatecachedelta:
2456 if populatecachedelta:
3148 dp = self.deltaparent(rev)
2457 dp = self.deltaparent(rev)
3149 if dp != nullrev:
2458 if dp != nullrev:
3150 cachedelta = (dp, bytes(self._chunk(rev)))
2459 cachedelta = (dp, bytes(self._chunk(rev)))
3151
2460
3152 if not cachedelta:
2461 if not cachedelta:
3153 rawtext = self.revision(rev, raw=True)
2462 rawtext = self.revision(rev, raw=True)
3154
2463
3155
2464
3156 if deltareuse == self.DELTAREUSEFULLADD:
2465 if deltareuse == self.DELTAREUSEFULLADD:
3157 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2466 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
3158 cachedelta=cachedelta,
2467 cachedelta=cachedelta,
3159 node=node, flags=flags,
2468 node=node, flags=flags,
3160 deltacomputer=deltacomputer)
2469 deltacomputer=deltacomputer)
3161 else:
2470 else:
3162 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2471 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
3163 checkambig=False)
2472 checkambig=False)
3164 dfh = None
2473 dfh = None
3165 if not destrevlog._inline:
2474 if not destrevlog._inline:
3166 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2475 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
3167 try:
2476 try:
3168 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2477 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
3169 p2, flags, cachedelta, ifh, dfh,
2478 p2, flags, cachedelta, ifh, dfh,
3170 deltacomputer=deltacomputer)
2479 deltacomputer=deltacomputer)
3171 finally:
2480 finally:
3172 if dfh:
2481 if dfh:
3173 dfh.close()
2482 dfh.close()
3174 ifh.close()
2483 ifh.close()
3175
2484
3176 if addrevisioncb:
2485 if addrevisioncb:
3177 addrevisioncb(self, rev, node)
2486 addrevisioncb(self, rev, node)
3178 finally:
2487 finally:
3179 destrevlog._lazydeltabase = oldlazydeltabase
2488 destrevlog._lazydeltabase = oldlazydeltabase
3180 destrevlog._deltabothparents = oldamd
2489 destrevlog._deltabothparents = oldamd
@@ -1,46 +1,43 b''
1 # revlogdeltas.py - constant used for revlog logic
1 # revlogdeltas.py - constant used for revlog logic
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2018 Octobus <contact@octobus.net>
4 # Copyright 2018 Octobus <contact@octobus.net>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8 """Helper class to compute deltas stored inside revlogs"""
8 """Helper class to compute deltas stored inside revlogs"""
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 from .. import (
12 from .. import (
13 util,
13 util,
14 )
14 )
15
15
16 # revlog header flags
16 # revlog header flags
17 REVLOGV0 = 0
17 REVLOGV0 = 0
18 REVLOGV1 = 1
18 REVLOGV1 = 1
19 # Dummy value until file format is finalized.
19 # Dummy value until file format is finalized.
20 # Reminder: change the bounds check in revlog.__init__ when this is changed.
20 # Reminder: change the bounds check in revlog.__init__ when this is changed.
21 REVLOGV2 = 0xDEAD
21 REVLOGV2 = 0xDEAD
22 FLAG_INLINE_DATA = (1 << 16)
22 FLAG_INLINE_DATA = (1 << 16)
23 FLAG_GENERALDELTA = (1 << 17)
23 FLAG_GENERALDELTA = (1 << 17)
24 REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
24 REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
25 REVLOG_DEFAULT_FORMAT = REVLOGV1
25 REVLOG_DEFAULT_FORMAT = REVLOGV1
26 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
26 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
27 REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
27 REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
28 REVLOGV2_FLAGS = REVLOGV1_FLAGS
28 REVLOGV2_FLAGS = REVLOGV1_FLAGS
29
29
30 # revlog index flags
30 # revlog index flags
31 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
31 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
32 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
32 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
33 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
33 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
34 REVIDX_DEFAULT_FLAGS = 0
34 REVIDX_DEFAULT_FLAGS = 0
35 # stable order in which flags need to be processed and their processors applied
35 # stable order in which flags need to be processed and their processors applied
36 REVIDX_FLAGS_ORDER = [
36 REVIDX_FLAGS_ORDER = [
37 REVIDX_ISCENSORED,
37 REVIDX_ISCENSORED,
38 REVIDX_ELLIPSIS,
38 REVIDX_ELLIPSIS,
39 REVIDX_EXTSTORED,
39 REVIDX_EXTSTORED,
40 ]
40 ]
41 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
41 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
42 # bitmark for flags that could cause rawdata content change
42 # bitmark for flags that could cause rawdata content change
43 REVIDX_RAWTEXT_CHANGING_FLAGS = REVIDX_ISCENSORED | REVIDX_EXTSTORED
43 REVIDX_RAWTEXT_CHANGING_FLAGS = REVIDX_ISCENSORED | REVIDX_EXTSTORED
44
45 # maximum <delta-chain-data>/<revision-text-length> ratio
46 LIMIT_DELTA2TEXT = 2
This diff has been collapsed as it changes many lines, (2854 lines changed) Show them Hide them
@@ -1,3180 +1,734 b''
1 # revlog.py - storage back-end for mercurial
1 # revlogdeltas.py - Logic around delta computation for revlog
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2018 Octobus <contact@octobus.net>
4 #
5 #
5 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
7
8 """Helper class to compute deltas stored inside revlogs"""
8 """Storage back-end for Mercurial.
9
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
12 """
13
9
14 from __future__ import absolute_import
10 from __future__ import absolute_import
15
11
16 import collections
17 import contextlib
18 import errno
19 import hashlib
20 import heapq
12 import heapq
21 import os
22 import re
23 import struct
13 import struct
24 import zlib
25
14
26 # import stuff from node for others to import from revlog
15 # import stuff from node for others to import from revlog
27 from .node import (
16 from ..node import (
28 bin,
29 hex,
30 nullhex,
31 nullid,
32 nullrev,
17 nullrev,
33 wdirfilenodeids,
34 wdirhex,
35 wdirid,
36 wdirrev,
37 )
18 )
38 from .i18n import _
19 from ..i18n import _
39 from .revlogutils.constants import (
20
40 FLAG_GENERALDELTA,
21 from .constants import (
41 FLAG_INLINE_DATA,
42 LIMIT_DELTA2TEXT,
43 REVIDX_DEFAULT_FLAGS,
44 REVIDX_ELLIPSIS,
45 REVIDX_EXTSTORED,
46 REVIDX_FLAGS_ORDER,
47 REVIDX_ISCENSORED,
22 REVIDX_ISCENSORED,
48 REVIDX_KNOWN_FLAGS,
49 REVIDX_RAWTEXT_CHANGING_FLAGS,
23 REVIDX_RAWTEXT_CHANGING_FLAGS,
50 REVLOGV0,
51 REVLOGV1,
52 REVLOGV1_FLAGS,
53 REVLOGV2,
54 REVLOGV2_FLAGS,
55 REVLOG_DEFAULT_FLAGS,
56 REVLOG_DEFAULT_FORMAT,
57 REVLOG_DEFAULT_VERSION,
58 )
24 )
59 from .thirdparty import (
25
26 from ..thirdparty import (
60 attr,
27 attr,
61 )
28 )
62 from . import (
29
63 ancestor,
30 from .. import (
64 error,
31 error,
65 mdiff,
32 mdiff,
66 policy,
67 pycompat,
68 repository,
69 templatefilters,
70 util,
71 )
33 )
72 from .utils import (
73 interfaceutil,
74 stringutil,
75 )
76
77 # blanked usage of all the name to prevent pyflakes constraints
78 # We need these name available in the module for extensions.
79 REVLOGV0
80 REVLOGV1
81 REVLOGV2
82 FLAG_INLINE_DATA
83 FLAG_GENERALDELTA
84 REVLOG_DEFAULT_FLAGS
85 REVLOG_DEFAULT_FORMAT
86 REVLOG_DEFAULT_VERSION
87 REVLOGV1_FLAGS
88 REVLOGV2_FLAGS
89 REVIDX_ISCENSORED
90 REVIDX_ELLIPSIS
91 REVIDX_EXTSTORED
92 REVIDX_DEFAULT_FLAGS
93 REVIDX_FLAGS_ORDER
94 REVIDX_KNOWN_FLAGS
95 REVIDX_RAWTEXT_CHANGING_FLAGS
96
97 parsers = policy.importmod(r'parsers')
98
99 # Aliased for performance.
100 _zlibdecompress = zlib.decompress
101
102 # max size of revlog with inline data
103 _maxinline = 131072
104 _chunksize = 1048576
105
34
106 RevlogError = error.RevlogError
35 RevlogError = error.RevlogError
107 LookupError = error.LookupError
108 AmbiguousPrefixLookupError = error.AmbiguousPrefixLookupError
109 CensoredNodeError = error.CensoredNodeError
36 CensoredNodeError = error.CensoredNodeError
110 ProgrammingError = error.ProgrammingError
111
112 # Store flag processors (cf. 'addflagprocessor()' to register)
113 _flagprocessors = {
114 REVIDX_ISCENSORED: None,
115 }
116
117 _mdre = re.compile('\1\n')
118 def parsemeta(text):
119 """return (metadatadict, metadatasize)"""
120 # text can be buffer, so we can't use .startswith or .index
121 if text[:2] != '\1\n':
122 return None, None
123 s = _mdre.search(text, 2).start()
124 mtext = text[2:s]
125 meta = {}
126 for l in mtext.splitlines():
127 k, v = l.split(": ", 1)
128 meta[k] = v
129 return meta, (s + 2)
130
131 def packmeta(meta, text):
132 keys = sorted(meta)
133 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
134 return "\1\n%s\1\n%s" % (metatext, text)
135
136 def _censoredtext(text):
137 m, offs = parsemeta(text)
138 return m and "censored" in m
139
140 def addflagprocessor(flag, processor):
141 """Register a flag processor on a revision data flag.
142
37
143 Invariant:
38 # maximum <delta-chain-data>/<revision-text-length> ratio
144 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
39 LIMIT_DELTA2TEXT = 2
145 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
146 - Only one flag processor can be registered on a specific flag.
147 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
148 following signatures:
149 - (read) f(self, rawtext) -> text, bool
150 - (write) f(self, text) -> rawtext, bool
151 - (raw) f(self, rawtext) -> bool
152 "text" is presented to the user. "rawtext" is stored in revlog data, not
153 directly visible to the user.
154 The boolean returned by these transforms is used to determine whether
155 the returned text can be used for hash integrity checking. For example,
156 if "write" returns False, then "text" is used to generate hash. If
157 "write" returns True, that basically means "rawtext" returned by "write"
158 should be used to generate hash. Usually, "write" and "read" return
159 different booleans. And "raw" returns a same boolean as "write".
160
161 Note: The 'raw' transform is used for changegroup generation and in some
162 debug commands. In this case the transform only indicates whether the
163 contents can be used for hash integrity checks.
164 """
165 if not flag & REVIDX_KNOWN_FLAGS:
166 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
167 raise ProgrammingError(msg)
168 if flag not in REVIDX_FLAGS_ORDER:
169 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
170 raise ProgrammingError(msg)
171 if flag in _flagprocessors:
172 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
173 raise error.Abort(msg)
174 _flagprocessors[flag] = processor
175
176 def getoffset(q):
177 return int(q >> 16)
178
179 def gettype(q):
180 return int(q & 0xFFFF)
181
182 def offset_type(offset, type):
183 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
184 raise ValueError('unknown revlog index flags')
185 return int(int(offset) << 16 | type)
186
187 _nullhash = hashlib.sha1(nullid)
188
189 def hash(text, p1, p2):
190 """generate a hash from the given text and its parent hashes
191
192 This hash combines both the current file contents and its history
193 in a manner that makes it easy to distinguish nodes with the same
194 content in the revision graph.
195 """
196 # As of now, if one of the parent node is null, p2 is null
197 if p2 == nullid:
198 # deep copy of a hash is faster than creating one
199 s = _nullhash.copy()
200 s.update(p1)
201 else:
202 # none of the parent nodes are nullid
203 if p1 < p2:
204 a = p1
205 b = p2
206 else:
207 a = p2
208 b = p1
209 s = hashlib.sha1(a)
210 s.update(b)
211 s.update(text)
212 return s.digest()
213
40
214 class _testrevlog(object):
41 class _testrevlog(object):
215 """minimalist fake revlog to use in doctests"""
42 """minimalist fake revlog to use in doctests"""
216
43
217 def __init__(self, data, density=0.5, mingap=0):
44 def __init__(self, data, density=0.5, mingap=0):
218 """data is an list of revision payload boundaries"""
45 """data is an list of revision payload boundaries"""
219 self._data = data
46 self._data = data
220 self._srdensitythreshold = density
47 self._srdensitythreshold = density
221 self._srmingapsize = mingap
48 self._srmingapsize = mingap
222
49
223 def start(self, rev):
50 def start(self, rev):
224 if rev == 0:
51 if rev == 0:
225 return 0
52 return 0
226 return self._data[rev - 1]
53 return self._data[rev - 1]
227
54
228 def end(self, rev):
55 def end(self, rev):
229 return self._data[rev]
56 return self._data[rev]
230
57
231 def length(self, rev):
58 def length(self, rev):
232 return self.end(rev) - self.start(rev)
59 return self.end(rev) - self.start(rev)
233
60
234 def __len__(self):
61 def __len__(self):
235 return len(self._data)
62 return len(self._data)
236
63
237 def _trimchunk(revlog, revs, startidx, endidx=None):
64 def slicechunk(revlog, revs, deltainfo=None, targetsize=None):
238 """returns revs[startidx:endidx] without empty trailing revs
239
240 Doctest Setup
241 >>> revlog = _testrevlog([
242 ... 5, #0
243 ... 10, #1
244 ... 12, #2
245 ... 12, #3 (empty)
246 ... 17, #4
247 ... 21, #5
248 ... 21, #6 (empty)
249 ... ])
250
251 Contiguous cases:
252 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
253 [0, 1, 2, 3, 4, 5]
254 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
255 [0, 1, 2, 3, 4]
256 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
257 [0, 1, 2]
258 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
259 [2]
260 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
261 [3, 4, 5]
262 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
263 [3, 4]
264
265 Discontiguous cases:
266 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
267 [1, 3, 5]
268 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
269 [1]
270 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
271 [3, 5]
272 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
273 [3, 5]
274 """
275 length = revlog.length
276
277 if endidx is None:
278 endidx = len(revs)
279
280 # If we have a non-emtpy delta candidate, there are nothing to trim
281 if revs[endidx - 1] < len(revlog):
282 # Trim empty revs at the end, except the very first revision of a chain
283 while (endidx > 1
284 and endidx > startidx
285 and length(revs[endidx - 1]) == 0):
286 endidx -= 1
287
288 return revs[startidx:endidx]
289
290 def _segmentspan(revlog, revs, deltainfo=None):
291 """Get the byte span of a segment of revisions
292
293 revs is a sorted array of revision numbers
294
295 >>> revlog = _testrevlog([
296 ... 5, #0
297 ... 10, #1
298 ... 12, #2
299 ... 12, #3 (empty)
300 ... 17, #4
301 ... ])
302
303 >>> _segmentspan(revlog, [0, 1, 2, 3, 4])
304 17
305 >>> _segmentspan(revlog, [0, 4])
306 17
307 >>> _segmentspan(revlog, [3, 4])
308 5
309 >>> _segmentspan(revlog, [1, 2, 3,])
310 7
311 >>> _segmentspan(revlog, [1, 3])
312 7
313 """
314 if not revs:
315 return 0
316 if deltainfo is not None and len(revlog) <= revs[-1]:
317 if len(revs) == 1:
318 return deltainfo.deltalen
319 offset = revlog.end(len(revlog) - 1)
320 end = deltainfo.deltalen + offset
321 else:
322 end = revlog.end(revs[-1])
323 return end - revlog.start(revs[0])
324
325 def _slicechunk(revlog, revs, deltainfo=None, targetsize=None):
326 """slice revs to reduce the amount of unrelated data to be read from disk.
65 """slice revs to reduce the amount of unrelated data to be read from disk.
327
66
328 ``revs`` is sliced into groups that should be read in one time.
67 ``revs`` is sliced into groups that should be read in one time.
329 Assume that revs are sorted.
68 Assume that revs are sorted.
330
69
331 The initial chunk is sliced until the overall density (payload/chunks-span
70 The initial chunk is sliced until the overall density (payload/chunks-span
332 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
71 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
333 `revlog._srmingapsize` is skipped.
72 `revlog._srmingapsize` is skipped.
334
73
335 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
74 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
336 For consistency with other slicing choice, this limit won't go lower than
75 For consistency with other slicing choice, this limit won't go lower than
337 `revlog._srmingapsize`.
76 `revlog._srmingapsize`.
338
77
339 If individual revisions chunk are larger than this limit, they will still
78 If individual revisions chunk are larger than this limit, they will still
340 be raised individually.
79 be raised individually.
341
80
342 >>> revlog = _testrevlog([
81 >>> revlog = _testrevlog([
343 ... 5, #00 (5)
82 ... 5, #00 (5)
344 ... 10, #01 (5)
83 ... 10, #01 (5)
345 ... 12, #02 (2)
84 ... 12, #02 (2)
346 ... 12, #03 (empty)
85 ... 12, #03 (empty)
347 ... 27, #04 (15)
86 ... 27, #04 (15)
348 ... 31, #05 (4)
87 ... 31, #05 (4)
349 ... 31, #06 (empty)
88 ... 31, #06 (empty)
350 ... 42, #07 (11)
89 ... 42, #07 (11)
351 ... 47, #08 (5)
90 ... 47, #08 (5)
352 ... 47, #09 (empty)
91 ... 47, #09 (empty)
353 ... 48, #10 (1)
92 ... 48, #10 (1)
354 ... 51, #11 (3)
93 ... 51, #11 (3)
355 ... 74, #12 (23)
94 ... 74, #12 (23)
356 ... 85, #13 (11)
95 ... 85, #13 (11)
357 ... 86, #14 (1)
96 ... 86, #14 (1)
358 ... 91, #15 (5)
97 ... 91, #15 (5)
359 ... ])
98 ... ])
360
99
361 >>> list(_slicechunk(revlog, list(range(16))))
100 >>> list(slicechunk(revlog, list(range(16))))
362 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
101 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
363 >>> list(_slicechunk(revlog, [0, 15]))
102 >>> list(slicechunk(revlog, [0, 15]))
364 [[0], [15]]
103 [[0], [15]]
365 >>> list(_slicechunk(revlog, [0, 11, 15]))
104 >>> list(slicechunk(revlog, [0, 11, 15]))
366 [[0], [11], [15]]
105 [[0], [11], [15]]
367 >>> list(_slicechunk(revlog, [0, 11, 13, 15]))
106 >>> list(slicechunk(revlog, [0, 11, 13, 15]))
368 [[0], [11, 13, 15]]
107 [[0], [11, 13, 15]]
369 >>> list(_slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
108 >>> list(slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
370 [[1, 2], [5, 8, 10, 11], [14]]
109 [[1, 2], [5, 8, 10, 11], [14]]
371
110
372 Slicing with a maximum chunk size
111 Slicing with a maximum chunk size
373 >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
112 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
374 [[0], [11], [13], [15]]
113 [[0], [11], [13], [15]]
375 >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
114 >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
376 [[0], [11], [13, 15]]
115 [[0], [11], [13, 15]]
377 """
116 """
378 if targetsize is not None:
117 if targetsize is not None:
379 targetsize = max(targetsize, revlog._srmingapsize)
118 targetsize = max(targetsize, revlog._srmingapsize)
380 # targetsize should not be specified when evaluating delta candidates:
119 # targetsize should not be specified when evaluating delta candidates:
381 # * targetsize is used to ensure we stay within specification when reading,
120 # * targetsize is used to ensure we stay within specification when reading,
382 # * deltainfo is used to pick are good delta chain when writing.
121 # * deltainfo is used to pick are good delta chain when writing.
383 if not (deltainfo is None or targetsize is None):
122 if not (deltainfo is None or targetsize is None):
384 msg = 'cannot use `targetsize` with a `deltainfo`'
123 msg = 'cannot use `targetsize` with a `deltainfo`'
385 raise error.ProgrammingError(msg)
124 raise error.ProgrammingError(msg)
386 for chunk in _slicechunktodensity(revlog, revs,
125 for chunk in _slicechunktodensity(revlog, revs,
387 deltainfo,
126 deltainfo,
388 revlog._srdensitythreshold,
127 revlog._srdensitythreshold,
389 revlog._srmingapsize):
128 revlog._srmingapsize):
390 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
129 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
391 yield subchunk
130 yield subchunk
392
131
393 def _slicechunktosize(revlog, revs, targetsize=None):
132 def _slicechunktosize(revlog, revs, targetsize=None):
394 """slice revs to match the target size
133 """slice revs to match the target size
395
134
396 This is intended to be used on chunk that density slicing selected by that
135 This is intended to be used on chunk that density slicing selected by that
397 are still too large compared to the read garantee of revlog. This might
136 are still too large compared to the read garantee of revlog. This might
398 happens when "minimal gap size" interrupted the slicing or when chain are
137 happens when "minimal gap size" interrupted the slicing or when chain are
399 built in a way that create large blocks next to each other.
138 built in a way that create large blocks next to each other.
400
139
401 >>> revlog = _testrevlog([
140 >>> revlog = _testrevlog([
402 ... 3, #0 (3)
141 ... 3, #0 (3)
403 ... 5, #1 (2)
142 ... 5, #1 (2)
404 ... 6, #2 (1)
143 ... 6, #2 (1)
405 ... 8, #3 (2)
144 ... 8, #3 (2)
406 ... 8, #4 (empty)
145 ... 8, #4 (empty)
407 ... 11, #5 (3)
146 ... 11, #5 (3)
408 ... 12, #6 (1)
147 ... 12, #6 (1)
409 ... 13, #7 (1)
148 ... 13, #7 (1)
410 ... 14, #8 (1)
149 ... 14, #8 (1)
411 ... ])
150 ... ])
412
151
413 Cases where chunk is already small enough
152 Cases where chunk is already small enough
414 >>> list(_slicechunktosize(revlog, [0], 3))
153 >>> list(_slicechunktosize(revlog, [0], 3))
415 [[0]]
154 [[0]]
416 >>> list(_slicechunktosize(revlog, [6, 7], 3))
155 >>> list(_slicechunktosize(revlog, [6, 7], 3))
417 [[6, 7]]
156 [[6, 7]]
418 >>> list(_slicechunktosize(revlog, [0], None))
157 >>> list(_slicechunktosize(revlog, [0], None))
419 [[0]]
158 [[0]]
420 >>> list(_slicechunktosize(revlog, [6, 7], None))
159 >>> list(_slicechunktosize(revlog, [6, 7], None))
421 [[6, 7]]
160 [[6, 7]]
422
161
423 cases where we need actual slicing
162 cases where we need actual slicing
424 >>> list(_slicechunktosize(revlog, [0, 1], 3))
163 >>> list(_slicechunktosize(revlog, [0, 1], 3))
425 [[0], [1]]
164 [[0], [1]]
426 >>> list(_slicechunktosize(revlog, [1, 3], 3))
165 >>> list(_slicechunktosize(revlog, [1, 3], 3))
427 [[1], [3]]
166 [[1], [3]]
428 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
167 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
429 [[1, 2], [3]]
168 [[1, 2], [3]]
430 >>> list(_slicechunktosize(revlog, [3, 5], 3))
169 >>> list(_slicechunktosize(revlog, [3, 5], 3))
431 [[3], [5]]
170 [[3], [5]]
432 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
171 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
433 [[3], [5]]
172 [[3], [5]]
434 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
173 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
435 [[5], [6, 7, 8]]
174 [[5], [6, 7, 8]]
436 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
175 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
437 [[0], [1, 2], [3], [5], [6, 7, 8]]
176 [[0], [1, 2], [3], [5], [6, 7, 8]]
438
177
439 Case with too large individual chunk (must return valid chunk)
178 Case with too large individual chunk (must return valid chunk)
440 >>> list(_slicechunktosize(revlog, [0, 1], 2))
179 >>> list(_slicechunktosize(revlog, [0, 1], 2))
441 [[0], [1]]
180 [[0], [1]]
442 >>> list(_slicechunktosize(revlog, [1, 3], 1))
181 >>> list(_slicechunktosize(revlog, [1, 3], 1))
443 [[1], [3]]
182 [[1], [3]]
444 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
183 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
445 [[3], [5]]
184 [[3], [5]]
446 """
185 """
447 assert targetsize is None or 0 <= targetsize
186 assert targetsize is None or 0 <= targetsize
448 if targetsize is None or _segmentspan(revlog, revs) <= targetsize:
187 if targetsize is None or segmentspan(revlog, revs) <= targetsize:
449 yield revs
188 yield revs
450 return
189 return
451
190
452 startrevidx = 0
191 startrevidx = 0
453 startdata = revlog.start(revs[0])
192 startdata = revlog.start(revs[0])
454 endrevidx = 0
193 endrevidx = 0
455 iterrevs = enumerate(revs)
194 iterrevs = enumerate(revs)
456 next(iterrevs) # skip first rev.
195 next(iterrevs) # skip first rev.
457 for idx, r in iterrevs:
196 for idx, r in iterrevs:
458 span = revlog.end(r) - startdata
197 span = revlog.end(r) - startdata
459 if span <= targetsize:
198 if span <= targetsize:
460 endrevidx = idx
199 endrevidx = idx
461 else:
200 else:
462 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
201 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
463 if chunk:
202 if chunk:
464 yield chunk
203 yield chunk
465 startrevidx = idx
204 startrevidx = idx
466 startdata = revlog.start(r)
205 startdata = revlog.start(r)
467 endrevidx = idx
206 endrevidx = idx
468 yield _trimchunk(revlog, revs, startrevidx)
207 yield _trimchunk(revlog, revs, startrevidx)
469
208
470 def _slicechunktodensity(revlog, revs, deltainfo=None, targetdensity=0.5,
209 def _slicechunktodensity(revlog, revs, deltainfo=None, targetdensity=0.5,
471 mingapsize=0):
210 mingapsize=0):
472 """slice revs to reduce the amount of unrelated data to be read from disk.
211 """slice revs to reduce the amount of unrelated data to be read from disk.
473
212
474 ``revs`` is sliced into groups that should be read in one time.
213 ``revs`` is sliced into groups that should be read in one time.
475 Assume that revs are sorted.
214 Assume that revs are sorted.
476
215
477 ``deltainfo`` is a _deltainfo instance of a revision that we would append
216 ``deltainfo`` is a _deltainfo instance of a revision that we would append
478 to the top of the revlog.
217 to the top of the revlog.
479
218
480 The initial chunk is sliced until the overall density (payload/chunks-span
219 The initial chunk is sliced until the overall density (payload/chunks-span
481 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
220 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
482 skipped.
221 skipped.
483
222
484 >>> revlog = _testrevlog([
223 >>> revlog = _testrevlog([
485 ... 5, #00 (5)
224 ... 5, #00 (5)
486 ... 10, #01 (5)
225 ... 10, #01 (5)
487 ... 12, #02 (2)
226 ... 12, #02 (2)
488 ... 12, #03 (empty)
227 ... 12, #03 (empty)
489 ... 27, #04 (15)
228 ... 27, #04 (15)
490 ... 31, #05 (4)
229 ... 31, #05 (4)
491 ... 31, #06 (empty)
230 ... 31, #06 (empty)
492 ... 42, #07 (11)
231 ... 42, #07 (11)
493 ... 47, #08 (5)
232 ... 47, #08 (5)
494 ... 47, #09 (empty)
233 ... 47, #09 (empty)
495 ... 48, #10 (1)
234 ... 48, #10 (1)
496 ... 51, #11 (3)
235 ... 51, #11 (3)
497 ... 74, #12 (23)
236 ... 74, #12 (23)
498 ... 85, #13 (11)
237 ... 85, #13 (11)
499 ... 86, #14 (1)
238 ... 86, #14 (1)
500 ... 91, #15 (5)
239 ... 91, #15 (5)
501 ... ])
240 ... ])
502
241
503 >>> list(_slicechunktodensity(revlog, list(range(16))))
242 >>> list(_slicechunktodensity(revlog, list(range(16))))
504 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
243 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
505 >>> list(_slicechunktodensity(revlog, [0, 15]))
244 >>> list(_slicechunktodensity(revlog, [0, 15]))
506 [[0], [15]]
245 [[0], [15]]
507 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
246 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
508 [[0], [11], [15]]
247 [[0], [11], [15]]
509 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
248 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
510 [[0], [11, 13, 15]]
249 [[0], [11, 13, 15]]
511 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
250 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
512 [[1, 2], [5, 8, 10, 11], [14]]
251 [[1, 2], [5, 8, 10, 11], [14]]
513 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
252 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
514 ... mingapsize=20))
253 ... mingapsize=20))
515 [[1, 2, 3, 5, 8, 10, 11], [14]]
254 [[1, 2, 3, 5, 8, 10, 11], [14]]
516 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
255 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
517 ... targetdensity=0.95))
256 ... targetdensity=0.95))
518 [[1, 2], [5], [8, 10, 11], [14]]
257 [[1, 2], [5], [8, 10, 11], [14]]
519 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
258 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
520 ... targetdensity=0.95, mingapsize=12))
259 ... targetdensity=0.95, mingapsize=12))
521 [[1, 2], [5, 8, 10, 11], [14]]
260 [[1, 2], [5, 8, 10, 11], [14]]
522 """
261 """
523 start = revlog.start
262 start = revlog.start
524 length = revlog.length
263 length = revlog.length
525
264
526 if len(revs) <= 1:
265 if len(revs) <= 1:
527 yield revs
266 yield revs
528 return
267 return
529
268
530 nextrev = len(revlog)
269 nextrev = len(revlog)
531 nextoffset = revlog.end(nextrev - 1)
270 nextoffset = revlog.end(nextrev - 1)
532
271
533 if deltainfo is None:
272 if deltainfo is None:
534 deltachainspan = _segmentspan(revlog, revs)
273 deltachainspan = segmentspan(revlog, revs)
535 chainpayload = sum(length(r) for r in revs)
274 chainpayload = sum(length(r) for r in revs)
536 else:
275 else:
537 deltachainspan = deltainfo.distance
276 deltachainspan = deltainfo.distance
538 chainpayload = deltainfo.compresseddeltalen
277 chainpayload = deltainfo.compresseddeltalen
539
278
540 if deltachainspan < mingapsize:
279 if deltachainspan < mingapsize:
541 yield revs
280 yield revs
542 return
281 return
543
282
544 readdata = deltachainspan
283 readdata = deltachainspan
545
284
546 if deltachainspan:
285 if deltachainspan:
547 density = chainpayload / float(deltachainspan)
286 density = chainpayload / float(deltachainspan)
548 else:
287 else:
549 density = 1.0
288 density = 1.0
550
289
551 if density >= targetdensity:
290 if density >= targetdensity:
552 yield revs
291 yield revs
553 return
292 return
554
293
555 if deltainfo is not None and deltainfo.deltalen:
294 if deltainfo is not None and deltainfo.deltalen:
556 revs = list(revs)
295 revs = list(revs)
557 revs.append(nextrev)
296 revs.append(nextrev)
558
297
559 # Store the gaps in a heap to have them sorted by decreasing size
298 # Store the gaps in a heap to have them sorted by decreasing size
560 gapsheap = []
299 gapsheap = []
561 heapq.heapify(gapsheap)
300 heapq.heapify(gapsheap)
562 prevend = None
301 prevend = None
563 for i, rev in enumerate(revs):
302 for i, rev in enumerate(revs):
564 if rev < nextrev:
303 if rev < nextrev:
565 revstart = start(rev)
304 revstart = start(rev)
566 revlen = length(rev)
305 revlen = length(rev)
567 else:
306 else:
568 revstart = nextoffset
307 revstart = nextoffset
569 revlen = deltainfo.deltalen
308 revlen = deltainfo.deltalen
570
309
571 # Skip empty revisions to form larger holes
310 # Skip empty revisions to form larger holes
572 if revlen == 0:
311 if revlen == 0:
573 continue
312 continue
574
313
575 if prevend is not None:
314 if prevend is not None:
576 gapsize = revstart - prevend
315 gapsize = revstart - prevend
577 # only consider holes that are large enough
316 # only consider holes that are large enough
578 if gapsize > mingapsize:
317 if gapsize > mingapsize:
579 heapq.heappush(gapsheap, (-gapsize, i))
318 heapq.heappush(gapsheap, (-gapsize, i))
580
319
581 prevend = revstart + revlen
320 prevend = revstart + revlen
582
321
583 # Collect the indices of the largest holes until the density is acceptable
322 # Collect the indices of the largest holes until the density is acceptable
584 indicesheap = []
323 indicesheap = []
585 heapq.heapify(indicesheap)
324 heapq.heapify(indicesheap)
586 while gapsheap and density < targetdensity:
325 while gapsheap and density < targetdensity:
587 oppgapsize, gapidx = heapq.heappop(gapsheap)
326 oppgapsize, gapidx = heapq.heappop(gapsheap)
588
327
589 heapq.heappush(indicesheap, gapidx)
328 heapq.heappush(indicesheap, gapidx)
590
329
591 # the gap sizes are stored as negatives to be sorted decreasingly
330 # the gap sizes are stored as negatives to be sorted decreasingly
592 # by the heap
331 # by the heap
593 readdata -= (-oppgapsize)
332 readdata -= (-oppgapsize)
594 if readdata > 0:
333 if readdata > 0:
595 density = chainpayload / float(readdata)
334 density = chainpayload / float(readdata)
596 else:
335 else:
597 density = 1.0
336 density = 1.0
598
337
599 # Cut the revs at collected indices
338 # Cut the revs at collected indices
600 previdx = 0
339 previdx = 0
601 while indicesheap:
340 while indicesheap:
602 idx = heapq.heappop(indicesheap)
341 idx = heapq.heappop(indicesheap)
603
342
604 chunk = _trimchunk(revlog, revs, previdx, idx)
343 chunk = _trimchunk(revlog, revs, previdx, idx)
605 if chunk:
344 if chunk:
606 yield chunk
345 yield chunk
607
346
608 previdx = idx
347 previdx = idx
609
348
610 chunk = _trimchunk(revlog, revs, previdx)
349 chunk = _trimchunk(revlog, revs, previdx)
611 if chunk:
350 if chunk:
612 yield chunk
351 yield chunk
613
352
353 def _trimchunk(revlog, revs, startidx, endidx=None):
354 """returns revs[startidx:endidx] without empty trailing revs
355
356 Doctest Setup
357 >>> revlog = _testrevlog([
358 ... 5, #0
359 ... 10, #1
360 ... 12, #2
361 ... 12, #3 (empty)
362 ... 17, #4
363 ... 21, #5
364 ... 21, #6 (empty)
365 ... ])
366
367 Contiguous cases:
368 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
369 [0, 1, 2, 3, 4, 5]
370 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
371 [0, 1, 2, 3, 4]
372 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
373 [0, 1, 2]
374 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
375 [2]
376 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
377 [3, 4, 5]
378 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
379 [3, 4]
380
381 Discontiguous cases:
382 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
383 [1, 3, 5]
384 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
385 [1]
386 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
387 [3, 5]
388 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
389 [3, 5]
390 """
391 length = revlog.length
392
393 if endidx is None:
394 endidx = len(revs)
395
396 # If we have a non-emtpy delta candidate, there are nothing to trim
397 if revs[endidx - 1] < len(revlog):
398 # Trim empty revs at the end, except the very first revision of a chain
399 while (endidx > 1
400 and endidx > startidx
401 and length(revs[endidx - 1]) == 0):
402 endidx -= 1
403
404 return revs[startidx:endidx]
405
406 def segmentspan(revlog, revs, deltainfo=None):
407 """Get the byte span of a segment of revisions
408
409 revs is a sorted array of revision numbers
410
411 >>> revlog = _testrevlog([
412 ... 5, #0
413 ... 10, #1
414 ... 12, #2
415 ... 12, #3 (empty)
416 ... 17, #4
417 ... ])
418
419 >>> segmentspan(revlog, [0, 1, 2, 3, 4])
420 17
421 >>> segmentspan(revlog, [0, 4])
422 17
423 >>> segmentspan(revlog, [3, 4])
424 5
425 >>> segmentspan(revlog, [1, 2, 3,])
426 7
427 >>> segmentspan(revlog, [1, 3])
428 7
429 """
430 if not revs:
431 return 0
432 if deltainfo is not None and len(revlog) <= revs[-1]:
433 if len(revs) == 1:
434 return deltainfo.deltalen
435 offset = revlog.end(len(revlog) - 1)
436 end = deltainfo.deltalen + offset
437 else:
438 end = revlog.end(revs[-1])
439 return end - revlog.start(revs[0])
440
614 @attr.s(slots=True, frozen=True)
441 @attr.s(slots=True, frozen=True)
615 class _deltainfo(object):
442 class _deltainfo(object):
616 distance = attr.ib()
443 distance = attr.ib()
617 deltalen = attr.ib()
444 deltalen = attr.ib()
618 data = attr.ib()
445 data = attr.ib()
619 base = attr.ib()
446 base = attr.ib()
620 chainbase = attr.ib()
447 chainbase = attr.ib()
621 chainlen = attr.ib()
448 chainlen = attr.ib()
622 compresseddeltalen = attr.ib()
449 compresseddeltalen = attr.ib()
623 snapshotdepth = attr.ib()
450 snapshotdepth = attr.ib()
624
451
625 class _deltacomputer(object):
452 def isgooddeltainfo(revlog, deltainfo, revinfo):
453 """Returns True if the given delta is good. Good means that it is within
454 the disk span, disk size, and chain length bounds that we know to be
455 performant."""
456 if deltainfo is None:
457 return False
458
459 # - 'deltainfo.distance' is the distance from the base revision --
460 # bounding it limits the amount of I/O we need to do.
461 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
462 # deltas we need to apply -- bounding it limits the amount of CPU
463 # we consume.
464
465 if revlog._sparserevlog:
466 # As sparse-read will be used, we can consider that the distance,
467 # instead of being the span of the whole chunk,
468 # is the span of the largest read chunk
469 base = deltainfo.base
470
471 if base != nullrev:
472 deltachain = revlog._deltachain(base)[0]
473 else:
474 deltachain = []
475
476 # search for the first non-snapshot revision
477 for idx, r in enumerate(deltachain):
478 if not revlog.issnapshot(r):
479 break
480 deltachain = deltachain[idx:]
481 chunks = slicechunk(revlog, deltachain, deltainfo)
482 all_span = [segmentspan(revlog, revs, deltainfo)
483 for revs in chunks]
484 distance = max(all_span)
485 else:
486 distance = deltainfo.distance
487
488 textlen = revinfo.textlen
489 defaultmax = textlen * 4
490 maxdist = revlog._maxdeltachainspan
491 if not maxdist:
492 maxdist = distance # ensure the conditional pass
493 maxdist = max(maxdist, defaultmax)
494 if revlog._sparserevlog and maxdist < revlog._srmingapsize:
495 # In multiple place, we are ignoring irrelevant data range below a
496 # certain size. Be also apply this tradeoff here and relax span
497 # constraint for small enought content.
498 maxdist = revlog._srmingapsize
499
500 # Bad delta from read span:
501 #
502 # If the span of data read is larger than the maximum allowed.
503 if maxdist < distance:
504 return False
505
506 # Bad delta from new delta size:
507 #
508 # If the delta size is larger than the target text, storing the
509 # delta will be inefficient.
510 if textlen < deltainfo.deltalen:
511 return False
512
513 # Bad delta from cumulated payload size:
514 #
515 # If the sum of delta get larger than K * target text length.
516 if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
517 return False
518
519 # Bad delta from chain length:
520 #
521 # If the number of delta in the chain gets too high.
522 if (revlog._maxchainlen
523 and revlog._maxchainlen < deltainfo.chainlen):
524 return False
525
526 # bad delta from intermediate snapshot size limit
527 #
528 # If an intermediate snapshot size is higher than the limit. The
529 # limit exist to prevent endless chain of intermediate delta to be
530 # created.
531 if (deltainfo.snapshotdepth is not None and
532 (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen):
533 return False
534
535 # bad delta if new intermediate snapshot is larger than the previous
536 # snapshot
537 if (deltainfo.snapshotdepth
538 and revlog.length(deltainfo.base) < deltainfo.deltalen):
539 return False
540
541 return True
542
543 class deltacomputer(object):
626 def __init__(self, revlog):
544 def __init__(self, revlog):
627 self.revlog = revlog
545 self.revlog = revlog
628
546
629 def _getcandidaterevs(self, p1, p2, cachedelta):
547 def _getcandidaterevs(self, p1, p2, cachedelta):
630 """
548 """
631 Provides revisions that present an interest to be diffed against,
549 Provides revisions that present an interest to be diffed against,
632 grouped by level of easiness.
550 grouped by level of easiness.
633 """
551 """
634 revlog = self.revlog
552 revlog = self.revlog
635 gdelta = revlog._generaldelta
553 gdelta = revlog._generaldelta
636 curr = len(revlog)
554 curr = len(revlog)
637 prev = curr - 1
555 prev = curr - 1
638 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
556 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
639
557
640 # should we try to build a delta?
558 # should we try to build a delta?
641 if prev != nullrev and revlog._storedeltachains:
559 if prev != nullrev and revlog._storedeltachains:
642 tested = set()
560 tested = set()
643 # This condition is true most of the time when processing
561 # This condition is true most of the time when processing
644 # changegroup data into a generaldelta repo. The only time it
562 # changegroup data into a generaldelta repo. The only time it
645 # isn't true is if this is the first revision in a delta chain
563 # isn't true is if this is the first revision in a delta chain
646 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
564 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
647 if cachedelta and gdelta and revlog._lazydeltabase:
565 if cachedelta and gdelta and revlog._lazydeltabase:
648 # Assume what we received from the server is a good choice
566 # Assume what we received from the server is a good choice
649 # build delta will reuse the cache
567 # build delta will reuse the cache
650 yield (cachedelta[0],)
568 yield (cachedelta[0],)
651 tested.add(cachedelta[0])
569 tested.add(cachedelta[0])
652
570
653 if gdelta:
571 if gdelta:
654 # exclude already lazy tested base if any
572 # exclude already lazy tested base if any
655 parents = [p for p in (p1r, p2r)
573 parents = [p for p in (p1r, p2r)
656 if p != nullrev and p not in tested]
574 if p != nullrev and p not in tested]
657
575
658 if not revlog._deltabothparents and len(parents) == 2:
576 if not revlog._deltabothparents and len(parents) == 2:
659 parents.sort()
577 parents.sort()
660 # To minimize the chance of having to build a fulltext,
578 # To minimize the chance of having to build a fulltext,
661 # pick first whichever parent is closest to us (max rev)
579 # pick first whichever parent is closest to us (max rev)
662 yield (parents[1],)
580 yield (parents[1],)
663 # then the other one (min rev) if the first did not fit
581 # then the other one (min rev) if the first did not fit
664 yield (parents[0],)
582 yield (parents[0],)
665 tested.update(parents)
583 tested.update(parents)
666 elif len(parents) > 0:
584 elif len(parents) > 0:
667 # Test all parents (1 or 2), and keep the best candidate
585 # Test all parents (1 or 2), and keep the best candidate
668 yield parents
586 yield parents
669 tested.update(parents)
587 tested.update(parents)
670
588
671 if prev not in tested:
589 if prev not in tested:
672 # other approach failed try against prev to hopefully save us a
590 # other approach failed try against prev to hopefully save us a
673 # fulltext.
591 # fulltext.
674 yield (prev,)
592 yield (prev,)
675 tested.add(prev)
593 tested.add(prev)
676
594
677 def buildtext(self, revinfo, fh):
595 def buildtext(self, revinfo, fh):
678 """Builds a fulltext version of a revision
596 """Builds a fulltext version of a revision
679
597
680 revinfo: _revisioninfo instance that contains all needed info
598 revinfo: _revisioninfo instance that contains all needed info
681 fh: file handle to either the .i or the .d revlog file,
599 fh: file handle to either the .i or the .d revlog file,
682 depending on whether it is inlined or not
600 depending on whether it is inlined or not
683 """
601 """
684 btext = revinfo.btext
602 btext = revinfo.btext
685 if btext[0] is not None:
603 if btext[0] is not None:
686 return btext[0]
604 return btext[0]
687
605
688 revlog = self.revlog
606 revlog = self.revlog
689 cachedelta = revinfo.cachedelta
607 cachedelta = revinfo.cachedelta
690 flags = revinfo.flags
608 flags = revinfo.flags
691 node = revinfo.node
609 node = revinfo.node
692
610
693 baserev = cachedelta[0]
611 baserev = cachedelta[0]
694 delta = cachedelta[1]
612 delta = cachedelta[1]
695 # special case deltas which replace entire base; no need to decode
613 # special case deltas which replace entire base; no need to decode
696 # base revision. this neatly avoids censored bases, which throw when
614 # base revision. this neatly avoids censored bases, which throw when
697 # they're decoded.
615 # they're decoded.
698 hlen = struct.calcsize(">lll")
616 hlen = struct.calcsize(">lll")
699 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
617 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
700 len(delta) - hlen):
618 len(delta) - hlen):
701 btext[0] = delta[hlen:]
619 btext[0] = delta[hlen:]
702 else:
620 else:
703 # deltabase is rawtext before changed by flag processors, which is
621 # deltabase is rawtext before changed by flag processors, which is
704 # equivalent to non-raw text
622 # equivalent to non-raw text
705 basetext = revlog.revision(baserev, _df=fh, raw=False)
623 basetext = revlog.revision(baserev, _df=fh, raw=False)
706 btext[0] = mdiff.patch(basetext, delta)
624 btext[0] = mdiff.patch(basetext, delta)
707
625
708 try:
626 try:
709 res = revlog._processflags(btext[0], flags, 'read', raw=True)
627 res = revlog._processflags(btext[0], flags, 'read', raw=True)
710 btext[0], validatehash = res
628 btext[0], validatehash = res
711 if validatehash:
629 if validatehash:
712 revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
630 revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
713 if flags & REVIDX_ISCENSORED:
631 if flags & REVIDX_ISCENSORED:
714 raise RevlogError(_('node %s is not censored') % node)
632 raise RevlogError(_('node %s is not censored') % node)
715 except CensoredNodeError:
633 except CensoredNodeError:
716 # must pass the censored index flag to add censored revisions
634 # must pass the censored index flag to add censored revisions
717 if not flags & REVIDX_ISCENSORED:
635 if not flags & REVIDX_ISCENSORED:
718 raise
636 raise
719 return btext[0]
637 return btext[0]
720
638
721 def _builddeltadiff(self, base, revinfo, fh):
639 def _builddeltadiff(self, base, revinfo, fh):
722 revlog = self.revlog
640 revlog = self.revlog
723 t = self.buildtext(revinfo, fh)
641 t = self.buildtext(revinfo, fh)
724 if revlog.iscensored(base):
642 if revlog.iscensored(base):
725 # deltas based on a censored revision must replace the
643 # deltas based on a censored revision must replace the
726 # full content in one patch, so delta works everywhere
644 # full content in one patch, so delta works everywhere
727 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
645 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
728 delta = header + t
646 delta = header + t
729 else:
647 else:
730 ptext = revlog.revision(base, _df=fh, raw=True)
648 ptext = revlog.revision(base, _df=fh, raw=True)
731 delta = mdiff.textdiff(ptext, t)
649 delta = mdiff.textdiff(ptext, t)
732
650
733 return delta
651 return delta
734
652
735 def _builddeltainfo(self, revinfo, base, fh):
653 def _builddeltainfo(self, revinfo, base, fh):
736 # can we use the cached delta?
654 # can we use the cached delta?
737 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
655 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
738 delta = revinfo.cachedelta[1]
656 delta = revinfo.cachedelta[1]
739 else:
657 else:
740 delta = self._builddeltadiff(base, revinfo, fh)
658 delta = self._builddeltadiff(base, revinfo, fh)
741 revlog = self.revlog
659 revlog = self.revlog
742 header, data = revlog.compress(delta)
660 header, data = revlog.compress(delta)
743 deltalen = len(header) + len(data)
661 deltalen = len(header) + len(data)
744 chainbase = revlog.chainbase(base)
662 chainbase = revlog.chainbase(base)
745 offset = revlog.end(len(revlog) - 1)
663 offset = revlog.end(len(revlog) - 1)
746 dist = deltalen + offset - revlog.start(chainbase)
664 dist = deltalen + offset - revlog.start(chainbase)
747 if revlog._generaldelta:
665 if revlog._generaldelta:
748 deltabase = base
666 deltabase = base
749 else:
667 else:
750 deltabase = chainbase
668 deltabase = chainbase
751 chainlen, compresseddeltalen = revlog._chaininfo(base)
669 chainlen, compresseddeltalen = revlog._chaininfo(base)
752 chainlen += 1
670 chainlen += 1
753 compresseddeltalen += deltalen
671 compresseddeltalen += deltalen
754
672
755 revlog = self.revlog
673 revlog = self.revlog
756 snapshotdepth = None
674 snapshotdepth = None
757 if deltabase == nullrev:
675 if deltabase == nullrev:
758 snapshotdepth = 0
676 snapshotdepth = 0
759 elif revlog._sparserevlog and revlog.issnapshot(deltabase):
677 elif revlog._sparserevlog and revlog.issnapshot(deltabase):
760 # A delta chain should always be one full snapshot,
678 # A delta chain should always be one full snapshot,
761 # zero or more semi-snapshots, and zero or more deltas
679 # zero or more semi-snapshots, and zero or more deltas
762 p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
680 p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
763 if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
681 if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
764 snapshotdepth = len(revlog._deltachain(deltabase)[0])
682 snapshotdepth = len(revlog._deltachain(deltabase)[0])
765
683
766 return _deltainfo(dist, deltalen, (header, data), deltabase,
684 return _deltainfo(dist, deltalen, (header, data), deltabase,
767 chainbase, chainlen, compresseddeltalen,
685 chainbase, chainlen, compresseddeltalen,
768 snapshotdepth)
686 snapshotdepth)
769
687
770 def finddeltainfo(self, revinfo, fh):
688 def finddeltainfo(self, revinfo, fh):
771 """Find an acceptable delta against a candidate revision
689 """Find an acceptable delta against a candidate revision
772
690
773 revinfo: information about the revision (instance of _revisioninfo)
691 revinfo: information about the revision (instance of _revisioninfo)
774 fh: file handle to either the .i or the .d revlog file,
692 fh: file handle to either the .i or the .d revlog file,
775 depending on whether it is inlined or not
693 depending on whether it is inlined or not
776
694
777 Returns the first acceptable candidate revision, as ordered by
695 Returns the first acceptable candidate revision, as ordered by
778 _getcandidaterevs
696 _getcandidaterevs
779 """
697 """
780 if not revinfo.textlen:
698 if not revinfo.textlen:
781 return None # empty file do not need delta
699 return None # empty file do not need delta
782
700
783 cachedelta = revinfo.cachedelta
701 cachedelta = revinfo.cachedelta
784 p1 = revinfo.p1
702 p1 = revinfo.p1
785 p2 = revinfo.p2
703 p2 = revinfo.p2
786 revlog = self.revlog
704 revlog = self.revlog
787
705
788 deltalength = self.revlog.length
706 deltalength = self.revlog.length
789 deltaparent = self.revlog.deltaparent
707 deltaparent = self.revlog.deltaparent
790
708
791 deltainfo = None
709 deltainfo = None
792 deltas_limit = revinfo.textlen * LIMIT_DELTA2TEXT
710 deltas_limit = revinfo.textlen * LIMIT_DELTA2TEXT
793 for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
711 for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
794 # filter out delta base that will never produce good delta
712 # filter out delta base that will never produce good delta
795 candidaterevs = [r for r in candidaterevs
713 candidaterevs = [r for r in candidaterevs
796 if self.revlog.length(r) <= deltas_limit]
714 if self.revlog.length(r) <= deltas_limit]
797 nominateddeltas = []
715 nominateddeltas = []
798 for candidaterev in candidaterevs:
716 for candidaterev in candidaterevs:
799 # skip over empty delta (no need to include them in a chain)
717 # skip over empty delta (no need to include them in a chain)
800 while candidaterev != nullrev and not deltalength(candidaterev):
718 while candidaterev != nullrev and not deltalength(candidaterev):
801 candidaterev = deltaparent(candidaterev)
719 candidaterev = deltaparent(candidaterev)
802 # no need to try a delta against nullid, this will be handled
720 # no need to try a delta against nullid, this will be handled
803 # by fulltext later.
721 # by fulltext later.
804 if candidaterev == nullrev:
722 if candidaterev == nullrev:
805 continue
723 continue
806 # no delta for rawtext-changing revs (see "candelta" for why)
724 # no delta for rawtext-changing revs (see "candelta" for why)
807 if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
725 if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
808 continue
726 continue
809 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
727 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
810 if revlog._isgooddeltainfo(candidatedelta, revinfo):
728 if isgooddeltainfo(self.revlog, candidatedelta, revinfo):
811 nominateddeltas.append(candidatedelta)
729 nominateddeltas.append(candidatedelta)
812 if nominateddeltas:
730 if nominateddeltas:
813 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
731 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
814 break
732 break
815
733
816 return deltainfo
734 return deltainfo
817
818 @attr.s(slots=True, frozen=True)
819 class _revisioninfo(object):
820 """Information about a revision that allows building its fulltext
821 node: expected hash of the revision
822 p1, p2: parent revs of the revision
823 btext: built text cache consisting of a one-element list
824 cachedelta: (baserev, uncompressed_delta) or None
825 flags: flags associated to the revision storage
826
827 One of btext[0] or cachedelta must be set.
828 """
829 node = attr.ib()
830 p1 = attr.ib()
831 p2 = attr.ib()
832 btext = attr.ib()
833 textlen = attr.ib()
834 cachedelta = attr.ib()
835 flags = attr.ib()
836
837 @interfaceutil.implementer(repository.irevisiondelta)
838 @attr.s(slots=True, frozen=True)
839 class revlogrevisiondelta(object):
840 node = attr.ib()
841 p1node = attr.ib()
842 p2node = attr.ib()
843 basenode = attr.ib()
844 linknode = attr.ib()
845 flags = attr.ib()
846 baserevisionsize = attr.ib()
847 revision = attr.ib()
848 delta = attr.ib()
849
850 # index v0:
851 # 4 bytes: offset
852 # 4 bytes: compressed length
853 # 4 bytes: base rev
854 # 4 bytes: link rev
855 # 20 bytes: parent 1 nodeid
856 # 20 bytes: parent 2 nodeid
857 # 20 bytes: nodeid
858 indexformatv0 = struct.Struct(">4l20s20s20s")
859 indexformatv0_pack = indexformatv0.pack
860 indexformatv0_unpack = indexformatv0.unpack
861
862 class revlogoldindex(list):
863 def __getitem__(self, i):
864 if i == -1:
865 return (0, 0, 0, -1, -1, -1, -1, nullid)
866 return list.__getitem__(self, i)
867
868 class revlogoldio(object):
869 def __init__(self):
870 self.size = indexformatv0.size
871
872 def parseindex(self, data, inline):
873 s = self.size
874 index = []
875 nodemap = {nullid: nullrev}
876 n = off = 0
877 l = len(data)
878 while off + s <= l:
879 cur = data[off:off + s]
880 off += s
881 e = indexformatv0_unpack(cur)
882 # transform to revlogv1 format
883 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
884 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
885 index.append(e2)
886 nodemap[e[6]] = n
887 n += 1
888
889 return revlogoldindex(index), nodemap, None
890
891 def packentry(self, entry, node, version, rev):
892 if gettype(entry[0]):
893 raise RevlogError(_('index entry flags need revlog version 1'))
894 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
895 node(entry[5]), node(entry[6]), entry[7])
896 return indexformatv0_pack(*e2)
897
898 # index ng:
899 # 6 bytes: offset
900 # 2 bytes: flags
901 # 4 bytes: compressed length
902 # 4 bytes: uncompressed length
903 # 4 bytes: base rev
904 # 4 bytes: link rev
905 # 4 bytes: parent 1 rev
906 # 4 bytes: parent 2 rev
907 # 32 bytes: nodeid
908 indexformatng = struct.Struct(">Qiiiiii20s12x")
909 indexformatng_pack = indexformatng.pack
910 versionformat = struct.Struct(">I")
911 versionformat_pack = versionformat.pack
912 versionformat_unpack = versionformat.unpack
913
914 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
915 # signed integer)
916 _maxentrysize = 0x7fffffff
917
918 class revlogio(object):
919 def __init__(self):
920 self.size = indexformatng.size
921
922 def parseindex(self, data, inline):
923 # call the C implementation to parse the index data
924 index, cache = parsers.parse_index2(data, inline)
925 return index, getattr(index, 'nodemap', None), cache
926
927 def packentry(self, entry, node, version, rev):
928 p = indexformatng_pack(*entry)
929 if rev == 0:
930 p = versionformat_pack(version) + p[4:]
931 return p
932
933 class revlog(object):
934 """
935 the underlying revision storage object
936
937 A revlog consists of two parts, an index and the revision data.
938
939 The index is a file with a fixed record size containing
940 information on each revision, including its nodeid (hash), the
941 nodeids of its parents, the position and offset of its data within
942 the data file, and the revision it's based on. Finally, each entry
943 contains a linkrev entry that can serve as a pointer to external
944 data.
945
946 The revision data itself is a linear collection of data chunks.
947 Each chunk represents a revision and is usually represented as a
948 delta against the previous chunk. To bound lookup time, runs of
949 deltas are limited to about 2 times the length of the original
950 version data. This makes retrieval of a version proportional to
951 its size, or O(1) relative to the number of revisions.
952
953 Both pieces of the revlog are written to in an append-only
954 fashion, which means we never need to rewrite a file to insert or
955 remove data, and can use some simple techniques to avoid the need
956 for locking while reading.
957
958 If checkambig, indexfile is opened with checkambig=True at
959 writing, to avoid file stat ambiguity.
960
961 If mmaplargeindex is True, and an mmapindexthreshold is set, the
962 index will be mmapped rather than read if it is larger than the
963 configured threshold.
964
965 If censorable is True, the revlog can have censored revisions.
966 """
967 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
968 mmaplargeindex=False, censorable=False):
969 """
970 create a revlog object
971
972 opener is a function that abstracts the file opening operation
973 and can be used to implement COW semantics or the like.
974 """
975 self.indexfile = indexfile
976 self.datafile = datafile or (indexfile[:-2] + ".d")
977 self.opener = opener
978 # When True, indexfile is opened with checkambig=True at writing, to
979 # avoid file stat ambiguity.
980 self._checkambig = checkambig
981 self._censorable = censorable
982 # 3-tuple of (node, rev, text) for a raw revision.
983 self._cache = None
984 # Maps rev to chain base rev.
985 self._chainbasecache = util.lrucachedict(100)
986 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
987 self._chunkcache = (0, '')
988 # How much data to read and cache into the raw revlog data cache.
989 self._chunkcachesize = 65536
990 self._maxchainlen = None
991 self._deltabothparents = True
992 self.index = []
993 # Mapping of partial identifiers to full nodes.
994 self._pcache = {}
995 # Mapping of revision integer to full node.
996 self._nodecache = {nullid: nullrev}
997 self._nodepos = None
998 self._compengine = 'zlib'
999 self._maxdeltachainspan = -1
1000 self._withsparseread = False
1001 self._sparserevlog = False
1002 self._srdensitythreshold = 0.50
1003 self._srmingapsize = 262144
1004
1005 mmapindexthreshold = None
1006 v = REVLOG_DEFAULT_VERSION
1007 opts = getattr(opener, 'options', None)
1008 if opts is not None:
1009 if 'revlogv2' in opts:
1010 # version 2 revlogs always use generaldelta.
1011 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
1012 elif 'revlogv1' in opts:
1013 if 'generaldelta' in opts:
1014 v |= FLAG_GENERALDELTA
1015 else:
1016 v = 0
1017 if 'chunkcachesize' in opts:
1018 self._chunkcachesize = opts['chunkcachesize']
1019 if 'maxchainlen' in opts:
1020 self._maxchainlen = opts['maxchainlen']
1021 if 'deltabothparents' in opts:
1022 self._deltabothparents = opts['deltabothparents']
1023 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
1024 if 'compengine' in opts:
1025 self._compengine = opts['compengine']
1026 if 'maxdeltachainspan' in opts:
1027 self._maxdeltachainspan = opts['maxdeltachainspan']
1028 if mmaplargeindex and 'mmapindexthreshold' in opts:
1029 mmapindexthreshold = opts['mmapindexthreshold']
1030 self._sparserevlog = bool(opts.get('sparse-revlog', False))
1031 withsparseread = bool(opts.get('with-sparse-read', False))
1032 # sparse-revlog forces sparse-read
1033 self._withsparseread = self._sparserevlog or withsparseread
1034 if 'sparse-read-density-threshold' in opts:
1035 self._srdensitythreshold = opts['sparse-read-density-threshold']
1036 if 'sparse-read-min-gap-size' in opts:
1037 self._srmingapsize = opts['sparse-read-min-gap-size']
1038
1039 if self._chunkcachesize <= 0:
1040 raise RevlogError(_('revlog chunk cache size %r is not greater '
1041 'than 0') % self._chunkcachesize)
1042 elif self._chunkcachesize & (self._chunkcachesize - 1):
1043 raise RevlogError(_('revlog chunk cache size %r is not a power '
1044 'of 2') % self._chunkcachesize)
1045
1046 indexdata = ''
1047 self._initempty = True
1048 try:
1049 with self._indexfp() as f:
1050 if (mmapindexthreshold is not None and
1051 self.opener.fstat(f).st_size >= mmapindexthreshold):
1052 indexdata = util.buffer(util.mmapread(f))
1053 else:
1054 indexdata = f.read()
1055 if len(indexdata) > 0:
1056 v = versionformat_unpack(indexdata[:4])[0]
1057 self._initempty = False
1058 except IOError as inst:
1059 if inst.errno != errno.ENOENT:
1060 raise
1061
1062 self.version = v
1063 self._inline = v & FLAG_INLINE_DATA
1064 self._generaldelta = v & FLAG_GENERALDELTA
1065 flags = v & ~0xFFFF
1066 fmt = v & 0xFFFF
1067 if fmt == REVLOGV0:
1068 if flags:
1069 raise RevlogError(_('unknown flags (%#04x) in version %d '
1070 'revlog %s') %
1071 (flags >> 16, fmt, self.indexfile))
1072 elif fmt == REVLOGV1:
1073 if flags & ~REVLOGV1_FLAGS:
1074 raise RevlogError(_('unknown flags (%#04x) in version %d '
1075 'revlog %s') %
1076 (flags >> 16, fmt, self.indexfile))
1077 elif fmt == REVLOGV2:
1078 if flags & ~REVLOGV2_FLAGS:
1079 raise RevlogError(_('unknown flags (%#04x) in version %d '
1080 'revlog %s') %
1081 (flags >> 16, fmt, self.indexfile))
1082 else:
1083 raise RevlogError(_('unknown version (%d) in revlog %s') %
1084 (fmt, self.indexfile))
1085
1086 self._storedeltachains = True
1087
1088 self._io = revlogio()
1089 if self.version == REVLOGV0:
1090 self._io = revlogoldio()
1091 try:
1092 d = self._io.parseindex(indexdata, self._inline)
1093 except (ValueError, IndexError):
1094 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
1095 self.index, nodemap, self._chunkcache = d
1096 if nodemap is not None:
1097 self.nodemap = self._nodecache = nodemap
1098 if not self._chunkcache:
1099 self._chunkclear()
1100 # revnum -> (chain-length, sum-delta-length)
1101 self._chaininfocache = {}
1102 # revlog header -> revlog compressor
1103 self._decompressors = {}
1104
1105 @util.propertycache
1106 def _compressor(self):
1107 return util.compengines[self._compengine].revlogcompressor()
1108
1109 def _indexfp(self, mode='r'):
1110 """file object for the revlog's index file"""
1111 args = {r'mode': mode}
1112 if mode != 'r':
1113 args[r'checkambig'] = self._checkambig
1114 if mode == 'w':
1115 args[r'atomictemp'] = True
1116 return self.opener(self.indexfile, **args)
1117
1118 def _datafp(self, mode='r'):
1119 """file object for the revlog's data file"""
1120 return self.opener(self.datafile, mode=mode)
1121
1122 @contextlib.contextmanager
1123 def _datareadfp(self, existingfp=None):
1124 """file object suitable to read data"""
1125 if existingfp is not None:
1126 yield existingfp
1127 else:
1128 if self._inline:
1129 func = self._indexfp
1130 else:
1131 func = self._datafp
1132 with func() as fp:
1133 yield fp
1134
1135 def tip(self):
1136 return self.node(len(self.index) - 1)
1137 def __contains__(self, rev):
1138 return 0 <= rev < len(self)
1139 def __len__(self):
1140 return len(self.index)
1141 def __iter__(self):
1142 return iter(pycompat.xrange(len(self)))
1143 def revs(self, start=0, stop=None):
1144 """iterate over all rev in this revlog (from start to stop)"""
1145 step = 1
1146 length = len(self)
1147 if stop is not None:
1148 if start > stop:
1149 step = -1
1150 stop += step
1151 if stop > length:
1152 stop = length
1153 else:
1154 stop = length
1155 return pycompat.xrange(start, stop, step)
1156
1157 @util.propertycache
1158 def nodemap(self):
1159 if self.index:
1160 # populate mapping down to the initial node
1161 node0 = self.index[0][7] # get around changelog filtering
1162 self.rev(node0)
1163 return self._nodecache
1164
1165 def hasnode(self, node):
1166 try:
1167 self.rev(node)
1168 return True
1169 except KeyError:
1170 return False
1171
1172 def candelta(self, baserev, rev):
1173 """whether two revisions (baserev, rev) can be delta-ed or not"""
1174 # Disable delta if either rev requires a content-changing flag
1175 # processor (ex. LFS). This is because such flag processor can alter
1176 # the rawtext content that the delta will be based on, and two clients
1177 # could have a same revlog node with different flags (i.e. different
1178 # rawtext contents) and the delta could be incompatible.
1179 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
1180 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
1181 return False
1182 return True
1183
1184 def clearcaches(self):
1185 self._cache = None
1186 self._chainbasecache.clear()
1187 self._chunkcache = (0, '')
1188 self._pcache = {}
1189
1190 try:
1191 self._nodecache.clearcaches()
1192 except AttributeError:
1193 self._nodecache = {nullid: nullrev}
1194 self._nodepos = None
1195
1196 def rev(self, node):
1197 try:
1198 return self._nodecache[node]
1199 except TypeError:
1200 raise
1201 except RevlogError:
1202 # parsers.c radix tree lookup failed
1203 if node == wdirid or node in wdirfilenodeids:
1204 raise error.WdirUnsupported
1205 raise LookupError(node, self.indexfile, _('no node'))
1206 except KeyError:
1207 # pure python cache lookup failed
1208 n = self._nodecache
1209 i = self.index
1210 p = self._nodepos
1211 if p is None:
1212 p = len(i) - 1
1213 else:
1214 assert p < len(i)
1215 for r in pycompat.xrange(p, -1, -1):
1216 v = i[r][7]
1217 n[v] = r
1218 if v == node:
1219 self._nodepos = r - 1
1220 return r
1221 if node == wdirid or node in wdirfilenodeids:
1222 raise error.WdirUnsupported
1223 raise LookupError(node, self.indexfile, _('no node'))
1224
1225 # Accessors for index entries.
1226
1227 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1228 # are flags.
1229 def start(self, rev):
1230 return int(self.index[rev][0] >> 16)
1231
1232 def flags(self, rev):
1233 return self.index[rev][0] & 0xFFFF
1234
1235 def length(self, rev):
1236 return self.index[rev][1]
1237
1238 def rawsize(self, rev):
1239 """return the length of the uncompressed text for a given revision"""
1240 l = self.index[rev][2]
1241 if l >= 0:
1242 return l
1243
1244 t = self.revision(rev, raw=True)
1245 return len(t)
1246
1247 def size(self, rev):
1248 """length of non-raw text (processed by a "read" flag processor)"""
1249 # fast path: if no "read" flag processor could change the content,
1250 # size is rawsize. note: ELLIPSIS is known to not change the content.
1251 flags = self.flags(rev)
1252 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1253 return self.rawsize(rev)
1254
1255 return len(self.revision(rev, raw=False))
1256
1257 def chainbase(self, rev):
1258 base = self._chainbasecache.get(rev)
1259 if base is not None:
1260 return base
1261
1262 index = self.index
1263 iterrev = rev
1264 base = index[iterrev][3]
1265 while base != iterrev:
1266 iterrev = base
1267 base = index[iterrev][3]
1268
1269 self._chainbasecache[rev] = base
1270 return base
1271
1272 def linkrev(self, rev):
1273 return self.index[rev][4]
1274
1275 def parentrevs(self, rev):
1276 try:
1277 entry = self.index[rev]
1278 except IndexError:
1279 if rev == wdirrev:
1280 raise error.WdirUnsupported
1281 raise
1282
1283 return entry[5], entry[6]
1284
1285 def node(self, rev):
1286 try:
1287 return self.index[rev][7]
1288 except IndexError:
1289 if rev == wdirrev:
1290 raise error.WdirUnsupported
1291 raise
1292
1293 # Derived from index values.
1294
1295 def end(self, rev):
1296 return self.start(rev) + self.length(rev)
1297
1298 def parents(self, node):
1299 i = self.index
1300 d = i[self.rev(node)]
1301 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
1302
1303 def chainlen(self, rev):
1304 return self._chaininfo(rev)[0]
1305
1306 def _chaininfo(self, rev):
1307 chaininfocache = self._chaininfocache
1308 if rev in chaininfocache:
1309 return chaininfocache[rev]
1310 index = self.index
1311 generaldelta = self._generaldelta
1312 iterrev = rev
1313 e = index[iterrev]
1314 clen = 0
1315 compresseddeltalen = 0
1316 while iterrev != e[3]:
1317 clen += 1
1318 compresseddeltalen += e[1]
1319 if generaldelta:
1320 iterrev = e[3]
1321 else:
1322 iterrev -= 1
1323 if iterrev in chaininfocache:
1324 t = chaininfocache[iterrev]
1325 clen += t[0]
1326 compresseddeltalen += t[1]
1327 break
1328 e = index[iterrev]
1329 else:
1330 # Add text length of base since decompressing that also takes
1331 # work. For cache hits the length is already included.
1332 compresseddeltalen += e[1]
1333 r = (clen, compresseddeltalen)
1334 chaininfocache[rev] = r
1335 return r
1336
1337 def _deltachain(self, rev, stoprev=None):
1338 """Obtain the delta chain for a revision.
1339
1340 ``stoprev`` specifies a revision to stop at. If not specified, we
1341 stop at the base of the chain.
1342
1343 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1344 revs in ascending order and ``stopped`` is a bool indicating whether
1345 ``stoprev`` was hit.
1346 """
1347 # Try C implementation.
1348 try:
1349 return self.index.deltachain(rev, stoprev, self._generaldelta)
1350 except AttributeError:
1351 pass
1352
1353 chain = []
1354
1355 # Alias to prevent attribute lookup in tight loop.
1356 index = self.index
1357 generaldelta = self._generaldelta
1358
1359 iterrev = rev
1360 e = index[iterrev]
1361 while iterrev != e[3] and iterrev != stoprev:
1362 chain.append(iterrev)
1363 if generaldelta:
1364 iterrev = e[3]
1365 else:
1366 iterrev -= 1
1367 e = index[iterrev]
1368
1369 if iterrev == stoprev:
1370 stopped = True
1371 else:
1372 chain.append(iterrev)
1373 stopped = False
1374
1375 chain.reverse()
1376 return chain, stopped
1377
1378 def ancestors(self, revs, stoprev=0, inclusive=False):
1379 """Generate the ancestors of 'revs' in reverse topological order.
1380 Does not generate revs lower than stoprev.
1381
1382 See the documentation for ancestor.lazyancestors for more details."""
1383
1384 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
1385 inclusive=inclusive)
1386
1387 def descendants(self, revs):
1388 """Generate the descendants of 'revs' in revision order.
1389
1390 Yield a sequence of revision numbers starting with a child of
1391 some rev in revs, i.e., each revision is *not* considered a
1392 descendant of itself. Results are ordered by revision number (a
1393 topological sort)."""
1394 first = min(revs)
1395 if first == nullrev:
1396 for i in self:
1397 yield i
1398 return
1399
1400 seen = set(revs)
1401 for i in self.revs(start=first + 1):
1402 for x in self.parentrevs(i):
1403 if x != nullrev and x in seen:
1404 seen.add(i)
1405 yield i
1406 break
1407
1408 def findcommonmissing(self, common=None, heads=None):
1409 """Return a tuple of the ancestors of common and the ancestors of heads
1410 that are not ancestors of common. In revset terminology, we return the
1411 tuple:
1412
1413 ::common, (::heads) - (::common)
1414
1415 The list is sorted by revision number, meaning it is
1416 topologically sorted.
1417
1418 'heads' and 'common' are both lists of node IDs. If heads is
1419 not supplied, uses all of the revlog's heads. If common is not
1420 supplied, uses nullid."""
1421 if common is None:
1422 common = [nullid]
1423 if heads is None:
1424 heads = self.heads()
1425
1426 common = [self.rev(n) for n in common]
1427 heads = [self.rev(n) for n in heads]
1428
1429 # we want the ancestors, but inclusive
1430 class lazyset(object):
1431 def __init__(self, lazyvalues):
1432 self.addedvalues = set()
1433 self.lazyvalues = lazyvalues
1434
1435 def __contains__(self, value):
1436 return value in self.addedvalues or value in self.lazyvalues
1437
1438 def __iter__(self):
1439 added = self.addedvalues
1440 for r in added:
1441 yield r
1442 for r in self.lazyvalues:
1443 if not r in added:
1444 yield r
1445
1446 def add(self, value):
1447 self.addedvalues.add(value)
1448
1449 def update(self, values):
1450 self.addedvalues.update(values)
1451
1452 has = lazyset(self.ancestors(common))
1453 has.add(nullrev)
1454 has.update(common)
1455
1456 # take all ancestors from heads that aren't in has
1457 missing = set()
1458 visit = collections.deque(r for r in heads if r not in has)
1459 while visit:
1460 r = visit.popleft()
1461 if r in missing:
1462 continue
1463 else:
1464 missing.add(r)
1465 for p in self.parentrevs(r):
1466 if p not in has:
1467 visit.append(p)
1468 missing = list(missing)
1469 missing.sort()
1470 return has, [self.node(miss) for miss in missing]
1471
1472 def incrementalmissingrevs(self, common=None):
1473 """Return an object that can be used to incrementally compute the
1474 revision numbers of the ancestors of arbitrary sets that are not
1475 ancestors of common. This is an ancestor.incrementalmissingancestors
1476 object.
1477
1478 'common' is a list of revision numbers. If common is not supplied, uses
1479 nullrev.
1480 """
1481 if common is None:
1482 common = [nullrev]
1483
1484 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1485
1486 def findmissingrevs(self, common=None, heads=None):
1487 """Return the revision numbers of the ancestors of heads that
1488 are not ancestors of common.
1489
1490 More specifically, return a list of revision numbers corresponding to
1491 nodes N such that every N satisfies the following constraints:
1492
1493 1. N is an ancestor of some node in 'heads'
1494 2. N is not an ancestor of any node in 'common'
1495
1496 The list is sorted by revision number, meaning it is
1497 topologically sorted.
1498
1499 'heads' and 'common' are both lists of revision numbers. If heads is
1500 not supplied, uses all of the revlog's heads. If common is not
1501 supplied, uses nullid."""
1502 if common is None:
1503 common = [nullrev]
1504 if heads is None:
1505 heads = self.headrevs()
1506
1507 inc = self.incrementalmissingrevs(common=common)
1508 return inc.missingancestors(heads)
1509
1510 def findmissing(self, common=None, heads=None):
1511 """Return the ancestors of heads that are not ancestors of common.
1512
1513 More specifically, return a list of nodes N such that every N
1514 satisfies the following constraints:
1515
1516 1. N is an ancestor of some node in 'heads'
1517 2. N is not an ancestor of any node in 'common'
1518
1519 The list is sorted by revision number, meaning it is
1520 topologically sorted.
1521
1522 'heads' and 'common' are both lists of node IDs. If heads is
1523 not supplied, uses all of the revlog's heads. If common is not
1524 supplied, uses nullid."""
1525 if common is None:
1526 common = [nullid]
1527 if heads is None:
1528 heads = self.heads()
1529
1530 common = [self.rev(n) for n in common]
1531 heads = [self.rev(n) for n in heads]
1532
1533 inc = self.incrementalmissingrevs(common=common)
1534 return [self.node(r) for r in inc.missingancestors(heads)]
1535
1536 def nodesbetween(self, roots=None, heads=None):
1537 """Return a topological path from 'roots' to 'heads'.
1538
1539 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1540 topologically sorted list of all nodes N that satisfy both of
1541 these constraints:
1542
1543 1. N is a descendant of some node in 'roots'
1544 2. N is an ancestor of some node in 'heads'
1545
1546 Every node is considered to be both a descendant and an ancestor
1547 of itself, so every reachable node in 'roots' and 'heads' will be
1548 included in 'nodes'.
1549
1550 'outroots' is the list of reachable nodes in 'roots', i.e., the
1551 subset of 'roots' that is returned in 'nodes'. Likewise,
1552 'outheads' is the subset of 'heads' that is also in 'nodes'.
1553
1554 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1555 unspecified, uses nullid as the only root. If 'heads' is
1556 unspecified, uses list of all of the revlog's heads."""
1557 nonodes = ([], [], [])
1558 if roots is not None:
1559 roots = list(roots)
1560 if not roots:
1561 return nonodes
1562 lowestrev = min([self.rev(n) for n in roots])
1563 else:
1564 roots = [nullid] # Everybody's a descendant of nullid
1565 lowestrev = nullrev
1566 if (lowestrev == nullrev) and (heads is None):
1567 # We want _all_ the nodes!
1568 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1569 if heads is None:
1570 # All nodes are ancestors, so the latest ancestor is the last
1571 # node.
1572 highestrev = len(self) - 1
1573 # Set ancestors to None to signal that every node is an ancestor.
1574 ancestors = None
1575 # Set heads to an empty dictionary for later discovery of heads
1576 heads = {}
1577 else:
1578 heads = list(heads)
1579 if not heads:
1580 return nonodes
1581 ancestors = set()
1582 # Turn heads into a dictionary so we can remove 'fake' heads.
1583 # Also, later we will be using it to filter out the heads we can't
1584 # find from roots.
1585 heads = dict.fromkeys(heads, False)
1586 # Start at the top and keep marking parents until we're done.
1587 nodestotag = set(heads)
1588 # Remember where the top was so we can use it as a limit later.
1589 highestrev = max([self.rev(n) for n in nodestotag])
1590 while nodestotag:
1591 # grab a node to tag
1592 n = nodestotag.pop()
1593 # Never tag nullid
1594 if n == nullid:
1595 continue
1596 # A node's revision number represents its place in a
1597 # topologically sorted list of nodes.
1598 r = self.rev(n)
1599 if r >= lowestrev:
1600 if n not in ancestors:
1601 # If we are possibly a descendant of one of the roots
1602 # and we haven't already been marked as an ancestor
1603 ancestors.add(n) # Mark as ancestor
1604 # Add non-nullid parents to list of nodes to tag.
1605 nodestotag.update([p for p in self.parents(n) if
1606 p != nullid])
1607 elif n in heads: # We've seen it before, is it a fake head?
1608 # So it is, real heads should not be the ancestors of
1609 # any other heads.
1610 heads.pop(n)
1611 if not ancestors:
1612 return nonodes
1613 # Now that we have our set of ancestors, we want to remove any
1614 # roots that are not ancestors.
1615
1616 # If one of the roots was nullid, everything is included anyway.
1617 if lowestrev > nullrev:
1618 # But, since we weren't, let's recompute the lowest rev to not
1619 # include roots that aren't ancestors.
1620
1621 # Filter out roots that aren't ancestors of heads
1622 roots = [root for root in roots if root in ancestors]
1623 # Recompute the lowest revision
1624 if roots:
1625 lowestrev = min([self.rev(root) for root in roots])
1626 else:
1627 # No more roots? Return empty list
1628 return nonodes
1629 else:
1630 # We are descending from nullid, and don't need to care about
1631 # any other roots.
1632 lowestrev = nullrev
1633 roots = [nullid]
1634 # Transform our roots list into a set.
1635 descendants = set(roots)
1636 # Also, keep the original roots so we can filter out roots that aren't
1637 # 'real' roots (i.e. are descended from other roots).
1638 roots = descendants.copy()
1639 # Our topologically sorted list of output nodes.
1640 orderedout = []
1641 # Don't start at nullid since we don't want nullid in our output list,
1642 # and if nullid shows up in descendants, empty parents will look like
1643 # they're descendants.
1644 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1645 n = self.node(r)
1646 isdescendant = False
1647 if lowestrev == nullrev: # Everybody is a descendant of nullid
1648 isdescendant = True
1649 elif n in descendants:
1650 # n is already a descendant
1651 isdescendant = True
1652 # This check only needs to be done here because all the roots
1653 # will start being marked is descendants before the loop.
1654 if n in roots:
1655 # If n was a root, check if it's a 'real' root.
1656 p = tuple(self.parents(n))
1657 # If any of its parents are descendants, it's not a root.
1658 if (p[0] in descendants) or (p[1] in descendants):
1659 roots.remove(n)
1660 else:
1661 p = tuple(self.parents(n))
1662 # A node is a descendant if either of its parents are
1663 # descendants. (We seeded the dependents list with the roots
1664 # up there, remember?)
1665 if (p[0] in descendants) or (p[1] in descendants):
1666 descendants.add(n)
1667 isdescendant = True
1668 if isdescendant and ((ancestors is None) or (n in ancestors)):
1669 # Only include nodes that are both descendants and ancestors.
1670 orderedout.append(n)
1671 if (ancestors is not None) and (n in heads):
1672 # We're trying to figure out which heads are reachable
1673 # from roots.
1674 # Mark this head as having been reached
1675 heads[n] = True
1676 elif ancestors is None:
1677 # Otherwise, we're trying to discover the heads.
1678 # Assume this is a head because if it isn't, the next step
1679 # will eventually remove it.
1680 heads[n] = True
1681 # But, obviously its parents aren't.
1682 for p in self.parents(n):
1683 heads.pop(p, None)
1684 heads = [head for head, flag in heads.iteritems() if flag]
1685 roots = list(roots)
1686 assert orderedout
1687 assert roots
1688 assert heads
1689 return (orderedout, roots, heads)
1690
1691 def headrevs(self):
1692 try:
1693 return self.index.headrevs()
1694 except AttributeError:
1695 return self._headrevs()
1696
1697 def computephases(self, roots):
1698 return self.index.computephasesmapsets(roots)
1699
1700 def _headrevs(self):
1701 count = len(self)
1702 if not count:
1703 return [nullrev]
1704 # we won't iter over filtered rev so nobody is a head at start
1705 ishead = [0] * (count + 1)
1706 index = self.index
1707 for r in self:
1708 ishead[r] = 1 # I may be an head
1709 e = index[r]
1710 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1711 return [r for r, val in enumerate(ishead) if val]
1712
1713 def heads(self, start=None, stop=None):
1714 """return the list of all nodes that have no children
1715
1716 if start is specified, only heads that are descendants of
1717 start will be returned
1718 if stop is specified, it will consider all the revs from stop
1719 as if they had no children
1720 """
1721 if start is None and stop is None:
1722 if not len(self):
1723 return [nullid]
1724 return [self.node(r) for r in self.headrevs()]
1725
1726 if start is None:
1727 start = nullid
1728 if stop is None:
1729 stop = []
1730 stoprevs = set([self.rev(n) for n in stop])
1731 startrev = self.rev(start)
1732 reachable = {startrev}
1733 heads = {startrev}
1734
1735 parentrevs = self.parentrevs
1736 for r in self.revs(start=startrev + 1):
1737 for p in parentrevs(r):
1738 if p in reachable:
1739 if r not in stoprevs:
1740 reachable.add(r)
1741 heads.add(r)
1742 if p in heads and p not in stoprevs:
1743 heads.remove(p)
1744
1745 return [self.node(r) for r in heads]
1746
1747 def children(self, node):
1748 """find the children of a given node"""
1749 c = []
1750 p = self.rev(node)
1751 for r in self.revs(start=p + 1):
1752 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1753 if prevs:
1754 for pr in prevs:
1755 if pr == p:
1756 c.append(self.node(r))
1757 elif p == nullrev:
1758 c.append(self.node(r))
1759 return c
1760
1761 def commonancestorsheads(self, a, b):
1762 """calculate all the heads of the common ancestors of nodes a and b"""
1763 a, b = self.rev(a), self.rev(b)
1764 ancs = self._commonancestorsheads(a, b)
1765 return pycompat.maplist(self.node, ancs)
1766
1767 def _commonancestorsheads(self, *revs):
1768 """calculate all the heads of the common ancestors of revs"""
1769 try:
1770 ancs = self.index.commonancestorsheads(*revs)
1771 except (AttributeError, OverflowError): # C implementation failed
1772 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1773 return ancs
1774
1775 def isancestor(self, a, b):
1776 """return True if node a is an ancestor of node b
1777
1778 A revision is considered an ancestor of itself."""
1779 a, b = self.rev(a), self.rev(b)
1780 return self.isancestorrev(a, b)
1781
1782 def isancestorrev(self, a, b):
1783 """return True if revision a is an ancestor of revision b
1784
1785 A revision is considered an ancestor of itself.
1786
1787 The implementation of this is trivial but the use of
1788 commonancestorsheads is not."""
1789 if a == nullrev:
1790 return True
1791 elif a == b:
1792 return True
1793 elif a > b:
1794 return False
1795 return a in self._commonancestorsheads(a, b)
1796
1797 def ancestor(self, a, b):
1798 """calculate the "best" common ancestor of nodes a and b"""
1799
1800 a, b = self.rev(a), self.rev(b)
1801 try:
1802 ancs = self.index.ancestors(a, b)
1803 except (AttributeError, OverflowError):
1804 ancs = ancestor.ancestors(self.parentrevs, a, b)
1805 if ancs:
1806 # choose a consistent winner when there's a tie
1807 return min(map(self.node, ancs))
1808 return nullid
1809
1810 def _match(self, id):
1811 if isinstance(id, int):
1812 # rev
1813 return self.node(id)
1814 if len(id) == 20:
1815 # possibly a binary node
1816 # odds of a binary node being all hex in ASCII are 1 in 10**25
1817 try:
1818 node = id
1819 self.rev(node) # quick search the index
1820 return node
1821 except LookupError:
1822 pass # may be partial hex id
1823 try:
1824 # str(rev)
1825 rev = int(id)
1826 if "%d" % rev != id:
1827 raise ValueError
1828 if rev < 0:
1829 rev = len(self) + rev
1830 if rev < 0 or rev >= len(self):
1831 raise ValueError
1832 return self.node(rev)
1833 except (ValueError, OverflowError):
1834 pass
1835 if len(id) == 40:
1836 try:
1837 # a full hex nodeid?
1838 node = bin(id)
1839 self.rev(node)
1840 return node
1841 except (TypeError, LookupError):
1842 pass
1843
1844 def _partialmatch(self, id):
1845 # we don't care wdirfilenodeids as they should be always full hash
1846 maybewdir = wdirhex.startswith(id)
1847 try:
1848 partial = self.index.partialmatch(id)
1849 if partial and self.hasnode(partial):
1850 if maybewdir:
1851 # single 'ff...' match in radix tree, ambiguous with wdir
1852 raise RevlogError
1853 return partial
1854 if maybewdir:
1855 # no 'ff...' match in radix tree, wdir identified
1856 raise error.WdirUnsupported
1857 return None
1858 except RevlogError:
1859 # parsers.c radix tree lookup gave multiple matches
1860 # fast path: for unfiltered changelog, radix tree is accurate
1861 if not getattr(self, 'filteredrevs', None):
1862 raise AmbiguousPrefixLookupError(id, self.indexfile,
1863 _('ambiguous identifier'))
1864 # fall through to slow path that filters hidden revisions
1865 except (AttributeError, ValueError):
1866 # we are pure python, or key was too short to search radix tree
1867 pass
1868
1869 if id in self._pcache:
1870 return self._pcache[id]
1871
1872 if len(id) <= 40:
1873 try:
1874 # hex(node)[:...]
1875 l = len(id) // 2 # grab an even number of digits
1876 prefix = bin(id[:l * 2])
1877 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1878 nl = [n for n in nl if hex(n).startswith(id) and
1879 self.hasnode(n)]
1880 if nullhex.startswith(id):
1881 nl.append(nullid)
1882 if len(nl) > 0:
1883 if len(nl) == 1 and not maybewdir:
1884 self._pcache[id] = nl[0]
1885 return nl[0]
1886 raise AmbiguousPrefixLookupError(id, self.indexfile,
1887 _('ambiguous identifier'))
1888 if maybewdir:
1889 raise error.WdirUnsupported
1890 return None
1891 except TypeError:
1892 pass
1893
1894 def lookup(self, id):
1895 """locate a node based on:
1896 - revision number or str(revision number)
1897 - nodeid or subset of hex nodeid
1898 """
1899 n = self._match(id)
1900 if n is not None:
1901 return n
1902 n = self._partialmatch(id)
1903 if n:
1904 return n
1905
1906 raise LookupError(id, self.indexfile, _('no match found'))
1907
1908 def shortest(self, node, minlength=1):
1909 """Find the shortest unambiguous prefix that matches node."""
1910 def isvalid(prefix):
1911 try:
1912 node = self._partialmatch(prefix)
1913 except error.RevlogError:
1914 return False
1915 except error.WdirUnsupported:
1916 # single 'ff...' match
1917 return True
1918 if node is None:
1919 raise LookupError(node, self.indexfile, _('no node'))
1920 return True
1921
1922 def maybewdir(prefix):
1923 return all(c == 'f' for c in prefix)
1924
1925 hexnode = hex(node)
1926
1927 def disambiguate(hexnode, minlength):
1928 """Disambiguate against wdirid."""
1929 for length in range(minlength, 41):
1930 prefix = hexnode[:length]
1931 if not maybewdir(prefix):
1932 return prefix
1933
1934 if not getattr(self, 'filteredrevs', None):
1935 try:
1936 length = max(self.index.shortest(node), minlength)
1937 return disambiguate(hexnode, length)
1938 except RevlogError:
1939 if node != wdirid:
1940 raise LookupError(node, self.indexfile, _('no node'))
1941 except AttributeError:
1942 # Fall through to pure code
1943 pass
1944
1945 if node == wdirid:
1946 for length in range(minlength, 41):
1947 prefix = hexnode[:length]
1948 if isvalid(prefix):
1949 return prefix
1950
1951 for length in range(minlength, 41):
1952 prefix = hexnode[:length]
1953 if isvalid(prefix):
1954 return disambiguate(hexnode, length)
1955
1956 def cmp(self, node, text):
1957 """compare text with a given file revision
1958
1959 returns True if text is different than what is stored.
1960 """
1961 p1, p2 = self.parents(node)
1962 return hash(text, p1, p2) != node
1963
1964 def _cachesegment(self, offset, data):
1965 """Add a segment to the revlog cache.
1966
1967 Accepts an absolute offset and the data that is at that location.
1968 """
1969 o, d = self._chunkcache
1970 # try to add to existing cache
1971 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1972 self._chunkcache = o, d + data
1973 else:
1974 self._chunkcache = offset, data
1975
1976 def _readsegment(self, offset, length, df=None):
1977 """Load a segment of raw data from the revlog.
1978
1979 Accepts an absolute offset, length to read, and an optional existing
1980 file handle to read from.
1981
1982 If an existing file handle is passed, it will be seeked and the
1983 original seek position will NOT be restored.
1984
1985 Returns a str or buffer of raw byte data.
1986 """
1987 # Cache data both forward and backward around the requested
1988 # data, in a fixed size window. This helps speed up operations
1989 # involving reading the revlog backwards.
1990 cachesize = self._chunkcachesize
1991 realoffset = offset & ~(cachesize - 1)
1992 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1993 - realoffset)
1994 with self._datareadfp(df) as df:
1995 df.seek(realoffset)
1996 d = df.read(reallength)
1997 self._cachesegment(realoffset, d)
1998 if offset != realoffset or reallength != length:
1999 return util.buffer(d, offset - realoffset, length)
2000 return d
2001
2002 def _getsegment(self, offset, length, df=None):
2003 """Obtain a segment of raw data from the revlog.
2004
2005 Accepts an absolute offset, length of bytes to obtain, and an
2006 optional file handle to the already-opened revlog. If the file
2007 handle is used, it's original seek position will not be preserved.
2008
2009 Requests for data may be returned from a cache.
2010
2011 Returns a str or a buffer instance of raw byte data.
2012 """
2013 o, d = self._chunkcache
2014 l = len(d)
2015
2016 # is it in the cache?
2017 cachestart = offset - o
2018 cacheend = cachestart + length
2019 if cachestart >= 0 and cacheend <= l:
2020 if cachestart == 0 and cacheend == l:
2021 return d # avoid a copy
2022 return util.buffer(d, cachestart, cacheend - cachestart)
2023
2024 return self._readsegment(offset, length, df=df)
2025
2026 def _getsegmentforrevs(self, startrev, endrev, df=None):
2027 """Obtain a segment of raw data corresponding to a range of revisions.
2028
2029 Accepts the start and end revisions and an optional already-open
2030 file handle to be used for reading. If the file handle is read, its
2031 seek position will not be preserved.
2032
2033 Requests for data may be satisfied by a cache.
2034
2035 Returns a 2-tuple of (offset, data) for the requested range of
2036 revisions. Offset is the integer offset from the beginning of the
2037 revlog and data is a str or buffer of the raw byte data.
2038
2039 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
2040 to determine where each revision's data begins and ends.
2041 """
2042 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
2043 # (functions are expensive).
2044 index = self.index
2045 istart = index[startrev]
2046 start = int(istart[0] >> 16)
2047 if startrev == endrev:
2048 end = start + istart[1]
2049 else:
2050 iend = index[endrev]
2051 end = int(iend[0] >> 16) + iend[1]
2052
2053 if self._inline:
2054 start += (startrev + 1) * self._io.size
2055 end += (endrev + 1) * self._io.size
2056 length = end - start
2057
2058 return start, self._getsegment(start, length, df=df)
2059
2060 def _chunk(self, rev, df=None):
2061 """Obtain a single decompressed chunk for a revision.
2062
2063 Accepts an integer revision and an optional already-open file handle
2064 to be used for reading. If used, the seek position of the file will not
2065 be preserved.
2066
2067 Returns a str holding uncompressed data for the requested revision.
2068 """
2069 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
2070
2071 def _chunks(self, revs, df=None, targetsize=None):
2072 """Obtain decompressed chunks for the specified revisions.
2073
2074 Accepts an iterable of numeric revisions that are assumed to be in
2075 ascending order. Also accepts an optional already-open file handle
2076 to be used for reading. If used, the seek position of the file will
2077 not be preserved.
2078
2079 This function is similar to calling ``self._chunk()`` multiple times,
2080 but is faster.
2081
2082 Returns a list with decompressed data for each requested revision.
2083 """
2084 if not revs:
2085 return []
2086 start = self.start
2087 length = self.length
2088 inline = self._inline
2089 iosize = self._io.size
2090 buffer = util.buffer
2091
2092 l = []
2093 ladd = l.append
2094
2095 if not self._withsparseread:
2096 slicedchunks = (revs,)
2097 else:
2098 slicedchunks = _slicechunk(self, revs, targetsize=targetsize)
2099
2100 for revschunk in slicedchunks:
2101 firstrev = revschunk[0]
2102 # Skip trailing revisions with empty diff
2103 for lastrev in revschunk[::-1]:
2104 if length(lastrev) != 0:
2105 break
2106
2107 try:
2108 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
2109 except OverflowError:
2110 # issue4215 - we can't cache a run of chunks greater than
2111 # 2G on Windows
2112 return [self._chunk(rev, df=df) for rev in revschunk]
2113
2114 decomp = self.decompress
2115 for rev in revschunk:
2116 chunkstart = start(rev)
2117 if inline:
2118 chunkstart += (rev + 1) * iosize
2119 chunklength = length(rev)
2120 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
2121
2122 return l
2123
2124 def _chunkclear(self):
2125 """Clear the raw chunk cache."""
2126 self._chunkcache = (0, '')
2127
2128 def deltaparent(self, rev):
2129 """return deltaparent of the given revision"""
2130 base = self.index[rev][3]
2131 if base == rev:
2132 return nullrev
2133 elif self._generaldelta:
2134 return base
2135 else:
2136 return rev - 1
2137
2138 def issnapshot(self, rev):
2139 """tells whether rev is a snapshot
2140 """
2141 if rev == nullrev:
2142 return True
2143 deltap = self.deltaparent(rev)
2144 if deltap == nullrev:
2145 return True
2146 p1, p2 = self.parentrevs(rev)
2147 if deltap in (p1, p2):
2148 return False
2149 return self.issnapshot(deltap)
2150
2151 def snapshotdepth(self, rev):
2152 """number of snapshot in the chain before this one"""
2153 if not self.issnapshot(rev):
2154 raise ProgrammingError('revision %d not a snapshot')
2155 return len(self._deltachain(rev)[0]) - 1
2156
2157 def revdiff(self, rev1, rev2):
2158 """return or calculate a delta between two revisions
2159
2160 The delta calculated is in binary form and is intended to be written to
2161 revlog data directly. So this function needs raw revision data.
2162 """
2163 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2164 return bytes(self._chunk(rev2))
2165
2166 return mdiff.textdiff(self.revision(rev1, raw=True),
2167 self.revision(rev2, raw=True))
2168
2169 def revision(self, nodeorrev, _df=None, raw=False):
2170 """return an uncompressed revision of a given node or revision
2171 number.
2172
2173 _df - an existing file handle to read from. (internal-only)
2174 raw - an optional argument specifying if the revision data is to be
2175 treated as raw data when applying flag transforms. 'raw' should be set
2176 to True when generating changegroups or in debug commands.
2177 """
2178 if isinstance(nodeorrev, int):
2179 rev = nodeorrev
2180 node = self.node(rev)
2181 else:
2182 node = nodeorrev
2183 rev = None
2184
2185 cachedrev = None
2186 flags = None
2187 rawtext = None
2188 if node == nullid:
2189 return ""
2190 if self._cache:
2191 if self._cache[0] == node:
2192 # _cache only stores rawtext
2193 if raw:
2194 return self._cache[2]
2195 # duplicated, but good for perf
2196 if rev is None:
2197 rev = self.rev(node)
2198 if flags is None:
2199 flags = self.flags(rev)
2200 # no extra flags set, no flag processor runs, text = rawtext
2201 if flags == REVIDX_DEFAULT_FLAGS:
2202 return self._cache[2]
2203 # rawtext is reusable. need to run flag processor
2204 rawtext = self._cache[2]
2205
2206 cachedrev = self._cache[1]
2207
2208 # look up what we need to read
2209 if rawtext is None:
2210 if rev is None:
2211 rev = self.rev(node)
2212
2213 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2214 if stopped:
2215 rawtext = self._cache[2]
2216
2217 # drop cache to save memory
2218 self._cache = None
2219
2220 targetsize = None
2221 rawsize = self.index[rev][2]
2222 if 0 <= rawsize:
2223 targetsize = 4 * rawsize
2224
2225 bins = self._chunks(chain, df=_df, targetsize=targetsize)
2226 if rawtext is None:
2227 rawtext = bytes(bins[0])
2228 bins = bins[1:]
2229
2230 rawtext = mdiff.patches(rawtext, bins)
2231 self._cache = (node, rev, rawtext)
2232
2233 if flags is None:
2234 if rev is None:
2235 rev = self.rev(node)
2236 flags = self.flags(rev)
2237
2238 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
2239 if validatehash:
2240 self.checkhash(text, node, rev=rev)
2241
2242 return text
2243
2244 def hash(self, text, p1, p2):
2245 """Compute a node hash.
2246
2247 Available as a function so that subclasses can replace the hash
2248 as needed.
2249 """
2250 return hash(text, p1, p2)
2251
2252 def _processflags(self, text, flags, operation, raw=False):
2253 """Inspect revision data flags and applies transforms defined by
2254 registered flag processors.
2255
2256 ``text`` - the revision data to process
2257 ``flags`` - the revision flags
2258 ``operation`` - the operation being performed (read or write)
2259 ``raw`` - an optional argument describing if the raw transform should be
2260 applied.
2261
2262 This method processes the flags in the order (or reverse order if
2263 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
2264 flag processors registered for present flags. The order of flags defined
2265 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
2266
2267 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
2268 processed text and ``validatehash`` is a bool indicating whether the
2269 returned text should be checked for hash integrity.
2270
2271 Note: If the ``raw`` argument is set, it has precedence over the
2272 operation and will only update the value of ``validatehash``.
2273 """
2274 # fast path: no flag processors will run
2275 if flags == 0:
2276 return text, True
2277 if not operation in ('read', 'write'):
2278 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
2279 # Check all flags are known.
2280 if flags & ~REVIDX_KNOWN_FLAGS:
2281 raise RevlogError(_("incompatible revision flag '%#x'") %
2282 (flags & ~REVIDX_KNOWN_FLAGS))
2283 validatehash = True
2284 # Depending on the operation (read or write), the order might be
2285 # reversed due to non-commutative transforms.
2286 orderedflags = REVIDX_FLAGS_ORDER
2287 if operation == 'write':
2288 orderedflags = reversed(orderedflags)
2289
2290 for flag in orderedflags:
2291 # If a flagprocessor has been registered for a known flag, apply the
2292 # related operation transform and update result tuple.
2293 if flag & flags:
2294 vhash = True
2295
2296 if flag not in _flagprocessors:
2297 message = _("missing processor for flag '%#x'") % (flag)
2298 raise RevlogError(message)
2299
2300 processor = _flagprocessors[flag]
2301 if processor is not None:
2302 readtransform, writetransform, rawtransform = processor
2303
2304 if raw:
2305 vhash = rawtransform(self, text)
2306 elif operation == 'read':
2307 text, vhash = readtransform(self, text)
2308 else: # write operation
2309 text, vhash = writetransform(self, text)
2310 validatehash = validatehash and vhash
2311
2312 return text, validatehash
2313
2314 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2315 """Check node hash integrity.
2316
2317 Available as a function so that subclasses can extend hash mismatch
2318 behaviors as needed.
2319 """
2320 try:
2321 if p1 is None and p2 is None:
2322 p1, p2 = self.parents(node)
2323 if node != self.hash(text, p1, p2):
2324 revornode = rev
2325 if revornode is None:
2326 revornode = templatefilters.short(hex(node))
2327 raise RevlogError(_("integrity check failed on %s:%s")
2328 % (self.indexfile, pycompat.bytestr(revornode)))
2329 except RevlogError:
2330 if self._censorable and _censoredtext(text):
2331 raise error.CensoredNodeError(self.indexfile, node, text)
2332 raise
2333
2334 def _enforceinlinesize(self, tr, fp=None):
2335 """Check if the revlog is too big for inline and convert if so.
2336
2337 This should be called after revisions are added to the revlog. If the
2338 revlog has grown too large to be an inline revlog, it will convert it
2339 to use multiple index and data files.
2340 """
2341 tiprev = len(self) - 1
2342 if (not self._inline or
2343 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
2344 return
2345
2346 trinfo = tr.find(self.indexfile)
2347 if trinfo is None:
2348 raise RevlogError(_("%s not found in the transaction")
2349 % self.indexfile)
2350
2351 trindex = trinfo[2]
2352 if trindex is not None:
2353 dataoff = self.start(trindex)
2354 else:
2355 # revlog was stripped at start of transaction, use all leftover data
2356 trindex = len(self) - 1
2357 dataoff = self.end(tiprev)
2358
2359 tr.add(self.datafile, dataoff)
2360
2361 if fp:
2362 fp.flush()
2363 fp.close()
2364
2365 with self._datafp('w') as df:
2366 for r in self:
2367 df.write(self._getsegmentforrevs(r, r)[1])
2368
2369 with self._indexfp('w') as fp:
2370 self.version &= ~FLAG_INLINE_DATA
2371 self._inline = False
2372 io = self._io
2373 for i in self:
2374 e = io.packentry(self.index[i], self.node, self.version, i)
2375 fp.write(e)
2376
2377 # the temp file replace the real index when we exit the context
2378 # manager
2379
2380 tr.replace(self.indexfile, trindex * self._io.size)
2381 self._chunkclear()
2382
2383 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
2384 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
2385 """add a revision to the log
2386
2387 text - the revision data to add
2388 transaction - the transaction object used for rollback
2389 link - the linkrev data to add
2390 p1, p2 - the parent nodeids of the revision
2391 cachedelta - an optional precomputed delta
2392 node - nodeid of revision; typically node is not specified, and it is
2393 computed by default as hash(text, p1, p2), however subclasses might
2394 use different hashing method (and override checkhash() in such case)
2395 flags - the known flags to set on the revision
2396 deltacomputer - an optional _deltacomputer instance shared between
2397 multiple calls
2398 """
2399 if link == nullrev:
2400 raise RevlogError(_("attempted to add linkrev -1 to %s")
2401 % self.indexfile)
2402
2403 if flags:
2404 node = node or self.hash(text, p1, p2)
2405
2406 rawtext, validatehash = self._processflags(text, flags, 'write')
2407
2408 # If the flag processor modifies the revision data, ignore any provided
2409 # cachedelta.
2410 if rawtext != text:
2411 cachedelta = None
2412
2413 if len(rawtext) > _maxentrysize:
2414 raise RevlogError(
2415 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
2416 % (self.indexfile, len(rawtext)))
2417
2418 node = node or self.hash(rawtext, p1, p2)
2419 if node in self.nodemap:
2420 return node
2421
2422 if validatehash:
2423 self.checkhash(rawtext, node, p1=p1, p2=p2)
2424
2425 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
2426 flags, cachedelta=cachedelta,
2427 deltacomputer=deltacomputer)
2428
2429 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
2430 cachedelta=None, deltacomputer=None):
2431 """add a raw revision with known flags, node and parents
2432 useful when reusing a revision not stored in this revlog (ex: received
2433 over wire, or read from an external bundle).
2434 """
2435 dfh = None
2436 if not self._inline:
2437 dfh = self._datafp("a+")
2438 ifh = self._indexfp("a+")
2439 try:
2440 return self._addrevision(node, rawtext, transaction, link, p1, p2,
2441 flags, cachedelta, ifh, dfh,
2442 deltacomputer=deltacomputer)
2443 finally:
2444 if dfh:
2445 dfh.close()
2446 ifh.close()
2447
2448 def compress(self, data):
2449 """Generate a possibly-compressed representation of data."""
2450 if not data:
2451 return '', data
2452
2453 compressed = self._compressor.compress(data)
2454
2455 if compressed:
2456 # The revlog compressor added the header in the returned data.
2457 return '', compressed
2458
2459 if data[0:1] == '\0':
2460 return '', data
2461 return 'u', data
2462
2463 def decompress(self, data):
2464 """Decompress a revlog chunk.
2465
2466 The chunk is expected to begin with a header identifying the
2467 format type so it can be routed to an appropriate decompressor.
2468 """
2469 if not data:
2470 return data
2471
2472 # Revlogs are read much more frequently than they are written and many
2473 # chunks only take microseconds to decompress, so performance is
2474 # important here.
2475 #
2476 # We can make a few assumptions about revlogs:
2477 #
2478 # 1) the majority of chunks will be compressed (as opposed to inline
2479 # raw data).
2480 # 2) decompressing *any* data will likely by at least 10x slower than
2481 # returning raw inline data.
2482 # 3) we want to prioritize common and officially supported compression
2483 # engines
2484 #
2485 # It follows that we want to optimize for "decompress compressed data
2486 # when encoded with common and officially supported compression engines"
2487 # case over "raw data" and "data encoded by less common or non-official
2488 # compression engines." That is why we have the inline lookup first
2489 # followed by the compengines lookup.
2490 #
2491 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2492 # compressed chunks. And this matters for changelog and manifest reads.
2493 t = data[0:1]
2494
2495 if t == 'x':
2496 try:
2497 return _zlibdecompress(data)
2498 except zlib.error as e:
2499 raise RevlogError(_('revlog decompress error: %s') %
2500 stringutil.forcebytestr(e))
2501 # '\0' is more common than 'u' so it goes first.
2502 elif t == '\0':
2503 return data
2504 elif t == 'u':
2505 return util.buffer(data, 1)
2506
2507 try:
2508 compressor = self._decompressors[t]
2509 except KeyError:
2510 try:
2511 engine = util.compengines.forrevlogheader(t)
2512 compressor = engine.revlogcompressor()
2513 self._decompressors[t] = compressor
2514 except KeyError:
2515 raise RevlogError(_('unknown compression type %r') % t)
2516
2517 return compressor.decompress(data)
2518
2519 def _isgooddeltainfo(self, deltainfo, revinfo):
2520 """Returns True if the given delta is good. Good means that it is within
2521 the disk span, disk size, and chain length bounds that we know to be
2522 performant."""
2523 if deltainfo is None:
2524 return False
2525
2526 # - 'deltainfo.distance' is the distance from the base revision --
2527 # bounding it limits the amount of I/O we need to do.
2528 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
2529 # deltas we need to apply -- bounding it limits the amount of CPU
2530 # we consume.
2531
2532 if self._sparserevlog:
2533 # As sparse-read will be used, we can consider that the distance,
2534 # instead of being the span of the whole chunk,
2535 # is the span of the largest read chunk
2536 base = deltainfo.base
2537
2538 if base != nullrev:
2539 deltachain = self._deltachain(base)[0]
2540 else:
2541 deltachain = []
2542
2543 # search for the first non-snapshot revision
2544 for idx, r in enumerate(deltachain):
2545 if not self.issnapshot(r):
2546 break
2547 deltachain = deltachain[idx:]
2548 chunks = _slicechunk(self, deltachain, deltainfo)
2549 all_span = [_segmentspan(self, revs, deltainfo) for revs in chunks]
2550 distance = max(all_span)
2551 else:
2552 distance = deltainfo.distance
2553
2554 textlen = revinfo.textlen
2555 defaultmax = textlen * 4
2556 maxdist = self._maxdeltachainspan
2557 if not maxdist:
2558 maxdist = distance # ensure the conditional pass
2559 maxdist = max(maxdist, defaultmax)
2560 if self._sparserevlog and maxdist < self._srmingapsize:
2561 # In multiple place, we are ignoring irrelevant data range below a
2562 # certain size. Be also apply this tradeoff here and relax span
2563 # constraint for small enought content.
2564 maxdist = self._srmingapsize
2565
2566 # Bad delta from read span:
2567 #
2568 # If the span of data read is larger than the maximum allowed.
2569 if maxdist < distance:
2570 return False
2571
2572 # Bad delta from new delta size:
2573 #
2574 # If the delta size is larger than the target text, storing the
2575 # delta will be inefficient.
2576 if textlen < deltainfo.deltalen:
2577 return False
2578
2579 # Bad delta from cumulated payload size:
2580 #
2581 # If the sum of delta get larger than K * target text length.
2582 if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
2583 return False
2584
2585 # Bad delta from chain length:
2586 #
2587 # If the number of delta in the chain gets too high.
2588 if self._maxchainlen and self._maxchainlen < deltainfo.chainlen:
2589 return False
2590
2591 # bad delta from intermediate snapshot size limit
2592 #
2593 # If an intermediate snapshot size is higher than the limit. The
2594 # limit exist to prevent endless chain of intermediate delta to be
2595 # created.
2596 if (deltainfo.snapshotdepth is not None and
2597 (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen):
2598 return False
2599
2600 # bad delta if new intermediate snapshot is larger than the previous
2601 # snapshot
2602 if (deltainfo.snapshotdepth
2603 and self.length(deltainfo.base) < deltainfo.deltalen):
2604 return False
2605
2606 return True
2607
2608 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2609 cachedelta, ifh, dfh, alwayscache=False,
2610 deltacomputer=None):
2611 """internal function to add revisions to the log
2612
2613 see addrevision for argument descriptions.
2614
2615 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2616
2617 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2618 be used.
2619
2620 invariants:
2621 - rawtext is optional (can be None); if not set, cachedelta must be set.
2622 if both are set, they must correspond to each other.
2623 """
2624 if node == nullid:
2625 raise RevlogError(_("%s: attempt to add null revision") %
2626 (self.indexfile))
2627 if node == wdirid or node in wdirfilenodeids:
2628 raise RevlogError(_("%s: attempt to add wdir revision") %
2629 (self.indexfile))
2630
2631 if self._inline:
2632 fh = ifh
2633 else:
2634 fh = dfh
2635
2636 btext = [rawtext]
2637
2638 curr = len(self)
2639 prev = curr - 1
2640 offset = self.end(prev)
2641 p1r, p2r = self.rev(p1), self.rev(p2)
2642
2643 # full versions are inserted when the needed deltas
2644 # become comparable to the uncompressed text
2645 if rawtext is None:
2646 # need rawtext size, before changed by flag processors, which is
2647 # the non-raw size. use revlog explicitly to avoid filelog's extra
2648 # logic that might remove metadata size.
2649 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2650 cachedelta[1])
2651 else:
2652 textlen = len(rawtext)
2653
2654 if deltacomputer is None:
2655 deltacomputer = _deltacomputer(self)
2656
2657 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2658
2659 # no delta for flag processor revision (see "candelta" for why)
2660 # not calling candelta since only one revision needs test, also to
2661 # avoid overhead fetching flags again.
2662 if flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
2663 deltainfo = None
2664 else:
2665 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2666
2667 if deltainfo is not None:
2668 base = deltainfo.base
2669 chainbase = deltainfo.chainbase
2670 data = deltainfo.data
2671 l = deltainfo.deltalen
2672 else:
2673 rawtext = deltacomputer.buildtext(revinfo, fh)
2674 data = self.compress(rawtext)
2675 l = len(data[1]) + len(data[0])
2676 base = chainbase = curr
2677
2678 e = (offset_type(offset, flags), l, textlen,
2679 base, link, p1r, p2r, node)
2680 self.index.append(e)
2681 self.nodemap[node] = curr
2682
2683 entry = self._io.packentry(e, self.node, self.version, curr)
2684 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
2685
2686 if alwayscache and rawtext is None:
2687 rawtext = deltacomputer.buildtext(revinfo, fh)
2688
2689 if type(rawtext) == bytes: # only accept immutable objects
2690 self._cache = (node, curr, rawtext)
2691 self._chainbasecache[curr] = chainbase
2692 return node
2693
2694 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2695 # Files opened in a+ mode have inconsistent behavior on various
2696 # platforms. Windows requires that a file positioning call be made
2697 # when the file handle transitions between reads and writes. See
2698 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2699 # platforms, Python or the platform itself can be buggy. Some versions
2700 # of Solaris have been observed to not append at the end of the file
2701 # if the file was seeked to before the end. See issue4943 for more.
2702 #
2703 # We work around this issue by inserting a seek() before writing.
2704 # Note: This is likely not necessary on Python 3.
2705 ifh.seek(0, os.SEEK_END)
2706 if dfh:
2707 dfh.seek(0, os.SEEK_END)
2708
2709 curr = len(self) - 1
2710 if not self._inline:
2711 transaction.add(self.datafile, offset)
2712 transaction.add(self.indexfile, curr * len(entry))
2713 if data[0]:
2714 dfh.write(data[0])
2715 dfh.write(data[1])
2716 ifh.write(entry)
2717 else:
2718 offset += curr * self._io.size
2719 transaction.add(self.indexfile, offset, curr)
2720 ifh.write(entry)
2721 ifh.write(data[0])
2722 ifh.write(data[1])
2723 self._enforceinlinesize(transaction, ifh)
2724
2725 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2726 """
2727 add a delta group
2728
2729 given a set of deltas, add them to the revision log. the
2730 first delta is against its parent, which should be in our
2731 log, the rest are against the previous delta.
2732
2733 If ``addrevisioncb`` is defined, it will be called with arguments of
2734 this revlog and the node that was added.
2735 """
2736
2737 nodes = []
2738
2739 r = len(self)
2740 end = 0
2741 if r:
2742 end = self.end(r - 1)
2743 ifh = self._indexfp("a+")
2744 isize = r * self._io.size
2745 if self._inline:
2746 transaction.add(self.indexfile, end + isize, r)
2747 dfh = None
2748 else:
2749 transaction.add(self.indexfile, isize, r)
2750 transaction.add(self.datafile, end)
2751 dfh = self._datafp("a+")
2752 def flush():
2753 if dfh:
2754 dfh.flush()
2755 ifh.flush()
2756 try:
2757 deltacomputer = _deltacomputer(self)
2758 # loop through our set of deltas
2759 for data in deltas:
2760 node, p1, p2, linknode, deltabase, delta, flags = data
2761 link = linkmapper(linknode)
2762 flags = flags or REVIDX_DEFAULT_FLAGS
2763
2764 nodes.append(node)
2765
2766 if node in self.nodemap:
2767 # this can happen if two branches make the same change
2768 continue
2769
2770 for p in (p1, p2):
2771 if p not in self.nodemap:
2772 raise LookupError(p, self.indexfile,
2773 _('unknown parent'))
2774
2775 if deltabase not in self.nodemap:
2776 raise LookupError(deltabase, self.indexfile,
2777 _('unknown delta base'))
2778
2779 baserev = self.rev(deltabase)
2780
2781 if baserev != nullrev and self.iscensored(baserev):
2782 # if base is censored, delta must be full replacement in a
2783 # single patch operation
2784 hlen = struct.calcsize(">lll")
2785 oldlen = self.rawsize(baserev)
2786 newlen = len(delta) - hlen
2787 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2788 raise error.CensoredBaseError(self.indexfile,
2789 self.node(baserev))
2790
2791 if not flags and self._peek_iscensored(baserev, delta, flush):
2792 flags |= REVIDX_ISCENSORED
2793
2794 # We assume consumers of addrevisioncb will want to retrieve
2795 # the added revision, which will require a call to
2796 # revision(). revision() will fast path if there is a cache
2797 # hit. So, we tell _addrevision() to always cache in this case.
2798 # We're only using addgroup() in the context of changegroup
2799 # generation so the revision data can always be handled as raw
2800 # by the flagprocessor.
2801 self._addrevision(node, None, transaction, link,
2802 p1, p2, flags, (baserev, delta),
2803 ifh, dfh,
2804 alwayscache=bool(addrevisioncb),
2805 deltacomputer=deltacomputer)
2806
2807 if addrevisioncb:
2808 addrevisioncb(self, node)
2809
2810 if not dfh and not self._inline:
2811 # addrevision switched from inline to conventional
2812 # reopen the index
2813 ifh.close()
2814 dfh = self._datafp("a+")
2815 ifh = self._indexfp("a+")
2816 finally:
2817 if dfh:
2818 dfh.close()
2819 ifh.close()
2820
2821 return nodes
2822
2823 def iscensored(self, rev):
2824 """Check if a file revision is censored."""
2825 if not self._censorable:
2826 return False
2827
2828 return self.flags(rev) & REVIDX_ISCENSORED
2829
2830 def _peek_iscensored(self, baserev, delta, flush):
2831 """Quickly check if a delta produces a censored revision."""
2832 if not self._censorable:
2833 return False
2834
2835 # Fragile heuristic: unless new file meta keys are added alphabetically
2836 # preceding "censored", all censored revisions are prefixed by
2837 # "\1\ncensored:". A delta producing such a censored revision must be a
2838 # full-replacement delta, so we inspect the first and only patch in the
2839 # delta for this prefix.
2840 hlen = struct.calcsize(">lll")
2841 if len(delta) <= hlen:
2842 return False
2843
2844 oldlen = self.rawsize(baserev)
2845 newlen = len(delta) - hlen
2846 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2847 return False
2848
2849 add = "\1\ncensored:"
2850 addlen = len(add)
2851 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2852
2853 def getstrippoint(self, minlink):
2854 """find the minimum rev that must be stripped to strip the linkrev
2855
2856 Returns a tuple containing the minimum rev and a set of all revs that
2857 have linkrevs that will be broken by this strip.
2858 """
2859 brokenrevs = set()
2860 strippoint = len(self)
2861
2862 heads = {}
2863 futurelargelinkrevs = set()
2864 for head in self.headrevs():
2865 headlinkrev = self.linkrev(head)
2866 heads[head] = headlinkrev
2867 if headlinkrev >= minlink:
2868 futurelargelinkrevs.add(headlinkrev)
2869
2870 # This algorithm involves walking down the rev graph, starting at the
2871 # heads. Since the revs are topologically sorted according to linkrev,
2872 # once all head linkrevs are below the minlink, we know there are
2873 # no more revs that could have a linkrev greater than minlink.
2874 # So we can stop walking.
2875 while futurelargelinkrevs:
2876 strippoint -= 1
2877 linkrev = heads.pop(strippoint)
2878
2879 if linkrev < minlink:
2880 brokenrevs.add(strippoint)
2881 else:
2882 futurelargelinkrevs.remove(linkrev)
2883
2884 for p in self.parentrevs(strippoint):
2885 if p != nullrev:
2886 plinkrev = self.linkrev(p)
2887 heads[p] = plinkrev
2888 if plinkrev >= minlink:
2889 futurelargelinkrevs.add(plinkrev)
2890
2891 return strippoint, brokenrevs
2892
2893 def strip(self, minlink, transaction):
2894 """truncate the revlog on the first revision with a linkrev >= minlink
2895
2896 This function is called when we're stripping revision minlink and
2897 its descendants from the repository.
2898
2899 We have to remove all revisions with linkrev >= minlink, because
2900 the equivalent changelog revisions will be renumbered after the
2901 strip.
2902
2903 So we truncate the revlog on the first of these revisions, and
2904 trust that the caller has saved the revisions that shouldn't be
2905 removed and that it'll re-add them after this truncation.
2906 """
2907 if len(self) == 0:
2908 return
2909
2910 rev, _ = self.getstrippoint(minlink)
2911 if rev == len(self):
2912 return
2913
2914 # first truncate the files on disk
2915 end = self.start(rev)
2916 if not self._inline:
2917 transaction.add(self.datafile, end)
2918 end = rev * self._io.size
2919 else:
2920 end += rev * self._io.size
2921
2922 transaction.add(self.indexfile, end)
2923
2924 # then reset internal state in memory to forget those revisions
2925 self._cache = None
2926 self._chaininfocache = {}
2927 self._chunkclear()
2928 for x in pycompat.xrange(rev, len(self)):
2929 del self.nodemap[self.node(x)]
2930
2931 del self.index[rev:-1]
2932 self._nodepos = None
2933
2934 def checksize(self):
2935 expected = 0
2936 if len(self):
2937 expected = max(0, self.end(len(self) - 1))
2938
2939 try:
2940 with self._datafp() as f:
2941 f.seek(0, 2)
2942 actual = f.tell()
2943 dd = actual - expected
2944 except IOError as inst:
2945 if inst.errno != errno.ENOENT:
2946 raise
2947 dd = 0
2948
2949 try:
2950 f = self.opener(self.indexfile)
2951 f.seek(0, 2)
2952 actual = f.tell()
2953 f.close()
2954 s = self._io.size
2955 i = max(0, actual // s)
2956 di = actual - (i * s)
2957 if self._inline:
2958 databytes = 0
2959 for r in self:
2960 databytes += max(0, self.length(r))
2961 dd = 0
2962 di = actual - len(self) * s - databytes
2963 except IOError as inst:
2964 if inst.errno != errno.ENOENT:
2965 raise
2966 di = 0
2967
2968 return (dd, di)
2969
2970 def files(self):
2971 res = [self.indexfile]
2972 if not self._inline:
2973 res.append(self.datafile)
2974 return res
2975
2976 def emitrevisiondeltas(self, requests):
2977 frev = self.rev
2978
2979 prevrev = None
2980 for request in requests:
2981 node = request.node
2982 rev = frev(node)
2983
2984 if prevrev is None:
2985 prevrev = self.index[rev][5]
2986
2987 # Requesting a full revision.
2988 if request.basenode == nullid:
2989 baserev = nullrev
2990 # Requesting an explicit revision.
2991 elif request.basenode is not None:
2992 baserev = frev(request.basenode)
2993 # Allowing us to choose.
2994 else:
2995 p1rev, p2rev = self.parentrevs(rev)
2996 deltaparentrev = self.deltaparent(rev)
2997
2998 # Avoid sending full revisions when delta parent is null. Pick
2999 # prev in that case. It's tempting to pick p1 in this case, as
3000 # p1 will be smaller in the common case. However, computing a
3001 # delta against p1 may require resolving the raw text of p1,
3002 # which could be expensive. The revlog caches should have prev
3003 # cached, meaning less CPU for delta generation. There is
3004 # likely room to add a flag and/or config option to control this
3005 # behavior.
3006 if deltaparentrev == nullrev and self._storedeltachains:
3007 baserev = prevrev
3008
3009 # Revlog is configured to use full snapshot for a reason.
3010 # Stick to full snapshot.
3011 elif deltaparentrev == nullrev:
3012 baserev = nullrev
3013
3014 # Pick previous when we can't be sure the base is available
3015 # on consumer.
3016 elif deltaparentrev not in (p1rev, p2rev, prevrev):
3017 baserev = prevrev
3018 else:
3019 baserev = deltaparentrev
3020
3021 if baserev != nullrev and not self.candelta(baserev, rev):
3022 baserev = nullrev
3023
3024 revision = None
3025 delta = None
3026 baserevisionsize = None
3027
3028 if self.iscensored(baserev) or self.iscensored(rev):
3029 try:
3030 revision = self.revision(node, raw=True)
3031 except error.CensoredNodeError as e:
3032 revision = e.tombstone
3033
3034 if baserev != nullrev:
3035 baserevisionsize = self.rawsize(baserev)
3036
3037 elif baserev == nullrev:
3038 revision = self.revision(node, raw=True)
3039 else:
3040 delta = self.revdiff(baserev, rev)
3041
3042 extraflags = REVIDX_ELLIPSIS if request.ellipsis else 0
3043
3044 yield revlogrevisiondelta(
3045 node=node,
3046 p1node=request.p1node,
3047 p2node=request.p2node,
3048 linknode=request.linknode,
3049 basenode=self.node(baserev),
3050 flags=self.flags(rev) | extraflags,
3051 baserevisionsize=baserevisionsize,
3052 revision=revision,
3053 delta=delta)
3054
3055 prevrev = rev
3056
3057 DELTAREUSEALWAYS = 'always'
3058 DELTAREUSESAMEREVS = 'samerevs'
3059 DELTAREUSENEVER = 'never'
3060
3061 DELTAREUSEFULLADD = 'fulladd'
3062
3063 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
3064
3065 def clone(self, tr, destrevlog, addrevisioncb=None,
3066 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
3067 """Copy this revlog to another, possibly with format changes.
3068
3069 The destination revlog will contain the same revisions and nodes.
3070 However, it may not be bit-for-bit identical due to e.g. delta encoding
3071 differences.
3072
3073 The ``deltareuse`` argument control how deltas from the existing revlog
3074 are preserved in the destination revlog. The argument can have the
3075 following values:
3076
3077 DELTAREUSEALWAYS
3078 Deltas will always be reused (if possible), even if the destination
3079 revlog would not select the same revisions for the delta. This is the
3080 fastest mode of operation.
3081 DELTAREUSESAMEREVS
3082 Deltas will be reused if the destination revlog would pick the same
3083 revisions for the delta. This mode strikes a balance between speed
3084 and optimization.
3085 DELTAREUSENEVER
3086 Deltas will never be reused. This is the slowest mode of execution.
3087 This mode can be used to recompute deltas (e.g. if the diff/delta
3088 algorithm changes).
3089
3090 Delta computation can be slow, so the choice of delta reuse policy can
3091 significantly affect run time.
3092
3093 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
3094 two extremes. Deltas will be reused if they are appropriate. But if the
3095 delta could choose a better revision, it will do so. This means if you
3096 are converting a non-generaldelta revlog to a generaldelta revlog,
3097 deltas will be recomputed if the delta's parent isn't a parent of the
3098 revision.
3099
3100 In addition to the delta policy, the ``deltabothparents`` argument
3101 controls whether to compute deltas against both parents for merges.
3102 By default, the current default is used.
3103 """
3104 if deltareuse not in self.DELTAREUSEALL:
3105 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
3106
3107 if len(destrevlog):
3108 raise ValueError(_('destination revlog is not empty'))
3109
3110 if getattr(self, 'filteredrevs', None):
3111 raise ValueError(_('source revlog has filtered revisions'))
3112 if getattr(destrevlog, 'filteredrevs', None):
3113 raise ValueError(_('destination revlog has filtered revisions'))
3114
3115 # lazydeltabase controls whether to reuse a cached delta, if possible.
3116 oldlazydeltabase = destrevlog._lazydeltabase
3117 oldamd = destrevlog._deltabothparents
3118
3119 try:
3120 if deltareuse == self.DELTAREUSEALWAYS:
3121 destrevlog._lazydeltabase = True
3122 elif deltareuse == self.DELTAREUSESAMEREVS:
3123 destrevlog._lazydeltabase = False
3124
3125 destrevlog._deltabothparents = deltabothparents or oldamd
3126
3127 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
3128 self.DELTAREUSESAMEREVS)
3129
3130 deltacomputer = _deltacomputer(destrevlog)
3131 index = self.index
3132 for rev in self:
3133 entry = index[rev]
3134
3135 # Some classes override linkrev to take filtered revs into
3136 # account. Use raw entry from index.
3137 flags = entry[0] & 0xffff
3138 linkrev = entry[4]
3139 p1 = index[entry[5]][7]
3140 p2 = index[entry[6]][7]
3141 node = entry[7]
3142
3143 # (Possibly) reuse the delta from the revlog if allowed and
3144 # the revlog chunk is a delta.
3145 cachedelta = None
3146 rawtext = None
3147 if populatecachedelta:
3148 dp = self.deltaparent(rev)
3149 if dp != nullrev:
3150 cachedelta = (dp, bytes(self._chunk(rev)))
3151
3152 if not cachedelta:
3153 rawtext = self.revision(rev, raw=True)
3154
3155
3156 if deltareuse == self.DELTAREUSEFULLADD:
3157 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
3158 cachedelta=cachedelta,
3159 node=node, flags=flags,
3160 deltacomputer=deltacomputer)
3161 else:
3162 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
3163 checkambig=False)
3164 dfh = None
3165 if not destrevlog._inline:
3166 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
3167 try:
3168 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
3169 p2, flags, cachedelta, ifh, dfh,
3170 deltacomputer=deltacomputer)
3171 finally:
3172 if dfh:
3173 dfh.close()
3174 ifh.close()
3175
3176 if addrevisioncb:
3177 addrevisioncb(self, rev, node)
3178 finally:
3179 destrevlog._lazydeltabase = oldlazydeltabase
3180 destrevlog._deltabothparents = oldamd
General Comments 0
You need to be logged in to leave comments. Login now