##// END OF EJS Templates
wireprotov2peer: stream decoded responses...
Gregory Szorc -
r39597:d06834e0 default
parent child Browse files
Show More
@@ -1,3364 +1,3364 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 . import (
35 from . import (
36 bundle2,
36 bundle2,
37 changegroup,
37 changegroup,
38 cmdutil,
38 cmdutil,
39 color,
39 color,
40 context,
40 context,
41 dagparser,
41 dagparser,
42 encoding,
42 encoding,
43 error,
43 error,
44 exchange,
44 exchange,
45 extensions,
45 extensions,
46 filemerge,
46 filemerge,
47 filesetlang,
47 filesetlang,
48 formatter,
48 formatter,
49 hg,
49 hg,
50 httppeer,
50 httppeer,
51 localrepo,
51 localrepo,
52 lock as lockmod,
52 lock as lockmod,
53 logcmdutil,
53 logcmdutil,
54 merge as mergemod,
54 merge as mergemod,
55 obsolete,
55 obsolete,
56 obsutil,
56 obsutil,
57 phases,
57 phases,
58 policy,
58 policy,
59 pvec,
59 pvec,
60 pycompat,
60 pycompat,
61 registrar,
61 registrar,
62 repair,
62 repair,
63 revlog,
63 revlog,
64 revset,
64 revset,
65 revsetlang,
65 revsetlang,
66 scmutil,
66 scmutil,
67 setdiscovery,
67 setdiscovery,
68 simplemerge,
68 simplemerge,
69 sshpeer,
69 sshpeer,
70 sslutil,
70 sslutil,
71 streamclone,
71 streamclone,
72 templater,
72 templater,
73 treediscovery,
73 treediscovery,
74 upgrade,
74 upgrade,
75 url as urlmod,
75 url as urlmod,
76 util,
76 util,
77 vfs as vfsmod,
77 vfs as vfsmod,
78 wireprotoframing,
78 wireprotoframing,
79 wireprotoserver,
79 wireprotoserver,
80 wireprotov2peer,
80 wireprotov2peer,
81 )
81 )
82 from .utils import (
82 from .utils import (
83 cborutil,
83 cborutil,
84 dateutil,
84 dateutil,
85 procutil,
85 procutil,
86 stringutil,
86 stringutil,
87 )
87 )
88
88
89 from .revlogutils import (
89 from .revlogutils import (
90 deltas as deltautil
90 deltas as deltautil
91 )
91 )
92
92
93 release = lockmod.release
93 release = lockmod.release
94
94
95 command = registrar.command()
95 command = registrar.command()
96
96
97 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
97 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
98 def debugancestor(ui, repo, *args):
98 def debugancestor(ui, repo, *args):
99 """find the ancestor revision of two revisions in a given index"""
99 """find the ancestor revision of two revisions in a given index"""
100 if len(args) == 3:
100 if len(args) == 3:
101 index, rev1, rev2 = args
101 index, rev1, rev2 = args
102 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
102 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
103 lookup = r.lookup
103 lookup = r.lookup
104 elif len(args) == 2:
104 elif len(args) == 2:
105 if not repo:
105 if not repo:
106 raise error.Abort(_('there is no Mercurial repository here '
106 raise error.Abort(_('there is no Mercurial repository here '
107 '(.hg not found)'))
107 '(.hg not found)'))
108 rev1, rev2 = args
108 rev1, rev2 = args
109 r = repo.changelog
109 r = repo.changelog
110 lookup = repo.lookup
110 lookup = repo.lookup
111 else:
111 else:
112 raise error.Abort(_('either two or three arguments required'))
112 raise error.Abort(_('either two or three arguments required'))
113 a = r.ancestor(lookup(rev1), lookup(rev2))
113 a = r.ancestor(lookup(rev1), lookup(rev2))
114 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
114 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
115
115
116 @command('debugapplystreamclonebundle', [], 'FILE')
116 @command('debugapplystreamclonebundle', [], 'FILE')
117 def debugapplystreamclonebundle(ui, repo, fname):
117 def debugapplystreamclonebundle(ui, repo, fname):
118 """apply a stream clone bundle file"""
118 """apply a stream clone bundle file"""
119 f = hg.openpath(ui, fname)
119 f = hg.openpath(ui, fname)
120 gen = exchange.readbundle(ui, f, fname)
120 gen = exchange.readbundle(ui, f, fname)
121 gen.apply(repo)
121 gen.apply(repo)
122
122
123 @command('debugbuilddag',
123 @command('debugbuilddag',
124 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
124 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
125 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
125 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
126 ('n', 'new-file', None, _('add new file at each rev'))],
126 ('n', 'new-file', None, _('add new file at each rev'))],
127 _('[OPTION]... [TEXT]'))
127 _('[OPTION]... [TEXT]'))
128 def debugbuilddag(ui, repo, text=None,
128 def debugbuilddag(ui, repo, text=None,
129 mergeable_file=False,
129 mergeable_file=False,
130 overwritten_file=False,
130 overwritten_file=False,
131 new_file=False):
131 new_file=False):
132 """builds a repo with a given DAG from scratch in the current empty repo
132 """builds a repo with a given DAG from scratch in the current empty repo
133
133
134 The description of the DAG is read from stdin if not given on the
134 The description of the DAG is read from stdin if not given on the
135 command line.
135 command line.
136
136
137 Elements:
137 Elements:
138
138
139 - "+n" is a linear run of n nodes based on the current default parent
139 - "+n" is a linear run of n nodes based on the current default parent
140 - "." is a single node based on the current default parent
140 - "." is a single node based on the current default parent
141 - "$" resets the default parent to null (implied at the start);
141 - "$" resets the default parent to null (implied at the start);
142 otherwise the default parent is always the last node created
142 otherwise the default parent is always the last node created
143 - "<p" sets the default parent to the backref p
143 - "<p" sets the default parent to the backref p
144 - "*p" is a fork at parent p, which is a backref
144 - "*p" is a fork at parent p, which is a backref
145 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
145 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
146 - "/p2" is a merge of the preceding node and p2
146 - "/p2" is a merge of the preceding node and p2
147 - ":tag" defines a local tag for the preceding node
147 - ":tag" defines a local tag for the preceding node
148 - "@branch" sets the named branch for subsequent nodes
148 - "@branch" sets the named branch for subsequent nodes
149 - "#...\\n" is a comment up to the end of the line
149 - "#...\\n" is a comment up to the end of the line
150
150
151 Whitespace between the above elements is ignored.
151 Whitespace between the above elements is ignored.
152
152
153 A backref is either
153 A backref is either
154
154
155 - a number n, which references the node curr-n, where curr is the current
155 - a number n, which references the node curr-n, where curr is the current
156 node, or
156 node, or
157 - the name of a local tag you placed earlier using ":tag", or
157 - the name of a local tag you placed earlier using ":tag", or
158 - empty to denote the default parent.
158 - empty to denote the default parent.
159
159
160 All string valued-elements are either strictly alphanumeric, or must
160 All string valued-elements are either strictly alphanumeric, or must
161 be enclosed in double quotes ("..."), with "\\" as escape character.
161 be enclosed in double quotes ("..."), with "\\" as escape character.
162 """
162 """
163
163
164 if text is None:
164 if text is None:
165 ui.status(_("reading DAG from stdin\n"))
165 ui.status(_("reading DAG from stdin\n"))
166 text = ui.fin.read()
166 text = ui.fin.read()
167
167
168 cl = repo.changelog
168 cl = repo.changelog
169 if len(cl) > 0:
169 if len(cl) > 0:
170 raise error.Abort(_('repository is not empty'))
170 raise error.Abort(_('repository is not empty'))
171
171
172 # determine number of revs in DAG
172 # determine number of revs in DAG
173 total = 0
173 total = 0
174 for type, data in dagparser.parsedag(text):
174 for type, data in dagparser.parsedag(text):
175 if type == 'n':
175 if type == 'n':
176 total += 1
176 total += 1
177
177
178 if mergeable_file:
178 if mergeable_file:
179 linesperrev = 2
179 linesperrev = 2
180 # make a file with k lines per rev
180 # make a file with k lines per rev
181 initialmergedlines = ['%d' % i
181 initialmergedlines = ['%d' % i
182 for i in pycompat.xrange(0, total * linesperrev)]
182 for i in pycompat.xrange(0, total * linesperrev)]
183 initialmergedlines.append("")
183 initialmergedlines.append("")
184
184
185 tags = []
185 tags = []
186 progress = ui.makeprogress(_('building'), unit=_('revisions'),
186 progress = ui.makeprogress(_('building'), unit=_('revisions'),
187 total=total)
187 total=total)
188 with progress, repo.wlock(), repo.lock(), repo.transaction("builddag"):
188 with progress, repo.wlock(), repo.lock(), repo.transaction("builddag"):
189 at = -1
189 at = -1
190 atbranch = 'default'
190 atbranch = 'default'
191 nodeids = []
191 nodeids = []
192 id = 0
192 id = 0
193 progress.update(id)
193 progress.update(id)
194 for type, data in dagparser.parsedag(text):
194 for type, data in dagparser.parsedag(text):
195 if type == 'n':
195 if type == 'n':
196 ui.note(('node %s\n' % pycompat.bytestr(data)))
196 ui.note(('node %s\n' % pycompat.bytestr(data)))
197 id, ps = data
197 id, ps = data
198
198
199 files = []
199 files = []
200 filecontent = {}
200 filecontent = {}
201
201
202 p2 = None
202 p2 = None
203 if mergeable_file:
203 if mergeable_file:
204 fn = "mf"
204 fn = "mf"
205 p1 = repo[ps[0]]
205 p1 = repo[ps[0]]
206 if len(ps) > 1:
206 if len(ps) > 1:
207 p2 = repo[ps[1]]
207 p2 = repo[ps[1]]
208 pa = p1.ancestor(p2)
208 pa = p1.ancestor(p2)
209 base, local, other = [x[fn].data() for x in (pa, p1,
209 base, local, other = [x[fn].data() for x in (pa, p1,
210 p2)]
210 p2)]
211 m3 = simplemerge.Merge3Text(base, local, other)
211 m3 = simplemerge.Merge3Text(base, local, other)
212 ml = [l.strip() for l in m3.merge_lines()]
212 ml = [l.strip() for l in m3.merge_lines()]
213 ml.append("")
213 ml.append("")
214 elif at > 0:
214 elif at > 0:
215 ml = p1[fn].data().split("\n")
215 ml = p1[fn].data().split("\n")
216 else:
216 else:
217 ml = initialmergedlines
217 ml = initialmergedlines
218 ml[id * linesperrev] += " r%i" % id
218 ml[id * linesperrev] += " r%i" % id
219 mergedtext = "\n".join(ml)
219 mergedtext = "\n".join(ml)
220 files.append(fn)
220 files.append(fn)
221 filecontent[fn] = mergedtext
221 filecontent[fn] = mergedtext
222
222
223 if overwritten_file:
223 if overwritten_file:
224 fn = "of"
224 fn = "of"
225 files.append(fn)
225 files.append(fn)
226 filecontent[fn] = "r%i\n" % id
226 filecontent[fn] = "r%i\n" % id
227
227
228 if new_file:
228 if new_file:
229 fn = "nf%i" % id
229 fn = "nf%i" % id
230 files.append(fn)
230 files.append(fn)
231 filecontent[fn] = "r%i\n" % id
231 filecontent[fn] = "r%i\n" % id
232 if len(ps) > 1:
232 if len(ps) > 1:
233 if not p2:
233 if not p2:
234 p2 = repo[ps[1]]
234 p2 = repo[ps[1]]
235 for fn in p2:
235 for fn in p2:
236 if fn.startswith("nf"):
236 if fn.startswith("nf"):
237 files.append(fn)
237 files.append(fn)
238 filecontent[fn] = p2[fn].data()
238 filecontent[fn] = p2[fn].data()
239
239
240 def fctxfn(repo, cx, path):
240 def fctxfn(repo, cx, path):
241 if path in filecontent:
241 if path in filecontent:
242 return context.memfilectx(repo, cx, path,
242 return context.memfilectx(repo, cx, path,
243 filecontent[path])
243 filecontent[path])
244 return None
244 return None
245
245
246 if len(ps) == 0 or ps[0] < 0:
246 if len(ps) == 0 or ps[0] < 0:
247 pars = [None, None]
247 pars = [None, None]
248 elif len(ps) == 1:
248 elif len(ps) == 1:
249 pars = [nodeids[ps[0]], None]
249 pars = [nodeids[ps[0]], None]
250 else:
250 else:
251 pars = [nodeids[p] for p in ps]
251 pars = [nodeids[p] for p in ps]
252 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
252 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
253 date=(id, 0),
253 date=(id, 0),
254 user="debugbuilddag",
254 user="debugbuilddag",
255 extra={'branch': atbranch})
255 extra={'branch': atbranch})
256 nodeid = repo.commitctx(cx)
256 nodeid = repo.commitctx(cx)
257 nodeids.append(nodeid)
257 nodeids.append(nodeid)
258 at = id
258 at = id
259 elif type == 'l':
259 elif type == 'l':
260 id, name = data
260 id, name = data
261 ui.note(('tag %s\n' % name))
261 ui.note(('tag %s\n' % name))
262 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
262 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
263 elif type == 'a':
263 elif type == 'a':
264 ui.note(('branch %s\n' % data))
264 ui.note(('branch %s\n' % data))
265 atbranch = data
265 atbranch = data
266 progress.update(id)
266 progress.update(id)
267
267
268 if tags:
268 if tags:
269 repo.vfs.write("localtags", "".join(tags))
269 repo.vfs.write("localtags", "".join(tags))
270
270
271 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
271 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
272 indent_string = ' ' * indent
272 indent_string = ' ' * indent
273 if all:
273 if all:
274 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
274 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
275 % indent_string)
275 % indent_string)
276
276
277 def showchunks(named):
277 def showchunks(named):
278 ui.write("\n%s%s\n" % (indent_string, named))
278 ui.write("\n%s%s\n" % (indent_string, named))
279 for deltadata in gen.deltaiter():
279 for deltadata in gen.deltaiter():
280 node, p1, p2, cs, deltabase, delta, flags = deltadata
280 node, p1, p2, cs, deltabase, delta, flags = deltadata
281 ui.write("%s%s %s %s %s %s %d\n" %
281 ui.write("%s%s %s %s %s %s %d\n" %
282 (indent_string, hex(node), hex(p1), hex(p2),
282 (indent_string, hex(node), hex(p1), hex(p2),
283 hex(cs), hex(deltabase), len(delta)))
283 hex(cs), hex(deltabase), len(delta)))
284
284
285 chunkdata = gen.changelogheader()
285 chunkdata = gen.changelogheader()
286 showchunks("changelog")
286 showchunks("changelog")
287 chunkdata = gen.manifestheader()
287 chunkdata = gen.manifestheader()
288 showchunks("manifest")
288 showchunks("manifest")
289 for chunkdata in iter(gen.filelogheader, {}):
289 for chunkdata in iter(gen.filelogheader, {}):
290 fname = chunkdata['filename']
290 fname = chunkdata['filename']
291 showchunks(fname)
291 showchunks(fname)
292 else:
292 else:
293 if isinstance(gen, bundle2.unbundle20):
293 if isinstance(gen, bundle2.unbundle20):
294 raise error.Abort(_('use debugbundle2 for this file'))
294 raise error.Abort(_('use debugbundle2 for this file'))
295 chunkdata = gen.changelogheader()
295 chunkdata = gen.changelogheader()
296 for deltadata in gen.deltaiter():
296 for deltadata in gen.deltaiter():
297 node, p1, p2, cs, deltabase, delta, flags = deltadata
297 node, p1, p2, cs, deltabase, delta, flags = deltadata
298 ui.write("%s%s\n" % (indent_string, hex(node)))
298 ui.write("%s%s\n" % (indent_string, hex(node)))
299
299
300 def _debugobsmarkers(ui, part, indent=0, **opts):
300 def _debugobsmarkers(ui, part, indent=0, **opts):
301 """display version and markers contained in 'data'"""
301 """display version and markers contained in 'data'"""
302 opts = pycompat.byteskwargs(opts)
302 opts = pycompat.byteskwargs(opts)
303 data = part.read()
303 data = part.read()
304 indent_string = ' ' * indent
304 indent_string = ' ' * indent
305 try:
305 try:
306 version, markers = obsolete._readmarkers(data)
306 version, markers = obsolete._readmarkers(data)
307 except error.UnknownVersion as exc:
307 except error.UnknownVersion as exc:
308 msg = "%sunsupported version: %s (%d bytes)\n"
308 msg = "%sunsupported version: %s (%d bytes)\n"
309 msg %= indent_string, exc.version, len(data)
309 msg %= indent_string, exc.version, len(data)
310 ui.write(msg)
310 ui.write(msg)
311 else:
311 else:
312 msg = "%sversion: %d (%d bytes)\n"
312 msg = "%sversion: %d (%d bytes)\n"
313 msg %= indent_string, version, len(data)
313 msg %= indent_string, version, len(data)
314 ui.write(msg)
314 ui.write(msg)
315 fm = ui.formatter('debugobsolete', opts)
315 fm = ui.formatter('debugobsolete', opts)
316 for rawmarker in sorted(markers):
316 for rawmarker in sorted(markers):
317 m = obsutil.marker(None, rawmarker)
317 m = obsutil.marker(None, rawmarker)
318 fm.startitem()
318 fm.startitem()
319 fm.plain(indent_string)
319 fm.plain(indent_string)
320 cmdutil.showmarker(fm, m)
320 cmdutil.showmarker(fm, m)
321 fm.end()
321 fm.end()
322
322
323 def _debugphaseheads(ui, data, indent=0):
323 def _debugphaseheads(ui, data, indent=0):
324 """display version and markers contained in 'data'"""
324 """display version and markers contained in 'data'"""
325 indent_string = ' ' * indent
325 indent_string = ' ' * indent
326 headsbyphase = phases.binarydecode(data)
326 headsbyphase = phases.binarydecode(data)
327 for phase in phases.allphases:
327 for phase in phases.allphases:
328 for head in headsbyphase[phase]:
328 for head in headsbyphase[phase]:
329 ui.write(indent_string)
329 ui.write(indent_string)
330 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
330 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
331
331
332 def _quasirepr(thing):
332 def _quasirepr(thing):
333 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
333 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
334 return '{%s}' % (
334 return '{%s}' % (
335 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
335 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
336 return pycompat.bytestr(repr(thing))
336 return pycompat.bytestr(repr(thing))
337
337
338 def _debugbundle2(ui, gen, all=None, **opts):
338 def _debugbundle2(ui, gen, all=None, **opts):
339 """lists the contents of a bundle2"""
339 """lists the contents of a bundle2"""
340 if not isinstance(gen, bundle2.unbundle20):
340 if not isinstance(gen, bundle2.unbundle20):
341 raise error.Abort(_('not a bundle2 file'))
341 raise error.Abort(_('not a bundle2 file'))
342 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
342 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
343 parttypes = opts.get(r'part_type', [])
343 parttypes = opts.get(r'part_type', [])
344 for part in gen.iterparts():
344 for part in gen.iterparts():
345 if parttypes and part.type not in parttypes:
345 if parttypes and part.type not in parttypes:
346 continue
346 continue
347 msg = '%s -- %s (mandatory: %r)\n'
347 msg = '%s -- %s (mandatory: %r)\n'
348 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
348 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
349 if part.type == 'changegroup':
349 if part.type == 'changegroup':
350 version = part.params.get('version', '01')
350 version = part.params.get('version', '01')
351 cg = changegroup.getunbundler(version, part, 'UN')
351 cg = changegroup.getunbundler(version, part, 'UN')
352 if not ui.quiet:
352 if not ui.quiet:
353 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
353 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
354 if part.type == 'obsmarkers':
354 if part.type == 'obsmarkers':
355 if not ui.quiet:
355 if not ui.quiet:
356 _debugobsmarkers(ui, part, indent=4, **opts)
356 _debugobsmarkers(ui, part, indent=4, **opts)
357 if part.type == 'phase-heads':
357 if part.type == 'phase-heads':
358 if not ui.quiet:
358 if not ui.quiet:
359 _debugphaseheads(ui, part, indent=4)
359 _debugphaseheads(ui, part, indent=4)
360
360
361 @command('debugbundle',
361 @command('debugbundle',
362 [('a', 'all', None, _('show all details')),
362 [('a', 'all', None, _('show all details')),
363 ('', 'part-type', [], _('show only the named part type')),
363 ('', 'part-type', [], _('show only the named part type')),
364 ('', 'spec', None, _('print the bundlespec of the bundle'))],
364 ('', 'spec', None, _('print the bundlespec of the bundle'))],
365 _('FILE'),
365 _('FILE'),
366 norepo=True)
366 norepo=True)
367 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
367 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
368 """lists the contents of a bundle"""
368 """lists the contents of a bundle"""
369 with hg.openpath(ui, bundlepath) as f:
369 with hg.openpath(ui, bundlepath) as f:
370 if spec:
370 if spec:
371 spec = exchange.getbundlespec(ui, f)
371 spec = exchange.getbundlespec(ui, f)
372 ui.write('%s\n' % spec)
372 ui.write('%s\n' % spec)
373 return
373 return
374
374
375 gen = exchange.readbundle(ui, f, bundlepath)
375 gen = exchange.readbundle(ui, f, bundlepath)
376 if isinstance(gen, bundle2.unbundle20):
376 if isinstance(gen, bundle2.unbundle20):
377 return _debugbundle2(ui, gen, all=all, **opts)
377 return _debugbundle2(ui, gen, all=all, **opts)
378 _debugchangegroup(ui, gen, all=all, **opts)
378 _debugchangegroup(ui, gen, all=all, **opts)
379
379
380 @command('debugcapabilities',
380 @command('debugcapabilities',
381 [], _('PATH'),
381 [], _('PATH'),
382 norepo=True)
382 norepo=True)
383 def debugcapabilities(ui, path, **opts):
383 def debugcapabilities(ui, path, **opts):
384 """lists the capabilities of a remote peer"""
384 """lists the capabilities of a remote peer"""
385 opts = pycompat.byteskwargs(opts)
385 opts = pycompat.byteskwargs(opts)
386 peer = hg.peer(ui, opts, path)
386 peer = hg.peer(ui, opts, path)
387 caps = peer.capabilities()
387 caps = peer.capabilities()
388 ui.write(('Main capabilities:\n'))
388 ui.write(('Main capabilities:\n'))
389 for c in sorted(caps):
389 for c in sorted(caps):
390 ui.write((' %s\n') % c)
390 ui.write((' %s\n') % c)
391 b2caps = bundle2.bundle2caps(peer)
391 b2caps = bundle2.bundle2caps(peer)
392 if b2caps:
392 if b2caps:
393 ui.write(('Bundle2 capabilities:\n'))
393 ui.write(('Bundle2 capabilities:\n'))
394 for key, values in sorted(b2caps.iteritems()):
394 for key, values in sorted(b2caps.iteritems()):
395 ui.write((' %s\n') % key)
395 ui.write((' %s\n') % key)
396 for v in values:
396 for v in values:
397 ui.write((' %s\n') % v)
397 ui.write((' %s\n') % v)
398
398
399 @command('debugcheckstate', [], '')
399 @command('debugcheckstate', [], '')
400 def debugcheckstate(ui, repo):
400 def debugcheckstate(ui, repo):
401 """validate the correctness of the current dirstate"""
401 """validate the correctness of the current dirstate"""
402 parent1, parent2 = repo.dirstate.parents()
402 parent1, parent2 = repo.dirstate.parents()
403 m1 = repo[parent1].manifest()
403 m1 = repo[parent1].manifest()
404 m2 = repo[parent2].manifest()
404 m2 = repo[parent2].manifest()
405 errors = 0
405 errors = 0
406 for f in repo.dirstate:
406 for f in repo.dirstate:
407 state = repo.dirstate[f]
407 state = repo.dirstate[f]
408 if state in "nr" and f not in m1:
408 if state in "nr" and f not in m1:
409 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
409 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
410 errors += 1
410 errors += 1
411 if state in "a" and f in m1:
411 if state in "a" and f in m1:
412 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
412 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
413 errors += 1
413 errors += 1
414 if state in "m" and f not in m1 and f not in m2:
414 if state in "m" and f not in m1 and f not in m2:
415 ui.warn(_("%s in state %s, but not in either manifest\n") %
415 ui.warn(_("%s in state %s, but not in either manifest\n") %
416 (f, state))
416 (f, state))
417 errors += 1
417 errors += 1
418 for f in m1:
418 for f in m1:
419 state = repo.dirstate[f]
419 state = repo.dirstate[f]
420 if state not in "nrm":
420 if state not in "nrm":
421 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
421 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
422 errors += 1
422 errors += 1
423 if errors:
423 if errors:
424 error = _(".hg/dirstate inconsistent with current parent's manifest")
424 error = _(".hg/dirstate inconsistent with current parent's manifest")
425 raise error.Abort(error)
425 raise error.Abort(error)
426
426
427 @command('debugcolor',
427 @command('debugcolor',
428 [('', 'style', None, _('show all configured styles'))],
428 [('', 'style', None, _('show all configured styles'))],
429 'hg debugcolor')
429 'hg debugcolor')
430 def debugcolor(ui, repo, **opts):
430 def debugcolor(ui, repo, **opts):
431 """show available color, effects or style"""
431 """show available color, effects or style"""
432 ui.write(('color mode: %s\n') % stringutil.pprint(ui._colormode))
432 ui.write(('color mode: %s\n') % stringutil.pprint(ui._colormode))
433 if opts.get(r'style'):
433 if opts.get(r'style'):
434 return _debugdisplaystyle(ui)
434 return _debugdisplaystyle(ui)
435 else:
435 else:
436 return _debugdisplaycolor(ui)
436 return _debugdisplaycolor(ui)
437
437
438 def _debugdisplaycolor(ui):
438 def _debugdisplaycolor(ui):
439 ui = ui.copy()
439 ui = ui.copy()
440 ui._styles.clear()
440 ui._styles.clear()
441 for effect in color._activeeffects(ui).keys():
441 for effect in color._activeeffects(ui).keys():
442 ui._styles[effect] = effect
442 ui._styles[effect] = effect
443 if ui._terminfoparams:
443 if ui._terminfoparams:
444 for k, v in ui.configitems('color'):
444 for k, v in ui.configitems('color'):
445 if k.startswith('color.'):
445 if k.startswith('color.'):
446 ui._styles[k] = k[6:]
446 ui._styles[k] = k[6:]
447 elif k.startswith('terminfo.'):
447 elif k.startswith('terminfo.'):
448 ui._styles[k] = k[9:]
448 ui._styles[k] = k[9:]
449 ui.write(_('available colors:\n'))
449 ui.write(_('available colors:\n'))
450 # sort label with a '_' after the other to group '_background' entry.
450 # sort label with a '_' after the other to group '_background' entry.
451 items = sorted(ui._styles.items(),
451 items = sorted(ui._styles.items(),
452 key=lambda i: ('_' in i[0], i[0], i[1]))
452 key=lambda i: ('_' in i[0], i[0], i[1]))
453 for colorname, label in items:
453 for colorname, label in items:
454 ui.write(('%s\n') % colorname, label=label)
454 ui.write(('%s\n') % colorname, label=label)
455
455
456 def _debugdisplaystyle(ui):
456 def _debugdisplaystyle(ui):
457 ui.write(_('available style:\n'))
457 ui.write(_('available style:\n'))
458 if not ui._styles:
458 if not ui._styles:
459 return
459 return
460 width = max(len(s) for s in ui._styles)
460 width = max(len(s) for s in ui._styles)
461 for label, effects in sorted(ui._styles.items()):
461 for label, effects in sorted(ui._styles.items()):
462 ui.write('%s' % label, label=label)
462 ui.write('%s' % label, label=label)
463 if effects:
463 if effects:
464 # 50
464 # 50
465 ui.write(': ')
465 ui.write(': ')
466 ui.write(' ' * (max(0, width - len(label))))
466 ui.write(' ' * (max(0, width - len(label))))
467 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
467 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
468 ui.write('\n')
468 ui.write('\n')
469
469
470 @command('debugcreatestreamclonebundle', [], 'FILE')
470 @command('debugcreatestreamclonebundle', [], 'FILE')
471 def debugcreatestreamclonebundle(ui, repo, fname):
471 def debugcreatestreamclonebundle(ui, repo, fname):
472 """create a stream clone bundle file
472 """create a stream clone bundle file
473
473
474 Stream bundles are special bundles that are essentially archives of
474 Stream bundles are special bundles that are essentially archives of
475 revlog files. They are commonly used for cloning very quickly.
475 revlog files. They are commonly used for cloning very quickly.
476 """
476 """
477 # TODO we may want to turn this into an abort when this functionality
477 # TODO we may want to turn this into an abort when this functionality
478 # is moved into `hg bundle`.
478 # is moved into `hg bundle`.
479 if phases.hassecret(repo):
479 if phases.hassecret(repo):
480 ui.warn(_('(warning: stream clone bundle will contain secret '
480 ui.warn(_('(warning: stream clone bundle will contain secret '
481 'revisions)\n'))
481 'revisions)\n'))
482
482
483 requirements, gen = streamclone.generatebundlev1(repo)
483 requirements, gen = streamclone.generatebundlev1(repo)
484 changegroup.writechunks(ui, gen, fname)
484 changegroup.writechunks(ui, gen, fname)
485
485
486 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
486 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
487
487
488 @command('debugdag',
488 @command('debugdag',
489 [('t', 'tags', None, _('use tags as labels')),
489 [('t', 'tags', None, _('use tags as labels')),
490 ('b', 'branches', None, _('annotate with branch names')),
490 ('b', 'branches', None, _('annotate with branch names')),
491 ('', 'dots', None, _('use dots for runs')),
491 ('', 'dots', None, _('use dots for runs')),
492 ('s', 'spaces', None, _('separate elements by spaces'))],
492 ('s', 'spaces', None, _('separate elements by spaces'))],
493 _('[OPTION]... [FILE [REV]...]'),
493 _('[OPTION]... [FILE [REV]...]'),
494 optionalrepo=True)
494 optionalrepo=True)
495 def debugdag(ui, repo, file_=None, *revs, **opts):
495 def debugdag(ui, repo, file_=None, *revs, **opts):
496 """format the changelog or an index DAG as a concise textual description
496 """format the changelog or an index DAG as a concise textual description
497
497
498 If you pass a revlog index, the revlog's DAG is emitted. If you list
498 If you pass a revlog index, the revlog's DAG is emitted. If you list
499 revision numbers, they get labeled in the output as rN.
499 revision numbers, they get labeled in the output as rN.
500
500
501 Otherwise, the changelog DAG of the current repo is emitted.
501 Otherwise, the changelog DAG of the current repo is emitted.
502 """
502 """
503 spaces = opts.get(r'spaces')
503 spaces = opts.get(r'spaces')
504 dots = opts.get(r'dots')
504 dots = opts.get(r'dots')
505 if file_:
505 if file_:
506 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
506 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
507 file_)
507 file_)
508 revs = set((int(r) for r in revs))
508 revs = set((int(r) for r in revs))
509 def events():
509 def events():
510 for r in rlog:
510 for r in rlog:
511 yield 'n', (r, list(p for p in rlog.parentrevs(r)
511 yield 'n', (r, list(p for p in rlog.parentrevs(r)
512 if p != -1))
512 if p != -1))
513 if r in revs:
513 if r in revs:
514 yield 'l', (r, "r%i" % r)
514 yield 'l', (r, "r%i" % r)
515 elif repo:
515 elif repo:
516 cl = repo.changelog
516 cl = repo.changelog
517 tags = opts.get(r'tags')
517 tags = opts.get(r'tags')
518 branches = opts.get(r'branches')
518 branches = opts.get(r'branches')
519 if tags:
519 if tags:
520 labels = {}
520 labels = {}
521 for l, n in repo.tags().items():
521 for l, n in repo.tags().items():
522 labels.setdefault(cl.rev(n), []).append(l)
522 labels.setdefault(cl.rev(n), []).append(l)
523 def events():
523 def events():
524 b = "default"
524 b = "default"
525 for r in cl:
525 for r in cl:
526 if branches:
526 if branches:
527 newb = cl.read(cl.node(r))[5]['branch']
527 newb = cl.read(cl.node(r))[5]['branch']
528 if newb != b:
528 if newb != b:
529 yield 'a', newb
529 yield 'a', newb
530 b = newb
530 b = newb
531 yield 'n', (r, list(p for p in cl.parentrevs(r)
531 yield 'n', (r, list(p for p in cl.parentrevs(r)
532 if p != -1))
532 if p != -1))
533 if tags:
533 if tags:
534 ls = labels.get(r)
534 ls = labels.get(r)
535 if ls:
535 if ls:
536 for l in ls:
536 for l in ls:
537 yield 'l', (r, l)
537 yield 'l', (r, l)
538 else:
538 else:
539 raise error.Abort(_('need repo for changelog dag'))
539 raise error.Abort(_('need repo for changelog dag'))
540
540
541 for line in dagparser.dagtextlines(events(),
541 for line in dagparser.dagtextlines(events(),
542 addspaces=spaces,
542 addspaces=spaces,
543 wraplabels=True,
543 wraplabels=True,
544 wrapannotations=True,
544 wrapannotations=True,
545 wrapnonlinear=dots,
545 wrapnonlinear=dots,
546 usedots=dots,
546 usedots=dots,
547 maxlinewidth=70):
547 maxlinewidth=70):
548 ui.write(line)
548 ui.write(line)
549 ui.write("\n")
549 ui.write("\n")
550
550
551 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
551 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
552 def debugdata(ui, repo, file_, rev=None, **opts):
552 def debugdata(ui, repo, file_, rev=None, **opts):
553 """dump the contents of a data file revision"""
553 """dump the contents of a data file revision"""
554 opts = pycompat.byteskwargs(opts)
554 opts = pycompat.byteskwargs(opts)
555 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
555 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
556 if rev is not None:
556 if rev is not None:
557 raise error.CommandError('debugdata', _('invalid arguments'))
557 raise error.CommandError('debugdata', _('invalid arguments'))
558 file_, rev = None, file_
558 file_, rev = None, file_
559 elif rev is None:
559 elif rev is None:
560 raise error.CommandError('debugdata', _('invalid arguments'))
560 raise error.CommandError('debugdata', _('invalid arguments'))
561 r = cmdutil.openstorage(repo, 'debugdata', file_, opts)
561 r = cmdutil.openstorage(repo, 'debugdata', file_, opts)
562 try:
562 try:
563 ui.write(r.revision(r.lookup(rev), raw=True))
563 ui.write(r.revision(r.lookup(rev), raw=True))
564 except KeyError:
564 except KeyError:
565 raise error.Abort(_('invalid revision identifier %s') % rev)
565 raise error.Abort(_('invalid revision identifier %s') % rev)
566
566
567 @command('debugdate',
567 @command('debugdate',
568 [('e', 'extended', None, _('try extended date formats'))],
568 [('e', 'extended', None, _('try extended date formats'))],
569 _('[-e] DATE [RANGE]'),
569 _('[-e] DATE [RANGE]'),
570 norepo=True, optionalrepo=True)
570 norepo=True, optionalrepo=True)
571 def debugdate(ui, date, range=None, **opts):
571 def debugdate(ui, date, range=None, **opts):
572 """parse and display a date"""
572 """parse and display a date"""
573 if opts[r"extended"]:
573 if opts[r"extended"]:
574 d = dateutil.parsedate(date, util.extendeddateformats)
574 d = dateutil.parsedate(date, util.extendeddateformats)
575 else:
575 else:
576 d = dateutil.parsedate(date)
576 d = dateutil.parsedate(date)
577 ui.write(("internal: %d %d\n") % d)
577 ui.write(("internal: %d %d\n") % d)
578 ui.write(("standard: %s\n") % dateutil.datestr(d))
578 ui.write(("standard: %s\n") % dateutil.datestr(d))
579 if range:
579 if range:
580 m = dateutil.matchdate(range)
580 m = dateutil.matchdate(range)
581 ui.write(("match: %s\n") % m(d[0]))
581 ui.write(("match: %s\n") % m(d[0]))
582
582
583 @command('debugdeltachain',
583 @command('debugdeltachain',
584 cmdutil.debugrevlogopts + cmdutil.formatteropts,
584 cmdutil.debugrevlogopts + cmdutil.formatteropts,
585 _('-c|-m|FILE'),
585 _('-c|-m|FILE'),
586 optionalrepo=True)
586 optionalrepo=True)
587 def debugdeltachain(ui, repo, file_=None, **opts):
587 def debugdeltachain(ui, repo, file_=None, **opts):
588 """dump information about delta chains in a revlog
588 """dump information about delta chains in a revlog
589
589
590 Output can be templatized. Available template keywords are:
590 Output can be templatized. Available template keywords are:
591
591
592 :``rev``: revision number
592 :``rev``: revision number
593 :``chainid``: delta chain identifier (numbered by unique base)
593 :``chainid``: delta chain identifier (numbered by unique base)
594 :``chainlen``: delta chain length to this revision
594 :``chainlen``: delta chain length to this revision
595 :``prevrev``: previous revision in delta chain
595 :``prevrev``: previous revision in delta chain
596 :``deltatype``: role of delta / how it was computed
596 :``deltatype``: role of delta / how it was computed
597 :``compsize``: compressed size of revision
597 :``compsize``: compressed size of revision
598 :``uncompsize``: uncompressed size of revision
598 :``uncompsize``: uncompressed size of revision
599 :``chainsize``: total size of compressed revisions in chain
599 :``chainsize``: total size of compressed revisions in chain
600 :``chainratio``: total chain size divided by uncompressed revision size
600 :``chainratio``: total chain size divided by uncompressed revision size
601 (new delta chains typically start at ratio 2.00)
601 (new delta chains typically start at ratio 2.00)
602 :``lindist``: linear distance from base revision in delta chain to end
602 :``lindist``: linear distance from base revision in delta chain to end
603 of this revision
603 of this revision
604 :``extradist``: total size of revisions not part of this delta chain from
604 :``extradist``: total size of revisions not part of this delta chain from
605 base of delta chain to end of this revision; a measurement
605 base of delta chain to end of this revision; a measurement
606 of how much extra data we need to read/seek across to read
606 of how much extra data we need to read/seek across to read
607 the delta chain for this revision
607 the delta chain for this revision
608 :``extraratio``: extradist divided by chainsize; another representation of
608 :``extraratio``: extradist divided by chainsize; another representation of
609 how much unrelated data is needed to load this delta chain
609 how much unrelated data is needed to load this delta chain
610
610
611 If the repository is configured to use the sparse read, additional keywords
611 If the repository is configured to use the sparse read, additional keywords
612 are available:
612 are available:
613
613
614 :``readsize``: total size of data read from the disk for a revision
614 :``readsize``: total size of data read from the disk for a revision
615 (sum of the sizes of all the blocks)
615 (sum of the sizes of all the blocks)
616 :``largestblock``: size of the largest block of data read from the disk
616 :``largestblock``: size of the largest block of data read from the disk
617 :``readdensity``: density of useful bytes in the data read from the disk
617 :``readdensity``: density of useful bytes in the data read from the disk
618 :``srchunks``: in how many data hunks the whole revision would be read
618 :``srchunks``: in how many data hunks the whole revision would be read
619
619
620 The sparse read can be enabled with experimental.sparse-read = True
620 The sparse read can be enabled with experimental.sparse-read = True
621 """
621 """
622 opts = pycompat.byteskwargs(opts)
622 opts = pycompat.byteskwargs(opts)
623 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
623 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
624 index = r.index
624 index = r.index
625 start = r.start
625 start = r.start
626 length = r.length
626 length = r.length
627 generaldelta = r.version & revlog.FLAG_GENERALDELTA
627 generaldelta = r.version & revlog.FLAG_GENERALDELTA
628 withsparseread = getattr(r, '_withsparseread', False)
628 withsparseread = getattr(r, '_withsparseread', False)
629
629
630 def revinfo(rev):
630 def revinfo(rev):
631 e = index[rev]
631 e = index[rev]
632 compsize = e[1]
632 compsize = e[1]
633 uncompsize = e[2]
633 uncompsize = e[2]
634 chainsize = 0
634 chainsize = 0
635
635
636 if generaldelta:
636 if generaldelta:
637 if e[3] == e[5]:
637 if e[3] == e[5]:
638 deltatype = 'p1'
638 deltatype = 'p1'
639 elif e[3] == e[6]:
639 elif e[3] == e[6]:
640 deltatype = 'p2'
640 deltatype = 'p2'
641 elif e[3] == rev - 1:
641 elif e[3] == rev - 1:
642 deltatype = 'prev'
642 deltatype = 'prev'
643 elif e[3] == rev:
643 elif e[3] == rev:
644 deltatype = 'base'
644 deltatype = 'base'
645 else:
645 else:
646 deltatype = 'other'
646 deltatype = 'other'
647 else:
647 else:
648 if e[3] == rev:
648 if e[3] == rev:
649 deltatype = 'base'
649 deltatype = 'base'
650 else:
650 else:
651 deltatype = 'prev'
651 deltatype = 'prev'
652
652
653 chain = r._deltachain(rev)[0]
653 chain = r._deltachain(rev)[0]
654 for iterrev in chain:
654 for iterrev in chain:
655 e = index[iterrev]
655 e = index[iterrev]
656 chainsize += e[1]
656 chainsize += e[1]
657
657
658 return compsize, uncompsize, deltatype, chain, chainsize
658 return compsize, uncompsize, deltatype, chain, chainsize
659
659
660 fm = ui.formatter('debugdeltachain', opts)
660 fm = ui.formatter('debugdeltachain', opts)
661
661
662 fm.plain(' rev chain# chainlen prev delta '
662 fm.plain(' rev chain# chainlen prev delta '
663 'size rawsize chainsize ratio lindist extradist '
663 'size rawsize chainsize ratio lindist extradist '
664 'extraratio')
664 'extraratio')
665 if withsparseread:
665 if withsparseread:
666 fm.plain(' readsize largestblk rddensity srchunks')
666 fm.plain(' readsize largestblk rddensity srchunks')
667 fm.plain('\n')
667 fm.plain('\n')
668
668
669 chainbases = {}
669 chainbases = {}
670 for rev in r:
670 for rev in r:
671 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
671 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
672 chainbase = chain[0]
672 chainbase = chain[0]
673 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
673 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
674 basestart = start(chainbase)
674 basestart = start(chainbase)
675 revstart = start(rev)
675 revstart = start(rev)
676 lineardist = revstart + comp - basestart
676 lineardist = revstart + comp - basestart
677 extradist = lineardist - chainsize
677 extradist = lineardist - chainsize
678 try:
678 try:
679 prevrev = chain[-2]
679 prevrev = chain[-2]
680 except IndexError:
680 except IndexError:
681 prevrev = -1
681 prevrev = -1
682
682
683 if uncomp != 0:
683 if uncomp != 0:
684 chainratio = float(chainsize) / float(uncomp)
684 chainratio = float(chainsize) / float(uncomp)
685 else:
685 else:
686 chainratio = chainsize
686 chainratio = chainsize
687
687
688 if chainsize != 0:
688 if chainsize != 0:
689 extraratio = float(extradist) / float(chainsize)
689 extraratio = float(extradist) / float(chainsize)
690 else:
690 else:
691 extraratio = extradist
691 extraratio = extradist
692
692
693 fm.startitem()
693 fm.startitem()
694 fm.write('rev chainid chainlen prevrev deltatype compsize '
694 fm.write('rev chainid chainlen prevrev deltatype compsize '
695 'uncompsize chainsize chainratio lindist extradist '
695 'uncompsize chainsize chainratio lindist extradist '
696 'extraratio',
696 'extraratio',
697 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
697 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
698 rev, chainid, len(chain), prevrev, deltatype, comp,
698 rev, chainid, len(chain), prevrev, deltatype, comp,
699 uncomp, chainsize, chainratio, lineardist, extradist,
699 uncomp, chainsize, chainratio, lineardist, extradist,
700 extraratio,
700 extraratio,
701 rev=rev, chainid=chainid, chainlen=len(chain),
701 rev=rev, chainid=chainid, chainlen=len(chain),
702 prevrev=prevrev, deltatype=deltatype, compsize=comp,
702 prevrev=prevrev, deltatype=deltatype, compsize=comp,
703 uncompsize=uncomp, chainsize=chainsize,
703 uncompsize=uncomp, chainsize=chainsize,
704 chainratio=chainratio, lindist=lineardist,
704 chainratio=chainratio, lindist=lineardist,
705 extradist=extradist, extraratio=extraratio)
705 extradist=extradist, extraratio=extraratio)
706 if withsparseread:
706 if withsparseread:
707 readsize = 0
707 readsize = 0
708 largestblock = 0
708 largestblock = 0
709 srchunks = 0
709 srchunks = 0
710
710
711 for revschunk in deltautil.slicechunk(r, chain):
711 for revschunk in deltautil.slicechunk(r, chain):
712 srchunks += 1
712 srchunks += 1
713 blkend = start(revschunk[-1]) + length(revschunk[-1])
713 blkend = start(revschunk[-1]) + length(revschunk[-1])
714 blksize = blkend - start(revschunk[0])
714 blksize = blkend - start(revschunk[0])
715
715
716 readsize += blksize
716 readsize += blksize
717 if largestblock < blksize:
717 if largestblock < blksize:
718 largestblock = blksize
718 largestblock = blksize
719
719
720 if readsize:
720 if readsize:
721 readdensity = float(chainsize) / float(readsize)
721 readdensity = float(chainsize) / float(readsize)
722 else:
722 else:
723 readdensity = 1
723 readdensity = 1
724
724
725 fm.write('readsize largestblock readdensity srchunks',
725 fm.write('readsize largestblock readdensity srchunks',
726 ' %10d %10d %9.5f %8d',
726 ' %10d %10d %9.5f %8d',
727 readsize, largestblock, readdensity, srchunks,
727 readsize, largestblock, readdensity, srchunks,
728 readsize=readsize, largestblock=largestblock,
728 readsize=readsize, largestblock=largestblock,
729 readdensity=readdensity, srchunks=srchunks)
729 readdensity=readdensity, srchunks=srchunks)
730
730
731 fm.plain('\n')
731 fm.plain('\n')
732
732
733 fm.end()
733 fm.end()
734
734
735 @command('debugdirstate|debugstate',
735 @command('debugdirstate|debugstate',
736 [('', 'nodates', None, _('do not display the saved mtime')),
736 [('', 'nodates', None, _('do not display the saved mtime')),
737 ('', 'datesort', None, _('sort by saved mtime'))],
737 ('', 'datesort', None, _('sort by saved mtime'))],
738 _('[OPTION]...'))
738 _('[OPTION]...'))
739 def debugstate(ui, repo, **opts):
739 def debugstate(ui, repo, **opts):
740 """show the contents of the current dirstate"""
740 """show the contents of the current dirstate"""
741
741
742 nodates = opts.get(r'nodates')
742 nodates = opts.get(r'nodates')
743 datesort = opts.get(r'datesort')
743 datesort = opts.get(r'datesort')
744
744
745 timestr = ""
745 timestr = ""
746 if datesort:
746 if datesort:
747 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
747 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
748 else:
748 else:
749 keyfunc = None # sort by filename
749 keyfunc = None # sort by filename
750 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
750 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
751 if ent[3] == -1:
751 if ent[3] == -1:
752 timestr = 'unset '
752 timestr = 'unset '
753 elif nodates:
753 elif nodates:
754 timestr = 'set '
754 timestr = 'set '
755 else:
755 else:
756 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
756 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
757 time.localtime(ent[3]))
757 time.localtime(ent[3]))
758 timestr = encoding.strtolocal(timestr)
758 timestr = encoding.strtolocal(timestr)
759 if ent[1] & 0o20000:
759 if ent[1] & 0o20000:
760 mode = 'lnk'
760 mode = 'lnk'
761 else:
761 else:
762 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
762 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
763 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
763 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
764 for f in repo.dirstate.copies():
764 for f in repo.dirstate.copies():
765 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
765 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
766
766
767 @command('debugdiscovery',
767 @command('debugdiscovery',
768 [('', 'old', None, _('use old-style discovery')),
768 [('', 'old', None, _('use old-style discovery')),
769 ('', 'nonheads', None,
769 ('', 'nonheads', None,
770 _('use old-style discovery with non-heads included')),
770 _('use old-style discovery with non-heads included')),
771 ('', 'rev', [], 'restrict discovery to this set of revs'),
771 ('', 'rev', [], 'restrict discovery to this set of revs'),
772 ] + cmdutil.remoteopts,
772 ] + cmdutil.remoteopts,
773 _('[--rev REV] [OTHER]'))
773 _('[--rev REV] [OTHER]'))
774 def debugdiscovery(ui, repo, remoteurl="default", **opts):
774 def debugdiscovery(ui, repo, remoteurl="default", **opts):
775 """runs the changeset discovery protocol in isolation"""
775 """runs the changeset discovery protocol in isolation"""
776 opts = pycompat.byteskwargs(opts)
776 opts = pycompat.byteskwargs(opts)
777 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
777 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
778 remote = hg.peer(repo, opts, remoteurl)
778 remote = hg.peer(repo, opts, remoteurl)
779 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
779 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
780
780
781 # make sure tests are repeatable
781 # make sure tests are repeatable
782 random.seed(12323)
782 random.seed(12323)
783
783
784 def doit(pushedrevs, remoteheads, remote=remote):
784 def doit(pushedrevs, remoteheads, remote=remote):
785 if opts.get('old'):
785 if opts.get('old'):
786 if not util.safehasattr(remote, 'branches'):
786 if not util.safehasattr(remote, 'branches'):
787 # enable in-client legacy support
787 # enable in-client legacy support
788 remote = localrepo.locallegacypeer(remote.local())
788 remote = localrepo.locallegacypeer(remote.local())
789 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
789 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
790 force=True)
790 force=True)
791 common = set(common)
791 common = set(common)
792 if not opts.get('nonheads'):
792 if not opts.get('nonheads'):
793 ui.write(("unpruned common: %s\n") %
793 ui.write(("unpruned common: %s\n") %
794 " ".join(sorted(short(n) for n in common)))
794 " ".join(sorted(short(n) for n in common)))
795
795
796 clnode = repo.changelog.node
796 clnode = repo.changelog.node
797 common = repo.revs('heads(::%ln)', common)
797 common = repo.revs('heads(::%ln)', common)
798 common = {clnode(r) for r in common}
798 common = {clnode(r) for r in common}
799 else:
799 else:
800 nodes = None
800 nodes = None
801 if pushedrevs:
801 if pushedrevs:
802 revs = scmutil.revrange(repo, pushedrevs)
802 revs = scmutil.revrange(repo, pushedrevs)
803 nodes = [repo[r].node() for r in revs]
803 nodes = [repo[r].node() for r in revs]
804 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
804 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
805 ancestorsof=nodes)
805 ancestorsof=nodes)
806 common = set(common)
806 common = set(common)
807 rheads = set(hds)
807 rheads = set(hds)
808 lheads = set(repo.heads())
808 lheads = set(repo.heads())
809 ui.write(("common heads: %s\n") %
809 ui.write(("common heads: %s\n") %
810 " ".join(sorted(short(n) for n in common)))
810 " ".join(sorted(short(n) for n in common)))
811 if lheads <= common:
811 if lheads <= common:
812 ui.write(("local is subset\n"))
812 ui.write(("local is subset\n"))
813 elif rheads <= common:
813 elif rheads <= common:
814 ui.write(("remote is subset\n"))
814 ui.write(("remote is subset\n"))
815
815
816 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
816 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
817 localrevs = opts['rev']
817 localrevs = opts['rev']
818 doit(localrevs, remoterevs)
818 doit(localrevs, remoterevs)
819
819
820 _chunksize = 4 << 10
820 _chunksize = 4 << 10
821
821
822 @command('debugdownload',
822 @command('debugdownload',
823 [
823 [
824 ('o', 'output', '', _('path')),
824 ('o', 'output', '', _('path')),
825 ],
825 ],
826 optionalrepo=True)
826 optionalrepo=True)
827 def debugdownload(ui, repo, url, output=None, **opts):
827 def debugdownload(ui, repo, url, output=None, **opts):
828 """download a resource using Mercurial logic and config
828 """download a resource using Mercurial logic and config
829 """
829 """
830 fh = urlmod.open(ui, url, output)
830 fh = urlmod.open(ui, url, output)
831
831
832 dest = ui
832 dest = ui
833 if output:
833 if output:
834 dest = open(output, "wb", _chunksize)
834 dest = open(output, "wb", _chunksize)
835 try:
835 try:
836 data = fh.read(_chunksize)
836 data = fh.read(_chunksize)
837 while data:
837 while data:
838 dest.write(data)
838 dest.write(data)
839 data = fh.read(_chunksize)
839 data = fh.read(_chunksize)
840 finally:
840 finally:
841 if output:
841 if output:
842 dest.close()
842 dest.close()
843
843
844 @command('debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
844 @command('debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
845 def debugextensions(ui, repo, **opts):
845 def debugextensions(ui, repo, **opts):
846 '''show information about active extensions'''
846 '''show information about active extensions'''
847 opts = pycompat.byteskwargs(opts)
847 opts = pycompat.byteskwargs(opts)
848 exts = extensions.extensions(ui)
848 exts = extensions.extensions(ui)
849 hgver = util.version()
849 hgver = util.version()
850 fm = ui.formatter('debugextensions', opts)
850 fm = ui.formatter('debugextensions', opts)
851 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
851 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
852 isinternal = extensions.ismoduleinternal(extmod)
852 isinternal = extensions.ismoduleinternal(extmod)
853 extsource = pycompat.fsencode(extmod.__file__)
853 extsource = pycompat.fsencode(extmod.__file__)
854 if isinternal:
854 if isinternal:
855 exttestedwith = [] # never expose magic string to users
855 exttestedwith = [] # never expose magic string to users
856 else:
856 else:
857 exttestedwith = getattr(extmod, 'testedwith', '').split()
857 exttestedwith = getattr(extmod, 'testedwith', '').split()
858 extbuglink = getattr(extmod, 'buglink', None)
858 extbuglink = getattr(extmod, 'buglink', None)
859
859
860 fm.startitem()
860 fm.startitem()
861
861
862 if ui.quiet or ui.verbose:
862 if ui.quiet or ui.verbose:
863 fm.write('name', '%s\n', extname)
863 fm.write('name', '%s\n', extname)
864 else:
864 else:
865 fm.write('name', '%s', extname)
865 fm.write('name', '%s', extname)
866 if isinternal or hgver in exttestedwith:
866 if isinternal or hgver in exttestedwith:
867 fm.plain('\n')
867 fm.plain('\n')
868 elif not exttestedwith:
868 elif not exttestedwith:
869 fm.plain(_(' (untested!)\n'))
869 fm.plain(_(' (untested!)\n'))
870 else:
870 else:
871 lasttestedversion = exttestedwith[-1]
871 lasttestedversion = exttestedwith[-1]
872 fm.plain(' (%s!)\n' % lasttestedversion)
872 fm.plain(' (%s!)\n' % lasttestedversion)
873
873
874 fm.condwrite(ui.verbose and extsource, 'source',
874 fm.condwrite(ui.verbose and extsource, 'source',
875 _(' location: %s\n'), extsource or "")
875 _(' location: %s\n'), extsource or "")
876
876
877 if ui.verbose:
877 if ui.verbose:
878 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
878 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
879 fm.data(bundled=isinternal)
879 fm.data(bundled=isinternal)
880
880
881 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
881 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
882 _(' tested with: %s\n'),
882 _(' tested with: %s\n'),
883 fm.formatlist(exttestedwith, name='ver'))
883 fm.formatlist(exttestedwith, name='ver'))
884
884
885 fm.condwrite(ui.verbose and extbuglink, 'buglink',
885 fm.condwrite(ui.verbose and extbuglink, 'buglink',
886 _(' bug reporting: %s\n'), extbuglink or "")
886 _(' bug reporting: %s\n'), extbuglink or "")
887
887
888 fm.end()
888 fm.end()
889
889
890 @command('debugfileset',
890 @command('debugfileset',
891 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV')),
891 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV')),
892 ('', 'all-files', False,
892 ('', 'all-files', False,
893 _('test files from all revisions and working directory')),
893 _('test files from all revisions and working directory')),
894 ('s', 'show-matcher', None,
894 ('s', 'show-matcher', None,
895 _('print internal representation of matcher')),
895 _('print internal representation of matcher')),
896 ('p', 'show-stage', [],
896 ('p', 'show-stage', [],
897 _('print parsed tree at the given stage'), _('NAME'))],
897 _('print parsed tree at the given stage'), _('NAME'))],
898 _('[-r REV] [--all-files] [OPTION]... FILESPEC'))
898 _('[-r REV] [--all-files] [OPTION]... FILESPEC'))
899 def debugfileset(ui, repo, expr, **opts):
899 def debugfileset(ui, repo, expr, **opts):
900 '''parse and apply a fileset specification'''
900 '''parse and apply a fileset specification'''
901 from . import fileset
901 from . import fileset
902 fileset.symbols # force import of fileset so we have predicates to optimize
902 fileset.symbols # force import of fileset so we have predicates to optimize
903 opts = pycompat.byteskwargs(opts)
903 opts = pycompat.byteskwargs(opts)
904 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
904 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
905
905
906 stages = [
906 stages = [
907 ('parsed', pycompat.identity),
907 ('parsed', pycompat.identity),
908 ('analyzed', filesetlang.analyze),
908 ('analyzed', filesetlang.analyze),
909 ('optimized', filesetlang.optimize),
909 ('optimized', filesetlang.optimize),
910 ]
910 ]
911 stagenames = set(n for n, f in stages)
911 stagenames = set(n for n, f in stages)
912
912
913 showalways = set()
913 showalways = set()
914 if ui.verbose and not opts['show_stage']:
914 if ui.verbose and not opts['show_stage']:
915 # show parsed tree by --verbose (deprecated)
915 # show parsed tree by --verbose (deprecated)
916 showalways.add('parsed')
916 showalways.add('parsed')
917 if opts['show_stage'] == ['all']:
917 if opts['show_stage'] == ['all']:
918 showalways.update(stagenames)
918 showalways.update(stagenames)
919 else:
919 else:
920 for n in opts['show_stage']:
920 for n in opts['show_stage']:
921 if n not in stagenames:
921 if n not in stagenames:
922 raise error.Abort(_('invalid stage name: %s') % n)
922 raise error.Abort(_('invalid stage name: %s') % n)
923 showalways.update(opts['show_stage'])
923 showalways.update(opts['show_stage'])
924
924
925 tree = filesetlang.parse(expr)
925 tree = filesetlang.parse(expr)
926 for n, f in stages:
926 for n, f in stages:
927 tree = f(tree)
927 tree = f(tree)
928 if n in showalways:
928 if n in showalways:
929 if opts['show_stage'] or n != 'parsed':
929 if opts['show_stage'] or n != 'parsed':
930 ui.write(("* %s:\n") % n)
930 ui.write(("* %s:\n") % n)
931 ui.write(filesetlang.prettyformat(tree), "\n")
931 ui.write(filesetlang.prettyformat(tree), "\n")
932
932
933 files = set()
933 files = set()
934 if opts['all_files']:
934 if opts['all_files']:
935 for r in repo:
935 for r in repo:
936 c = repo[r]
936 c = repo[r]
937 files.update(c.files())
937 files.update(c.files())
938 files.update(c.substate)
938 files.update(c.substate)
939 if opts['all_files'] or ctx.rev() is None:
939 if opts['all_files'] or ctx.rev() is None:
940 wctx = repo[None]
940 wctx = repo[None]
941 files.update(repo.dirstate.walk(scmutil.matchall(repo),
941 files.update(repo.dirstate.walk(scmutil.matchall(repo),
942 subrepos=list(wctx.substate),
942 subrepos=list(wctx.substate),
943 unknown=True, ignored=True))
943 unknown=True, ignored=True))
944 files.update(wctx.substate)
944 files.update(wctx.substate)
945 else:
945 else:
946 files.update(ctx.files())
946 files.update(ctx.files())
947 files.update(ctx.substate)
947 files.update(ctx.substate)
948
948
949 m = ctx.matchfileset(expr)
949 m = ctx.matchfileset(expr)
950 if opts['show_matcher'] or (opts['show_matcher'] is None and ui.verbose):
950 if opts['show_matcher'] or (opts['show_matcher'] is None and ui.verbose):
951 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
951 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
952 for f in sorted(files):
952 for f in sorted(files):
953 if not m(f):
953 if not m(f):
954 continue
954 continue
955 ui.write("%s\n" % f)
955 ui.write("%s\n" % f)
956
956
957 @command('debugformat',
957 @command('debugformat',
958 [] + cmdutil.formatteropts)
958 [] + cmdutil.formatteropts)
959 def debugformat(ui, repo, **opts):
959 def debugformat(ui, repo, **opts):
960 """display format information about the current repository
960 """display format information about the current repository
961
961
962 Use --verbose to get extra information about current config value and
962 Use --verbose to get extra information about current config value and
963 Mercurial default."""
963 Mercurial default."""
964 opts = pycompat.byteskwargs(opts)
964 opts = pycompat.byteskwargs(opts)
965 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
965 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
966 maxvariantlength = max(len('format-variant'), maxvariantlength)
966 maxvariantlength = max(len('format-variant'), maxvariantlength)
967
967
968 def makeformatname(name):
968 def makeformatname(name):
969 return '%s:' + (' ' * (maxvariantlength - len(name)))
969 return '%s:' + (' ' * (maxvariantlength - len(name)))
970
970
971 fm = ui.formatter('debugformat', opts)
971 fm = ui.formatter('debugformat', opts)
972 if fm.isplain():
972 if fm.isplain():
973 def formatvalue(value):
973 def formatvalue(value):
974 if util.safehasattr(value, 'startswith'):
974 if util.safehasattr(value, 'startswith'):
975 return value
975 return value
976 if value:
976 if value:
977 return 'yes'
977 return 'yes'
978 else:
978 else:
979 return 'no'
979 return 'no'
980 else:
980 else:
981 formatvalue = pycompat.identity
981 formatvalue = pycompat.identity
982
982
983 fm.plain('format-variant')
983 fm.plain('format-variant')
984 fm.plain(' ' * (maxvariantlength - len('format-variant')))
984 fm.plain(' ' * (maxvariantlength - len('format-variant')))
985 fm.plain(' repo')
985 fm.plain(' repo')
986 if ui.verbose:
986 if ui.verbose:
987 fm.plain(' config default')
987 fm.plain(' config default')
988 fm.plain('\n')
988 fm.plain('\n')
989 for fv in upgrade.allformatvariant:
989 for fv in upgrade.allformatvariant:
990 fm.startitem()
990 fm.startitem()
991 repovalue = fv.fromrepo(repo)
991 repovalue = fv.fromrepo(repo)
992 configvalue = fv.fromconfig(repo)
992 configvalue = fv.fromconfig(repo)
993
993
994 if repovalue != configvalue:
994 if repovalue != configvalue:
995 namelabel = 'formatvariant.name.mismatchconfig'
995 namelabel = 'formatvariant.name.mismatchconfig'
996 repolabel = 'formatvariant.repo.mismatchconfig'
996 repolabel = 'formatvariant.repo.mismatchconfig'
997 elif repovalue != fv.default:
997 elif repovalue != fv.default:
998 namelabel = 'formatvariant.name.mismatchdefault'
998 namelabel = 'formatvariant.name.mismatchdefault'
999 repolabel = 'formatvariant.repo.mismatchdefault'
999 repolabel = 'formatvariant.repo.mismatchdefault'
1000 else:
1000 else:
1001 namelabel = 'formatvariant.name.uptodate'
1001 namelabel = 'formatvariant.name.uptodate'
1002 repolabel = 'formatvariant.repo.uptodate'
1002 repolabel = 'formatvariant.repo.uptodate'
1003
1003
1004 fm.write('name', makeformatname(fv.name), fv.name,
1004 fm.write('name', makeformatname(fv.name), fv.name,
1005 label=namelabel)
1005 label=namelabel)
1006 fm.write('repo', ' %3s', formatvalue(repovalue),
1006 fm.write('repo', ' %3s', formatvalue(repovalue),
1007 label=repolabel)
1007 label=repolabel)
1008 if fv.default != configvalue:
1008 if fv.default != configvalue:
1009 configlabel = 'formatvariant.config.special'
1009 configlabel = 'formatvariant.config.special'
1010 else:
1010 else:
1011 configlabel = 'formatvariant.config.default'
1011 configlabel = 'formatvariant.config.default'
1012 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
1012 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
1013 label=configlabel)
1013 label=configlabel)
1014 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
1014 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
1015 label='formatvariant.default')
1015 label='formatvariant.default')
1016 fm.plain('\n')
1016 fm.plain('\n')
1017 fm.end()
1017 fm.end()
1018
1018
1019 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
1019 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
1020 def debugfsinfo(ui, path="."):
1020 def debugfsinfo(ui, path="."):
1021 """show information detected about current filesystem"""
1021 """show information detected about current filesystem"""
1022 ui.write(('path: %s\n') % path)
1022 ui.write(('path: %s\n') % path)
1023 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
1023 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
1024 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1024 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1025 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
1025 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
1026 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1026 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1027 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
1027 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
1028 casesensitive = '(unknown)'
1028 casesensitive = '(unknown)'
1029 try:
1029 try:
1030 with pycompat.namedtempfile(prefix='.debugfsinfo', dir=path) as f:
1030 with pycompat.namedtempfile(prefix='.debugfsinfo', dir=path) as f:
1031 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
1031 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
1032 except OSError:
1032 except OSError:
1033 pass
1033 pass
1034 ui.write(('case-sensitive: %s\n') % casesensitive)
1034 ui.write(('case-sensitive: %s\n') % casesensitive)
1035
1035
1036 @command('debuggetbundle',
1036 @command('debuggetbundle',
1037 [('H', 'head', [], _('id of head node'), _('ID')),
1037 [('H', 'head', [], _('id of head node'), _('ID')),
1038 ('C', 'common', [], _('id of common node'), _('ID')),
1038 ('C', 'common', [], _('id of common node'), _('ID')),
1039 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1039 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1040 _('REPO FILE [-H|-C ID]...'),
1040 _('REPO FILE [-H|-C ID]...'),
1041 norepo=True)
1041 norepo=True)
1042 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1042 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1043 """retrieves a bundle from a repo
1043 """retrieves a bundle from a repo
1044
1044
1045 Every ID must be a full-length hex node id string. Saves the bundle to the
1045 Every ID must be a full-length hex node id string. Saves the bundle to the
1046 given file.
1046 given file.
1047 """
1047 """
1048 opts = pycompat.byteskwargs(opts)
1048 opts = pycompat.byteskwargs(opts)
1049 repo = hg.peer(ui, opts, repopath)
1049 repo = hg.peer(ui, opts, repopath)
1050 if not repo.capable('getbundle'):
1050 if not repo.capable('getbundle'):
1051 raise error.Abort("getbundle() not supported by target repository")
1051 raise error.Abort("getbundle() not supported by target repository")
1052 args = {}
1052 args = {}
1053 if common:
1053 if common:
1054 args[r'common'] = [bin(s) for s in common]
1054 args[r'common'] = [bin(s) for s in common]
1055 if head:
1055 if head:
1056 args[r'heads'] = [bin(s) for s in head]
1056 args[r'heads'] = [bin(s) for s in head]
1057 # TODO: get desired bundlecaps from command line.
1057 # TODO: get desired bundlecaps from command line.
1058 args[r'bundlecaps'] = None
1058 args[r'bundlecaps'] = None
1059 bundle = repo.getbundle('debug', **args)
1059 bundle = repo.getbundle('debug', **args)
1060
1060
1061 bundletype = opts.get('type', 'bzip2').lower()
1061 bundletype = opts.get('type', 'bzip2').lower()
1062 btypes = {'none': 'HG10UN',
1062 btypes = {'none': 'HG10UN',
1063 'bzip2': 'HG10BZ',
1063 'bzip2': 'HG10BZ',
1064 'gzip': 'HG10GZ',
1064 'gzip': 'HG10GZ',
1065 'bundle2': 'HG20'}
1065 'bundle2': 'HG20'}
1066 bundletype = btypes.get(bundletype)
1066 bundletype = btypes.get(bundletype)
1067 if bundletype not in bundle2.bundletypes:
1067 if bundletype not in bundle2.bundletypes:
1068 raise error.Abort(_('unknown bundle type specified with --type'))
1068 raise error.Abort(_('unknown bundle type specified with --type'))
1069 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1069 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1070
1070
1071 @command('debugignore', [], '[FILE]')
1071 @command('debugignore', [], '[FILE]')
1072 def debugignore(ui, repo, *files, **opts):
1072 def debugignore(ui, repo, *files, **opts):
1073 """display the combined ignore pattern and information about ignored files
1073 """display the combined ignore pattern and information about ignored files
1074
1074
1075 With no argument display the combined ignore pattern.
1075 With no argument display the combined ignore pattern.
1076
1076
1077 Given space separated file names, shows if the given file is ignored and
1077 Given space separated file names, shows if the given file is ignored and
1078 if so, show the ignore rule (file and line number) that matched it.
1078 if so, show the ignore rule (file and line number) that matched it.
1079 """
1079 """
1080 ignore = repo.dirstate._ignore
1080 ignore = repo.dirstate._ignore
1081 if not files:
1081 if not files:
1082 # Show all the patterns
1082 # Show all the patterns
1083 ui.write("%s\n" % pycompat.byterepr(ignore))
1083 ui.write("%s\n" % pycompat.byterepr(ignore))
1084 else:
1084 else:
1085 m = scmutil.match(repo[None], pats=files)
1085 m = scmutil.match(repo[None], pats=files)
1086 for f in m.files():
1086 for f in m.files():
1087 nf = util.normpath(f)
1087 nf = util.normpath(f)
1088 ignored = None
1088 ignored = None
1089 ignoredata = None
1089 ignoredata = None
1090 if nf != '.':
1090 if nf != '.':
1091 if ignore(nf):
1091 if ignore(nf):
1092 ignored = nf
1092 ignored = nf
1093 ignoredata = repo.dirstate._ignorefileandline(nf)
1093 ignoredata = repo.dirstate._ignorefileandline(nf)
1094 else:
1094 else:
1095 for p in util.finddirs(nf):
1095 for p in util.finddirs(nf):
1096 if ignore(p):
1096 if ignore(p):
1097 ignored = p
1097 ignored = p
1098 ignoredata = repo.dirstate._ignorefileandline(p)
1098 ignoredata = repo.dirstate._ignorefileandline(p)
1099 break
1099 break
1100 if ignored:
1100 if ignored:
1101 if ignored == nf:
1101 if ignored == nf:
1102 ui.write(_("%s is ignored\n") % m.uipath(f))
1102 ui.write(_("%s is ignored\n") % m.uipath(f))
1103 else:
1103 else:
1104 ui.write(_("%s is ignored because of "
1104 ui.write(_("%s is ignored because of "
1105 "containing folder %s\n")
1105 "containing folder %s\n")
1106 % (m.uipath(f), ignored))
1106 % (m.uipath(f), ignored))
1107 ignorefile, lineno, line = ignoredata
1107 ignorefile, lineno, line = ignoredata
1108 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1108 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1109 % (ignorefile, lineno, line))
1109 % (ignorefile, lineno, line))
1110 else:
1110 else:
1111 ui.write(_("%s is not ignored\n") % m.uipath(f))
1111 ui.write(_("%s is not ignored\n") % m.uipath(f))
1112
1112
1113 @command('debugindex', cmdutil.debugrevlogopts + cmdutil.formatteropts,
1113 @command('debugindex', cmdutil.debugrevlogopts + cmdutil.formatteropts,
1114 _('-c|-m|FILE'))
1114 _('-c|-m|FILE'))
1115 def debugindex(ui, repo, file_=None, **opts):
1115 def debugindex(ui, repo, file_=None, **opts):
1116 """dump index data for a storage primitive"""
1116 """dump index data for a storage primitive"""
1117 opts = pycompat.byteskwargs(opts)
1117 opts = pycompat.byteskwargs(opts)
1118 store = cmdutil.openstorage(repo, 'debugindex', file_, opts)
1118 store = cmdutil.openstorage(repo, 'debugindex', file_, opts)
1119
1119
1120 if ui.debugflag:
1120 if ui.debugflag:
1121 shortfn = hex
1121 shortfn = hex
1122 else:
1122 else:
1123 shortfn = short
1123 shortfn = short
1124
1124
1125 idlen = 12
1125 idlen = 12
1126 for i in store:
1126 for i in store:
1127 idlen = len(shortfn(store.node(i)))
1127 idlen = len(shortfn(store.node(i)))
1128 break
1128 break
1129
1129
1130 fm = ui.formatter('debugindex', opts)
1130 fm = ui.formatter('debugindex', opts)
1131 fm.plain(b' rev linkrev %s %s p2\n' % (
1131 fm.plain(b' rev linkrev %s %s p2\n' % (
1132 b'nodeid'.ljust(idlen),
1132 b'nodeid'.ljust(idlen),
1133 b'p1'.ljust(idlen)))
1133 b'p1'.ljust(idlen)))
1134
1134
1135 for rev in store:
1135 for rev in store:
1136 node = store.node(rev)
1136 node = store.node(rev)
1137 parents = store.parents(node)
1137 parents = store.parents(node)
1138
1138
1139 fm.startitem()
1139 fm.startitem()
1140 fm.write(b'rev', b'%6d ', rev)
1140 fm.write(b'rev', b'%6d ', rev)
1141 fm.write(b'linkrev', '%7d ', store.linkrev(rev))
1141 fm.write(b'linkrev', '%7d ', store.linkrev(rev))
1142 fm.write(b'node', '%s ', shortfn(node))
1142 fm.write(b'node', '%s ', shortfn(node))
1143 fm.write(b'p1', '%s ', shortfn(parents[0]))
1143 fm.write(b'p1', '%s ', shortfn(parents[0]))
1144 fm.write(b'p2', '%s', shortfn(parents[1]))
1144 fm.write(b'p2', '%s', shortfn(parents[1]))
1145 fm.plain(b'\n')
1145 fm.plain(b'\n')
1146
1146
1147 fm.end()
1147 fm.end()
1148
1148
1149 @command('debugindexdot', cmdutil.debugrevlogopts,
1149 @command('debugindexdot', cmdutil.debugrevlogopts,
1150 _('-c|-m|FILE'), optionalrepo=True)
1150 _('-c|-m|FILE'), optionalrepo=True)
1151 def debugindexdot(ui, repo, file_=None, **opts):
1151 def debugindexdot(ui, repo, file_=None, **opts):
1152 """dump an index DAG as a graphviz dot file"""
1152 """dump an index DAG as a graphviz dot file"""
1153 opts = pycompat.byteskwargs(opts)
1153 opts = pycompat.byteskwargs(opts)
1154 r = cmdutil.openstorage(repo, 'debugindexdot', file_, opts)
1154 r = cmdutil.openstorage(repo, 'debugindexdot', file_, opts)
1155 ui.write(("digraph G {\n"))
1155 ui.write(("digraph G {\n"))
1156 for i in r:
1156 for i in r:
1157 node = r.node(i)
1157 node = r.node(i)
1158 pp = r.parents(node)
1158 pp = r.parents(node)
1159 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1159 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1160 if pp[1] != nullid:
1160 if pp[1] != nullid:
1161 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1161 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1162 ui.write("}\n")
1162 ui.write("}\n")
1163
1163
1164 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1164 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1165 def debuginstall(ui, **opts):
1165 def debuginstall(ui, **opts):
1166 '''test Mercurial installation
1166 '''test Mercurial installation
1167
1167
1168 Returns 0 on success.
1168 Returns 0 on success.
1169 '''
1169 '''
1170 opts = pycompat.byteskwargs(opts)
1170 opts = pycompat.byteskwargs(opts)
1171
1171
1172 def writetemp(contents):
1172 def writetemp(contents):
1173 (fd, name) = pycompat.mkstemp(prefix="hg-debuginstall-")
1173 (fd, name) = pycompat.mkstemp(prefix="hg-debuginstall-")
1174 f = os.fdopen(fd, r"wb")
1174 f = os.fdopen(fd, r"wb")
1175 f.write(contents)
1175 f.write(contents)
1176 f.close()
1176 f.close()
1177 return name
1177 return name
1178
1178
1179 problems = 0
1179 problems = 0
1180
1180
1181 fm = ui.formatter('debuginstall', opts)
1181 fm = ui.formatter('debuginstall', opts)
1182 fm.startitem()
1182 fm.startitem()
1183
1183
1184 # encoding
1184 # encoding
1185 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1185 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1186 err = None
1186 err = None
1187 try:
1187 try:
1188 codecs.lookup(pycompat.sysstr(encoding.encoding))
1188 codecs.lookup(pycompat.sysstr(encoding.encoding))
1189 except LookupError as inst:
1189 except LookupError as inst:
1190 err = stringutil.forcebytestr(inst)
1190 err = stringutil.forcebytestr(inst)
1191 problems += 1
1191 problems += 1
1192 fm.condwrite(err, 'encodingerror', _(" %s\n"
1192 fm.condwrite(err, 'encodingerror', _(" %s\n"
1193 " (check that your locale is properly set)\n"), err)
1193 " (check that your locale is properly set)\n"), err)
1194
1194
1195 # Python
1195 # Python
1196 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1196 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1197 pycompat.sysexecutable)
1197 pycompat.sysexecutable)
1198 fm.write('pythonver', _("checking Python version (%s)\n"),
1198 fm.write('pythonver', _("checking Python version (%s)\n"),
1199 ("%d.%d.%d" % sys.version_info[:3]))
1199 ("%d.%d.%d" % sys.version_info[:3]))
1200 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1200 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1201 os.path.dirname(pycompat.fsencode(os.__file__)))
1201 os.path.dirname(pycompat.fsencode(os.__file__)))
1202
1202
1203 security = set(sslutil.supportedprotocols)
1203 security = set(sslutil.supportedprotocols)
1204 if sslutil.hassni:
1204 if sslutil.hassni:
1205 security.add('sni')
1205 security.add('sni')
1206
1206
1207 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1207 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1208 fm.formatlist(sorted(security), name='protocol',
1208 fm.formatlist(sorted(security), name='protocol',
1209 fmt='%s', sep=','))
1209 fmt='%s', sep=','))
1210
1210
1211 # These are warnings, not errors. So don't increment problem count. This
1211 # These are warnings, not errors. So don't increment problem count. This
1212 # may change in the future.
1212 # may change in the future.
1213 if 'tls1.2' not in security:
1213 if 'tls1.2' not in security:
1214 fm.plain(_(' TLS 1.2 not supported by Python install; '
1214 fm.plain(_(' TLS 1.2 not supported by Python install; '
1215 'network connections lack modern security\n'))
1215 'network connections lack modern security\n'))
1216 if 'sni' not in security:
1216 if 'sni' not in security:
1217 fm.plain(_(' SNI not supported by Python install; may have '
1217 fm.plain(_(' SNI not supported by Python install; may have '
1218 'connectivity issues with some servers\n'))
1218 'connectivity issues with some servers\n'))
1219
1219
1220 # TODO print CA cert info
1220 # TODO print CA cert info
1221
1221
1222 # hg version
1222 # hg version
1223 hgver = util.version()
1223 hgver = util.version()
1224 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1224 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1225 hgver.split('+')[0])
1225 hgver.split('+')[0])
1226 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1226 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1227 '+'.join(hgver.split('+')[1:]))
1227 '+'.join(hgver.split('+')[1:]))
1228
1228
1229 # compiled modules
1229 # compiled modules
1230 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1230 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1231 policy.policy)
1231 policy.policy)
1232 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1232 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1233 os.path.dirname(pycompat.fsencode(__file__)))
1233 os.path.dirname(pycompat.fsencode(__file__)))
1234
1234
1235 if policy.policy in ('c', 'allow'):
1235 if policy.policy in ('c', 'allow'):
1236 err = None
1236 err = None
1237 try:
1237 try:
1238 from .cext import (
1238 from .cext import (
1239 base85,
1239 base85,
1240 bdiff,
1240 bdiff,
1241 mpatch,
1241 mpatch,
1242 osutil,
1242 osutil,
1243 )
1243 )
1244 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1244 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1245 except Exception as inst:
1245 except Exception as inst:
1246 err = stringutil.forcebytestr(inst)
1246 err = stringutil.forcebytestr(inst)
1247 problems += 1
1247 problems += 1
1248 fm.condwrite(err, 'extensionserror', " %s\n", err)
1248 fm.condwrite(err, 'extensionserror', " %s\n", err)
1249
1249
1250 compengines = util.compengines._engines.values()
1250 compengines = util.compengines._engines.values()
1251 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1251 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1252 fm.formatlist(sorted(e.name() for e in compengines),
1252 fm.formatlist(sorted(e.name() for e in compengines),
1253 name='compengine', fmt='%s', sep=', '))
1253 name='compengine', fmt='%s', sep=', '))
1254 fm.write('compenginesavail', _('checking available compression engines '
1254 fm.write('compenginesavail', _('checking available compression engines '
1255 '(%s)\n'),
1255 '(%s)\n'),
1256 fm.formatlist(sorted(e.name() for e in compengines
1256 fm.formatlist(sorted(e.name() for e in compengines
1257 if e.available()),
1257 if e.available()),
1258 name='compengine', fmt='%s', sep=', '))
1258 name='compengine', fmt='%s', sep=', '))
1259 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1259 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1260 fm.write('compenginesserver', _('checking available compression engines '
1260 fm.write('compenginesserver', _('checking available compression engines '
1261 'for wire protocol (%s)\n'),
1261 'for wire protocol (%s)\n'),
1262 fm.formatlist([e.name() for e in wirecompengines
1262 fm.formatlist([e.name() for e in wirecompengines
1263 if e.wireprotosupport()],
1263 if e.wireprotosupport()],
1264 name='compengine', fmt='%s', sep=', '))
1264 name='compengine', fmt='%s', sep=', '))
1265 re2 = 'missing'
1265 re2 = 'missing'
1266 if util._re2:
1266 if util._re2:
1267 re2 = 'available'
1267 re2 = 'available'
1268 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1268 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1269 fm.data(re2=bool(util._re2))
1269 fm.data(re2=bool(util._re2))
1270
1270
1271 # templates
1271 # templates
1272 p = templater.templatepaths()
1272 p = templater.templatepaths()
1273 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1273 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1274 fm.condwrite(not p, '', _(" no template directories found\n"))
1274 fm.condwrite(not p, '', _(" no template directories found\n"))
1275 if p:
1275 if p:
1276 m = templater.templatepath("map-cmdline.default")
1276 m = templater.templatepath("map-cmdline.default")
1277 if m:
1277 if m:
1278 # template found, check if it is working
1278 # template found, check if it is working
1279 err = None
1279 err = None
1280 try:
1280 try:
1281 templater.templater.frommapfile(m)
1281 templater.templater.frommapfile(m)
1282 except Exception as inst:
1282 except Exception as inst:
1283 err = stringutil.forcebytestr(inst)
1283 err = stringutil.forcebytestr(inst)
1284 p = None
1284 p = None
1285 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1285 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1286 else:
1286 else:
1287 p = None
1287 p = None
1288 fm.condwrite(p, 'defaulttemplate',
1288 fm.condwrite(p, 'defaulttemplate',
1289 _("checking default template (%s)\n"), m)
1289 _("checking default template (%s)\n"), m)
1290 fm.condwrite(not m, 'defaulttemplatenotfound',
1290 fm.condwrite(not m, 'defaulttemplatenotfound',
1291 _(" template '%s' not found\n"), "default")
1291 _(" template '%s' not found\n"), "default")
1292 if not p:
1292 if not p:
1293 problems += 1
1293 problems += 1
1294 fm.condwrite(not p, '',
1294 fm.condwrite(not p, '',
1295 _(" (templates seem to have been installed incorrectly)\n"))
1295 _(" (templates seem to have been installed incorrectly)\n"))
1296
1296
1297 # editor
1297 # editor
1298 editor = ui.geteditor()
1298 editor = ui.geteditor()
1299 editor = util.expandpath(editor)
1299 editor = util.expandpath(editor)
1300 editorbin = procutil.shellsplit(editor)[0]
1300 editorbin = procutil.shellsplit(editor)[0]
1301 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1301 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1302 cmdpath = procutil.findexe(editorbin)
1302 cmdpath = procutil.findexe(editorbin)
1303 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1303 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1304 _(" No commit editor set and can't find %s in PATH\n"
1304 _(" No commit editor set and can't find %s in PATH\n"
1305 " (specify a commit editor in your configuration"
1305 " (specify a commit editor in your configuration"
1306 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1306 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1307 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1307 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1308 _(" Can't find editor '%s' in PATH\n"
1308 _(" Can't find editor '%s' in PATH\n"
1309 " (specify a commit editor in your configuration"
1309 " (specify a commit editor in your configuration"
1310 " file)\n"), not cmdpath and editorbin)
1310 " file)\n"), not cmdpath and editorbin)
1311 if not cmdpath and editor != 'vi':
1311 if not cmdpath and editor != 'vi':
1312 problems += 1
1312 problems += 1
1313
1313
1314 # check username
1314 # check username
1315 username = None
1315 username = None
1316 err = None
1316 err = None
1317 try:
1317 try:
1318 username = ui.username()
1318 username = ui.username()
1319 except error.Abort as e:
1319 except error.Abort as e:
1320 err = stringutil.forcebytestr(e)
1320 err = stringutil.forcebytestr(e)
1321 problems += 1
1321 problems += 1
1322
1322
1323 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1323 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1324 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1324 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1325 " (specify a username in your configuration file)\n"), err)
1325 " (specify a username in your configuration file)\n"), err)
1326
1326
1327 fm.condwrite(not problems, '',
1327 fm.condwrite(not problems, '',
1328 _("no problems detected\n"))
1328 _("no problems detected\n"))
1329 if not problems:
1329 if not problems:
1330 fm.data(problems=problems)
1330 fm.data(problems=problems)
1331 fm.condwrite(problems, 'problems',
1331 fm.condwrite(problems, 'problems',
1332 _("%d problems detected,"
1332 _("%d problems detected,"
1333 " please check your install!\n"), problems)
1333 " please check your install!\n"), problems)
1334 fm.end()
1334 fm.end()
1335
1335
1336 return problems
1336 return problems
1337
1337
1338 @command('debugknown', [], _('REPO ID...'), norepo=True)
1338 @command('debugknown', [], _('REPO ID...'), norepo=True)
1339 def debugknown(ui, repopath, *ids, **opts):
1339 def debugknown(ui, repopath, *ids, **opts):
1340 """test whether node ids are known to a repo
1340 """test whether node ids are known to a repo
1341
1341
1342 Every ID must be a full-length hex node id string. Returns a list of 0s
1342 Every ID must be a full-length hex node id string. Returns a list of 0s
1343 and 1s indicating unknown/known.
1343 and 1s indicating unknown/known.
1344 """
1344 """
1345 opts = pycompat.byteskwargs(opts)
1345 opts = pycompat.byteskwargs(opts)
1346 repo = hg.peer(ui, opts, repopath)
1346 repo = hg.peer(ui, opts, repopath)
1347 if not repo.capable('known'):
1347 if not repo.capable('known'):
1348 raise error.Abort("known() not supported by target repository")
1348 raise error.Abort("known() not supported by target repository")
1349 flags = repo.known([bin(s) for s in ids])
1349 flags = repo.known([bin(s) for s in ids])
1350 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1350 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1351
1351
1352 @command('debuglabelcomplete', [], _('LABEL...'))
1352 @command('debuglabelcomplete', [], _('LABEL...'))
1353 def debuglabelcomplete(ui, repo, *args):
1353 def debuglabelcomplete(ui, repo, *args):
1354 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1354 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1355 debugnamecomplete(ui, repo, *args)
1355 debugnamecomplete(ui, repo, *args)
1356
1356
1357 @command('debuglocks',
1357 @command('debuglocks',
1358 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1358 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1359 ('W', 'force-wlock', None,
1359 ('W', 'force-wlock', None,
1360 _('free the working state lock (DANGEROUS)')),
1360 _('free the working state lock (DANGEROUS)')),
1361 ('s', 'set-lock', None, _('set the store lock until stopped')),
1361 ('s', 'set-lock', None, _('set the store lock until stopped')),
1362 ('S', 'set-wlock', None,
1362 ('S', 'set-wlock', None,
1363 _('set the working state lock until stopped'))],
1363 _('set the working state lock until stopped'))],
1364 _('[OPTION]...'))
1364 _('[OPTION]...'))
1365 def debuglocks(ui, repo, **opts):
1365 def debuglocks(ui, repo, **opts):
1366 """show or modify state of locks
1366 """show or modify state of locks
1367
1367
1368 By default, this command will show which locks are held. This
1368 By default, this command will show which locks are held. This
1369 includes the user and process holding the lock, the amount of time
1369 includes the user and process holding the lock, the amount of time
1370 the lock has been held, and the machine name where the process is
1370 the lock has been held, and the machine name where the process is
1371 running if it's not local.
1371 running if it's not local.
1372
1372
1373 Locks protect the integrity of Mercurial's data, so should be
1373 Locks protect the integrity of Mercurial's data, so should be
1374 treated with care. System crashes or other interruptions may cause
1374 treated with care. System crashes or other interruptions may cause
1375 locks to not be properly released, though Mercurial will usually
1375 locks to not be properly released, though Mercurial will usually
1376 detect and remove such stale locks automatically.
1376 detect and remove such stale locks automatically.
1377
1377
1378 However, detecting stale locks may not always be possible (for
1378 However, detecting stale locks may not always be possible (for
1379 instance, on a shared filesystem). Removing locks may also be
1379 instance, on a shared filesystem). Removing locks may also be
1380 blocked by filesystem permissions.
1380 blocked by filesystem permissions.
1381
1381
1382 Setting a lock will prevent other commands from changing the data.
1382 Setting a lock will prevent other commands from changing the data.
1383 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1383 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1384 The set locks are removed when the command exits.
1384 The set locks are removed when the command exits.
1385
1385
1386 Returns 0 if no locks are held.
1386 Returns 0 if no locks are held.
1387
1387
1388 """
1388 """
1389
1389
1390 if opts.get(r'force_lock'):
1390 if opts.get(r'force_lock'):
1391 repo.svfs.unlink('lock')
1391 repo.svfs.unlink('lock')
1392 if opts.get(r'force_wlock'):
1392 if opts.get(r'force_wlock'):
1393 repo.vfs.unlink('wlock')
1393 repo.vfs.unlink('wlock')
1394 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1394 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1395 return 0
1395 return 0
1396
1396
1397 locks = []
1397 locks = []
1398 try:
1398 try:
1399 if opts.get(r'set_wlock'):
1399 if opts.get(r'set_wlock'):
1400 try:
1400 try:
1401 locks.append(repo.wlock(False))
1401 locks.append(repo.wlock(False))
1402 except error.LockHeld:
1402 except error.LockHeld:
1403 raise error.Abort(_('wlock is already held'))
1403 raise error.Abort(_('wlock is already held'))
1404 if opts.get(r'set_lock'):
1404 if opts.get(r'set_lock'):
1405 try:
1405 try:
1406 locks.append(repo.lock(False))
1406 locks.append(repo.lock(False))
1407 except error.LockHeld:
1407 except error.LockHeld:
1408 raise error.Abort(_('lock is already held'))
1408 raise error.Abort(_('lock is already held'))
1409 if len(locks):
1409 if len(locks):
1410 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1410 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1411 return 0
1411 return 0
1412 finally:
1412 finally:
1413 release(*locks)
1413 release(*locks)
1414
1414
1415 now = time.time()
1415 now = time.time()
1416 held = 0
1416 held = 0
1417
1417
1418 def report(vfs, name, method):
1418 def report(vfs, name, method):
1419 # this causes stale locks to get reaped for more accurate reporting
1419 # this causes stale locks to get reaped for more accurate reporting
1420 try:
1420 try:
1421 l = method(False)
1421 l = method(False)
1422 except error.LockHeld:
1422 except error.LockHeld:
1423 l = None
1423 l = None
1424
1424
1425 if l:
1425 if l:
1426 l.release()
1426 l.release()
1427 else:
1427 else:
1428 try:
1428 try:
1429 st = vfs.lstat(name)
1429 st = vfs.lstat(name)
1430 age = now - st[stat.ST_MTIME]
1430 age = now - st[stat.ST_MTIME]
1431 user = util.username(st.st_uid)
1431 user = util.username(st.st_uid)
1432 locker = vfs.readlock(name)
1432 locker = vfs.readlock(name)
1433 if ":" in locker:
1433 if ":" in locker:
1434 host, pid = locker.split(':')
1434 host, pid = locker.split(':')
1435 if host == socket.gethostname():
1435 if host == socket.gethostname():
1436 locker = 'user %s, process %s' % (user, pid)
1436 locker = 'user %s, process %s' % (user, pid)
1437 else:
1437 else:
1438 locker = 'user %s, process %s, host %s' \
1438 locker = 'user %s, process %s, host %s' \
1439 % (user, pid, host)
1439 % (user, pid, host)
1440 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1440 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1441 return 1
1441 return 1
1442 except OSError as e:
1442 except OSError as e:
1443 if e.errno != errno.ENOENT:
1443 if e.errno != errno.ENOENT:
1444 raise
1444 raise
1445
1445
1446 ui.write(("%-6s free\n") % (name + ":"))
1446 ui.write(("%-6s free\n") % (name + ":"))
1447 return 0
1447 return 0
1448
1448
1449 held += report(repo.svfs, "lock", repo.lock)
1449 held += report(repo.svfs, "lock", repo.lock)
1450 held += report(repo.vfs, "wlock", repo.wlock)
1450 held += report(repo.vfs, "wlock", repo.wlock)
1451
1451
1452 return held
1452 return held
1453
1453
1454 @command('debugmanifestfulltextcache', [
1454 @command('debugmanifestfulltextcache', [
1455 ('', 'clear', False, _('clear the cache')),
1455 ('', 'clear', False, _('clear the cache')),
1456 ('a', 'add', '', _('add the given manifest node to the cache'),
1456 ('a', 'add', '', _('add the given manifest node to the cache'),
1457 _('NODE'))
1457 _('NODE'))
1458 ], '')
1458 ], '')
1459 def debugmanifestfulltextcache(ui, repo, add=None, **opts):
1459 def debugmanifestfulltextcache(ui, repo, add=None, **opts):
1460 """show, clear or amend the contents of the manifest fulltext cache"""
1460 """show, clear or amend the contents of the manifest fulltext cache"""
1461 with repo.lock():
1461 with repo.lock():
1462 r = repo.manifestlog.getstorage(b'')
1462 r = repo.manifestlog.getstorage(b'')
1463 try:
1463 try:
1464 cache = r._fulltextcache
1464 cache = r._fulltextcache
1465 except AttributeError:
1465 except AttributeError:
1466 ui.warn(_(
1466 ui.warn(_(
1467 "Current revlog implementation doesn't appear to have a "
1467 "Current revlog implementation doesn't appear to have a "
1468 'manifest fulltext cache\n'))
1468 'manifest fulltext cache\n'))
1469 return
1469 return
1470
1470
1471 if opts.get(r'clear'):
1471 if opts.get(r'clear'):
1472 cache.clear()
1472 cache.clear()
1473
1473
1474 if add:
1474 if add:
1475 try:
1475 try:
1476 manifest = repo.manifestlog[r.lookup(add)]
1476 manifest = repo.manifestlog[r.lookup(add)]
1477 except error.LookupError as e:
1477 except error.LookupError as e:
1478 raise error.Abort(e, hint="Check your manifest node id")
1478 raise error.Abort(e, hint="Check your manifest node id")
1479 manifest.read() # stores revisision in cache too
1479 manifest.read() # stores revisision in cache too
1480
1480
1481 if not len(cache):
1481 if not len(cache):
1482 ui.write(_('Cache empty'))
1482 ui.write(_('Cache empty'))
1483 else:
1483 else:
1484 ui.write(
1484 ui.write(
1485 _('Cache contains %d manifest entries, in order of most to '
1485 _('Cache contains %d manifest entries, in order of most to '
1486 'least recent:\n') % (len(cache),))
1486 'least recent:\n') % (len(cache),))
1487 totalsize = 0
1487 totalsize = 0
1488 for nodeid in cache:
1488 for nodeid in cache:
1489 # Use cache.get to not update the LRU order
1489 # Use cache.get to not update the LRU order
1490 data = cache.get(nodeid)
1490 data = cache.get(nodeid)
1491 size = len(data)
1491 size = len(data)
1492 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
1492 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
1493 ui.write(_('id: %s, size %s\n') % (
1493 ui.write(_('id: %s, size %s\n') % (
1494 hex(nodeid), util.bytecount(size)))
1494 hex(nodeid), util.bytecount(size)))
1495 ondisk = cache._opener.stat('manifestfulltextcache').st_size
1495 ondisk = cache._opener.stat('manifestfulltextcache').st_size
1496 ui.write(
1496 ui.write(
1497 _('Total cache data size %s, on-disk %s\n') % (
1497 _('Total cache data size %s, on-disk %s\n') % (
1498 util.bytecount(totalsize), util.bytecount(ondisk))
1498 util.bytecount(totalsize), util.bytecount(ondisk))
1499 )
1499 )
1500
1500
1501 @command('debugmergestate', [], '')
1501 @command('debugmergestate', [], '')
1502 def debugmergestate(ui, repo, *args):
1502 def debugmergestate(ui, repo, *args):
1503 """print merge state
1503 """print merge state
1504
1504
1505 Use --verbose to print out information about whether v1 or v2 merge state
1505 Use --verbose to print out information about whether v1 or v2 merge state
1506 was chosen."""
1506 was chosen."""
1507 def _hashornull(h):
1507 def _hashornull(h):
1508 if h == nullhex:
1508 if h == nullhex:
1509 return 'null'
1509 return 'null'
1510 else:
1510 else:
1511 return h
1511 return h
1512
1512
1513 def printrecords(version):
1513 def printrecords(version):
1514 ui.write(('* version %d records\n') % version)
1514 ui.write(('* version %d records\n') % version)
1515 if version == 1:
1515 if version == 1:
1516 records = v1records
1516 records = v1records
1517 else:
1517 else:
1518 records = v2records
1518 records = v2records
1519
1519
1520 for rtype, record in records:
1520 for rtype, record in records:
1521 # pretty print some record types
1521 # pretty print some record types
1522 if rtype == 'L':
1522 if rtype == 'L':
1523 ui.write(('local: %s\n') % record)
1523 ui.write(('local: %s\n') % record)
1524 elif rtype == 'O':
1524 elif rtype == 'O':
1525 ui.write(('other: %s\n') % record)
1525 ui.write(('other: %s\n') % record)
1526 elif rtype == 'm':
1526 elif rtype == 'm':
1527 driver, mdstate = record.split('\0', 1)
1527 driver, mdstate = record.split('\0', 1)
1528 ui.write(('merge driver: %s (state "%s")\n')
1528 ui.write(('merge driver: %s (state "%s")\n')
1529 % (driver, mdstate))
1529 % (driver, mdstate))
1530 elif rtype in 'FDC':
1530 elif rtype in 'FDC':
1531 r = record.split('\0')
1531 r = record.split('\0')
1532 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1532 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1533 if version == 1:
1533 if version == 1:
1534 onode = 'not stored in v1 format'
1534 onode = 'not stored in v1 format'
1535 flags = r[7]
1535 flags = r[7]
1536 else:
1536 else:
1537 onode, flags = r[7:9]
1537 onode, flags = r[7:9]
1538 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1538 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1539 % (f, rtype, state, _hashornull(hash)))
1539 % (f, rtype, state, _hashornull(hash)))
1540 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1540 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1541 ui.write((' ancestor path: %s (node %s)\n')
1541 ui.write((' ancestor path: %s (node %s)\n')
1542 % (afile, _hashornull(anode)))
1542 % (afile, _hashornull(anode)))
1543 ui.write((' other path: %s (node %s)\n')
1543 ui.write((' other path: %s (node %s)\n')
1544 % (ofile, _hashornull(onode)))
1544 % (ofile, _hashornull(onode)))
1545 elif rtype == 'f':
1545 elif rtype == 'f':
1546 filename, rawextras = record.split('\0', 1)
1546 filename, rawextras = record.split('\0', 1)
1547 extras = rawextras.split('\0')
1547 extras = rawextras.split('\0')
1548 i = 0
1548 i = 0
1549 extrastrings = []
1549 extrastrings = []
1550 while i < len(extras):
1550 while i < len(extras):
1551 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1551 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1552 i += 2
1552 i += 2
1553
1553
1554 ui.write(('file extras: %s (%s)\n')
1554 ui.write(('file extras: %s (%s)\n')
1555 % (filename, ', '.join(extrastrings)))
1555 % (filename, ', '.join(extrastrings)))
1556 elif rtype == 'l':
1556 elif rtype == 'l':
1557 labels = record.split('\0', 2)
1557 labels = record.split('\0', 2)
1558 labels = [l for l in labels if len(l) > 0]
1558 labels = [l for l in labels if len(l) > 0]
1559 ui.write(('labels:\n'))
1559 ui.write(('labels:\n'))
1560 ui.write((' local: %s\n' % labels[0]))
1560 ui.write((' local: %s\n' % labels[0]))
1561 ui.write((' other: %s\n' % labels[1]))
1561 ui.write((' other: %s\n' % labels[1]))
1562 if len(labels) > 2:
1562 if len(labels) > 2:
1563 ui.write((' base: %s\n' % labels[2]))
1563 ui.write((' base: %s\n' % labels[2]))
1564 else:
1564 else:
1565 ui.write(('unrecognized entry: %s\t%s\n')
1565 ui.write(('unrecognized entry: %s\t%s\n')
1566 % (rtype, record.replace('\0', '\t')))
1566 % (rtype, record.replace('\0', '\t')))
1567
1567
1568 # Avoid mergestate.read() since it may raise an exception for unsupported
1568 # Avoid mergestate.read() since it may raise an exception for unsupported
1569 # merge state records. We shouldn't be doing this, but this is OK since this
1569 # merge state records. We shouldn't be doing this, but this is OK since this
1570 # command is pretty low-level.
1570 # command is pretty low-level.
1571 ms = mergemod.mergestate(repo)
1571 ms = mergemod.mergestate(repo)
1572
1572
1573 # sort so that reasonable information is on top
1573 # sort so that reasonable information is on top
1574 v1records = ms._readrecordsv1()
1574 v1records = ms._readrecordsv1()
1575 v2records = ms._readrecordsv2()
1575 v2records = ms._readrecordsv2()
1576 order = 'LOml'
1576 order = 'LOml'
1577 def key(r):
1577 def key(r):
1578 idx = order.find(r[0])
1578 idx = order.find(r[0])
1579 if idx == -1:
1579 if idx == -1:
1580 return (1, r[1])
1580 return (1, r[1])
1581 else:
1581 else:
1582 return (0, idx)
1582 return (0, idx)
1583 v1records.sort(key=key)
1583 v1records.sort(key=key)
1584 v2records.sort(key=key)
1584 v2records.sort(key=key)
1585
1585
1586 if not v1records and not v2records:
1586 if not v1records and not v2records:
1587 ui.write(('no merge state found\n'))
1587 ui.write(('no merge state found\n'))
1588 elif not v2records:
1588 elif not v2records:
1589 ui.note(('no version 2 merge state\n'))
1589 ui.note(('no version 2 merge state\n'))
1590 printrecords(1)
1590 printrecords(1)
1591 elif ms._v1v2match(v1records, v2records):
1591 elif ms._v1v2match(v1records, v2records):
1592 ui.note(('v1 and v2 states match: using v2\n'))
1592 ui.note(('v1 and v2 states match: using v2\n'))
1593 printrecords(2)
1593 printrecords(2)
1594 else:
1594 else:
1595 ui.note(('v1 and v2 states mismatch: using v1\n'))
1595 ui.note(('v1 and v2 states mismatch: using v1\n'))
1596 printrecords(1)
1596 printrecords(1)
1597 if ui.verbose:
1597 if ui.verbose:
1598 printrecords(2)
1598 printrecords(2)
1599
1599
1600 @command('debugnamecomplete', [], _('NAME...'))
1600 @command('debugnamecomplete', [], _('NAME...'))
1601 def debugnamecomplete(ui, repo, *args):
1601 def debugnamecomplete(ui, repo, *args):
1602 '''complete "names" - tags, open branch names, bookmark names'''
1602 '''complete "names" - tags, open branch names, bookmark names'''
1603
1603
1604 names = set()
1604 names = set()
1605 # since we previously only listed open branches, we will handle that
1605 # since we previously only listed open branches, we will handle that
1606 # specially (after this for loop)
1606 # specially (after this for loop)
1607 for name, ns in repo.names.iteritems():
1607 for name, ns in repo.names.iteritems():
1608 if name != 'branches':
1608 if name != 'branches':
1609 names.update(ns.listnames(repo))
1609 names.update(ns.listnames(repo))
1610 names.update(tag for (tag, heads, tip, closed)
1610 names.update(tag for (tag, heads, tip, closed)
1611 in repo.branchmap().iterbranches() if not closed)
1611 in repo.branchmap().iterbranches() if not closed)
1612 completions = set()
1612 completions = set()
1613 if not args:
1613 if not args:
1614 args = ['']
1614 args = ['']
1615 for a in args:
1615 for a in args:
1616 completions.update(n for n in names if n.startswith(a))
1616 completions.update(n for n in names if n.startswith(a))
1617 ui.write('\n'.join(sorted(completions)))
1617 ui.write('\n'.join(sorted(completions)))
1618 ui.write('\n')
1618 ui.write('\n')
1619
1619
1620 @command('debugobsolete',
1620 @command('debugobsolete',
1621 [('', 'flags', 0, _('markers flag')),
1621 [('', 'flags', 0, _('markers flag')),
1622 ('', 'record-parents', False,
1622 ('', 'record-parents', False,
1623 _('record parent information for the precursor')),
1623 _('record parent information for the precursor')),
1624 ('r', 'rev', [], _('display markers relevant to REV')),
1624 ('r', 'rev', [], _('display markers relevant to REV')),
1625 ('', 'exclusive', False, _('restrict display to markers only '
1625 ('', 'exclusive', False, _('restrict display to markers only '
1626 'relevant to REV')),
1626 'relevant to REV')),
1627 ('', 'index', False, _('display index of the marker')),
1627 ('', 'index', False, _('display index of the marker')),
1628 ('', 'delete', [], _('delete markers specified by indices')),
1628 ('', 'delete', [], _('delete markers specified by indices')),
1629 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1629 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1630 _('[OBSOLETED [REPLACEMENT ...]]'))
1630 _('[OBSOLETED [REPLACEMENT ...]]'))
1631 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1631 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1632 """create arbitrary obsolete marker
1632 """create arbitrary obsolete marker
1633
1633
1634 With no arguments, displays the list of obsolescence markers."""
1634 With no arguments, displays the list of obsolescence markers."""
1635
1635
1636 opts = pycompat.byteskwargs(opts)
1636 opts = pycompat.byteskwargs(opts)
1637
1637
1638 def parsenodeid(s):
1638 def parsenodeid(s):
1639 try:
1639 try:
1640 # We do not use revsingle/revrange functions here to accept
1640 # We do not use revsingle/revrange functions here to accept
1641 # arbitrary node identifiers, possibly not present in the
1641 # arbitrary node identifiers, possibly not present in the
1642 # local repository.
1642 # local repository.
1643 n = bin(s)
1643 n = bin(s)
1644 if len(n) != len(nullid):
1644 if len(n) != len(nullid):
1645 raise TypeError()
1645 raise TypeError()
1646 return n
1646 return n
1647 except TypeError:
1647 except TypeError:
1648 raise error.Abort('changeset references must be full hexadecimal '
1648 raise error.Abort('changeset references must be full hexadecimal '
1649 'node identifiers')
1649 'node identifiers')
1650
1650
1651 if opts.get('delete'):
1651 if opts.get('delete'):
1652 indices = []
1652 indices = []
1653 for v in opts.get('delete'):
1653 for v in opts.get('delete'):
1654 try:
1654 try:
1655 indices.append(int(v))
1655 indices.append(int(v))
1656 except ValueError:
1656 except ValueError:
1657 raise error.Abort(_('invalid index value: %r') % v,
1657 raise error.Abort(_('invalid index value: %r') % v,
1658 hint=_('use integers for indices'))
1658 hint=_('use integers for indices'))
1659
1659
1660 if repo.currenttransaction():
1660 if repo.currenttransaction():
1661 raise error.Abort(_('cannot delete obsmarkers in the middle '
1661 raise error.Abort(_('cannot delete obsmarkers in the middle '
1662 'of transaction.'))
1662 'of transaction.'))
1663
1663
1664 with repo.lock():
1664 with repo.lock():
1665 n = repair.deleteobsmarkers(repo.obsstore, indices)
1665 n = repair.deleteobsmarkers(repo.obsstore, indices)
1666 ui.write(_('deleted %i obsolescence markers\n') % n)
1666 ui.write(_('deleted %i obsolescence markers\n') % n)
1667
1667
1668 return
1668 return
1669
1669
1670 if precursor is not None:
1670 if precursor is not None:
1671 if opts['rev']:
1671 if opts['rev']:
1672 raise error.Abort('cannot select revision when creating marker')
1672 raise error.Abort('cannot select revision when creating marker')
1673 metadata = {}
1673 metadata = {}
1674 metadata['user'] = encoding.fromlocal(opts['user'] or ui.username())
1674 metadata['user'] = encoding.fromlocal(opts['user'] or ui.username())
1675 succs = tuple(parsenodeid(succ) for succ in successors)
1675 succs = tuple(parsenodeid(succ) for succ in successors)
1676 l = repo.lock()
1676 l = repo.lock()
1677 try:
1677 try:
1678 tr = repo.transaction('debugobsolete')
1678 tr = repo.transaction('debugobsolete')
1679 try:
1679 try:
1680 date = opts.get('date')
1680 date = opts.get('date')
1681 if date:
1681 if date:
1682 date = dateutil.parsedate(date)
1682 date = dateutil.parsedate(date)
1683 else:
1683 else:
1684 date = None
1684 date = None
1685 prec = parsenodeid(precursor)
1685 prec = parsenodeid(precursor)
1686 parents = None
1686 parents = None
1687 if opts['record_parents']:
1687 if opts['record_parents']:
1688 if prec not in repo.unfiltered():
1688 if prec not in repo.unfiltered():
1689 raise error.Abort('cannot used --record-parents on '
1689 raise error.Abort('cannot used --record-parents on '
1690 'unknown changesets')
1690 'unknown changesets')
1691 parents = repo.unfiltered()[prec].parents()
1691 parents = repo.unfiltered()[prec].parents()
1692 parents = tuple(p.node() for p in parents)
1692 parents = tuple(p.node() for p in parents)
1693 repo.obsstore.create(tr, prec, succs, opts['flags'],
1693 repo.obsstore.create(tr, prec, succs, opts['flags'],
1694 parents=parents, date=date,
1694 parents=parents, date=date,
1695 metadata=metadata, ui=ui)
1695 metadata=metadata, ui=ui)
1696 tr.close()
1696 tr.close()
1697 except ValueError as exc:
1697 except ValueError as exc:
1698 raise error.Abort(_('bad obsmarker input: %s') %
1698 raise error.Abort(_('bad obsmarker input: %s') %
1699 pycompat.bytestr(exc))
1699 pycompat.bytestr(exc))
1700 finally:
1700 finally:
1701 tr.release()
1701 tr.release()
1702 finally:
1702 finally:
1703 l.release()
1703 l.release()
1704 else:
1704 else:
1705 if opts['rev']:
1705 if opts['rev']:
1706 revs = scmutil.revrange(repo, opts['rev'])
1706 revs = scmutil.revrange(repo, opts['rev'])
1707 nodes = [repo[r].node() for r in revs]
1707 nodes = [repo[r].node() for r in revs]
1708 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1708 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1709 exclusive=opts['exclusive']))
1709 exclusive=opts['exclusive']))
1710 markers.sort(key=lambda x: x._data)
1710 markers.sort(key=lambda x: x._data)
1711 else:
1711 else:
1712 markers = obsutil.getmarkers(repo)
1712 markers = obsutil.getmarkers(repo)
1713
1713
1714 markerstoiter = markers
1714 markerstoiter = markers
1715 isrelevant = lambda m: True
1715 isrelevant = lambda m: True
1716 if opts.get('rev') and opts.get('index'):
1716 if opts.get('rev') and opts.get('index'):
1717 markerstoiter = obsutil.getmarkers(repo)
1717 markerstoiter = obsutil.getmarkers(repo)
1718 markerset = set(markers)
1718 markerset = set(markers)
1719 isrelevant = lambda m: m in markerset
1719 isrelevant = lambda m: m in markerset
1720
1720
1721 fm = ui.formatter('debugobsolete', opts)
1721 fm = ui.formatter('debugobsolete', opts)
1722 for i, m in enumerate(markerstoiter):
1722 for i, m in enumerate(markerstoiter):
1723 if not isrelevant(m):
1723 if not isrelevant(m):
1724 # marker can be irrelevant when we're iterating over a set
1724 # marker can be irrelevant when we're iterating over a set
1725 # of markers (markerstoiter) which is bigger than the set
1725 # of markers (markerstoiter) which is bigger than the set
1726 # of markers we want to display (markers)
1726 # of markers we want to display (markers)
1727 # this can happen if both --index and --rev options are
1727 # this can happen if both --index and --rev options are
1728 # provided and thus we need to iterate over all of the markers
1728 # provided and thus we need to iterate over all of the markers
1729 # to get the correct indices, but only display the ones that
1729 # to get the correct indices, but only display the ones that
1730 # are relevant to --rev value
1730 # are relevant to --rev value
1731 continue
1731 continue
1732 fm.startitem()
1732 fm.startitem()
1733 ind = i if opts.get('index') else None
1733 ind = i if opts.get('index') else None
1734 cmdutil.showmarker(fm, m, index=ind)
1734 cmdutil.showmarker(fm, m, index=ind)
1735 fm.end()
1735 fm.end()
1736
1736
1737 @command('debugpathcomplete',
1737 @command('debugpathcomplete',
1738 [('f', 'full', None, _('complete an entire path')),
1738 [('f', 'full', None, _('complete an entire path')),
1739 ('n', 'normal', None, _('show only normal files')),
1739 ('n', 'normal', None, _('show only normal files')),
1740 ('a', 'added', None, _('show only added files')),
1740 ('a', 'added', None, _('show only added files')),
1741 ('r', 'removed', None, _('show only removed files'))],
1741 ('r', 'removed', None, _('show only removed files'))],
1742 _('FILESPEC...'))
1742 _('FILESPEC...'))
1743 def debugpathcomplete(ui, repo, *specs, **opts):
1743 def debugpathcomplete(ui, repo, *specs, **opts):
1744 '''complete part or all of a tracked path
1744 '''complete part or all of a tracked path
1745
1745
1746 This command supports shells that offer path name completion. It
1746 This command supports shells that offer path name completion. It
1747 currently completes only files already known to the dirstate.
1747 currently completes only files already known to the dirstate.
1748
1748
1749 Completion extends only to the next path segment unless
1749 Completion extends only to the next path segment unless
1750 --full is specified, in which case entire paths are used.'''
1750 --full is specified, in which case entire paths are used.'''
1751
1751
1752 def complete(path, acceptable):
1752 def complete(path, acceptable):
1753 dirstate = repo.dirstate
1753 dirstate = repo.dirstate
1754 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1754 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1755 rootdir = repo.root + pycompat.ossep
1755 rootdir = repo.root + pycompat.ossep
1756 if spec != repo.root and not spec.startswith(rootdir):
1756 if spec != repo.root and not spec.startswith(rootdir):
1757 return [], []
1757 return [], []
1758 if os.path.isdir(spec):
1758 if os.path.isdir(spec):
1759 spec += '/'
1759 spec += '/'
1760 spec = spec[len(rootdir):]
1760 spec = spec[len(rootdir):]
1761 fixpaths = pycompat.ossep != '/'
1761 fixpaths = pycompat.ossep != '/'
1762 if fixpaths:
1762 if fixpaths:
1763 spec = spec.replace(pycompat.ossep, '/')
1763 spec = spec.replace(pycompat.ossep, '/')
1764 speclen = len(spec)
1764 speclen = len(spec)
1765 fullpaths = opts[r'full']
1765 fullpaths = opts[r'full']
1766 files, dirs = set(), set()
1766 files, dirs = set(), set()
1767 adddir, addfile = dirs.add, files.add
1767 adddir, addfile = dirs.add, files.add
1768 for f, st in dirstate.iteritems():
1768 for f, st in dirstate.iteritems():
1769 if f.startswith(spec) and st[0] in acceptable:
1769 if f.startswith(spec) and st[0] in acceptable:
1770 if fixpaths:
1770 if fixpaths:
1771 f = f.replace('/', pycompat.ossep)
1771 f = f.replace('/', pycompat.ossep)
1772 if fullpaths:
1772 if fullpaths:
1773 addfile(f)
1773 addfile(f)
1774 continue
1774 continue
1775 s = f.find(pycompat.ossep, speclen)
1775 s = f.find(pycompat.ossep, speclen)
1776 if s >= 0:
1776 if s >= 0:
1777 adddir(f[:s])
1777 adddir(f[:s])
1778 else:
1778 else:
1779 addfile(f)
1779 addfile(f)
1780 return files, dirs
1780 return files, dirs
1781
1781
1782 acceptable = ''
1782 acceptable = ''
1783 if opts[r'normal']:
1783 if opts[r'normal']:
1784 acceptable += 'nm'
1784 acceptable += 'nm'
1785 if opts[r'added']:
1785 if opts[r'added']:
1786 acceptable += 'a'
1786 acceptable += 'a'
1787 if opts[r'removed']:
1787 if opts[r'removed']:
1788 acceptable += 'r'
1788 acceptable += 'r'
1789 cwd = repo.getcwd()
1789 cwd = repo.getcwd()
1790 if not specs:
1790 if not specs:
1791 specs = ['.']
1791 specs = ['.']
1792
1792
1793 files, dirs = set(), set()
1793 files, dirs = set(), set()
1794 for spec in specs:
1794 for spec in specs:
1795 f, d = complete(spec, acceptable or 'nmar')
1795 f, d = complete(spec, acceptable or 'nmar')
1796 files.update(f)
1796 files.update(f)
1797 dirs.update(d)
1797 dirs.update(d)
1798 files.update(dirs)
1798 files.update(dirs)
1799 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1799 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1800 ui.write('\n')
1800 ui.write('\n')
1801
1801
1802 @command('debugpeer', [], _('PATH'), norepo=True)
1802 @command('debugpeer', [], _('PATH'), norepo=True)
1803 def debugpeer(ui, path):
1803 def debugpeer(ui, path):
1804 """establish a connection to a peer repository"""
1804 """establish a connection to a peer repository"""
1805 # Always enable peer request logging. Requires --debug to display
1805 # Always enable peer request logging. Requires --debug to display
1806 # though.
1806 # though.
1807 overrides = {
1807 overrides = {
1808 ('devel', 'debug.peer-request'): True,
1808 ('devel', 'debug.peer-request'): True,
1809 }
1809 }
1810
1810
1811 with ui.configoverride(overrides):
1811 with ui.configoverride(overrides):
1812 peer = hg.peer(ui, {}, path)
1812 peer = hg.peer(ui, {}, path)
1813
1813
1814 local = peer.local() is not None
1814 local = peer.local() is not None
1815 canpush = peer.canpush()
1815 canpush = peer.canpush()
1816
1816
1817 ui.write(_('url: %s\n') % peer.url())
1817 ui.write(_('url: %s\n') % peer.url())
1818 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1818 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1819 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1819 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1820
1820
1821 @command('debugpickmergetool',
1821 @command('debugpickmergetool',
1822 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1822 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1823 ('', 'changedelete', None, _('emulate merging change and delete')),
1823 ('', 'changedelete', None, _('emulate merging change and delete')),
1824 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1824 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1825 _('[PATTERN]...'),
1825 _('[PATTERN]...'),
1826 inferrepo=True)
1826 inferrepo=True)
1827 def debugpickmergetool(ui, repo, *pats, **opts):
1827 def debugpickmergetool(ui, repo, *pats, **opts):
1828 """examine which merge tool is chosen for specified file
1828 """examine which merge tool is chosen for specified file
1829
1829
1830 As described in :hg:`help merge-tools`, Mercurial examines
1830 As described in :hg:`help merge-tools`, Mercurial examines
1831 configurations below in this order to decide which merge tool is
1831 configurations below in this order to decide which merge tool is
1832 chosen for specified file.
1832 chosen for specified file.
1833
1833
1834 1. ``--tool`` option
1834 1. ``--tool`` option
1835 2. ``HGMERGE`` environment variable
1835 2. ``HGMERGE`` environment variable
1836 3. configurations in ``merge-patterns`` section
1836 3. configurations in ``merge-patterns`` section
1837 4. configuration of ``ui.merge``
1837 4. configuration of ``ui.merge``
1838 5. configurations in ``merge-tools`` section
1838 5. configurations in ``merge-tools`` section
1839 6. ``hgmerge`` tool (for historical reason only)
1839 6. ``hgmerge`` tool (for historical reason only)
1840 7. default tool for fallback (``:merge`` or ``:prompt``)
1840 7. default tool for fallback (``:merge`` or ``:prompt``)
1841
1841
1842 This command writes out examination result in the style below::
1842 This command writes out examination result in the style below::
1843
1843
1844 FILE = MERGETOOL
1844 FILE = MERGETOOL
1845
1845
1846 By default, all files known in the first parent context of the
1846 By default, all files known in the first parent context of the
1847 working directory are examined. Use file patterns and/or -I/-X
1847 working directory are examined. Use file patterns and/or -I/-X
1848 options to limit target files. -r/--rev is also useful to examine
1848 options to limit target files. -r/--rev is also useful to examine
1849 files in another context without actual updating to it.
1849 files in another context without actual updating to it.
1850
1850
1851 With --debug, this command shows warning messages while matching
1851 With --debug, this command shows warning messages while matching
1852 against ``merge-patterns`` and so on, too. It is recommended to
1852 against ``merge-patterns`` and so on, too. It is recommended to
1853 use this option with explicit file patterns and/or -I/-X options,
1853 use this option with explicit file patterns and/or -I/-X options,
1854 because this option increases amount of output per file according
1854 because this option increases amount of output per file according
1855 to configurations in hgrc.
1855 to configurations in hgrc.
1856
1856
1857 With -v/--verbose, this command shows configurations below at
1857 With -v/--verbose, this command shows configurations below at
1858 first (only if specified).
1858 first (only if specified).
1859
1859
1860 - ``--tool`` option
1860 - ``--tool`` option
1861 - ``HGMERGE`` environment variable
1861 - ``HGMERGE`` environment variable
1862 - configuration of ``ui.merge``
1862 - configuration of ``ui.merge``
1863
1863
1864 If merge tool is chosen before matching against
1864 If merge tool is chosen before matching against
1865 ``merge-patterns``, this command can't show any helpful
1865 ``merge-patterns``, this command can't show any helpful
1866 information, even with --debug. In such case, information above is
1866 information, even with --debug. In such case, information above is
1867 useful to know why a merge tool is chosen.
1867 useful to know why a merge tool is chosen.
1868 """
1868 """
1869 opts = pycompat.byteskwargs(opts)
1869 opts = pycompat.byteskwargs(opts)
1870 overrides = {}
1870 overrides = {}
1871 if opts['tool']:
1871 if opts['tool']:
1872 overrides[('ui', 'forcemerge')] = opts['tool']
1872 overrides[('ui', 'forcemerge')] = opts['tool']
1873 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1873 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1874
1874
1875 with ui.configoverride(overrides, 'debugmergepatterns'):
1875 with ui.configoverride(overrides, 'debugmergepatterns'):
1876 hgmerge = encoding.environ.get("HGMERGE")
1876 hgmerge = encoding.environ.get("HGMERGE")
1877 if hgmerge is not None:
1877 if hgmerge is not None:
1878 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1878 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1879 uimerge = ui.config("ui", "merge")
1879 uimerge = ui.config("ui", "merge")
1880 if uimerge:
1880 if uimerge:
1881 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1881 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1882
1882
1883 ctx = scmutil.revsingle(repo, opts.get('rev'))
1883 ctx = scmutil.revsingle(repo, opts.get('rev'))
1884 m = scmutil.match(ctx, pats, opts)
1884 m = scmutil.match(ctx, pats, opts)
1885 changedelete = opts['changedelete']
1885 changedelete = opts['changedelete']
1886 for path in ctx.walk(m):
1886 for path in ctx.walk(m):
1887 fctx = ctx[path]
1887 fctx = ctx[path]
1888 try:
1888 try:
1889 if not ui.debugflag:
1889 if not ui.debugflag:
1890 ui.pushbuffer(error=True)
1890 ui.pushbuffer(error=True)
1891 tool, toolpath = filemerge._picktool(repo, ui, path,
1891 tool, toolpath = filemerge._picktool(repo, ui, path,
1892 fctx.isbinary(),
1892 fctx.isbinary(),
1893 'l' in fctx.flags(),
1893 'l' in fctx.flags(),
1894 changedelete)
1894 changedelete)
1895 finally:
1895 finally:
1896 if not ui.debugflag:
1896 if not ui.debugflag:
1897 ui.popbuffer()
1897 ui.popbuffer()
1898 ui.write(('%s = %s\n') % (path, tool))
1898 ui.write(('%s = %s\n') % (path, tool))
1899
1899
1900 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1900 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1901 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1901 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1902 '''access the pushkey key/value protocol
1902 '''access the pushkey key/value protocol
1903
1903
1904 With two args, list the keys in the given namespace.
1904 With two args, list the keys in the given namespace.
1905
1905
1906 With five args, set a key to new if it currently is set to old.
1906 With five args, set a key to new if it currently is set to old.
1907 Reports success or failure.
1907 Reports success or failure.
1908 '''
1908 '''
1909
1909
1910 target = hg.peer(ui, {}, repopath)
1910 target = hg.peer(ui, {}, repopath)
1911 if keyinfo:
1911 if keyinfo:
1912 key, old, new = keyinfo
1912 key, old, new = keyinfo
1913 with target.commandexecutor() as e:
1913 with target.commandexecutor() as e:
1914 r = e.callcommand('pushkey', {
1914 r = e.callcommand('pushkey', {
1915 'namespace': namespace,
1915 'namespace': namespace,
1916 'key': key,
1916 'key': key,
1917 'old': old,
1917 'old': old,
1918 'new': new,
1918 'new': new,
1919 }).result()
1919 }).result()
1920
1920
1921 ui.status(pycompat.bytestr(r) + '\n')
1921 ui.status(pycompat.bytestr(r) + '\n')
1922 return not r
1922 return not r
1923 else:
1923 else:
1924 for k, v in sorted(target.listkeys(namespace).iteritems()):
1924 for k, v in sorted(target.listkeys(namespace).iteritems()):
1925 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
1925 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
1926 stringutil.escapestr(v)))
1926 stringutil.escapestr(v)))
1927
1927
1928 @command('debugpvec', [], _('A B'))
1928 @command('debugpvec', [], _('A B'))
1929 def debugpvec(ui, repo, a, b=None):
1929 def debugpvec(ui, repo, a, b=None):
1930 ca = scmutil.revsingle(repo, a)
1930 ca = scmutil.revsingle(repo, a)
1931 cb = scmutil.revsingle(repo, b)
1931 cb = scmutil.revsingle(repo, b)
1932 pa = pvec.ctxpvec(ca)
1932 pa = pvec.ctxpvec(ca)
1933 pb = pvec.ctxpvec(cb)
1933 pb = pvec.ctxpvec(cb)
1934 if pa == pb:
1934 if pa == pb:
1935 rel = "="
1935 rel = "="
1936 elif pa > pb:
1936 elif pa > pb:
1937 rel = ">"
1937 rel = ">"
1938 elif pa < pb:
1938 elif pa < pb:
1939 rel = "<"
1939 rel = "<"
1940 elif pa | pb:
1940 elif pa | pb:
1941 rel = "|"
1941 rel = "|"
1942 ui.write(_("a: %s\n") % pa)
1942 ui.write(_("a: %s\n") % pa)
1943 ui.write(_("b: %s\n") % pb)
1943 ui.write(_("b: %s\n") % pb)
1944 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1944 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1945 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1945 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1946 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1946 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1947 pa.distance(pb), rel))
1947 pa.distance(pb), rel))
1948
1948
1949 @command('debugrebuilddirstate|debugrebuildstate',
1949 @command('debugrebuilddirstate|debugrebuildstate',
1950 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1950 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1951 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1951 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1952 'the working copy parent')),
1952 'the working copy parent')),
1953 ],
1953 ],
1954 _('[-r REV]'))
1954 _('[-r REV]'))
1955 def debugrebuilddirstate(ui, repo, rev, **opts):
1955 def debugrebuilddirstate(ui, repo, rev, **opts):
1956 """rebuild the dirstate as it would look like for the given revision
1956 """rebuild the dirstate as it would look like for the given revision
1957
1957
1958 If no revision is specified the first current parent will be used.
1958 If no revision is specified the first current parent will be used.
1959
1959
1960 The dirstate will be set to the files of the given revision.
1960 The dirstate will be set to the files of the given revision.
1961 The actual working directory content or existing dirstate
1961 The actual working directory content or existing dirstate
1962 information such as adds or removes is not considered.
1962 information such as adds or removes is not considered.
1963
1963
1964 ``minimal`` will only rebuild the dirstate status for files that claim to be
1964 ``minimal`` will only rebuild the dirstate status for files that claim to be
1965 tracked but are not in the parent manifest, or that exist in the parent
1965 tracked but are not in the parent manifest, or that exist in the parent
1966 manifest but are not in the dirstate. It will not change adds, removes, or
1966 manifest but are not in the dirstate. It will not change adds, removes, or
1967 modified files that are in the working copy parent.
1967 modified files that are in the working copy parent.
1968
1968
1969 One use of this command is to make the next :hg:`status` invocation
1969 One use of this command is to make the next :hg:`status` invocation
1970 check the actual file content.
1970 check the actual file content.
1971 """
1971 """
1972 ctx = scmutil.revsingle(repo, rev)
1972 ctx = scmutil.revsingle(repo, rev)
1973 with repo.wlock():
1973 with repo.wlock():
1974 dirstate = repo.dirstate
1974 dirstate = repo.dirstate
1975 changedfiles = None
1975 changedfiles = None
1976 # See command doc for what minimal does.
1976 # See command doc for what minimal does.
1977 if opts.get(r'minimal'):
1977 if opts.get(r'minimal'):
1978 manifestfiles = set(ctx.manifest().keys())
1978 manifestfiles = set(ctx.manifest().keys())
1979 dirstatefiles = set(dirstate)
1979 dirstatefiles = set(dirstate)
1980 manifestonly = manifestfiles - dirstatefiles
1980 manifestonly = manifestfiles - dirstatefiles
1981 dsonly = dirstatefiles - manifestfiles
1981 dsonly = dirstatefiles - manifestfiles
1982 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1982 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1983 changedfiles = manifestonly | dsnotadded
1983 changedfiles = manifestonly | dsnotadded
1984
1984
1985 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1985 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1986
1986
1987 @command('debugrebuildfncache', [], '')
1987 @command('debugrebuildfncache', [], '')
1988 def debugrebuildfncache(ui, repo):
1988 def debugrebuildfncache(ui, repo):
1989 """rebuild the fncache file"""
1989 """rebuild the fncache file"""
1990 repair.rebuildfncache(ui, repo)
1990 repair.rebuildfncache(ui, repo)
1991
1991
1992 @command('debugrename',
1992 @command('debugrename',
1993 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1993 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1994 _('[-r REV] FILE'))
1994 _('[-r REV] FILE'))
1995 def debugrename(ui, repo, file1, *pats, **opts):
1995 def debugrename(ui, repo, file1, *pats, **opts):
1996 """dump rename information"""
1996 """dump rename information"""
1997
1997
1998 opts = pycompat.byteskwargs(opts)
1998 opts = pycompat.byteskwargs(opts)
1999 ctx = scmutil.revsingle(repo, opts.get('rev'))
1999 ctx = scmutil.revsingle(repo, opts.get('rev'))
2000 m = scmutil.match(ctx, (file1,) + pats, opts)
2000 m = scmutil.match(ctx, (file1,) + pats, opts)
2001 for abs in ctx.walk(m):
2001 for abs in ctx.walk(m):
2002 fctx = ctx[abs]
2002 fctx = ctx[abs]
2003 o = fctx.filelog().renamed(fctx.filenode())
2003 o = fctx.filelog().renamed(fctx.filenode())
2004 rel = m.rel(abs)
2004 rel = m.rel(abs)
2005 if o:
2005 if o:
2006 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2006 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2007 else:
2007 else:
2008 ui.write(_("%s not renamed\n") % rel)
2008 ui.write(_("%s not renamed\n") % rel)
2009
2009
2010 @command('debugrevlog', cmdutil.debugrevlogopts +
2010 @command('debugrevlog', cmdutil.debugrevlogopts +
2011 [('d', 'dump', False, _('dump index data'))],
2011 [('d', 'dump', False, _('dump index data'))],
2012 _('-c|-m|FILE'),
2012 _('-c|-m|FILE'),
2013 optionalrepo=True)
2013 optionalrepo=True)
2014 def debugrevlog(ui, repo, file_=None, **opts):
2014 def debugrevlog(ui, repo, file_=None, **opts):
2015 """show data and statistics about a revlog"""
2015 """show data and statistics about a revlog"""
2016 opts = pycompat.byteskwargs(opts)
2016 opts = pycompat.byteskwargs(opts)
2017 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2017 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2018
2018
2019 if opts.get("dump"):
2019 if opts.get("dump"):
2020 numrevs = len(r)
2020 numrevs = len(r)
2021 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
2021 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
2022 " rawsize totalsize compression heads chainlen\n"))
2022 " rawsize totalsize compression heads chainlen\n"))
2023 ts = 0
2023 ts = 0
2024 heads = set()
2024 heads = set()
2025
2025
2026 for rev in pycompat.xrange(numrevs):
2026 for rev in pycompat.xrange(numrevs):
2027 dbase = r.deltaparent(rev)
2027 dbase = r.deltaparent(rev)
2028 if dbase == -1:
2028 if dbase == -1:
2029 dbase = rev
2029 dbase = rev
2030 cbase = r.chainbase(rev)
2030 cbase = r.chainbase(rev)
2031 clen = r.chainlen(rev)
2031 clen = r.chainlen(rev)
2032 p1, p2 = r.parentrevs(rev)
2032 p1, p2 = r.parentrevs(rev)
2033 rs = r.rawsize(rev)
2033 rs = r.rawsize(rev)
2034 ts = ts + rs
2034 ts = ts + rs
2035 heads -= set(r.parentrevs(rev))
2035 heads -= set(r.parentrevs(rev))
2036 heads.add(rev)
2036 heads.add(rev)
2037 try:
2037 try:
2038 compression = ts / r.end(rev)
2038 compression = ts / r.end(rev)
2039 except ZeroDivisionError:
2039 except ZeroDivisionError:
2040 compression = 0
2040 compression = 0
2041 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2041 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2042 "%11d %5d %8d\n" %
2042 "%11d %5d %8d\n" %
2043 (rev, p1, p2, r.start(rev), r.end(rev),
2043 (rev, p1, p2, r.start(rev), r.end(rev),
2044 r.start(dbase), r.start(cbase),
2044 r.start(dbase), r.start(cbase),
2045 r.start(p1), r.start(p2),
2045 r.start(p1), r.start(p2),
2046 rs, ts, compression, len(heads), clen))
2046 rs, ts, compression, len(heads), clen))
2047 return 0
2047 return 0
2048
2048
2049 v = r.version
2049 v = r.version
2050 format = v & 0xFFFF
2050 format = v & 0xFFFF
2051 flags = []
2051 flags = []
2052 gdelta = False
2052 gdelta = False
2053 if v & revlog.FLAG_INLINE_DATA:
2053 if v & revlog.FLAG_INLINE_DATA:
2054 flags.append('inline')
2054 flags.append('inline')
2055 if v & revlog.FLAG_GENERALDELTA:
2055 if v & revlog.FLAG_GENERALDELTA:
2056 gdelta = True
2056 gdelta = True
2057 flags.append('generaldelta')
2057 flags.append('generaldelta')
2058 if not flags:
2058 if not flags:
2059 flags = ['(none)']
2059 flags = ['(none)']
2060
2060
2061 ### tracks merge vs single parent
2061 ### tracks merge vs single parent
2062 nummerges = 0
2062 nummerges = 0
2063
2063
2064 ### tracks ways the "delta" are build
2064 ### tracks ways the "delta" are build
2065 # nodelta
2065 # nodelta
2066 numempty = 0
2066 numempty = 0
2067 numemptytext = 0
2067 numemptytext = 0
2068 numemptydelta = 0
2068 numemptydelta = 0
2069 # full file content
2069 # full file content
2070 numfull = 0
2070 numfull = 0
2071 # intermediate snapshot against a prior snapshot
2071 # intermediate snapshot against a prior snapshot
2072 numsemi = 0
2072 numsemi = 0
2073 # snapshot count per depth
2073 # snapshot count per depth
2074 numsnapdepth = collections.defaultdict(lambda: 0)
2074 numsnapdepth = collections.defaultdict(lambda: 0)
2075 # delta against previous revision
2075 # delta against previous revision
2076 numprev = 0
2076 numprev = 0
2077 # delta against first or second parent (not prev)
2077 # delta against first or second parent (not prev)
2078 nump1 = 0
2078 nump1 = 0
2079 nump2 = 0
2079 nump2 = 0
2080 # delta against neither prev nor parents
2080 # delta against neither prev nor parents
2081 numother = 0
2081 numother = 0
2082 # delta against prev that are also first or second parent
2082 # delta against prev that are also first or second parent
2083 # (details of `numprev`)
2083 # (details of `numprev`)
2084 nump1prev = 0
2084 nump1prev = 0
2085 nump2prev = 0
2085 nump2prev = 0
2086
2086
2087 # data about delta chain of each revs
2087 # data about delta chain of each revs
2088 chainlengths = []
2088 chainlengths = []
2089 chainbases = []
2089 chainbases = []
2090 chainspans = []
2090 chainspans = []
2091
2091
2092 # data about each revision
2092 # data about each revision
2093 datasize = [None, 0, 0]
2093 datasize = [None, 0, 0]
2094 fullsize = [None, 0, 0]
2094 fullsize = [None, 0, 0]
2095 semisize = [None, 0, 0]
2095 semisize = [None, 0, 0]
2096 # snapshot count per depth
2096 # snapshot count per depth
2097 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2097 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2098 deltasize = [None, 0, 0]
2098 deltasize = [None, 0, 0]
2099 chunktypecounts = {}
2099 chunktypecounts = {}
2100 chunktypesizes = {}
2100 chunktypesizes = {}
2101
2101
2102 def addsize(size, l):
2102 def addsize(size, l):
2103 if l[0] is None or size < l[0]:
2103 if l[0] is None or size < l[0]:
2104 l[0] = size
2104 l[0] = size
2105 if size > l[1]:
2105 if size > l[1]:
2106 l[1] = size
2106 l[1] = size
2107 l[2] += size
2107 l[2] += size
2108
2108
2109 numrevs = len(r)
2109 numrevs = len(r)
2110 for rev in pycompat.xrange(numrevs):
2110 for rev in pycompat.xrange(numrevs):
2111 p1, p2 = r.parentrevs(rev)
2111 p1, p2 = r.parentrevs(rev)
2112 delta = r.deltaparent(rev)
2112 delta = r.deltaparent(rev)
2113 if format > 0:
2113 if format > 0:
2114 addsize(r.rawsize(rev), datasize)
2114 addsize(r.rawsize(rev), datasize)
2115 if p2 != nullrev:
2115 if p2 != nullrev:
2116 nummerges += 1
2116 nummerges += 1
2117 size = r.length(rev)
2117 size = r.length(rev)
2118 if delta == nullrev:
2118 if delta == nullrev:
2119 chainlengths.append(0)
2119 chainlengths.append(0)
2120 chainbases.append(r.start(rev))
2120 chainbases.append(r.start(rev))
2121 chainspans.append(size)
2121 chainspans.append(size)
2122 if size == 0:
2122 if size == 0:
2123 numempty += 1
2123 numempty += 1
2124 numemptytext += 1
2124 numemptytext += 1
2125 else:
2125 else:
2126 numfull += 1
2126 numfull += 1
2127 numsnapdepth[0] += 1
2127 numsnapdepth[0] += 1
2128 addsize(size, fullsize)
2128 addsize(size, fullsize)
2129 addsize(size, snapsizedepth[0])
2129 addsize(size, snapsizedepth[0])
2130 else:
2130 else:
2131 chainlengths.append(chainlengths[delta] + 1)
2131 chainlengths.append(chainlengths[delta] + 1)
2132 baseaddr = chainbases[delta]
2132 baseaddr = chainbases[delta]
2133 revaddr = r.start(rev)
2133 revaddr = r.start(rev)
2134 chainbases.append(baseaddr)
2134 chainbases.append(baseaddr)
2135 chainspans.append((revaddr - baseaddr) + size)
2135 chainspans.append((revaddr - baseaddr) + size)
2136 if size == 0:
2136 if size == 0:
2137 numempty += 1
2137 numempty += 1
2138 numemptydelta += 1
2138 numemptydelta += 1
2139 elif r.issnapshot(rev):
2139 elif r.issnapshot(rev):
2140 addsize(size, semisize)
2140 addsize(size, semisize)
2141 numsemi += 1
2141 numsemi += 1
2142 depth = r.snapshotdepth(rev)
2142 depth = r.snapshotdepth(rev)
2143 numsnapdepth[depth] += 1
2143 numsnapdepth[depth] += 1
2144 addsize(size, snapsizedepth[depth])
2144 addsize(size, snapsizedepth[depth])
2145 else:
2145 else:
2146 addsize(size, deltasize)
2146 addsize(size, deltasize)
2147 if delta == rev - 1:
2147 if delta == rev - 1:
2148 numprev += 1
2148 numprev += 1
2149 if delta == p1:
2149 if delta == p1:
2150 nump1prev += 1
2150 nump1prev += 1
2151 elif delta == p2:
2151 elif delta == p2:
2152 nump2prev += 1
2152 nump2prev += 1
2153 elif delta == p1:
2153 elif delta == p1:
2154 nump1 += 1
2154 nump1 += 1
2155 elif delta == p2:
2155 elif delta == p2:
2156 nump2 += 1
2156 nump2 += 1
2157 elif delta != nullrev:
2157 elif delta != nullrev:
2158 numother += 1
2158 numother += 1
2159
2159
2160 # Obtain data on the raw chunks in the revlog.
2160 # Obtain data on the raw chunks in the revlog.
2161 if util.safehasattr(r, '_getsegmentforrevs'):
2161 if util.safehasattr(r, '_getsegmentforrevs'):
2162 segment = r._getsegmentforrevs(rev, rev)[1]
2162 segment = r._getsegmentforrevs(rev, rev)[1]
2163 else:
2163 else:
2164 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2164 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2165 if segment:
2165 if segment:
2166 chunktype = bytes(segment[0:1])
2166 chunktype = bytes(segment[0:1])
2167 else:
2167 else:
2168 chunktype = 'empty'
2168 chunktype = 'empty'
2169
2169
2170 if chunktype not in chunktypecounts:
2170 if chunktype not in chunktypecounts:
2171 chunktypecounts[chunktype] = 0
2171 chunktypecounts[chunktype] = 0
2172 chunktypesizes[chunktype] = 0
2172 chunktypesizes[chunktype] = 0
2173
2173
2174 chunktypecounts[chunktype] += 1
2174 chunktypecounts[chunktype] += 1
2175 chunktypesizes[chunktype] += size
2175 chunktypesizes[chunktype] += size
2176
2176
2177 # Adjust size min value for empty cases
2177 # Adjust size min value for empty cases
2178 for size in (datasize, fullsize, semisize, deltasize):
2178 for size in (datasize, fullsize, semisize, deltasize):
2179 if size[0] is None:
2179 if size[0] is None:
2180 size[0] = 0
2180 size[0] = 0
2181
2181
2182 numdeltas = numrevs - numfull - numempty - numsemi
2182 numdeltas = numrevs - numfull - numempty - numsemi
2183 numoprev = numprev - nump1prev - nump2prev
2183 numoprev = numprev - nump1prev - nump2prev
2184 totalrawsize = datasize[2]
2184 totalrawsize = datasize[2]
2185 datasize[2] /= numrevs
2185 datasize[2] /= numrevs
2186 fulltotal = fullsize[2]
2186 fulltotal = fullsize[2]
2187 fullsize[2] /= numfull
2187 fullsize[2] /= numfull
2188 semitotal = semisize[2]
2188 semitotal = semisize[2]
2189 snaptotal = {}
2189 snaptotal = {}
2190 if 0 < numsemi:
2190 if 0 < numsemi:
2191 semisize[2] /= numsemi
2191 semisize[2] /= numsemi
2192 for depth in snapsizedepth:
2192 for depth in snapsizedepth:
2193 snaptotal[depth] = snapsizedepth[depth][2]
2193 snaptotal[depth] = snapsizedepth[depth][2]
2194 snapsizedepth[depth][2] /= numsnapdepth[depth]
2194 snapsizedepth[depth][2] /= numsnapdepth[depth]
2195
2195
2196 deltatotal = deltasize[2]
2196 deltatotal = deltasize[2]
2197 if numdeltas > 0:
2197 if numdeltas > 0:
2198 deltasize[2] /= numdeltas
2198 deltasize[2] /= numdeltas
2199 totalsize = fulltotal + semitotal + deltatotal
2199 totalsize = fulltotal + semitotal + deltatotal
2200 avgchainlen = sum(chainlengths) / numrevs
2200 avgchainlen = sum(chainlengths) / numrevs
2201 maxchainlen = max(chainlengths)
2201 maxchainlen = max(chainlengths)
2202 maxchainspan = max(chainspans)
2202 maxchainspan = max(chainspans)
2203 compratio = 1
2203 compratio = 1
2204 if totalsize:
2204 if totalsize:
2205 compratio = totalrawsize / totalsize
2205 compratio = totalrawsize / totalsize
2206
2206
2207 basedfmtstr = '%%%dd\n'
2207 basedfmtstr = '%%%dd\n'
2208 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2208 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2209
2209
2210 def dfmtstr(max):
2210 def dfmtstr(max):
2211 return basedfmtstr % len(str(max))
2211 return basedfmtstr % len(str(max))
2212 def pcfmtstr(max, padding=0):
2212 def pcfmtstr(max, padding=0):
2213 return basepcfmtstr % (len(str(max)), ' ' * padding)
2213 return basepcfmtstr % (len(str(max)), ' ' * padding)
2214
2214
2215 def pcfmt(value, total):
2215 def pcfmt(value, total):
2216 if total:
2216 if total:
2217 return (value, 100 * float(value) / total)
2217 return (value, 100 * float(value) / total)
2218 else:
2218 else:
2219 return value, 100.0
2219 return value, 100.0
2220
2220
2221 ui.write(('format : %d\n') % format)
2221 ui.write(('format : %d\n') % format)
2222 ui.write(('flags : %s\n') % ', '.join(flags))
2222 ui.write(('flags : %s\n') % ', '.join(flags))
2223
2223
2224 ui.write('\n')
2224 ui.write('\n')
2225 fmt = pcfmtstr(totalsize)
2225 fmt = pcfmtstr(totalsize)
2226 fmt2 = dfmtstr(totalsize)
2226 fmt2 = dfmtstr(totalsize)
2227 ui.write(('revisions : ') + fmt2 % numrevs)
2227 ui.write(('revisions : ') + fmt2 % numrevs)
2228 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2228 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2229 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2229 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2230 ui.write(('revisions : ') + fmt2 % numrevs)
2230 ui.write(('revisions : ') + fmt2 % numrevs)
2231 ui.write((' empty : ') + fmt % pcfmt(numempty, numrevs))
2231 ui.write((' empty : ') + fmt % pcfmt(numempty, numrevs))
2232 ui.write((' text : ')
2232 ui.write((' text : ')
2233 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta))
2233 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta))
2234 ui.write((' delta : ')
2234 ui.write((' delta : ')
2235 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta))
2235 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta))
2236 ui.write((' snapshot : ') + fmt % pcfmt(numfull + numsemi, numrevs))
2236 ui.write((' snapshot : ') + fmt % pcfmt(numfull + numsemi, numrevs))
2237 for depth in sorted(numsnapdepth):
2237 for depth in sorted(numsnapdepth):
2238 ui.write((' lvl-%-3d : ' % depth)
2238 ui.write((' lvl-%-3d : ' % depth)
2239 + fmt % pcfmt(numsnapdepth[depth], numrevs))
2239 + fmt % pcfmt(numsnapdepth[depth], numrevs))
2240 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2240 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2241 ui.write(('revision size : ') + fmt2 % totalsize)
2241 ui.write(('revision size : ') + fmt2 % totalsize)
2242 ui.write((' snapshot : ')
2242 ui.write((' snapshot : ')
2243 + fmt % pcfmt(fulltotal + semitotal, totalsize))
2243 + fmt % pcfmt(fulltotal + semitotal, totalsize))
2244 for depth in sorted(numsnapdepth):
2244 for depth in sorted(numsnapdepth):
2245 ui.write((' lvl-%-3d : ' % depth)
2245 ui.write((' lvl-%-3d : ' % depth)
2246 + fmt % pcfmt(snaptotal[depth], totalsize))
2246 + fmt % pcfmt(snaptotal[depth], totalsize))
2247 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2247 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2248
2248
2249 def fmtchunktype(chunktype):
2249 def fmtchunktype(chunktype):
2250 if chunktype == 'empty':
2250 if chunktype == 'empty':
2251 return ' %s : ' % chunktype
2251 return ' %s : ' % chunktype
2252 elif chunktype in pycompat.bytestr(string.ascii_letters):
2252 elif chunktype in pycompat.bytestr(string.ascii_letters):
2253 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2253 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2254 else:
2254 else:
2255 return ' 0x%s : ' % hex(chunktype)
2255 return ' 0x%s : ' % hex(chunktype)
2256
2256
2257 ui.write('\n')
2257 ui.write('\n')
2258 ui.write(('chunks : ') + fmt2 % numrevs)
2258 ui.write(('chunks : ') + fmt2 % numrevs)
2259 for chunktype in sorted(chunktypecounts):
2259 for chunktype in sorted(chunktypecounts):
2260 ui.write(fmtchunktype(chunktype))
2260 ui.write(fmtchunktype(chunktype))
2261 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2261 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2262 ui.write(('chunks size : ') + fmt2 % totalsize)
2262 ui.write(('chunks size : ') + fmt2 % totalsize)
2263 for chunktype in sorted(chunktypecounts):
2263 for chunktype in sorted(chunktypecounts):
2264 ui.write(fmtchunktype(chunktype))
2264 ui.write(fmtchunktype(chunktype))
2265 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2265 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2266
2266
2267 ui.write('\n')
2267 ui.write('\n')
2268 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2268 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2269 ui.write(('avg chain length : ') + fmt % avgchainlen)
2269 ui.write(('avg chain length : ') + fmt % avgchainlen)
2270 ui.write(('max chain length : ') + fmt % maxchainlen)
2270 ui.write(('max chain length : ') + fmt % maxchainlen)
2271 ui.write(('max chain reach : ') + fmt % maxchainspan)
2271 ui.write(('max chain reach : ') + fmt % maxchainspan)
2272 ui.write(('compression ratio : ') + fmt % compratio)
2272 ui.write(('compression ratio : ') + fmt % compratio)
2273
2273
2274 if format > 0:
2274 if format > 0:
2275 ui.write('\n')
2275 ui.write('\n')
2276 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2276 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2277 % tuple(datasize))
2277 % tuple(datasize))
2278 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2278 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2279 % tuple(fullsize))
2279 % tuple(fullsize))
2280 ui.write(('inter-snapshot size (min/max/avg) : %d / %d / %d\n')
2280 ui.write(('inter-snapshot size (min/max/avg) : %d / %d / %d\n')
2281 % tuple(semisize))
2281 % tuple(semisize))
2282 for depth in sorted(snapsizedepth):
2282 for depth in sorted(snapsizedepth):
2283 if depth == 0:
2283 if depth == 0:
2284 continue
2284 continue
2285 ui.write((' level-%-3d (min/max/avg) : %d / %d / %d\n')
2285 ui.write((' level-%-3d (min/max/avg) : %d / %d / %d\n')
2286 % ((depth,) + tuple(snapsizedepth[depth])))
2286 % ((depth,) + tuple(snapsizedepth[depth])))
2287 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2287 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2288 % tuple(deltasize))
2288 % tuple(deltasize))
2289
2289
2290 if numdeltas > 0:
2290 if numdeltas > 0:
2291 ui.write('\n')
2291 ui.write('\n')
2292 fmt = pcfmtstr(numdeltas)
2292 fmt = pcfmtstr(numdeltas)
2293 fmt2 = pcfmtstr(numdeltas, 4)
2293 fmt2 = pcfmtstr(numdeltas, 4)
2294 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2294 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2295 if numprev > 0:
2295 if numprev > 0:
2296 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2296 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2297 numprev))
2297 numprev))
2298 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2298 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2299 numprev))
2299 numprev))
2300 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2300 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2301 numprev))
2301 numprev))
2302 if gdelta:
2302 if gdelta:
2303 ui.write(('deltas against p1 : ')
2303 ui.write(('deltas against p1 : ')
2304 + fmt % pcfmt(nump1, numdeltas))
2304 + fmt % pcfmt(nump1, numdeltas))
2305 ui.write(('deltas against p2 : ')
2305 ui.write(('deltas against p2 : ')
2306 + fmt % pcfmt(nump2, numdeltas))
2306 + fmt % pcfmt(nump2, numdeltas))
2307 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2307 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2308 numdeltas))
2308 numdeltas))
2309
2309
2310 @command('debugrevlogindex', cmdutil.debugrevlogopts +
2310 @command('debugrevlogindex', cmdutil.debugrevlogopts +
2311 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2311 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2312 _('[-f FORMAT] -c|-m|FILE'),
2312 _('[-f FORMAT] -c|-m|FILE'),
2313 optionalrepo=True)
2313 optionalrepo=True)
2314 def debugrevlogindex(ui, repo, file_=None, **opts):
2314 def debugrevlogindex(ui, repo, file_=None, **opts):
2315 """dump the contents of a revlog index"""
2315 """dump the contents of a revlog index"""
2316 opts = pycompat.byteskwargs(opts)
2316 opts = pycompat.byteskwargs(opts)
2317 r = cmdutil.openrevlog(repo, 'debugrevlogindex', file_, opts)
2317 r = cmdutil.openrevlog(repo, 'debugrevlogindex', file_, opts)
2318 format = opts.get('format', 0)
2318 format = opts.get('format', 0)
2319 if format not in (0, 1):
2319 if format not in (0, 1):
2320 raise error.Abort(_("unknown format %d") % format)
2320 raise error.Abort(_("unknown format %d") % format)
2321
2321
2322 if ui.debugflag:
2322 if ui.debugflag:
2323 shortfn = hex
2323 shortfn = hex
2324 else:
2324 else:
2325 shortfn = short
2325 shortfn = short
2326
2326
2327 # There might not be anything in r, so have a sane default
2327 # There might not be anything in r, so have a sane default
2328 idlen = 12
2328 idlen = 12
2329 for i in r:
2329 for i in r:
2330 idlen = len(shortfn(r.node(i)))
2330 idlen = len(shortfn(r.node(i)))
2331 break
2331 break
2332
2332
2333 if format == 0:
2333 if format == 0:
2334 if ui.verbose:
2334 if ui.verbose:
2335 ui.write((" rev offset length linkrev"
2335 ui.write((" rev offset length linkrev"
2336 " %s %s p2\n") % ("nodeid".ljust(idlen),
2336 " %s %s p2\n") % ("nodeid".ljust(idlen),
2337 "p1".ljust(idlen)))
2337 "p1".ljust(idlen)))
2338 else:
2338 else:
2339 ui.write((" rev linkrev %s %s p2\n") % (
2339 ui.write((" rev linkrev %s %s p2\n") % (
2340 "nodeid".ljust(idlen), "p1".ljust(idlen)))
2340 "nodeid".ljust(idlen), "p1".ljust(idlen)))
2341 elif format == 1:
2341 elif format == 1:
2342 if ui.verbose:
2342 if ui.verbose:
2343 ui.write((" rev flag offset length size link p1"
2343 ui.write((" rev flag offset length size link p1"
2344 " p2 %s\n") % "nodeid".rjust(idlen))
2344 " p2 %s\n") % "nodeid".rjust(idlen))
2345 else:
2345 else:
2346 ui.write((" rev flag size link p1 p2 %s\n") %
2346 ui.write((" rev flag size link p1 p2 %s\n") %
2347 "nodeid".rjust(idlen))
2347 "nodeid".rjust(idlen))
2348
2348
2349 for i in r:
2349 for i in r:
2350 node = r.node(i)
2350 node = r.node(i)
2351 if format == 0:
2351 if format == 0:
2352 try:
2352 try:
2353 pp = r.parents(node)
2353 pp = r.parents(node)
2354 except Exception:
2354 except Exception:
2355 pp = [nullid, nullid]
2355 pp = [nullid, nullid]
2356 if ui.verbose:
2356 if ui.verbose:
2357 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
2357 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
2358 i, r.start(i), r.length(i), r.linkrev(i),
2358 i, r.start(i), r.length(i), r.linkrev(i),
2359 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2359 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2360 else:
2360 else:
2361 ui.write("% 6d % 7d %s %s %s\n" % (
2361 ui.write("% 6d % 7d %s %s %s\n" % (
2362 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
2362 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
2363 shortfn(pp[1])))
2363 shortfn(pp[1])))
2364 elif format == 1:
2364 elif format == 1:
2365 pr = r.parentrevs(i)
2365 pr = r.parentrevs(i)
2366 if ui.verbose:
2366 if ui.verbose:
2367 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
2367 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
2368 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2368 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2369 r.linkrev(i), pr[0], pr[1], shortfn(node)))
2369 r.linkrev(i), pr[0], pr[1], shortfn(node)))
2370 else:
2370 else:
2371 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
2371 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
2372 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
2372 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
2373 shortfn(node)))
2373 shortfn(node)))
2374
2374
2375 @command('debugrevspec',
2375 @command('debugrevspec',
2376 [('', 'optimize', None,
2376 [('', 'optimize', None,
2377 _('print parsed tree after optimizing (DEPRECATED)')),
2377 _('print parsed tree after optimizing (DEPRECATED)')),
2378 ('', 'show-revs', True, _('print list of result revisions (default)')),
2378 ('', 'show-revs', True, _('print list of result revisions (default)')),
2379 ('s', 'show-set', None, _('print internal representation of result set')),
2379 ('s', 'show-set', None, _('print internal representation of result set')),
2380 ('p', 'show-stage', [],
2380 ('p', 'show-stage', [],
2381 _('print parsed tree at the given stage'), _('NAME')),
2381 _('print parsed tree at the given stage'), _('NAME')),
2382 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2382 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2383 ('', 'verify-optimized', False, _('verify optimized result')),
2383 ('', 'verify-optimized', False, _('verify optimized result')),
2384 ],
2384 ],
2385 ('REVSPEC'))
2385 ('REVSPEC'))
2386 def debugrevspec(ui, repo, expr, **opts):
2386 def debugrevspec(ui, repo, expr, **opts):
2387 """parse and apply a revision specification
2387 """parse and apply a revision specification
2388
2388
2389 Use -p/--show-stage option to print the parsed tree at the given stages.
2389 Use -p/--show-stage option to print the parsed tree at the given stages.
2390 Use -p all to print tree at every stage.
2390 Use -p all to print tree at every stage.
2391
2391
2392 Use --no-show-revs option with -s or -p to print only the set
2392 Use --no-show-revs option with -s or -p to print only the set
2393 representation or the parsed tree respectively.
2393 representation or the parsed tree respectively.
2394
2394
2395 Use --verify-optimized to compare the optimized result with the unoptimized
2395 Use --verify-optimized to compare the optimized result with the unoptimized
2396 one. Returns 1 if the optimized result differs.
2396 one. Returns 1 if the optimized result differs.
2397 """
2397 """
2398 opts = pycompat.byteskwargs(opts)
2398 opts = pycompat.byteskwargs(opts)
2399 aliases = ui.configitems('revsetalias')
2399 aliases = ui.configitems('revsetalias')
2400 stages = [
2400 stages = [
2401 ('parsed', lambda tree: tree),
2401 ('parsed', lambda tree: tree),
2402 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2402 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2403 ui.warn)),
2403 ui.warn)),
2404 ('concatenated', revsetlang.foldconcat),
2404 ('concatenated', revsetlang.foldconcat),
2405 ('analyzed', revsetlang.analyze),
2405 ('analyzed', revsetlang.analyze),
2406 ('optimized', revsetlang.optimize),
2406 ('optimized', revsetlang.optimize),
2407 ]
2407 ]
2408 if opts['no_optimized']:
2408 if opts['no_optimized']:
2409 stages = stages[:-1]
2409 stages = stages[:-1]
2410 if opts['verify_optimized'] and opts['no_optimized']:
2410 if opts['verify_optimized'] and opts['no_optimized']:
2411 raise error.Abort(_('cannot use --verify-optimized with '
2411 raise error.Abort(_('cannot use --verify-optimized with '
2412 '--no-optimized'))
2412 '--no-optimized'))
2413 stagenames = set(n for n, f in stages)
2413 stagenames = set(n for n, f in stages)
2414
2414
2415 showalways = set()
2415 showalways = set()
2416 showchanged = set()
2416 showchanged = set()
2417 if ui.verbose and not opts['show_stage']:
2417 if ui.verbose and not opts['show_stage']:
2418 # show parsed tree by --verbose (deprecated)
2418 # show parsed tree by --verbose (deprecated)
2419 showalways.add('parsed')
2419 showalways.add('parsed')
2420 showchanged.update(['expanded', 'concatenated'])
2420 showchanged.update(['expanded', 'concatenated'])
2421 if opts['optimize']:
2421 if opts['optimize']:
2422 showalways.add('optimized')
2422 showalways.add('optimized')
2423 if opts['show_stage'] and opts['optimize']:
2423 if opts['show_stage'] and opts['optimize']:
2424 raise error.Abort(_('cannot use --optimize with --show-stage'))
2424 raise error.Abort(_('cannot use --optimize with --show-stage'))
2425 if opts['show_stage'] == ['all']:
2425 if opts['show_stage'] == ['all']:
2426 showalways.update(stagenames)
2426 showalways.update(stagenames)
2427 else:
2427 else:
2428 for n in opts['show_stage']:
2428 for n in opts['show_stage']:
2429 if n not in stagenames:
2429 if n not in stagenames:
2430 raise error.Abort(_('invalid stage name: %s') % n)
2430 raise error.Abort(_('invalid stage name: %s') % n)
2431 showalways.update(opts['show_stage'])
2431 showalways.update(opts['show_stage'])
2432
2432
2433 treebystage = {}
2433 treebystage = {}
2434 printedtree = None
2434 printedtree = None
2435 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
2435 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
2436 for n, f in stages:
2436 for n, f in stages:
2437 treebystage[n] = tree = f(tree)
2437 treebystage[n] = tree = f(tree)
2438 if n in showalways or (n in showchanged and tree != printedtree):
2438 if n in showalways or (n in showchanged and tree != printedtree):
2439 if opts['show_stage'] or n != 'parsed':
2439 if opts['show_stage'] or n != 'parsed':
2440 ui.write(("* %s:\n") % n)
2440 ui.write(("* %s:\n") % n)
2441 ui.write(revsetlang.prettyformat(tree), "\n")
2441 ui.write(revsetlang.prettyformat(tree), "\n")
2442 printedtree = tree
2442 printedtree = tree
2443
2443
2444 if opts['verify_optimized']:
2444 if opts['verify_optimized']:
2445 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2445 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2446 brevs = revset.makematcher(treebystage['optimized'])(repo)
2446 brevs = revset.makematcher(treebystage['optimized'])(repo)
2447 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2447 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2448 ui.write(("* analyzed set:\n"), stringutil.prettyrepr(arevs), "\n")
2448 ui.write(("* analyzed set:\n"), stringutil.prettyrepr(arevs), "\n")
2449 ui.write(("* optimized set:\n"), stringutil.prettyrepr(brevs), "\n")
2449 ui.write(("* optimized set:\n"), stringutil.prettyrepr(brevs), "\n")
2450 arevs = list(arevs)
2450 arevs = list(arevs)
2451 brevs = list(brevs)
2451 brevs = list(brevs)
2452 if arevs == brevs:
2452 if arevs == brevs:
2453 return 0
2453 return 0
2454 ui.write(('--- analyzed\n'), label='diff.file_a')
2454 ui.write(('--- analyzed\n'), label='diff.file_a')
2455 ui.write(('+++ optimized\n'), label='diff.file_b')
2455 ui.write(('+++ optimized\n'), label='diff.file_b')
2456 sm = difflib.SequenceMatcher(None, arevs, brevs)
2456 sm = difflib.SequenceMatcher(None, arevs, brevs)
2457 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2457 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2458 if tag in ('delete', 'replace'):
2458 if tag in ('delete', 'replace'):
2459 for c in arevs[alo:ahi]:
2459 for c in arevs[alo:ahi]:
2460 ui.write('-%s\n' % c, label='diff.deleted')
2460 ui.write('-%s\n' % c, label='diff.deleted')
2461 if tag in ('insert', 'replace'):
2461 if tag in ('insert', 'replace'):
2462 for c in brevs[blo:bhi]:
2462 for c in brevs[blo:bhi]:
2463 ui.write('+%s\n' % c, label='diff.inserted')
2463 ui.write('+%s\n' % c, label='diff.inserted')
2464 if tag == 'equal':
2464 if tag == 'equal':
2465 for c in arevs[alo:ahi]:
2465 for c in arevs[alo:ahi]:
2466 ui.write(' %s\n' % c)
2466 ui.write(' %s\n' % c)
2467 return 1
2467 return 1
2468
2468
2469 func = revset.makematcher(tree)
2469 func = revset.makematcher(tree)
2470 revs = func(repo)
2470 revs = func(repo)
2471 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2471 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2472 ui.write(("* set:\n"), stringutil.prettyrepr(revs), "\n")
2472 ui.write(("* set:\n"), stringutil.prettyrepr(revs), "\n")
2473 if not opts['show_revs']:
2473 if not opts['show_revs']:
2474 return
2474 return
2475 for c in revs:
2475 for c in revs:
2476 ui.write("%d\n" % c)
2476 ui.write("%d\n" % c)
2477
2477
2478 @command('debugserve', [
2478 @command('debugserve', [
2479 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2479 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2480 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2480 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2481 ('', 'logiofile', '', _('file to log server I/O to')),
2481 ('', 'logiofile', '', _('file to log server I/O to')),
2482 ], '')
2482 ], '')
2483 def debugserve(ui, repo, **opts):
2483 def debugserve(ui, repo, **opts):
2484 """run a server with advanced settings
2484 """run a server with advanced settings
2485
2485
2486 This command is similar to :hg:`serve`. It exists partially as a
2486 This command is similar to :hg:`serve`. It exists partially as a
2487 workaround to the fact that ``hg serve --stdio`` must have specific
2487 workaround to the fact that ``hg serve --stdio`` must have specific
2488 arguments for security reasons.
2488 arguments for security reasons.
2489 """
2489 """
2490 opts = pycompat.byteskwargs(opts)
2490 opts = pycompat.byteskwargs(opts)
2491
2491
2492 if not opts['sshstdio']:
2492 if not opts['sshstdio']:
2493 raise error.Abort(_('only --sshstdio is currently supported'))
2493 raise error.Abort(_('only --sshstdio is currently supported'))
2494
2494
2495 logfh = None
2495 logfh = None
2496
2496
2497 if opts['logiofd'] and opts['logiofile']:
2497 if opts['logiofd'] and opts['logiofile']:
2498 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2498 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2499
2499
2500 if opts['logiofd']:
2500 if opts['logiofd']:
2501 # Line buffered because output is line based.
2501 # Line buffered because output is line based.
2502 try:
2502 try:
2503 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2503 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2504 except OSError as e:
2504 except OSError as e:
2505 if e.errno != errno.ESPIPE:
2505 if e.errno != errno.ESPIPE:
2506 raise
2506 raise
2507 # can't seek a pipe, so `ab` mode fails on py3
2507 # can't seek a pipe, so `ab` mode fails on py3
2508 logfh = os.fdopen(int(opts['logiofd']), r'wb', 1)
2508 logfh = os.fdopen(int(opts['logiofd']), r'wb', 1)
2509 elif opts['logiofile']:
2509 elif opts['logiofile']:
2510 logfh = open(opts['logiofile'], 'ab', 1)
2510 logfh = open(opts['logiofile'], 'ab', 1)
2511
2511
2512 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2512 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2513 s.serve_forever()
2513 s.serve_forever()
2514
2514
2515 @command('debugsetparents', [], _('REV1 [REV2]'))
2515 @command('debugsetparents', [], _('REV1 [REV2]'))
2516 def debugsetparents(ui, repo, rev1, rev2=None):
2516 def debugsetparents(ui, repo, rev1, rev2=None):
2517 """manually set the parents of the current working directory
2517 """manually set the parents of the current working directory
2518
2518
2519 This is useful for writing repository conversion tools, but should
2519 This is useful for writing repository conversion tools, but should
2520 be used with care. For example, neither the working directory nor the
2520 be used with care. For example, neither the working directory nor the
2521 dirstate is updated, so file status may be incorrect after running this
2521 dirstate is updated, so file status may be incorrect after running this
2522 command.
2522 command.
2523
2523
2524 Returns 0 on success.
2524 Returns 0 on success.
2525 """
2525 """
2526
2526
2527 node1 = scmutil.revsingle(repo, rev1).node()
2527 node1 = scmutil.revsingle(repo, rev1).node()
2528 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2528 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2529
2529
2530 with repo.wlock():
2530 with repo.wlock():
2531 repo.setparents(node1, node2)
2531 repo.setparents(node1, node2)
2532
2532
2533 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2533 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2534 def debugssl(ui, repo, source=None, **opts):
2534 def debugssl(ui, repo, source=None, **opts):
2535 '''test a secure connection to a server
2535 '''test a secure connection to a server
2536
2536
2537 This builds the certificate chain for the server on Windows, installing the
2537 This builds the certificate chain for the server on Windows, installing the
2538 missing intermediates and trusted root via Windows Update if necessary. It
2538 missing intermediates and trusted root via Windows Update if necessary. It
2539 does nothing on other platforms.
2539 does nothing on other platforms.
2540
2540
2541 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2541 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2542 that server is used. See :hg:`help urls` for more information.
2542 that server is used. See :hg:`help urls` for more information.
2543
2543
2544 If the update succeeds, retry the original operation. Otherwise, the cause
2544 If the update succeeds, retry the original operation. Otherwise, the cause
2545 of the SSL error is likely another issue.
2545 of the SSL error is likely another issue.
2546 '''
2546 '''
2547 if not pycompat.iswindows:
2547 if not pycompat.iswindows:
2548 raise error.Abort(_('certificate chain building is only possible on '
2548 raise error.Abort(_('certificate chain building is only possible on '
2549 'Windows'))
2549 'Windows'))
2550
2550
2551 if not source:
2551 if not source:
2552 if not repo:
2552 if not repo:
2553 raise error.Abort(_("there is no Mercurial repository here, and no "
2553 raise error.Abort(_("there is no Mercurial repository here, and no "
2554 "server specified"))
2554 "server specified"))
2555 source = "default"
2555 source = "default"
2556
2556
2557 source, branches = hg.parseurl(ui.expandpath(source))
2557 source, branches = hg.parseurl(ui.expandpath(source))
2558 url = util.url(source)
2558 url = util.url(source)
2559 addr = None
2559 addr = None
2560
2560
2561 defaultport = {'https': 443, 'ssh': 22}
2561 defaultport = {'https': 443, 'ssh': 22}
2562 if url.scheme in defaultport:
2562 if url.scheme in defaultport:
2563 try:
2563 try:
2564 addr = (url.host, int(url.port or defaultport[url.scheme]))
2564 addr = (url.host, int(url.port or defaultport[url.scheme]))
2565 except ValueError:
2565 except ValueError:
2566 raise error.Abort(_("malformed port number in URL"))
2566 raise error.Abort(_("malformed port number in URL"))
2567 else:
2567 else:
2568 raise error.Abort(_("only https and ssh connections are supported"))
2568 raise error.Abort(_("only https and ssh connections are supported"))
2569
2569
2570 from . import win32
2570 from . import win32
2571
2571
2572 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2572 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2573 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2573 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2574
2574
2575 try:
2575 try:
2576 s.connect(addr)
2576 s.connect(addr)
2577 cert = s.getpeercert(True)
2577 cert = s.getpeercert(True)
2578
2578
2579 ui.status(_('checking the certificate chain for %s\n') % url.host)
2579 ui.status(_('checking the certificate chain for %s\n') % url.host)
2580
2580
2581 complete = win32.checkcertificatechain(cert, build=False)
2581 complete = win32.checkcertificatechain(cert, build=False)
2582
2582
2583 if not complete:
2583 if not complete:
2584 ui.status(_('certificate chain is incomplete, updating... '))
2584 ui.status(_('certificate chain is incomplete, updating... '))
2585
2585
2586 if not win32.checkcertificatechain(cert):
2586 if not win32.checkcertificatechain(cert):
2587 ui.status(_('failed.\n'))
2587 ui.status(_('failed.\n'))
2588 else:
2588 else:
2589 ui.status(_('done.\n'))
2589 ui.status(_('done.\n'))
2590 else:
2590 else:
2591 ui.status(_('full certificate chain is available\n'))
2591 ui.status(_('full certificate chain is available\n'))
2592 finally:
2592 finally:
2593 s.close()
2593 s.close()
2594
2594
2595 @command('debugsub',
2595 @command('debugsub',
2596 [('r', 'rev', '',
2596 [('r', 'rev', '',
2597 _('revision to check'), _('REV'))],
2597 _('revision to check'), _('REV'))],
2598 _('[-r REV] [REV]'))
2598 _('[-r REV] [REV]'))
2599 def debugsub(ui, repo, rev=None):
2599 def debugsub(ui, repo, rev=None):
2600 ctx = scmutil.revsingle(repo, rev, None)
2600 ctx = scmutil.revsingle(repo, rev, None)
2601 for k, v in sorted(ctx.substate.items()):
2601 for k, v in sorted(ctx.substate.items()):
2602 ui.write(('path %s\n') % k)
2602 ui.write(('path %s\n') % k)
2603 ui.write((' source %s\n') % v[0])
2603 ui.write((' source %s\n') % v[0])
2604 ui.write((' revision %s\n') % v[1])
2604 ui.write((' revision %s\n') % v[1])
2605
2605
2606 @command('debugsuccessorssets',
2606 @command('debugsuccessorssets',
2607 [('', 'closest', False, _('return closest successors sets only'))],
2607 [('', 'closest', False, _('return closest successors sets only'))],
2608 _('[REV]'))
2608 _('[REV]'))
2609 def debugsuccessorssets(ui, repo, *revs, **opts):
2609 def debugsuccessorssets(ui, repo, *revs, **opts):
2610 """show set of successors for revision
2610 """show set of successors for revision
2611
2611
2612 A successors set of changeset A is a consistent group of revisions that
2612 A successors set of changeset A is a consistent group of revisions that
2613 succeed A. It contains non-obsolete changesets only unless closests
2613 succeed A. It contains non-obsolete changesets only unless closests
2614 successors set is set.
2614 successors set is set.
2615
2615
2616 In most cases a changeset A has a single successors set containing a single
2616 In most cases a changeset A has a single successors set containing a single
2617 successor (changeset A replaced by A').
2617 successor (changeset A replaced by A').
2618
2618
2619 A changeset that is made obsolete with no successors are called "pruned".
2619 A changeset that is made obsolete with no successors are called "pruned".
2620 Such changesets have no successors sets at all.
2620 Such changesets have no successors sets at all.
2621
2621
2622 A changeset that has been "split" will have a successors set containing
2622 A changeset that has been "split" will have a successors set containing
2623 more than one successor.
2623 more than one successor.
2624
2624
2625 A changeset that has been rewritten in multiple different ways is called
2625 A changeset that has been rewritten in multiple different ways is called
2626 "divergent". Such changesets have multiple successor sets (each of which
2626 "divergent". Such changesets have multiple successor sets (each of which
2627 may also be split, i.e. have multiple successors).
2627 may also be split, i.e. have multiple successors).
2628
2628
2629 Results are displayed as follows::
2629 Results are displayed as follows::
2630
2630
2631 <rev1>
2631 <rev1>
2632 <successors-1A>
2632 <successors-1A>
2633 <rev2>
2633 <rev2>
2634 <successors-2A>
2634 <successors-2A>
2635 <successors-2B1> <successors-2B2> <successors-2B3>
2635 <successors-2B1> <successors-2B2> <successors-2B3>
2636
2636
2637 Here rev2 has two possible (i.e. divergent) successors sets. The first
2637 Here rev2 has two possible (i.e. divergent) successors sets. The first
2638 holds one element, whereas the second holds three (i.e. the changeset has
2638 holds one element, whereas the second holds three (i.e. the changeset has
2639 been split).
2639 been split).
2640 """
2640 """
2641 # passed to successorssets caching computation from one call to another
2641 # passed to successorssets caching computation from one call to another
2642 cache = {}
2642 cache = {}
2643 ctx2str = bytes
2643 ctx2str = bytes
2644 node2str = short
2644 node2str = short
2645 for rev in scmutil.revrange(repo, revs):
2645 for rev in scmutil.revrange(repo, revs):
2646 ctx = repo[rev]
2646 ctx = repo[rev]
2647 ui.write('%s\n'% ctx2str(ctx))
2647 ui.write('%s\n'% ctx2str(ctx))
2648 for succsset in obsutil.successorssets(repo, ctx.node(),
2648 for succsset in obsutil.successorssets(repo, ctx.node(),
2649 closest=opts[r'closest'],
2649 closest=opts[r'closest'],
2650 cache=cache):
2650 cache=cache):
2651 if succsset:
2651 if succsset:
2652 ui.write(' ')
2652 ui.write(' ')
2653 ui.write(node2str(succsset[0]))
2653 ui.write(node2str(succsset[0]))
2654 for node in succsset[1:]:
2654 for node in succsset[1:]:
2655 ui.write(' ')
2655 ui.write(' ')
2656 ui.write(node2str(node))
2656 ui.write(node2str(node))
2657 ui.write('\n')
2657 ui.write('\n')
2658
2658
2659 @command('debugtemplate',
2659 @command('debugtemplate',
2660 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2660 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2661 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2661 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2662 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2662 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2663 optionalrepo=True)
2663 optionalrepo=True)
2664 def debugtemplate(ui, repo, tmpl, **opts):
2664 def debugtemplate(ui, repo, tmpl, **opts):
2665 """parse and apply a template
2665 """parse and apply a template
2666
2666
2667 If -r/--rev is given, the template is processed as a log template and
2667 If -r/--rev is given, the template is processed as a log template and
2668 applied to the given changesets. Otherwise, it is processed as a generic
2668 applied to the given changesets. Otherwise, it is processed as a generic
2669 template.
2669 template.
2670
2670
2671 Use --verbose to print the parsed tree.
2671 Use --verbose to print the parsed tree.
2672 """
2672 """
2673 revs = None
2673 revs = None
2674 if opts[r'rev']:
2674 if opts[r'rev']:
2675 if repo is None:
2675 if repo is None:
2676 raise error.RepoError(_('there is no Mercurial repository here '
2676 raise error.RepoError(_('there is no Mercurial repository here '
2677 '(.hg not found)'))
2677 '(.hg not found)'))
2678 revs = scmutil.revrange(repo, opts[r'rev'])
2678 revs = scmutil.revrange(repo, opts[r'rev'])
2679
2679
2680 props = {}
2680 props = {}
2681 for d in opts[r'define']:
2681 for d in opts[r'define']:
2682 try:
2682 try:
2683 k, v = (e.strip() for e in d.split('=', 1))
2683 k, v = (e.strip() for e in d.split('=', 1))
2684 if not k or k == 'ui':
2684 if not k or k == 'ui':
2685 raise ValueError
2685 raise ValueError
2686 props[k] = v
2686 props[k] = v
2687 except ValueError:
2687 except ValueError:
2688 raise error.Abort(_('malformed keyword definition: %s') % d)
2688 raise error.Abort(_('malformed keyword definition: %s') % d)
2689
2689
2690 if ui.verbose:
2690 if ui.verbose:
2691 aliases = ui.configitems('templatealias')
2691 aliases = ui.configitems('templatealias')
2692 tree = templater.parse(tmpl)
2692 tree = templater.parse(tmpl)
2693 ui.note(templater.prettyformat(tree), '\n')
2693 ui.note(templater.prettyformat(tree), '\n')
2694 newtree = templater.expandaliases(tree, aliases)
2694 newtree = templater.expandaliases(tree, aliases)
2695 if newtree != tree:
2695 if newtree != tree:
2696 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2696 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2697
2697
2698 if revs is None:
2698 if revs is None:
2699 tres = formatter.templateresources(ui, repo)
2699 tres = formatter.templateresources(ui, repo)
2700 t = formatter.maketemplater(ui, tmpl, resources=tres)
2700 t = formatter.maketemplater(ui, tmpl, resources=tres)
2701 if ui.verbose:
2701 if ui.verbose:
2702 kwds, funcs = t.symbolsuseddefault()
2702 kwds, funcs = t.symbolsuseddefault()
2703 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2703 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2704 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2704 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2705 ui.write(t.renderdefault(props))
2705 ui.write(t.renderdefault(props))
2706 else:
2706 else:
2707 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2707 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2708 if ui.verbose:
2708 if ui.verbose:
2709 kwds, funcs = displayer.t.symbolsuseddefault()
2709 kwds, funcs = displayer.t.symbolsuseddefault()
2710 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2710 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2711 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2711 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2712 for r in revs:
2712 for r in revs:
2713 displayer.show(repo[r], **pycompat.strkwargs(props))
2713 displayer.show(repo[r], **pycompat.strkwargs(props))
2714 displayer.close()
2714 displayer.close()
2715
2715
2716 @command('debuguigetpass', [
2716 @command('debuguigetpass', [
2717 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2717 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2718 ], _('[-p TEXT]'), norepo=True)
2718 ], _('[-p TEXT]'), norepo=True)
2719 def debuguigetpass(ui, prompt=''):
2719 def debuguigetpass(ui, prompt=''):
2720 """show prompt to type password"""
2720 """show prompt to type password"""
2721 r = ui.getpass(prompt)
2721 r = ui.getpass(prompt)
2722 ui.write(('respose: %s\n') % r)
2722 ui.write(('respose: %s\n') % r)
2723
2723
2724 @command('debuguiprompt', [
2724 @command('debuguiprompt', [
2725 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2725 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2726 ], _('[-p TEXT]'), norepo=True)
2726 ], _('[-p TEXT]'), norepo=True)
2727 def debuguiprompt(ui, prompt=''):
2727 def debuguiprompt(ui, prompt=''):
2728 """show plain prompt"""
2728 """show plain prompt"""
2729 r = ui.prompt(prompt)
2729 r = ui.prompt(prompt)
2730 ui.write(('response: %s\n') % r)
2730 ui.write(('response: %s\n') % r)
2731
2731
2732 @command('debugupdatecaches', [])
2732 @command('debugupdatecaches', [])
2733 def debugupdatecaches(ui, repo, *pats, **opts):
2733 def debugupdatecaches(ui, repo, *pats, **opts):
2734 """warm all known caches in the repository"""
2734 """warm all known caches in the repository"""
2735 with repo.wlock(), repo.lock():
2735 with repo.wlock(), repo.lock():
2736 repo.updatecaches(full=True)
2736 repo.updatecaches(full=True)
2737
2737
2738 @command('debugupgraderepo', [
2738 @command('debugupgraderepo', [
2739 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2739 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2740 ('', 'run', False, _('performs an upgrade')),
2740 ('', 'run', False, _('performs an upgrade')),
2741 ])
2741 ])
2742 def debugupgraderepo(ui, repo, run=False, optimize=None):
2742 def debugupgraderepo(ui, repo, run=False, optimize=None):
2743 """upgrade a repository to use different features
2743 """upgrade a repository to use different features
2744
2744
2745 If no arguments are specified, the repository is evaluated for upgrade
2745 If no arguments are specified, the repository is evaluated for upgrade
2746 and a list of problems and potential optimizations is printed.
2746 and a list of problems and potential optimizations is printed.
2747
2747
2748 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2748 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2749 can be influenced via additional arguments. More details will be provided
2749 can be influenced via additional arguments. More details will be provided
2750 by the command output when run without ``--run``.
2750 by the command output when run without ``--run``.
2751
2751
2752 During the upgrade, the repository will be locked and no writes will be
2752 During the upgrade, the repository will be locked and no writes will be
2753 allowed.
2753 allowed.
2754
2754
2755 At the end of the upgrade, the repository may not be readable while new
2755 At the end of the upgrade, the repository may not be readable while new
2756 repository data is swapped in. This window will be as long as it takes to
2756 repository data is swapped in. This window will be as long as it takes to
2757 rename some directories inside the ``.hg`` directory. On most machines, this
2757 rename some directories inside the ``.hg`` directory. On most machines, this
2758 should complete almost instantaneously and the chances of a consumer being
2758 should complete almost instantaneously and the chances of a consumer being
2759 unable to access the repository should be low.
2759 unable to access the repository should be low.
2760 """
2760 """
2761 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2761 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2762
2762
2763 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2763 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2764 inferrepo=True)
2764 inferrepo=True)
2765 def debugwalk(ui, repo, *pats, **opts):
2765 def debugwalk(ui, repo, *pats, **opts):
2766 """show how files match on given patterns"""
2766 """show how files match on given patterns"""
2767 opts = pycompat.byteskwargs(opts)
2767 opts = pycompat.byteskwargs(opts)
2768 m = scmutil.match(repo[None], pats, opts)
2768 m = scmutil.match(repo[None], pats, opts)
2769 if ui.verbose:
2769 if ui.verbose:
2770 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
2770 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
2771 items = list(repo[None].walk(m))
2771 items = list(repo[None].walk(m))
2772 if not items:
2772 if not items:
2773 return
2773 return
2774 f = lambda fn: fn
2774 f = lambda fn: fn
2775 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2775 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2776 f = lambda fn: util.normpath(fn)
2776 f = lambda fn: util.normpath(fn)
2777 fmt = 'f %%-%ds %%-%ds %%s' % (
2777 fmt = 'f %%-%ds %%-%ds %%s' % (
2778 max([len(abs) for abs in items]),
2778 max([len(abs) for abs in items]),
2779 max([len(m.rel(abs)) for abs in items]))
2779 max([len(m.rel(abs)) for abs in items]))
2780 for abs in items:
2780 for abs in items:
2781 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2781 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2782 ui.write("%s\n" % line.rstrip())
2782 ui.write("%s\n" % line.rstrip())
2783
2783
2784 @command('debugwhyunstable', [], _('REV'))
2784 @command('debugwhyunstable', [], _('REV'))
2785 def debugwhyunstable(ui, repo, rev):
2785 def debugwhyunstable(ui, repo, rev):
2786 """explain instabilities of a changeset"""
2786 """explain instabilities of a changeset"""
2787 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
2787 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
2788 dnodes = ''
2788 dnodes = ''
2789 if entry.get('divergentnodes'):
2789 if entry.get('divergentnodes'):
2790 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2790 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2791 for ctx in entry['divergentnodes']) + ' '
2791 for ctx in entry['divergentnodes']) + ' '
2792 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2792 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2793 entry['reason'], entry['node']))
2793 entry['reason'], entry['node']))
2794
2794
2795 @command('debugwireargs',
2795 @command('debugwireargs',
2796 [('', 'three', '', 'three'),
2796 [('', 'three', '', 'three'),
2797 ('', 'four', '', 'four'),
2797 ('', 'four', '', 'four'),
2798 ('', 'five', '', 'five'),
2798 ('', 'five', '', 'five'),
2799 ] + cmdutil.remoteopts,
2799 ] + cmdutil.remoteopts,
2800 _('REPO [OPTIONS]... [ONE [TWO]]'),
2800 _('REPO [OPTIONS]... [ONE [TWO]]'),
2801 norepo=True)
2801 norepo=True)
2802 def debugwireargs(ui, repopath, *vals, **opts):
2802 def debugwireargs(ui, repopath, *vals, **opts):
2803 opts = pycompat.byteskwargs(opts)
2803 opts = pycompat.byteskwargs(opts)
2804 repo = hg.peer(ui, opts, repopath)
2804 repo = hg.peer(ui, opts, repopath)
2805 for opt in cmdutil.remoteopts:
2805 for opt in cmdutil.remoteopts:
2806 del opts[opt[1]]
2806 del opts[opt[1]]
2807 args = {}
2807 args = {}
2808 for k, v in opts.iteritems():
2808 for k, v in opts.iteritems():
2809 if v:
2809 if v:
2810 args[k] = v
2810 args[k] = v
2811 args = pycompat.strkwargs(args)
2811 args = pycompat.strkwargs(args)
2812 # run twice to check that we don't mess up the stream for the next command
2812 # run twice to check that we don't mess up the stream for the next command
2813 res1 = repo.debugwireargs(*vals, **args)
2813 res1 = repo.debugwireargs(*vals, **args)
2814 res2 = repo.debugwireargs(*vals, **args)
2814 res2 = repo.debugwireargs(*vals, **args)
2815 ui.write("%s\n" % res1)
2815 ui.write("%s\n" % res1)
2816 if res1 != res2:
2816 if res1 != res2:
2817 ui.warn("%s\n" % res2)
2817 ui.warn("%s\n" % res2)
2818
2818
2819 def _parsewirelangblocks(fh):
2819 def _parsewirelangblocks(fh):
2820 activeaction = None
2820 activeaction = None
2821 blocklines = []
2821 blocklines = []
2822
2822
2823 for line in fh:
2823 for line in fh:
2824 line = line.rstrip()
2824 line = line.rstrip()
2825 if not line:
2825 if not line:
2826 continue
2826 continue
2827
2827
2828 if line.startswith(b'#'):
2828 if line.startswith(b'#'):
2829 continue
2829 continue
2830
2830
2831 if not line.startswith(b' '):
2831 if not line.startswith(b' '):
2832 # New block. Flush previous one.
2832 # New block. Flush previous one.
2833 if activeaction:
2833 if activeaction:
2834 yield activeaction, blocklines
2834 yield activeaction, blocklines
2835
2835
2836 activeaction = line
2836 activeaction = line
2837 blocklines = []
2837 blocklines = []
2838 continue
2838 continue
2839
2839
2840 # Else we start with an indent.
2840 # Else we start with an indent.
2841
2841
2842 if not activeaction:
2842 if not activeaction:
2843 raise error.Abort(_('indented line outside of block'))
2843 raise error.Abort(_('indented line outside of block'))
2844
2844
2845 blocklines.append(line)
2845 blocklines.append(line)
2846
2846
2847 # Flush last block.
2847 # Flush last block.
2848 if activeaction:
2848 if activeaction:
2849 yield activeaction, blocklines
2849 yield activeaction, blocklines
2850
2850
2851 @command('debugwireproto',
2851 @command('debugwireproto',
2852 [
2852 [
2853 ('', 'localssh', False, _('start an SSH server for this repo')),
2853 ('', 'localssh', False, _('start an SSH server for this repo')),
2854 ('', 'peer', '', _('construct a specific version of the peer')),
2854 ('', 'peer', '', _('construct a specific version of the peer')),
2855 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2855 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2856 ('', 'nologhandshake', False,
2856 ('', 'nologhandshake', False,
2857 _('do not log I/O related to the peer handshake')),
2857 _('do not log I/O related to the peer handshake')),
2858 ] + cmdutil.remoteopts,
2858 ] + cmdutil.remoteopts,
2859 _('[PATH]'),
2859 _('[PATH]'),
2860 optionalrepo=True)
2860 optionalrepo=True)
2861 def debugwireproto(ui, repo, path=None, **opts):
2861 def debugwireproto(ui, repo, path=None, **opts):
2862 """send wire protocol commands to a server
2862 """send wire protocol commands to a server
2863
2863
2864 This command can be used to issue wire protocol commands to remote
2864 This command can be used to issue wire protocol commands to remote
2865 peers and to debug the raw data being exchanged.
2865 peers and to debug the raw data being exchanged.
2866
2866
2867 ``--localssh`` will start an SSH server against the current repository
2867 ``--localssh`` will start an SSH server against the current repository
2868 and connect to that. By default, the connection will perform a handshake
2868 and connect to that. By default, the connection will perform a handshake
2869 and establish an appropriate peer instance.
2869 and establish an appropriate peer instance.
2870
2870
2871 ``--peer`` can be used to bypass the handshake protocol and construct a
2871 ``--peer`` can be used to bypass the handshake protocol and construct a
2872 peer instance using the specified class type. Valid values are ``raw``,
2872 peer instance using the specified class type. Valid values are ``raw``,
2873 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2873 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2874 raw data payloads and don't support higher-level command actions.
2874 raw data payloads and don't support higher-level command actions.
2875
2875
2876 ``--noreadstderr`` can be used to disable automatic reading from stderr
2876 ``--noreadstderr`` can be used to disable automatic reading from stderr
2877 of the peer (for SSH connections only). Disabling automatic reading of
2877 of the peer (for SSH connections only). Disabling automatic reading of
2878 stderr is useful for making output more deterministic.
2878 stderr is useful for making output more deterministic.
2879
2879
2880 Commands are issued via a mini language which is specified via stdin.
2880 Commands are issued via a mini language which is specified via stdin.
2881 The language consists of individual actions to perform. An action is
2881 The language consists of individual actions to perform. An action is
2882 defined by a block. A block is defined as a line with no leading
2882 defined by a block. A block is defined as a line with no leading
2883 space followed by 0 or more lines with leading space. Blocks are
2883 space followed by 0 or more lines with leading space. Blocks are
2884 effectively a high-level command with additional metadata.
2884 effectively a high-level command with additional metadata.
2885
2885
2886 Lines beginning with ``#`` are ignored.
2886 Lines beginning with ``#`` are ignored.
2887
2887
2888 The following sections denote available actions.
2888 The following sections denote available actions.
2889
2889
2890 raw
2890 raw
2891 ---
2891 ---
2892
2892
2893 Send raw data to the server.
2893 Send raw data to the server.
2894
2894
2895 The block payload contains the raw data to send as one atomic send
2895 The block payload contains the raw data to send as one atomic send
2896 operation. The data may not actually be delivered in a single system
2896 operation. The data may not actually be delivered in a single system
2897 call: it depends on the abilities of the transport being used.
2897 call: it depends on the abilities of the transport being used.
2898
2898
2899 Each line in the block is de-indented and concatenated. Then, that
2899 Each line in the block is de-indented and concatenated. Then, that
2900 value is evaluated as a Python b'' literal. This allows the use of
2900 value is evaluated as a Python b'' literal. This allows the use of
2901 backslash escaping, etc.
2901 backslash escaping, etc.
2902
2902
2903 raw+
2903 raw+
2904 ----
2904 ----
2905
2905
2906 Behaves like ``raw`` except flushes output afterwards.
2906 Behaves like ``raw`` except flushes output afterwards.
2907
2907
2908 command <X>
2908 command <X>
2909 -----------
2909 -----------
2910
2910
2911 Send a request to run a named command, whose name follows the ``command``
2911 Send a request to run a named command, whose name follows the ``command``
2912 string.
2912 string.
2913
2913
2914 Arguments to the command are defined as lines in this block. The format of
2914 Arguments to the command are defined as lines in this block. The format of
2915 each line is ``<key> <value>``. e.g.::
2915 each line is ``<key> <value>``. e.g.::
2916
2916
2917 command listkeys
2917 command listkeys
2918 namespace bookmarks
2918 namespace bookmarks
2919
2919
2920 If the value begins with ``eval:``, it will be interpreted as a Python
2920 If the value begins with ``eval:``, it will be interpreted as a Python
2921 literal expression. Otherwise values are interpreted as Python b'' literals.
2921 literal expression. Otherwise values are interpreted as Python b'' literals.
2922 This allows sending complex types and encoding special byte sequences via
2922 This allows sending complex types and encoding special byte sequences via
2923 backslash escaping.
2923 backslash escaping.
2924
2924
2925 The following arguments have special meaning:
2925 The following arguments have special meaning:
2926
2926
2927 ``PUSHFILE``
2927 ``PUSHFILE``
2928 When defined, the *push* mechanism of the peer will be used instead
2928 When defined, the *push* mechanism of the peer will be used instead
2929 of the static request-response mechanism and the content of the
2929 of the static request-response mechanism and the content of the
2930 file specified in the value of this argument will be sent as the
2930 file specified in the value of this argument will be sent as the
2931 command payload.
2931 command payload.
2932
2932
2933 This can be used to submit a local bundle file to the remote.
2933 This can be used to submit a local bundle file to the remote.
2934
2934
2935 batchbegin
2935 batchbegin
2936 ----------
2936 ----------
2937
2937
2938 Instruct the peer to begin a batched send.
2938 Instruct the peer to begin a batched send.
2939
2939
2940 All ``command`` blocks are queued for execution until the next
2940 All ``command`` blocks are queued for execution until the next
2941 ``batchsubmit`` block.
2941 ``batchsubmit`` block.
2942
2942
2943 batchsubmit
2943 batchsubmit
2944 -----------
2944 -----------
2945
2945
2946 Submit previously queued ``command`` blocks as a batch request.
2946 Submit previously queued ``command`` blocks as a batch request.
2947
2947
2948 This action MUST be paired with a ``batchbegin`` action.
2948 This action MUST be paired with a ``batchbegin`` action.
2949
2949
2950 httprequest <method> <path>
2950 httprequest <method> <path>
2951 ---------------------------
2951 ---------------------------
2952
2952
2953 (HTTP peer only)
2953 (HTTP peer only)
2954
2954
2955 Send an HTTP request to the peer.
2955 Send an HTTP request to the peer.
2956
2956
2957 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2957 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2958
2958
2959 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2959 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2960 headers to add to the request. e.g. ``Accept: foo``.
2960 headers to add to the request. e.g. ``Accept: foo``.
2961
2961
2962 The following arguments are special:
2962 The following arguments are special:
2963
2963
2964 ``BODYFILE``
2964 ``BODYFILE``
2965 The content of the file defined as the value to this argument will be
2965 The content of the file defined as the value to this argument will be
2966 transferred verbatim as the HTTP request body.
2966 transferred verbatim as the HTTP request body.
2967
2967
2968 ``frame <type> <flags> <payload>``
2968 ``frame <type> <flags> <payload>``
2969 Send a unified protocol frame as part of the request body.
2969 Send a unified protocol frame as part of the request body.
2970
2970
2971 All frames will be collected and sent as the body to the HTTP
2971 All frames will be collected and sent as the body to the HTTP
2972 request.
2972 request.
2973
2973
2974 close
2974 close
2975 -----
2975 -----
2976
2976
2977 Close the connection to the server.
2977 Close the connection to the server.
2978
2978
2979 flush
2979 flush
2980 -----
2980 -----
2981
2981
2982 Flush data written to the server.
2982 Flush data written to the server.
2983
2983
2984 readavailable
2984 readavailable
2985 -------------
2985 -------------
2986
2986
2987 Close the write end of the connection and read all available data from
2987 Close the write end of the connection and read all available data from
2988 the server.
2988 the server.
2989
2989
2990 If the connection to the server encompasses multiple pipes, we poll both
2990 If the connection to the server encompasses multiple pipes, we poll both
2991 pipes and read available data.
2991 pipes and read available data.
2992
2992
2993 readline
2993 readline
2994 --------
2994 --------
2995
2995
2996 Read a line of output from the server. If there are multiple output
2996 Read a line of output from the server. If there are multiple output
2997 pipes, reads only the main pipe.
2997 pipes, reads only the main pipe.
2998
2998
2999 ereadline
2999 ereadline
3000 ---------
3000 ---------
3001
3001
3002 Like ``readline``, but read from the stderr pipe, if available.
3002 Like ``readline``, but read from the stderr pipe, if available.
3003
3003
3004 read <X>
3004 read <X>
3005 --------
3005 --------
3006
3006
3007 ``read()`` N bytes from the server's main output pipe.
3007 ``read()`` N bytes from the server's main output pipe.
3008
3008
3009 eread <X>
3009 eread <X>
3010 ---------
3010 ---------
3011
3011
3012 ``read()`` N bytes from the server's stderr pipe, if available.
3012 ``read()`` N bytes from the server's stderr pipe, if available.
3013
3013
3014 Specifying Unified Frame-Based Protocol Frames
3014 Specifying Unified Frame-Based Protocol Frames
3015 ----------------------------------------------
3015 ----------------------------------------------
3016
3016
3017 It is possible to emit a *Unified Frame-Based Protocol* by using special
3017 It is possible to emit a *Unified Frame-Based Protocol* by using special
3018 syntax.
3018 syntax.
3019
3019
3020 A frame is composed as a type, flags, and payload. These can be parsed
3020 A frame is composed as a type, flags, and payload. These can be parsed
3021 from a string of the form:
3021 from a string of the form:
3022
3022
3023 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
3023 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
3024
3024
3025 ``request-id`` and ``stream-id`` are integers defining the request and
3025 ``request-id`` and ``stream-id`` are integers defining the request and
3026 stream identifiers.
3026 stream identifiers.
3027
3027
3028 ``type`` can be an integer value for the frame type or the string name
3028 ``type`` can be an integer value for the frame type or the string name
3029 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
3029 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
3030 ``command-name``.
3030 ``command-name``.
3031
3031
3032 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
3032 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
3033 components. Each component (and there can be just one) can be an integer
3033 components. Each component (and there can be just one) can be an integer
3034 or a flag name for stream flags or frame flags, respectively. Values are
3034 or a flag name for stream flags or frame flags, respectively. Values are
3035 resolved to integers and then bitwise OR'd together.
3035 resolved to integers and then bitwise OR'd together.
3036
3036
3037 ``payload`` represents the raw frame payload. If it begins with
3037 ``payload`` represents the raw frame payload. If it begins with
3038 ``cbor:``, the following string is evaluated as Python code and the
3038 ``cbor:``, the following string is evaluated as Python code and the
3039 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
3039 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
3040 as a Python byte string literal.
3040 as a Python byte string literal.
3041 """
3041 """
3042 opts = pycompat.byteskwargs(opts)
3042 opts = pycompat.byteskwargs(opts)
3043
3043
3044 if opts['localssh'] and not repo:
3044 if opts['localssh'] and not repo:
3045 raise error.Abort(_('--localssh requires a repository'))
3045 raise error.Abort(_('--localssh requires a repository'))
3046
3046
3047 if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'):
3047 if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'):
3048 raise error.Abort(_('invalid value for --peer'),
3048 raise error.Abort(_('invalid value for --peer'),
3049 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
3049 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
3050
3050
3051 if path and opts['localssh']:
3051 if path and opts['localssh']:
3052 raise error.Abort(_('cannot specify --localssh with an explicit '
3052 raise error.Abort(_('cannot specify --localssh with an explicit '
3053 'path'))
3053 'path'))
3054
3054
3055 if ui.interactive():
3055 if ui.interactive():
3056 ui.write(_('(waiting for commands on stdin)\n'))
3056 ui.write(_('(waiting for commands on stdin)\n'))
3057
3057
3058 blocks = list(_parsewirelangblocks(ui.fin))
3058 blocks = list(_parsewirelangblocks(ui.fin))
3059
3059
3060 proc = None
3060 proc = None
3061 stdin = None
3061 stdin = None
3062 stdout = None
3062 stdout = None
3063 stderr = None
3063 stderr = None
3064 opener = None
3064 opener = None
3065
3065
3066 if opts['localssh']:
3066 if opts['localssh']:
3067 # We start the SSH server in its own process so there is process
3067 # We start the SSH server in its own process so there is process
3068 # separation. This prevents a whole class of potential bugs around
3068 # separation. This prevents a whole class of potential bugs around
3069 # shared state from interfering with server operation.
3069 # shared state from interfering with server operation.
3070 args = procutil.hgcmd() + [
3070 args = procutil.hgcmd() + [
3071 '-R', repo.root,
3071 '-R', repo.root,
3072 'debugserve', '--sshstdio',
3072 'debugserve', '--sshstdio',
3073 ]
3073 ]
3074 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
3074 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
3075 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
3075 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
3076 bufsize=0)
3076 bufsize=0)
3077
3077
3078 stdin = proc.stdin
3078 stdin = proc.stdin
3079 stdout = proc.stdout
3079 stdout = proc.stdout
3080 stderr = proc.stderr
3080 stderr = proc.stderr
3081
3081
3082 # We turn the pipes into observers so we can log I/O.
3082 # We turn the pipes into observers so we can log I/O.
3083 if ui.verbose or opts['peer'] == 'raw':
3083 if ui.verbose or opts['peer'] == 'raw':
3084 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
3084 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
3085 logdata=True)
3085 logdata=True)
3086 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
3086 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
3087 logdata=True)
3087 logdata=True)
3088 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
3088 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
3089 logdata=True)
3089 logdata=True)
3090
3090
3091 # --localssh also implies the peer connection settings.
3091 # --localssh also implies the peer connection settings.
3092
3092
3093 url = 'ssh://localserver'
3093 url = 'ssh://localserver'
3094 autoreadstderr = not opts['noreadstderr']
3094 autoreadstderr = not opts['noreadstderr']
3095
3095
3096 if opts['peer'] == 'ssh1':
3096 if opts['peer'] == 'ssh1':
3097 ui.write(_('creating ssh peer for wire protocol version 1\n'))
3097 ui.write(_('creating ssh peer for wire protocol version 1\n'))
3098 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
3098 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
3099 None, autoreadstderr=autoreadstderr)
3099 None, autoreadstderr=autoreadstderr)
3100 elif opts['peer'] == 'ssh2':
3100 elif opts['peer'] == 'ssh2':
3101 ui.write(_('creating ssh peer for wire protocol version 2\n'))
3101 ui.write(_('creating ssh peer for wire protocol version 2\n'))
3102 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
3102 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
3103 None, autoreadstderr=autoreadstderr)
3103 None, autoreadstderr=autoreadstderr)
3104 elif opts['peer'] == 'raw':
3104 elif opts['peer'] == 'raw':
3105 ui.write(_('using raw connection to peer\n'))
3105 ui.write(_('using raw connection to peer\n'))
3106 peer = None
3106 peer = None
3107 else:
3107 else:
3108 ui.write(_('creating ssh peer from handshake results\n'))
3108 ui.write(_('creating ssh peer from handshake results\n'))
3109 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
3109 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
3110 autoreadstderr=autoreadstderr)
3110 autoreadstderr=autoreadstderr)
3111
3111
3112 elif path:
3112 elif path:
3113 # We bypass hg.peer() so we can proxy the sockets.
3113 # We bypass hg.peer() so we can proxy the sockets.
3114 # TODO consider not doing this because we skip
3114 # TODO consider not doing this because we skip
3115 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
3115 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
3116 u = util.url(path)
3116 u = util.url(path)
3117 if u.scheme != 'http':
3117 if u.scheme != 'http':
3118 raise error.Abort(_('only http:// paths are currently supported'))
3118 raise error.Abort(_('only http:// paths are currently supported'))
3119
3119
3120 url, authinfo = u.authinfo()
3120 url, authinfo = u.authinfo()
3121 openerargs = {
3121 openerargs = {
3122 r'useragent': b'Mercurial debugwireproto',
3122 r'useragent': b'Mercurial debugwireproto',
3123 }
3123 }
3124
3124
3125 # Turn pipes/sockets into observers so we can log I/O.
3125 # Turn pipes/sockets into observers so we can log I/O.
3126 if ui.verbose:
3126 if ui.verbose:
3127 openerargs.update({
3127 openerargs.update({
3128 r'loggingfh': ui,
3128 r'loggingfh': ui,
3129 r'loggingname': b's',
3129 r'loggingname': b's',
3130 r'loggingopts': {
3130 r'loggingopts': {
3131 r'logdata': True,
3131 r'logdata': True,
3132 r'logdataapis': False,
3132 r'logdataapis': False,
3133 },
3133 },
3134 })
3134 })
3135
3135
3136 if ui.debugflag:
3136 if ui.debugflag:
3137 openerargs[r'loggingopts'][r'logdataapis'] = True
3137 openerargs[r'loggingopts'][r'logdataapis'] = True
3138
3138
3139 # Don't send default headers when in raw mode. This allows us to
3139 # Don't send default headers when in raw mode. This allows us to
3140 # bypass most of the behavior of our URL handling code so we can
3140 # bypass most of the behavior of our URL handling code so we can
3141 # have near complete control over what's sent on the wire.
3141 # have near complete control over what's sent on the wire.
3142 if opts['peer'] == 'raw':
3142 if opts['peer'] == 'raw':
3143 openerargs[r'sendaccept'] = False
3143 openerargs[r'sendaccept'] = False
3144
3144
3145 opener = urlmod.opener(ui, authinfo, **openerargs)
3145 opener = urlmod.opener(ui, authinfo, **openerargs)
3146
3146
3147 if opts['peer'] == 'http2':
3147 if opts['peer'] == 'http2':
3148 ui.write(_('creating http peer for wire protocol version 2\n'))
3148 ui.write(_('creating http peer for wire protocol version 2\n'))
3149 # We go through makepeer() because we need an API descriptor for
3149 # We go through makepeer() because we need an API descriptor for
3150 # the peer instance to be useful.
3150 # the peer instance to be useful.
3151 with ui.configoverride({
3151 with ui.configoverride({
3152 ('experimental', 'httppeer.advertise-v2'): True}):
3152 ('experimental', 'httppeer.advertise-v2'): True}):
3153 if opts['nologhandshake']:
3153 if opts['nologhandshake']:
3154 ui.pushbuffer()
3154 ui.pushbuffer()
3155
3155
3156 peer = httppeer.makepeer(ui, path, opener=opener)
3156 peer = httppeer.makepeer(ui, path, opener=opener)
3157
3157
3158 if opts['nologhandshake']:
3158 if opts['nologhandshake']:
3159 ui.popbuffer()
3159 ui.popbuffer()
3160
3160
3161 if not isinstance(peer, httppeer.httpv2peer):
3161 if not isinstance(peer, httppeer.httpv2peer):
3162 raise error.Abort(_('could not instantiate HTTP peer for '
3162 raise error.Abort(_('could not instantiate HTTP peer for '
3163 'wire protocol version 2'),
3163 'wire protocol version 2'),
3164 hint=_('the server may not have the feature '
3164 hint=_('the server may not have the feature '
3165 'enabled or is not allowing this '
3165 'enabled or is not allowing this '
3166 'client version'))
3166 'client version'))
3167
3167
3168 elif opts['peer'] == 'raw':
3168 elif opts['peer'] == 'raw':
3169 ui.write(_('using raw connection to peer\n'))
3169 ui.write(_('using raw connection to peer\n'))
3170 peer = None
3170 peer = None
3171 elif opts['peer']:
3171 elif opts['peer']:
3172 raise error.Abort(_('--peer %s not supported with HTTP peers') %
3172 raise error.Abort(_('--peer %s not supported with HTTP peers') %
3173 opts['peer'])
3173 opts['peer'])
3174 else:
3174 else:
3175 peer = httppeer.makepeer(ui, path, opener=opener)
3175 peer = httppeer.makepeer(ui, path, opener=opener)
3176
3176
3177 # We /could/ populate stdin/stdout with sock.makefile()...
3177 # We /could/ populate stdin/stdout with sock.makefile()...
3178 else:
3178 else:
3179 raise error.Abort(_('unsupported connection configuration'))
3179 raise error.Abort(_('unsupported connection configuration'))
3180
3180
3181 batchedcommands = None
3181 batchedcommands = None
3182
3182
3183 # Now perform actions based on the parsed wire language instructions.
3183 # Now perform actions based on the parsed wire language instructions.
3184 for action, lines in blocks:
3184 for action, lines in blocks:
3185 if action in ('raw', 'raw+'):
3185 if action in ('raw', 'raw+'):
3186 if not stdin:
3186 if not stdin:
3187 raise error.Abort(_('cannot call raw/raw+ on this peer'))
3187 raise error.Abort(_('cannot call raw/raw+ on this peer'))
3188
3188
3189 # Concatenate the data together.
3189 # Concatenate the data together.
3190 data = ''.join(l.lstrip() for l in lines)
3190 data = ''.join(l.lstrip() for l in lines)
3191 data = stringutil.unescapestr(data)
3191 data = stringutil.unescapestr(data)
3192 stdin.write(data)
3192 stdin.write(data)
3193
3193
3194 if action == 'raw+':
3194 if action == 'raw+':
3195 stdin.flush()
3195 stdin.flush()
3196 elif action == 'flush':
3196 elif action == 'flush':
3197 if not stdin:
3197 if not stdin:
3198 raise error.Abort(_('cannot call flush on this peer'))
3198 raise error.Abort(_('cannot call flush on this peer'))
3199 stdin.flush()
3199 stdin.flush()
3200 elif action.startswith('command'):
3200 elif action.startswith('command'):
3201 if not peer:
3201 if not peer:
3202 raise error.Abort(_('cannot send commands unless peer instance '
3202 raise error.Abort(_('cannot send commands unless peer instance '
3203 'is available'))
3203 'is available'))
3204
3204
3205 command = action.split(' ', 1)[1]
3205 command = action.split(' ', 1)[1]
3206
3206
3207 args = {}
3207 args = {}
3208 for line in lines:
3208 for line in lines:
3209 # We need to allow empty values.
3209 # We need to allow empty values.
3210 fields = line.lstrip().split(' ', 1)
3210 fields = line.lstrip().split(' ', 1)
3211 if len(fields) == 1:
3211 if len(fields) == 1:
3212 key = fields[0]
3212 key = fields[0]
3213 value = ''
3213 value = ''
3214 else:
3214 else:
3215 key, value = fields
3215 key, value = fields
3216
3216
3217 if value.startswith('eval:'):
3217 if value.startswith('eval:'):
3218 value = stringutil.evalpythonliteral(value[5:])
3218 value = stringutil.evalpythonliteral(value[5:])
3219 else:
3219 else:
3220 value = stringutil.unescapestr(value)
3220 value = stringutil.unescapestr(value)
3221
3221
3222 args[key] = value
3222 args[key] = value
3223
3223
3224 if batchedcommands is not None:
3224 if batchedcommands is not None:
3225 batchedcommands.append((command, args))
3225 batchedcommands.append((command, args))
3226 continue
3226 continue
3227
3227
3228 ui.status(_('sending %s command\n') % command)
3228 ui.status(_('sending %s command\n') % command)
3229
3229
3230 if 'PUSHFILE' in args:
3230 if 'PUSHFILE' in args:
3231 with open(args['PUSHFILE'], r'rb') as fh:
3231 with open(args['PUSHFILE'], r'rb') as fh:
3232 del args['PUSHFILE']
3232 del args['PUSHFILE']
3233 res, output = peer._callpush(command, fh,
3233 res, output = peer._callpush(command, fh,
3234 **pycompat.strkwargs(args))
3234 **pycompat.strkwargs(args))
3235 ui.status(_('result: %s\n') % stringutil.escapestr(res))
3235 ui.status(_('result: %s\n') % stringutil.escapestr(res))
3236 ui.status(_('remote output: %s\n') %
3236 ui.status(_('remote output: %s\n') %
3237 stringutil.escapestr(output))
3237 stringutil.escapestr(output))
3238 else:
3238 else:
3239 with peer.commandexecutor() as e:
3239 with peer.commandexecutor() as e:
3240 res = e.callcommand(command, args).result()
3240 res = e.callcommand(command, args).result()
3241
3241
3242 if isinstance(res, wireprotov2peer.commandresponse):
3242 if isinstance(res, wireprotov2peer.commandresponse):
3243 val = list(res.cborobjects())
3243 val = res.objects()
3244 ui.status(_('response: %s\n') %
3244 ui.status(_('response: %s\n') %
3245 stringutil.pprint(val, bprefix=True, indent=2))
3245 stringutil.pprint(val, bprefix=True, indent=2))
3246 else:
3246 else:
3247 ui.status(_('response: %s\n') %
3247 ui.status(_('response: %s\n') %
3248 stringutil.pprint(res, bprefix=True, indent=2))
3248 stringutil.pprint(res, bprefix=True, indent=2))
3249
3249
3250 elif action == 'batchbegin':
3250 elif action == 'batchbegin':
3251 if batchedcommands is not None:
3251 if batchedcommands is not None:
3252 raise error.Abort(_('nested batchbegin not allowed'))
3252 raise error.Abort(_('nested batchbegin not allowed'))
3253
3253
3254 batchedcommands = []
3254 batchedcommands = []
3255 elif action == 'batchsubmit':
3255 elif action == 'batchsubmit':
3256 # There is a batching API we could go through. But it would be
3256 # There is a batching API we could go through. But it would be
3257 # difficult to normalize requests into function calls. It is easier
3257 # difficult to normalize requests into function calls. It is easier
3258 # to bypass this layer and normalize to commands + args.
3258 # to bypass this layer and normalize to commands + args.
3259 ui.status(_('sending batch with %d sub-commands\n') %
3259 ui.status(_('sending batch with %d sub-commands\n') %
3260 len(batchedcommands))
3260 len(batchedcommands))
3261 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
3261 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
3262 ui.status(_('response #%d: %s\n') %
3262 ui.status(_('response #%d: %s\n') %
3263 (i, stringutil.escapestr(chunk)))
3263 (i, stringutil.escapestr(chunk)))
3264
3264
3265 batchedcommands = None
3265 batchedcommands = None
3266
3266
3267 elif action.startswith('httprequest '):
3267 elif action.startswith('httprequest '):
3268 if not opener:
3268 if not opener:
3269 raise error.Abort(_('cannot use httprequest without an HTTP '
3269 raise error.Abort(_('cannot use httprequest without an HTTP '
3270 'peer'))
3270 'peer'))
3271
3271
3272 request = action.split(' ', 2)
3272 request = action.split(' ', 2)
3273 if len(request) != 3:
3273 if len(request) != 3:
3274 raise error.Abort(_('invalid httprequest: expected format is '
3274 raise error.Abort(_('invalid httprequest: expected format is '
3275 '"httprequest <method> <path>'))
3275 '"httprequest <method> <path>'))
3276
3276
3277 method, httppath = request[1:]
3277 method, httppath = request[1:]
3278 headers = {}
3278 headers = {}
3279 body = None
3279 body = None
3280 frames = []
3280 frames = []
3281 for line in lines:
3281 for line in lines:
3282 line = line.lstrip()
3282 line = line.lstrip()
3283 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3283 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3284 if m:
3284 if m:
3285 headers[m.group(1)] = m.group(2)
3285 headers[m.group(1)] = m.group(2)
3286 continue
3286 continue
3287
3287
3288 if line.startswith(b'BODYFILE '):
3288 if line.startswith(b'BODYFILE '):
3289 with open(line.split(b' ', 1), 'rb') as fh:
3289 with open(line.split(b' ', 1), 'rb') as fh:
3290 body = fh.read()
3290 body = fh.read()
3291 elif line.startswith(b'frame '):
3291 elif line.startswith(b'frame '):
3292 frame = wireprotoframing.makeframefromhumanstring(
3292 frame = wireprotoframing.makeframefromhumanstring(
3293 line[len(b'frame '):])
3293 line[len(b'frame '):])
3294
3294
3295 frames.append(frame)
3295 frames.append(frame)
3296 else:
3296 else:
3297 raise error.Abort(_('unknown argument to httprequest: %s') %
3297 raise error.Abort(_('unknown argument to httprequest: %s') %
3298 line)
3298 line)
3299
3299
3300 url = path + httppath
3300 url = path + httppath
3301
3301
3302 if frames:
3302 if frames:
3303 body = b''.join(bytes(f) for f in frames)
3303 body = b''.join(bytes(f) for f in frames)
3304
3304
3305 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3305 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3306
3306
3307 # urllib.Request insists on using has_data() as a proxy for
3307 # urllib.Request insists on using has_data() as a proxy for
3308 # determining the request method. Override that to use our
3308 # determining the request method. Override that to use our
3309 # explicitly requested method.
3309 # explicitly requested method.
3310 req.get_method = lambda: pycompat.sysstr(method)
3310 req.get_method = lambda: pycompat.sysstr(method)
3311
3311
3312 try:
3312 try:
3313 res = opener.open(req)
3313 res = opener.open(req)
3314 body = res.read()
3314 body = res.read()
3315 except util.urlerr.urlerror as e:
3315 except util.urlerr.urlerror as e:
3316 # read() method must be called, but only exists in Python 2
3316 # read() method must be called, but only exists in Python 2
3317 getattr(e, 'read', lambda: None)()
3317 getattr(e, 'read', lambda: None)()
3318 continue
3318 continue
3319
3319
3320 if res.headers.get('Content-Type') == 'application/mercurial-cbor':
3320 if res.headers.get('Content-Type') == 'application/mercurial-cbor':
3321 ui.write(_('cbor> %s\n') %
3321 ui.write(_('cbor> %s\n') %
3322 stringutil.pprint(cborutil.decodeall(body)[0],
3322 stringutil.pprint(cborutil.decodeall(body)[0],
3323 bprefix=True,
3323 bprefix=True,
3324 indent=2))
3324 indent=2))
3325
3325
3326 elif action == 'close':
3326 elif action == 'close':
3327 peer.close()
3327 peer.close()
3328 elif action == 'readavailable':
3328 elif action == 'readavailable':
3329 if not stdout or not stderr:
3329 if not stdout or not stderr:
3330 raise error.Abort(_('readavailable not available on this peer'))
3330 raise error.Abort(_('readavailable not available on this peer'))
3331
3331
3332 stdin.close()
3332 stdin.close()
3333 stdout.read()
3333 stdout.read()
3334 stderr.read()
3334 stderr.read()
3335
3335
3336 elif action == 'readline':
3336 elif action == 'readline':
3337 if not stdout:
3337 if not stdout:
3338 raise error.Abort(_('readline not available on this peer'))
3338 raise error.Abort(_('readline not available on this peer'))
3339 stdout.readline()
3339 stdout.readline()
3340 elif action == 'ereadline':
3340 elif action == 'ereadline':
3341 if not stderr:
3341 if not stderr:
3342 raise error.Abort(_('ereadline not available on this peer'))
3342 raise error.Abort(_('ereadline not available on this peer'))
3343 stderr.readline()
3343 stderr.readline()
3344 elif action.startswith('read '):
3344 elif action.startswith('read '):
3345 count = int(action.split(' ', 1)[1])
3345 count = int(action.split(' ', 1)[1])
3346 if not stdout:
3346 if not stdout:
3347 raise error.Abort(_('read not available on this peer'))
3347 raise error.Abort(_('read not available on this peer'))
3348 stdout.read(count)
3348 stdout.read(count)
3349 elif action.startswith('eread '):
3349 elif action.startswith('eread '):
3350 count = int(action.split(' ', 1)[1])
3350 count = int(action.split(' ', 1)[1])
3351 if not stderr:
3351 if not stderr:
3352 raise error.Abort(_('eread not available on this peer'))
3352 raise error.Abort(_('eread not available on this peer'))
3353 stderr.read(count)
3353 stderr.read(count)
3354 else:
3354 else:
3355 raise error.Abort(_('unknown action: %s') % action)
3355 raise error.Abort(_('unknown action: %s') % action)
3356
3356
3357 if batchedcommands is not None:
3357 if batchedcommands is not None:
3358 raise error.Abort(_('unclosed "batchbegin" request'))
3358 raise error.Abort(_('unclosed "batchbegin" request'))
3359
3359
3360 if peer:
3360 if peer:
3361 peer.close()
3361 peer.close()
3362
3362
3363 if proc:
3363 if proc:
3364 proc.kill()
3364 proc.kill()
@@ -1,212 +1,293 b''
1 # wireprotov2peer.py - client side code for wire protocol version 2
1 # wireprotov2peer.py - client side code for wire protocol version 2
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import threading
11
10 from .i18n import _
12 from .i18n import _
11 from . import (
13 from . import (
12 encoding,
14 encoding,
13 error,
15 error,
14 util,
15 wireprotoframing,
16 wireprotoframing,
16 )
17 )
17 from .utils import (
18 from .utils import (
18 cborutil,
19 cborutil,
19 )
20 )
20
21
21 def formatrichmessage(atoms):
22 def formatrichmessage(atoms):
22 """Format an encoded message from the framing protocol."""
23 """Format an encoded message from the framing protocol."""
23
24
24 chunks = []
25 chunks = []
25
26
26 for atom in atoms:
27 for atom in atoms:
27 msg = _(atom[b'msg'])
28 msg = _(atom[b'msg'])
28
29
29 if b'args' in atom:
30 if b'args' in atom:
30 msg = msg % tuple(atom[b'args'])
31 msg = msg % tuple(atom[b'args'])
31
32
32 chunks.append(msg)
33 chunks.append(msg)
33
34
34 return b''.join(chunks)
35 return b''.join(chunks)
35
36
36 class commandresponse(object):
37 class commandresponse(object):
37 """Represents the response to a command request."""
38 """Represents the response to a command request.
39
40 Instances track the state of the command and hold its results.
41
42 An external entity is required to update the state of the object when
43 events occur.
44 """
38
45
39 def __init__(self, requestid, command):
46 def __init__(self, requestid, command):
40 self.requestid = requestid
47 self.requestid = requestid
41 self.command = command
48 self.command = command
42
49
43 self.b = util.bytesio()
50 # Whether all remote input related to this command has been
51 # received.
52 self._inputcomplete = False
53
54 # We have a lock that is acquired when important object state is
55 # mutated. This is to prevent race conditions between 1 thread
56 # sending us new data and another consuming it.
57 self._lock = threading.RLock()
58
59 # An event is set when state of the object changes. This event
60 # is waited on by the generator emitting objects.
61 self._serviceable = threading.Event()
62
63 self._pendingevents = []
64 self._decoder = cborutil.bufferingdecoder()
65 self._seeninitial = False
66
67 def _oninputcomplete(self):
68 with self._lock:
69 self._inputcomplete = True
70 self._serviceable.set()
71
72 def _onresponsedata(self, data):
73 available, readcount, wanted = self._decoder.decode(data)
74
75 if not available:
76 return
77
78 with self._lock:
79 for o in self._decoder.getavailable():
80 if not self._seeninitial:
81 self._handleinitial(o)
82 continue
83
84 self._pendingevents.append(o)
85
86 self._serviceable.set()
44
87
45 def cborobjects(self):
88 def _handleinitial(self, o):
46 """Obtain decoded CBOR objects from this response."""
89 self._seeninitial = True
47 self.b.seek(0)
90 if o[b'status'] == 'ok':
91 return
92
93 atoms = [{'msg': o[b'error'][b'message']}]
94 if b'args' in o[b'error']:
95 atoms[0]['args'] = o[b'error'][b'args']
96
97 raise error.RepoError(formatrichmessage(atoms))
98
99 def objects(self):
100 """Obtained decoded objects from this response.
101
102 This is a generator of data structures that were decoded from the
103 command response.
104
105 Obtaining the next member of the generator may block due to waiting
106 on external data to become available.
48
107
49 for v in cborutil.decodeall(self.b.getvalue()):
108 If the server encountered an error in the middle of serving the data
50 yield v
109 or if another error occurred, an exception may be raised when
110 advancing the generator.
111 """
112 while True:
113 # TODO this can infinite loop if self._inputcomplete is never
114 # set. We likely want to tie the lifetime of this object/state
115 # to that of the background thread receiving frames and updating
116 # our state.
117 self._serviceable.wait(1.0)
118
119 with self._lock:
120 self._serviceable.clear()
121
122 # Make copies because objects could be mutated during
123 # iteration.
124 stop = self._inputcomplete
125 pending = list(self._pendingevents)
126 self._pendingevents[:] = []
127
128 for o in pending:
129 yield o
130
131 if stop:
132 break
51
133
52 class clienthandler(object):
134 class clienthandler(object):
53 """Object to handle higher-level client activities.
135 """Object to handle higher-level client activities.
54
136
55 The ``clientreactor`` is used to hold low-level state about the frame-based
137 The ``clientreactor`` is used to hold low-level state about the frame-based
56 protocol, such as which requests and streams are active. This type is used
138 protocol, such as which requests and streams are active. This type is used
57 for higher-level operations, such as reading frames from a socket, exposing
139 for higher-level operations, such as reading frames from a socket, exposing
58 and managing a higher-level primitive for representing command responses,
140 and managing a higher-level primitive for representing command responses,
59 etc. This class is what peers should probably use to bridge wire activity
141 etc. This class is what peers should probably use to bridge wire activity
60 with the higher-level peer API.
142 with the higher-level peer API.
61 """
143 """
62
144
63 def __init__(self, ui, clientreactor):
145 def __init__(self, ui, clientreactor):
64 self._ui = ui
146 self._ui = ui
65 self._reactor = clientreactor
147 self._reactor = clientreactor
66 self._requests = {}
148 self._requests = {}
67 self._futures = {}
149 self._futures = {}
68 self._responses = {}
150 self._responses = {}
69
151
70 def callcommand(self, command, args, f):
152 def callcommand(self, command, args, f):
71 """Register a request to call a command.
153 """Register a request to call a command.
72
154
73 Returns an iterable of frames that should be sent over the wire.
155 Returns an iterable of frames that should be sent over the wire.
74 """
156 """
75 request, action, meta = self._reactor.callcommand(command, args)
157 request, action, meta = self._reactor.callcommand(command, args)
76
158
77 if action != 'noop':
159 if action != 'noop':
78 raise error.ProgrammingError('%s not yet supported' % action)
160 raise error.ProgrammingError('%s not yet supported' % action)
79
161
80 rid = request.requestid
162 rid = request.requestid
81 self._requests[rid] = request
163 self._requests[rid] = request
82 self._futures[rid] = f
164 self._futures[rid] = f
165 # TODO we need some kind of lifetime on response instances otherwise
166 # objects() may deadlock.
83 self._responses[rid] = commandresponse(rid, command)
167 self._responses[rid] = commandresponse(rid, command)
84
168
85 return iter(())
169 return iter(())
86
170
87 def flushcommands(self):
171 def flushcommands(self):
88 """Flush all queued commands.
172 """Flush all queued commands.
89
173
90 Returns an iterable of frames that should be sent over the wire.
174 Returns an iterable of frames that should be sent over the wire.
91 """
175 """
92 action, meta = self._reactor.flushcommands()
176 action, meta = self._reactor.flushcommands()
93
177
94 if action != 'sendframes':
178 if action != 'sendframes':
95 raise error.ProgrammingError('%s not yet supported' % action)
179 raise error.ProgrammingError('%s not yet supported' % action)
96
180
97 return meta['framegen']
181 return meta['framegen']
98
182
99 def readframe(self, fh):
183 def readframe(self, fh):
100 """Attempt to read and process a frame.
184 """Attempt to read and process a frame.
101
185
102 Returns None if no frame was read. Presumably this means EOF.
186 Returns None if no frame was read. Presumably this means EOF.
103 """
187 """
104 frame = wireprotoframing.readframe(fh)
188 frame = wireprotoframing.readframe(fh)
105 if frame is None:
189 if frame is None:
106 # TODO tell reactor?
190 # TODO tell reactor?
107 return
191 return
108
192
109 self._ui.note(_('received %r\n') % frame)
193 self._ui.note(_('received %r\n') % frame)
110 self._processframe(frame)
194 self._processframe(frame)
111
195
112 return True
196 return True
113
197
114 def _processframe(self, frame):
198 def _processframe(self, frame):
115 """Process a single read frame."""
199 """Process a single read frame."""
116
200
117 action, meta = self._reactor.onframerecv(frame)
201 action, meta = self._reactor.onframerecv(frame)
118
202
119 if action == 'error':
203 if action == 'error':
120 e = error.RepoError(meta['message'])
204 e = error.RepoError(meta['message'])
121
205
206 if frame.requestid in self._responses:
207 self._responses[frame.requestid]._oninputcomplete()
208
122 if frame.requestid in self._futures:
209 if frame.requestid in self._futures:
123 self._futures[frame.requestid].set_exception(e)
210 self._futures[frame.requestid].set_exception(e)
211 del self._futures[frame.requestid]
124 else:
212 else:
125 raise e
213 raise e
126
214
127 return
215 return
128
216
129 if frame.requestid not in self._requests:
217 if frame.requestid not in self._requests:
130 raise error.ProgrammingError(
218 raise error.ProgrammingError(
131 'received frame for unknown request; this is either a bug in '
219 'received frame for unknown request; this is either a bug in '
132 'the clientreactor not screening for this or this instance was '
220 'the clientreactor not screening for this or this instance was '
133 'never told about this request: %r' % frame)
221 'never told about this request: %r' % frame)
134
222
135 response = self._responses[frame.requestid]
223 response = self._responses[frame.requestid]
136
224
137 if action == 'responsedata':
225 if action == 'responsedata':
138 # Any failures processing this frame should bubble up to the
226 # Any failures processing this frame should bubble up to the
139 # future tracking the request.
227 # future tracking the request.
140 try:
228 try:
141 self._processresponsedata(frame, meta, response)
229 self._processresponsedata(frame, meta, response)
142 except BaseException as e:
230 except BaseException as e:
143 self._futures[frame.requestid].set_exception(e)
231 self._futures[frame.requestid].set_exception(e)
232 del self._futures[frame.requestid]
233 response._oninputcomplete()
144 else:
234 else:
145 raise error.ProgrammingError(
235 raise error.ProgrammingError(
146 'unhandled action from clientreactor: %s' % action)
236 'unhandled action from clientreactor: %s' % action)
147
237
148 def _processresponsedata(self, frame, meta, response):
238 def _processresponsedata(self, frame, meta, response):
149 # This buffers all data until end of stream is received. This
239 # This can raise. The caller can handle it.
150 # is bad for performance.
240 response._onresponsedata(meta['data'])
151 # TODO make response data streamable
152 response.b.write(meta['data'])
153
241
154 if meta['eos']:
242 if meta['eos']:
155 # If the command has a decoder, resolve the future to the
243 response._oninputcomplete()
156 # decoded value. Otherwise resolve to the rich response object.
244 del self._requests[frame.requestid]
157 decoder = COMMAND_DECODERS.get(response.command)
158
159 # TODO consider always resolving the overall status map.
160 if decoder:
161 objs = response.cborobjects()
162
163 overall = next(objs)
164
245
165 if overall['status'] == 'ok':
246 # If the command has a decoder, we wait until all input has been
166 self._futures[frame.requestid].set_result(decoder(objs))
247 # received before resolving the future. Otherwise we resolve the
167 else:
248 # future immediately.
168 atoms = [{'msg': overall['error']['message']}]
249 if frame.requestid not in self._futures:
169 if 'args' in overall['error']:
250 return
170 atoms[0]['args'] = overall['error']['args']
171 e = error.RepoError(formatrichmessage(atoms))
172 self._futures[frame.requestid].set_exception(e)
173 else:
174 self._futures[frame.requestid].set_result(response)
175
251
176 del self._requests[frame.requestid]
252 if response.command not in COMMAND_DECODERS:
253 self._futures[frame.requestid].set_result(response.objects())
254 del self._futures[frame.requestid]
255 elif response._inputcomplete:
256 decoded = COMMAND_DECODERS[response.command](response.objects())
257 self._futures[frame.requestid].set_result(decoded)
177 del self._futures[frame.requestid]
258 del self._futures[frame.requestid]
178
259
179 def decodebranchmap(objs):
260 def decodebranchmap(objs):
180 # Response should be a single CBOR map of branch name to array of nodes.
261 # Response should be a single CBOR map of branch name to array of nodes.
181 bm = next(objs)
262 bm = next(objs)
182
263
183 return {encoding.tolocal(k): v for k, v in bm.items()}
264 return {encoding.tolocal(k): v for k, v in bm.items()}
184
265
185 def decodeheads(objs):
266 def decodeheads(objs):
186 # Array of node bytestrings.
267 # Array of node bytestrings.
187 return next(objs)
268 return next(objs)
188
269
189 def decodeknown(objs):
270 def decodeknown(objs):
190 # Bytestring where each byte is a 0 or 1.
271 # Bytestring where each byte is a 0 or 1.
191 raw = next(objs)
272 raw = next(objs)
192
273
193 return [True if c == '1' else False for c in raw]
274 return [True if c == '1' else False for c in raw]
194
275
195 def decodelistkeys(objs):
276 def decodelistkeys(objs):
196 # Map with bytestring keys and values.
277 # Map with bytestring keys and values.
197 return next(objs)
278 return next(objs)
198
279
199 def decodelookup(objs):
280 def decodelookup(objs):
200 return next(objs)
281 return next(objs)
201
282
202 def decodepushkey(objs):
283 def decodepushkey(objs):
203 return next(objs)
284 return next(objs)
204
285
205 COMMAND_DECODERS = {
286 COMMAND_DECODERS = {
206 'branchmap': decodebranchmap,
287 'branchmap': decodebranchmap,
207 'heads': decodeheads,
288 'heads': decodeheads,
208 'known': decodeknown,
289 'known': decodeknown,
209 'listkeys': decodelistkeys,
290 'listkeys': decodelistkeys,
210 'lookup': decodelookup,
291 'lookup': decodelookup,
211 'pushkey': decodepushkey,
292 'pushkey': decodepushkey,
212 }
293 }
@@ -1,622 +1,619 b''
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4 $ enabledummycommands
4 $ enabledummycommands
5
5
6 $ hg init server
6 $ hg init server
7 $ cat > server/.hg/hgrc << EOF
7 $ cat > server/.hg/hgrc << EOF
8 > [experimental]
8 > [experimental]
9 > web.apiserver = true
9 > web.apiserver = true
10 > EOF
10 > EOF
11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
12 $ cat hg.pid > $DAEMON_PIDS
12 $ cat hg.pid > $DAEMON_PIDS
13
13
14 HTTP v2 protocol not enabled by default
14 HTTP v2 protocol not enabled by default
15
15
16 $ sendhttpraw << EOF
16 $ sendhttpraw << EOF
17 > httprequest GET api/$HTTPV2
17 > httprequest GET api/$HTTPV2
18 > user-agent: test
18 > user-agent: test
19 > EOF
19 > EOF
20 using raw connection to peer
20 using raw connection to peer
21 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
21 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
22 s> Accept-Encoding: identity\r\n
22 s> Accept-Encoding: identity\r\n
23 s> user-agent: test\r\n
23 s> user-agent: test\r\n
24 s> host: $LOCALIP:$HGPORT\r\n (glob)
24 s> host: $LOCALIP:$HGPORT\r\n (glob)
25 s> \r\n
25 s> \r\n
26 s> makefile('rb', None)
26 s> makefile('rb', None)
27 s> HTTP/1.1 404 Not Found\r\n
27 s> HTTP/1.1 404 Not Found\r\n
28 s> Server: testing stub value\r\n
28 s> Server: testing stub value\r\n
29 s> Date: $HTTP_DATE$\r\n
29 s> Date: $HTTP_DATE$\r\n
30 s> Content-Type: text/plain\r\n
30 s> Content-Type: text/plain\r\n
31 s> Content-Length: 33\r\n
31 s> Content-Length: 33\r\n
32 s> \r\n
32 s> \r\n
33 s> API exp-http-v2-0001 not enabled\n
33 s> API exp-http-v2-0001 not enabled\n
34
34
35 Restart server with support for HTTP v2 API
35 Restart server with support for HTTP v2 API
36
36
37 $ killdaemons.py
37 $ killdaemons.py
38 $ enablehttpv2 server
38 $ enablehttpv2 server
39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
40 $ cat hg.pid > $DAEMON_PIDS
40 $ cat hg.pid > $DAEMON_PIDS
41
41
42 Request to unknown command yields 404
42 Request to unknown command yields 404
43
43
44 $ sendhttpraw << EOF
44 $ sendhttpraw << EOF
45 > httprequest POST api/$HTTPV2/ro/badcommand
45 > httprequest POST api/$HTTPV2/ro/badcommand
46 > user-agent: test
46 > user-agent: test
47 > EOF
47 > EOF
48 using raw connection to peer
48 using raw connection to peer
49 s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
49 s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
50 s> Accept-Encoding: identity\r\n
50 s> Accept-Encoding: identity\r\n
51 s> user-agent: test\r\n
51 s> user-agent: test\r\n
52 s> host: $LOCALIP:$HGPORT\r\n (glob)
52 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 s> \r\n
53 s> \r\n
54 s> makefile('rb', None)
54 s> makefile('rb', None)
55 s> HTTP/1.1 404 Not Found\r\n
55 s> HTTP/1.1 404 Not Found\r\n
56 s> Server: testing stub value\r\n
56 s> Server: testing stub value\r\n
57 s> Date: $HTTP_DATE$\r\n
57 s> Date: $HTTP_DATE$\r\n
58 s> Content-Type: text/plain\r\n
58 s> Content-Type: text/plain\r\n
59 s> Content-Length: 42\r\n
59 s> Content-Length: 42\r\n
60 s> \r\n
60 s> \r\n
61 s> unknown wire protocol command: badcommand\n
61 s> unknown wire protocol command: badcommand\n
62
62
63 GET to read-only command yields a 405
63 GET to read-only command yields a 405
64
64
65 $ sendhttpraw << EOF
65 $ sendhttpraw << EOF
66 > httprequest GET api/$HTTPV2/ro/customreadonly
66 > httprequest GET api/$HTTPV2/ro/customreadonly
67 > user-agent: test
67 > user-agent: test
68 > EOF
68 > EOF
69 using raw connection to peer
69 using raw connection to peer
70 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
70 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
71 s> Accept-Encoding: identity\r\n
71 s> Accept-Encoding: identity\r\n
72 s> user-agent: test\r\n
72 s> user-agent: test\r\n
73 s> host: $LOCALIP:$HGPORT\r\n (glob)
73 s> host: $LOCALIP:$HGPORT\r\n (glob)
74 s> \r\n
74 s> \r\n
75 s> makefile('rb', None)
75 s> makefile('rb', None)
76 s> HTTP/1.1 405 Method Not Allowed\r\n
76 s> HTTP/1.1 405 Method Not Allowed\r\n
77 s> Server: testing stub value\r\n
77 s> Server: testing stub value\r\n
78 s> Date: $HTTP_DATE$\r\n
78 s> Date: $HTTP_DATE$\r\n
79 s> Allow: POST\r\n
79 s> Allow: POST\r\n
80 s> Content-Length: 30\r\n
80 s> Content-Length: 30\r\n
81 s> \r\n
81 s> \r\n
82 s> commands require POST requests
82 s> commands require POST requests
83
83
84 Missing Accept header results in 406
84 Missing Accept header results in 406
85
85
86 $ sendhttpraw << EOF
86 $ sendhttpraw << EOF
87 > httprequest POST api/$HTTPV2/ro/customreadonly
87 > httprequest POST api/$HTTPV2/ro/customreadonly
88 > user-agent: test
88 > user-agent: test
89 > EOF
89 > EOF
90 using raw connection to peer
90 using raw connection to peer
91 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
91 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
92 s> Accept-Encoding: identity\r\n
92 s> Accept-Encoding: identity\r\n
93 s> user-agent: test\r\n
93 s> user-agent: test\r\n
94 s> host: $LOCALIP:$HGPORT\r\n (glob)
94 s> host: $LOCALIP:$HGPORT\r\n (glob)
95 s> \r\n
95 s> \r\n
96 s> makefile('rb', None)
96 s> makefile('rb', None)
97 s> HTTP/1.1 406 Not Acceptable\r\n
97 s> HTTP/1.1 406 Not Acceptable\r\n
98 s> Server: testing stub value\r\n
98 s> Server: testing stub value\r\n
99 s> Date: $HTTP_DATE$\r\n
99 s> Date: $HTTP_DATE$\r\n
100 s> Content-Type: text/plain\r\n
100 s> Content-Type: text/plain\r\n
101 s> Content-Length: 85\r\n
101 s> Content-Length: 85\r\n
102 s> \r\n
102 s> \r\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
104
104
105 Bad Accept header results in 406
105 Bad Accept header results in 406
106
106
107 $ sendhttpraw << EOF
107 $ sendhttpraw << EOF
108 > httprequest POST api/$HTTPV2/ro/customreadonly
108 > httprequest POST api/$HTTPV2/ro/customreadonly
109 > accept: invalid
109 > accept: invalid
110 > user-agent: test
110 > user-agent: test
111 > EOF
111 > EOF
112 using raw connection to peer
112 using raw connection to peer
113 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
113 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
114 s> Accept-Encoding: identity\r\n
114 s> Accept-Encoding: identity\r\n
115 s> accept: invalid\r\n
115 s> accept: invalid\r\n
116 s> user-agent: test\r\n
116 s> user-agent: test\r\n
117 s> host: $LOCALIP:$HGPORT\r\n (glob)
117 s> host: $LOCALIP:$HGPORT\r\n (glob)
118 s> \r\n
118 s> \r\n
119 s> makefile('rb', None)
119 s> makefile('rb', None)
120 s> HTTP/1.1 406 Not Acceptable\r\n
120 s> HTTP/1.1 406 Not Acceptable\r\n
121 s> Server: testing stub value\r\n
121 s> Server: testing stub value\r\n
122 s> Date: $HTTP_DATE$\r\n
122 s> Date: $HTTP_DATE$\r\n
123 s> Content-Type: text/plain\r\n
123 s> Content-Type: text/plain\r\n
124 s> Content-Length: 85\r\n
124 s> Content-Length: 85\r\n
125 s> \r\n
125 s> \r\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
127
127
128 Bad Content-Type header results in 415
128 Bad Content-Type header results in 415
129
129
130 $ sendhttpraw << EOF
130 $ sendhttpraw << EOF
131 > httprequest POST api/$HTTPV2/ro/customreadonly
131 > httprequest POST api/$HTTPV2/ro/customreadonly
132 > accept: $MEDIATYPE
132 > accept: $MEDIATYPE
133 > user-agent: test
133 > user-agent: test
134 > content-type: badmedia
134 > content-type: badmedia
135 > EOF
135 > EOF
136 using raw connection to peer
136 using raw connection to peer
137 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
137 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
138 s> Accept-Encoding: identity\r\n
138 s> Accept-Encoding: identity\r\n
139 s> accept: application/mercurial-exp-framing-0005\r\n
139 s> accept: application/mercurial-exp-framing-0005\r\n
140 s> content-type: badmedia\r\n
140 s> content-type: badmedia\r\n
141 s> user-agent: test\r\n
141 s> user-agent: test\r\n
142 s> host: $LOCALIP:$HGPORT\r\n (glob)
142 s> host: $LOCALIP:$HGPORT\r\n (glob)
143 s> \r\n
143 s> \r\n
144 s> makefile('rb', None)
144 s> makefile('rb', None)
145 s> HTTP/1.1 415 Unsupported Media Type\r\n
145 s> HTTP/1.1 415 Unsupported Media Type\r\n
146 s> Server: testing stub value\r\n
146 s> Server: testing stub value\r\n
147 s> Date: $HTTP_DATE$\r\n
147 s> Date: $HTTP_DATE$\r\n
148 s> Content-Type: text/plain\r\n
148 s> Content-Type: text/plain\r\n
149 s> Content-Length: 88\r\n
149 s> Content-Length: 88\r\n
150 s> \r\n
150 s> \r\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0005\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0005\n
152
152
153 Request to read-only command works out of the box
153 Request to read-only command works out of the box
154
154
155 $ sendhttpraw << EOF
155 $ sendhttpraw << EOF
156 > httprequest POST api/$HTTPV2/ro/customreadonly
156 > httprequest POST api/$HTTPV2/ro/customreadonly
157 > accept: $MEDIATYPE
157 > accept: $MEDIATYPE
158 > content-type: $MEDIATYPE
158 > content-type: $MEDIATYPE
159 > user-agent: test
159 > user-agent: test
160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
161 > EOF
161 > EOF
162 using raw connection to peer
162 using raw connection to peer
163 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
163 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
164 s> Accept-Encoding: identity\r\n
164 s> Accept-Encoding: identity\r\n
165 s> *\r\n (glob)
165 s> *\r\n (glob)
166 s> content-type: application/mercurial-exp-framing-0005\r\n
166 s> content-type: application/mercurial-exp-framing-0005\r\n
167 s> user-agent: test\r\n
167 s> user-agent: test\r\n
168 s> content-length: 29\r\n
168 s> content-length: 29\r\n
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 s> \r\n
170 s> \r\n
171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
172 s> makefile('rb', None)
172 s> makefile('rb', None)
173 s> HTTP/1.1 200 OK\r\n
173 s> HTTP/1.1 200 OK\r\n
174 s> Server: testing stub value\r\n
174 s> Server: testing stub value\r\n
175 s> Date: $HTTP_DATE$\r\n
175 s> Date: $HTTP_DATE$\r\n
176 s> Content-Type: application/mercurial-exp-framing-0005\r\n
176 s> Content-Type: application/mercurial-exp-framing-0005\r\n
177 s> Transfer-Encoding: chunked\r\n
177 s> Transfer-Encoding: chunked\r\n
178 s> \r\n
178 s> \r\n
179 s> 13\r\n
179 s> 13\r\n
180 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
180 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
181 s> \r\n
181 s> \r\n
182 s> 27\r\n
182 s> 27\r\n
183 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
183 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
184 s> \r\n
184 s> \r\n
185 s> 8\r\n
185 s> 8\r\n
186 s> \x00\x00\x00\x01\x00\x02\x002
186 s> \x00\x00\x00\x01\x00\x02\x002
187 s> \r\n
187 s> \r\n
188 s> 0\r\n
188 s> 0\r\n
189 s> \r\n
189 s> \r\n
190
190
191 $ sendhttpv2peer << EOF
191 $ sendhttpv2peer << EOF
192 > command customreadonly
192 > command customreadonly
193 > EOF
193 > EOF
194 creating http peer for wire protocol version 2
194 creating http peer for wire protocol version 2
195 sending customreadonly command
195 sending customreadonly command
196 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
196 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
197 s> Accept-Encoding: identity\r\n
197 s> Accept-Encoding: identity\r\n
198 s> accept: application/mercurial-exp-framing-0005\r\n
198 s> accept: application/mercurial-exp-framing-0005\r\n
199 s> content-type: application/mercurial-exp-framing-0005\r\n
199 s> content-type: application/mercurial-exp-framing-0005\r\n
200 s> content-length: 29\r\n
200 s> content-length: 29\r\n
201 s> host: $LOCALIP:$HGPORT\r\n (glob)
201 s> host: $LOCALIP:$HGPORT\r\n (glob)
202 s> user-agent: Mercurial debugwireproto\r\n
202 s> user-agent: Mercurial debugwireproto\r\n
203 s> \r\n
203 s> \r\n
204 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
204 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
205 s> makefile('rb', None)
205 s> makefile('rb', None)
206 s> HTTP/1.1 200 OK\r\n
206 s> HTTP/1.1 200 OK\r\n
207 s> Server: testing stub value\r\n
207 s> Server: testing stub value\r\n
208 s> Date: $HTTP_DATE$\r\n
208 s> Date: $HTTP_DATE$\r\n
209 s> Content-Type: application/mercurial-exp-framing-0005\r\n
209 s> Content-Type: application/mercurial-exp-framing-0005\r\n
210 s> Transfer-Encoding: chunked\r\n
210 s> Transfer-Encoding: chunked\r\n
211 s> \r\n
211 s> \r\n
212 s> 13\r\n
212 s> 13\r\n
213 s> \x0b\x00\x00\x01\x00\x02\x011
213 s> \x0b\x00\x00\x01\x00\x02\x011
214 s> \xa1FstatusBok
214 s> \xa1FstatusBok
215 s> \r\n
215 s> \r\n
216 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
216 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
217 s> 27\r\n
217 s> 27\r\n
218 s> \x1f\x00\x00\x01\x00\x02\x001
218 s> \x1f\x00\x00\x01\x00\x02\x001
219 s> X\x1dcustomreadonly bytes response
219 s> X\x1dcustomreadonly bytes response
220 s> \r\n
220 s> \r\n
221 received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
221 received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
222 s> 8\r\n
222 s> 8\r\n
223 s> \x00\x00\x00\x01\x00\x02\x002
223 s> \x00\x00\x00\x01\x00\x02\x002
224 s> \r\n
224 s> \r\n
225 s> 0\r\n
225 s> 0\r\n
226 s> \r\n
226 s> \r\n
227 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
227 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
228 response: [
228 response: gen[
229 {
230 b'status': b'ok'
231 },
232 b'customreadonly bytes response'
229 b'customreadonly bytes response'
233 ]
230 ]
234
231
235 Request to read-write command fails because server is read-only by default
232 Request to read-write command fails because server is read-only by default
236
233
237 GET to read-write request yields 405
234 GET to read-write request yields 405
238
235
239 $ sendhttpraw << EOF
236 $ sendhttpraw << EOF
240 > httprequest GET api/$HTTPV2/rw/customreadonly
237 > httprequest GET api/$HTTPV2/rw/customreadonly
241 > user-agent: test
238 > user-agent: test
242 > EOF
239 > EOF
243 using raw connection to peer
240 using raw connection to peer
244 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
241 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
245 s> Accept-Encoding: identity\r\n
242 s> Accept-Encoding: identity\r\n
246 s> user-agent: test\r\n
243 s> user-agent: test\r\n
247 s> host: $LOCALIP:$HGPORT\r\n (glob)
244 s> host: $LOCALIP:$HGPORT\r\n (glob)
248 s> \r\n
245 s> \r\n
249 s> makefile('rb', None)
246 s> makefile('rb', None)
250 s> HTTP/1.1 405 Method Not Allowed\r\n
247 s> HTTP/1.1 405 Method Not Allowed\r\n
251 s> Server: testing stub value\r\n
248 s> Server: testing stub value\r\n
252 s> Date: $HTTP_DATE$\r\n
249 s> Date: $HTTP_DATE$\r\n
253 s> Allow: POST\r\n
250 s> Allow: POST\r\n
254 s> Content-Length: 30\r\n
251 s> Content-Length: 30\r\n
255 s> \r\n
252 s> \r\n
256 s> commands require POST requests
253 s> commands require POST requests
257
254
258 Even for unknown commands
255 Even for unknown commands
259
256
260 $ sendhttpraw << EOF
257 $ sendhttpraw << EOF
261 > httprequest GET api/$HTTPV2/rw/badcommand
258 > httprequest GET api/$HTTPV2/rw/badcommand
262 > user-agent: test
259 > user-agent: test
263 > EOF
260 > EOF
264 using raw connection to peer
261 using raw connection to peer
265 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
262 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
266 s> Accept-Encoding: identity\r\n
263 s> Accept-Encoding: identity\r\n
267 s> user-agent: test\r\n
264 s> user-agent: test\r\n
268 s> host: $LOCALIP:$HGPORT\r\n (glob)
265 s> host: $LOCALIP:$HGPORT\r\n (glob)
269 s> \r\n
266 s> \r\n
270 s> makefile('rb', None)
267 s> makefile('rb', None)
271 s> HTTP/1.1 405 Method Not Allowed\r\n
268 s> HTTP/1.1 405 Method Not Allowed\r\n
272 s> Server: testing stub value\r\n
269 s> Server: testing stub value\r\n
273 s> Date: $HTTP_DATE$\r\n
270 s> Date: $HTTP_DATE$\r\n
274 s> Allow: POST\r\n
271 s> Allow: POST\r\n
275 s> Content-Length: 30\r\n
272 s> Content-Length: 30\r\n
276 s> \r\n
273 s> \r\n
277 s> commands require POST requests
274 s> commands require POST requests
278
275
279 SSL required by default
276 SSL required by default
280
277
281 $ sendhttpraw << EOF
278 $ sendhttpraw << EOF
282 > httprequest POST api/$HTTPV2/rw/customreadonly
279 > httprequest POST api/$HTTPV2/rw/customreadonly
283 > user-agent: test
280 > user-agent: test
284 > EOF
281 > EOF
285 using raw connection to peer
282 using raw connection to peer
286 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
283 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
287 s> Accept-Encoding: identity\r\n
284 s> Accept-Encoding: identity\r\n
288 s> user-agent: test\r\n
285 s> user-agent: test\r\n
289 s> host: $LOCALIP:$HGPORT\r\n (glob)
286 s> host: $LOCALIP:$HGPORT\r\n (glob)
290 s> \r\n
287 s> \r\n
291 s> makefile('rb', None)
288 s> makefile('rb', None)
292 s> HTTP/1.1 403 ssl required\r\n
289 s> HTTP/1.1 403 ssl required\r\n
293 s> Server: testing stub value\r\n
290 s> Server: testing stub value\r\n
294 s> Date: $HTTP_DATE$\r\n
291 s> Date: $HTTP_DATE$\r\n
295 s> Content-Length: 17\r\n
292 s> Content-Length: 17\r\n
296 s> \r\n
293 s> \r\n
297 s> permission denied
294 s> permission denied
298
295
299 Restart server to allow non-ssl read-write operations
296 Restart server to allow non-ssl read-write operations
300
297
301 $ killdaemons.py
298 $ killdaemons.py
302 $ cat > server/.hg/hgrc << EOF
299 $ cat > server/.hg/hgrc << EOF
303 > [experimental]
300 > [experimental]
304 > web.apiserver = true
301 > web.apiserver = true
305 > web.api.http-v2 = true
302 > web.api.http-v2 = true
306 > [web]
303 > [web]
307 > push_ssl = false
304 > push_ssl = false
308 > allow-push = *
305 > allow-push = *
309 > EOF
306 > EOF
310
307
311 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
308 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
312 $ cat hg.pid > $DAEMON_PIDS
309 $ cat hg.pid > $DAEMON_PIDS
313
310
314 Authorized request for valid read-write command works
311 Authorized request for valid read-write command works
315
312
316 $ sendhttpraw << EOF
313 $ sendhttpraw << EOF
317 > httprequest POST api/$HTTPV2/rw/customreadonly
314 > httprequest POST api/$HTTPV2/rw/customreadonly
318 > user-agent: test
315 > user-agent: test
319 > accept: $MEDIATYPE
316 > accept: $MEDIATYPE
320 > content-type: $MEDIATYPE
317 > content-type: $MEDIATYPE
321 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
318 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
322 > EOF
319 > EOF
323 using raw connection to peer
320 using raw connection to peer
324 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
321 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
325 s> Accept-Encoding: identity\r\n
322 s> Accept-Encoding: identity\r\n
326 s> accept: application/mercurial-exp-framing-0005\r\n
323 s> accept: application/mercurial-exp-framing-0005\r\n
327 s> content-type: application/mercurial-exp-framing-0005\r\n
324 s> content-type: application/mercurial-exp-framing-0005\r\n
328 s> user-agent: test\r\n
325 s> user-agent: test\r\n
329 s> content-length: 29\r\n
326 s> content-length: 29\r\n
330 s> host: $LOCALIP:$HGPORT\r\n (glob)
327 s> host: $LOCALIP:$HGPORT\r\n (glob)
331 s> \r\n
328 s> \r\n
332 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
329 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
333 s> makefile('rb', None)
330 s> makefile('rb', None)
334 s> HTTP/1.1 200 OK\r\n
331 s> HTTP/1.1 200 OK\r\n
335 s> Server: testing stub value\r\n
332 s> Server: testing stub value\r\n
336 s> Date: $HTTP_DATE$\r\n
333 s> Date: $HTTP_DATE$\r\n
337 s> Content-Type: application/mercurial-exp-framing-0005\r\n
334 s> Content-Type: application/mercurial-exp-framing-0005\r\n
338 s> Transfer-Encoding: chunked\r\n
335 s> Transfer-Encoding: chunked\r\n
339 s> \r\n
336 s> \r\n
340 s> 13\r\n
337 s> 13\r\n
341 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
338 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
342 s> \r\n
339 s> \r\n
343 s> 27\r\n
340 s> 27\r\n
344 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
341 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
345 s> \r\n
342 s> \r\n
346 s> 8\r\n
343 s> 8\r\n
347 s> \x00\x00\x00\x01\x00\x02\x002
344 s> \x00\x00\x00\x01\x00\x02\x002
348 s> \r\n
345 s> \r\n
349 s> 0\r\n
346 s> 0\r\n
350 s> \r\n
347 s> \r\n
351
348
352 Authorized request for unknown command is rejected
349 Authorized request for unknown command is rejected
353
350
354 $ sendhttpraw << EOF
351 $ sendhttpraw << EOF
355 > httprequest POST api/$HTTPV2/rw/badcommand
352 > httprequest POST api/$HTTPV2/rw/badcommand
356 > user-agent: test
353 > user-agent: test
357 > accept: $MEDIATYPE
354 > accept: $MEDIATYPE
358 > EOF
355 > EOF
359 using raw connection to peer
356 using raw connection to peer
360 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
357 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
361 s> Accept-Encoding: identity\r\n
358 s> Accept-Encoding: identity\r\n
362 s> accept: application/mercurial-exp-framing-0005\r\n
359 s> accept: application/mercurial-exp-framing-0005\r\n
363 s> user-agent: test\r\n
360 s> user-agent: test\r\n
364 s> host: $LOCALIP:$HGPORT\r\n (glob)
361 s> host: $LOCALIP:$HGPORT\r\n (glob)
365 s> \r\n
362 s> \r\n
366 s> makefile('rb', None)
363 s> makefile('rb', None)
367 s> HTTP/1.1 404 Not Found\r\n
364 s> HTTP/1.1 404 Not Found\r\n
368 s> Server: testing stub value\r\n
365 s> Server: testing stub value\r\n
369 s> Date: $HTTP_DATE$\r\n
366 s> Date: $HTTP_DATE$\r\n
370 s> Content-Type: text/plain\r\n
367 s> Content-Type: text/plain\r\n
371 s> Content-Length: 42\r\n
368 s> Content-Length: 42\r\n
372 s> \r\n
369 s> \r\n
373 s> unknown wire protocol command: badcommand\n
370 s> unknown wire protocol command: badcommand\n
374
371
375 debugreflect isn't enabled by default
372 debugreflect isn't enabled by default
376
373
377 $ sendhttpraw << EOF
374 $ sendhttpraw << EOF
378 > httprequest POST api/$HTTPV2/ro/debugreflect
375 > httprequest POST api/$HTTPV2/ro/debugreflect
379 > user-agent: test
376 > user-agent: test
380 > EOF
377 > EOF
381 using raw connection to peer
378 using raw connection to peer
382 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
379 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
383 s> Accept-Encoding: identity\r\n
380 s> Accept-Encoding: identity\r\n
384 s> user-agent: test\r\n
381 s> user-agent: test\r\n
385 s> host: $LOCALIP:$HGPORT\r\n (glob)
382 s> host: $LOCALIP:$HGPORT\r\n (glob)
386 s> \r\n
383 s> \r\n
387 s> makefile('rb', None)
384 s> makefile('rb', None)
388 s> HTTP/1.1 404 Not Found\r\n
385 s> HTTP/1.1 404 Not Found\r\n
389 s> Server: testing stub value\r\n
386 s> Server: testing stub value\r\n
390 s> Date: $HTTP_DATE$\r\n
387 s> Date: $HTTP_DATE$\r\n
391 s> Content-Type: text/plain\r\n
388 s> Content-Type: text/plain\r\n
392 s> Content-Length: 34\r\n
389 s> Content-Length: 34\r\n
393 s> \r\n
390 s> \r\n
394 s> debugreflect service not available
391 s> debugreflect service not available
395
392
396 Restart server to get debugreflect endpoint
393 Restart server to get debugreflect endpoint
397
394
398 $ killdaemons.py
395 $ killdaemons.py
399 $ cat > server/.hg/hgrc << EOF
396 $ cat > server/.hg/hgrc << EOF
400 > [experimental]
397 > [experimental]
401 > web.apiserver = true
398 > web.apiserver = true
402 > web.api.debugreflect = true
399 > web.api.debugreflect = true
403 > web.api.http-v2 = true
400 > web.api.http-v2 = true
404 > [web]
401 > [web]
405 > push_ssl = false
402 > push_ssl = false
406 > allow-push = *
403 > allow-push = *
407 > EOF
404 > EOF
408
405
409 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
406 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
410 $ cat hg.pid > $DAEMON_PIDS
407 $ cat hg.pid > $DAEMON_PIDS
411
408
412 Command frames can be reflected via debugreflect
409 Command frames can be reflected via debugreflect
413
410
414 $ sendhttpraw << EOF
411 $ sendhttpraw << EOF
415 > httprequest POST api/$HTTPV2/ro/debugreflect
412 > httprequest POST api/$HTTPV2/ro/debugreflect
416 > accept: $MEDIATYPE
413 > accept: $MEDIATYPE
417 > content-type: $MEDIATYPE
414 > content-type: $MEDIATYPE
418 > user-agent: test
415 > user-agent: test
419 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
416 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
420 > EOF
417 > EOF
421 using raw connection to peer
418 using raw connection to peer
422 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
419 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
423 s> Accept-Encoding: identity\r\n
420 s> Accept-Encoding: identity\r\n
424 s> accept: application/mercurial-exp-framing-0005\r\n
421 s> accept: application/mercurial-exp-framing-0005\r\n
425 s> content-type: application/mercurial-exp-framing-0005\r\n
422 s> content-type: application/mercurial-exp-framing-0005\r\n
426 s> user-agent: test\r\n
423 s> user-agent: test\r\n
427 s> content-length: 47\r\n
424 s> content-length: 47\r\n
428 s> host: $LOCALIP:$HGPORT\r\n (glob)
425 s> host: $LOCALIP:$HGPORT\r\n (glob)
429 s> \r\n
426 s> \r\n
430 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
427 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
431 s> makefile('rb', None)
428 s> makefile('rb', None)
432 s> HTTP/1.1 200 OK\r\n
429 s> HTTP/1.1 200 OK\r\n
433 s> Server: testing stub value\r\n
430 s> Server: testing stub value\r\n
434 s> Date: $HTTP_DATE$\r\n
431 s> Date: $HTTP_DATE$\r\n
435 s> Content-Type: text/plain\r\n
432 s> Content-Type: text/plain\r\n
436 s> Content-Length: 205\r\n
433 s> Content-Length: 205\r\n
437 s> \r\n
434 s> \r\n
438 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
435 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
439 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
436 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
440 s> received: <no frame>\n
437 s> received: <no frame>\n
441 s> {"action": "noop"}
438 s> {"action": "noop"}
442
439
443 Multiple requests to regular command URL are not allowed
440 Multiple requests to regular command URL are not allowed
444
441
445 $ sendhttpraw << EOF
442 $ sendhttpraw << EOF
446 > httprequest POST api/$HTTPV2/ro/customreadonly
443 > httprequest POST api/$HTTPV2/ro/customreadonly
447 > accept: $MEDIATYPE
444 > accept: $MEDIATYPE
448 > content-type: $MEDIATYPE
445 > content-type: $MEDIATYPE
449 > user-agent: test
446 > user-agent: test
450 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
447 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
451 > EOF
448 > EOF
452 using raw connection to peer
449 using raw connection to peer
453 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
450 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
454 s> Accept-Encoding: identity\r\n
451 s> Accept-Encoding: identity\r\n
455 s> accept: application/mercurial-exp-framing-0005\r\n
452 s> accept: application/mercurial-exp-framing-0005\r\n
456 s> content-type: application/mercurial-exp-framing-0005\r\n
453 s> content-type: application/mercurial-exp-framing-0005\r\n
457 s> user-agent: test\r\n
454 s> user-agent: test\r\n
458 s> content-length: 29\r\n
455 s> content-length: 29\r\n
459 s> host: $LOCALIP:$HGPORT\r\n (glob)
456 s> host: $LOCALIP:$HGPORT\r\n (glob)
460 s> \r\n
457 s> \r\n
461 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
458 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
462 s> makefile('rb', None)
459 s> makefile('rb', None)
463 s> HTTP/1.1 200 OK\r\n
460 s> HTTP/1.1 200 OK\r\n
464 s> Server: testing stub value\r\n
461 s> Server: testing stub value\r\n
465 s> Date: $HTTP_DATE$\r\n
462 s> Date: $HTTP_DATE$\r\n
466 s> Content-Type: application/mercurial-exp-framing-0005\r\n
463 s> Content-Type: application/mercurial-exp-framing-0005\r\n
467 s> Transfer-Encoding: chunked\r\n
464 s> Transfer-Encoding: chunked\r\n
468 s> \r\n
465 s> \r\n
469 s> 13\r\n
466 s> 13\r\n
470 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
467 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
471 s> \r\n
468 s> \r\n
472 s> 27\r\n
469 s> 27\r\n
473 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
470 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
474 s> \r\n
471 s> \r\n
475 s> 8\r\n
472 s> 8\r\n
476 s> \x00\x00\x00\x01\x00\x02\x002
473 s> \x00\x00\x00\x01\x00\x02\x002
477 s> \r\n
474 s> \r\n
478 s> 0\r\n
475 s> 0\r\n
479 s> \r\n
476 s> \r\n
480
477
481 Multiple requests to "multirequest" URL are allowed
478 Multiple requests to "multirequest" URL are allowed
482
479
483 $ sendhttpraw << EOF
480 $ sendhttpraw << EOF
484 > httprequest POST api/$HTTPV2/ro/multirequest
481 > httprequest POST api/$HTTPV2/ro/multirequest
485 > accept: $MEDIATYPE
482 > accept: $MEDIATYPE
486 > content-type: $MEDIATYPE
483 > content-type: $MEDIATYPE
487 > user-agent: test
484 > user-agent: test
488 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
485 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
489 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
486 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
490 > EOF
487 > EOF
491 using raw connection to peer
488 using raw connection to peer
492 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
489 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
493 s> Accept-Encoding: identity\r\n
490 s> Accept-Encoding: identity\r\n
494 s> *\r\n (glob)
491 s> *\r\n (glob)
495 s> *\r\n (glob)
492 s> *\r\n (glob)
496 s> user-agent: test\r\n
493 s> user-agent: test\r\n
497 s> content-length: 58\r\n
494 s> content-length: 58\r\n
498 s> host: $LOCALIP:$HGPORT\r\n (glob)
495 s> host: $LOCALIP:$HGPORT\r\n (glob)
499 s> \r\n
496 s> \r\n
500 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
497 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
501 s> makefile('rb', None)
498 s> makefile('rb', None)
502 s> HTTP/1.1 200 OK\r\n
499 s> HTTP/1.1 200 OK\r\n
503 s> Server: testing stub value\r\n
500 s> Server: testing stub value\r\n
504 s> Date: $HTTP_DATE$\r\n
501 s> Date: $HTTP_DATE$\r\n
505 s> Content-Type: application/mercurial-exp-framing-0005\r\n
502 s> Content-Type: application/mercurial-exp-framing-0005\r\n
506 s> Transfer-Encoding: chunked\r\n
503 s> Transfer-Encoding: chunked\r\n
507 s> \r\n
504 s> \r\n
508 s> 13\r\n
505 s> 13\r\n
509 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
506 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
510 s> \r\n
507 s> \r\n
511 s> 27\r\n
508 s> 27\r\n
512 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
509 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
513 s> \r\n
510 s> \r\n
514 s> 8\r\n
511 s> 8\r\n
515 s> \x00\x00\x00\x01\x00\x02\x002
512 s> \x00\x00\x00\x01\x00\x02\x002
516 s> \r\n
513 s> \r\n
517 s> 13\r\n
514 s> 13\r\n
518 s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
515 s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
519 s> \r\n
516 s> \r\n
520 s> 27\r\n
517 s> 27\r\n
521 s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
518 s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
522 s> \r\n
519 s> \r\n
523 s> 8\r\n
520 s> 8\r\n
524 s> \x00\x00\x00\x03\x00\x02\x002
521 s> \x00\x00\x00\x03\x00\x02\x002
525 s> \r\n
522 s> \r\n
526 s> 0\r\n
523 s> 0\r\n
527 s> \r\n
524 s> \r\n
528
525
529 Interleaved requests to "multirequest" are processed
526 Interleaved requests to "multirequest" are processed
530
527
531 $ sendhttpraw << EOF
528 $ sendhttpraw << EOF
532 > httprequest POST api/$HTTPV2/ro/multirequest
529 > httprequest POST api/$HTTPV2/ro/multirequest
533 > accept: $MEDIATYPE
530 > accept: $MEDIATYPE
534 > content-type: $MEDIATYPE
531 > content-type: $MEDIATYPE
535 > user-agent: test
532 > user-agent: test
536 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
533 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
537 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
534 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
538 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
535 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
539 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
536 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
540 > EOF
537 > EOF
541 using raw connection to peer
538 using raw connection to peer
542 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
539 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
543 s> Accept-Encoding: identity\r\n
540 s> Accept-Encoding: identity\r\n
544 s> accept: application/mercurial-exp-framing-0005\r\n
541 s> accept: application/mercurial-exp-framing-0005\r\n
545 s> content-type: application/mercurial-exp-framing-0005\r\n
542 s> content-type: application/mercurial-exp-framing-0005\r\n
546 s> user-agent: test\r\n
543 s> user-agent: test\r\n
547 s> content-length: 115\r\n
544 s> content-length: 115\r\n
548 s> host: $LOCALIP:$HGPORT\r\n (glob)
545 s> host: $LOCALIP:$HGPORT\r\n (glob)
549 s> \r\n
546 s> \r\n
550 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
547 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
551 s> makefile('rb', None)
548 s> makefile('rb', None)
552 s> HTTP/1.1 200 OK\r\n
549 s> HTTP/1.1 200 OK\r\n
553 s> Server: testing stub value\r\n
550 s> Server: testing stub value\r\n
554 s> Date: $HTTP_DATE$\r\n
551 s> Date: $HTTP_DATE$\r\n
555 s> Content-Type: application/mercurial-exp-framing-0005\r\n
552 s> Content-Type: application/mercurial-exp-framing-0005\r\n
556 s> Transfer-Encoding: chunked\r\n
553 s> Transfer-Encoding: chunked\r\n
557 s> \r\n
554 s> \r\n
558 s> 13\r\n
555 s> 13\r\n
559 s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
556 s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
560 s> \r\n
557 s> \r\n
561 s> 28\r\n
558 s> 28\r\n
562 s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
559 s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
563 s> \r\n
560 s> \r\n
564 s> 8\r\n
561 s> 8\r\n
565 s> \x00\x00\x00\x03\x00\x02\x002
562 s> \x00\x00\x00\x03\x00\x02\x002
566 s> \r\n
563 s> \r\n
567 s> 13\r\n
564 s> 13\r\n
568 s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
565 s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
569 s> \r\n
566 s> \r\n
570 s> 9\r\n
567 s> 9\r\n
571 s> \x01\x00\x00\x01\x00\x02\x001\xa0
568 s> \x01\x00\x00\x01\x00\x02\x001\xa0
572 s> \r\n
569 s> \r\n
573 s> 8\r\n
570 s> 8\r\n
574 s> \x00\x00\x00\x01\x00\x02\x002
571 s> \x00\x00\x00\x01\x00\x02\x002
575 s> \r\n
572 s> \r\n
576 s> 0\r\n
573 s> 0\r\n
577 s> \r\n
574 s> \r\n
578
575
579 Restart server to disable read-write access
576 Restart server to disable read-write access
580
577
581 $ killdaemons.py
578 $ killdaemons.py
582 $ cat > server/.hg/hgrc << EOF
579 $ cat > server/.hg/hgrc << EOF
583 > [experimental]
580 > [experimental]
584 > web.apiserver = true
581 > web.apiserver = true
585 > web.api.debugreflect = true
582 > web.api.debugreflect = true
586 > web.api.http-v2 = true
583 > web.api.http-v2 = true
587 > [web]
584 > [web]
588 > push_ssl = false
585 > push_ssl = false
589 > EOF
586 > EOF
590
587
591 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
588 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
592 $ cat hg.pid > $DAEMON_PIDS
589 $ cat hg.pid > $DAEMON_PIDS
593
590
594 Attempting to run a read-write command via multirequest on read-only URL is not allowed
591 Attempting to run a read-write command via multirequest on read-only URL is not allowed
595
592
596 $ sendhttpraw << EOF
593 $ sendhttpraw << EOF
597 > httprequest POST api/$HTTPV2/ro/multirequest
594 > httprequest POST api/$HTTPV2/ro/multirequest
598 > accept: $MEDIATYPE
595 > accept: $MEDIATYPE
599 > content-type: $MEDIATYPE
596 > content-type: $MEDIATYPE
600 > user-agent: test
597 > user-agent: test
601 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
598 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
602 > EOF
599 > EOF
603 using raw connection to peer
600 using raw connection to peer
604 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
601 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
605 s> Accept-Encoding: identity\r\n
602 s> Accept-Encoding: identity\r\n
606 s> accept: application/mercurial-exp-framing-0005\r\n
603 s> accept: application/mercurial-exp-framing-0005\r\n
607 s> content-type: application/mercurial-exp-framing-0005\r\n
604 s> content-type: application/mercurial-exp-framing-0005\r\n
608 s> user-agent: test\r\n
605 s> user-agent: test\r\n
609 s> content-length: 22\r\n
606 s> content-length: 22\r\n
610 s> host: $LOCALIP:$HGPORT\r\n (glob)
607 s> host: $LOCALIP:$HGPORT\r\n (glob)
611 s> \r\n
608 s> \r\n
612 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
609 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
613 s> makefile('rb', None)
610 s> makefile('rb', None)
614 s> HTTP/1.1 403 Forbidden\r\n
611 s> HTTP/1.1 403 Forbidden\r\n
615 s> Server: testing stub value\r\n
612 s> Server: testing stub value\r\n
616 s> Date: $HTTP_DATE$\r\n
613 s> Date: $HTTP_DATE$\r\n
617 s> Content-Type: text/plain\r\n
614 s> Content-Type: text/plain\r\n
618 s> Content-Length: 52\r\n
615 s> Content-Length: 52\r\n
619 s> \r\n
616 s> \r\n
620 s> insufficient permissions to execute command: pushkey
617 s> insufficient permissions to execute command: pushkey
621
618
622 $ cat error.log
619 $ cat error.log
@@ -1,431 +1,428 b''
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4
4
5 $ hg init server
5 $ hg init server
6
6
7 zstd isn't present in plain builds. Make tests easier by removing
7 zstd isn't present in plain builds. Make tests easier by removing
8 zstd from the equation.
8 zstd from the equation.
9
9
10 $ cat >> server/.hg/hgrc << EOF
10 $ cat >> server/.hg/hgrc << EOF
11 > [server]
11 > [server]
12 > compressionengines = zlib
12 > compressionengines = zlib
13 > EOF
13 > EOF
14
14
15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
16 $ cat hg.pid > $DAEMON_PIDS
16 $ cat hg.pid > $DAEMON_PIDS
17
17
18 A normal capabilities request is serviced for version 1
18 A normal capabilities request is serviced for version 1
19
19
20 $ sendhttpraw << EOF
20 $ sendhttpraw << EOF
21 > httprequest GET ?cmd=capabilities
21 > httprequest GET ?cmd=capabilities
22 > user-agent: test
22 > user-agent: test
23 > EOF
23 > EOF
24 using raw connection to peer
24 using raw connection to peer
25 s> GET /?cmd=capabilities HTTP/1.1\r\n
25 s> GET /?cmd=capabilities HTTP/1.1\r\n
26 s> Accept-Encoding: identity\r\n
26 s> Accept-Encoding: identity\r\n
27 s> user-agent: test\r\n
27 s> user-agent: test\r\n
28 s> host: $LOCALIP:$HGPORT\r\n (glob)
28 s> host: $LOCALIP:$HGPORT\r\n (glob)
29 s> \r\n
29 s> \r\n
30 s> makefile('rb', None)
30 s> makefile('rb', None)
31 s> HTTP/1.1 200 Script output follows\r\n
31 s> HTTP/1.1 200 Script output follows\r\n
32 s> Server: testing stub value\r\n
32 s> Server: testing stub value\r\n
33 s> Date: $HTTP_DATE$\r\n
33 s> Date: $HTTP_DATE$\r\n
34 s> Content-Type: application/mercurial-0.1\r\n
34 s> Content-Type: application/mercurial-0.1\r\n
35 s> Content-Length: *\r\n (glob)
35 s> Content-Length: *\r\n (glob)
36 s> \r\n
36 s> \r\n
37 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
37 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
38
38
39 A proper request without the API server enabled returns the legacy response
39 A proper request without the API server enabled returns the legacy response
40
40
41 $ sendhttpraw << EOF
41 $ sendhttpraw << EOF
42 > httprequest GET ?cmd=capabilities
42 > httprequest GET ?cmd=capabilities
43 > user-agent: test
43 > user-agent: test
44 > x-hgupgrade-1: foo
44 > x-hgupgrade-1: foo
45 > x-hgproto-1: cbor
45 > x-hgproto-1: cbor
46 > EOF
46 > EOF
47 using raw connection to peer
47 using raw connection to peer
48 s> GET /?cmd=capabilities HTTP/1.1\r\n
48 s> GET /?cmd=capabilities HTTP/1.1\r\n
49 s> Accept-Encoding: identity\r\n
49 s> Accept-Encoding: identity\r\n
50 s> user-agent: test\r\n
50 s> user-agent: test\r\n
51 s> x-hgproto-1: cbor\r\n
51 s> x-hgproto-1: cbor\r\n
52 s> x-hgupgrade-1: foo\r\n
52 s> x-hgupgrade-1: foo\r\n
53 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 s> host: $LOCALIP:$HGPORT\r\n (glob)
54 s> \r\n
54 s> \r\n
55 s> makefile('rb', None)
55 s> makefile('rb', None)
56 s> HTTP/1.1 200 Script output follows\r\n
56 s> HTTP/1.1 200 Script output follows\r\n
57 s> Server: testing stub value\r\n
57 s> Server: testing stub value\r\n
58 s> Date: $HTTP_DATE$\r\n
58 s> Date: $HTTP_DATE$\r\n
59 s> Content-Type: application/mercurial-0.1\r\n
59 s> Content-Type: application/mercurial-0.1\r\n
60 s> Content-Length: *\r\n (glob)
60 s> Content-Length: *\r\n (glob)
61 s> \r\n
61 s> \r\n
62 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
62 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
63
63
64 Restart with just API server enabled. This enables serving the new format.
64 Restart with just API server enabled. This enables serving the new format.
65
65
66 $ killdaemons.py
66 $ killdaemons.py
67 $ cat error.log
67 $ cat error.log
68
68
69 $ cat >> server/.hg/hgrc << EOF
69 $ cat >> server/.hg/hgrc << EOF
70 > [experimental]
70 > [experimental]
71 > web.apiserver = true
71 > web.apiserver = true
72 > EOF
72 > EOF
73
73
74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
75 $ cat hg.pid > $DAEMON_PIDS
75 $ cat hg.pid > $DAEMON_PIDS
76
76
77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
78
78
79 $ sendhttpraw << EOF
79 $ sendhttpraw << EOF
80 > httprequest GET ?cmd=capabilities
80 > httprequest GET ?cmd=capabilities
81 > user-agent: test
81 > user-agent: test
82 > x-hgupgrade-1: foo bar
82 > x-hgupgrade-1: foo bar
83 > EOF
83 > EOF
84 using raw connection to peer
84 using raw connection to peer
85 s> GET /?cmd=capabilities HTTP/1.1\r\n
85 s> GET /?cmd=capabilities HTTP/1.1\r\n
86 s> Accept-Encoding: identity\r\n
86 s> Accept-Encoding: identity\r\n
87 s> user-agent: test\r\n
87 s> user-agent: test\r\n
88 s> x-hgupgrade-1: foo bar\r\n
88 s> x-hgupgrade-1: foo bar\r\n
89 s> host: $LOCALIP:$HGPORT\r\n (glob)
89 s> host: $LOCALIP:$HGPORT\r\n (glob)
90 s> \r\n
90 s> \r\n
91 s> makefile('rb', None)
91 s> makefile('rb', None)
92 s> HTTP/1.1 200 Script output follows\r\n
92 s> HTTP/1.1 200 Script output follows\r\n
93 s> Server: testing stub value\r\n
93 s> Server: testing stub value\r\n
94 s> Date: $HTTP_DATE$\r\n
94 s> Date: $HTTP_DATE$\r\n
95 s> Content-Type: application/mercurial-0.1\r\n
95 s> Content-Type: application/mercurial-0.1\r\n
96 s> Content-Length: *\r\n (glob)
96 s> Content-Length: *\r\n (glob)
97 s> \r\n
97 s> \r\n
98 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
98 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
99
99
100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
101
101
102 $ sendhttpraw << EOF
102 $ sendhttpraw << EOF
103 > httprequest GET ?cmd=capabilities
103 > httprequest GET ?cmd=capabilities
104 > user-agent: test
104 > user-agent: test
105 > x-hgupgrade-1: foo bar
105 > x-hgupgrade-1: foo bar
106 > x-hgproto-1: some value
106 > x-hgproto-1: some value
107 > EOF
107 > EOF
108 using raw connection to peer
108 using raw connection to peer
109 s> GET /?cmd=capabilities HTTP/1.1\r\n
109 s> GET /?cmd=capabilities HTTP/1.1\r\n
110 s> Accept-Encoding: identity\r\n
110 s> Accept-Encoding: identity\r\n
111 s> user-agent: test\r\n
111 s> user-agent: test\r\n
112 s> x-hgproto-1: some value\r\n
112 s> x-hgproto-1: some value\r\n
113 s> x-hgupgrade-1: foo bar\r\n
113 s> x-hgupgrade-1: foo bar\r\n
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 s> \r\n
115 s> \r\n
116 s> makefile('rb', None)
116 s> makefile('rb', None)
117 s> HTTP/1.1 200 Script output follows\r\n
117 s> HTTP/1.1 200 Script output follows\r\n
118 s> Server: testing stub value\r\n
118 s> Server: testing stub value\r\n
119 s> Date: $HTTP_DATE$\r\n
119 s> Date: $HTTP_DATE$\r\n
120 s> Content-Type: application/mercurial-0.1\r\n
120 s> Content-Type: application/mercurial-0.1\r\n
121 s> Content-Length: *\r\n (glob)
121 s> Content-Length: *\r\n (glob)
122 s> \r\n
122 s> \r\n
123 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
123 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
124
124
125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
126
126
127 $ sendhttpraw << EOF
127 $ sendhttpraw << EOF
128 > httprequest GET ?cmd=capabilities
128 > httprequest GET ?cmd=capabilities
129 > user-agent: test
129 > user-agent: test
130 > x-hgupgrade-1: foo bar
130 > x-hgupgrade-1: foo bar
131 > x-hgproto-1: cbor
131 > x-hgproto-1: cbor
132 > EOF
132 > EOF
133 using raw connection to peer
133 using raw connection to peer
134 s> GET /?cmd=capabilities HTTP/1.1\r\n
134 s> GET /?cmd=capabilities HTTP/1.1\r\n
135 s> Accept-Encoding: identity\r\n
135 s> Accept-Encoding: identity\r\n
136 s> user-agent: test\r\n
136 s> user-agent: test\r\n
137 s> x-hgproto-1: cbor\r\n
137 s> x-hgproto-1: cbor\r\n
138 s> x-hgupgrade-1: foo bar\r\n
138 s> x-hgupgrade-1: foo bar\r\n
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
140 s> \r\n
140 s> \r\n
141 s> makefile('rb', None)
141 s> makefile('rb', None)
142 s> HTTP/1.1 200 OK\r\n
142 s> HTTP/1.1 200 OK\r\n
143 s> Server: testing stub value\r\n
143 s> Server: testing stub value\r\n
144 s> Date: $HTTP_DATE$\r\n
144 s> Date: $HTTP_DATE$\r\n
145 s> Content-Type: application/mercurial-cbor\r\n
145 s> Content-Type: application/mercurial-cbor\r\n
146 s> Content-Length: *\r\n (glob)
146 s> Content-Length: *\r\n (glob)
147 s> \r\n
147 s> \r\n
148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
149 cbor> {
149 cbor> {
150 b'apibase': b'api/',
150 b'apibase': b'api/',
151 b'apis': {},
151 b'apis': {},
152 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
152 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
153 }
153 }
154
154
155 Restart server to enable HTTPv2
155 Restart server to enable HTTPv2
156
156
157 $ killdaemons.py
157 $ killdaemons.py
158 $ enablehttpv2 server
158 $ enablehttpv2 server
159 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
159 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
160 $ cat hg.pid > $DAEMON_PIDS
160 $ cat hg.pid > $DAEMON_PIDS
161
161
162 Only requested API services are returned
162 Only requested API services are returned
163
163
164 $ sendhttpraw << EOF
164 $ sendhttpraw << EOF
165 > httprequest GET ?cmd=capabilities
165 > httprequest GET ?cmd=capabilities
166 > user-agent: test
166 > user-agent: test
167 > x-hgupgrade-1: foo bar
167 > x-hgupgrade-1: foo bar
168 > x-hgproto-1: cbor
168 > x-hgproto-1: cbor
169 > EOF
169 > EOF
170 using raw connection to peer
170 using raw connection to peer
171 s> GET /?cmd=capabilities HTTP/1.1\r\n
171 s> GET /?cmd=capabilities HTTP/1.1\r\n
172 s> Accept-Encoding: identity\r\n
172 s> Accept-Encoding: identity\r\n
173 s> user-agent: test\r\n
173 s> user-agent: test\r\n
174 s> x-hgproto-1: cbor\r\n
174 s> x-hgproto-1: cbor\r\n
175 s> x-hgupgrade-1: foo bar\r\n
175 s> x-hgupgrade-1: foo bar\r\n
176 s> host: $LOCALIP:$HGPORT\r\n (glob)
176 s> host: $LOCALIP:$HGPORT\r\n (glob)
177 s> \r\n
177 s> \r\n
178 s> makefile('rb', None)
178 s> makefile('rb', None)
179 s> HTTP/1.1 200 OK\r\n
179 s> HTTP/1.1 200 OK\r\n
180 s> Server: testing stub value\r\n
180 s> Server: testing stub value\r\n
181 s> Date: $HTTP_DATE$\r\n
181 s> Date: $HTTP_DATE$\r\n
182 s> Content-Type: application/mercurial-cbor\r\n
182 s> Content-Type: application/mercurial-cbor\r\n
183 s> Content-Length: *\r\n (glob)
183 s> Content-Length: *\r\n (glob)
184 s> \r\n
184 s> \r\n
185 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
185 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
186 cbor> {
186 cbor> {
187 b'apibase': b'api/',
187 b'apibase': b'api/',
188 b'apis': {},
188 b'apis': {},
189 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
189 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
190 }
190 }
191
191
192 Request for HTTPv2 service returns information about it
192 Request for HTTPv2 service returns information about it
193
193
194 $ sendhttpraw << EOF
194 $ sendhttpraw << EOF
195 > httprequest GET ?cmd=capabilities
195 > httprequest GET ?cmd=capabilities
196 > user-agent: test
196 > user-agent: test
197 > x-hgupgrade-1: exp-http-v2-0001 foo bar
197 > x-hgupgrade-1: exp-http-v2-0001 foo bar
198 > x-hgproto-1: cbor
198 > x-hgproto-1: cbor
199 > EOF
199 > EOF
200 using raw connection to peer
200 using raw connection to peer
201 s> GET /?cmd=capabilities HTTP/1.1\r\n
201 s> GET /?cmd=capabilities HTTP/1.1\r\n
202 s> Accept-Encoding: identity\r\n
202 s> Accept-Encoding: identity\r\n
203 s> user-agent: test\r\n
203 s> user-agent: test\r\n
204 s> x-hgproto-1: cbor\r\n
204 s> x-hgproto-1: cbor\r\n
205 s> x-hgupgrade-1: exp-http-v2-0001 foo bar\r\n
205 s> x-hgupgrade-1: exp-http-v2-0001 foo bar\r\n
206 s> host: $LOCALIP:$HGPORT\r\n (glob)
206 s> host: $LOCALIP:$HGPORT\r\n (glob)
207 s> \r\n
207 s> \r\n
208 s> makefile('rb', None)
208 s> makefile('rb', None)
209 s> HTTP/1.1 200 OK\r\n
209 s> HTTP/1.1 200 OK\r\n
210 s> Server: testing stub value\r\n
210 s> Server: testing stub value\r\n
211 s> Date: $HTTP_DATE$\r\n
211 s> Date: $HTTP_DATE$\r\n
212 s> Content-Type: application/mercurial-cbor\r\n
212 s> Content-Type: application/mercurial-cbor\r\n
213 s> Content-Length: *\r\n (glob)
213 s> Content-Length: *\r\n (glob)
214 s> \r\n
214 s> \r\n
215 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
215 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
216 cbor> {
216 cbor> {
217 b'apibase': b'api/',
217 b'apibase': b'api/',
218 b'apis': {
218 b'apis': {
219 b'exp-http-v2-0001': {
219 b'exp-http-v2-0001': {
220 b'commands': {
220 b'commands': {
221 b'branchmap': {
221 b'branchmap': {
222 b'args': {},
222 b'args': {},
223 b'permissions': [
223 b'permissions': [
224 b'pull'
224 b'pull'
225 ]
225 ]
226 },
226 },
227 b'capabilities': {
227 b'capabilities': {
228 b'args': {},
228 b'args': {},
229 b'permissions': [
229 b'permissions': [
230 b'pull'
230 b'pull'
231 ]
231 ]
232 },
232 },
233 b'heads': {
233 b'heads': {
234 b'args': {
234 b'args': {
235 b'publiconly': False
235 b'publiconly': False
236 },
236 },
237 b'permissions': [
237 b'permissions': [
238 b'pull'
238 b'pull'
239 ]
239 ]
240 },
240 },
241 b'known': {
241 b'known': {
242 b'args': {
242 b'args': {
243 b'nodes': [
243 b'nodes': [
244 b'deadbeef'
244 b'deadbeef'
245 ]
245 ]
246 },
246 },
247 b'permissions': [
247 b'permissions': [
248 b'pull'
248 b'pull'
249 ]
249 ]
250 },
250 },
251 b'listkeys': {
251 b'listkeys': {
252 b'args': {
252 b'args': {
253 b'namespace': b'ns'
253 b'namespace': b'ns'
254 },
254 },
255 b'permissions': [
255 b'permissions': [
256 b'pull'
256 b'pull'
257 ]
257 ]
258 },
258 },
259 b'lookup': {
259 b'lookup': {
260 b'args': {
260 b'args': {
261 b'key': b'foo'
261 b'key': b'foo'
262 },
262 },
263 b'permissions': [
263 b'permissions': [
264 b'pull'
264 b'pull'
265 ]
265 ]
266 },
266 },
267 b'pushkey': {
267 b'pushkey': {
268 b'args': {
268 b'args': {
269 b'key': b'key',
269 b'key': b'key',
270 b'namespace': b'ns',
270 b'namespace': b'ns',
271 b'new': b'new',
271 b'new': b'new',
272 b'old': b'old'
272 b'old': b'old'
273 },
273 },
274 b'permissions': [
274 b'permissions': [
275 b'push'
275 b'push'
276 ]
276 ]
277 }
277 }
278 },
278 },
279 b'compression': [
279 b'compression': [
280 {
280 {
281 b'name': b'zlib'
281 b'name': b'zlib'
282 }
282 }
283 ],
283 ],
284 b'framingmediatypes': [
284 b'framingmediatypes': [
285 b'application/mercurial-exp-framing-0005'
285 b'application/mercurial-exp-framing-0005'
286 ],
286 ],
287 b'rawrepoformats': [
287 b'rawrepoformats': [
288 b'generaldelta',
288 b'generaldelta',
289 b'revlogv1'
289 b'revlogv1'
290 ]
290 ]
291 }
291 }
292 },
292 },
293 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
293 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
294 }
294 }
295
295
296 capabilities command returns expected info
296 capabilities command returns expected info
297
297
298 $ sendhttpv2peerhandshake << EOF
298 $ sendhttpv2peerhandshake << EOF
299 > command capabilities
299 > command capabilities
300 > EOF
300 > EOF
301 creating http peer for wire protocol version 2
301 creating http peer for wire protocol version 2
302 s> GET /?cmd=capabilities HTTP/1.1\r\n
302 s> GET /?cmd=capabilities HTTP/1.1\r\n
303 s> Accept-Encoding: identity\r\n
303 s> Accept-Encoding: identity\r\n
304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
305 s> x-hgproto-1: cbor\r\n
305 s> x-hgproto-1: cbor\r\n
306 s> x-hgupgrade-1: exp-http-v2-0001\r\n
306 s> x-hgupgrade-1: exp-http-v2-0001\r\n
307 s> accept: application/mercurial-0.1\r\n
307 s> accept: application/mercurial-0.1\r\n
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
309 s> user-agent: Mercurial debugwireproto\r\n
309 s> user-agent: Mercurial debugwireproto\r\n
310 s> \r\n
310 s> \r\n
311 s> makefile('rb', None)
311 s> makefile('rb', None)
312 s> HTTP/1.1 200 OK\r\n
312 s> HTTP/1.1 200 OK\r\n
313 s> Server: testing stub value\r\n
313 s> Server: testing stub value\r\n
314 s> Date: $HTTP_DATE$\r\n
314 s> Date: $HTTP_DATE$\r\n
315 s> Content-Type: application/mercurial-cbor\r\n
315 s> Content-Type: application/mercurial-cbor\r\n
316 s> Content-Length: *\r\n (glob)
316 s> Content-Length: *\r\n (glob)
317 s> \r\n
317 s> \r\n
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
319 sending capabilities command
319 sending capabilities command
320 s> POST /api/exp-http-v2-0001/ro/capabilities HTTP/1.1\r\n
320 s> POST /api/exp-http-v2-0001/ro/capabilities HTTP/1.1\r\n
321 s> Accept-Encoding: identity\r\n
321 s> Accept-Encoding: identity\r\n
322 s> *\r\n (glob)
322 s> *\r\n (glob)
323 s> content-type: application/mercurial-exp-framing-0005\r\n
323 s> content-type: application/mercurial-exp-framing-0005\r\n
324 s> content-length: 27\r\n
324 s> content-length: 27\r\n
325 s> host: $LOCALIP:$HGPORT\r\n (glob)
325 s> host: $LOCALIP:$HGPORT\r\n (glob)
326 s> user-agent: Mercurial debugwireproto\r\n
326 s> user-agent: Mercurial debugwireproto\r\n
327 s> \r\n
327 s> \r\n
328 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
328 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
329 s> makefile('rb', None)
329 s> makefile('rb', None)
330 s> HTTP/1.1 200 OK\r\n
330 s> HTTP/1.1 200 OK\r\n
331 s> Server: testing stub value\r\n
331 s> Server: testing stub value\r\n
332 s> Date: $HTTP_DATE$\r\n
332 s> Date: $HTTP_DATE$\r\n
333 s> Content-Type: application/mercurial-exp-framing-0005\r\n
333 s> Content-Type: application/mercurial-exp-framing-0005\r\n
334 s> Transfer-Encoding: chunked\r\n
334 s> Transfer-Encoding: chunked\r\n
335 s> \r\n
335 s> \r\n
336 s> 13\r\n
336 s> 13\r\n
337 s> \x0b\x00\x00\x01\x00\x02\x011
337 s> \x0b\x00\x00\x01\x00\x02\x011
338 s> \xa1FstatusBok
338 s> \xa1FstatusBok
339 s> \r\n
339 s> \r\n
340 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
340 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
341 s> 1cc\r\n
341 s> 1cc\r\n
342 s> \xc4\x01\x00\x01\x00\x02\x001
342 s> \xc4\x01\x00\x01\x00\x02\x001
343 s> \xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1
343 s> \xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1
344 s> \r\n
344 s> \r\n
345 received frame(size=452; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
345 received frame(size=452; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
346 s> 8\r\n
346 s> 8\r\n
347 s> \x00\x00\x00\x01\x00\x02\x002
347 s> \x00\x00\x00\x01\x00\x02\x002
348 s> \r\n
348 s> \r\n
349 s> 0\r\n
349 s> 0\r\n
350 s> \r\n
350 s> \r\n
351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
352 response: [
352 response: gen[
353 {
354 b'status': b'ok'
355 },
356 {
353 {
357 b'commands': {
354 b'commands': {
358 b'branchmap': {
355 b'branchmap': {
359 b'args': {},
356 b'args': {},
360 b'permissions': [
357 b'permissions': [
361 b'pull'
358 b'pull'
362 ]
359 ]
363 },
360 },
364 b'capabilities': {
361 b'capabilities': {
365 b'args': {},
362 b'args': {},
366 b'permissions': [
363 b'permissions': [
367 b'pull'
364 b'pull'
368 ]
365 ]
369 },
366 },
370 b'heads': {
367 b'heads': {
371 b'args': {
368 b'args': {
372 b'publiconly': False
369 b'publiconly': False
373 },
370 },
374 b'permissions': [
371 b'permissions': [
375 b'pull'
372 b'pull'
376 ]
373 ]
377 },
374 },
378 b'known': {
375 b'known': {
379 b'args': {
376 b'args': {
380 b'nodes': [
377 b'nodes': [
381 b'deadbeef'
378 b'deadbeef'
382 ]
379 ]
383 },
380 },
384 b'permissions': [
381 b'permissions': [
385 b'pull'
382 b'pull'
386 ]
383 ]
387 },
384 },
388 b'listkeys': {
385 b'listkeys': {
389 b'args': {
386 b'args': {
390 b'namespace': b'ns'
387 b'namespace': b'ns'
391 },
388 },
392 b'permissions': [
389 b'permissions': [
393 b'pull'
390 b'pull'
394 ]
391 ]
395 },
392 },
396 b'lookup': {
393 b'lookup': {
397 b'args': {
394 b'args': {
398 b'key': b'foo'
395 b'key': b'foo'
399 },
396 },
400 b'permissions': [
397 b'permissions': [
401 b'pull'
398 b'pull'
402 ]
399 ]
403 },
400 },
404 b'pushkey': {
401 b'pushkey': {
405 b'args': {
402 b'args': {
406 b'key': b'key',
403 b'key': b'key',
407 b'namespace': b'ns',
404 b'namespace': b'ns',
408 b'new': b'new',
405 b'new': b'new',
409 b'old': b'old'
406 b'old': b'old'
410 },
407 },
411 b'permissions': [
408 b'permissions': [
412 b'push'
409 b'push'
413 ]
410 ]
414 }
411 }
415 },
412 },
416 b'compression': [
413 b'compression': [
417 {
414 {
418 b'name': b'zlib'
415 b'name': b'zlib'
419 }
416 }
420 ],
417 ],
421 b'framingmediatypes': [
418 b'framingmediatypes': [
422 b'application/mercurial-exp-framing-0005'
419 b'application/mercurial-exp-framing-0005'
423 ],
420 ],
424 b'rawrepoformats': [
421 b'rawrepoformats': [
425 b'generaldelta',
422 b'generaldelta',
426 b'revlogv1'
423 b'revlogv1'
427 ]
424 ]
428 }
425 }
429 ]
426 ]
430
427
431 $ cat error.log
428 $ cat error.log
General Comments 0
You need to be logged in to leave comments. Login now