##// END OF EJS Templates
wireproto: define permissions-based routing of HTTPv2 wire protocol...
Gregory Szorc -
r37065:fddcb51b default
parent child Browse files
Show More
@@ -1,3016 +1,3021 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 tempfile
24 import tempfile
25 import time
25 import time
26
26
27 from .i18n import _
27 from .i18n import _
28 from .node import (
28 from .node import (
29 bin,
29 bin,
30 hex,
30 hex,
31 nullhex,
31 nullhex,
32 nullid,
32 nullid,
33 nullrev,
33 nullrev,
34 short,
34 short,
35 )
35 )
36 from . import (
36 from . import (
37 bundle2,
37 bundle2,
38 changegroup,
38 changegroup,
39 cmdutil,
39 cmdutil,
40 color,
40 color,
41 context,
41 context,
42 dagparser,
42 dagparser,
43 dagutil,
43 dagutil,
44 encoding,
44 encoding,
45 error,
45 error,
46 exchange,
46 exchange,
47 extensions,
47 extensions,
48 filemerge,
48 filemerge,
49 fileset,
49 fileset,
50 formatter,
50 formatter,
51 hg,
51 hg,
52 httppeer,
52 httppeer,
53 localrepo,
53 localrepo,
54 lock as lockmod,
54 lock as lockmod,
55 logcmdutil,
55 logcmdutil,
56 merge as mergemod,
56 merge as mergemod,
57 obsolete,
57 obsolete,
58 obsutil,
58 obsutil,
59 phases,
59 phases,
60 policy,
60 policy,
61 pvec,
61 pvec,
62 pycompat,
62 pycompat,
63 registrar,
63 registrar,
64 repair,
64 repair,
65 revlog,
65 revlog,
66 revset,
66 revset,
67 revsetlang,
67 revsetlang,
68 scmutil,
68 scmutil,
69 setdiscovery,
69 setdiscovery,
70 simplemerge,
70 simplemerge,
71 smartset,
71 smartset,
72 sshpeer,
72 sshpeer,
73 sslutil,
73 sslutil,
74 streamclone,
74 streamclone,
75 templater,
75 templater,
76 treediscovery,
76 treediscovery,
77 upgrade,
77 upgrade,
78 url as urlmod,
78 url as urlmod,
79 util,
79 util,
80 vfs as vfsmod,
80 vfs as vfsmod,
81 wireprotoserver,
81 wireprotoserver,
82 )
82 )
83 from .utils import dateutil
83 from .utils import dateutil
84
84
85 release = lockmod.release
85 release = lockmod.release
86
86
87 command = registrar.command()
87 command = registrar.command()
88
88
89 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
89 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
90 def debugancestor(ui, repo, *args):
90 def debugancestor(ui, repo, *args):
91 """find the ancestor revision of two revisions in a given index"""
91 """find the ancestor revision of two revisions in a given index"""
92 if len(args) == 3:
92 if len(args) == 3:
93 index, rev1, rev2 = args
93 index, rev1, rev2 = args
94 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
94 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
95 lookup = r.lookup
95 lookup = r.lookup
96 elif len(args) == 2:
96 elif len(args) == 2:
97 if not repo:
97 if not repo:
98 raise error.Abort(_('there is no Mercurial repository here '
98 raise error.Abort(_('there is no Mercurial repository here '
99 '(.hg not found)'))
99 '(.hg not found)'))
100 rev1, rev2 = args
100 rev1, rev2 = args
101 r = repo.changelog
101 r = repo.changelog
102 lookup = repo.lookup
102 lookup = repo.lookup
103 else:
103 else:
104 raise error.Abort(_('either two or three arguments required'))
104 raise error.Abort(_('either two or three arguments required'))
105 a = r.ancestor(lookup(rev1), lookup(rev2))
105 a = r.ancestor(lookup(rev1), lookup(rev2))
106 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
106 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
107
107
108 @command('debugapplystreamclonebundle', [], 'FILE')
108 @command('debugapplystreamclonebundle', [], 'FILE')
109 def debugapplystreamclonebundle(ui, repo, fname):
109 def debugapplystreamclonebundle(ui, repo, fname):
110 """apply a stream clone bundle file"""
110 """apply a stream clone bundle file"""
111 f = hg.openpath(ui, fname)
111 f = hg.openpath(ui, fname)
112 gen = exchange.readbundle(ui, f, fname)
112 gen = exchange.readbundle(ui, f, fname)
113 gen.apply(repo)
113 gen.apply(repo)
114
114
115 @command('debugbuilddag',
115 @command('debugbuilddag',
116 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
116 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
117 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
117 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
118 ('n', 'new-file', None, _('add new file at each rev'))],
118 ('n', 'new-file', None, _('add new file at each rev'))],
119 _('[OPTION]... [TEXT]'))
119 _('[OPTION]... [TEXT]'))
120 def debugbuilddag(ui, repo, text=None,
120 def debugbuilddag(ui, repo, text=None,
121 mergeable_file=False,
121 mergeable_file=False,
122 overwritten_file=False,
122 overwritten_file=False,
123 new_file=False):
123 new_file=False):
124 """builds a repo with a given DAG from scratch in the current empty repo
124 """builds a repo with a given DAG from scratch in the current empty repo
125
125
126 The description of the DAG is read from stdin if not given on the
126 The description of the DAG is read from stdin if not given on the
127 command line.
127 command line.
128
128
129 Elements:
129 Elements:
130
130
131 - "+n" is a linear run of n nodes based on the current default parent
131 - "+n" is a linear run of n nodes based on the current default parent
132 - "." is a single node based on the current default parent
132 - "." is a single node based on the current default parent
133 - "$" resets the default parent to null (implied at the start);
133 - "$" resets the default parent to null (implied at the start);
134 otherwise the default parent is always the last node created
134 otherwise the default parent is always the last node created
135 - "<p" sets the default parent to the backref p
135 - "<p" sets the default parent to the backref p
136 - "*p" is a fork at parent p, which is a backref
136 - "*p" is a fork at parent p, which is a backref
137 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
137 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
138 - "/p2" is a merge of the preceding node and p2
138 - "/p2" is a merge of the preceding node and p2
139 - ":tag" defines a local tag for the preceding node
139 - ":tag" defines a local tag for the preceding node
140 - "@branch" sets the named branch for subsequent nodes
140 - "@branch" sets the named branch for subsequent nodes
141 - "#...\\n" is a comment up to the end of the line
141 - "#...\\n" is a comment up to the end of the line
142
142
143 Whitespace between the above elements is ignored.
143 Whitespace between the above elements is ignored.
144
144
145 A backref is either
145 A backref is either
146
146
147 - a number n, which references the node curr-n, where curr is the current
147 - a number n, which references the node curr-n, where curr is the current
148 node, or
148 node, or
149 - the name of a local tag you placed earlier using ":tag", or
149 - the name of a local tag you placed earlier using ":tag", or
150 - empty to denote the default parent.
150 - empty to denote the default parent.
151
151
152 All string valued-elements are either strictly alphanumeric, or must
152 All string valued-elements are either strictly alphanumeric, or must
153 be enclosed in double quotes ("..."), with "\\" as escape character.
153 be enclosed in double quotes ("..."), with "\\" as escape character.
154 """
154 """
155
155
156 if text is None:
156 if text is None:
157 ui.status(_("reading DAG from stdin\n"))
157 ui.status(_("reading DAG from stdin\n"))
158 text = ui.fin.read()
158 text = ui.fin.read()
159
159
160 cl = repo.changelog
160 cl = repo.changelog
161 if len(cl) > 0:
161 if len(cl) > 0:
162 raise error.Abort(_('repository is not empty'))
162 raise error.Abort(_('repository is not empty'))
163
163
164 # determine number of revs in DAG
164 # determine number of revs in DAG
165 total = 0
165 total = 0
166 for type, data in dagparser.parsedag(text):
166 for type, data in dagparser.parsedag(text):
167 if type == 'n':
167 if type == 'n':
168 total += 1
168 total += 1
169
169
170 if mergeable_file:
170 if mergeable_file:
171 linesperrev = 2
171 linesperrev = 2
172 # make a file with k lines per rev
172 # make a file with k lines per rev
173 initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)]
173 initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)]
174 initialmergedlines.append("")
174 initialmergedlines.append("")
175
175
176 tags = []
176 tags = []
177
177
178 wlock = lock = tr = None
178 wlock = lock = tr = None
179 try:
179 try:
180 wlock = repo.wlock()
180 wlock = repo.wlock()
181 lock = repo.lock()
181 lock = repo.lock()
182 tr = repo.transaction("builddag")
182 tr = repo.transaction("builddag")
183
183
184 at = -1
184 at = -1
185 atbranch = 'default'
185 atbranch = 'default'
186 nodeids = []
186 nodeids = []
187 id = 0
187 id = 0
188 ui.progress(_('building'), id, unit=_('revisions'), total=total)
188 ui.progress(_('building'), id, unit=_('revisions'), total=total)
189 for type, data in dagparser.parsedag(text):
189 for type, data in dagparser.parsedag(text):
190 if type == 'n':
190 if type == 'n':
191 ui.note(('node %s\n' % pycompat.bytestr(data)))
191 ui.note(('node %s\n' % pycompat.bytestr(data)))
192 id, ps = data
192 id, ps = data
193
193
194 files = []
194 files = []
195 filecontent = {}
195 filecontent = {}
196
196
197 p2 = None
197 p2 = None
198 if mergeable_file:
198 if mergeable_file:
199 fn = "mf"
199 fn = "mf"
200 p1 = repo[ps[0]]
200 p1 = repo[ps[0]]
201 if len(ps) > 1:
201 if len(ps) > 1:
202 p2 = repo[ps[1]]
202 p2 = repo[ps[1]]
203 pa = p1.ancestor(p2)
203 pa = p1.ancestor(p2)
204 base, local, other = [x[fn].data() for x in (pa, p1,
204 base, local, other = [x[fn].data() for x in (pa, p1,
205 p2)]
205 p2)]
206 m3 = simplemerge.Merge3Text(base, local, other)
206 m3 = simplemerge.Merge3Text(base, local, other)
207 ml = [l.strip() for l in m3.merge_lines()]
207 ml = [l.strip() for l in m3.merge_lines()]
208 ml.append("")
208 ml.append("")
209 elif at > 0:
209 elif at > 0:
210 ml = p1[fn].data().split("\n")
210 ml = p1[fn].data().split("\n")
211 else:
211 else:
212 ml = initialmergedlines
212 ml = initialmergedlines
213 ml[id * linesperrev] += " r%i" % id
213 ml[id * linesperrev] += " r%i" % id
214 mergedtext = "\n".join(ml)
214 mergedtext = "\n".join(ml)
215 files.append(fn)
215 files.append(fn)
216 filecontent[fn] = mergedtext
216 filecontent[fn] = mergedtext
217
217
218 if overwritten_file:
218 if overwritten_file:
219 fn = "of"
219 fn = "of"
220 files.append(fn)
220 files.append(fn)
221 filecontent[fn] = "r%i\n" % id
221 filecontent[fn] = "r%i\n" % id
222
222
223 if new_file:
223 if new_file:
224 fn = "nf%i" % id
224 fn = "nf%i" % id
225 files.append(fn)
225 files.append(fn)
226 filecontent[fn] = "r%i\n" % id
226 filecontent[fn] = "r%i\n" % id
227 if len(ps) > 1:
227 if len(ps) > 1:
228 if not p2:
228 if not p2:
229 p2 = repo[ps[1]]
229 p2 = repo[ps[1]]
230 for fn in p2:
230 for fn in p2:
231 if fn.startswith("nf"):
231 if fn.startswith("nf"):
232 files.append(fn)
232 files.append(fn)
233 filecontent[fn] = p2[fn].data()
233 filecontent[fn] = p2[fn].data()
234
234
235 def fctxfn(repo, cx, path):
235 def fctxfn(repo, cx, path):
236 if path in filecontent:
236 if path in filecontent:
237 return context.memfilectx(repo, cx, path,
237 return context.memfilectx(repo, cx, path,
238 filecontent[path])
238 filecontent[path])
239 return None
239 return None
240
240
241 if len(ps) == 0 or ps[0] < 0:
241 if len(ps) == 0 or ps[0] < 0:
242 pars = [None, None]
242 pars = [None, None]
243 elif len(ps) == 1:
243 elif len(ps) == 1:
244 pars = [nodeids[ps[0]], None]
244 pars = [nodeids[ps[0]], None]
245 else:
245 else:
246 pars = [nodeids[p] for p in ps]
246 pars = [nodeids[p] for p in ps]
247 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
247 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
248 date=(id, 0),
248 date=(id, 0),
249 user="debugbuilddag",
249 user="debugbuilddag",
250 extra={'branch': atbranch})
250 extra={'branch': atbranch})
251 nodeid = repo.commitctx(cx)
251 nodeid = repo.commitctx(cx)
252 nodeids.append(nodeid)
252 nodeids.append(nodeid)
253 at = id
253 at = id
254 elif type == 'l':
254 elif type == 'l':
255 id, name = data
255 id, name = data
256 ui.note(('tag %s\n' % name))
256 ui.note(('tag %s\n' % name))
257 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
257 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
258 elif type == 'a':
258 elif type == 'a':
259 ui.note(('branch %s\n' % data))
259 ui.note(('branch %s\n' % data))
260 atbranch = data
260 atbranch = data
261 ui.progress(_('building'), id, unit=_('revisions'), total=total)
261 ui.progress(_('building'), id, unit=_('revisions'), total=total)
262 tr.close()
262 tr.close()
263
263
264 if tags:
264 if tags:
265 repo.vfs.write("localtags", "".join(tags))
265 repo.vfs.write("localtags", "".join(tags))
266 finally:
266 finally:
267 ui.progress(_('building'), None)
267 ui.progress(_('building'), None)
268 release(tr, lock, wlock)
268 release(tr, lock, wlock)
269
269
270 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
270 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
271 indent_string = ' ' * indent
271 indent_string = ' ' * indent
272 if all:
272 if all:
273 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
273 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
274 % indent_string)
274 % indent_string)
275
275
276 def showchunks(named):
276 def showchunks(named):
277 ui.write("\n%s%s\n" % (indent_string, named))
277 ui.write("\n%s%s\n" % (indent_string, named))
278 for deltadata in gen.deltaiter():
278 for deltadata in gen.deltaiter():
279 node, p1, p2, cs, deltabase, delta, flags = deltadata
279 node, p1, p2, cs, deltabase, delta, flags = deltadata
280 ui.write("%s%s %s %s %s %s %d\n" %
280 ui.write("%s%s %s %s %s %s %d\n" %
281 (indent_string, hex(node), hex(p1), hex(p2),
281 (indent_string, hex(node), hex(p1), hex(p2),
282 hex(cs), hex(deltabase), len(delta)))
282 hex(cs), hex(deltabase), len(delta)))
283
283
284 chunkdata = gen.changelogheader()
284 chunkdata = gen.changelogheader()
285 showchunks("changelog")
285 showchunks("changelog")
286 chunkdata = gen.manifestheader()
286 chunkdata = gen.manifestheader()
287 showchunks("manifest")
287 showchunks("manifest")
288 for chunkdata in iter(gen.filelogheader, {}):
288 for chunkdata in iter(gen.filelogheader, {}):
289 fname = chunkdata['filename']
289 fname = chunkdata['filename']
290 showchunks(fname)
290 showchunks(fname)
291 else:
291 else:
292 if isinstance(gen, bundle2.unbundle20):
292 if isinstance(gen, bundle2.unbundle20):
293 raise error.Abort(_('use debugbundle2 for this file'))
293 raise error.Abort(_('use debugbundle2 for this file'))
294 chunkdata = gen.changelogheader()
294 chunkdata = gen.changelogheader()
295 for deltadata in gen.deltaiter():
295 for deltadata in gen.deltaiter():
296 node, p1, p2, cs, deltabase, delta, flags = deltadata
296 node, p1, p2, cs, deltabase, delta, flags = deltadata
297 ui.write("%s%s\n" % (indent_string, hex(node)))
297 ui.write("%s%s\n" % (indent_string, hex(node)))
298
298
299 def _debugobsmarkers(ui, part, indent=0, **opts):
299 def _debugobsmarkers(ui, part, indent=0, **opts):
300 """display version and markers contained in 'data'"""
300 """display version and markers contained in 'data'"""
301 opts = pycompat.byteskwargs(opts)
301 opts = pycompat.byteskwargs(opts)
302 data = part.read()
302 data = part.read()
303 indent_string = ' ' * indent
303 indent_string = ' ' * indent
304 try:
304 try:
305 version, markers = obsolete._readmarkers(data)
305 version, markers = obsolete._readmarkers(data)
306 except error.UnknownVersion as exc:
306 except error.UnknownVersion as exc:
307 msg = "%sunsupported version: %s (%d bytes)\n"
307 msg = "%sunsupported version: %s (%d bytes)\n"
308 msg %= indent_string, exc.version, len(data)
308 msg %= indent_string, exc.version, len(data)
309 ui.write(msg)
309 ui.write(msg)
310 else:
310 else:
311 msg = "%sversion: %d (%d bytes)\n"
311 msg = "%sversion: %d (%d bytes)\n"
312 msg %= indent_string, version, len(data)
312 msg %= indent_string, version, len(data)
313 ui.write(msg)
313 ui.write(msg)
314 fm = ui.formatter('debugobsolete', opts)
314 fm = ui.formatter('debugobsolete', opts)
315 for rawmarker in sorted(markers):
315 for rawmarker in sorted(markers):
316 m = obsutil.marker(None, rawmarker)
316 m = obsutil.marker(None, rawmarker)
317 fm.startitem()
317 fm.startitem()
318 fm.plain(indent_string)
318 fm.plain(indent_string)
319 cmdutil.showmarker(fm, m)
319 cmdutil.showmarker(fm, m)
320 fm.end()
320 fm.end()
321
321
322 def _debugphaseheads(ui, data, indent=0):
322 def _debugphaseheads(ui, data, indent=0):
323 """display version and markers contained in 'data'"""
323 """display version and markers contained in 'data'"""
324 indent_string = ' ' * indent
324 indent_string = ' ' * indent
325 headsbyphase = phases.binarydecode(data)
325 headsbyphase = phases.binarydecode(data)
326 for phase in phases.allphases:
326 for phase in phases.allphases:
327 for head in headsbyphase[phase]:
327 for head in headsbyphase[phase]:
328 ui.write(indent_string)
328 ui.write(indent_string)
329 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
329 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
330
330
331 def _quasirepr(thing):
331 def _quasirepr(thing):
332 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
332 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
333 return '{%s}' % (
333 return '{%s}' % (
334 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
334 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
335 return pycompat.bytestr(repr(thing))
335 return pycompat.bytestr(repr(thing))
336
336
337 def _debugbundle2(ui, gen, all=None, **opts):
337 def _debugbundle2(ui, gen, all=None, **opts):
338 """lists the contents of a bundle2"""
338 """lists the contents of a bundle2"""
339 if not isinstance(gen, bundle2.unbundle20):
339 if not isinstance(gen, bundle2.unbundle20):
340 raise error.Abort(_('not a bundle2 file'))
340 raise error.Abort(_('not a bundle2 file'))
341 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
341 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
342 parttypes = opts.get(r'part_type', [])
342 parttypes = opts.get(r'part_type', [])
343 for part in gen.iterparts():
343 for part in gen.iterparts():
344 if parttypes and part.type not in parttypes:
344 if parttypes and part.type not in parttypes:
345 continue
345 continue
346 ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
346 ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
347 if part.type == 'changegroup':
347 if part.type == 'changegroup':
348 version = part.params.get('version', '01')
348 version = part.params.get('version', '01')
349 cg = changegroup.getunbundler(version, part, 'UN')
349 cg = changegroup.getunbundler(version, part, 'UN')
350 if not ui.quiet:
350 if not ui.quiet:
351 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
351 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
352 if part.type == 'obsmarkers':
352 if part.type == 'obsmarkers':
353 if not ui.quiet:
353 if not ui.quiet:
354 _debugobsmarkers(ui, part, indent=4, **opts)
354 _debugobsmarkers(ui, part, indent=4, **opts)
355 if part.type == 'phase-heads':
355 if part.type == 'phase-heads':
356 if not ui.quiet:
356 if not ui.quiet:
357 _debugphaseheads(ui, part, indent=4)
357 _debugphaseheads(ui, part, indent=4)
358
358
359 @command('debugbundle',
359 @command('debugbundle',
360 [('a', 'all', None, _('show all details')),
360 [('a', 'all', None, _('show all details')),
361 ('', 'part-type', [], _('show only the named part type')),
361 ('', 'part-type', [], _('show only the named part type')),
362 ('', 'spec', None, _('print the bundlespec of the bundle'))],
362 ('', 'spec', None, _('print the bundlespec of the bundle'))],
363 _('FILE'),
363 _('FILE'),
364 norepo=True)
364 norepo=True)
365 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
365 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
366 """lists the contents of a bundle"""
366 """lists the contents of a bundle"""
367 with hg.openpath(ui, bundlepath) as f:
367 with hg.openpath(ui, bundlepath) as f:
368 if spec:
368 if spec:
369 spec = exchange.getbundlespec(ui, f)
369 spec = exchange.getbundlespec(ui, f)
370 ui.write('%s\n' % spec)
370 ui.write('%s\n' % spec)
371 return
371 return
372
372
373 gen = exchange.readbundle(ui, f, bundlepath)
373 gen = exchange.readbundle(ui, f, bundlepath)
374 if isinstance(gen, bundle2.unbundle20):
374 if isinstance(gen, bundle2.unbundle20):
375 return _debugbundle2(ui, gen, all=all, **opts)
375 return _debugbundle2(ui, gen, all=all, **opts)
376 _debugchangegroup(ui, gen, all=all, **opts)
376 _debugchangegroup(ui, gen, all=all, **opts)
377
377
378 @command('debugcapabilities',
378 @command('debugcapabilities',
379 [], _('PATH'),
379 [], _('PATH'),
380 norepo=True)
380 norepo=True)
381 def debugcapabilities(ui, path, **opts):
381 def debugcapabilities(ui, path, **opts):
382 """lists the capabilities of a remote peer"""
382 """lists the capabilities of a remote peer"""
383 opts = pycompat.byteskwargs(opts)
383 opts = pycompat.byteskwargs(opts)
384 peer = hg.peer(ui, opts, path)
384 peer = hg.peer(ui, opts, path)
385 caps = peer.capabilities()
385 caps = peer.capabilities()
386 ui.write(('Main capabilities:\n'))
386 ui.write(('Main capabilities:\n'))
387 for c in sorted(caps):
387 for c in sorted(caps):
388 ui.write((' %s\n') % c)
388 ui.write((' %s\n') % c)
389 b2caps = bundle2.bundle2caps(peer)
389 b2caps = bundle2.bundle2caps(peer)
390 if b2caps:
390 if b2caps:
391 ui.write(('Bundle2 capabilities:\n'))
391 ui.write(('Bundle2 capabilities:\n'))
392 for key, values in sorted(b2caps.iteritems()):
392 for key, values in sorted(b2caps.iteritems()):
393 ui.write((' %s\n') % key)
393 ui.write((' %s\n') % key)
394 for v in values:
394 for v in values:
395 ui.write((' %s\n') % v)
395 ui.write((' %s\n') % v)
396
396
397 @command('debugcheckstate', [], '')
397 @command('debugcheckstate', [], '')
398 def debugcheckstate(ui, repo):
398 def debugcheckstate(ui, repo):
399 """validate the correctness of the current dirstate"""
399 """validate the correctness of the current dirstate"""
400 parent1, parent2 = repo.dirstate.parents()
400 parent1, parent2 = repo.dirstate.parents()
401 m1 = repo[parent1].manifest()
401 m1 = repo[parent1].manifest()
402 m2 = repo[parent2].manifest()
402 m2 = repo[parent2].manifest()
403 errors = 0
403 errors = 0
404 for f in repo.dirstate:
404 for f in repo.dirstate:
405 state = repo.dirstate[f]
405 state = repo.dirstate[f]
406 if state in "nr" and f not in m1:
406 if state in "nr" and f not in m1:
407 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
407 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
408 errors += 1
408 errors += 1
409 if state in "a" and f in m1:
409 if state in "a" and f in m1:
410 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
410 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
411 errors += 1
411 errors += 1
412 if state in "m" and f not in m1 and f not in m2:
412 if state in "m" and f not in m1 and f not in m2:
413 ui.warn(_("%s in state %s, but not in either manifest\n") %
413 ui.warn(_("%s in state %s, but not in either manifest\n") %
414 (f, state))
414 (f, state))
415 errors += 1
415 errors += 1
416 for f in m1:
416 for f in m1:
417 state = repo.dirstate[f]
417 state = repo.dirstate[f]
418 if state not in "nrm":
418 if state not in "nrm":
419 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
419 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
420 errors += 1
420 errors += 1
421 if errors:
421 if errors:
422 error = _(".hg/dirstate inconsistent with current parent's manifest")
422 error = _(".hg/dirstate inconsistent with current parent's manifest")
423 raise error.Abort(error)
423 raise error.Abort(error)
424
424
425 @command('debugcolor',
425 @command('debugcolor',
426 [('', 'style', None, _('show all configured styles'))],
426 [('', 'style', None, _('show all configured styles'))],
427 'hg debugcolor')
427 'hg debugcolor')
428 def debugcolor(ui, repo, **opts):
428 def debugcolor(ui, repo, **opts):
429 """show available color, effects or style"""
429 """show available color, effects or style"""
430 ui.write(('color mode: %s\n') % ui._colormode)
430 ui.write(('color mode: %s\n') % ui._colormode)
431 if opts.get(r'style'):
431 if opts.get(r'style'):
432 return _debugdisplaystyle(ui)
432 return _debugdisplaystyle(ui)
433 else:
433 else:
434 return _debugdisplaycolor(ui)
434 return _debugdisplaycolor(ui)
435
435
436 def _debugdisplaycolor(ui):
436 def _debugdisplaycolor(ui):
437 ui = ui.copy()
437 ui = ui.copy()
438 ui._styles.clear()
438 ui._styles.clear()
439 for effect in color._activeeffects(ui).keys():
439 for effect in color._activeeffects(ui).keys():
440 ui._styles[effect] = effect
440 ui._styles[effect] = effect
441 if ui._terminfoparams:
441 if ui._terminfoparams:
442 for k, v in ui.configitems('color'):
442 for k, v in ui.configitems('color'):
443 if k.startswith('color.'):
443 if k.startswith('color.'):
444 ui._styles[k] = k[6:]
444 ui._styles[k] = k[6:]
445 elif k.startswith('terminfo.'):
445 elif k.startswith('terminfo.'):
446 ui._styles[k] = k[9:]
446 ui._styles[k] = k[9:]
447 ui.write(_('available colors:\n'))
447 ui.write(_('available colors:\n'))
448 # sort label with a '_' after the other to group '_background' entry.
448 # sort label with a '_' after the other to group '_background' entry.
449 items = sorted(ui._styles.items(),
449 items = sorted(ui._styles.items(),
450 key=lambda i: ('_' in i[0], i[0], i[1]))
450 key=lambda i: ('_' in i[0], i[0], i[1]))
451 for colorname, label in items:
451 for colorname, label in items:
452 ui.write(('%s\n') % colorname, label=label)
452 ui.write(('%s\n') % colorname, label=label)
453
453
454 def _debugdisplaystyle(ui):
454 def _debugdisplaystyle(ui):
455 ui.write(_('available style:\n'))
455 ui.write(_('available style:\n'))
456 width = max(len(s) for s in ui._styles)
456 width = max(len(s) for s in ui._styles)
457 for label, effects in sorted(ui._styles.items()):
457 for label, effects in sorted(ui._styles.items()):
458 ui.write('%s' % label, label=label)
458 ui.write('%s' % label, label=label)
459 if effects:
459 if effects:
460 # 50
460 # 50
461 ui.write(': ')
461 ui.write(': ')
462 ui.write(' ' * (max(0, width - len(label))))
462 ui.write(' ' * (max(0, width - len(label))))
463 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
463 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
464 ui.write('\n')
464 ui.write('\n')
465
465
466 @command('debugcreatestreamclonebundle', [], 'FILE')
466 @command('debugcreatestreamclonebundle', [], 'FILE')
467 def debugcreatestreamclonebundle(ui, repo, fname):
467 def debugcreatestreamclonebundle(ui, repo, fname):
468 """create a stream clone bundle file
468 """create a stream clone bundle file
469
469
470 Stream bundles are special bundles that are essentially archives of
470 Stream bundles are special bundles that are essentially archives of
471 revlog files. They are commonly used for cloning very quickly.
471 revlog files. They are commonly used for cloning very quickly.
472 """
472 """
473 # TODO we may want to turn this into an abort when this functionality
473 # TODO we may want to turn this into an abort when this functionality
474 # is moved into `hg bundle`.
474 # is moved into `hg bundle`.
475 if phases.hassecret(repo):
475 if phases.hassecret(repo):
476 ui.warn(_('(warning: stream clone bundle will contain secret '
476 ui.warn(_('(warning: stream clone bundle will contain secret '
477 'revisions)\n'))
477 'revisions)\n'))
478
478
479 requirements, gen = streamclone.generatebundlev1(repo)
479 requirements, gen = streamclone.generatebundlev1(repo)
480 changegroup.writechunks(ui, gen, fname)
480 changegroup.writechunks(ui, gen, fname)
481
481
482 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
482 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
483
483
484 @command('debugdag',
484 @command('debugdag',
485 [('t', 'tags', None, _('use tags as labels')),
485 [('t', 'tags', None, _('use tags as labels')),
486 ('b', 'branches', None, _('annotate with branch names')),
486 ('b', 'branches', None, _('annotate with branch names')),
487 ('', 'dots', None, _('use dots for runs')),
487 ('', 'dots', None, _('use dots for runs')),
488 ('s', 'spaces', None, _('separate elements by spaces'))],
488 ('s', 'spaces', None, _('separate elements by spaces'))],
489 _('[OPTION]... [FILE [REV]...]'),
489 _('[OPTION]... [FILE [REV]...]'),
490 optionalrepo=True)
490 optionalrepo=True)
491 def debugdag(ui, repo, file_=None, *revs, **opts):
491 def debugdag(ui, repo, file_=None, *revs, **opts):
492 """format the changelog or an index DAG as a concise textual description
492 """format the changelog or an index DAG as a concise textual description
493
493
494 If you pass a revlog index, the revlog's DAG is emitted. If you list
494 If you pass a revlog index, the revlog's DAG is emitted. If you list
495 revision numbers, they get labeled in the output as rN.
495 revision numbers, they get labeled in the output as rN.
496
496
497 Otherwise, the changelog DAG of the current repo is emitted.
497 Otherwise, the changelog DAG of the current repo is emitted.
498 """
498 """
499 spaces = opts.get(r'spaces')
499 spaces = opts.get(r'spaces')
500 dots = opts.get(r'dots')
500 dots = opts.get(r'dots')
501 if file_:
501 if file_:
502 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
502 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
503 file_)
503 file_)
504 revs = set((int(r) for r in revs))
504 revs = set((int(r) for r in revs))
505 def events():
505 def events():
506 for r in rlog:
506 for r in rlog:
507 yield 'n', (r, list(p for p in rlog.parentrevs(r)
507 yield 'n', (r, list(p for p in rlog.parentrevs(r)
508 if p != -1))
508 if p != -1))
509 if r in revs:
509 if r in revs:
510 yield 'l', (r, "r%i" % r)
510 yield 'l', (r, "r%i" % r)
511 elif repo:
511 elif repo:
512 cl = repo.changelog
512 cl = repo.changelog
513 tags = opts.get(r'tags')
513 tags = opts.get(r'tags')
514 branches = opts.get(r'branches')
514 branches = opts.get(r'branches')
515 if tags:
515 if tags:
516 labels = {}
516 labels = {}
517 for l, n in repo.tags().items():
517 for l, n in repo.tags().items():
518 labels.setdefault(cl.rev(n), []).append(l)
518 labels.setdefault(cl.rev(n), []).append(l)
519 def events():
519 def events():
520 b = "default"
520 b = "default"
521 for r in cl:
521 for r in cl:
522 if branches:
522 if branches:
523 newb = cl.read(cl.node(r))[5]['branch']
523 newb = cl.read(cl.node(r))[5]['branch']
524 if newb != b:
524 if newb != b:
525 yield 'a', newb
525 yield 'a', newb
526 b = newb
526 b = newb
527 yield 'n', (r, list(p for p in cl.parentrevs(r)
527 yield 'n', (r, list(p for p in cl.parentrevs(r)
528 if p != -1))
528 if p != -1))
529 if tags:
529 if tags:
530 ls = labels.get(r)
530 ls = labels.get(r)
531 if ls:
531 if ls:
532 for l in ls:
532 for l in ls:
533 yield 'l', (r, l)
533 yield 'l', (r, l)
534 else:
534 else:
535 raise error.Abort(_('need repo for changelog dag'))
535 raise error.Abort(_('need repo for changelog dag'))
536
536
537 for line in dagparser.dagtextlines(events(),
537 for line in dagparser.dagtextlines(events(),
538 addspaces=spaces,
538 addspaces=spaces,
539 wraplabels=True,
539 wraplabels=True,
540 wrapannotations=True,
540 wrapannotations=True,
541 wrapnonlinear=dots,
541 wrapnonlinear=dots,
542 usedots=dots,
542 usedots=dots,
543 maxlinewidth=70):
543 maxlinewidth=70):
544 ui.write(line)
544 ui.write(line)
545 ui.write("\n")
545 ui.write("\n")
546
546
547 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
547 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
548 def debugdata(ui, repo, file_, rev=None, **opts):
548 def debugdata(ui, repo, file_, rev=None, **opts):
549 """dump the contents of a data file revision"""
549 """dump the contents of a data file revision"""
550 opts = pycompat.byteskwargs(opts)
550 opts = pycompat.byteskwargs(opts)
551 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
551 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
552 if rev is not None:
552 if rev is not None:
553 raise error.CommandError('debugdata', _('invalid arguments'))
553 raise error.CommandError('debugdata', _('invalid arguments'))
554 file_, rev = None, file_
554 file_, rev = None, file_
555 elif rev is None:
555 elif rev is None:
556 raise error.CommandError('debugdata', _('invalid arguments'))
556 raise error.CommandError('debugdata', _('invalid arguments'))
557 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
557 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
558 try:
558 try:
559 ui.write(r.revision(r.lookup(rev), raw=True))
559 ui.write(r.revision(r.lookup(rev), raw=True))
560 except KeyError:
560 except KeyError:
561 raise error.Abort(_('invalid revision identifier %s') % rev)
561 raise error.Abort(_('invalid revision identifier %s') % rev)
562
562
563 @command('debugdate',
563 @command('debugdate',
564 [('e', 'extended', None, _('try extended date formats'))],
564 [('e', 'extended', None, _('try extended date formats'))],
565 _('[-e] DATE [RANGE]'),
565 _('[-e] DATE [RANGE]'),
566 norepo=True, optionalrepo=True)
566 norepo=True, optionalrepo=True)
567 def debugdate(ui, date, range=None, **opts):
567 def debugdate(ui, date, range=None, **opts):
568 """parse and display a date"""
568 """parse and display a date"""
569 if opts[r"extended"]:
569 if opts[r"extended"]:
570 d = dateutil.parsedate(date, util.extendeddateformats)
570 d = dateutil.parsedate(date, util.extendeddateformats)
571 else:
571 else:
572 d = dateutil.parsedate(date)
572 d = dateutil.parsedate(date)
573 ui.write(("internal: %d %d\n") % d)
573 ui.write(("internal: %d %d\n") % d)
574 ui.write(("standard: %s\n") % dateutil.datestr(d))
574 ui.write(("standard: %s\n") % dateutil.datestr(d))
575 if range:
575 if range:
576 m = dateutil.matchdate(range)
576 m = dateutil.matchdate(range)
577 ui.write(("match: %s\n") % m(d[0]))
577 ui.write(("match: %s\n") % m(d[0]))
578
578
579 @command('debugdeltachain',
579 @command('debugdeltachain',
580 cmdutil.debugrevlogopts + cmdutil.formatteropts,
580 cmdutil.debugrevlogopts + cmdutil.formatteropts,
581 _('-c|-m|FILE'),
581 _('-c|-m|FILE'),
582 optionalrepo=True)
582 optionalrepo=True)
583 def debugdeltachain(ui, repo, file_=None, **opts):
583 def debugdeltachain(ui, repo, file_=None, **opts):
584 """dump information about delta chains in a revlog
584 """dump information about delta chains in a revlog
585
585
586 Output can be templatized. Available template keywords are:
586 Output can be templatized. Available template keywords are:
587
587
588 :``rev``: revision number
588 :``rev``: revision number
589 :``chainid``: delta chain identifier (numbered by unique base)
589 :``chainid``: delta chain identifier (numbered by unique base)
590 :``chainlen``: delta chain length to this revision
590 :``chainlen``: delta chain length to this revision
591 :``prevrev``: previous revision in delta chain
591 :``prevrev``: previous revision in delta chain
592 :``deltatype``: role of delta / how it was computed
592 :``deltatype``: role of delta / how it was computed
593 :``compsize``: compressed size of revision
593 :``compsize``: compressed size of revision
594 :``uncompsize``: uncompressed size of revision
594 :``uncompsize``: uncompressed size of revision
595 :``chainsize``: total size of compressed revisions in chain
595 :``chainsize``: total size of compressed revisions in chain
596 :``chainratio``: total chain size divided by uncompressed revision size
596 :``chainratio``: total chain size divided by uncompressed revision size
597 (new delta chains typically start at ratio 2.00)
597 (new delta chains typically start at ratio 2.00)
598 :``lindist``: linear distance from base revision in delta chain to end
598 :``lindist``: linear distance from base revision in delta chain to end
599 of this revision
599 of this revision
600 :``extradist``: total size of revisions not part of this delta chain from
600 :``extradist``: total size of revisions not part of this delta chain from
601 base of delta chain to end of this revision; a measurement
601 base of delta chain to end of this revision; a measurement
602 of how much extra data we need to read/seek across to read
602 of how much extra data we need to read/seek across to read
603 the delta chain for this revision
603 the delta chain for this revision
604 :``extraratio``: extradist divided by chainsize; another representation of
604 :``extraratio``: extradist divided by chainsize; another representation of
605 how much unrelated data is needed to load this delta chain
605 how much unrelated data is needed to load this delta chain
606
606
607 If the repository is configured to use the sparse read, additional keywords
607 If the repository is configured to use the sparse read, additional keywords
608 are available:
608 are available:
609
609
610 :``readsize``: total size of data read from the disk for a revision
610 :``readsize``: total size of data read from the disk for a revision
611 (sum of the sizes of all the blocks)
611 (sum of the sizes of all the blocks)
612 :``largestblock``: size of the largest block of data read from the disk
612 :``largestblock``: size of the largest block of data read from the disk
613 :``readdensity``: density of useful bytes in the data read from the disk
613 :``readdensity``: density of useful bytes in the data read from the disk
614 :``srchunks``: in how many data hunks the whole revision would be read
614 :``srchunks``: in how many data hunks the whole revision would be read
615
615
616 The sparse read can be enabled with experimental.sparse-read = True
616 The sparse read can be enabled with experimental.sparse-read = True
617 """
617 """
618 opts = pycompat.byteskwargs(opts)
618 opts = pycompat.byteskwargs(opts)
619 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
619 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
620 index = r.index
620 index = r.index
621 generaldelta = r.version & revlog.FLAG_GENERALDELTA
621 generaldelta = r.version & revlog.FLAG_GENERALDELTA
622 withsparseread = getattr(r, '_withsparseread', False)
622 withsparseread = getattr(r, '_withsparseread', False)
623
623
624 def revinfo(rev):
624 def revinfo(rev):
625 e = index[rev]
625 e = index[rev]
626 compsize = e[1]
626 compsize = e[1]
627 uncompsize = e[2]
627 uncompsize = e[2]
628 chainsize = 0
628 chainsize = 0
629
629
630 if generaldelta:
630 if generaldelta:
631 if e[3] == e[5]:
631 if e[3] == e[5]:
632 deltatype = 'p1'
632 deltatype = 'p1'
633 elif e[3] == e[6]:
633 elif e[3] == e[6]:
634 deltatype = 'p2'
634 deltatype = 'p2'
635 elif e[3] == rev - 1:
635 elif e[3] == rev - 1:
636 deltatype = 'prev'
636 deltatype = 'prev'
637 elif e[3] == rev:
637 elif e[3] == rev:
638 deltatype = 'base'
638 deltatype = 'base'
639 else:
639 else:
640 deltatype = 'other'
640 deltatype = 'other'
641 else:
641 else:
642 if e[3] == rev:
642 if e[3] == rev:
643 deltatype = 'base'
643 deltatype = 'base'
644 else:
644 else:
645 deltatype = 'prev'
645 deltatype = 'prev'
646
646
647 chain = r._deltachain(rev)[0]
647 chain = r._deltachain(rev)[0]
648 for iterrev in chain:
648 for iterrev in chain:
649 e = index[iterrev]
649 e = index[iterrev]
650 chainsize += e[1]
650 chainsize += e[1]
651
651
652 return compsize, uncompsize, deltatype, chain, chainsize
652 return compsize, uncompsize, deltatype, chain, chainsize
653
653
654 fm = ui.formatter('debugdeltachain', opts)
654 fm = ui.formatter('debugdeltachain', opts)
655
655
656 fm.plain(' rev chain# chainlen prev delta '
656 fm.plain(' rev chain# chainlen prev delta '
657 'size rawsize chainsize ratio lindist extradist '
657 'size rawsize chainsize ratio lindist extradist '
658 'extraratio')
658 'extraratio')
659 if withsparseread:
659 if withsparseread:
660 fm.plain(' readsize largestblk rddensity srchunks')
660 fm.plain(' readsize largestblk rddensity srchunks')
661 fm.plain('\n')
661 fm.plain('\n')
662
662
663 chainbases = {}
663 chainbases = {}
664 for rev in r:
664 for rev in r:
665 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
665 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
666 chainbase = chain[0]
666 chainbase = chain[0]
667 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
667 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
668 start = r.start
668 start = r.start
669 length = r.length
669 length = r.length
670 basestart = start(chainbase)
670 basestart = start(chainbase)
671 revstart = start(rev)
671 revstart = start(rev)
672 lineardist = revstart + comp - basestart
672 lineardist = revstart + comp - basestart
673 extradist = lineardist - chainsize
673 extradist = lineardist - chainsize
674 try:
674 try:
675 prevrev = chain[-2]
675 prevrev = chain[-2]
676 except IndexError:
676 except IndexError:
677 prevrev = -1
677 prevrev = -1
678
678
679 chainratio = float(chainsize) / float(uncomp)
679 chainratio = float(chainsize) / float(uncomp)
680 extraratio = float(extradist) / float(chainsize)
680 extraratio = float(extradist) / float(chainsize)
681
681
682 fm.startitem()
682 fm.startitem()
683 fm.write('rev chainid chainlen prevrev deltatype compsize '
683 fm.write('rev chainid chainlen prevrev deltatype compsize '
684 'uncompsize chainsize chainratio lindist extradist '
684 'uncompsize chainsize chainratio lindist extradist '
685 'extraratio',
685 'extraratio',
686 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
686 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
687 rev, chainid, len(chain), prevrev, deltatype, comp,
687 rev, chainid, len(chain), prevrev, deltatype, comp,
688 uncomp, chainsize, chainratio, lineardist, extradist,
688 uncomp, chainsize, chainratio, lineardist, extradist,
689 extraratio,
689 extraratio,
690 rev=rev, chainid=chainid, chainlen=len(chain),
690 rev=rev, chainid=chainid, chainlen=len(chain),
691 prevrev=prevrev, deltatype=deltatype, compsize=comp,
691 prevrev=prevrev, deltatype=deltatype, compsize=comp,
692 uncompsize=uncomp, chainsize=chainsize,
692 uncompsize=uncomp, chainsize=chainsize,
693 chainratio=chainratio, lindist=lineardist,
693 chainratio=chainratio, lindist=lineardist,
694 extradist=extradist, extraratio=extraratio)
694 extradist=extradist, extraratio=extraratio)
695 if withsparseread:
695 if withsparseread:
696 readsize = 0
696 readsize = 0
697 largestblock = 0
697 largestblock = 0
698 srchunks = 0
698 srchunks = 0
699
699
700 for revschunk in revlog._slicechunk(r, chain):
700 for revschunk in revlog._slicechunk(r, chain):
701 srchunks += 1
701 srchunks += 1
702 blkend = start(revschunk[-1]) + length(revschunk[-1])
702 blkend = start(revschunk[-1]) + length(revschunk[-1])
703 blksize = blkend - start(revschunk[0])
703 blksize = blkend - start(revschunk[0])
704
704
705 readsize += blksize
705 readsize += blksize
706 if largestblock < blksize:
706 if largestblock < blksize:
707 largestblock = blksize
707 largestblock = blksize
708
708
709 readdensity = float(chainsize) / float(readsize)
709 readdensity = float(chainsize) / float(readsize)
710
710
711 fm.write('readsize largestblock readdensity srchunks',
711 fm.write('readsize largestblock readdensity srchunks',
712 ' %10d %10d %9.5f %8d',
712 ' %10d %10d %9.5f %8d',
713 readsize, largestblock, readdensity, srchunks,
713 readsize, largestblock, readdensity, srchunks,
714 readsize=readsize, largestblock=largestblock,
714 readsize=readsize, largestblock=largestblock,
715 readdensity=readdensity, srchunks=srchunks)
715 readdensity=readdensity, srchunks=srchunks)
716
716
717 fm.plain('\n')
717 fm.plain('\n')
718
718
719 fm.end()
719 fm.end()
720
720
721 @command('debugdirstate|debugstate',
721 @command('debugdirstate|debugstate',
722 [('', 'nodates', None, _('do not display the saved mtime')),
722 [('', 'nodates', None, _('do not display the saved mtime')),
723 ('', 'datesort', None, _('sort by saved mtime'))],
723 ('', 'datesort', None, _('sort by saved mtime'))],
724 _('[OPTION]...'))
724 _('[OPTION]...'))
725 def debugstate(ui, repo, **opts):
725 def debugstate(ui, repo, **opts):
726 """show the contents of the current dirstate"""
726 """show the contents of the current dirstate"""
727
727
728 nodates = opts.get(r'nodates')
728 nodates = opts.get(r'nodates')
729 datesort = opts.get(r'datesort')
729 datesort = opts.get(r'datesort')
730
730
731 timestr = ""
731 timestr = ""
732 if datesort:
732 if datesort:
733 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
733 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
734 else:
734 else:
735 keyfunc = None # sort by filename
735 keyfunc = None # sort by filename
736 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
736 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
737 if ent[3] == -1:
737 if ent[3] == -1:
738 timestr = 'unset '
738 timestr = 'unset '
739 elif nodates:
739 elif nodates:
740 timestr = 'set '
740 timestr = 'set '
741 else:
741 else:
742 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
742 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
743 time.localtime(ent[3]))
743 time.localtime(ent[3]))
744 timestr = encoding.strtolocal(timestr)
744 timestr = encoding.strtolocal(timestr)
745 if ent[1] & 0o20000:
745 if ent[1] & 0o20000:
746 mode = 'lnk'
746 mode = 'lnk'
747 else:
747 else:
748 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
748 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
749 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
749 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
750 for f in repo.dirstate.copies():
750 for f in repo.dirstate.copies():
751 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
751 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
752
752
753 @command('debugdiscovery',
753 @command('debugdiscovery',
754 [('', 'old', None, _('use old-style discovery')),
754 [('', 'old', None, _('use old-style discovery')),
755 ('', 'nonheads', None,
755 ('', 'nonheads', None,
756 _('use old-style discovery with non-heads included')),
756 _('use old-style discovery with non-heads included')),
757 ('', 'rev', [], 'restrict discovery to this set of revs'),
757 ('', 'rev', [], 'restrict discovery to this set of revs'),
758 ] + cmdutil.remoteopts,
758 ] + cmdutil.remoteopts,
759 _('[--rev REV] [OTHER]'))
759 _('[--rev REV] [OTHER]'))
760 def debugdiscovery(ui, repo, remoteurl="default", **opts):
760 def debugdiscovery(ui, repo, remoteurl="default", **opts):
761 """runs the changeset discovery protocol in isolation"""
761 """runs the changeset discovery protocol in isolation"""
762 opts = pycompat.byteskwargs(opts)
762 opts = pycompat.byteskwargs(opts)
763 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
763 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
764 remote = hg.peer(repo, opts, remoteurl)
764 remote = hg.peer(repo, opts, remoteurl)
765 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
765 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
766
766
767 # make sure tests are repeatable
767 # make sure tests are repeatable
768 random.seed(12323)
768 random.seed(12323)
769
769
770 def doit(pushedrevs, remoteheads, remote=remote):
770 def doit(pushedrevs, remoteheads, remote=remote):
771 if opts.get('old'):
771 if opts.get('old'):
772 if not util.safehasattr(remote, 'branches'):
772 if not util.safehasattr(remote, 'branches'):
773 # enable in-client legacy support
773 # enable in-client legacy support
774 remote = localrepo.locallegacypeer(remote.local())
774 remote = localrepo.locallegacypeer(remote.local())
775 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
775 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
776 force=True)
776 force=True)
777 common = set(common)
777 common = set(common)
778 if not opts.get('nonheads'):
778 if not opts.get('nonheads'):
779 ui.write(("unpruned common: %s\n") %
779 ui.write(("unpruned common: %s\n") %
780 " ".join(sorted(short(n) for n in common)))
780 " ".join(sorted(short(n) for n in common)))
781 dag = dagutil.revlogdag(repo.changelog)
781 dag = dagutil.revlogdag(repo.changelog)
782 all = dag.ancestorset(dag.internalizeall(common))
782 all = dag.ancestorset(dag.internalizeall(common))
783 common = dag.externalizeall(dag.headsetofconnecteds(all))
783 common = dag.externalizeall(dag.headsetofconnecteds(all))
784 else:
784 else:
785 nodes = None
785 nodes = None
786 if pushedrevs:
786 if pushedrevs:
787 revs = scmutil.revrange(repo, pushedrevs)
787 revs = scmutil.revrange(repo, pushedrevs)
788 nodes = [repo[r].node() for r in revs]
788 nodes = [repo[r].node() for r in revs]
789 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
789 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
790 ancestorsof=nodes)
790 ancestorsof=nodes)
791 common = set(common)
791 common = set(common)
792 rheads = set(hds)
792 rheads = set(hds)
793 lheads = set(repo.heads())
793 lheads = set(repo.heads())
794 ui.write(("common heads: %s\n") %
794 ui.write(("common heads: %s\n") %
795 " ".join(sorted(short(n) for n in common)))
795 " ".join(sorted(short(n) for n in common)))
796 if lheads <= common:
796 if lheads <= common:
797 ui.write(("local is subset\n"))
797 ui.write(("local is subset\n"))
798 elif rheads <= common:
798 elif rheads <= common:
799 ui.write(("remote is subset\n"))
799 ui.write(("remote is subset\n"))
800
800
801 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
801 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
802 localrevs = opts['rev']
802 localrevs = opts['rev']
803 doit(localrevs, remoterevs)
803 doit(localrevs, remoterevs)
804
804
805 _chunksize = 4 << 10
805 _chunksize = 4 << 10
806
806
807 @command('debugdownload',
807 @command('debugdownload',
808 [
808 [
809 ('o', 'output', '', _('path')),
809 ('o', 'output', '', _('path')),
810 ],
810 ],
811 optionalrepo=True)
811 optionalrepo=True)
812 def debugdownload(ui, repo, url, output=None, **opts):
812 def debugdownload(ui, repo, url, output=None, **opts):
813 """download a resource using Mercurial logic and config
813 """download a resource using Mercurial logic and config
814 """
814 """
815 fh = urlmod.open(ui, url, output)
815 fh = urlmod.open(ui, url, output)
816
816
817 dest = ui
817 dest = ui
818 if output:
818 if output:
819 dest = open(output, "wb", _chunksize)
819 dest = open(output, "wb", _chunksize)
820 try:
820 try:
821 data = fh.read(_chunksize)
821 data = fh.read(_chunksize)
822 while data:
822 while data:
823 dest.write(data)
823 dest.write(data)
824 data = fh.read(_chunksize)
824 data = fh.read(_chunksize)
825 finally:
825 finally:
826 if output:
826 if output:
827 dest.close()
827 dest.close()
828
828
829 @command('debugextensions', cmdutil.formatteropts, [], norepo=True)
829 @command('debugextensions', cmdutil.formatteropts, [], norepo=True)
830 def debugextensions(ui, **opts):
830 def debugextensions(ui, **opts):
831 '''show information about active extensions'''
831 '''show information about active extensions'''
832 opts = pycompat.byteskwargs(opts)
832 opts = pycompat.byteskwargs(opts)
833 exts = extensions.extensions(ui)
833 exts = extensions.extensions(ui)
834 hgver = util.version()
834 hgver = util.version()
835 fm = ui.formatter('debugextensions', opts)
835 fm = ui.formatter('debugextensions', opts)
836 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
836 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
837 isinternal = extensions.ismoduleinternal(extmod)
837 isinternal = extensions.ismoduleinternal(extmod)
838 extsource = pycompat.fsencode(extmod.__file__)
838 extsource = pycompat.fsencode(extmod.__file__)
839 if isinternal:
839 if isinternal:
840 exttestedwith = [] # never expose magic string to users
840 exttestedwith = [] # never expose magic string to users
841 else:
841 else:
842 exttestedwith = getattr(extmod, 'testedwith', '').split()
842 exttestedwith = getattr(extmod, 'testedwith', '').split()
843 extbuglink = getattr(extmod, 'buglink', None)
843 extbuglink = getattr(extmod, 'buglink', None)
844
844
845 fm.startitem()
845 fm.startitem()
846
846
847 if ui.quiet or ui.verbose:
847 if ui.quiet or ui.verbose:
848 fm.write('name', '%s\n', extname)
848 fm.write('name', '%s\n', extname)
849 else:
849 else:
850 fm.write('name', '%s', extname)
850 fm.write('name', '%s', extname)
851 if isinternal or hgver in exttestedwith:
851 if isinternal or hgver in exttestedwith:
852 fm.plain('\n')
852 fm.plain('\n')
853 elif not exttestedwith:
853 elif not exttestedwith:
854 fm.plain(_(' (untested!)\n'))
854 fm.plain(_(' (untested!)\n'))
855 else:
855 else:
856 lasttestedversion = exttestedwith[-1]
856 lasttestedversion = exttestedwith[-1]
857 fm.plain(' (%s!)\n' % lasttestedversion)
857 fm.plain(' (%s!)\n' % lasttestedversion)
858
858
859 fm.condwrite(ui.verbose and extsource, 'source',
859 fm.condwrite(ui.verbose and extsource, 'source',
860 _(' location: %s\n'), extsource or "")
860 _(' location: %s\n'), extsource or "")
861
861
862 if ui.verbose:
862 if ui.verbose:
863 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
863 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
864 fm.data(bundled=isinternal)
864 fm.data(bundled=isinternal)
865
865
866 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
866 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
867 _(' tested with: %s\n'),
867 _(' tested with: %s\n'),
868 fm.formatlist(exttestedwith, name='ver'))
868 fm.formatlist(exttestedwith, name='ver'))
869
869
870 fm.condwrite(ui.verbose and extbuglink, 'buglink',
870 fm.condwrite(ui.verbose and extbuglink, 'buglink',
871 _(' bug reporting: %s\n'), extbuglink or "")
871 _(' bug reporting: %s\n'), extbuglink or "")
872
872
873 fm.end()
873 fm.end()
874
874
875 @command('debugfileset',
875 @command('debugfileset',
876 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
876 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
877 _('[-r REV] FILESPEC'))
877 _('[-r REV] FILESPEC'))
878 def debugfileset(ui, repo, expr, **opts):
878 def debugfileset(ui, repo, expr, **opts):
879 '''parse and apply a fileset specification'''
879 '''parse and apply a fileset specification'''
880 ctx = scmutil.revsingle(repo, opts.get(r'rev'), None)
880 ctx = scmutil.revsingle(repo, opts.get(r'rev'), None)
881 if ui.verbose:
881 if ui.verbose:
882 tree = fileset.parse(expr)
882 tree = fileset.parse(expr)
883 ui.note(fileset.prettyformat(tree), "\n")
883 ui.note(fileset.prettyformat(tree), "\n")
884
884
885 for f in ctx.getfileset(expr):
885 for f in ctx.getfileset(expr):
886 ui.write("%s\n" % f)
886 ui.write("%s\n" % f)
887
887
888 @command('debugformat',
888 @command('debugformat',
889 [] + cmdutil.formatteropts,
889 [] + cmdutil.formatteropts,
890 _(''))
890 _(''))
891 def debugformat(ui, repo, **opts):
891 def debugformat(ui, repo, **opts):
892 """display format information about the current repository
892 """display format information about the current repository
893
893
894 Use --verbose to get extra information about current config value and
894 Use --verbose to get extra information about current config value and
895 Mercurial default."""
895 Mercurial default."""
896 opts = pycompat.byteskwargs(opts)
896 opts = pycompat.byteskwargs(opts)
897 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
897 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
898 maxvariantlength = max(len('format-variant'), maxvariantlength)
898 maxvariantlength = max(len('format-variant'), maxvariantlength)
899
899
900 def makeformatname(name):
900 def makeformatname(name):
901 return '%s:' + (' ' * (maxvariantlength - len(name)))
901 return '%s:' + (' ' * (maxvariantlength - len(name)))
902
902
903 fm = ui.formatter('debugformat', opts)
903 fm = ui.formatter('debugformat', opts)
904 if fm.isplain():
904 if fm.isplain():
905 def formatvalue(value):
905 def formatvalue(value):
906 if util.safehasattr(value, 'startswith'):
906 if util.safehasattr(value, 'startswith'):
907 return value
907 return value
908 if value:
908 if value:
909 return 'yes'
909 return 'yes'
910 else:
910 else:
911 return 'no'
911 return 'no'
912 else:
912 else:
913 formatvalue = pycompat.identity
913 formatvalue = pycompat.identity
914
914
915 fm.plain('format-variant')
915 fm.plain('format-variant')
916 fm.plain(' ' * (maxvariantlength - len('format-variant')))
916 fm.plain(' ' * (maxvariantlength - len('format-variant')))
917 fm.plain(' repo')
917 fm.plain(' repo')
918 if ui.verbose:
918 if ui.verbose:
919 fm.plain(' config default')
919 fm.plain(' config default')
920 fm.plain('\n')
920 fm.plain('\n')
921 for fv in upgrade.allformatvariant:
921 for fv in upgrade.allformatvariant:
922 fm.startitem()
922 fm.startitem()
923 repovalue = fv.fromrepo(repo)
923 repovalue = fv.fromrepo(repo)
924 configvalue = fv.fromconfig(repo)
924 configvalue = fv.fromconfig(repo)
925
925
926 if repovalue != configvalue:
926 if repovalue != configvalue:
927 namelabel = 'formatvariant.name.mismatchconfig'
927 namelabel = 'formatvariant.name.mismatchconfig'
928 repolabel = 'formatvariant.repo.mismatchconfig'
928 repolabel = 'formatvariant.repo.mismatchconfig'
929 elif repovalue != fv.default:
929 elif repovalue != fv.default:
930 namelabel = 'formatvariant.name.mismatchdefault'
930 namelabel = 'formatvariant.name.mismatchdefault'
931 repolabel = 'formatvariant.repo.mismatchdefault'
931 repolabel = 'formatvariant.repo.mismatchdefault'
932 else:
932 else:
933 namelabel = 'formatvariant.name.uptodate'
933 namelabel = 'formatvariant.name.uptodate'
934 repolabel = 'formatvariant.repo.uptodate'
934 repolabel = 'formatvariant.repo.uptodate'
935
935
936 fm.write('name', makeformatname(fv.name), fv.name,
936 fm.write('name', makeformatname(fv.name), fv.name,
937 label=namelabel)
937 label=namelabel)
938 fm.write('repo', ' %3s', formatvalue(repovalue),
938 fm.write('repo', ' %3s', formatvalue(repovalue),
939 label=repolabel)
939 label=repolabel)
940 if fv.default != configvalue:
940 if fv.default != configvalue:
941 configlabel = 'formatvariant.config.special'
941 configlabel = 'formatvariant.config.special'
942 else:
942 else:
943 configlabel = 'formatvariant.config.default'
943 configlabel = 'formatvariant.config.default'
944 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
944 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
945 label=configlabel)
945 label=configlabel)
946 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
946 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
947 label='formatvariant.default')
947 label='formatvariant.default')
948 fm.plain('\n')
948 fm.plain('\n')
949 fm.end()
949 fm.end()
950
950
951 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
951 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
952 def debugfsinfo(ui, path="."):
952 def debugfsinfo(ui, path="."):
953 """show information detected about current filesystem"""
953 """show information detected about current filesystem"""
954 ui.write(('path: %s\n') % path)
954 ui.write(('path: %s\n') % path)
955 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
955 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
956 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
956 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
957 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
957 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
958 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
958 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
959 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
959 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
960 casesensitive = '(unknown)'
960 casesensitive = '(unknown)'
961 try:
961 try:
962 with tempfile.NamedTemporaryFile(prefix='.debugfsinfo', dir=path) as f:
962 with tempfile.NamedTemporaryFile(prefix='.debugfsinfo', dir=path) as f:
963 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
963 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
964 except OSError:
964 except OSError:
965 pass
965 pass
966 ui.write(('case-sensitive: %s\n') % casesensitive)
966 ui.write(('case-sensitive: %s\n') % casesensitive)
967
967
968 @command('debuggetbundle',
968 @command('debuggetbundle',
969 [('H', 'head', [], _('id of head node'), _('ID')),
969 [('H', 'head', [], _('id of head node'), _('ID')),
970 ('C', 'common', [], _('id of common node'), _('ID')),
970 ('C', 'common', [], _('id of common node'), _('ID')),
971 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
971 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
972 _('REPO FILE [-H|-C ID]...'),
972 _('REPO FILE [-H|-C ID]...'),
973 norepo=True)
973 norepo=True)
974 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
974 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
975 """retrieves a bundle from a repo
975 """retrieves a bundle from a repo
976
976
977 Every ID must be a full-length hex node id string. Saves the bundle to the
977 Every ID must be a full-length hex node id string. Saves the bundle to the
978 given file.
978 given file.
979 """
979 """
980 opts = pycompat.byteskwargs(opts)
980 opts = pycompat.byteskwargs(opts)
981 repo = hg.peer(ui, opts, repopath)
981 repo = hg.peer(ui, opts, repopath)
982 if not repo.capable('getbundle'):
982 if not repo.capable('getbundle'):
983 raise error.Abort("getbundle() not supported by target repository")
983 raise error.Abort("getbundle() not supported by target repository")
984 args = {}
984 args = {}
985 if common:
985 if common:
986 args[r'common'] = [bin(s) for s in common]
986 args[r'common'] = [bin(s) for s in common]
987 if head:
987 if head:
988 args[r'heads'] = [bin(s) for s in head]
988 args[r'heads'] = [bin(s) for s in head]
989 # TODO: get desired bundlecaps from command line.
989 # TODO: get desired bundlecaps from command line.
990 args[r'bundlecaps'] = None
990 args[r'bundlecaps'] = None
991 bundle = repo.getbundle('debug', **args)
991 bundle = repo.getbundle('debug', **args)
992
992
993 bundletype = opts.get('type', 'bzip2').lower()
993 bundletype = opts.get('type', 'bzip2').lower()
994 btypes = {'none': 'HG10UN',
994 btypes = {'none': 'HG10UN',
995 'bzip2': 'HG10BZ',
995 'bzip2': 'HG10BZ',
996 'gzip': 'HG10GZ',
996 'gzip': 'HG10GZ',
997 'bundle2': 'HG20'}
997 'bundle2': 'HG20'}
998 bundletype = btypes.get(bundletype)
998 bundletype = btypes.get(bundletype)
999 if bundletype not in bundle2.bundletypes:
999 if bundletype not in bundle2.bundletypes:
1000 raise error.Abort(_('unknown bundle type specified with --type'))
1000 raise error.Abort(_('unknown bundle type specified with --type'))
1001 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1001 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1002
1002
1003 @command('debugignore', [], '[FILE]')
1003 @command('debugignore', [], '[FILE]')
1004 def debugignore(ui, repo, *files, **opts):
1004 def debugignore(ui, repo, *files, **opts):
1005 """display the combined ignore pattern and information about ignored files
1005 """display the combined ignore pattern and information about ignored files
1006
1006
1007 With no argument display the combined ignore pattern.
1007 With no argument display the combined ignore pattern.
1008
1008
1009 Given space separated file names, shows if the given file is ignored and
1009 Given space separated file names, shows if the given file is ignored and
1010 if so, show the ignore rule (file and line number) that matched it.
1010 if so, show the ignore rule (file and line number) that matched it.
1011 """
1011 """
1012 ignore = repo.dirstate._ignore
1012 ignore = repo.dirstate._ignore
1013 if not files:
1013 if not files:
1014 # Show all the patterns
1014 # Show all the patterns
1015 ui.write("%s\n" % pycompat.byterepr(ignore))
1015 ui.write("%s\n" % pycompat.byterepr(ignore))
1016 else:
1016 else:
1017 m = scmutil.match(repo[None], pats=files)
1017 m = scmutil.match(repo[None], pats=files)
1018 for f in m.files():
1018 for f in m.files():
1019 nf = util.normpath(f)
1019 nf = util.normpath(f)
1020 ignored = None
1020 ignored = None
1021 ignoredata = None
1021 ignoredata = None
1022 if nf != '.':
1022 if nf != '.':
1023 if ignore(nf):
1023 if ignore(nf):
1024 ignored = nf
1024 ignored = nf
1025 ignoredata = repo.dirstate._ignorefileandline(nf)
1025 ignoredata = repo.dirstate._ignorefileandline(nf)
1026 else:
1026 else:
1027 for p in util.finddirs(nf):
1027 for p in util.finddirs(nf):
1028 if ignore(p):
1028 if ignore(p):
1029 ignored = p
1029 ignored = p
1030 ignoredata = repo.dirstate._ignorefileandline(p)
1030 ignoredata = repo.dirstate._ignorefileandline(p)
1031 break
1031 break
1032 if ignored:
1032 if ignored:
1033 if ignored == nf:
1033 if ignored == nf:
1034 ui.write(_("%s is ignored\n") % m.uipath(f))
1034 ui.write(_("%s is ignored\n") % m.uipath(f))
1035 else:
1035 else:
1036 ui.write(_("%s is ignored because of "
1036 ui.write(_("%s is ignored because of "
1037 "containing folder %s\n")
1037 "containing folder %s\n")
1038 % (m.uipath(f), ignored))
1038 % (m.uipath(f), ignored))
1039 ignorefile, lineno, line = ignoredata
1039 ignorefile, lineno, line = ignoredata
1040 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1040 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1041 % (ignorefile, lineno, line))
1041 % (ignorefile, lineno, line))
1042 else:
1042 else:
1043 ui.write(_("%s is not ignored\n") % m.uipath(f))
1043 ui.write(_("%s is not ignored\n") % m.uipath(f))
1044
1044
1045 @command('debugindex', cmdutil.debugrevlogopts +
1045 @command('debugindex', cmdutil.debugrevlogopts +
1046 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1046 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1047 _('[-f FORMAT] -c|-m|FILE'),
1047 _('[-f FORMAT] -c|-m|FILE'),
1048 optionalrepo=True)
1048 optionalrepo=True)
1049 def debugindex(ui, repo, file_=None, **opts):
1049 def debugindex(ui, repo, file_=None, **opts):
1050 """dump the contents of an index file"""
1050 """dump the contents of an index file"""
1051 opts = pycompat.byteskwargs(opts)
1051 opts = pycompat.byteskwargs(opts)
1052 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1052 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1053 format = opts.get('format', 0)
1053 format = opts.get('format', 0)
1054 if format not in (0, 1):
1054 if format not in (0, 1):
1055 raise error.Abort(_("unknown format %d") % format)
1055 raise error.Abort(_("unknown format %d") % format)
1056
1056
1057 generaldelta = r.version & revlog.FLAG_GENERALDELTA
1057 generaldelta = r.version & revlog.FLAG_GENERALDELTA
1058 if generaldelta:
1058 if generaldelta:
1059 basehdr = ' delta'
1059 basehdr = ' delta'
1060 else:
1060 else:
1061 basehdr = ' base'
1061 basehdr = ' base'
1062
1062
1063 if ui.debugflag:
1063 if ui.debugflag:
1064 shortfn = hex
1064 shortfn = hex
1065 else:
1065 else:
1066 shortfn = short
1066 shortfn = short
1067
1067
1068 # There might not be anything in r, so have a sane default
1068 # There might not be anything in r, so have a sane default
1069 idlen = 12
1069 idlen = 12
1070 for i in r:
1070 for i in r:
1071 idlen = len(shortfn(r.node(i)))
1071 idlen = len(shortfn(r.node(i)))
1072 break
1072 break
1073
1073
1074 if format == 0:
1074 if format == 0:
1075 ui.write((" rev offset length " + basehdr + " linkrev"
1075 ui.write((" rev offset length " + basehdr + " linkrev"
1076 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
1076 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
1077 elif format == 1:
1077 elif format == 1:
1078 ui.write((" rev flag offset length"
1078 ui.write((" rev flag offset length"
1079 " size " + basehdr + " link p1 p2"
1079 " size " + basehdr + " link p1 p2"
1080 " %s\n") % "nodeid".rjust(idlen))
1080 " %s\n") % "nodeid".rjust(idlen))
1081
1081
1082 for i in r:
1082 for i in r:
1083 node = r.node(i)
1083 node = r.node(i)
1084 if generaldelta:
1084 if generaldelta:
1085 base = r.deltaparent(i)
1085 base = r.deltaparent(i)
1086 else:
1086 else:
1087 base = r.chainbase(i)
1087 base = r.chainbase(i)
1088 if format == 0:
1088 if format == 0:
1089 try:
1089 try:
1090 pp = r.parents(node)
1090 pp = r.parents(node)
1091 except Exception:
1091 except Exception:
1092 pp = [nullid, nullid]
1092 pp = [nullid, nullid]
1093 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1093 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1094 i, r.start(i), r.length(i), base, r.linkrev(i),
1094 i, r.start(i), r.length(i), base, r.linkrev(i),
1095 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
1095 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
1096 elif format == 1:
1096 elif format == 1:
1097 pr = r.parentrevs(i)
1097 pr = r.parentrevs(i)
1098 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1098 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1099 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1099 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1100 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
1100 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
1101
1101
1102 @command('debugindexdot', cmdutil.debugrevlogopts,
1102 @command('debugindexdot', cmdutil.debugrevlogopts,
1103 _('-c|-m|FILE'), optionalrepo=True)
1103 _('-c|-m|FILE'), optionalrepo=True)
1104 def debugindexdot(ui, repo, file_=None, **opts):
1104 def debugindexdot(ui, repo, file_=None, **opts):
1105 """dump an index DAG as a graphviz dot file"""
1105 """dump an index DAG as a graphviz dot file"""
1106 opts = pycompat.byteskwargs(opts)
1106 opts = pycompat.byteskwargs(opts)
1107 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
1107 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
1108 ui.write(("digraph G {\n"))
1108 ui.write(("digraph G {\n"))
1109 for i in r:
1109 for i in r:
1110 node = r.node(i)
1110 node = r.node(i)
1111 pp = r.parents(node)
1111 pp = r.parents(node)
1112 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1112 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1113 if pp[1] != nullid:
1113 if pp[1] != nullid:
1114 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1114 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1115 ui.write("}\n")
1115 ui.write("}\n")
1116
1116
1117 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1117 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1118 def debuginstall(ui, **opts):
1118 def debuginstall(ui, **opts):
1119 '''test Mercurial installation
1119 '''test Mercurial installation
1120
1120
1121 Returns 0 on success.
1121 Returns 0 on success.
1122 '''
1122 '''
1123 opts = pycompat.byteskwargs(opts)
1123 opts = pycompat.byteskwargs(opts)
1124
1124
1125 def writetemp(contents):
1125 def writetemp(contents):
1126 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1126 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1127 f = os.fdopen(fd, r"wb")
1127 f = os.fdopen(fd, r"wb")
1128 f.write(contents)
1128 f.write(contents)
1129 f.close()
1129 f.close()
1130 return name
1130 return name
1131
1131
1132 problems = 0
1132 problems = 0
1133
1133
1134 fm = ui.formatter('debuginstall', opts)
1134 fm = ui.formatter('debuginstall', opts)
1135 fm.startitem()
1135 fm.startitem()
1136
1136
1137 # encoding
1137 # encoding
1138 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1138 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1139 err = None
1139 err = None
1140 try:
1140 try:
1141 codecs.lookup(pycompat.sysstr(encoding.encoding))
1141 codecs.lookup(pycompat.sysstr(encoding.encoding))
1142 except LookupError as inst:
1142 except LookupError as inst:
1143 err = util.forcebytestr(inst)
1143 err = util.forcebytestr(inst)
1144 problems += 1
1144 problems += 1
1145 fm.condwrite(err, 'encodingerror', _(" %s\n"
1145 fm.condwrite(err, 'encodingerror', _(" %s\n"
1146 " (check that your locale is properly set)\n"), err)
1146 " (check that your locale is properly set)\n"), err)
1147
1147
1148 # Python
1148 # Python
1149 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1149 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1150 pycompat.sysexecutable)
1150 pycompat.sysexecutable)
1151 fm.write('pythonver', _("checking Python version (%s)\n"),
1151 fm.write('pythonver', _("checking Python version (%s)\n"),
1152 ("%d.%d.%d" % sys.version_info[:3]))
1152 ("%d.%d.%d" % sys.version_info[:3]))
1153 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1153 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1154 os.path.dirname(pycompat.fsencode(os.__file__)))
1154 os.path.dirname(pycompat.fsencode(os.__file__)))
1155
1155
1156 security = set(sslutil.supportedprotocols)
1156 security = set(sslutil.supportedprotocols)
1157 if sslutil.hassni:
1157 if sslutil.hassni:
1158 security.add('sni')
1158 security.add('sni')
1159
1159
1160 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1160 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1161 fm.formatlist(sorted(security), name='protocol',
1161 fm.formatlist(sorted(security), name='protocol',
1162 fmt='%s', sep=','))
1162 fmt='%s', sep=','))
1163
1163
1164 # These are warnings, not errors. So don't increment problem count. This
1164 # These are warnings, not errors. So don't increment problem count. This
1165 # may change in the future.
1165 # may change in the future.
1166 if 'tls1.2' not in security:
1166 if 'tls1.2' not in security:
1167 fm.plain(_(' TLS 1.2 not supported by Python install; '
1167 fm.plain(_(' TLS 1.2 not supported by Python install; '
1168 'network connections lack modern security\n'))
1168 'network connections lack modern security\n'))
1169 if 'sni' not in security:
1169 if 'sni' not in security:
1170 fm.plain(_(' SNI not supported by Python install; may have '
1170 fm.plain(_(' SNI not supported by Python install; may have '
1171 'connectivity issues with some servers\n'))
1171 'connectivity issues with some servers\n'))
1172
1172
1173 # TODO print CA cert info
1173 # TODO print CA cert info
1174
1174
1175 # hg version
1175 # hg version
1176 hgver = util.version()
1176 hgver = util.version()
1177 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1177 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1178 hgver.split('+')[0])
1178 hgver.split('+')[0])
1179 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1179 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1180 '+'.join(hgver.split('+')[1:]))
1180 '+'.join(hgver.split('+')[1:]))
1181
1181
1182 # compiled modules
1182 # compiled modules
1183 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1183 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1184 policy.policy)
1184 policy.policy)
1185 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1185 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1186 os.path.dirname(pycompat.fsencode(__file__)))
1186 os.path.dirname(pycompat.fsencode(__file__)))
1187
1187
1188 if policy.policy in ('c', 'allow'):
1188 if policy.policy in ('c', 'allow'):
1189 err = None
1189 err = None
1190 try:
1190 try:
1191 from .cext import (
1191 from .cext import (
1192 base85,
1192 base85,
1193 bdiff,
1193 bdiff,
1194 mpatch,
1194 mpatch,
1195 osutil,
1195 osutil,
1196 )
1196 )
1197 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1197 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1198 except Exception as inst:
1198 except Exception as inst:
1199 err = util.forcebytestr(inst)
1199 err = util.forcebytestr(inst)
1200 problems += 1
1200 problems += 1
1201 fm.condwrite(err, 'extensionserror', " %s\n", err)
1201 fm.condwrite(err, 'extensionserror', " %s\n", err)
1202
1202
1203 compengines = util.compengines._engines.values()
1203 compengines = util.compengines._engines.values()
1204 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1204 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1205 fm.formatlist(sorted(e.name() for e in compengines),
1205 fm.formatlist(sorted(e.name() for e in compengines),
1206 name='compengine', fmt='%s', sep=', '))
1206 name='compengine', fmt='%s', sep=', '))
1207 fm.write('compenginesavail', _('checking available compression engines '
1207 fm.write('compenginesavail', _('checking available compression engines '
1208 '(%s)\n'),
1208 '(%s)\n'),
1209 fm.formatlist(sorted(e.name() for e in compengines
1209 fm.formatlist(sorted(e.name() for e in compengines
1210 if e.available()),
1210 if e.available()),
1211 name='compengine', fmt='%s', sep=', '))
1211 name='compengine', fmt='%s', sep=', '))
1212 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1212 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1213 fm.write('compenginesserver', _('checking available compression engines '
1213 fm.write('compenginesserver', _('checking available compression engines '
1214 'for wire protocol (%s)\n'),
1214 'for wire protocol (%s)\n'),
1215 fm.formatlist([e.name() for e in wirecompengines
1215 fm.formatlist([e.name() for e in wirecompengines
1216 if e.wireprotosupport()],
1216 if e.wireprotosupport()],
1217 name='compengine', fmt='%s', sep=', '))
1217 name='compengine', fmt='%s', sep=', '))
1218 re2 = 'missing'
1218 re2 = 'missing'
1219 if util._re2:
1219 if util._re2:
1220 re2 = 'available'
1220 re2 = 'available'
1221 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1221 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1222 fm.data(re2=bool(util._re2))
1222 fm.data(re2=bool(util._re2))
1223
1223
1224 # templates
1224 # templates
1225 p = templater.templatepaths()
1225 p = templater.templatepaths()
1226 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1226 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1227 fm.condwrite(not p, '', _(" no template directories found\n"))
1227 fm.condwrite(not p, '', _(" no template directories found\n"))
1228 if p:
1228 if p:
1229 m = templater.templatepath("map-cmdline.default")
1229 m = templater.templatepath("map-cmdline.default")
1230 if m:
1230 if m:
1231 # template found, check if it is working
1231 # template found, check if it is working
1232 err = None
1232 err = None
1233 try:
1233 try:
1234 templater.templater.frommapfile(m)
1234 templater.templater.frommapfile(m)
1235 except Exception as inst:
1235 except Exception as inst:
1236 err = util.forcebytestr(inst)
1236 err = util.forcebytestr(inst)
1237 p = None
1237 p = None
1238 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1238 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1239 else:
1239 else:
1240 p = None
1240 p = None
1241 fm.condwrite(p, 'defaulttemplate',
1241 fm.condwrite(p, 'defaulttemplate',
1242 _("checking default template (%s)\n"), m)
1242 _("checking default template (%s)\n"), m)
1243 fm.condwrite(not m, 'defaulttemplatenotfound',
1243 fm.condwrite(not m, 'defaulttemplatenotfound',
1244 _(" template '%s' not found\n"), "default")
1244 _(" template '%s' not found\n"), "default")
1245 if not p:
1245 if not p:
1246 problems += 1
1246 problems += 1
1247 fm.condwrite(not p, '',
1247 fm.condwrite(not p, '',
1248 _(" (templates seem to have been installed incorrectly)\n"))
1248 _(" (templates seem to have been installed incorrectly)\n"))
1249
1249
1250 # editor
1250 # editor
1251 editor = ui.geteditor()
1251 editor = ui.geteditor()
1252 editor = util.expandpath(editor)
1252 editor = util.expandpath(editor)
1253 editorbin = util.shellsplit(editor)[0]
1253 editorbin = util.shellsplit(editor)[0]
1254 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1254 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1255 cmdpath = util.findexe(editorbin)
1255 cmdpath = util.findexe(editorbin)
1256 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1256 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1257 _(" No commit editor set and can't find %s in PATH\n"
1257 _(" No commit editor set and can't find %s in PATH\n"
1258 " (specify a commit editor in your configuration"
1258 " (specify a commit editor in your configuration"
1259 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1259 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1260 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1260 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1261 _(" Can't find editor '%s' in PATH\n"
1261 _(" Can't find editor '%s' in PATH\n"
1262 " (specify a commit editor in your configuration"
1262 " (specify a commit editor in your configuration"
1263 " file)\n"), not cmdpath and editorbin)
1263 " file)\n"), not cmdpath and editorbin)
1264 if not cmdpath and editor != 'vi':
1264 if not cmdpath and editor != 'vi':
1265 problems += 1
1265 problems += 1
1266
1266
1267 # check username
1267 # check username
1268 username = None
1268 username = None
1269 err = None
1269 err = None
1270 try:
1270 try:
1271 username = ui.username()
1271 username = ui.username()
1272 except error.Abort as e:
1272 except error.Abort as e:
1273 err = util.forcebytestr(e)
1273 err = util.forcebytestr(e)
1274 problems += 1
1274 problems += 1
1275
1275
1276 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1276 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1277 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1277 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1278 " (specify a username in your configuration file)\n"), err)
1278 " (specify a username in your configuration file)\n"), err)
1279
1279
1280 fm.condwrite(not problems, '',
1280 fm.condwrite(not problems, '',
1281 _("no problems detected\n"))
1281 _("no problems detected\n"))
1282 if not problems:
1282 if not problems:
1283 fm.data(problems=problems)
1283 fm.data(problems=problems)
1284 fm.condwrite(problems, 'problems',
1284 fm.condwrite(problems, 'problems',
1285 _("%d problems detected,"
1285 _("%d problems detected,"
1286 " please check your install!\n"), problems)
1286 " please check your install!\n"), problems)
1287 fm.end()
1287 fm.end()
1288
1288
1289 return problems
1289 return problems
1290
1290
1291 @command('debugknown', [], _('REPO ID...'), norepo=True)
1291 @command('debugknown', [], _('REPO ID...'), norepo=True)
1292 def debugknown(ui, repopath, *ids, **opts):
1292 def debugknown(ui, repopath, *ids, **opts):
1293 """test whether node ids are known to a repo
1293 """test whether node ids are known to a repo
1294
1294
1295 Every ID must be a full-length hex node id string. Returns a list of 0s
1295 Every ID must be a full-length hex node id string. Returns a list of 0s
1296 and 1s indicating unknown/known.
1296 and 1s indicating unknown/known.
1297 """
1297 """
1298 opts = pycompat.byteskwargs(opts)
1298 opts = pycompat.byteskwargs(opts)
1299 repo = hg.peer(ui, opts, repopath)
1299 repo = hg.peer(ui, opts, repopath)
1300 if not repo.capable('known'):
1300 if not repo.capable('known'):
1301 raise error.Abort("known() not supported by target repository")
1301 raise error.Abort("known() not supported by target repository")
1302 flags = repo.known([bin(s) for s in ids])
1302 flags = repo.known([bin(s) for s in ids])
1303 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1303 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1304
1304
1305 @command('debuglabelcomplete', [], _('LABEL...'))
1305 @command('debuglabelcomplete', [], _('LABEL...'))
1306 def debuglabelcomplete(ui, repo, *args):
1306 def debuglabelcomplete(ui, repo, *args):
1307 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1307 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1308 debugnamecomplete(ui, repo, *args)
1308 debugnamecomplete(ui, repo, *args)
1309
1309
1310 @command('debuglocks',
1310 @command('debuglocks',
1311 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1311 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1312 ('W', 'force-wlock', None,
1312 ('W', 'force-wlock', None,
1313 _('free the working state lock (DANGEROUS)')),
1313 _('free the working state lock (DANGEROUS)')),
1314 ('s', 'set-lock', None, _('set the store lock until stopped')),
1314 ('s', 'set-lock', None, _('set the store lock until stopped')),
1315 ('S', 'set-wlock', None,
1315 ('S', 'set-wlock', None,
1316 _('set the working state lock until stopped'))],
1316 _('set the working state lock until stopped'))],
1317 _('[OPTION]...'))
1317 _('[OPTION]...'))
1318 def debuglocks(ui, repo, **opts):
1318 def debuglocks(ui, repo, **opts):
1319 """show or modify state of locks
1319 """show or modify state of locks
1320
1320
1321 By default, this command will show which locks are held. This
1321 By default, this command will show which locks are held. This
1322 includes the user and process holding the lock, the amount of time
1322 includes the user and process holding the lock, the amount of time
1323 the lock has been held, and the machine name where the process is
1323 the lock has been held, and the machine name where the process is
1324 running if it's not local.
1324 running if it's not local.
1325
1325
1326 Locks protect the integrity of Mercurial's data, so should be
1326 Locks protect the integrity of Mercurial's data, so should be
1327 treated with care. System crashes or other interruptions may cause
1327 treated with care. System crashes or other interruptions may cause
1328 locks to not be properly released, though Mercurial will usually
1328 locks to not be properly released, though Mercurial will usually
1329 detect and remove such stale locks automatically.
1329 detect and remove such stale locks automatically.
1330
1330
1331 However, detecting stale locks may not always be possible (for
1331 However, detecting stale locks may not always be possible (for
1332 instance, on a shared filesystem). Removing locks may also be
1332 instance, on a shared filesystem). Removing locks may also be
1333 blocked by filesystem permissions.
1333 blocked by filesystem permissions.
1334
1334
1335 Setting a lock will prevent other commands from changing the data.
1335 Setting a lock will prevent other commands from changing the data.
1336 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1336 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1337 The set locks are removed when the command exits.
1337 The set locks are removed when the command exits.
1338
1338
1339 Returns 0 if no locks are held.
1339 Returns 0 if no locks are held.
1340
1340
1341 """
1341 """
1342
1342
1343 if opts.get(r'force_lock'):
1343 if opts.get(r'force_lock'):
1344 repo.svfs.unlink('lock')
1344 repo.svfs.unlink('lock')
1345 if opts.get(r'force_wlock'):
1345 if opts.get(r'force_wlock'):
1346 repo.vfs.unlink('wlock')
1346 repo.vfs.unlink('wlock')
1347 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1347 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1348 return 0
1348 return 0
1349
1349
1350 locks = []
1350 locks = []
1351 try:
1351 try:
1352 if opts.get(r'set_wlock'):
1352 if opts.get(r'set_wlock'):
1353 try:
1353 try:
1354 locks.append(repo.wlock(False))
1354 locks.append(repo.wlock(False))
1355 except error.LockHeld:
1355 except error.LockHeld:
1356 raise error.Abort(_('wlock is already held'))
1356 raise error.Abort(_('wlock is already held'))
1357 if opts.get(r'set_lock'):
1357 if opts.get(r'set_lock'):
1358 try:
1358 try:
1359 locks.append(repo.lock(False))
1359 locks.append(repo.lock(False))
1360 except error.LockHeld:
1360 except error.LockHeld:
1361 raise error.Abort(_('lock is already held'))
1361 raise error.Abort(_('lock is already held'))
1362 if len(locks):
1362 if len(locks):
1363 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1363 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1364 return 0
1364 return 0
1365 finally:
1365 finally:
1366 release(*locks)
1366 release(*locks)
1367
1367
1368 now = time.time()
1368 now = time.time()
1369 held = 0
1369 held = 0
1370
1370
1371 def report(vfs, name, method):
1371 def report(vfs, name, method):
1372 # this causes stale locks to get reaped for more accurate reporting
1372 # this causes stale locks to get reaped for more accurate reporting
1373 try:
1373 try:
1374 l = method(False)
1374 l = method(False)
1375 except error.LockHeld:
1375 except error.LockHeld:
1376 l = None
1376 l = None
1377
1377
1378 if l:
1378 if l:
1379 l.release()
1379 l.release()
1380 else:
1380 else:
1381 try:
1381 try:
1382 st = vfs.lstat(name)
1382 st = vfs.lstat(name)
1383 age = now - st[stat.ST_MTIME]
1383 age = now - st[stat.ST_MTIME]
1384 user = util.username(st.st_uid)
1384 user = util.username(st.st_uid)
1385 locker = vfs.readlock(name)
1385 locker = vfs.readlock(name)
1386 if ":" in locker:
1386 if ":" in locker:
1387 host, pid = locker.split(':')
1387 host, pid = locker.split(':')
1388 if host == socket.gethostname():
1388 if host == socket.gethostname():
1389 locker = 'user %s, process %s' % (user, pid)
1389 locker = 'user %s, process %s' % (user, pid)
1390 else:
1390 else:
1391 locker = 'user %s, process %s, host %s' \
1391 locker = 'user %s, process %s, host %s' \
1392 % (user, pid, host)
1392 % (user, pid, host)
1393 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1393 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1394 return 1
1394 return 1
1395 except OSError as e:
1395 except OSError as e:
1396 if e.errno != errno.ENOENT:
1396 if e.errno != errno.ENOENT:
1397 raise
1397 raise
1398
1398
1399 ui.write(("%-6s free\n") % (name + ":"))
1399 ui.write(("%-6s free\n") % (name + ":"))
1400 return 0
1400 return 0
1401
1401
1402 held += report(repo.svfs, "lock", repo.lock)
1402 held += report(repo.svfs, "lock", repo.lock)
1403 held += report(repo.vfs, "wlock", repo.wlock)
1403 held += report(repo.vfs, "wlock", repo.wlock)
1404
1404
1405 return held
1405 return held
1406
1406
1407 @command('debugmergestate', [], '')
1407 @command('debugmergestate', [], '')
1408 def debugmergestate(ui, repo, *args):
1408 def debugmergestate(ui, repo, *args):
1409 """print merge state
1409 """print merge state
1410
1410
1411 Use --verbose to print out information about whether v1 or v2 merge state
1411 Use --verbose to print out information about whether v1 or v2 merge state
1412 was chosen."""
1412 was chosen."""
1413 def _hashornull(h):
1413 def _hashornull(h):
1414 if h == nullhex:
1414 if h == nullhex:
1415 return 'null'
1415 return 'null'
1416 else:
1416 else:
1417 return h
1417 return h
1418
1418
1419 def printrecords(version):
1419 def printrecords(version):
1420 ui.write(('* version %d records\n') % version)
1420 ui.write(('* version %d records\n') % version)
1421 if version == 1:
1421 if version == 1:
1422 records = v1records
1422 records = v1records
1423 else:
1423 else:
1424 records = v2records
1424 records = v2records
1425
1425
1426 for rtype, record in records:
1426 for rtype, record in records:
1427 # pretty print some record types
1427 # pretty print some record types
1428 if rtype == 'L':
1428 if rtype == 'L':
1429 ui.write(('local: %s\n') % record)
1429 ui.write(('local: %s\n') % record)
1430 elif rtype == 'O':
1430 elif rtype == 'O':
1431 ui.write(('other: %s\n') % record)
1431 ui.write(('other: %s\n') % record)
1432 elif rtype == 'm':
1432 elif rtype == 'm':
1433 driver, mdstate = record.split('\0', 1)
1433 driver, mdstate = record.split('\0', 1)
1434 ui.write(('merge driver: %s (state "%s")\n')
1434 ui.write(('merge driver: %s (state "%s")\n')
1435 % (driver, mdstate))
1435 % (driver, mdstate))
1436 elif rtype in 'FDC':
1436 elif rtype in 'FDC':
1437 r = record.split('\0')
1437 r = record.split('\0')
1438 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1438 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1439 if version == 1:
1439 if version == 1:
1440 onode = 'not stored in v1 format'
1440 onode = 'not stored in v1 format'
1441 flags = r[7]
1441 flags = r[7]
1442 else:
1442 else:
1443 onode, flags = r[7:9]
1443 onode, flags = r[7:9]
1444 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1444 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1445 % (f, rtype, state, _hashornull(hash)))
1445 % (f, rtype, state, _hashornull(hash)))
1446 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1446 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1447 ui.write((' ancestor path: %s (node %s)\n')
1447 ui.write((' ancestor path: %s (node %s)\n')
1448 % (afile, _hashornull(anode)))
1448 % (afile, _hashornull(anode)))
1449 ui.write((' other path: %s (node %s)\n')
1449 ui.write((' other path: %s (node %s)\n')
1450 % (ofile, _hashornull(onode)))
1450 % (ofile, _hashornull(onode)))
1451 elif rtype == 'f':
1451 elif rtype == 'f':
1452 filename, rawextras = record.split('\0', 1)
1452 filename, rawextras = record.split('\0', 1)
1453 extras = rawextras.split('\0')
1453 extras = rawextras.split('\0')
1454 i = 0
1454 i = 0
1455 extrastrings = []
1455 extrastrings = []
1456 while i < len(extras):
1456 while i < len(extras):
1457 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1457 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1458 i += 2
1458 i += 2
1459
1459
1460 ui.write(('file extras: %s (%s)\n')
1460 ui.write(('file extras: %s (%s)\n')
1461 % (filename, ', '.join(extrastrings)))
1461 % (filename, ', '.join(extrastrings)))
1462 elif rtype == 'l':
1462 elif rtype == 'l':
1463 labels = record.split('\0', 2)
1463 labels = record.split('\0', 2)
1464 labels = [l for l in labels if len(l) > 0]
1464 labels = [l for l in labels if len(l) > 0]
1465 ui.write(('labels:\n'))
1465 ui.write(('labels:\n'))
1466 ui.write((' local: %s\n' % labels[0]))
1466 ui.write((' local: %s\n' % labels[0]))
1467 ui.write((' other: %s\n' % labels[1]))
1467 ui.write((' other: %s\n' % labels[1]))
1468 if len(labels) > 2:
1468 if len(labels) > 2:
1469 ui.write((' base: %s\n' % labels[2]))
1469 ui.write((' base: %s\n' % labels[2]))
1470 else:
1470 else:
1471 ui.write(('unrecognized entry: %s\t%s\n')
1471 ui.write(('unrecognized entry: %s\t%s\n')
1472 % (rtype, record.replace('\0', '\t')))
1472 % (rtype, record.replace('\0', '\t')))
1473
1473
1474 # Avoid mergestate.read() since it may raise an exception for unsupported
1474 # Avoid mergestate.read() since it may raise an exception for unsupported
1475 # merge state records. We shouldn't be doing this, but this is OK since this
1475 # merge state records. We shouldn't be doing this, but this is OK since this
1476 # command is pretty low-level.
1476 # command is pretty low-level.
1477 ms = mergemod.mergestate(repo)
1477 ms = mergemod.mergestate(repo)
1478
1478
1479 # sort so that reasonable information is on top
1479 # sort so that reasonable information is on top
1480 v1records = ms._readrecordsv1()
1480 v1records = ms._readrecordsv1()
1481 v2records = ms._readrecordsv2()
1481 v2records = ms._readrecordsv2()
1482 order = 'LOml'
1482 order = 'LOml'
1483 def key(r):
1483 def key(r):
1484 idx = order.find(r[0])
1484 idx = order.find(r[0])
1485 if idx == -1:
1485 if idx == -1:
1486 return (1, r[1])
1486 return (1, r[1])
1487 else:
1487 else:
1488 return (0, idx)
1488 return (0, idx)
1489 v1records.sort(key=key)
1489 v1records.sort(key=key)
1490 v2records.sort(key=key)
1490 v2records.sort(key=key)
1491
1491
1492 if not v1records and not v2records:
1492 if not v1records and not v2records:
1493 ui.write(('no merge state found\n'))
1493 ui.write(('no merge state found\n'))
1494 elif not v2records:
1494 elif not v2records:
1495 ui.note(('no version 2 merge state\n'))
1495 ui.note(('no version 2 merge state\n'))
1496 printrecords(1)
1496 printrecords(1)
1497 elif ms._v1v2match(v1records, v2records):
1497 elif ms._v1v2match(v1records, v2records):
1498 ui.note(('v1 and v2 states match: using v2\n'))
1498 ui.note(('v1 and v2 states match: using v2\n'))
1499 printrecords(2)
1499 printrecords(2)
1500 else:
1500 else:
1501 ui.note(('v1 and v2 states mismatch: using v1\n'))
1501 ui.note(('v1 and v2 states mismatch: using v1\n'))
1502 printrecords(1)
1502 printrecords(1)
1503 if ui.verbose:
1503 if ui.verbose:
1504 printrecords(2)
1504 printrecords(2)
1505
1505
1506 @command('debugnamecomplete', [], _('NAME...'))
1506 @command('debugnamecomplete', [], _('NAME...'))
1507 def debugnamecomplete(ui, repo, *args):
1507 def debugnamecomplete(ui, repo, *args):
1508 '''complete "names" - tags, open branch names, bookmark names'''
1508 '''complete "names" - tags, open branch names, bookmark names'''
1509
1509
1510 names = set()
1510 names = set()
1511 # since we previously only listed open branches, we will handle that
1511 # since we previously only listed open branches, we will handle that
1512 # specially (after this for loop)
1512 # specially (after this for loop)
1513 for name, ns in repo.names.iteritems():
1513 for name, ns in repo.names.iteritems():
1514 if name != 'branches':
1514 if name != 'branches':
1515 names.update(ns.listnames(repo))
1515 names.update(ns.listnames(repo))
1516 names.update(tag for (tag, heads, tip, closed)
1516 names.update(tag for (tag, heads, tip, closed)
1517 in repo.branchmap().iterbranches() if not closed)
1517 in repo.branchmap().iterbranches() if not closed)
1518 completions = set()
1518 completions = set()
1519 if not args:
1519 if not args:
1520 args = ['']
1520 args = ['']
1521 for a in args:
1521 for a in args:
1522 completions.update(n for n in names if n.startswith(a))
1522 completions.update(n for n in names if n.startswith(a))
1523 ui.write('\n'.join(sorted(completions)))
1523 ui.write('\n'.join(sorted(completions)))
1524 ui.write('\n')
1524 ui.write('\n')
1525
1525
1526 @command('debugobsolete',
1526 @command('debugobsolete',
1527 [('', 'flags', 0, _('markers flag')),
1527 [('', 'flags', 0, _('markers flag')),
1528 ('', 'record-parents', False,
1528 ('', 'record-parents', False,
1529 _('record parent information for the precursor')),
1529 _('record parent information for the precursor')),
1530 ('r', 'rev', [], _('display markers relevant to REV')),
1530 ('r', 'rev', [], _('display markers relevant to REV')),
1531 ('', 'exclusive', False, _('restrict display to markers only '
1531 ('', 'exclusive', False, _('restrict display to markers only '
1532 'relevant to REV')),
1532 'relevant to REV')),
1533 ('', 'index', False, _('display index of the marker')),
1533 ('', 'index', False, _('display index of the marker')),
1534 ('', 'delete', [], _('delete markers specified by indices')),
1534 ('', 'delete', [], _('delete markers specified by indices')),
1535 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1535 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1536 _('[OBSOLETED [REPLACEMENT ...]]'))
1536 _('[OBSOLETED [REPLACEMENT ...]]'))
1537 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1537 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1538 """create arbitrary obsolete marker
1538 """create arbitrary obsolete marker
1539
1539
1540 With no arguments, displays the list of obsolescence markers."""
1540 With no arguments, displays the list of obsolescence markers."""
1541
1541
1542 opts = pycompat.byteskwargs(opts)
1542 opts = pycompat.byteskwargs(opts)
1543
1543
1544 def parsenodeid(s):
1544 def parsenodeid(s):
1545 try:
1545 try:
1546 # We do not use revsingle/revrange functions here to accept
1546 # We do not use revsingle/revrange functions here to accept
1547 # arbitrary node identifiers, possibly not present in the
1547 # arbitrary node identifiers, possibly not present in the
1548 # local repository.
1548 # local repository.
1549 n = bin(s)
1549 n = bin(s)
1550 if len(n) != len(nullid):
1550 if len(n) != len(nullid):
1551 raise TypeError()
1551 raise TypeError()
1552 return n
1552 return n
1553 except TypeError:
1553 except TypeError:
1554 raise error.Abort('changeset references must be full hexadecimal '
1554 raise error.Abort('changeset references must be full hexadecimal '
1555 'node identifiers')
1555 'node identifiers')
1556
1556
1557 if opts.get('delete'):
1557 if opts.get('delete'):
1558 indices = []
1558 indices = []
1559 for v in opts.get('delete'):
1559 for v in opts.get('delete'):
1560 try:
1560 try:
1561 indices.append(int(v))
1561 indices.append(int(v))
1562 except ValueError:
1562 except ValueError:
1563 raise error.Abort(_('invalid index value: %r') % v,
1563 raise error.Abort(_('invalid index value: %r') % v,
1564 hint=_('use integers for indices'))
1564 hint=_('use integers for indices'))
1565
1565
1566 if repo.currenttransaction():
1566 if repo.currenttransaction():
1567 raise error.Abort(_('cannot delete obsmarkers in the middle '
1567 raise error.Abort(_('cannot delete obsmarkers in the middle '
1568 'of transaction.'))
1568 'of transaction.'))
1569
1569
1570 with repo.lock():
1570 with repo.lock():
1571 n = repair.deleteobsmarkers(repo.obsstore, indices)
1571 n = repair.deleteobsmarkers(repo.obsstore, indices)
1572 ui.write(_('deleted %i obsolescence markers\n') % n)
1572 ui.write(_('deleted %i obsolescence markers\n') % n)
1573
1573
1574 return
1574 return
1575
1575
1576 if precursor is not None:
1576 if precursor is not None:
1577 if opts['rev']:
1577 if opts['rev']:
1578 raise error.Abort('cannot select revision when creating marker')
1578 raise error.Abort('cannot select revision when creating marker')
1579 metadata = {}
1579 metadata = {}
1580 metadata['user'] = opts['user'] or ui.username()
1580 metadata['user'] = opts['user'] or ui.username()
1581 succs = tuple(parsenodeid(succ) for succ in successors)
1581 succs = tuple(parsenodeid(succ) for succ in successors)
1582 l = repo.lock()
1582 l = repo.lock()
1583 try:
1583 try:
1584 tr = repo.transaction('debugobsolete')
1584 tr = repo.transaction('debugobsolete')
1585 try:
1585 try:
1586 date = opts.get('date')
1586 date = opts.get('date')
1587 if date:
1587 if date:
1588 date = dateutil.parsedate(date)
1588 date = dateutil.parsedate(date)
1589 else:
1589 else:
1590 date = None
1590 date = None
1591 prec = parsenodeid(precursor)
1591 prec = parsenodeid(precursor)
1592 parents = None
1592 parents = None
1593 if opts['record_parents']:
1593 if opts['record_parents']:
1594 if prec not in repo.unfiltered():
1594 if prec not in repo.unfiltered():
1595 raise error.Abort('cannot used --record-parents on '
1595 raise error.Abort('cannot used --record-parents on '
1596 'unknown changesets')
1596 'unknown changesets')
1597 parents = repo.unfiltered()[prec].parents()
1597 parents = repo.unfiltered()[prec].parents()
1598 parents = tuple(p.node() for p in parents)
1598 parents = tuple(p.node() for p in parents)
1599 repo.obsstore.create(tr, prec, succs, opts['flags'],
1599 repo.obsstore.create(tr, prec, succs, opts['flags'],
1600 parents=parents, date=date,
1600 parents=parents, date=date,
1601 metadata=metadata, ui=ui)
1601 metadata=metadata, ui=ui)
1602 tr.close()
1602 tr.close()
1603 except ValueError as exc:
1603 except ValueError as exc:
1604 raise error.Abort(_('bad obsmarker input: %s') %
1604 raise error.Abort(_('bad obsmarker input: %s') %
1605 pycompat.bytestr(exc))
1605 pycompat.bytestr(exc))
1606 finally:
1606 finally:
1607 tr.release()
1607 tr.release()
1608 finally:
1608 finally:
1609 l.release()
1609 l.release()
1610 else:
1610 else:
1611 if opts['rev']:
1611 if opts['rev']:
1612 revs = scmutil.revrange(repo, opts['rev'])
1612 revs = scmutil.revrange(repo, opts['rev'])
1613 nodes = [repo[r].node() for r in revs]
1613 nodes = [repo[r].node() for r in revs]
1614 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1614 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1615 exclusive=opts['exclusive']))
1615 exclusive=opts['exclusive']))
1616 markers.sort(key=lambda x: x._data)
1616 markers.sort(key=lambda x: x._data)
1617 else:
1617 else:
1618 markers = obsutil.getmarkers(repo)
1618 markers = obsutil.getmarkers(repo)
1619
1619
1620 markerstoiter = markers
1620 markerstoiter = markers
1621 isrelevant = lambda m: True
1621 isrelevant = lambda m: True
1622 if opts.get('rev') and opts.get('index'):
1622 if opts.get('rev') and opts.get('index'):
1623 markerstoiter = obsutil.getmarkers(repo)
1623 markerstoiter = obsutil.getmarkers(repo)
1624 markerset = set(markers)
1624 markerset = set(markers)
1625 isrelevant = lambda m: m in markerset
1625 isrelevant = lambda m: m in markerset
1626
1626
1627 fm = ui.formatter('debugobsolete', opts)
1627 fm = ui.formatter('debugobsolete', opts)
1628 for i, m in enumerate(markerstoiter):
1628 for i, m in enumerate(markerstoiter):
1629 if not isrelevant(m):
1629 if not isrelevant(m):
1630 # marker can be irrelevant when we're iterating over a set
1630 # marker can be irrelevant when we're iterating over a set
1631 # of markers (markerstoiter) which is bigger than the set
1631 # of markers (markerstoiter) which is bigger than the set
1632 # of markers we want to display (markers)
1632 # of markers we want to display (markers)
1633 # this can happen if both --index and --rev options are
1633 # this can happen if both --index and --rev options are
1634 # provided and thus we need to iterate over all of the markers
1634 # provided and thus we need to iterate over all of the markers
1635 # to get the correct indices, but only display the ones that
1635 # to get the correct indices, but only display the ones that
1636 # are relevant to --rev value
1636 # are relevant to --rev value
1637 continue
1637 continue
1638 fm.startitem()
1638 fm.startitem()
1639 ind = i if opts.get('index') else None
1639 ind = i if opts.get('index') else None
1640 cmdutil.showmarker(fm, m, index=ind)
1640 cmdutil.showmarker(fm, m, index=ind)
1641 fm.end()
1641 fm.end()
1642
1642
1643 @command('debugpathcomplete',
1643 @command('debugpathcomplete',
1644 [('f', 'full', None, _('complete an entire path')),
1644 [('f', 'full', None, _('complete an entire path')),
1645 ('n', 'normal', None, _('show only normal files')),
1645 ('n', 'normal', None, _('show only normal files')),
1646 ('a', 'added', None, _('show only added files')),
1646 ('a', 'added', None, _('show only added files')),
1647 ('r', 'removed', None, _('show only removed files'))],
1647 ('r', 'removed', None, _('show only removed files'))],
1648 _('FILESPEC...'))
1648 _('FILESPEC...'))
1649 def debugpathcomplete(ui, repo, *specs, **opts):
1649 def debugpathcomplete(ui, repo, *specs, **opts):
1650 '''complete part or all of a tracked path
1650 '''complete part or all of a tracked path
1651
1651
1652 This command supports shells that offer path name completion. It
1652 This command supports shells that offer path name completion. It
1653 currently completes only files already known to the dirstate.
1653 currently completes only files already known to the dirstate.
1654
1654
1655 Completion extends only to the next path segment unless
1655 Completion extends only to the next path segment unless
1656 --full is specified, in which case entire paths are used.'''
1656 --full is specified, in which case entire paths are used.'''
1657
1657
1658 def complete(path, acceptable):
1658 def complete(path, acceptable):
1659 dirstate = repo.dirstate
1659 dirstate = repo.dirstate
1660 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1660 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1661 rootdir = repo.root + pycompat.ossep
1661 rootdir = repo.root + pycompat.ossep
1662 if spec != repo.root and not spec.startswith(rootdir):
1662 if spec != repo.root and not spec.startswith(rootdir):
1663 return [], []
1663 return [], []
1664 if os.path.isdir(spec):
1664 if os.path.isdir(spec):
1665 spec += '/'
1665 spec += '/'
1666 spec = spec[len(rootdir):]
1666 spec = spec[len(rootdir):]
1667 fixpaths = pycompat.ossep != '/'
1667 fixpaths = pycompat.ossep != '/'
1668 if fixpaths:
1668 if fixpaths:
1669 spec = spec.replace(pycompat.ossep, '/')
1669 spec = spec.replace(pycompat.ossep, '/')
1670 speclen = len(spec)
1670 speclen = len(spec)
1671 fullpaths = opts[r'full']
1671 fullpaths = opts[r'full']
1672 files, dirs = set(), set()
1672 files, dirs = set(), set()
1673 adddir, addfile = dirs.add, files.add
1673 adddir, addfile = dirs.add, files.add
1674 for f, st in dirstate.iteritems():
1674 for f, st in dirstate.iteritems():
1675 if f.startswith(spec) and st[0] in acceptable:
1675 if f.startswith(spec) and st[0] in acceptable:
1676 if fixpaths:
1676 if fixpaths:
1677 f = f.replace('/', pycompat.ossep)
1677 f = f.replace('/', pycompat.ossep)
1678 if fullpaths:
1678 if fullpaths:
1679 addfile(f)
1679 addfile(f)
1680 continue
1680 continue
1681 s = f.find(pycompat.ossep, speclen)
1681 s = f.find(pycompat.ossep, speclen)
1682 if s >= 0:
1682 if s >= 0:
1683 adddir(f[:s])
1683 adddir(f[:s])
1684 else:
1684 else:
1685 addfile(f)
1685 addfile(f)
1686 return files, dirs
1686 return files, dirs
1687
1687
1688 acceptable = ''
1688 acceptable = ''
1689 if opts[r'normal']:
1689 if opts[r'normal']:
1690 acceptable += 'nm'
1690 acceptable += 'nm'
1691 if opts[r'added']:
1691 if opts[r'added']:
1692 acceptable += 'a'
1692 acceptable += 'a'
1693 if opts[r'removed']:
1693 if opts[r'removed']:
1694 acceptable += 'r'
1694 acceptable += 'r'
1695 cwd = repo.getcwd()
1695 cwd = repo.getcwd()
1696 if not specs:
1696 if not specs:
1697 specs = ['.']
1697 specs = ['.']
1698
1698
1699 files, dirs = set(), set()
1699 files, dirs = set(), set()
1700 for spec in specs:
1700 for spec in specs:
1701 f, d = complete(spec, acceptable or 'nmar')
1701 f, d = complete(spec, acceptable or 'nmar')
1702 files.update(f)
1702 files.update(f)
1703 dirs.update(d)
1703 dirs.update(d)
1704 files.update(dirs)
1704 files.update(dirs)
1705 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1705 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1706 ui.write('\n')
1706 ui.write('\n')
1707
1707
1708 @command('debugpeer', [], _('PATH'), norepo=True)
1708 @command('debugpeer', [], _('PATH'), norepo=True)
1709 def debugpeer(ui, path):
1709 def debugpeer(ui, path):
1710 """establish a connection to a peer repository"""
1710 """establish a connection to a peer repository"""
1711 # Always enable peer request logging. Requires --debug to display
1711 # Always enable peer request logging. Requires --debug to display
1712 # though.
1712 # though.
1713 overrides = {
1713 overrides = {
1714 ('devel', 'debug.peer-request'): True,
1714 ('devel', 'debug.peer-request'): True,
1715 }
1715 }
1716
1716
1717 with ui.configoverride(overrides):
1717 with ui.configoverride(overrides):
1718 peer = hg.peer(ui, {}, path)
1718 peer = hg.peer(ui, {}, path)
1719
1719
1720 local = peer.local() is not None
1720 local = peer.local() is not None
1721 canpush = peer.canpush()
1721 canpush = peer.canpush()
1722
1722
1723 ui.write(_('url: %s\n') % peer.url())
1723 ui.write(_('url: %s\n') % peer.url())
1724 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1724 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1725 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1725 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1726
1726
1727 @command('debugpickmergetool',
1727 @command('debugpickmergetool',
1728 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1728 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1729 ('', 'changedelete', None, _('emulate merging change and delete')),
1729 ('', 'changedelete', None, _('emulate merging change and delete')),
1730 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1730 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1731 _('[PATTERN]...'),
1731 _('[PATTERN]...'),
1732 inferrepo=True)
1732 inferrepo=True)
1733 def debugpickmergetool(ui, repo, *pats, **opts):
1733 def debugpickmergetool(ui, repo, *pats, **opts):
1734 """examine which merge tool is chosen for specified file
1734 """examine which merge tool is chosen for specified file
1735
1735
1736 As described in :hg:`help merge-tools`, Mercurial examines
1736 As described in :hg:`help merge-tools`, Mercurial examines
1737 configurations below in this order to decide which merge tool is
1737 configurations below in this order to decide which merge tool is
1738 chosen for specified file.
1738 chosen for specified file.
1739
1739
1740 1. ``--tool`` option
1740 1. ``--tool`` option
1741 2. ``HGMERGE`` environment variable
1741 2. ``HGMERGE`` environment variable
1742 3. configurations in ``merge-patterns`` section
1742 3. configurations in ``merge-patterns`` section
1743 4. configuration of ``ui.merge``
1743 4. configuration of ``ui.merge``
1744 5. configurations in ``merge-tools`` section
1744 5. configurations in ``merge-tools`` section
1745 6. ``hgmerge`` tool (for historical reason only)
1745 6. ``hgmerge`` tool (for historical reason only)
1746 7. default tool for fallback (``:merge`` or ``:prompt``)
1746 7. default tool for fallback (``:merge`` or ``:prompt``)
1747
1747
1748 This command writes out examination result in the style below::
1748 This command writes out examination result in the style below::
1749
1749
1750 FILE = MERGETOOL
1750 FILE = MERGETOOL
1751
1751
1752 By default, all files known in the first parent context of the
1752 By default, all files known in the first parent context of the
1753 working directory are examined. Use file patterns and/or -I/-X
1753 working directory are examined. Use file patterns and/or -I/-X
1754 options to limit target files. -r/--rev is also useful to examine
1754 options to limit target files. -r/--rev is also useful to examine
1755 files in another context without actual updating to it.
1755 files in another context without actual updating to it.
1756
1756
1757 With --debug, this command shows warning messages while matching
1757 With --debug, this command shows warning messages while matching
1758 against ``merge-patterns`` and so on, too. It is recommended to
1758 against ``merge-patterns`` and so on, too. It is recommended to
1759 use this option with explicit file patterns and/or -I/-X options,
1759 use this option with explicit file patterns and/or -I/-X options,
1760 because this option increases amount of output per file according
1760 because this option increases amount of output per file according
1761 to configurations in hgrc.
1761 to configurations in hgrc.
1762
1762
1763 With -v/--verbose, this command shows configurations below at
1763 With -v/--verbose, this command shows configurations below at
1764 first (only if specified).
1764 first (only if specified).
1765
1765
1766 - ``--tool`` option
1766 - ``--tool`` option
1767 - ``HGMERGE`` environment variable
1767 - ``HGMERGE`` environment variable
1768 - configuration of ``ui.merge``
1768 - configuration of ``ui.merge``
1769
1769
1770 If merge tool is chosen before matching against
1770 If merge tool is chosen before matching against
1771 ``merge-patterns``, this command can't show any helpful
1771 ``merge-patterns``, this command can't show any helpful
1772 information, even with --debug. In such case, information above is
1772 information, even with --debug. In such case, information above is
1773 useful to know why a merge tool is chosen.
1773 useful to know why a merge tool is chosen.
1774 """
1774 """
1775 opts = pycompat.byteskwargs(opts)
1775 opts = pycompat.byteskwargs(opts)
1776 overrides = {}
1776 overrides = {}
1777 if opts['tool']:
1777 if opts['tool']:
1778 overrides[('ui', 'forcemerge')] = opts['tool']
1778 overrides[('ui', 'forcemerge')] = opts['tool']
1779 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1779 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1780
1780
1781 with ui.configoverride(overrides, 'debugmergepatterns'):
1781 with ui.configoverride(overrides, 'debugmergepatterns'):
1782 hgmerge = encoding.environ.get("HGMERGE")
1782 hgmerge = encoding.environ.get("HGMERGE")
1783 if hgmerge is not None:
1783 if hgmerge is not None:
1784 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1784 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1785 uimerge = ui.config("ui", "merge")
1785 uimerge = ui.config("ui", "merge")
1786 if uimerge:
1786 if uimerge:
1787 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1787 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1788
1788
1789 ctx = scmutil.revsingle(repo, opts.get('rev'))
1789 ctx = scmutil.revsingle(repo, opts.get('rev'))
1790 m = scmutil.match(ctx, pats, opts)
1790 m = scmutil.match(ctx, pats, opts)
1791 changedelete = opts['changedelete']
1791 changedelete = opts['changedelete']
1792 for path in ctx.walk(m):
1792 for path in ctx.walk(m):
1793 fctx = ctx[path]
1793 fctx = ctx[path]
1794 try:
1794 try:
1795 if not ui.debugflag:
1795 if not ui.debugflag:
1796 ui.pushbuffer(error=True)
1796 ui.pushbuffer(error=True)
1797 tool, toolpath = filemerge._picktool(repo, ui, path,
1797 tool, toolpath = filemerge._picktool(repo, ui, path,
1798 fctx.isbinary(),
1798 fctx.isbinary(),
1799 'l' in fctx.flags(),
1799 'l' in fctx.flags(),
1800 changedelete)
1800 changedelete)
1801 finally:
1801 finally:
1802 if not ui.debugflag:
1802 if not ui.debugflag:
1803 ui.popbuffer()
1803 ui.popbuffer()
1804 ui.write(('%s = %s\n') % (path, tool))
1804 ui.write(('%s = %s\n') % (path, tool))
1805
1805
1806 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1806 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1807 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1807 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1808 '''access the pushkey key/value protocol
1808 '''access the pushkey key/value protocol
1809
1809
1810 With two args, list the keys in the given namespace.
1810 With two args, list the keys in the given namespace.
1811
1811
1812 With five args, set a key to new if it currently is set to old.
1812 With five args, set a key to new if it currently is set to old.
1813 Reports success or failure.
1813 Reports success or failure.
1814 '''
1814 '''
1815
1815
1816 target = hg.peer(ui, {}, repopath)
1816 target = hg.peer(ui, {}, repopath)
1817 if keyinfo:
1817 if keyinfo:
1818 key, old, new = keyinfo
1818 key, old, new = keyinfo
1819 r = target.pushkey(namespace, key, old, new)
1819 r = target.pushkey(namespace, key, old, new)
1820 ui.status(pycompat.bytestr(r) + '\n')
1820 ui.status(pycompat.bytestr(r) + '\n')
1821 return not r
1821 return not r
1822 else:
1822 else:
1823 for k, v in sorted(target.listkeys(namespace).iteritems()):
1823 for k, v in sorted(target.listkeys(namespace).iteritems()):
1824 ui.write("%s\t%s\n" % (util.escapestr(k),
1824 ui.write("%s\t%s\n" % (util.escapestr(k),
1825 util.escapestr(v)))
1825 util.escapestr(v)))
1826
1826
1827 @command('debugpvec', [], _('A B'))
1827 @command('debugpvec', [], _('A B'))
1828 def debugpvec(ui, repo, a, b=None):
1828 def debugpvec(ui, repo, a, b=None):
1829 ca = scmutil.revsingle(repo, a)
1829 ca = scmutil.revsingle(repo, a)
1830 cb = scmutil.revsingle(repo, b)
1830 cb = scmutil.revsingle(repo, b)
1831 pa = pvec.ctxpvec(ca)
1831 pa = pvec.ctxpvec(ca)
1832 pb = pvec.ctxpvec(cb)
1832 pb = pvec.ctxpvec(cb)
1833 if pa == pb:
1833 if pa == pb:
1834 rel = "="
1834 rel = "="
1835 elif pa > pb:
1835 elif pa > pb:
1836 rel = ">"
1836 rel = ">"
1837 elif pa < pb:
1837 elif pa < pb:
1838 rel = "<"
1838 rel = "<"
1839 elif pa | pb:
1839 elif pa | pb:
1840 rel = "|"
1840 rel = "|"
1841 ui.write(_("a: %s\n") % pa)
1841 ui.write(_("a: %s\n") % pa)
1842 ui.write(_("b: %s\n") % pb)
1842 ui.write(_("b: %s\n") % pb)
1843 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1843 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1844 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1844 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1845 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1845 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1846 pa.distance(pb), rel))
1846 pa.distance(pb), rel))
1847
1847
1848 @command('debugrebuilddirstate|debugrebuildstate',
1848 @command('debugrebuilddirstate|debugrebuildstate',
1849 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1849 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1850 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1850 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1851 'the working copy parent')),
1851 'the working copy parent')),
1852 ],
1852 ],
1853 _('[-r REV]'))
1853 _('[-r REV]'))
1854 def debugrebuilddirstate(ui, repo, rev, **opts):
1854 def debugrebuilddirstate(ui, repo, rev, **opts):
1855 """rebuild the dirstate as it would look like for the given revision
1855 """rebuild the dirstate as it would look like for the given revision
1856
1856
1857 If no revision is specified the first current parent will be used.
1857 If no revision is specified the first current parent will be used.
1858
1858
1859 The dirstate will be set to the files of the given revision.
1859 The dirstate will be set to the files of the given revision.
1860 The actual working directory content or existing dirstate
1860 The actual working directory content or existing dirstate
1861 information such as adds or removes is not considered.
1861 information such as adds or removes is not considered.
1862
1862
1863 ``minimal`` will only rebuild the dirstate status for files that claim to be
1863 ``minimal`` will only rebuild the dirstate status for files that claim to be
1864 tracked but are not in the parent manifest, or that exist in the parent
1864 tracked but are not in the parent manifest, or that exist in the parent
1865 manifest but are not in the dirstate. It will not change adds, removes, or
1865 manifest but are not in the dirstate. It will not change adds, removes, or
1866 modified files that are in the working copy parent.
1866 modified files that are in the working copy parent.
1867
1867
1868 One use of this command is to make the next :hg:`status` invocation
1868 One use of this command is to make the next :hg:`status` invocation
1869 check the actual file content.
1869 check the actual file content.
1870 """
1870 """
1871 ctx = scmutil.revsingle(repo, rev)
1871 ctx = scmutil.revsingle(repo, rev)
1872 with repo.wlock():
1872 with repo.wlock():
1873 dirstate = repo.dirstate
1873 dirstate = repo.dirstate
1874 changedfiles = None
1874 changedfiles = None
1875 # See command doc for what minimal does.
1875 # See command doc for what minimal does.
1876 if opts.get(r'minimal'):
1876 if opts.get(r'minimal'):
1877 manifestfiles = set(ctx.manifest().keys())
1877 manifestfiles = set(ctx.manifest().keys())
1878 dirstatefiles = set(dirstate)
1878 dirstatefiles = set(dirstate)
1879 manifestonly = manifestfiles - dirstatefiles
1879 manifestonly = manifestfiles - dirstatefiles
1880 dsonly = dirstatefiles - manifestfiles
1880 dsonly = dirstatefiles - manifestfiles
1881 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1881 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1882 changedfiles = manifestonly | dsnotadded
1882 changedfiles = manifestonly | dsnotadded
1883
1883
1884 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1884 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1885
1885
1886 @command('debugrebuildfncache', [], '')
1886 @command('debugrebuildfncache', [], '')
1887 def debugrebuildfncache(ui, repo):
1887 def debugrebuildfncache(ui, repo):
1888 """rebuild the fncache file"""
1888 """rebuild the fncache file"""
1889 repair.rebuildfncache(ui, repo)
1889 repair.rebuildfncache(ui, repo)
1890
1890
1891 @command('debugrename',
1891 @command('debugrename',
1892 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1892 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1893 _('[-r REV] FILE'))
1893 _('[-r REV] FILE'))
1894 def debugrename(ui, repo, file1, *pats, **opts):
1894 def debugrename(ui, repo, file1, *pats, **opts):
1895 """dump rename information"""
1895 """dump rename information"""
1896
1896
1897 opts = pycompat.byteskwargs(opts)
1897 opts = pycompat.byteskwargs(opts)
1898 ctx = scmutil.revsingle(repo, opts.get('rev'))
1898 ctx = scmutil.revsingle(repo, opts.get('rev'))
1899 m = scmutil.match(ctx, (file1,) + pats, opts)
1899 m = scmutil.match(ctx, (file1,) + pats, opts)
1900 for abs in ctx.walk(m):
1900 for abs in ctx.walk(m):
1901 fctx = ctx[abs]
1901 fctx = ctx[abs]
1902 o = fctx.filelog().renamed(fctx.filenode())
1902 o = fctx.filelog().renamed(fctx.filenode())
1903 rel = m.rel(abs)
1903 rel = m.rel(abs)
1904 if o:
1904 if o:
1905 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1905 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1906 else:
1906 else:
1907 ui.write(_("%s not renamed\n") % rel)
1907 ui.write(_("%s not renamed\n") % rel)
1908
1908
1909 @command('debugrevlog', cmdutil.debugrevlogopts +
1909 @command('debugrevlog', cmdutil.debugrevlogopts +
1910 [('d', 'dump', False, _('dump index data'))],
1910 [('d', 'dump', False, _('dump index data'))],
1911 _('-c|-m|FILE'),
1911 _('-c|-m|FILE'),
1912 optionalrepo=True)
1912 optionalrepo=True)
1913 def debugrevlog(ui, repo, file_=None, **opts):
1913 def debugrevlog(ui, repo, file_=None, **opts):
1914 """show data and statistics about a revlog"""
1914 """show data and statistics about a revlog"""
1915 opts = pycompat.byteskwargs(opts)
1915 opts = pycompat.byteskwargs(opts)
1916 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1916 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1917
1917
1918 if opts.get("dump"):
1918 if opts.get("dump"):
1919 numrevs = len(r)
1919 numrevs = len(r)
1920 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1920 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1921 " rawsize totalsize compression heads chainlen\n"))
1921 " rawsize totalsize compression heads chainlen\n"))
1922 ts = 0
1922 ts = 0
1923 heads = set()
1923 heads = set()
1924
1924
1925 for rev in xrange(numrevs):
1925 for rev in xrange(numrevs):
1926 dbase = r.deltaparent(rev)
1926 dbase = r.deltaparent(rev)
1927 if dbase == -1:
1927 if dbase == -1:
1928 dbase = rev
1928 dbase = rev
1929 cbase = r.chainbase(rev)
1929 cbase = r.chainbase(rev)
1930 clen = r.chainlen(rev)
1930 clen = r.chainlen(rev)
1931 p1, p2 = r.parentrevs(rev)
1931 p1, p2 = r.parentrevs(rev)
1932 rs = r.rawsize(rev)
1932 rs = r.rawsize(rev)
1933 ts = ts + rs
1933 ts = ts + rs
1934 heads -= set(r.parentrevs(rev))
1934 heads -= set(r.parentrevs(rev))
1935 heads.add(rev)
1935 heads.add(rev)
1936 try:
1936 try:
1937 compression = ts / r.end(rev)
1937 compression = ts / r.end(rev)
1938 except ZeroDivisionError:
1938 except ZeroDivisionError:
1939 compression = 0
1939 compression = 0
1940 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1940 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1941 "%11d %5d %8d\n" %
1941 "%11d %5d %8d\n" %
1942 (rev, p1, p2, r.start(rev), r.end(rev),
1942 (rev, p1, p2, r.start(rev), r.end(rev),
1943 r.start(dbase), r.start(cbase),
1943 r.start(dbase), r.start(cbase),
1944 r.start(p1), r.start(p2),
1944 r.start(p1), r.start(p2),
1945 rs, ts, compression, len(heads), clen))
1945 rs, ts, compression, len(heads), clen))
1946 return 0
1946 return 0
1947
1947
1948 v = r.version
1948 v = r.version
1949 format = v & 0xFFFF
1949 format = v & 0xFFFF
1950 flags = []
1950 flags = []
1951 gdelta = False
1951 gdelta = False
1952 if v & revlog.FLAG_INLINE_DATA:
1952 if v & revlog.FLAG_INLINE_DATA:
1953 flags.append('inline')
1953 flags.append('inline')
1954 if v & revlog.FLAG_GENERALDELTA:
1954 if v & revlog.FLAG_GENERALDELTA:
1955 gdelta = True
1955 gdelta = True
1956 flags.append('generaldelta')
1956 flags.append('generaldelta')
1957 if not flags:
1957 if not flags:
1958 flags = ['(none)']
1958 flags = ['(none)']
1959
1959
1960 nummerges = 0
1960 nummerges = 0
1961 numfull = 0
1961 numfull = 0
1962 numprev = 0
1962 numprev = 0
1963 nump1 = 0
1963 nump1 = 0
1964 nump2 = 0
1964 nump2 = 0
1965 numother = 0
1965 numother = 0
1966 nump1prev = 0
1966 nump1prev = 0
1967 nump2prev = 0
1967 nump2prev = 0
1968 chainlengths = []
1968 chainlengths = []
1969 chainbases = []
1969 chainbases = []
1970 chainspans = []
1970 chainspans = []
1971
1971
1972 datasize = [None, 0, 0]
1972 datasize = [None, 0, 0]
1973 fullsize = [None, 0, 0]
1973 fullsize = [None, 0, 0]
1974 deltasize = [None, 0, 0]
1974 deltasize = [None, 0, 0]
1975 chunktypecounts = {}
1975 chunktypecounts = {}
1976 chunktypesizes = {}
1976 chunktypesizes = {}
1977
1977
1978 def addsize(size, l):
1978 def addsize(size, l):
1979 if l[0] is None or size < l[0]:
1979 if l[0] is None or size < l[0]:
1980 l[0] = size
1980 l[0] = size
1981 if size > l[1]:
1981 if size > l[1]:
1982 l[1] = size
1982 l[1] = size
1983 l[2] += size
1983 l[2] += size
1984
1984
1985 numrevs = len(r)
1985 numrevs = len(r)
1986 for rev in xrange(numrevs):
1986 for rev in xrange(numrevs):
1987 p1, p2 = r.parentrevs(rev)
1987 p1, p2 = r.parentrevs(rev)
1988 delta = r.deltaparent(rev)
1988 delta = r.deltaparent(rev)
1989 if format > 0:
1989 if format > 0:
1990 addsize(r.rawsize(rev), datasize)
1990 addsize(r.rawsize(rev), datasize)
1991 if p2 != nullrev:
1991 if p2 != nullrev:
1992 nummerges += 1
1992 nummerges += 1
1993 size = r.length(rev)
1993 size = r.length(rev)
1994 if delta == nullrev:
1994 if delta == nullrev:
1995 chainlengths.append(0)
1995 chainlengths.append(0)
1996 chainbases.append(r.start(rev))
1996 chainbases.append(r.start(rev))
1997 chainspans.append(size)
1997 chainspans.append(size)
1998 numfull += 1
1998 numfull += 1
1999 addsize(size, fullsize)
1999 addsize(size, fullsize)
2000 else:
2000 else:
2001 chainlengths.append(chainlengths[delta] + 1)
2001 chainlengths.append(chainlengths[delta] + 1)
2002 baseaddr = chainbases[delta]
2002 baseaddr = chainbases[delta]
2003 revaddr = r.start(rev)
2003 revaddr = r.start(rev)
2004 chainbases.append(baseaddr)
2004 chainbases.append(baseaddr)
2005 chainspans.append((revaddr - baseaddr) + size)
2005 chainspans.append((revaddr - baseaddr) + size)
2006 addsize(size, deltasize)
2006 addsize(size, deltasize)
2007 if delta == rev - 1:
2007 if delta == rev - 1:
2008 numprev += 1
2008 numprev += 1
2009 if delta == p1:
2009 if delta == p1:
2010 nump1prev += 1
2010 nump1prev += 1
2011 elif delta == p2:
2011 elif delta == p2:
2012 nump2prev += 1
2012 nump2prev += 1
2013 elif delta == p1:
2013 elif delta == p1:
2014 nump1 += 1
2014 nump1 += 1
2015 elif delta == p2:
2015 elif delta == p2:
2016 nump2 += 1
2016 nump2 += 1
2017 elif delta != nullrev:
2017 elif delta != nullrev:
2018 numother += 1
2018 numother += 1
2019
2019
2020 # Obtain data on the raw chunks in the revlog.
2020 # Obtain data on the raw chunks in the revlog.
2021 segment = r._getsegmentforrevs(rev, rev)[1]
2021 segment = r._getsegmentforrevs(rev, rev)[1]
2022 if segment:
2022 if segment:
2023 chunktype = bytes(segment[0:1])
2023 chunktype = bytes(segment[0:1])
2024 else:
2024 else:
2025 chunktype = 'empty'
2025 chunktype = 'empty'
2026
2026
2027 if chunktype not in chunktypecounts:
2027 if chunktype not in chunktypecounts:
2028 chunktypecounts[chunktype] = 0
2028 chunktypecounts[chunktype] = 0
2029 chunktypesizes[chunktype] = 0
2029 chunktypesizes[chunktype] = 0
2030
2030
2031 chunktypecounts[chunktype] += 1
2031 chunktypecounts[chunktype] += 1
2032 chunktypesizes[chunktype] += size
2032 chunktypesizes[chunktype] += size
2033
2033
2034 # Adjust size min value for empty cases
2034 # Adjust size min value for empty cases
2035 for size in (datasize, fullsize, deltasize):
2035 for size in (datasize, fullsize, deltasize):
2036 if size[0] is None:
2036 if size[0] is None:
2037 size[0] = 0
2037 size[0] = 0
2038
2038
2039 numdeltas = numrevs - numfull
2039 numdeltas = numrevs - numfull
2040 numoprev = numprev - nump1prev - nump2prev
2040 numoprev = numprev - nump1prev - nump2prev
2041 totalrawsize = datasize[2]
2041 totalrawsize = datasize[2]
2042 datasize[2] /= numrevs
2042 datasize[2] /= numrevs
2043 fulltotal = fullsize[2]
2043 fulltotal = fullsize[2]
2044 fullsize[2] /= numfull
2044 fullsize[2] /= numfull
2045 deltatotal = deltasize[2]
2045 deltatotal = deltasize[2]
2046 if numrevs - numfull > 0:
2046 if numrevs - numfull > 0:
2047 deltasize[2] /= numrevs - numfull
2047 deltasize[2] /= numrevs - numfull
2048 totalsize = fulltotal + deltatotal
2048 totalsize = fulltotal + deltatotal
2049 avgchainlen = sum(chainlengths) / numrevs
2049 avgchainlen = sum(chainlengths) / numrevs
2050 maxchainlen = max(chainlengths)
2050 maxchainlen = max(chainlengths)
2051 maxchainspan = max(chainspans)
2051 maxchainspan = max(chainspans)
2052 compratio = 1
2052 compratio = 1
2053 if totalsize:
2053 if totalsize:
2054 compratio = totalrawsize / totalsize
2054 compratio = totalrawsize / totalsize
2055
2055
2056 basedfmtstr = '%%%dd\n'
2056 basedfmtstr = '%%%dd\n'
2057 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2057 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2058
2058
2059 def dfmtstr(max):
2059 def dfmtstr(max):
2060 return basedfmtstr % len(str(max))
2060 return basedfmtstr % len(str(max))
2061 def pcfmtstr(max, padding=0):
2061 def pcfmtstr(max, padding=0):
2062 return basepcfmtstr % (len(str(max)), ' ' * padding)
2062 return basepcfmtstr % (len(str(max)), ' ' * padding)
2063
2063
2064 def pcfmt(value, total):
2064 def pcfmt(value, total):
2065 if total:
2065 if total:
2066 return (value, 100 * float(value) / total)
2066 return (value, 100 * float(value) / total)
2067 else:
2067 else:
2068 return value, 100.0
2068 return value, 100.0
2069
2069
2070 ui.write(('format : %d\n') % format)
2070 ui.write(('format : %d\n') % format)
2071 ui.write(('flags : %s\n') % ', '.join(flags))
2071 ui.write(('flags : %s\n') % ', '.join(flags))
2072
2072
2073 ui.write('\n')
2073 ui.write('\n')
2074 fmt = pcfmtstr(totalsize)
2074 fmt = pcfmtstr(totalsize)
2075 fmt2 = dfmtstr(totalsize)
2075 fmt2 = dfmtstr(totalsize)
2076 ui.write(('revisions : ') + fmt2 % numrevs)
2076 ui.write(('revisions : ') + fmt2 % numrevs)
2077 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2077 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2078 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2078 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2079 ui.write(('revisions : ') + fmt2 % numrevs)
2079 ui.write(('revisions : ') + fmt2 % numrevs)
2080 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2080 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2081 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2081 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2082 ui.write(('revision size : ') + fmt2 % totalsize)
2082 ui.write(('revision size : ') + fmt2 % totalsize)
2083 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2083 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2084 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2084 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2085
2085
2086 def fmtchunktype(chunktype):
2086 def fmtchunktype(chunktype):
2087 if chunktype == 'empty':
2087 if chunktype == 'empty':
2088 return ' %s : ' % chunktype
2088 return ' %s : ' % chunktype
2089 elif chunktype in pycompat.bytestr(string.ascii_letters):
2089 elif chunktype in pycompat.bytestr(string.ascii_letters):
2090 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2090 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2091 else:
2091 else:
2092 return ' 0x%s : ' % hex(chunktype)
2092 return ' 0x%s : ' % hex(chunktype)
2093
2093
2094 ui.write('\n')
2094 ui.write('\n')
2095 ui.write(('chunks : ') + fmt2 % numrevs)
2095 ui.write(('chunks : ') + fmt2 % numrevs)
2096 for chunktype in sorted(chunktypecounts):
2096 for chunktype in sorted(chunktypecounts):
2097 ui.write(fmtchunktype(chunktype))
2097 ui.write(fmtchunktype(chunktype))
2098 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2098 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2099 ui.write(('chunks size : ') + fmt2 % totalsize)
2099 ui.write(('chunks size : ') + fmt2 % totalsize)
2100 for chunktype in sorted(chunktypecounts):
2100 for chunktype in sorted(chunktypecounts):
2101 ui.write(fmtchunktype(chunktype))
2101 ui.write(fmtchunktype(chunktype))
2102 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2102 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2103
2103
2104 ui.write('\n')
2104 ui.write('\n')
2105 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2105 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2106 ui.write(('avg chain length : ') + fmt % avgchainlen)
2106 ui.write(('avg chain length : ') + fmt % avgchainlen)
2107 ui.write(('max chain length : ') + fmt % maxchainlen)
2107 ui.write(('max chain length : ') + fmt % maxchainlen)
2108 ui.write(('max chain reach : ') + fmt % maxchainspan)
2108 ui.write(('max chain reach : ') + fmt % maxchainspan)
2109 ui.write(('compression ratio : ') + fmt % compratio)
2109 ui.write(('compression ratio : ') + fmt % compratio)
2110
2110
2111 if format > 0:
2111 if format > 0:
2112 ui.write('\n')
2112 ui.write('\n')
2113 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2113 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2114 % tuple(datasize))
2114 % tuple(datasize))
2115 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2115 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2116 % tuple(fullsize))
2116 % tuple(fullsize))
2117 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2117 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2118 % tuple(deltasize))
2118 % tuple(deltasize))
2119
2119
2120 if numdeltas > 0:
2120 if numdeltas > 0:
2121 ui.write('\n')
2121 ui.write('\n')
2122 fmt = pcfmtstr(numdeltas)
2122 fmt = pcfmtstr(numdeltas)
2123 fmt2 = pcfmtstr(numdeltas, 4)
2123 fmt2 = pcfmtstr(numdeltas, 4)
2124 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2124 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2125 if numprev > 0:
2125 if numprev > 0:
2126 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2126 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2127 numprev))
2127 numprev))
2128 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2128 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2129 numprev))
2129 numprev))
2130 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2130 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2131 numprev))
2131 numprev))
2132 if gdelta:
2132 if gdelta:
2133 ui.write(('deltas against p1 : ')
2133 ui.write(('deltas against p1 : ')
2134 + fmt % pcfmt(nump1, numdeltas))
2134 + fmt % pcfmt(nump1, numdeltas))
2135 ui.write(('deltas against p2 : ')
2135 ui.write(('deltas against p2 : ')
2136 + fmt % pcfmt(nump2, numdeltas))
2136 + fmt % pcfmt(nump2, numdeltas))
2137 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2137 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2138 numdeltas))
2138 numdeltas))
2139
2139
2140 @command('debugrevspec',
2140 @command('debugrevspec',
2141 [('', 'optimize', None,
2141 [('', 'optimize', None,
2142 _('print parsed tree after optimizing (DEPRECATED)')),
2142 _('print parsed tree after optimizing (DEPRECATED)')),
2143 ('', 'show-revs', True, _('print list of result revisions (default)')),
2143 ('', 'show-revs', True, _('print list of result revisions (default)')),
2144 ('s', 'show-set', None, _('print internal representation of result set')),
2144 ('s', 'show-set', None, _('print internal representation of result set')),
2145 ('p', 'show-stage', [],
2145 ('p', 'show-stage', [],
2146 _('print parsed tree at the given stage'), _('NAME')),
2146 _('print parsed tree at the given stage'), _('NAME')),
2147 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2147 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2148 ('', 'verify-optimized', False, _('verify optimized result')),
2148 ('', 'verify-optimized', False, _('verify optimized result')),
2149 ],
2149 ],
2150 ('REVSPEC'))
2150 ('REVSPEC'))
2151 def debugrevspec(ui, repo, expr, **opts):
2151 def debugrevspec(ui, repo, expr, **opts):
2152 """parse and apply a revision specification
2152 """parse and apply a revision specification
2153
2153
2154 Use -p/--show-stage option to print the parsed tree at the given stages.
2154 Use -p/--show-stage option to print the parsed tree at the given stages.
2155 Use -p all to print tree at every stage.
2155 Use -p all to print tree at every stage.
2156
2156
2157 Use --no-show-revs option with -s or -p to print only the set
2157 Use --no-show-revs option with -s or -p to print only the set
2158 representation or the parsed tree respectively.
2158 representation or the parsed tree respectively.
2159
2159
2160 Use --verify-optimized to compare the optimized result with the unoptimized
2160 Use --verify-optimized to compare the optimized result with the unoptimized
2161 one. Returns 1 if the optimized result differs.
2161 one. Returns 1 if the optimized result differs.
2162 """
2162 """
2163 opts = pycompat.byteskwargs(opts)
2163 opts = pycompat.byteskwargs(opts)
2164 aliases = ui.configitems('revsetalias')
2164 aliases = ui.configitems('revsetalias')
2165 stages = [
2165 stages = [
2166 ('parsed', lambda tree: tree),
2166 ('parsed', lambda tree: tree),
2167 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2167 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2168 ui.warn)),
2168 ui.warn)),
2169 ('concatenated', revsetlang.foldconcat),
2169 ('concatenated', revsetlang.foldconcat),
2170 ('analyzed', revsetlang.analyze),
2170 ('analyzed', revsetlang.analyze),
2171 ('optimized', revsetlang.optimize),
2171 ('optimized', revsetlang.optimize),
2172 ]
2172 ]
2173 if opts['no_optimized']:
2173 if opts['no_optimized']:
2174 stages = stages[:-1]
2174 stages = stages[:-1]
2175 if opts['verify_optimized'] and opts['no_optimized']:
2175 if opts['verify_optimized'] and opts['no_optimized']:
2176 raise error.Abort(_('cannot use --verify-optimized with '
2176 raise error.Abort(_('cannot use --verify-optimized with '
2177 '--no-optimized'))
2177 '--no-optimized'))
2178 stagenames = set(n for n, f in stages)
2178 stagenames = set(n for n, f in stages)
2179
2179
2180 showalways = set()
2180 showalways = set()
2181 showchanged = set()
2181 showchanged = set()
2182 if ui.verbose and not opts['show_stage']:
2182 if ui.verbose and not opts['show_stage']:
2183 # show parsed tree by --verbose (deprecated)
2183 # show parsed tree by --verbose (deprecated)
2184 showalways.add('parsed')
2184 showalways.add('parsed')
2185 showchanged.update(['expanded', 'concatenated'])
2185 showchanged.update(['expanded', 'concatenated'])
2186 if opts['optimize']:
2186 if opts['optimize']:
2187 showalways.add('optimized')
2187 showalways.add('optimized')
2188 if opts['show_stage'] and opts['optimize']:
2188 if opts['show_stage'] and opts['optimize']:
2189 raise error.Abort(_('cannot use --optimize with --show-stage'))
2189 raise error.Abort(_('cannot use --optimize with --show-stage'))
2190 if opts['show_stage'] == ['all']:
2190 if opts['show_stage'] == ['all']:
2191 showalways.update(stagenames)
2191 showalways.update(stagenames)
2192 else:
2192 else:
2193 for n in opts['show_stage']:
2193 for n in opts['show_stage']:
2194 if n not in stagenames:
2194 if n not in stagenames:
2195 raise error.Abort(_('invalid stage name: %s') % n)
2195 raise error.Abort(_('invalid stage name: %s') % n)
2196 showalways.update(opts['show_stage'])
2196 showalways.update(opts['show_stage'])
2197
2197
2198 treebystage = {}
2198 treebystage = {}
2199 printedtree = None
2199 printedtree = None
2200 tree = revsetlang.parse(expr, lookup=repo.__contains__)
2200 tree = revsetlang.parse(expr, lookup=repo.__contains__)
2201 for n, f in stages:
2201 for n, f in stages:
2202 treebystage[n] = tree = f(tree)
2202 treebystage[n] = tree = f(tree)
2203 if n in showalways or (n in showchanged and tree != printedtree):
2203 if n in showalways or (n in showchanged and tree != printedtree):
2204 if opts['show_stage'] or n != 'parsed':
2204 if opts['show_stage'] or n != 'parsed':
2205 ui.write(("* %s:\n") % n)
2205 ui.write(("* %s:\n") % n)
2206 ui.write(revsetlang.prettyformat(tree), "\n")
2206 ui.write(revsetlang.prettyformat(tree), "\n")
2207 printedtree = tree
2207 printedtree = tree
2208
2208
2209 if opts['verify_optimized']:
2209 if opts['verify_optimized']:
2210 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2210 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2211 brevs = revset.makematcher(treebystage['optimized'])(repo)
2211 brevs = revset.makematcher(treebystage['optimized'])(repo)
2212 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2212 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2213 ui.write(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
2213 ui.write(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
2214 ui.write(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
2214 ui.write(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
2215 arevs = list(arevs)
2215 arevs = list(arevs)
2216 brevs = list(brevs)
2216 brevs = list(brevs)
2217 if arevs == brevs:
2217 if arevs == brevs:
2218 return 0
2218 return 0
2219 ui.write(('--- analyzed\n'), label='diff.file_a')
2219 ui.write(('--- analyzed\n'), label='diff.file_a')
2220 ui.write(('+++ optimized\n'), label='diff.file_b')
2220 ui.write(('+++ optimized\n'), label='diff.file_b')
2221 sm = difflib.SequenceMatcher(None, arevs, brevs)
2221 sm = difflib.SequenceMatcher(None, arevs, brevs)
2222 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2222 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2223 if tag in ('delete', 'replace'):
2223 if tag in ('delete', 'replace'):
2224 for c in arevs[alo:ahi]:
2224 for c in arevs[alo:ahi]:
2225 ui.write('-%s\n' % c, label='diff.deleted')
2225 ui.write('-%s\n' % c, label='diff.deleted')
2226 if tag in ('insert', 'replace'):
2226 if tag in ('insert', 'replace'):
2227 for c in brevs[blo:bhi]:
2227 for c in brevs[blo:bhi]:
2228 ui.write('+%s\n' % c, label='diff.inserted')
2228 ui.write('+%s\n' % c, label='diff.inserted')
2229 if tag == 'equal':
2229 if tag == 'equal':
2230 for c in arevs[alo:ahi]:
2230 for c in arevs[alo:ahi]:
2231 ui.write(' %s\n' % c)
2231 ui.write(' %s\n' % c)
2232 return 1
2232 return 1
2233
2233
2234 func = revset.makematcher(tree)
2234 func = revset.makematcher(tree)
2235 revs = func(repo)
2235 revs = func(repo)
2236 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2236 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2237 ui.write(("* set:\n"), smartset.prettyformat(revs), "\n")
2237 ui.write(("* set:\n"), smartset.prettyformat(revs), "\n")
2238 if not opts['show_revs']:
2238 if not opts['show_revs']:
2239 return
2239 return
2240 for c in revs:
2240 for c in revs:
2241 ui.write("%d\n" % c)
2241 ui.write("%d\n" % c)
2242
2242
2243 @command('debugserve', [
2243 @command('debugserve', [
2244 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2244 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2245 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2245 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2246 ('', 'logiofile', '', _('file to log server I/O to')),
2246 ('', 'logiofile', '', _('file to log server I/O to')),
2247 ], '')
2247 ], '')
2248 def debugserve(ui, repo, **opts):
2248 def debugserve(ui, repo, **opts):
2249 """run a server with advanced settings
2249 """run a server with advanced settings
2250
2250
2251 This command is similar to :hg:`serve`. It exists partially as a
2251 This command is similar to :hg:`serve`. It exists partially as a
2252 workaround to the fact that ``hg serve --stdio`` must have specific
2252 workaround to the fact that ``hg serve --stdio`` must have specific
2253 arguments for security reasons.
2253 arguments for security reasons.
2254 """
2254 """
2255 opts = pycompat.byteskwargs(opts)
2255 opts = pycompat.byteskwargs(opts)
2256
2256
2257 if not opts['sshstdio']:
2257 if not opts['sshstdio']:
2258 raise error.Abort(_('only --sshstdio is currently supported'))
2258 raise error.Abort(_('only --sshstdio is currently supported'))
2259
2259
2260 logfh = None
2260 logfh = None
2261
2261
2262 if opts['logiofd'] and opts['logiofile']:
2262 if opts['logiofd'] and opts['logiofile']:
2263 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2263 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2264
2264
2265 if opts['logiofd']:
2265 if opts['logiofd']:
2266 # Line buffered because output is line based.
2266 # Line buffered because output is line based.
2267 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2267 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2268 elif opts['logiofile']:
2268 elif opts['logiofile']:
2269 logfh = open(opts['logiofile'], 'ab', 1)
2269 logfh = open(opts['logiofile'], 'ab', 1)
2270
2270
2271 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2271 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2272 s.serve_forever()
2272 s.serve_forever()
2273
2273
2274 @command('debugsetparents', [], _('REV1 [REV2]'))
2274 @command('debugsetparents', [], _('REV1 [REV2]'))
2275 def debugsetparents(ui, repo, rev1, rev2=None):
2275 def debugsetparents(ui, repo, rev1, rev2=None):
2276 """manually set the parents of the current working directory
2276 """manually set the parents of the current working directory
2277
2277
2278 This is useful for writing repository conversion tools, but should
2278 This is useful for writing repository conversion tools, but should
2279 be used with care. For example, neither the working directory nor the
2279 be used with care. For example, neither the working directory nor the
2280 dirstate is updated, so file status may be incorrect after running this
2280 dirstate is updated, so file status may be incorrect after running this
2281 command.
2281 command.
2282
2282
2283 Returns 0 on success.
2283 Returns 0 on success.
2284 """
2284 """
2285
2285
2286 r1 = scmutil.revsingle(repo, rev1).node()
2286 r1 = scmutil.revsingle(repo, rev1).node()
2287 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2287 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2288
2288
2289 with repo.wlock():
2289 with repo.wlock():
2290 repo.setparents(r1, r2)
2290 repo.setparents(r1, r2)
2291
2291
2292 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2292 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2293 def debugssl(ui, repo, source=None, **opts):
2293 def debugssl(ui, repo, source=None, **opts):
2294 '''test a secure connection to a server
2294 '''test a secure connection to a server
2295
2295
2296 This builds the certificate chain for the server on Windows, installing the
2296 This builds the certificate chain for the server on Windows, installing the
2297 missing intermediates and trusted root via Windows Update if necessary. It
2297 missing intermediates and trusted root via Windows Update if necessary. It
2298 does nothing on other platforms.
2298 does nothing on other platforms.
2299
2299
2300 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2300 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2301 that server is used. See :hg:`help urls` for more information.
2301 that server is used. See :hg:`help urls` for more information.
2302
2302
2303 If the update succeeds, retry the original operation. Otherwise, the cause
2303 If the update succeeds, retry the original operation. Otherwise, the cause
2304 of the SSL error is likely another issue.
2304 of the SSL error is likely another issue.
2305 '''
2305 '''
2306 if not pycompat.iswindows:
2306 if not pycompat.iswindows:
2307 raise error.Abort(_('certificate chain building is only possible on '
2307 raise error.Abort(_('certificate chain building is only possible on '
2308 'Windows'))
2308 'Windows'))
2309
2309
2310 if not source:
2310 if not source:
2311 if not repo:
2311 if not repo:
2312 raise error.Abort(_("there is no Mercurial repository here, and no "
2312 raise error.Abort(_("there is no Mercurial repository here, and no "
2313 "server specified"))
2313 "server specified"))
2314 source = "default"
2314 source = "default"
2315
2315
2316 source, branches = hg.parseurl(ui.expandpath(source))
2316 source, branches = hg.parseurl(ui.expandpath(source))
2317 url = util.url(source)
2317 url = util.url(source)
2318 addr = None
2318 addr = None
2319
2319
2320 defaultport = {'https': 443, 'ssh': 22}
2320 defaultport = {'https': 443, 'ssh': 22}
2321 if url.scheme in defaultport:
2321 if url.scheme in defaultport:
2322 try:
2322 try:
2323 addr = (url.host, int(url.port or defaultport[url.scheme]))
2323 addr = (url.host, int(url.port or defaultport[url.scheme]))
2324 except ValueError:
2324 except ValueError:
2325 raise error.Abort(_("malformed port number in URL"))
2325 raise error.Abort(_("malformed port number in URL"))
2326 else:
2326 else:
2327 raise error.Abort(_("only https and ssh connections are supported"))
2327 raise error.Abort(_("only https and ssh connections are supported"))
2328
2328
2329 from . import win32
2329 from . import win32
2330
2330
2331 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2331 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2332 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2332 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2333
2333
2334 try:
2334 try:
2335 s.connect(addr)
2335 s.connect(addr)
2336 cert = s.getpeercert(True)
2336 cert = s.getpeercert(True)
2337
2337
2338 ui.status(_('checking the certificate chain for %s\n') % url.host)
2338 ui.status(_('checking the certificate chain for %s\n') % url.host)
2339
2339
2340 complete = win32.checkcertificatechain(cert, build=False)
2340 complete = win32.checkcertificatechain(cert, build=False)
2341
2341
2342 if not complete:
2342 if not complete:
2343 ui.status(_('certificate chain is incomplete, updating... '))
2343 ui.status(_('certificate chain is incomplete, updating... '))
2344
2344
2345 if not win32.checkcertificatechain(cert):
2345 if not win32.checkcertificatechain(cert):
2346 ui.status(_('failed.\n'))
2346 ui.status(_('failed.\n'))
2347 else:
2347 else:
2348 ui.status(_('done.\n'))
2348 ui.status(_('done.\n'))
2349 else:
2349 else:
2350 ui.status(_('full certificate chain is available\n'))
2350 ui.status(_('full certificate chain is available\n'))
2351 finally:
2351 finally:
2352 s.close()
2352 s.close()
2353
2353
2354 @command('debugsub',
2354 @command('debugsub',
2355 [('r', 'rev', '',
2355 [('r', 'rev', '',
2356 _('revision to check'), _('REV'))],
2356 _('revision to check'), _('REV'))],
2357 _('[-r REV] [REV]'))
2357 _('[-r REV] [REV]'))
2358 def debugsub(ui, repo, rev=None):
2358 def debugsub(ui, repo, rev=None):
2359 ctx = scmutil.revsingle(repo, rev, None)
2359 ctx = scmutil.revsingle(repo, rev, None)
2360 for k, v in sorted(ctx.substate.items()):
2360 for k, v in sorted(ctx.substate.items()):
2361 ui.write(('path %s\n') % k)
2361 ui.write(('path %s\n') % k)
2362 ui.write((' source %s\n') % v[0])
2362 ui.write((' source %s\n') % v[0])
2363 ui.write((' revision %s\n') % v[1])
2363 ui.write((' revision %s\n') % v[1])
2364
2364
2365 @command('debugsuccessorssets',
2365 @command('debugsuccessorssets',
2366 [('', 'closest', False, _('return closest successors sets only'))],
2366 [('', 'closest', False, _('return closest successors sets only'))],
2367 _('[REV]'))
2367 _('[REV]'))
2368 def debugsuccessorssets(ui, repo, *revs, **opts):
2368 def debugsuccessorssets(ui, repo, *revs, **opts):
2369 """show set of successors for revision
2369 """show set of successors for revision
2370
2370
2371 A successors set of changeset A is a consistent group of revisions that
2371 A successors set of changeset A is a consistent group of revisions that
2372 succeed A. It contains non-obsolete changesets only unless closests
2372 succeed A. It contains non-obsolete changesets only unless closests
2373 successors set is set.
2373 successors set is set.
2374
2374
2375 In most cases a changeset A has a single successors set containing a single
2375 In most cases a changeset A has a single successors set containing a single
2376 successor (changeset A replaced by A').
2376 successor (changeset A replaced by A').
2377
2377
2378 A changeset that is made obsolete with no successors are called "pruned".
2378 A changeset that is made obsolete with no successors are called "pruned".
2379 Such changesets have no successors sets at all.
2379 Such changesets have no successors sets at all.
2380
2380
2381 A changeset that has been "split" will have a successors set containing
2381 A changeset that has been "split" will have a successors set containing
2382 more than one successor.
2382 more than one successor.
2383
2383
2384 A changeset that has been rewritten in multiple different ways is called
2384 A changeset that has been rewritten in multiple different ways is called
2385 "divergent". Such changesets have multiple successor sets (each of which
2385 "divergent". Such changesets have multiple successor sets (each of which
2386 may also be split, i.e. have multiple successors).
2386 may also be split, i.e. have multiple successors).
2387
2387
2388 Results are displayed as follows::
2388 Results are displayed as follows::
2389
2389
2390 <rev1>
2390 <rev1>
2391 <successors-1A>
2391 <successors-1A>
2392 <rev2>
2392 <rev2>
2393 <successors-2A>
2393 <successors-2A>
2394 <successors-2B1> <successors-2B2> <successors-2B3>
2394 <successors-2B1> <successors-2B2> <successors-2B3>
2395
2395
2396 Here rev2 has two possible (i.e. divergent) successors sets. The first
2396 Here rev2 has two possible (i.e. divergent) successors sets. The first
2397 holds one element, whereas the second holds three (i.e. the changeset has
2397 holds one element, whereas the second holds three (i.e. the changeset has
2398 been split).
2398 been split).
2399 """
2399 """
2400 # passed to successorssets caching computation from one call to another
2400 # passed to successorssets caching computation from one call to another
2401 cache = {}
2401 cache = {}
2402 ctx2str = bytes
2402 ctx2str = bytes
2403 node2str = short
2403 node2str = short
2404 for rev in scmutil.revrange(repo, revs):
2404 for rev in scmutil.revrange(repo, revs):
2405 ctx = repo[rev]
2405 ctx = repo[rev]
2406 ui.write('%s\n'% ctx2str(ctx))
2406 ui.write('%s\n'% ctx2str(ctx))
2407 for succsset in obsutil.successorssets(repo, ctx.node(),
2407 for succsset in obsutil.successorssets(repo, ctx.node(),
2408 closest=opts[r'closest'],
2408 closest=opts[r'closest'],
2409 cache=cache):
2409 cache=cache):
2410 if succsset:
2410 if succsset:
2411 ui.write(' ')
2411 ui.write(' ')
2412 ui.write(node2str(succsset[0]))
2412 ui.write(node2str(succsset[0]))
2413 for node in succsset[1:]:
2413 for node in succsset[1:]:
2414 ui.write(' ')
2414 ui.write(' ')
2415 ui.write(node2str(node))
2415 ui.write(node2str(node))
2416 ui.write('\n')
2416 ui.write('\n')
2417
2417
2418 @command('debugtemplate',
2418 @command('debugtemplate',
2419 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2419 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2420 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2420 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2421 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2421 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2422 optionalrepo=True)
2422 optionalrepo=True)
2423 def debugtemplate(ui, repo, tmpl, **opts):
2423 def debugtemplate(ui, repo, tmpl, **opts):
2424 """parse and apply a template
2424 """parse and apply a template
2425
2425
2426 If -r/--rev is given, the template is processed as a log template and
2426 If -r/--rev is given, the template is processed as a log template and
2427 applied to the given changesets. Otherwise, it is processed as a generic
2427 applied to the given changesets. Otherwise, it is processed as a generic
2428 template.
2428 template.
2429
2429
2430 Use --verbose to print the parsed tree.
2430 Use --verbose to print the parsed tree.
2431 """
2431 """
2432 revs = None
2432 revs = None
2433 if opts[r'rev']:
2433 if opts[r'rev']:
2434 if repo is None:
2434 if repo is None:
2435 raise error.RepoError(_('there is no Mercurial repository here '
2435 raise error.RepoError(_('there is no Mercurial repository here '
2436 '(.hg not found)'))
2436 '(.hg not found)'))
2437 revs = scmutil.revrange(repo, opts[r'rev'])
2437 revs = scmutil.revrange(repo, opts[r'rev'])
2438
2438
2439 props = {}
2439 props = {}
2440 for d in opts[r'define']:
2440 for d in opts[r'define']:
2441 try:
2441 try:
2442 k, v = (e.strip() for e in d.split('=', 1))
2442 k, v = (e.strip() for e in d.split('=', 1))
2443 if not k or k == 'ui':
2443 if not k or k == 'ui':
2444 raise ValueError
2444 raise ValueError
2445 props[k] = v
2445 props[k] = v
2446 except ValueError:
2446 except ValueError:
2447 raise error.Abort(_('malformed keyword definition: %s') % d)
2447 raise error.Abort(_('malformed keyword definition: %s') % d)
2448
2448
2449 if ui.verbose:
2449 if ui.verbose:
2450 aliases = ui.configitems('templatealias')
2450 aliases = ui.configitems('templatealias')
2451 tree = templater.parse(tmpl)
2451 tree = templater.parse(tmpl)
2452 ui.note(templater.prettyformat(tree), '\n')
2452 ui.note(templater.prettyformat(tree), '\n')
2453 newtree = templater.expandaliases(tree, aliases)
2453 newtree = templater.expandaliases(tree, aliases)
2454 if newtree != tree:
2454 if newtree != tree:
2455 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2455 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2456
2456
2457 if revs is None:
2457 if revs is None:
2458 tres = formatter.templateresources(ui, repo)
2458 tres = formatter.templateresources(ui, repo)
2459 t = formatter.maketemplater(ui, tmpl, resources=tres)
2459 t = formatter.maketemplater(ui, tmpl, resources=tres)
2460 ui.write(t.renderdefault(props))
2460 ui.write(t.renderdefault(props))
2461 else:
2461 else:
2462 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2462 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2463 for r in revs:
2463 for r in revs:
2464 displayer.show(repo[r], **pycompat.strkwargs(props))
2464 displayer.show(repo[r], **pycompat.strkwargs(props))
2465 displayer.close()
2465 displayer.close()
2466
2466
2467 @command('debuguigetpass', [
2467 @command('debuguigetpass', [
2468 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2468 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2469 ], _('[-p TEXT]'), norepo=True)
2469 ], _('[-p TEXT]'), norepo=True)
2470 def debuguigetpass(ui, prompt=''):
2470 def debuguigetpass(ui, prompt=''):
2471 """show prompt to type password"""
2471 """show prompt to type password"""
2472 r = ui.getpass(prompt)
2472 r = ui.getpass(prompt)
2473 ui.write(('respose: %s\n') % r)
2473 ui.write(('respose: %s\n') % r)
2474
2474
2475 @command('debuguiprompt', [
2475 @command('debuguiprompt', [
2476 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2476 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2477 ], _('[-p TEXT]'), norepo=True)
2477 ], _('[-p TEXT]'), norepo=True)
2478 def debuguiprompt(ui, prompt=''):
2478 def debuguiprompt(ui, prompt=''):
2479 """show plain prompt"""
2479 """show plain prompt"""
2480 r = ui.prompt(prompt)
2480 r = ui.prompt(prompt)
2481 ui.write(('response: %s\n') % r)
2481 ui.write(('response: %s\n') % r)
2482
2482
2483 @command('debugupdatecaches', [])
2483 @command('debugupdatecaches', [])
2484 def debugupdatecaches(ui, repo, *pats, **opts):
2484 def debugupdatecaches(ui, repo, *pats, **opts):
2485 """warm all known caches in the repository"""
2485 """warm all known caches in the repository"""
2486 with repo.wlock(), repo.lock():
2486 with repo.wlock(), repo.lock():
2487 repo.updatecaches(full=True)
2487 repo.updatecaches(full=True)
2488
2488
2489 @command('debugupgraderepo', [
2489 @command('debugupgraderepo', [
2490 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2490 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2491 ('', 'run', False, _('performs an upgrade')),
2491 ('', 'run', False, _('performs an upgrade')),
2492 ])
2492 ])
2493 def debugupgraderepo(ui, repo, run=False, optimize=None):
2493 def debugupgraderepo(ui, repo, run=False, optimize=None):
2494 """upgrade a repository to use different features
2494 """upgrade a repository to use different features
2495
2495
2496 If no arguments are specified, the repository is evaluated for upgrade
2496 If no arguments are specified, the repository is evaluated for upgrade
2497 and a list of problems and potential optimizations is printed.
2497 and a list of problems and potential optimizations is printed.
2498
2498
2499 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2499 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2500 can be influenced via additional arguments. More details will be provided
2500 can be influenced via additional arguments. More details will be provided
2501 by the command output when run without ``--run``.
2501 by the command output when run without ``--run``.
2502
2502
2503 During the upgrade, the repository will be locked and no writes will be
2503 During the upgrade, the repository will be locked and no writes will be
2504 allowed.
2504 allowed.
2505
2505
2506 At the end of the upgrade, the repository may not be readable while new
2506 At the end of the upgrade, the repository may not be readable while new
2507 repository data is swapped in. This window will be as long as it takes to
2507 repository data is swapped in. This window will be as long as it takes to
2508 rename some directories inside the ``.hg`` directory. On most machines, this
2508 rename some directories inside the ``.hg`` directory. On most machines, this
2509 should complete almost instantaneously and the chances of a consumer being
2509 should complete almost instantaneously and the chances of a consumer being
2510 unable to access the repository should be low.
2510 unable to access the repository should be low.
2511 """
2511 """
2512 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2512 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2513
2513
2514 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2514 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2515 inferrepo=True)
2515 inferrepo=True)
2516 def debugwalk(ui, repo, *pats, **opts):
2516 def debugwalk(ui, repo, *pats, **opts):
2517 """show how files match on given patterns"""
2517 """show how files match on given patterns"""
2518 opts = pycompat.byteskwargs(opts)
2518 opts = pycompat.byteskwargs(opts)
2519 m = scmutil.match(repo[None], pats, opts)
2519 m = scmutil.match(repo[None], pats, opts)
2520 ui.write(('matcher: %r\n' % m))
2520 ui.write(('matcher: %r\n' % m))
2521 items = list(repo[None].walk(m))
2521 items = list(repo[None].walk(m))
2522 if not items:
2522 if not items:
2523 return
2523 return
2524 f = lambda fn: fn
2524 f = lambda fn: fn
2525 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2525 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2526 f = lambda fn: util.normpath(fn)
2526 f = lambda fn: util.normpath(fn)
2527 fmt = 'f %%-%ds %%-%ds %%s' % (
2527 fmt = 'f %%-%ds %%-%ds %%s' % (
2528 max([len(abs) for abs in items]),
2528 max([len(abs) for abs in items]),
2529 max([len(m.rel(abs)) for abs in items]))
2529 max([len(m.rel(abs)) for abs in items]))
2530 for abs in items:
2530 for abs in items:
2531 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2531 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2532 ui.write("%s\n" % line.rstrip())
2532 ui.write("%s\n" % line.rstrip())
2533
2533
2534 @command('debugwhyunstable', [], _('REV'))
2534 @command('debugwhyunstable', [], _('REV'))
2535 def debugwhyunstable(ui, repo, rev):
2535 def debugwhyunstable(ui, repo, rev):
2536 """explain instabilities of a changeset"""
2536 """explain instabilities of a changeset"""
2537 for entry in obsutil.whyunstable(repo, repo[rev]):
2537 for entry in obsutil.whyunstable(repo, repo[rev]):
2538 dnodes = ''
2538 dnodes = ''
2539 if entry.get('divergentnodes'):
2539 if entry.get('divergentnodes'):
2540 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2540 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2541 for ctx in entry['divergentnodes']) + ' '
2541 for ctx in entry['divergentnodes']) + ' '
2542 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2542 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2543 entry['reason'], entry['node']))
2543 entry['reason'], entry['node']))
2544
2544
2545 @command('debugwireargs',
2545 @command('debugwireargs',
2546 [('', 'three', '', 'three'),
2546 [('', 'three', '', 'three'),
2547 ('', 'four', '', 'four'),
2547 ('', 'four', '', 'four'),
2548 ('', 'five', '', 'five'),
2548 ('', 'five', '', 'five'),
2549 ] + cmdutil.remoteopts,
2549 ] + cmdutil.remoteopts,
2550 _('REPO [OPTIONS]... [ONE [TWO]]'),
2550 _('REPO [OPTIONS]... [ONE [TWO]]'),
2551 norepo=True)
2551 norepo=True)
2552 def debugwireargs(ui, repopath, *vals, **opts):
2552 def debugwireargs(ui, repopath, *vals, **opts):
2553 opts = pycompat.byteskwargs(opts)
2553 opts = pycompat.byteskwargs(opts)
2554 repo = hg.peer(ui, opts, repopath)
2554 repo = hg.peer(ui, opts, repopath)
2555 for opt in cmdutil.remoteopts:
2555 for opt in cmdutil.remoteopts:
2556 del opts[opt[1]]
2556 del opts[opt[1]]
2557 args = {}
2557 args = {}
2558 for k, v in opts.iteritems():
2558 for k, v in opts.iteritems():
2559 if v:
2559 if v:
2560 args[k] = v
2560 args[k] = v
2561 args = pycompat.strkwargs(args)
2561 args = pycompat.strkwargs(args)
2562 # run twice to check that we don't mess up the stream for the next command
2562 # run twice to check that we don't mess up the stream for the next command
2563 res1 = repo.debugwireargs(*vals, **args)
2563 res1 = repo.debugwireargs(*vals, **args)
2564 res2 = repo.debugwireargs(*vals, **args)
2564 res2 = repo.debugwireargs(*vals, **args)
2565 ui.write("%s\n" % res1)
2565 ui.write("%s\n" % res1)
2566 if res1 != res2:
2566 if res1 != res2:
2567 ui.warn("%s\n" % res2)
2567 ui.warn("%s\n" % res2)
2568
2568
2569 def _parsewirelangblocks(fh):
2569 def _parsewirelangblocks(fh):
2570 activeaction = None
2570 activeaction = None
2571 blocklines = []
2571 blocklines = []
2572
2572
2573 for line in fh:
2573 for line in fh:
2574 line = line.rstrip()
2574 line = line.rstrip()
2575 if not line:
2575 if not line:
2576 continue
2576 continue
2577
2577
2578 if line.startswith(b'#'):
2578 if line.startswith(b'#'):
2579 continue
2579 continue
2580
2580
2581 if not line.startswith(' '):
2581 if not line.startswith(' '):
2582 # New block. Flush previous one.
2582 # New block. Flush previous one.
2583 if activeaction:
2583 if activeaction:
2584 yield activeaction, blocklines
2584 yield activeaction, blocklines
2585
2585
2586 activeaction = line
2586 activeaction = line
2587 blocklines = []
2587 blocklines = []
2588 continue
2588 continue
2589
2589
2590 # Else we start with an indent.
2590 # Else we start with an indent.
2591
2591
2592 if not activeaction:
2592 if not activeaction:
2593 raise error.Abort(_('indented line outside of block'))
2593 raise error.Abort(_('indented line outside of block'))
2594
2594
2595 blocklines.append(line)
2595 blocklines.append(line)
2596
2596
2597 # Flush last block.
2597 # Flush last block.
2598 if activeaction:
2598 if activeaction:
2599 yield activeaction, blocklines
2599 yield activeaction, blocklines
2600
2600
2601 @command('debugwireproto',
2601 @command('debugwireproto',
2602 [
2602 [
2603 ('', 'localssh', False, _('start an SSH server for this repo')),
2603 ('', 'localssh', False, _('start an SSH server for this repo')),
2604 ('', 'peer', '', _('construct a specific version of the peer')),
2604 ('', 'peer', '', _('construct a specific version of the peer')),
2605 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2605 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2606 ] + cmdutil.remoteopts,
2606 ] + cmdutil.remoteopts,
2607 _('[PATH]'),
2607 _('[PATH]'),
2608 optionalrepo=True)
2608 optionalrepo=True)
2609 def debugwireproto(ui, repo, path=None, **opts):
2609 def debugwireproto(ui, repo, path=None, **opts):
2610 """send wire protocol commands to a server
2610 """send wire protocol commands to a server
2611
2611
2612 This command can be used to issue wire protocol commands to remote
2612 This command can be used to issue wire protocol commands to remote
2613 peers and to debug the raw data being exchanged.
2613 peers and to debug the raw data being exchanged.
2614
2614
2615 ``--localssh`` will start an SSH server against the current repository
2615 ``--localssh`` will start an SSH server against the current repository
2616 and connect to that. By default, the connection will perform a handshake
2616 and connect to that. By default, the connection will perform a handshake
2617 and establish an appropriate peer instance.
2617 and establish an appropriate peer instance.
2618
2618
2619 ``--peer`` can be used to bypass the handshake protocol and construct a
2619 ``--peer`` can be used to bypass the handshake protocol and construct a
2620 peer instance using the specified class type. Valid values are ``raw``,
2620 peer instance using the specified class type. Valid values are ``raw``,
2621 ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data
2621 ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data
2622 payloads and don't support higher-level command actions.
2622 payloads and don't support higher-level command actions.
2623
2623
2624 ``--noreadstderr`` can be used to disable automatic reading from stderr
2624 ``--noreadstderr`` can be used to disable automatic reading from stderr
2625 of the peer (for SSH connections only). Disabling automatic reading of
2625 of the peer (for SSH connections only). Disabling automatic reading of
2626 stderr is useful for making output more deterministic.
2626 stderr is useful for making output more deterministic.
2627
2627
2628 Commands are issued via a mini language which is specified via stdin.
2628 Commands are issued via a mini language which is specified via stdin.
2629 The language consists of individual actions to perform. An action is
2629 The language consists of individual actions to perform. An action is
2630 defined by a block. A block is defined as a line with no leading
2630 defined by a block. A block is defined as a line with no leading
2631 space followed by 0 or more lines with leading space. Blocks are
2631 space followed by 0 or more lines with leading space. Blocks are
2632 effectively a high-level command with additional metadata.
2632 effectively a high-level command with additional metadata.
2633
2633
2634 Lines beginning with ``#`` are ignored.
2634 Lines beginning with ``#`` are ignored.
2635
2635
2636 The following sections denote available actions.
2636 The following sections denote available actions.
2637
2637
2638 raw
2638 raw
2639 ---
2639 ---
2640
2640
2641 Send raw data to the server.
2641 Send raw data to the server.
2642
2642
2643 The block payload contains the raw data to send as one atomic send
2643 The block payload contains the raw data to send as one atomic send
2644 operation. The data may not actually be delivered in a single system
2644 operation. The data may not actually be delivered in a single system
2645 call: it depends on the abilities of the transport being used.
2645 call: it depends on the abilities of the transport being used.
2646
2646
2647 Each line in the block is de-indented and concatenated. Then, that
2647 Each line in the block is de-indented and concatenated. Then, that
2648 value is evaluated as a Python b'' literal. This allows the use of
2648 value is evaluated as a Python b'' literal. This allows the use of
2649 backslash escaping, etc.
2649 backslash escaping, etc.
2650
2650
2651 raw+
2651 raw+
2652 ----
2652 ----
2653
2653
2654 Behaves like ``raw`` except flushes output afterwards.
2654 Behaves like ``raw`` except flushes output afterwards.
2655
2655
2656 command <X>
2656 command <X>
2657 -----------
2657 -----------
2658
2658
2659 Send a request to run a named command, whose name follows the ``command``
2659 Send a request to run a named command, whose name follows the ``command``
2660 string.
2660 string.
2661
2661
2662 Arguments to the command are defined as lines in this block. The format of
2662 Arguments to the command are defined as lines in this block. The format of
2663 each line is ``<key> <value>``. e.g.::
2663 each line is ``<key> <value>``. e.g.::
2664
2664
2665 command listkeys
2665 command listkeys
2666 namespace bookmarks
2666 namespace bookmarks
2667
2667
2668 Values are interpreted as Python b'' literals. This allows encoding
2668 Values are interpreted as Python b'' literals. This allows encoding
2669 special byte sequences via backslash escaping.
2669 special byte sequences via backslash escaping.
2670
2670
2671 The following arguments have special meaning:
2671 The following arguments have special meaning:
2672
2672
2673 ``PUSHFILE``
2673 ``PUSHFILE``
2674 When defined, the *push* mechanism of the peer will be used instead
2674 When defined, the *push* mechanism of the peer will be used instead
2675 of the static request-response mechanism and the content of the
2675 of the static request-response mechanism and the content of the
2676 file specified in the value of this argument will be sent as the
2676 file specified in the value of this argument will be sent as the
2677 command payload.
2677 command payload.
2678
2678
2679 This can be used to submit a local bundle file to the remote.
2679 This can be used to submit a local bundle file to the remote.
2680
2680
2681 batchbegin
2681 batchbegin
2682 ----------
2682 ----------
2683
2683
2684 Instruct the peer to begin a batched send.
2684 Instruct the peer to begin a batched send.
2685
2685
2686 All ``command`` blocks are queued for execution until the next
2686 All ``command`` blocks are queued for execution until the next
2687 ``batchsubmit`` block.
2687 ``batchsubmit`` block.
2688
2688
2689 batchsubmit
2689 batchsubmit
2690 -----------
2690 -----------
2691
2691
2692 Submit previously queued ``command`` blocks as a batch request.
2692 Submit previously queued ``command`` blocks as a batch request.
2693
2693
2694 This action MUST be paired with a ``batchbegin`` action.
2694 This action MUST be paired with a ``batchbegin`` action.
2695
2695
2696 httprequest <method> <path>
2696 httprequest <method> <path>
2697 ---------------------------
2697 ---------------------------
2698
2698
2699 (HTTP peer only)
2699 (HTTP peer only)
2700
2700
2701 Send an HTTP request to the peer.
2701 Send an HTTP request to the peer.
2702
2702
2703 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2703 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2704
2704
2705 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2705 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2706 headers to add to the request. e.g. ``Accept: foo``.
2706 headers to add to the request. e.g. ``Accept: foo``.
2707
2707
2708 The following arguments are special:
2708 The following arguments are special:
2709
2709
2710 ``BODYFILE``
2710 ``BODYFILE``
2711 The content of the file defined as the value to this argument will be
2711 The content of the file defined as the value to this argument will be
2712 transferred verbatim as the HTTP request body.
2712 transferred verbatim as the HTTP request body.
2713
2713
2714 close
2714 close
2715 -----
2715 -----
2716
2716
2717 Close the connection to the server.
2717 Close the connection to the server.
2718
2718
2719 flush
2719 flush
2720 -----
2720 -----
2721
2721
2722 Flush data written to the server.
2722 Flush data written to the server.
2723
2723
2724 readavailable
2724 readavailable
2725 -------------
2725 -------------
2726
2726
2727 Close the write end of the connection and read all available data from
2727 Close the write end of the connection and read all available data from
2728 the server.
2728 the server.
2729
2729
2730 If the connection to the server encompasses multiple pipes, we poll both
2730 If the connection to the server encompasses multiple pipes, we poll both
2731 pipes and read available data.
2731 pipes and read available data.
2732
2732
2733 readline
2733 readline
2734 --------
2734 --------
2735
2735
2736 Read a line of output from the server. If there are multiple output
2736 Read a line of output from the server. If there are multiple output
2737 pipes, reads only the main pipe.
2737 pipes, reads only the main pipe.
2738
2738
2739 ereadline
2739 ereadline
2740 ---------
2740 ---------
2741
2741
2742 Like ``readline``, but read from the stderr pipe, if available.
2742 Like ``readline``, but read from the stderr pipe, if available.
2743
2743
2744 read <X>
2744 read <X>
2745 --------
2745 --------
2746
2746
2747 ``read()`` N bytes from the server's main output pipe.
2747 ``read()`` N bytes from the server's main output pipe.
2748
2748
2749 eread <X>
2749 eread <X>
2750 ---------
2750 ---------
2751
2751
2752 ``read()`` N bytes from the server's stderr pipe, if available.
2752 ``read()`` N bytes from the server's stderr pipe, if available.
2753 """
2753 """
2754 opts = pycompat.byteskwargs(opts)
2754 opts = pycompat.byteskwargs(opts)
2755
2755
2756 if opts['localssh'] and not repo:
2756 if opts['localssh'] and not repo:
2757 raise error.Abort(_('--localssh requires a repository'))
2757 raise error.Abort(_('--localssh requires a repository'))
2758
2758
2759 if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'):
2759 if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'):
2760 raise error.Abort(_('invalid value for --peer'),
2760 raise error.Abort(_('invalid value for --peer'),
2761 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2761 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2762
2762
2763 if path and opts['localssh']:
2763 if path and opts['localssh']:
2764 raise error.Abort(_('cannot specify --localssh with an explicit '
2764 raise error.Abort(_('cannot specify --localssh with an explicit '
2765 'path'))
2765 'path'))
2766
2766
2767 if ui.interactive():
2767 if ui.interactive():
2768 ui.write(_('(waiting for commands on stdin)\n'))
2768 ui.write(_('(waiting for commands on stdin)\n'))
2769
2769
2770 blocks = list(_parsewirelangblocks(ui.fin))
2770 blocks = list(_parsewirelangblocks(ui.fin))
2771
2771
2772 proc = None
2772 proc = None
2773 stdin = None
2773 stdin = None
2774 stdout = None
2774 stdout = None
2775 stderr = None
2775 stderr = None
2776 opener = None
2776 opener = None
2777
2777
2778 if opts['localssh']:
2778 if opts['localssh']:
2779 # We start the SSH server in its own process so there is process
2779 # We start the SSH server in its own process so there is process
2780 # separation. This prevents a whole class of potential bugs around
2780 # separation. This prevents a whole class of potential bugs around
2781 # shared state from interfering with server operation.
2781 # shared state from interfering with server operation.
2782 args = util.hgcmd() + [
2782 args = util.hgcmd() + [
2783 '-R', repo.root,
2783 '-R', repo.root,
2784 'debugserve', '--sshstdio',
2784 'debugserve', '--sshstdio',
2785 ]
2785 ]
2786 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
2786 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
2787 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
2787 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
2788 bufsize=0)
2788 bufsize=0)
2789
2789
2790 stdin = proc.stdin
2790 stdin = proc.stdin
2791 stdout = proc.stdout
2791 stdout = proc.stdout
2792 stderr = proc.stderr
2792 stderr = proc.stderr
2793
2793
2794 # We turn the pipes into observers so we can log I/O.
2794 # We turn the pipes into observers so we can log I/O.
2795 if ui.verbose or opts['peer'] == 'raw':
2795 if ui.verbose or opts['peer'] == 'raw':
2796 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
2796 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
2797 logdata=True)
2797 logdata=True)
2798 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
2798 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
2799 logdata=True)
2799 logdata=True)
2800 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
2800 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
2801 logdata=True)
2801 logdata=True)
2802
2802
2803 # --localssh also implies the peer connection settings.
2803 # --localssh also implies the peer connection settings.
2804
2804
2805 url = 'ssh://localserver'
2805 url = 'ssh://localserver'
2806 autoreadstderr = not opts['noreadstderr']
2806 autoreadstderr = not opts['noreadstderr']
2807
2807
2808 if opts['peer'] == 'ssh1':
2808 if opts['peer'] == 'ssh1':
2809 ui.write(_('creating ssh peer for wire protocol version 1\n'))
2809 ui.write(_('creating ssh peer for wire protocol version 1\n'))
2810 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
2810 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
2811 None, autoreadstderr=autoreadstderr)
2811 None, autoreadstderr=autoreadstderr)
2812 elif opts['peer'] == 'ssh2':
2812 elif opts['peer'] == 'ssh2':
2813 ui.write(_('creating ssh peer for wire protocol version 2\n'))
2813 ui.write(_('creating ssh peer for wire protocol version 2\n'))
2814 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
2814 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
2815 None, autoreadstderr=autoreadstderr)
2815 None, autoreadstderr=autoreadstderr)
2816 elif opts['peer'] == 'raw':
2816 elif opts['peer'] == 'raw':
2817 ui.write(_('using raw connection to peer\n'))
2817 ui.write(_('using raw connection to peer\n'))
2818 peer = None
2818 peer = None
2819 else:
2819 else:
2820 ui.write(_('creating ssh peer from handshake results\n'))
2820 ui.write(_('creating ssh peer from handshake results\n'))
2821 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2821 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2822 autoreadstderr=autoreadstderr)
2822 autoreadstderr=autoreadstderr)
2823
2823
2824 elif path:
2824 elif path:
2825 # We bypass hg.peer() so we can proxy the sockets.
2825 # We bypass hg.peer() so we can proxy the sockets.
2826 # TODO consider not doing this because we skip
2826 # TODO consider not doing this because we skip
2827 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
2827 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
2828 u = util.url(path)
2828 u = util.url(path)
2829 if u.scheme != 'http':
2829 if u.scheme != 'http':
2830 raise error.Abort(_('only http:// paths are currently supported'))
2830 raise error.Abort(_('only http:// paths are currently supported'))
2831
2831
2832 url, authinfo = u.authinfo()
2832 url, authinfo = u.authinfo()
2833 openerargs = {}
2833 openerargs = {}
2834
2834
2835 # Turn pipes/sockets into observers so we can log I/O.
2835 # Turn pipes/sockets into observers so we can log I/O.
2836 if ui.verbose:
2836 if ui.verbose:
2837 openerargs = {
2837 openerargs = {
2838 r'loggingfh': ui,
2838 r'loggingfh': ui,
2839 r'loggingname': b's',
2839 r'loggingname': b's',
2840 r'loggingopts': {
2840 r'loggingopts': {
2841 r'logdata': True,
2841 r'logdata': True,
2842 r'logdataapis': False,
2842 r'logdataapis': False,
2843 },
2843 },
2844 }
2844 }
2845
2845
2846 if ui.debugflag:
2846 if ui.debugflag:
2847 openerargs[r'loggingopts'][r'logdataapis'] = True
2847 openerargs[r'loggingopts'][r'logdataapis'] = True
2848
2848
2849 # Don't send default headers when in raw mode. This allows us to
2849 # Don't send default headers when in raw mode. This allows us to
2850 # bypass most of the behavior of our URL handling code so we can
2850 # bypass most of the behavior of our URL handling code so we can
2851 # have near complete control over what's sent on the wire.
2851 # have near complete control over what's sent on the wire.
2852 if opts['peer'] == 'raw':
2852 if opts['peer'] == 'raw':
2853 openerargs[r'sendaccept'] = False
2853 openerargs[r'sendaccept'] = False
2854
2854
2855 opener = urlmod.opener(ui, authinfo, **openerargs)
2855 opener = urlmod.opener(ui, authinfo, **openerargs)
2856
2856
2857 if opts['peer'] == 'raw':
2857 if opts['peer'] == 'raw':
2858 ui.write(_('using raw connection to peer\n'))
2858 ui.write(_('using raw connection to peer\n'))
2859 peer = None
2859 peer = None
2860 elif opts['peer']:
2860 elif opts['peer']:
2861 raise error.Abort(_('--peer %s not supported with HTTP peers') %
2861 raise error.Abort(_('--peer %s not supported with HTTP peers') %
2862 opts['peer'])
2862 opts['peer'])
2863 else:
2863 else:
2864 peer = httppeer.httppeer(ui, path, url, opener)
2864 peer = httppeer.httppeer(ui, path, url, opener)
2865 peer._fetchcaps()
2865 peer._fetchcaps()
2866
2866
2867 # We /could/ populate stdin/stdout with sock.makefile()...
2867 # We /could/ populate stdin/stdout with sock.makefile()...
2868 else:
2868 else:
2869 raise error.Abort(_('unsupported connection configuration'))
2869 raise error.Abort(_('unsupported connection configuration'))
2870
2870
2871 batchedcommands = None
2871 batchedcommands = None
2872
2872
2873 # Now perform actions based on the parsed wire language instructions.
2873 # Now perform actions based on the parsed wire language instructions.
2874 for action, lines in blocks:
2874 for action, lines in blocks:
2875 if action in ('raw', 'raw+'):
2875 if action in ('raw', 'raw+'):
2876 if not stdin:
2876 if not stdin:
2877 raise error.Abort(_('cannot call raw/raw+ on this peer'))
2877 raise error.Abort(_('cannot call raw/raw+ on this peer'))
2878
2878
2879 # Concatenate the data together.
2879 # Concatenate the data together.
2880 data = ''.join(l.lstrip() for l in lines)
2880 data = ''.join(l.lstrip() for l in lines)
2881 data = util.unescapestr(data)
2881 data = util.unescapestr(data)
2882 stdin.write(data)
2882 stdin.write(data)
2883
2883
2884 if action == 'raw+':
2884 if action == 'raw+':
2885 stdin.flush()
2885 stdin.flush()
2886 elif action == 'flush':
2886 elif action == 'flush':
2887 if not stdin:
2887 if not stdin:
2888 raise error.Abort(_('cannot call flush on this peer'))
2888 raise error.Abort(_('cannot call flush on this peer'))
2889 stdin.flush()
2889 stdin.flush()
2890 elif action.startswith('command'):
2890 elif action.startswith('command'):
2891 if not peer:
2891 if not peer:
2892 raise error.Abort(_('cannot send commands unless peer instance '
2892 raise error.Abort(_('cannot send commands unless peer instance '
2893 'is available'))
2893 'is available'))
2894
2894
2895 command = action.split(' ', 1)[1]
2895 command = action.split(' ', 1)[1]
2896
2896
2897 args = {}
2897 args = {}
2898 for line in lines:
2898 for line in lines:
2899 # We need to allow empty values.
2899 # We need to allow empty values.
2900 fields = line.lstrip().split(' ', 1)
2900 fields = line.lstrip().split(' ', 1)
2901 if len(fields) == 1:
2901 if len(fields) == 1:
2902 key = fields[0]
2902 key = fields[0]
2903 value = ''
2903 value = ''
2904 else:
2904 else:
2905 key, value = fields
2905 key, value = fields
2906
2906
2907 args[key] = util.unescapestr(value)
2907 args[key] = util.unescapestr(value)
2908
2908
2909 if batchedcommands is not None:
2909 if batchedcommands is not None:
2910 batchedcommands.append((command, args))
2910 batchedcommands.append((command, args))
2911 continue
2911 continue
2912
2912
2913 ui.status(_('sending %s command\n') % command)
2913 ui.status(_('sending %s command\n') % command)
2914
2914
2915 if 'PUSHFILE' in args:
2915 if 'PUSHFILE' in args:
2916 with open(args['PUSHFILE'], r'rb') as fh:
2916 with open(args['PUSHFILE'], r'rb') as fh:
2917 del args['PUSHFILE']
2917 del args['PUSHFILE']
2918 res, output = peer._callpush(command, fh,
2918 res, output = peer._callpush(command, fh,
2919 **pycompat.strkwargs(args))
2919 **pycompat.strkwargs(args))
2920 ui.status(_('result: %s\n') % util.escapedata(res))
2920 ui.status(_('result: %s\n') % util.escapedata(res))
2921 ui.status(_('remote output: %s\n') %
2921 ui.status(_('remote output: %s\n') %
2922 util.escapedata(output))
2922 util.escapedata(output))
2923 else:
2923 else:
2924 res = peer._call(command, **pycompat.strkwargs(args))
2924 res = peer._call(command, **pycompat.strkwargs(args))
2925 ui.status(_('response: %s\n') % util.escapedata(res))
2925 ui.status(_('response: %s\n') % util.escapedata(res))
2926
2926
2927 elif action == 'batchbegin':
2927 elif action == 'batchbegin':
2928 if batchedcommands is not None:
2928 if batchedcommands is not None:
2929 raise error.Abort(_('nested batchbegin not allowed'))
2929 raise error.Abort(_('nested batchbegin not allowed'))
2930
2930
2931 batchedcommands = []
2931 batchedcommands = []
2932 elif action == 'batchsubmit':
2932 elif action == 'batchsubmit':
2933 # There is a batching API we could go through. But it would be
2933 # There is a batching API we could go through. But it would be
2934 # difficult to normalize requests into function calls. It is easier
2934 # difficult to normalize requests into function calls. It is easier
2935 # to bypass this layer and normalize to commands + args.
2935 # to bypass this layer and normalize to commands + args.
2936 ui.status(_('sending batch with %d sub-commands\n') %
2936 ui.status(_('sending batch with %d sub-commands\n') %
2937 len(batchedcommands))
2937 len(batchedcommands))
2938 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
2938 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
2939 ui.status(_('response #%d: %s\n') % (i, util.escapedata(chunk)))
2939 ui.status(_('response #%d: %s\n') % (i, util.escapedata(chunk)))
2940
2940
2941 batchedcommands = None
2941 batchedcommands = None
2942
2942
2943 elif action.startswith('httprequest '):
2943 elif action.startswith('httprequest '):
2944 if not opener:
2944 if not opener:
2945 raise error.Abort(_('cannot use httprequest without an HTTP '
2945 raise error.Abort(_('cannot use httprequest without an HTTP '
2946 'peer'))
2946 'peer'))
2947
2947
2948 request = action.split(' ', 2)
2948 request = action.split(' ', 2)
2949 if len(request) != 3:
2949 if len(request) != 3:
2950 raise error.Abort(_('invalid httprequest: expected format is '
2950 raise error.Abort(_('invalid httprequest: expected format is '
2951 '"httprequest <method> <path>'))
2951 '"httprequest <method> <path>'))
2952
2952
2953 method, httppath = request[1:]
2953 method, httppath = request[1:]
2954 headers = {}
2954 headers = {}
2955 body = None
2955 body = None
2956 for line in lines:
2956 for line in lines:
2957 line = line.lstrip()
2957 line = line.lstrip()
2958 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
2958 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
2959 if m:
2959 if m:
2960 headers[m.group(1)] = m.group(2)
2960 headers[m.group(1)] = m.group(2)
2961 continue
2961 continue
2962
2962
2963 if line.startswith(b'BODYFILE '):
2963 if line.startswith(b'BODYFILE '):
2964 with open(line.split(b' ', 1), 'rb') as fh:
2964 with open(line.split(b' ', 1), 'rb') as fh:
2965 body = fh.read()
2965 body = fh.read()
2966 else:
2966 else:
2967 raise error.Abort(_('unknown argument to httprequest: %s') %
2967 raise error.Abort(_('unknown argument to httprequest: %s') %
2968 line)
2968 line)
2969
2969
2970 url = path + httppath
2970 url = path + httppath
2971 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
2971 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
2972
2972
2973 # urllib.Request insists on using has_data() as a proxy for
2974 # determining the request method. Override that to use our
2975 # explicitly requested method.
2976 req.get_method = lambda: method
2977
2973 try:
2978 try:
2974 opener.open(req).read()
2979 opener.open(req).read()
2975 except util.urlerr.urlerror as e:
2980 except util.urlerr.urlerror as e:
2976 e.read()
2981 e.read()
2977
2982
2978 elif action == 'close':
2983 elif action == 'close':
2979 peer.close()
2984 peer.close()
2980 elif action == 'readavailable':
2985 elif action == 'readavailable':
2981 if not stdout or not stderr:
2986 if not stdout or not stderr:
2982 raise error.Abort(_('readavailable not available on this peer'))
2987 raise error.Abort(_('readavailable not available on this peer'))
2983
2988
2984 stdin.close()
2989 stdin.close()
2985 stdout.read()
2990 stdout.read()
2986 stderr.read()
2991 stderr.read()
2987
2992
2988 elif action == 'readline':
2993 elif action == 'readline':
2989 if not stdout:
2994 if not stdout:
2990 raise error.Abort(_('readline not available on this peer'))
2995 raise error.Abort(_('readline not available on this peer'))
2991 stdout.readline()
2996 stdout.readline()
2992 elif action == 'ereadline':
2997 elif action == 'ereadline':
2993 if not stderr:
2998 if not stderr:
2994 raise error.Abort(_('ereadline not available on this peer'))
2999 raise error.Abort(_('ereadline not available on this peer'))
2995 stderr.readline()
3000 stderr.readline()
2996 elif action.startswith('read '):
3001 elif action.startswith('read '):
2997 count = int(action.split(' ', 1)[1])
3002 count = int(action.split(' ', 1)[1])
2998 if not stdout:
3003 if not stdout:
2999 raise error.Abort(_('read not available on this peer'))
3004 raise error.Abort(_('read not available on this peer'))
3000 stdout.read(count)
3005 stdout.read(count)
3001 elif action.startswith('eread '):
3006 elif action.startswith('eread '):
3002 count = int(action.split(' ', 1)[1])
3007 count = int(action.split(' ', 1)[1])
3003 if not stderr:
3008 if not stderr:
3004 raise error.Abort(_('eread not available on this peer'))
3009 raise error.Abort(_('eread not available on this peer'))
3005 stderr.read(count)
3010 stderr.read(count)
3006 else:
3011 else:
3007 raise error.Abort(_('unknown action: %s') % action)
3012 raise error.Abort(_('unknown action: %s') % action)
3008
3013
3009 if batchedcommands is not None:
3014 if batchedcommands is not None:
3010 raise error.Abort(_('unclosed "batchbegin" request'))
3015 raise error.Abort(_('unclosed "batchbegin" request'))
3011
3016
3012 if peer:
3017 if peer:
3013 peer.close()
3018 peer.close()
3014
3019
3015 if proc:
3020 if proc:
3016 proc.kill()
3021 proc.kill()
@@ -1,1104 +1,1144 b''
1 The Mercurial wire protocol is a request-response based protocol
1 The Mercurial wire protocol is a request-response based protocol
2 with multiple wire representations.
2 with multiple wire representations.
3
3
4 Each request is modeled as a command name, a dictionary of arguments, and
4 Each request is modeled as a command name, a dictionary of arguments, and
5 optional raw input. Command arguments and their types are intrinsic
5 optional raw input. Command arguments and their types are intrinsic
6 properties of commands. So is the response type of the command. This means
6 properties of commands. So is the response type of the command. This means
7 clients can't always send arbitrary arguments to servers and servers can't
7 clients can't always send arbitrary arguments to servers and servers can't
8 return multiple response types.
8 return multiple response types.
9
9
10 The protocol is synchronous and does not support multiplexing (concurrent
10 The protocol is synchronous and does not support multiplexing (concurrent
11 commands).
11 commands).
12
12
13 Handshake
13 Handshake
14 =========
14 =========
15
15
16 It is required or common for clients to perform a *handshake* when connecting
16 It is required or common for clients to perform a *handshake* when connecting
17 to a server. The handshake serves the following purposes:
17 to a server. The handshake serves the following purposes:
18
18
19 * Negotiating protocol/transport level options
19 * Negotiating protocol/transport level options
20 * Allows the client to learn about server capabilities to influence
20 * Allows the client to learn about server capabilities to influence
21 future requests
21 future requests
22 * Ensures the underlying transport channel is in a *clean* state
22 * Ensures the underlying transport channel is in a *clean* state
23
23
24 An important goal of the handshake is to allow clients to use more modern
24 An important goal of the handshake is to allow clients to use more modern
25 wire protocol features. By default, clients must assume they are talking
25 wire protocol features. By default, clients must assume they are talking
26 to an old version of Mercurial server (possibly even the very first
26 to an old version of Mercurial server (possibly even the very first
27 implementation). So, clients should not attempt to call or utilize modern
27 implementation). So, clients should not attempt to call or utilize modern
28 wire protocol features until they have confirmation that the server
28 wire protocol features until they have confirmation that the server
29 supports them. The handshake implementation is designed to allow both
29 supports them. The handshake implementation is designed to allow both
30 ends to utilize the latest set of features and capabilities with as
30 ends to utilize the latest set of features and capabilities with as
31 few round trips as possible.
31 few round trips as possible.
32
32
33 The handshake mechanism varies by transport and protocol and is documented
33 The handshake mechanism varies by transport and protocol and is documented
34 in the sections below.
34 in the sections below.
35
35
36 HTTP Protocol
36 HTTP Protocol
37 =============
37 =============
38
38
39 Handshake
39 Handshake
40 ---------
40 ---------
41
41
42 The client sends a ``capabilities`` command request (``?cmd=capabilities``)
42 The client sends a ``capabilities`` command request (``?cmd=capabilities``)
43 as soon as HTTP requests may be issued.
43 as soon as HTTP requests may be issued.
44
44
45 The server responds with a capabilities string, which the client parses to
45 The server responds with a capabilities string, which the client parses to
46 learn about the server's abilities.
46 learn about the server's abilities.
47
47
48 HTTP Version 1 Transport
48 HTTP Version 1 Transport
49 ------------------------
49 ------------------------
50
50
51 Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are
51 Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are
52 sent to the base URL of the repository with the command name sent in
52 sent to the base URL of the repository with the command name sent in
53 the ``cmd`` query string parameter. e.g.
53 the ``cmd`` query string parameter. e.g.
54 ``https://example.com/repo?cmd=capabilities``. The HTTP method is ``GET``
54 ``https://example.com/repo?cmd=capabilities``. The HTTP method is ``GET``
55 or ``POST`` depending on the command and whether there is a request
55 or ``POST`` depending on the command and whether there is a request
56 body.
56 body.
57
57
58 Command arguments can be sent multiple ways.
58 Command arguments can be sent multiple ways.
59
59
60 The simplest is part of the URL query string using ``x-www-form-urlencoded``
60 The simplest is part of the URL query string using ``x-www-form-urlencoded``
61 encoding (see Python's ``urllib.urlencode()``. However, many servers impose
61 encoding (see Python's ``urllib.urlencode()``. However, many servers impose
62 length limitations on the URL. So this mechanism is typically only used if
62 length limitations on the URL. So this mechanism is typically only used if
63 the server doesn't support other mechanisms.
63 the server doesn't support other mechanisms.
64
64
65 If the server supports the ``httpheader`` capability, command arguments can
65 If the server supports the ``httpheader`` capability, command arguments can
66 be sent in HTTP request headers named ``X-HgArg-<N>`` where ``<N>`` is an
66 be sent in HTTP request headers named ``X-HgArg-<N>`` where ``<N>`` is an
67 integer starting at 1. A ``x-www-form-urlencoded`` representation of the
67 integer starting at 1. A ``x-www-form-urlencoded`` representation of the
68 arguments is obtained. This full string is then split into chunks and sent
68 arguments is obtained. This full string is then split into chunks and sent
69 in numbered ``X-HgArg-<N>`` headers. The maximum length of each HTTP header
69 in numbered ``X-HgArg-<N>`` headers. The maximum length of each HTTP header
70 is defined by the server in the ``httpheader`` capability value, which defaults
70 is defined by the server in the ``httpheader`` capability value, which defaults
71 to ``1024``. The server reassembles the encoded arguments string by
71 to ``1024``. The server reassembles the encoded arguments string by
72 concatenating the ``X-HgArg-<N>`` headers then URL decodes them into a
72 concatenating the ``X-HgArg-<N>`` headers then URL decodes them into a
73 dictionary.
73 dictionary.
74
74
75 The list of ``X-HgArg-<N>`` headers should be added to the ``Vary`` request
75 The list of ``X-HgArg-<N>`` headers should be added to the ``Vary`` request
76 header to instruct caches to take these headers into consideration when caching
76 header to instruct caches to take these headers into consideration when caching
77 requests.
77 requests.
78
78
79 If the server supports the ``httppostargs`` capability, the client
79 If the server supports the ``httppostargs`` capability, the client
80 may send command arguments in the HTTP request body as part of an
80 may send command arguments in the HTTP request body as part of an
81 HTTP POST request. The command arguments will be URL encoded just like
81 HTTP POST request. The command arguments will be URL encoded just like
82 they would for sending them via HTTP headers. However, no splitting is
82 they would for sending them via HTTP headers. However, no splitting is
83 performed: the raw arguments are included in the HTTP request body.
83 performed: the raw arguments are included in the HTTP request body.
84
84
85 The client sends a ``X-HgArgs-Post`` header with the string length of the
85 The client sends a ``X-HgArgs-Post`` header with the string length of the
86 encoded arguments data. Additional data may be included in the HTTP
86 encoded arguments data. Additional data may be included in the HTTP
87 request body immediately following the argument data. The offset of the
87 request body immediately following the argument data. The offset of the
88 non-argument data is defined by the ``X-HgArgs-Post`` header. The
88 non-argument data is defined by the ``X-HgArgs-Post`` header. The
89 ``X-HgArgs-Post`` header is not required if there is no argument data.
89 ``X-HgArgs-Post`` header is not required if there is no argument data.
90
90
91 Additional command data can be sent as part of the HTTP request body. The
91 Additional command data can be sent as part of the HTTP request body. The
92 default ``Content-Type`` when sending data is ``application/mercurial-0.1``.
92 default ``Content-Type`` when sending data is ``application/mercurial-0.1``.
93 A ``Content-Length`` header is currently always sent.
93 A ``Content-Length`` header is currently always sent.
94
94
95 Example HTTP requests::
95 Example HTTP requests::
96
96
97 GET /repo?cmd=capabilities
97 GET /repo?cmd=capabilities
98 X-HgArg-1: foo=bar&baz=hello%20world
98 X-HgArg-1: foo=bar&baz=hello%20world
99
99
100 The request media type should be chosen based on server support. If the
100 The request media type should be chosen based on server support. If the
101 ``httpmediatype`` server capability is present, the client should send
101 ``httpmediatype`` server capability is present, the client should send
102 the newest mutually supported media type. If this capability is absent,
102 the newest mutually supported media type. If this capability is absent,
103 the client must assume the server only supports the
103 the client must assume the server only supports the
104 ``application/mercurial-0.1`` media type.
104 ``application/mercurial-0.1`` media type.
105
105
106 The ``Content-Type`` HTTP response header identifies the response as coming
106 The ``Content-Type`` HTTP response header identifies the response as coming
107 from Mercurial and can also be used to signal an error has occurred.
107 from Mercurial and can also be used to signal an error has occurred.
108
108
109 The ``application/mercurial-*`` media types indicate a generic Mercurial
109 The ``application/mercurial-*`` media types indicate a generic Mercurial
110 data type.
110 data type.
111
111
112 The ``application/mercurial-0.1`` media type is raw Mercurial data. It is the
112 The ``application/mercurial-0.1`` media type is raw Mercurial data. It is the
113 predecessor of the format below.
113 predecessor of the format below.
114
114
115 The ``application/mercurial-0.2`` media type is compression framed Mercurial
115 The ``application/mercurial-0.2`` media type is compression framed Mercurial
116 data. The first byte of the payload indicates the length of the compression
116 data. The first byte of the payload indicates the length of the compression
117 format identifier that follows. Next are N bytes indicating the compression
117 format identifier that follows. Next are N bytes indicating the compression
118 format. e.g. ``zlib``. The remaining bytes are compressed according to that
118 format. e.g. ``zlib``. The remaining bytes are compressed according to that
119 compression format. The decompressed data behaves the same as with
119 compression format. The decompressed data behaves the same as with
120 ``application/mercurial-0.1``.
120 ``application/mercurial-0.1``.
121
121
122 The ``application/hg-error`` media type indicates a generic error occurred.
122 The ``application/hg-error`` media type indicates a generic error occurred.
123 The content of the HTTP response body typically holds text describing the
123 The content of the HTTP response body typically holds text describing the
124 error.
124 error.
125
125
126 The ``application/hg-changegroup`` media type indicates a changegroup response
126 The ``application/hg-changegroup`` media type indicates a changegroup response
127 type.
127 type.
128
128
129 Clients also accept the ``text/plain`` media type. All other media
129 Clients also accept the ``text/plain`` media type. All other media
130 types should cause the client to error.
130 types should cause the client to error.
131
131
132 Behavior of media types is further described in the ``Content Negotiation``
132 Behavior of media types is further described in the ``Content Negotiation``
133 section below.
133 section below.
134
134
135 Clients should issue a ``User-Agent`` request header that identifies the client.
135 Clients should issue a ``User-Agent`` request header that identifies the client.
136 The server should not use the ``User-Agent`` for feature detection.
136 The server should not use the ``User-Agent`` for feature detection.
137
137
138 A command returning a ``string`` response issues a
138 A command returning a ``string`` response issues a
139 ``application/mercurial-0.*`` media type and the HTTP response body contains
139 ``application/mercurial-0.*`` media type and the HTTP response body contains
140 the raw string value (after compression decoding, if used). A
140 the raw string value (after compression decoding, if used). A
141 ``Content-Length`` header is typically issued, but not required.
141 ``Content-Length`` header is typically issued, but not required.
142
142
143 A command returning a ``stream`` response issues a
143 A command returning a ``stream`` response issues a
144 ``application/mercurial-0.*`` media type and the HTTP response is typically
144 ``application/mercurial-0.*`` media type and the HTTP response is typically
145 using *chunked transfer* (``Transfer-Encoding: chunked``).
145 using *chunked transfer* (``Transfer-Encoding: chunked``).
146
146
147 HTTP Version 2 Transport
148 ------------------------
149
150 **Experimental - feature under active development**
151
152 Version 2 of the HTTP protocol is exposed under the ``/api/*`` URL space.
153 It's final API name is not yet formalized.
154
155 Commands are triggered by sending HTTP requests against URLs of the
156 form ``<permission>/<command>``, where ``<permission>`` is ``ro`` or
157 ``rw``, meaning read-only and read-write, respectively and ``<command>``
158 is a named wire protocol command.
159
160 Commands that modify repository state in meaningful ways MUST NOT be
161 exposed under the ``ro`` URL prefix. All available commands MUST be
162 available under the ``rw`` URL prefix.
163
164 Server adminstrators MAY implement blanket HTTP authentication keyed
165 off the URL prefix. For example, a server may require authentication
166 for all ``rw/*`` URLs and let unauthenticated requests to ``ro/*``
167 URL proceed. A server MAY issue an HTTP 401, 403, or 407 response
168 in accordance with RFC 7235. Clients SHOULD recognize the HTTP Basic
169 (RFC 7617) and Digest (RFC 7616) authentication schemes. Clients SHOULD
170 make an attempt to recognize unknown schemes using the
171 ``WWW-Authenticate`` response header on a 401 response, as defined by
172 RFC 7235.
173
174 Read-only commands are accessible under ``rw/*`` URLs so clients can
175 signal the intent of the operation very early in the connection
176 lifecycle. For example, a ``push`` operation - which consists of
177 various read-only commands mixed with at least one read-write command -
178 can perform all commands against ``rw/*`` URLs so that any server-side
179 authentication requirements are discovered upon attempting the first
180 command - not potentially several commands into the exchange. This
181 allows clients to fail faster or prompt for credentials as soon as the
182 exchange takes place. This provides a better end-user experience.
183
184 Requests to unknown commands or URLS result in an HTTP 404.
185 TODO formally define response type, how error is communicated, etc.
186
147 SSH Protocol
187 SSH Protocol
148 ============
188 ============
149
189
150 Handshake
190 Handshake
151 ---------
191 ---------
152
192
153 For all clients, the handshake consists of the client sending 1 or more
193 For all clients, the handshake consists of the client sending 1 or more
154 commands to the server using version 1 of the transport. Servers respond
194 commands to the server using version 1 of the transport. Servers respond
155 to commands they know how to respond to and send an empty response (``0\n``)
195 to commands they know how to respond to and send an empty response (``0\n``)
156 for unknown commands (per standard behavior of version 1 of the transport).
196 for unknown commands (per standard behavior of version 1 of the transport).
157 Clients then typically look for a response to the newest sent command to
197 Clients then typically look for a response to the newest sent command to
158 determine which transport version to use and what the available features for
198 determine which transport version to use and what the available features for
159 the connection and server are.
199 the connection and server are.
160
200
161 Preceding any response from client-issued commands, the server may print
201 Preceding any response from client-issued commands, the server may print
162 non-protocol output. It is common for SSH servers to print banners, message
202 non-protocol output. It is common for SSH servers to print banners, message
163 of the day announcements, etc when clients connect. It is assumed that any
203 of the day announcements, etc when clients connect. It is assumed that any
164 such *banner* output will precede any Mercurial server output. So clients
204 such *banner* output will precede any Mercurial server output. So clients
165 must be prepared to handle server output on initial connect that isn't
205 must be prepared to handle server output on initial connect that isn't
166 in response to any client-issued command and doesn't conform to Mercurial's
206 in response to any client-issued command and doesn't conform to Mercurial's
167 wire protocol. This *banner* output should only be on stdout. However,
207 wire protocol. This *banner* output should only be on stdout. However,
168 some servers may send output on stderr.
208 some servers may send output on stderr.
169
209
170 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
210 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
171 having the value
211 having the value
172 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
212 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
173
213
174 The ``between`` command has been supported since the original Mercurial
214 The ``between`` command has been supported since the original Mercurial
175 SSH server. Requesting the empty range will return a ``\n`` string response,
215 SSH server. Requesting the empty range will return a ``\n`` string response,
176 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
216 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
177 followed by the value, which happens to be a newline).
217 followed by the value, which happens to be a newline).
178
218
179 For pre 0.9.1 clients and all servers, the exchange looks like::
219 For pre 0.9.1 clients and all servers, the exchange looks like::
180
220
181 c: between\n
221 c: between\n
182 c: pairs 81\n
222 c: pairs 81\n
183 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
223 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
184 s: 1\n
224 s: 1\n
185 s: \n
225 s: \n
186
226
187 0.9.1+ clients send a ``hello`` command (with no arguments) before the
227 0.9.1+ clients send a ``hello`` command (with no arguments) before the
188 ``between`` command. The response to this command allows clients to
228 ``between`` command. The response to this command allows clients to
189 discover server capabilities and settings.
229 discover server capabilities and settings.
190
230
191 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
231 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
192 like::
232 like::
193
233
194 c: hello\n
234 c: hello\n
195 c: between\n
235 c: between\n
196 c: pairs 81\n
236 c: pairs 81\n
197 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
237 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
198 s: 324\n
238 s: 324\n
199 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
239 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
200 s: 1\n
240 s: 1\n
201 s: \n
241 s: \n
202
242
203 And a similar scenario but with servers sending a banner on connect::
243 And a similar scenario but with servers sending a banner on connect::
204
244
205 c: hello\n
245 c: hello\n
206 c: between\n
246 c: between\n
207 c: pairs 81\n
247 c: pairs 81\n
208 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
248 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
209 s: welcome to the server\n
249 s: welcome to the server\n
210 s: if you find any issues, email someone@somewhere.com\n
250 s: if you find any issues, email someone@somewhere.com\n
211 s: 324\n
251 s: 324\n
212 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
252 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
213 s: 1\n
253 s: 1\n
214 s: \n
254 s: \n
215
255
216 Note that output from the ``hello`` command is terminated by a ``\n``. This is
256 Note that output from the ``hello`` command is terminated by a ``\n``. This is
217 part of the response payload and not part of the wire protocol adding a newline
257 part of the response payload and not part of the wire protocol adding a newline
218 after responses. In other words, the length of the response contains the
258 after responses. In other words, the length of the response contains the
219 trailing ``\n``.
259 trailing ``\n``.
220
260
221 Clients supporting version 2 of the SSH transport send a line beginning
261 Clients supporting version 2 of the SSH transport send a line beginning
222 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
262 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
223 (which isn't a well-formed command line because it doesn't consist of a
263 (which isn't a well-formed command line because it doesn't consist of a
224 single command name) serves to both communicate the client's intent to
264 single command name) serves to both communicate the client's intent to
225 switch to transport version 2 (transports are version 1 by default) as
265 switch to transport version 2 (transports are version 1 by default) as
226 well as to advertise the client's transport-level capabilities so the
266 well as to advertise the client's transport-level capabilities so the
227 server may satisfy that request immediately.
267 server may satisfy that request immediately.
228
268
229 The upgrade line has the form:
269 The upgrade line has the form:
230
270
231 upgrade <token> <transport capabilities>
271 upgrade <token> <transport capabilities>
232
272
233 That is the literal string ``upgrade`` followed by a space, followed by
273 That is the literal string ``upgrade`` followed by a space, followed by
234 a randomly generated string, followed by a space, followed by a string
274 a randomly generated string, followed by a space, followed by a string
235 denoting the client's transport capabilities.
275 denoting the client's transport capabilities.
236
276
237 The token can be anything. However, a random UUID is recommended. (Use
277 The token can be anything. However, a random UUID is recommended. (Use
238 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
278 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
239 client's MAC address.)
279 client's MAC address.)
240
280
241 The transport capabilities string is a URL/percent encoded string
281 The transport capabilities string is a URL/percent encoded string
242 containing key-value pairs defining the client's transport-level
282 containing key-value pairs defining the client's transport-level
243 capabilities. The following capabilities are defined:
283 capabilities. The following capabilities are defined:
244
284
245 proto
285 proto
246 A comma-delimited list of transport protocol versions the client
286 A comma-delimited list of transport protocol versions the client
247 supports. e.g. ``ssh-v2``.
287 supports. e.g. ``ssh-v2``.
248
288
249 If the server does not recognize the ``upgrade`` line, it should issue
289 If the server does not recognize the ``upgrade`` line, it should issue
250 an empty response and continue processing the ``hello`` and ``between``
290 an empty response and continue processing the ``hello`` and ``between``
251 commands. Here is an example handshake between a version 2 aware client
291 commands. Here is an example handshake between a version 2 aware client
252 and a non version 2 aware server:
292 and a non version 2 aware server:
253
293
254 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
294 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
255 c: hello\n
295 c: hello\n
256 c: between\n
296 c: between\n
257 c: pairs 81\n
297 c: pairs 81\n
258 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
298 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
259 s: 0\n
299 s: 0\n
260 s: 324\n
300 s: 324\n
261 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
301 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
262 s: 1\n
302 s: 1\n
263 s: \n
303 s: \n
264
304
265 (The initial ``0\n`` line from the server indicates an empty response to
305 (The initial ``0\n`` line from the server indicates an empty response to
266 the unknown ``upgrade ..`` command/line.)
306 the unknown ``upgrade ..`` command/line.)
267
307
268 If the server recognizes the ``upgrade`` line and is willing to satisfy that
308 If the server recognizes the ``upgrade`` line and is willing to satisfy that
269 upgrade request, it replies to with a payload of the following form:
309 upgrade request, it replies to with a payload of the following form:
270
310
271 upgraded <token> <transport name>\n
311 upgraded <token> <transport name>\n
272
312
273 This line is the literal string ``upgraded``, a space, the token that was
313 This line is the literal string ``upgraded``, a space, the token that was
274 specified by the client in its ``upgrade ...`` request line, a space, and the
314 specified by the client in its ``upgrade ...`` request line, a space, and the
275 name of the transport protocol that was chosen by the server. The transport
315 name of the transport protocol that was chosen by the server. The transport
276 name MUST match one of the names the client specified in the ``proto`` field
316 name MUST match one of the names the client specified in the ``proto`` field
277 of its ``upgrade ...`` request line.
317 of its ``upgrade ...`` request line.
278
318
279 If a server issues an ``upgraded`` response, it MUST also read and ignore
319 If a server issues an ``upgraded`` response, it MUST also read and ignore
280 the lines associated with the ``hello`` and ``between`` command requests
320 the lines associated with the ``hello`` and ``between`` command requests
281 that were issued by the server. It is assumed that the negotiated transport
321 that were issued by the server. It is assumed that the negotiated transport
282 will respond with equivalent requested information following the transport
322 will respond with equivalent requested information following the transport
283 handshake.
323 handshake.
284
324
285 All data following the ``\n`` terminating the ``upgraded`` line is the
325 All data following the ``\n`` terminating the ``upgraded`` line is the
286 domain of the negotiated transport. It is common for the data immediately
326 domain of the negotiated transport. It is common for the data immediately
287 following to contain additional metadata about the state of the transport and
327 following to contain additional metadata about the state of the transport and
288 the server. However, this isn't strictly speaking part of the transport
328 the server. However, this isn't strictly speaking part of the transport
289 handshake and isn't covered by this section.
329 handshake and isn't covered by this section.
290
330
291 Here is an example handshake between a version 2 aware client and a version
331 Here is an example handshake between a version 2 aware client and a version
292 2 aware server:
332 2 aware server:
293
333
294 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
334 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
295 c: hello\n
335 c: hello\n
296 c: between\n
336 c: between\n
297 c: pairs 81\n
337 c: pairs 81\n
298 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
338 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
299 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
339 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
300 s: <additional transport specific data>
340 s: <additional transport specific data>
301
341
302 The client-issued token that is echoed in the response provides a more
342 The client-issued token that is echoed in the response provides a more
303 resilient mechanism for differentiating *banner* output from Mercurial
343 resilient mechanism for differentiating *banner* output from Mercurial
304 output. In version 1, properly formatted banner output could get confused
344 output. In version 1, properly formatted banner output could get confused
305 for Mercurial server output. By submitting a randomly generated token
345 for Mercurial server output. By submitting a randomly generated token
306 that is then present in the response, the client can look for that token
346 that is then present in the response, the client can look for that token
307 in response lines and have reasonable certainty that the line did not
347 in response lines and have reasonable certainty that the line did not
308 originate from a *banner* message.
348 originate from a *banner* message.
309
349
310 SSH Version 1 Transport
350 SSH Version 1 Transport
311 -----------------------
351 -----------------------
312
352
313 The SSH transport (version 1) is a custom text-based protocol suitable for
353 The SSH transport (version 1) is a custom text-based protocol suitable for
314 use over any bi-directional stream transport. It is most commonly used with
354 use over any bi-directional stream transport. It is most commonly used with
315 SSH.
355 SSH.
316
356
317 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
357 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
318 stderr, and stdout file descriptors of the started process are used to exchange
358 stderr, and stdout file descriptors of the started process are used to exchange
319 data. When Mercurial connects to a remote server over SSH, it actually starts
359 data. When Mercurial connects to a remote server over SSH, it actually starts
320 a ``hg serve --stdio`` process on the remote server.
360 a ``hg serve --stdio`` process on the remote server.
321
361
322 Commands are issued by sending the command name followed by a trailing newline
362 Commands are issued by sending the command name followed by a trailing newline
323 ``\n`` to the server. e.g. ``capabilities\n``.
363 ``\n`` to the server. e.g. ``capabilities\n``.
324
364
325 Command arguments are sent in the following format::
365 Command arguments are sent in the following format::
326
366
327 <argument> <length>\n<value>
367 <argument> <length>\n<value>
328
368
329 That is, the argument string name followed by a space followed by the
369 That is, the argument string name followed by a space followed by the
330 integer length of the value (expressed as a string) followed by a newline
370 integer length of the value (expressed as a string) followed by a newline
331 (``\n``) followed by the raw argument value.
371 (``\n``) followed by the raw argument value.
332
372
333 Dictionary arguments are encoded differently::
373 Dictionary arguments are encoded differently::
334
374
335 <argument> <# elements>\n
375 <argument> <# elements>\n
336 <key1> <length1>\n<value1>
376 <key1> <length1>\n<value1>
337 <key2> <length2>\n<value2>
377 <key2> <length2>\n<value2>
338 ...
378 ...
339
379
340 Non-argument data is sent immediately after the final argument value. It is
380 Non-argument data is sent immediately after the final argument value. It is
341 encoded in chunks::
381 encoded in chunks::
342
382
343 <length>\n<data>
383 <length>\n<data>
344
384
345 Each command declares a list of supported arguments and their types. If a
385 Each command declares a list of supported arguments and their types. If a
346 client sends an unknown argument to the server, the server should abort
386 client sends an unknown argument to the server, the server should abort
347 immediately. The special argument ``*`` in a command's definition indicates
387 immediately. The special argument ``*`` in a command's definition indicates
348 that all argument names are allowed.
388 that all argument names are allowed.
349
389
350 The definition of supported arguments and types is initially made when a
390 The definition of supported arguments and types is initially made when a
351 new command is implemented. The client and server must initially independently
391 new command is implemented. The client and server must initially independently
352 agree on the arguments and their types. This initial set of arguments can be
392 agree on the arguments and their types. This initial set of arguments can be
353 supplemented through the presence of *capabilities* advertised by the server.
393 supplemented through the presence of *capabilities* advertised by the server.
354
394
355 Each command has a defined expected response type.
395 Each command has a defined expected response type.
356
396
357 A ``string`` response type is a length framed value. The response consists of
397 A ``string`` response type is a length framed value. The response consists of
358 the string encoded integer length of a value followed by a newline (``\n``)
398 the string encoded integer length of a value followed by a newline (``\n``)
359 followed by the value. Empty values are allowed (and are represented as
399 followed by the value. Empty values are allowed (and are represented as
360 ``0\n``).
400 ``0\n``).
361
401
362 A ``stream`` response type consists of raw bytes of data. There is no framing.
402 A ``stream`` response type consists of raw bytes of data. There is no framing.
363
403
364 A generic error response type is also supported. It consists of a an error
404 A generic error response type is also supported. It consists of a an error
365 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
405 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
366 written to ``stdout``.
406 written to ``stdout``.
367
407
368 If the server receives an unknown command, it will send an empty ``string``
408 If the server receives an unknown command, it will send an empty ``string``
369 response.
409 response.
370
410
371 The server terminates if it receives an empty command (a ``\n`` character).
411 The server terminates if it receives an empty command (a ``\n`` character).
372
412
373 SSH Version 2 Transport
413 SSH Version 2 Transport
374 -----------------------
414 -----------------------
375
415
376 **Experimental**
416 **Experimental**
377
417
378 Version 2 of the SSH transport behaves identically to version 1 of the SSH
418 Version 2 of the SSH transport behaves identically to version 1 of the SSH
379 transport with the exception of handshake semantics. See above for how
419 transport with the exception of handshake semantics. See above for how
380 version 2 of the SSH transport is negotiated.
420 version 2 of the SSH transport is negotiated.
381
421
382 Immediately following the ``upgraded`` line signaling a switch to version
422 Immediately following the ``upgraded`` line signaling a switch to version
383 2 of the SSH protocol, the server automatically sends additional details
423 2 of the SSH protocol, the server automatically sends additional details
384 about the capabilities of the remote server. This has the form:
424 about the capabilities of the remote server. This has the form:
385
425
386 <integer length of value>\n
426 <integer length of value>\n
387 capabilities: ...\n
427 capabilities: ...\n
388
428
389 e.g.
429 e.g.
390
430
391 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
431 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
392 s: 240\n
432 s: 240\n
393 s: capabilities: known getbundle batch ...\n
433 s: capabilities: known getbundle batch ...\n
394
434
395 Following capabilities advertisement, the peers communicate using version
435 Following capabilities advertisement, the peers communicate using version
396 1 of the SSH transport.
436 1 of the SSH transport.
397
437
398 Capabilities
438 Capabilities
399 ============
439 ============
400
440
401 Servers advertise supported wire protocol features. This allows clients to
441 Servers advertise supported wire protocol features. This allows clients to
402 probe for server features before blindly calling a command or passing a
442 probe for server features before blindly calling a command or passing a
403 specific argument.
443 specific argument.
404
444
405 The server's features are exposed via a *capabilities* string. This is a
445 The server's features are exposed via a *capabilities* string. This is a
406 space-delimited string of tokens/features. Some features are single words
446 space-delimited string of tokens/features. Some features are single words
407 like ``lookup`` or ``batch``. Others are complicated key-value pairs
447 like ``lookup`` or ``batch``. Others are complicated key-value pairs
408 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
448 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
409 values are used, each feature name can define its own encoding of sub-values.
449 values are used, each feature name can define its own encoding of sub-values.
410 Comma-delimited and ``x-www-form-urlencoded`` values are common.
450 Comma-delimited and ``x-www-form-urlencoded`` values are common.
411
451
412 The following document capabilities defined by the canonical Mercurial server
452 The following document capabilities defined by the canonical Mercurial server
413 implementation.
453 implementation.
414
454
415 batch
455 batch
416 -----
456 -----
417
457
418 Whether the server supports the ``batch`` command.
458 Whether the server supports the ``batch`` command.
419
459
420 This capability/command was introduced in Mercurial 1.9 (released July 2011).
460 This capability/command was introduced in Mercurial 1.9 (released July 2011).
421
461
422 branchmap
462 branchmap
423 ---------
463 ---------
424
464
425 Whether the server supports the ``branchmap`` command.
465 Whether the server supports the ``branchmap`` command.
426
466
427 This capability/command was introduced in Mercurial 1.3 (released July 2009).
467 This capability/command was introduced in Mercurial 1.3 (released July 2009).
428
468
429 bundle2-exp
469 bundle2-exp
430 -----------
470 -----------
431
471
432 Precursor to ``bundle2`` capability that was used before bundle2 was a
472 Precursor to ``bundle2`` capability that was used before bundle2 was a
433 stable feature.
473 stable feature.
434
474
435 This capability was introduced in Mercurial 3.0 behind an experimental
475 This capability was introduced in Mercurial 3.0 behind an experimental
436 flag. This capability should not be observed in the wild.
476 flag. This capability should not be observed in the wild.
437
477
438 bundle2
478 bundle2
439 -------
479 -------
440
480
441 Indicates whether the server supports the ``bundle2`` data exchange format.
481 Indicates whether the server supports the ``bundle2`` data exchange format.
442
482
443 The value of the capability is a URL quoted, newline (``\n``) delimited
483 The value of the capability is a URL quoted, newline (``\n``) delimited
444 list of keys or key-value pairs.
484 list of keys or key-value pairs.
445
485
446 A key is simply a URL encoded string.
486 A key is simply a URL encoded string.
447
487
448 A key-value pair is a URL encoded key separated from a URL encoded value by
488 A key-value pair is a URL encoded key separated from a URL encoded value by
449 an ``=``. If the value is a list, elements are delimited by a ``,`` after
489 an ``=``. If the value is a list, elements are delimited by a ``,`` after
450 URL encoding.
490 URL encoding.
451
491
452 For example, say we have the values::
492 For example, say we have the values::
453
493
454 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
494 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
455
495
456 We would first construct a string::
496 We would first construct a string::
457
497
458 HG20\nchangegroup=01,02\ndigests=sha1,sha512
498 HG20\nchangegroup=01,02\ndigests=sha1,sha512
459
499
460 We would then URL quote this string::
500 We would then URL quote this string::
461
501
462 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
502 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
463
503
464 This capability was introduced in Mercurial 3.4 (released May 2015).
504 This capability was introduced in Mercurial 3.4 (released May 2015).
465
505
466 changegroupsubset
506 changegroupsubset
467 -----------------
507 -----------------
468
508
469 Whether the server supports the ``changegroupsubset`` command.
509 Whether the server supports the ``changegroupsubset`` command.
470
510
471 This capability was introduced in Mercurial 0.9.2 (released December
511 This capability was introduced in Mercurial 0.9.2 (released December
472 2006).
512 2006).
473
513
474 This capability was introduced at the same time as the ``lookup``
514 This capability was introduced at the same time as the ``lookup``
475 capability/command.
515 capability/command.
476
516
477 compression
517 compression
478 -----------
518 -----------
479
519
480 Declares support for negotiating compression formats.
520 Declares support for negotiating compression formats.
481
521
482 Presence of this capability indicates the server supports dynamic selection
522 Presence of this capability indicates the server supports dynamic selection
483 of compression formats based on the client request.
523 of compression formats based on the client request.
484
524
485 Servers advertising this capability are required to support the
525 Servers advertising this capability are required to support the
486 ``application/mercurial-0.2`` media type in response to commands returning
526 ``application/mercurial-0.2`` media type in response to commands returning
487 streams. Servers may support this media type on any command.
527 streams. Servers may support this media type on any command.
488
528
489 The value of the capability is a comma-delimited list of strings declaring
529 The value of the capability is a comma-delimited list of strings declaring
490 supported compression formats. The order of the compression formats is in
530 supported compression formats. The order of the compression formats is in
491 server-preferred order, most preferred first.
531 server-preferred order, most preferred first.
492
532
493 The identifiers used by the official Mercurial distribution are:
533 The identifiers used by the official Mercurial distribution are:
494
534
495 bzip2
535 bzip2
496 bzip2
536 bzip2
497 none
537 none
498 uncompressed / raw data
538 uncompressed / raw data
499 zlib
539 zlib
500 zlib (no gzip header)
540 zlib (no gzip header)
501 zstd
541 zstd
502 zstd
542 zstd
503
543
504 This capability was introduced in Mercurial 4.1 (released February 2017).
544 This capability was introduced in Mercurial 4.1 (released February 2017).
505
545
506 getbundle
546 getbundle
507 ---------
547 ---------
508
548
509 Whether the server supports the ``getbundle`` command.
549 Whether the server supports the ``getbundle`` command.
510
550
511 This capability was introduced in Mercurial 1.9 (released July 2011).
551 This capability was introduced in Mercurial 1.9 (released July 2011).
512
552
513 httpheader
553 httpheader
514 ----------
554 ----------
515
555
516 Whether the server supports receiving command arguments via HTTP request
556 Whether the server supports receiving command arguments via HTTP request
517 headers.
557 headers.
518
558
519 The value of the capability is an integer describing the max header
559 The value of the capability is an integer describing the max header
520 length that clients should send. Clients should ignore any content after a
560 length that clients should send. Clients should ignore any content after a
521 comma in the value, as this is reserved for future use.
561 comma in the value, as this is reserved for future use.
522
562
523 This capability was introduced in Mercurial 1.9 (released July 2011).
563 This capability was introduced in Mercurial 1.9 (released July 2011).
524
564
525 httpmediatype
565 httpmediatype
526 -------------
566 -------------
527
567
528 Indicates which HTTP media types (``Content-Type`` header) the server is
568 Indicates which HTTP media types (``Content-Type`` header) the server is
529 capable of receiving and sending.
569 capable of receiving and sending.
530
570
531 The value of the capability is a comma-delimited list of strings identifying
571 The value of the capability is a comma-delimited list of strings identifying
532 support for media type and transmission direction. The following strings may
572 support for media type and transmission direction. The following strings may
533 be present:
573 be present:
534
574
535 0.1rx
575 0.1rx
536 Indicates server support for receiving ``application/mercurial-0.1`` media
576 Indicates server support for receiving ``application/mercurial-0.1`` media
537 types.
577 types.
538
578
539 0.1tx
579 0.1tx
540 Indicates server support for sending ``application/mercurial-0.1`` media
580 Indicates server support for sending ``application/mercurial-0.1`` media
541 types.
581 types.
542
582
543 0.2rx
583 0.2rx
544 Indicates server support for receiving ``application/mercurial-0.2`` media
584 Indicates server support for receiving ``application/mercurial-0.2`` media
545 types.
585 types.
546
586
547 0.2tx
587 0.2tx
548 Indicates server support for sending ``application/mercurial-0.2`` media
588 Indicates server support for sending ``application/mercurial-0.2`` media
549 types.
589 types.
550
590
551 minrx=X
591 minrx=X
552 Minimum media type version the server is capable of receiving. Value is a
592 Minimum media type version the server is capable of receiving. Value is a
553 string like ``0.2``.
593 string like ``0.2``.
554
594
555 This capability can be used by servers to limit connections from legacy
595 This capability can be used by servers to limit connections from legacy
556 clients not using the latest supported media type. However, only clients
596 clients not using the latest supported media type. However, only clients
557 with knowledge of this capability will know to consult this value. This
597 with knowledge of this capability will know to consult this value. This
558 capability is present so the client may issue a more user-friendly error
598 capability is present so the client may issue a more user-friendly error
559 when the server has locked out a legacy client.
599 when the server has locked out a legacy client.
560
600
561 mintx=X
601 mintx=X
562 Minimum media type version the server is capable of sending. Value is a
602 Minimum media type version the server is capable of sending. Value is a
563 string like ``0.1``.
603 string like ``0.1``.
564
604
565 Servers advertising support for the ``application/mercurial-0.2`` media type
605 Servers advertising support for the ``application/mercurial-0.2`` media type
566 should also advertise the ``compression`` capability.
606 should also advertise the ``compression`` capability.
567
607
568 This capability was introduced in Mercurial 4.1 (released February 2017).
608 This capability was introduced in Mercurial 4.1 (released February 2017).
569
609
570 httppostargs
610 httppostargs
571 ------------
611 ------------
572
612
573 **Experimental**
613 **Experimental**
574
614
575 Indicates that the server supports and prefers clients send command arguments
615 Indicates that the server supports and prefers clients send command arguments
576 via a HTTP POST request as part of the request body.
616 via a HTTP POST request as part of the request body.
577
617
578 This capability was introduced in Mercurial 3.8 (released May 2016).
618 This capability was introduced in Mercurial 3.8 (released May 2016).
579
619
580 known
620 known
581 -----
621 -----
582
622
583 Whether the server supports the ``known`` command.
623 Whether the server supports the ``known`` command.
584
624
585 This capability/command was introduced in Mercurial 1.9 (released July 2011).
625 This capability/command was introduced in Mercurial 1.9 (released July 2011).
586
626
587 lookup
627 lookup
588 ------
628 ------
589
629
590 Whether the server supports the ``lookup`` command.
630 Whether the server supports the ``lookup`` command.
591
631
592 This capability was introduced in Mercurial 0.9.2 (released December
632 This capability was introduced in Mercurial 0.9.2 (released December
593 2006).
633 2006).
594
634
595 This capability was introduced at the same time as the ``changegroupsubset``
635 This capability was introduced at the same time as the ``changegroupsubset``
596 capability/command.
636 capability/command.
597
637
598 pushkey
638 pushkey
599 -------
639 -------
600
640
601 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
641 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
602
642
603 This capability was introduced in Mercurial 1.6 (released July 2010).
643 This capability was introduced in Mercurial 1.6 (released July 2010).
604
644
605 standardbundle
645 standardbundle
606 --------------
646 --------------
607
647
608 **Unsupported**
648 **Unsupported**
609
649
610 This capability was introduced during the Mercurial 0.9.2 development cycle in
650 This capability was introduced during the Mercurial 0.9.2 development cycle in
611 2006. It was never present in a release, as it was replaced by the ``unbundle``
651 2006. It was never present in a release, as it was replaced by the ``unbundle``
612 capability. This capability should not be encountered in the wild.
652 capability. This capability should not be encountered in the wild.
613
653
614 stream-preferred
654 stream-preferred
615 ----------------
655 ----------------
616
656
617 If present the server prefers that clients clone using the streaming clone
657 If present the server prefers that clients clone using the streaming clone
618 protocol (``hg clone --stream``) rather than the standard
658 protocol (``hg clone --stream``) rather than the standard
619 changegroup/bundle based protocol.
659 changegroup/bundle based protocol.
620
660
621 This capability was introduced in Mercurial 2.2 (released May 2012).
661 This capability was introduced in Mercurial 2.2 (released May 2012).
622
662
623 streamreqs
663 streamreqs
624 ----------
664 ----------
625
665
626 Indicates whether the server supports *streaming clones* and the *requirements*
666 Indicates whether the server supports *streaming clones* and the *requirements*
627 that clients must support to receive it.
667 that clients must support to receive it.
628
668
629 If present, the server supports the ``stream_out`` command, which transmits
669 If present, the server supports the ``stream_out`` command, which transmits
630 raw revlogs from the repository instead of changegroups. This provides a faster
670 raw revlogs from the repository instead of changegroups. This provides a faster
631 cloning mechanism at the expense of more bandwidth used.
671 cloning mechanism at the expense of more bandwidth used.
632
672
633 The value of this capability is a comma-delimited list of repo format
673 The value of this capability is a comma-delimited list of repo format
634 *requirements*. These are requirements that impact the reading of data in
674 *requirements*. These are requirements that impact the reading of data in
635 the ``.hg/store`` directory. An example value is
675 the ``.hg/store`` directory. An example value is
636 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
676 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
637 the ``revlogv1`` and ``generaldelta`` requirements.
677 the ``revlogv1`` and ``generaldelta`` requirements.
638
678
639 If the only format requirement is ``revlogv1``, the server may expose the
679 If the only format requirement is ``revlogv1``, the server may expose the
640 ``stream`` capability instead of the ``streamreqs`` capability.
680 ``stream`` capability instead of the ``streamreqs`` capability.
641
681
642 This capability was introduced in Mercurial 1.7 (released November 2010).
682 This capability was introduced in Mercurial 1.7 (released November 2010).
643
683
644 stream
684 stream
645 ------
685 ------
646
686
647 Whether the server supports *streaming clones* from ``revlogv1`` repos.
687 Whether the server supports *streaming clones* from ``revlogv1`` repos.
648
688
649 If present, the server supports the ``stream_out`` command, which transmits
689 If present, the server supports the ``stream_out`` command, which transmits
650 raw revlogs from the repository instead of changegroups. This provides a faster
690 raw revlogs from the repository instead of changegroups. This provides a faster
651 cloning mechanism at the expense of more bandwidth used.
691 cloning mechanism at the expense of more bandwidth used.
652
692
653 This capability was introduced in Mercurial 0.9.1 (released July 2006).
693 This capability was introduced in Mercurial 0.9.1 (released July 2006).
654
694
655 When initially introduced, the value of the capability was the numeric
695 When initially introduced, the value of the capability was the numeric
656 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
696 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
657 ``revlogv1``. This simple integer value wasn't powerful enough, so the
697 ``revlogv1``. This simple integer value wasn't powerful enough, so the
658 ``streamreqs`` capability was invented to handle cases where the repo
698 ``streamreqs`` capability was invented to handle cases where the repo
659 requirements have more than just ``revlogv1``. Newer servers omit the
699 requirements have more than just ``revlogv1``. Newer servers omit the
660 ``=1`` since it was the only value supported and the value of ``1`` can
700 ``=1`` since it was the only value supported and the value of ``1`` can
661 be implied by clients.
701 be implied by clients.
662
702
663 unbundlehash
703 unbundlehash
664 ------------
704 ------------
665
705
666 Whether the ``unbundle`` commands supports receiving a hash of all the
706 Whether the ``unbundle`` commands supports receiving a hash of all the
667 heads instead of a list.
707 heads instead of a list.
668
708
669 For more, see the documentation for the ``unbundle`` command.
709 For more, see the documentation for the ``unbundle`` command.
670
710
671 This capability was introduced in Mercurial 1.9 (released July 2011).
711 This capability was introduced in Mercurial 1.9 (released July 2011).
672
712
673 unbundle
713 unbundle
674 --------
714 --------
675
715
676 Whether the server supports pushing via the ``unbundle`` command.
716 Whether the server supports pushing via the ``unbundle`` command.
677
717
678 This capability/command has been present since Mercurial 0.9.1 (released
718 This capability/command has been present since Mercurial 0.9.1 (released
679 July 2006).
719 July 2006).
680
720
681 Mercurial 0.9.2 (released December 2006) added values to the capability
721 Mercurial 0.9.2 (released December 2006) added values to the capability
682 indicating which bundle types the server supports receiving. This value is a
722 indicating which bundle types the server supports receiving. This value is a
683 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
723 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
684 reflects the priority/preference of that type, where the first value is the
724 reflects the priority/preference of that type, where the first value is the
685 most preferred type.
725 most preferred type.
686
726
687 Content Negotiation
727 Content Negotiation
688 ===================
728 ===================
689
729
690 The wire protocol has some mechanisms to help peers determine what content
730 The wire protocol has some mechanisms to help peers determine what content
691 types and encoding the other side will accept. Historically, these mechanisms
731 types and encoding the other side will accept. Historically, these mechanisms
692 have been built into commands themselves because most commands only send a
732 have been built into commands themselves because most commands only send a
693 well-defined response type and only certain commands needed to support
733 well-defined response type and only certain commands needed to support
694 functionality like compression.
734 functionality like compression.
695
735
696 Currently, only the HTTP version 1 transport supports content negotiation
736 Currently, only the HTTP version 1 transport supports content negotiation
697 at the protocol layer.
737 at the protocol layer.
698
738
699 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
739 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
700 request header, where ``<N>`` is an integer starting at 1 allowing the logical
740 request header, where ``<N>`` is an integer starting at 1 allowing the logical
701 value to span multiple headers. This value consists of a list of
741 value to span multiple headers. This value consists of a list of
702 space-delimited parameters. Each parameter denotes a feature or capability.
742 space-delimited parameters. Each parameter denotes a feature or capability.
703
743
704 The following parameters are defined:
744 The following parameters are defined:
705
745
706 0.1
746 0.1
707 Indicates the client supports receiving ``application/mercurial-0.1``
747 Indicates the client supports receiving ``application/mercurial-0.1``
708 responses.
748 responses.
709
749
710 0.2
750 0.2
711 Indicates the client supports receiving ``application/mercurial-0.2``
751 Indicates the client supports receiving ``application/mercurial-0.2``
712 responses.
752 responses.
713
753
714 comp
754 comp
715 Indicates compression formats the client can decode. Value is a list of
755 Indicates compression formats the client can decode. Value is a list of
716 comma delimited strings identifying compression formats ordered from
756 comma delimited strings identifying compression formats ordered from
717 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
757 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
718
758
719 This parameter does not have an effect if only the ``0.1`` parameter
759 This parameter does not have an effect if only the ``0.1`` parameter
720 is defined, as support for ``application/mercurial-0.2`` or greater is
760 is defined, as support for ``application/mercurial-0.2`` or greater is
721 required to use arbitrary compression formats.
761 required to use arbitrary compression formats.
722
762
723 If this parameter is not advertised, the server interprets this as
763 If this parameter is not advertised, the server interprets this as
724 equivalent to ``zlib,none``.
764 equivalent to ``zlib,none``.
725
765
726 Clients may choose to only send this header if the ``httpmediatype``
766 Clients may choose to only send this header if the ``httpmediatype``
727 server capability is present, as currently all server-side features
767 server capability is present, as currently all server-side features
728 consulting this header require the client to opt in to new protocol features
768 consulting this header require the client to opt in to new protocol features
729 advertised via the ``httpmediatype`` capability.
769 advertised via the ``httpmediatype`` capability.
730
770
731 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
771 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
732 value of ``0.1``. This is compatible with legacy clients.
772 value of ``0.1``. This is compatible with legacy clients.
733
773
734 A server receiving a request indicating support for multiple media type
774 A server receiving a request indicating support for multiple media type
735 versions may respond with any of the supported media types. Not all servers
775 versions may respond with any of the supported media types. Not all servers
736 may support all media types on all commands.
776 may support all media types on all commands.
737
777
738 Commands
778 Commands
739 ========
779 ========
740
780
741 This section contains a list of all wire protocol commands implemented by
781 This section contains a list of all wire protocol commands implemented by
742 the canonical Mercurial server.
782 the canonical Mercurial server.
743
783
744 batch
784 batch
745 -----
785 -----
746
786
747 Issue multiple commands while sending a single command request. The purpose
787 Issue multiple commands while sending a single command request. The purpose
748 of this command is to allow a client to issue multiple commands while avoiding
788 of this command is to allow a client to issue multiple commands while avoiding
749 multiple round trips to the server therefore enabling commands to complete
789 multiple round trips to the server therefore enabling commands to complete
750 quicker.
790 quicker.
751
791
752 The command accepts a ``cmds`` argument that contains a list of commands to
792 The command accepts a ``cmds`` argument that contains a list of commands to
753 execute.
793 execute.
754
794
755 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
795 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
756 form ``<command> <arguments>``. That is, the command name followed by a space
796 form ``<command> <arguments>``. That is, the command name followed by a space
757 followed by an argument string.
797 followed by an argument string.
758
798
759 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
799 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
760 corresponding to command arguments. Both the argument name and value are
800 corresponding to command arguments. Both the argument name and value are
761 escaped using a special substitution map::
801 escaped using a special substitution map::
762
802
763 : -> :c
803 : -> :c
764 , -> :o
804 , -> :o
765 ; -> :s
805 ; -> :s
766 = -> :e
806 = -> :e
767
807
768 The response type for this command is ``string``. The value contains a
808 The response type for this command is ``string``. The value contains a
769 ``;`` delimited list of responses for each requested command. Each value
809 ``;`` delimited list of responses for each requested command. Each value
770 in this list is escaped using the same substitution map used for arguments.
810 in this list is escaped using the same substitution map used for arguments.
771
811
772 If an error occurs, the generic error response may be sent.
812 If an error occurs, the generic error response may be sent.
773
813
774 between
814 between
775 -------
815 -------
776
816
777 (Legacy command used for discovery in old clients)
817 (Legacy command used for discovery in old clients)
778
818
779 Obtain nodes between pairs of nodes.
819 Obtain nodes between pairs of nodes.
780
820
781 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
821 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
782 hex node pairs. e.g.::
822 hex node pairs. e.g.::
783
823
784 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
824 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
785
825
786 Return type is a ``string``. Value consists of lines corresponding to each
826 Return type is a ``string``. Value consists of lines corresponding to each
787 requested range. Each line contains a space-delimited list of hex nodes.
827 requested range. Each line contains a space-delimited list of hex nodes.
788 A newline ``\n`` terminates each line, including the last one.
828 A newline ``\n`` terminates each line, including the last one.
789
829
790 branchmap
830 branchmap
791 ---------
831 ---------
792
832
793 Obtain heads in named branches.
833 Obtain heads in named branches.
794
834
795 Accepts no arguments. Return type is a ``string``.
835 Accepts no arguments. Return type is a ``string``.
796
836
797 Return value contains lines with URL encoded branch names followed by a space
837 Return value contains lines with URL encoded branch names followed by a space
798 followed by a space-delimited list of hex nodes of heads on that branch.
838 followed by a space-delimited list of hex nodes of heads on that branch.
799 e.g.::
839 e.g.::
800
840
801 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
841 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
802 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
842 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
803
843
804 There is no trailing newline.
844 There is no trailing newline.
805
845
806 branches
846 branches
807 --------
847 --------
808
848
809 (Legacy command used for discovery in old clients. Clients with ``getbundle``
849 (Legacy command used for discovery in old clients. Clients with ``getbundle``
810 use the ``known`` and ``heads`` commands instead.)
850 use the ``known`` and ``heads`` commands instead.)
811
851
812 Obtain ancestor changesets of specific nodes back to a branch point.
852 Obtain ancestor changesets of specific nodes back to a branch point.
813
853
814 Despite the name, this command has nothing to do with Mercurial named branches.
854 Despite the name, this command has nothing to do with Mercurial named branches.
815 Instead, it is related to DAG branches.
855 Instead, it is related to DAG branches.
816
856
817 The command accepts a ``nodes`` argument, which is a string of space-delimited
857 The command accepts a ``nodes`` argument, which is a string of space-delimited
818 hex nodes.
858 hex nodes.
819
859
820 For each node requested, the server will find the first ancestor node that is
860 For each node requested, the server will find the first ancestor node that is
821 a DAG root or is a merge.
861 a DAG root or is a merge.
822
862
823 Return type is a ``string``. Return value contains lines with result data for
863 Return type is a ``string``. Return value contains lines with result data for
824 each requested node. Each line contains space-delimited nodes followed by a
864 each requested node. Each line contains space-delimited nodes followed by a
825 newline (``\n``). The 4 nodes reported on each line correspond to the requested
865 newline (``\n``). The 4 nodes reported on each line correspond to the requested
826 node, the ancestor node found, and its 2 parent nodes (which may be the null
866 node, the ancestor node found, and its 2 parent nodes (which may be the null
827 node).
867 node).
828
868
829 capabilities
869 capabilities
830 ------------
870 ------------
831
871
832 Obtain the capabilities string for the repo.
872 Obtain the capabilities string for the repo.
833
873
834 Unlike the ``hello`` command, the capabilities string is not prefixed.
874 Unlike the ``hello`` command, the capabilities string is not prefixed.
835 There is no trailing newline.
875 There is no trailing newline.
836
876
837 This command does not accept any arguments. Return type is a ``string``.
877 This command does not accept any arguments. Return type is a ``string``.
838
878
839 This command was introduced in Mercurial 0.9.1 (released July 2006).
879 This command was introduced in Mercurial 0.9.1 (released July 2006).
840
880
841 changegroup
881 changegroup
842 -----------
882 -----------
843
883
844 (Legacy command: use ``getbundle`` instead)
884 (Legacy command: use ``getbundle`` instead)
845
885
846 Obtain a changegroup version 1 with data for changesets that are
886 Obtain a changegroup version 1 with data for changesets that are
847 descendants of client-specified changesets.
887 descendants of client-specified changesets.
848
888
849 The ``roots`` arguments contains a list of space-delimited hex nodes.
889 The ``roots`` arguments contains a list of space-delimited hex nodes.
850
890
851 The server responds with a changegroup version 1 containing all
891 The server responds with a changegroup version 1 containing all
852 changesets between the requested root/base nodes and the repo's head nodes
892 changesets between the requested root/base nodes and the repo's head nodes
853 at the time of the request.
893 at the time of the request.
854
894
855 The return type is a ``stream``.
895 The return type is a ``stream``.
856
896
857 changegroupsubset
897 changegroupsubset
858 -----------------
898 -----------------
859
899
860 (Legacy command: use ``getbundle`` instead)
900 (Legacy command: use ``getbundle`` instead)
861
901
862 Obtain a changegroup version 1 with data for changesetsets between
902 Obtain a changegroup version 1 with data for changesetsets between
863 client specified base and head nodes.
903 client specified base and head nodes.
864
904
865 The ``bases`` argument contains a list of space-delimited hex nodes.
905 The ``bases`` argument contains a list of space-delimited hex nodes.
866 The ``heads`` argument contains a list of space-delimited hex nodes.
906 The ``heads`` argument contains a list of space-delimited hex nodes.
867
907
868 The server responds with a changegroup version 1 containing all
908 The server responds with a changegroup version 1 containing all
869 changesets between the requested base and head nodes at the time of the
909 changesets between the requested base and head nodes at the time of the
870 request.
910 request.
871
911
872 The return type is a ``stream``.
912 The return type is a ``stream``.
873
913
874 clonebundles
914 clonebundles
875 ------------
915 ------------
876
916
877 Obtains a manifest of bundle URLs available to seed clones.
917 Obtains a manifest of bundle URLs available to seed clones.
878
918
879 Each returned line contains a URL followed by metadata. See the
919 Each returned line contains a URL followed by metadata. See the
880 documentation in the ``clonebundles`` extension for more.
920 documentation in the ``clonebundles`` extension for more.
881
921
882 The return type is a ``string``.
922 The return type is a ``string``.
883
923
884 getbundle
924 getbundle
885 ---------
925 ---------
886
926
887 Obtain a bundle containing repository data.
927 Obtain a bundle containing repository data.
888
928
889 This command accepts the following arguments:
929 This command accepts the following arguments:
890
930
891 heads
931 heads
892 List of space-delimited hex nodes of heads to retrieve.
932 List of space-delimited hex nodes of heads to retrieve.
893 common
933 common
894 List of space-delimited hex nodes that the client has in common with the
934 List of space-delimited hex nodes that the client has in common with the
895 server.
935 server.
896 obsmarkers
936 obsmarkers
897 Boolean indicating whether to include obsolescence markers as part
937 Boolean indicating whether to include obsolescence markers as part
898 of the response. Only works with bundle2.
938 of the response. Only works with bundle2.
899 bundlecaps
939 bundlecaps
900 Comma-delimited set of strings defining client bundle capabilities.
940 Comma-delimited set of strings defining client bundle capabilities.
901 listkeys
941 listkeys
902 Comma-delimited list of strings of ``pushkey`` namespaces. For each
942 Comma-delimited list of strings of ``pushkey`` namespaces. For each
903 namespace listed, a bundle2 part will be included with the content of
943 namespace listed, a bundle2 part will be included with the content of
904 that namespace.
944 that namespace.
905 cg
945 cg
906 Boolean indicating whether changegroup data is requested.
946 Boolean indicating whether changegroup data is requested.
907 cbattempted
947 cbattempted
908 Boolean indicating whether the client attempted to use the *clone bundles*
948 Boolean indicating whether the client attempted to use the *clone bundles*
909 feature before performing this request.
949 feature before performing this request.
910 bookmarks
950 bookmarks
911 Boolean indicating whether bookmark data is requested.
951 Boolean indicating whether bookmark data is requested.
912 phases
952 phases
913 Boolean indicating whether phases data is requested.
953 Boolean indicating whether phases data is requested.
914
954
915 The return type on success is a ``stream`` where the value is bundle.
955 The return type on success is a ``stream`` where the value is bundle.
916 On the HTTP version 1 transport, the response is zlib compressed.
956 On the HTTP version 1 transport, the response is zlib compressed.
917
957
918 If an error occurs, a generic error response can be sent.
958 If an error occurs, a generic error response can be sent.
919
959
920 Unless the client sends a false value for the ``cg`` argument, the returned
960 Unless the client sends a false value for the ``cg`` argument, the returned
921 bundle contains a changegroup with the nodes between the specified ``common``
961 bundle contains a changegroup with the nodes between the specified ``common``
922 and ``heads`` nodes. Depending on the command arguments, the type and content
962 and ``heads`` nodes. Depending on the command arguments, the type and content
923 of the returned bundle can vary significantly.
963 of the returned bundle can vary significantly.
924
964
925 The default behavior is for the server to send a raw changegroup version
965 The default behavior is for the server to send a raw changegroup version
926 ``01`` response.
966 ``01`` response.
927
967
928 If the ``bundlecaps`` provided by the client contain a value beginning
968 If the ``bundlecaps`` provided by the client contain a value beginning
929 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
969 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
930 additional repository data, such as ``pushkey`` namespace values.
970 additional repository data, such as ``pushkey`` namespace values.
931
971
932 heads
972 heads
933 -----
973 -----
934
974
935 Returns a list of space-delimited hex nodes of repository heads followed
975 Returns a list of space-delimited hex nodes of repository heads followed
936 by a newline. e.g.
976 by a newline. e.g.
937 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
977 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
938
978
939 This command does not accept any arguments. The return type is a ``string``.
979 This command does not accept any arguments. The return type is a ``string``.
940
980
941 hello
981 hello
942 -----
982 -----
943
983
944 Returns lines describing interesting things about the server in an RFC-822
984 Returns lines describing interesting things about the server in an RFC-822
945 like format.
985 like format.
946
986
947 Currently, the only line defines the server capabilities. It has the form::
987 Currently, the only line defines the server capabilities. It has the form::
948
988
949 capabilities: <value>
989 capabilities: <value>
950
990
951 See above for more about the capabilities string.
991 See above for more about the capabilities string.
952
992
953 SSH clients typically issue this command as soon as a connection is
993 SSH clients typically issue this command as soon as a connection is
954 established.
994 established.
955
995
956 This command does not accept any arguments. The return type is a ``string``.
996 This command does not accept any arguments. The return type is a ``string``.
957
997
958 This command was introduced in Mercurial 0.9.1 (released July 2006).
998 This command was introduced in Mercurial 0.9.1 (released July 2006).
959
999
960 listkeys
1000 listkeys
961 --------
1001 --------
962
1002
963 List values in a specified ``pushkey`` namespace.
1003 List values in a specified ``pushkey`` namespace.
964
1004
965 The ``namespace`` argument defines the pushkey namespace to operate on.
1005 The ``namespace`` argument defines the pushkey namespace to operate on.
966
1006
967 The return type is a ``string``. The value is an encoded dictionary of keys.
1007 The return type is a ``string``. The value is an encoded dictionary of keys.
968
1008
969 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
1009 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
970 values are separated by a tab (``\t``). Keys and values are both strings.
1010 values are separated by a tab (``\t``). Keys and values are both strings.
971
1011
972 lookup
1012 lookup
973 ------
1013 ------
974
1014
975 Try to resolve a value to a known repository revision.
1015 Try to resolve a value to a known repository revision.
976
1016
977 The ``key`` argument is converted from bytes to an
1017 The ``key`` argument is converted from bytes to an
978 ``encoding.localstr`` instance then passed into
1018 ``encoding.localstr`` instance then passed into
979 ``localrepository.__getitem__`` in an attempt to resolve it.
1019 ``localrepository.__getitem__`` in an attempt to resolve it.
980
1020
981 The return type is a ``string``.
1021 The return type is a ``string``.
982
1022
983 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
1023 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
984 returns ``0 <error string>\n``. e.g.::
1024 returns ``0 <error string>\n``. e.g.::
985
1025
986 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
1026 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
987
1027
988 0 unknown revision 'foo'\n
1028 0 unknown revision 'foo'\n
989
1029
990 known
1030 known
991 -----
1031 -----
992
1032
993 Determine whether multiple nodes are known.
1033 Determine whether multiple nodes are known.
994
1034
995 The ``nodes`` argument is a list of space-delimited hex nodes to check
1035 The ``nodes`` argument is a list of space-delimited hex nodes to check
996 for existence.
1036 for existence.
997
1037
998 The return type is ``string``.
1038 The return type is ``string``.
999
1039
1000 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1040 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1001 are known. If the Nth node specified in the ``nodes`` argument is known,
1041 are known. If the Nth node specified in the ``nodes`` argument is known,
1002 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1042 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1003 will be present at byte offset N.
1043 will be present at byte offset N.
1004
1044
1005 There is no trailing newline.
1045 There is no trailing newline.
1006
1046
1007 pushkey
1047 pushkey
1008 -------
1048 -------
1009
1049
1010 Set a value using the ``pushkey`` protocol.
1050 Set a value using the ``pushkey`` protocol.
1011
1051
1012 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1052 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1013 correspond to the pushkey namespace to operate on, the key within that
1053 correspond to the pushkey namespace to operate on, the key within that
1014 namespace to change, the old value (which may be empty), and the new value.
1054 namespace to change, the old value (which may be empty), and the new value.
1015 All arguments are string types.
1055 All arguments are string types.
1016
1056
1017 The return type is a ``string``. The value depends on the transport protocol.
1057 The return type is a ``string``. The value depends on the transport protocol.
1018
1058
1019 The SSH version 1 transport sends a string encoded integer followed by a
1059 The SSH version 1 transport sends a string encoded integer followed by a
1020 newline (``\n``) which indicates operation result. The server may send
1060 newline (``\n``) which indicates operation result. The server may send
1021 additional output on the ``stderr`` stream that should be displayed to the
1061 additional output on the ``stderr`` stream that should be displayed to the
1022 user.
1062 user.
1023
1063
1024 The HTTP version 1 transport sends a string encoded integer followed by a
1064 The HTTP version 1 transport sends a string encoded integer followed by a
1025 newline followed by additional server output that should be displayed to
1065 newline followed by additional server output that should be displayed to
1026 the user. This may include output from hooks, etc.
1066 the user. This may include output from hooks, etc.
1027
1067
1028 The integer result varies by namespace. ``0`` means an error has occurred
1068 The integer result varies by namespace. ``0`` means an error has occurred
1029 and there should be additional output to display to the user.
1069 and there should be additional output to display to the user.
1030
1070
1031 stream_out
1071 stream_out
1032 ----------
1072 ----------
1033
1073
1034 Obtain *streaming clone* data.
1074 Obtain *streaming clone* data.
1035
1075
1036 The return type is either a ``string`` or a ``stream``, depending on
1076 The return type is either a ``string`` or a ``stream``, depending on
1037 whether the request was fulfilled properly.
1077 whether the request was fulfilled properly.
1038
1078
1039 A return value of ``1\n`` indicates the server is not configured to serve
1079 A return value of ``1\n`` indicates the server is not configured to serve
1040 this data. If this is seen by the client, they may not have verified the
1080 this data. If this is seen by the client, they may not have verified the
1041 ``stream`` capability is set before making the request.
1081 ``stream`` capability is set before making the request.
1042
1082
1043 A return value of ``2\n`` indicates the server was unable to lock the
1083 A return value of ``2\n`` indicates the server was unable to lock the
1044 repository to generate data.
1084 repository to generate data.
1045
1085
1046 All other responses are a ``stream`` of bytes. The first line of this data
1086 All other responses are a ``stream`` of bytes. The first line of this data
1047 contains 2 space-delimited integers corresponding to the path count and
1087 contains 2 space-delimited integers corresponding to the path count and
1048 payload size, respectively::
1088 payload size, respectively::
1049
1089
1050 <path count> <payload size>\n
1090 <path count> <payload size>\n
1051
1091
1052 The ``<payload size>`` is the total size of path data: it does not include
1092 The ``<payload size>`` is the total size of path data: it does not include
1053 the size of the per-path header lines.
1093 the size of the per-path header lines.
1054
1094
1055 Following that header are ``<path count>`` entries. Each entry consists of a
1095 Following that header are ``<path count>`` entries. Each entry consists of a
1056 line with metadata followed by raw revlog data. The line consists of::
1096 line with metadata followed by raw revlog data. The line consists of::
1057
1097
1058 <store path>\0<size>\n
1098 <store path>\0<size>\n
1059
1099
1060 The ``<store path>`` is the encoded store path of the data that follows.
1100 The ``<store path>`` is the encoded store path of the data that follows.
1061 ``<size>`` is the amount of data for this store path/revlog that follows the
1101 ``<size>`` is the amount of data for this store path/revlog that follows the
1062 newline.
1102 newline.
1063
1103
1064 There is no trailer to indicate end of data. Instead, the client should stop
1104 There is no trailer to indicate end of data. Instead, the client should stop
1065 reading after ``<path count>`` entries are consumed.
1105 reading after ``<path count>`` entries are consumed.
1066
1106
1067 unbundle
1107 unbundle
1068 --------
1108 --------
1069
1109
1070 Send a bundle containing data (usually changegroup data) to the server.
1110 Send a bundle containing data (usually changegroup data) to the server.
1071
1111
1072 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1112 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1073 corresponding to server repository heads observed by the client. This is used
1113 corresponding to server repository heads observed by the client. This is used
1074 to detect race conditions and abort push operations before a server performs
1114 to detect race conditions and abort push operations before a server performs
1075 too much work or a client transfers too much data.
1115 too much work or a client transfers too much data.
1076
1116
1077 The request payload consists of a bundle to be applied to the repository,
1117 The request payload consists of a bundle to be applied to the repository,
1078 similarly to as if :hg:`unbundle` were called.
1118 similarly to as if :hg:`unbundle` were called.
1079
1119
1080 In most scenarios, a special ``push response`` type is returned. This type
1120 In most scenarios, a special ``push response`` type is returned. This type
1081 contains an integer describing the change in heads as a result of the
1121 contains an integer describing the change in heads as a result of the
1082 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1122 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1083 of heads remained the same. Values ``2`` and larger indicate the number of
1123 of heads remained the same. Values ``2`` and larger indicate the number of
1084 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1124 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1085 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1125 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1086 is 1 fewer head.
1126 is 1 fewer head.
1087
1127
1088 The encoding of the ``push response`` type varies by transport.
1128 The encoding of the ``push response`` type varies by transport.
1089
1129
1090 For the SSH version 1 transport, this type is composed of 2 ``string``
1130 For the SSH version 1 transport, this type is composed of 2 ``string``
1091 responses: an empty response (``0\n``) followed by the integer result value.
1131 responses: an empty response (``0\n``) followed by the integer result value.
1092 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1132 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1093
1133
1094 For the HTTP version 1 transport, the response is a ``string`` type composed
1134 For the HTTP version 1 transport, the response is a ``string`` type composed
1095 of an integer result value followed by a newline (``\n``) followed by string
1135 of an integer result value followed by a newline (``\n``) followed by string
1096 content holding server output that should be displayed on the client (output
1136 content holding server output that should be displayed on the client (output
1097 hooks, etc).
1137 hooks, etc).
1098
1138
1099 In some cases, the server may respond with a ``bundle2`` bundle. In this
1139 In some cases, the server may respond with a ``bundle2`` bundle. In this
1100 case, the response type is ``stream``. For the HTTP version 1 transport, the
1140 case, the response type is ``stream``. For the HTTP version 1 transport, the
1101 response is zlib compressed.
1141 response is zlib compressed.
1102
1142
1103 The server may also respond with a generic error type, which contains a string
1143 The server may also respond with a generic error type, which contains a string
1104 indicating the failure.
1144 indicating the failure.
@@ -1,723 +1,809 b''
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import struct
10 import struct
11 import sys
11 import sys
12 import threading
12 import threading
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 encoding,
16 encoding,
17 error,
17 error,
18 hook,
18 hook,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 wireproto,
21 wireproto,
22 wireprototypes,
22 wireprototypes,
23 )
23 )
24
24
25 stringio = util.stringio
25 stringio = util.stringio
26
26
27 urlerr = util.urlerr
27 urlerr = util.urlerr
28 urlreq = util.urlreq
28 urlreq = util.urlreq
29
29
30 HTTP_OK = 200
30 HTTP_OK = 200
31
31
32 HGTYPE = 'application/mercurial-0.1'
32 HGTYPE = 'application/mercurial-0.1'
33 HGTYPE2 = 'application/mercurial-0.2'
33 HGTYPE2 = 'application/mercurial-0.2'
34 HGERRTYPE = 'application/hg-error'
34 HGERRTYPE = 'application/hg-error'
35
35
36 HTTPV2 = wireprototypes.HTTPV2
36 HTTPV2 = wireprototypes.HTTPV2
37 SSHV1 = wireprototypes.SSHV1
37 SSHV1 = wireprototypes.SSHV1
38 SSHV2 = wireprototypes.SSHV2
38 SSHV2 = wireprototypes.SSHV2
39
39
40 def decodevaluefromheaders(req, headerprefix):
40 def decodevaluefromheaders(req, headerprefix):
41 """Decode a long value from multiple HTTP request headers.
41 """Decode a long value from multiple HTTP request headers.
42
42
43 Returns the value as a bytes, not a str.
43 Returns the value as a bytes, not a str.
44 """
44 """
45 chunks = []
45 chunks = []
46 i = 1
46 i = 1
47 while True:
47 while True:
48 v = req.headers.get(b'%s-%d' % (headerprefix, i))
48 v = req.headers.get(b'%s-%d' % (headerprefix, i))
49 if v is None:
49 if v is None:
50 break
50 break
51 chunks.append(pycompat.bytesurl(v))
51 chunks.append(pycompat.bytesurl(v))
52 i += 1
52 i += 1
53
53
54 return ''.join(chunks)
54 return ''.join(chunks)
55
55
56 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
56 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
57 def __init__(self, req, ui, checkperm):
57 def __init__(self, req, ui, checkperm):
58 self._req = req
58 self._req = req
59 self._ui = ui
59 self._ui = ui
60 self._checkperm = checkperm
60 self._checkperm = checkperm
61
61
62 @property
62 @property
63 def name(self):
63 def name(self):
64 return 'http-v1'
64 return 'http-v1'
65
65
66 def getargs(self, args):
66 def getargs(self, args):
67 knownargs = self._args()
67 knownargs = self._args()
68 data = {}
68 data = {}
69 keys = args.split()
69 keys = args.split()
70 for k in keys:
70 for k in keys:
71 if k == '*':
71 if k == '*':
72 star = {}
72 star = {}
73 for key in knownargs.keys():
73 for key in knownargs.keys():
74 if key != 'cmd' and key not in keys:
74 if key != 'cmd' and key not in keys:
75 star[key] = knownargs[key][0]
75 star[key] = knownargs[key][0]
76 data['*'] = star
76 data['*'] = star
77 else:
77 else:
78 data[k] = knownargs[k][0]
78 data[k] = knownargs[k][0]
79 return [data[k] for k in keys]
79 return [data[k] for k in keys]
80
80
81 def _args(self):
81 def _args(self):
82 args = self._req.qsparams.asdictoflists()
82 args = self._req.qsparams.asdictoflists()
83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
84 if postlen:
84 if postlen:
85 args.update(urlreq.parseqs(
85 args.update(urlreq.parseqs(
86 self._req.bodyfh.read(postlen), keep_blank_values=True))
86 self._req.bodyfh.read(postlen), keep_blank_values=True))
87 return args
87 return args
88
88
89 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
89 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
91 return args
91 return args
92
92
93 def forwardpayload(self, fp):
93 def forwardpayload(self, fp):
94 # Existing clients *always* send Content-Length.
94 # Existing clients *always* send Content-Length.
95 length = int(self._req.headers[b'Content-Length'])
95 length = int(self._req.headers[b'Content-Length'])
96
96
97 # If httppostargs is used, we need to read Content-Length
97 # If httppostargs is used, we need to read Content-Length
98 # minus the amount that was consumed by args.
98 # minus the amount that was consumed by args.
99 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
99 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
100 for s in util.filechunkiter(self._req.bodyfh, limit=length):
100 for s in util.filechunkiter(self._req.bodyfh, limit=length):
101 fp.write(s)
101 fp.write(s)
102
102
103 @contextlib.contextmanager
103 @contextlib.contextmanager
104 def mayberedirectstdio(self):
104 def mayberedirectstdio(self):
105 oldout = self._ui.fout
105 oldout = self._ui.fout
106 olderr = self._ui.ferr
106 olderr = self._ui.ferr
107
107
108 out = util.stringio()
108 out = util.stringio()
109
109
110 try:
110 try:
111 self._ui.fout = out
111 self._ui.fout = out
112 self._ui.ferr = out
112 self._ui.ferr = out
113 yield out
113 yield out
114 finally:
114 finally:
115 self._ui.fout = oldout
115 self._ui.fout = oldout
116 self._ui.ferr = olderr
116 self._ui.ferr = olderr
117
117
118 def client(self):
118 def client(self):
119 return 'remote:%s:%s:%s' % (
119 return 'remote:%s:%s:%s' % (
120 self._req.urlscheme,
120 self._req.urlscheme,
121 urlreq.quote(self._req.remotehost or ''),
121 urlreq.quote(self._req.remotehost or ''),
122 urlreq.quote(self._req.remoteuser or ''))
122 urlreq.quote(self._req.remoteuser or ''))
123
123
124 def addcapabilities(self, repo, caps):
124 def addcapabilities(self, repo, caps):
125 caps.append('httpheader=%d' %
125 caps.append('httpheader=%d' %
126 repo.ui.configint('server', 'maxhttpheaderlen'))
126 repo.ui.configint('server', 'maxhttpheaderlen'))
127 if repo.ui.configbool('experimental', 'httppostargs'):
127 if repo.ui.configbool('experimental', 'httppostargs'):
128 caps.append('httppostargs')
128 caps.append('httppostargs')
129
129
130 # FUTURE advertise 0.2rx once support is implemented
130 # FUTURE advertise 0.2rx once support is implemented
131 # FUTURE advertise minrx and mintx after consulting config option
131 # FUTURE advertise minrx and mintx after consulting config option
132 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
132 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
133
133
134 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
134 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
135 if compengines:
135 if compengines:
136 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
136 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
137 for e in compengines)
137 for e in compengines)
138 caps.append('compression=%s' % comptypes)
138 caps.append('compression=%s' % comptypes)
139
139
140 return caps
140 return caps
141
141
142 def checkperm(self, perm):
142 def checkperm(self, perm):
143 return self._checkperm(perm)
143 return self._checkperm(perm)
144
144
145 # This method exists mostly so that extensions like remotefilelog can
145 # This method exists mostly so that extensions like remotefilelog can
146 # disable a kludgey legacy method only over http. As of early 2018,
146 # disable a kludgey legacy method only over http. As of early 2018,
147 # there are no other known users, so with any luck we can discard this
147 # there are no other known users, so with any luck we can discard this
148 # hook if remotefilelog becomes a first-party extension.
148 # hook if remotefilelog becomes a first-party extension.
149 def iscmd(cmd):
149 def iscmd(cmd):
150 return cmd in wireproto.commands
150 return cmd in wireproto.commands
151
151
152 def handlewsgirequest(rctx, req, res, checkperm):
152 def handlewsgirequest(rctx, req, res, checkperm):
153 """Possibly process a wire protocol request.
153 """Possibly process a wire protocol request.
154
154
155 If the current request is a wire protocol request, the request is
155 If the current request is a wire protocol request, the request is
156 processed by this function.
156 processed by this function.
157
157
158 ``req`` is a ``parsedrequest`` instance.
158 ``req`` is a ``parsedrequest`` instance.
159 ``res`` is a ``wsgiresponse`` instance.
159 ``res`` is a ``wsgiresponse`` instance.
160
160
161 Returns a bool indicating if the request was serviced. If set, the caller
161 Returns a bool indicating if the request was serviced. If set, the caller
162 should stop processing the request, as a response has already been issued.
162 should stop processing the request, as a response has already been issued.
163 """
163 """
164 # Avoid cycle involving hg module.
164 # Avoid cycle involving hg module.
165 from .hgweb import common as hgwebcommon
165 from .hgweb import common as hgwebcommon
166
166
167 repo = rctx.repo
167 repo = rctx.repo
168
168
169 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
169 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
170 # string parameter. If it isn't present, this isn't a wire protocol
170 # string parameter. If it isn't present, this isn't a wire protocol
171 # request.
171 # request.
172 if 'cmd' not in req.qsparams:
172 if 'cmd' not in req.qsparams:
173 return False
173 return False
174
174
175 cmd = req.qsparams['cmd']
175 cmd = req.qsparams['cmd']
176
176
177 # The "cmd" request parameter is used by both the wire protocol and hgweb.
177 # The "cmd" request parameter is used by both the wire protocol and hgweb.
178 # While not all wire protocol commands are available for all transports,
178 # While not all wire protocol commands are available for all transports,
179 # if we see a "cmd" value that resembles a known wire protocol command, we
179 # if we see a "cmd" value that resembles a known wire protocol command, we
180 # route it to a protocol handler. This is better than routing possible
180 # route it to a protocol handler. This is better than routing possible
181 # wire protocol requests to hgweb because it prevents hgweb from using
181 # wire protocol requests to hgweb because it prevents hgweb from using
182 # known wire protocol commands and it is less confusing for machine
182 # known wire protocol commands and it is less confusing for machine
183 # clients.
183 # clients.
184 if not iscmd(cmd):
184 if not iscmd(cmd):
185 return False
185 return False
186
186
187 # The "cmd" query string argument is only valid on the root path of the
187 # The "cmd" query string argument is only valid on the root path of the
188 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
188 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
189 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
189 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
190 # in this case. We send an HTTP 404 for backwards compatibility reasons.
190 # in this case. We send an HTTP 404 for backwards compatibility reasons.
191 if req.dispatchpath:
191 if req.dispatchpath:
192 res.status = hgwebcommon.statusmessage(404)
192 res.status = hgwebcommon.statusmessage(404)
193 res.headers['Content-Type'] = HGTYPE
193 res.headers['Content-Type'] = HGTYPE
194 # TODO This is not a good response to issue for this request. This
194 # TODO This is not a good response to issue for this request. This
195 # is mostly for BC for now.
195 # is mostly for BC for now.
196 res.setbodybytes('0\n%s\n' % b'Not Found')
196 res.setbodybytes('0\n%s\n' % b'Not Found')
197 return True
197 return True
198
198
199 proto = httpv1protocolhandler(req, repo.ui,
199 proto = httpv1protocolhandler(req, repo.ui,
200 lambda perm: checkperm(rctx, req, perm))
200 lambda perm: checkperm(rctx, req, perm))
201
201
202 # The permissions checker should be the only thing that can raise an
202 # The permissions checker should be the only thing that can raise an
203 # ErrorResponse. It is kind of a layer violation to catch an hgweb
203 # ErrorResponse. It is kind of a layer violation to catch an hgweb
204 # exception here. So consider refactoring into a exception type that
204 # exception here. So consider refactoring into a exception type that
205 # is associated with the wire protocol.
205 # is associated with the wire protocol.
206 try:
206 try:
207 _callhttp(repo, req, res, proto, cmd)
207 _callhttp(repo, req, res, proto, cmd)
208 except hgwebcommon.ErrorResponse as e:
208 except hgwebcommon.ErrorResponse as e:
209 for k, v in e.headers:
209 for k, v in e.headers:
210 res.headers[k] = v
210 res.headers[k] = v
211 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
211 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
212 # TODO This response body assumes the failed command was
212 # TODO This response body assumes the failed command was
213 # "unbundle." That assumption is not always valid.
213 # "unbundle." That assumption is not always valid.
214 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
214 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
215
215
216 return True
216 return True
217
217
218 def handlewsgiapirequest(rctx, req, res, checkperm):
218 def handlewsgiapirequest(rctx, req, res, checkperm):
219 """Handle requests to /api/*."""
219 """Handle requests to /api/*."""
220 assert req.dispatchparts[0] == b'api'
220 assert req.dispatchparts[0] == b'api'
221
221
222 repo = rctx.repo
222 repo = rctx.repo
223
223
224 # This whole URL space is experimental for now. But we want to
224 # This whole URL space is experimental for now. But we want to
225 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
225 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
226 if not repo.ui.configbool('experimental', 'web.apiserver'):
226 if not repo.ui.configbool('experimental', 'web.apiserver'):
227 res.status = b'404 Not Found'
227 res.status = b'404 Not Found'
228 res.headers[b'Content-Type'] = b'text/plain'
228 res.headers[b'Content-Type'] = b'text/plain'
229 res.setbodybytes(_('Experimental API server endpoint not enabled'))
229 res.setbodybytes(_('Experimental API server endpoint not enabled'))
230 return
230 return
231
231
232 # The URL space is /api/<protocol>/*. The structure of URLs under varies
232 # The URL space is /api/<protocol>/*. The structure of URLs under varies
233 # by <protocol>.
233 # by <protocol>.
234
234
235 # Registered APIs are made available via config options of the name of
235 # Registered APIs are made available via config options of the name of
236 # the protocol.
236 # the protocol.
237 availableapis = set()
237 availableapis = set()
238 for k, v in API_HANDLERS.items():
238 for k, v in API_HANDLERS.items():
239 section, option = v['config']
239 section, option = v['config']
240 if repo.ui.configbool(section, option):
240 if repo.ui.configbool(section, option):
241 availableapis.add(k)
241 availableapis.add(k)
242
242
243 # Requests to /api/ list available APIs.
243 # Requests to /api/ list available APIs.
244 if req.dispatchparts == [b'api']:
244 if req.dispatchparts == [b'api']:
245 res.status = b'200 OK'
245 res.status = b'200 OK'
246 res.headers[b'Content-Type'] = b'text/plain'
246 res.headers[b'Content-Type'] = b'text/plain'
247 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
247 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
248 'one of the following:\n')]
248 'one of the following:\n')]
249 if availableapis:
249 if availableapis:
250 lines.extend(sorted(availableapis))
250 lines.extend(sorted(availableapis))
251 else:
251 else:
252 lines.append(_('(no available APIs)\n'))
252 lines.append(_('(no available APIs)\n'))
253 res.setbodybytes(b'\n'.join(lines))
253 res.setbodybytes(b'\n'.join(lines))
254 return
254 return
255
255
256 proto = req.dispatchparts[1]
256 proto = req.dispatchparts[1]
257
257
258 if proto not in API_HANDLERS:
258 if proto not in API_HANDLERS:
259 res.status = b'404 Not Found'
259 res.status = b'404 Not Found'
260 res.headers[b'Content-Type'] = b'text/plain'
260 res.headers[b'Content-Type'] = b'text/plain'
261 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
261 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
262 proto, b', '.join(sorted(availableapis))))
262 proto, b', '.join(sorted(availableapis))))
263 return
263 return
264
264
265 if proto not in availableapis:
265 if proto not in availableapis:
266 res.status = b'404 Not Found'
266 res.status = b'404 Not Found'
267 res.headers[b'Content-Type'] = b'text/plain'
267 res.headers[b'Content-Type'] = b'text/plain'
268 res.setbodybytes(_('API %s not enabled\n') % proto)
268 res.setbodybytes(_('API %s not enabled\n') % proto)
269 return
269 return
270
270
271 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
271 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
272 req.dispatchparts[2:])
272 req.dispatchparts[2:])
273
273
274 def _handlehttpv2request(rctx, req, res, checkperm, urlparts):
274 def _handlehttpv2request(rctx, req, res, checkperm, urlparts):
275 from .hgweb import common as hgwebcommon
276
277 # URL space looks like: <permissions>/<command>, where <permission> can
278 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
279
280 # Root URL does nothing meaningful... yet.
281 if not urlparts:
282 res.status = b'200 OK'
283 res.headers[b'Content-Type'] = b'text/plain'
284 res.setbodybytes(_('HTTP version 2 API handler'))
285 return
286
287 if len(urlparts) == 1:
288 res.status = b'404 Not Found'
289 res.headers[b'Content-Type'] = b'text/plain'
290 res.setbodybytes(_('do not know how to process %s\n') %
291 req.dispatchpath)
292 return
293
294 permission, command = urlparts[0:2]
295
296 if permission not in (b'ro', b'rw'):
297 res.status = b'404 Not Found'
298 res.headers[b'Content-Type'] = b'text/plain'
299 res.setbodybytes(_('unknown permission: %s') % permission)
300 return
301
302 # At some point we'll want to use our own API instead of recycling the
303 # behavior of version 1 of the wire protocol...
304 # TODO return reasonable responses - not responses that overload the
305 # HTTP status line message for error reporting.
306 try:
307 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
308 except hgwebcommon.ErrorResponse as e:
309 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
310 for k, v in e.headers:
311 res.headers[k] = v
312 res.setbodybytes('permission denied')
313 return
314
315 if command not in wireproto.commands:
316 res.status = b'404 Not Found'
317 res.headers[b'Content-Type'] = b'text/plain'
318 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
319 return
320
321 repo = rctx.repo
322 ui = repo.ui
323
324 proto = httpv2protocolhandler(req, ui)
325
326 if not wireproto.commands.commandavailable(command, proto):
327 res.status = b'404 Not Found'
328 res.headers[b'Content-Type'] = b'text/plain'
329 res.setbodybytes(_('invalid wire protocol command: %s') % command)
330 return
331
332 # We don't do anything meaningful yet.
275 res.status = b'200 OK'
333 res.status = b'200 OK'
276 res.headers[b'Content-Type'] = b'text/plain'
334 res.headers[b'Content-Type'] = b'text/plain'
277 res.setbodybytes(b'/'.join(urlparts) + b'\n')
335 res.setbodybytes(b'/'.join(urlparts) + b'\n')
278
336
279 # Maps API name to metadata so custom API can be registered.
337 # Maps API name to metadata so custom API can be registered.
280 API_HANDLERS = {
338 API_HANDLERS = {
281 HTTPV2: {
339 HTTPV2: {
282 'config': ('experimental', 'web.api.http-v2'),
340 'config': ('experimental', 'web.api.http-v2'),
283 'handler': _handlehttpv2request,
341 'handler': _handlehttpv2request,
284 },
342 },
285 }
343 }
286
344
345 class httpv2protocolhandler(wireprototypes.baseprotocolhandler):
346 def __init__(self, req, ui):
347 self._req = req
348 self._ui = ui
349
350 @property
351 def name(self):
352 return HTTPV2
353
354 def getargs(self, args):
355 raise NotImplementedError
356
357 def forwardpayload(self, fp):
358 raise NotImplementedError
359
360 @contextlib.contextmanager
361 def mayberedirectstdio(self):
362 raise NotImplementedError
363
364 def client(self):
365 raise NotImplementedError
366
367 def addcapabilities(self, repo, caps):
368 raise NotImplementedError
369
370 def checkperm(self, perm):
371 raise NotImplementedError
372
287 def _httpresponsetype(ui, req, prefer_uncompressed):
373 def _httpresponsetype(ui, req, prefer_uncompressed):
288 """Determine the appropriate response type and compression settings.
374 """Determine the appropriate response type and compression settings.
289
375
290 Returns a tuple of (mediatype, compengine, engineopts).
376 Returns a tuple of (mediatype, compengine, engineopts).
291 """
377 """
292 # Determine the response media type and compression engine based
378 # Determine the response media type and compression engine based
293 # on the request parameters.
379 # on the request parameters.
294 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
380 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
295
381
296 if '0.2' in protocaps:
382 if '0.2' in protocaps:
297 # All clients are expected to support uncompressed data.
383 # All clients are expected to support uncompressed data.
298 if prefer_uncompressed:
384 if prefer_uncompressed:
299 return HGTYPE2, util._noopengine(), {}
385 return HGTYPE2, util._noopengine(), {}
300
386
301 # Default as defined by wire protocol spec.
387 # Default as defined by wire protocol spec.
302 compformats = ['zlib', 'none']
388 compformats = ['zlib', 'none']
303 for cap in protocaps:
389 for cap in protocaps:
304 if cap.startswith('comp='):
390 if cap.startswith('comp='):
305 compformats = cap[5:].split(',')
391 compformats = cap[5:].split(',')
306 break
392 break
307
393
308 # Now find an agreed upon compression format.
394 # Now find an agreed upon compression format.
309 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
395 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
310 if engine.wireprotosupport().name in compformats:
396 if engine.wireprotosupport().name in compformats:
311 opts = {}
397 opts = {}
312 level = ui.configint('server', '%slevel' % engine.name())
398 level = ui.configint('server', '%slevel' % engine.name())
313 if level is not None:
399 if level is not None:
314 opts['level'] = level
400 opts['level'] = level
315
401
316 return HGTYPE2, engine, opts
402 return HGTYPE2, engine, opts
317
403
318 # No mutually supported compression format. Fall back to the
404 # No mutually supported compression format. Fall back to the
319 # legacy protocol.
405 # legacy protocol.
320
406
321 # Don't allow untrusted settings because disabling compression or
407 # Don't allow untrusted settings because disabling compression or
322 # setting a very high compression level could lead to flooding
408 # setting a very high compression level could lead to flooding
323 # the server's network or CPU.
409 # the server's network or CPU.
324 opts = {'level': ui.configint('server', 'zliblevel')}
410 opts = {'level': ui.configint('server', 'zliblevel')}
325 return HGTYPE, util.compengines['zlib'], opts
411 return HGTYPE, util.compengines['zlib'], opts
326
412
327 def _callhttp(repo, req, res, proto, cmd):
413 def _callhttp(repo, req, res, proto, cmd):
328 # Avoid cycle involving hg module.
414 # Avoid cycle involving hg module.
329 from .hgweb import common as hgwebcommon
415 from .hgweb import common as hgwebcommon
330
416
331 def genversion2(gen, engine, engineopts):
417 def genversion2(gen, engine, engineopts):
332 # application/mercurial-0.2 always sends a payload header
418 # application/mercurial-0.2 always sends a payload header
333 # identifying the compression engine.
419 # identifying the compression engine.
334 name = engine.wireprotosupport().name
420 name = engine.wireprotosupport().name
335 assert 0 < len(name) < 256
421 assert 0 < len(name) < 256
336 yield struct.pack('B', len(name))
422 yield struct.pack('B', len(name))
337 yield name
423 yield name
338
424
339 for chunk in gen:
425 for chunk in gen:
340 yield chunk
426 yield chunk
341
427
342 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
428 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
343 if code == HTTP_OK:
429 if code == HTTP_OK:
344 res.status = '200 Script output follows'
430 res.status = '200 Script output follows'
345 else:
431 else:
346 res.status = hgwebcommon.statusmessage(code)
432 res.status = hgwebcommon.statusmessage(code)
347
433
348 res.headers['Content-Type'] = contenttype
434 res.headers['Content-Type'] = contenttype
349
435
350 if bodybytes is not None:
436 if bodybytes is not None:
351 res.setbodybytes(bodybytes)
437 res.setbodybytes(bodybytes)
352 if bodygen is not None:
438 if bodygen is not None:
353 res.setbodygen(bodygen)
439 res.setbodygen(bodygen)
354
440
355 if not wireproto.commands.commandavailable(cmd, proto):
441 if not wireproto.commands.commandavailable(cmd, proto):
356 setresponse(HTTP_OK, HGERRTYPE,
442 setresponse(HTTP_OK, HGERRTYPE,
357 _('requested wire protocol command is not available over '
443 _('requested wire protocol command is not available over '
358 'HTTP'))
444 'HTTP'))
359 return
445 return
360
446
361 proto.checkperm(wireproto.commands[cmd].permission)
447 proto.checkperm(wireproto.commands[cmd].permission)
362
448
363 rsp = wireproto.dispatch(repo, proto, cmd)
449 rsp = wireproto.dispatch(repo, proto, cmd)
364
450
365 if isinstance(rsp, bytes):
451 if isinstance(rsp, bytes):
366 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
452 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
367 elif isinstance(rsp, wireprototypes.bytesresponse):
453 elif isinstance(rsp, wireprototypes.bytesresponse):
368 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
454 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
369 elif isinstance(rsp, wireprototypes.streamreslegacy):
455 elif isinstance(rsp, wireprototypes.streamreslegacy):
370 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
456 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
371 elif isinstance(rsp, wireprototypes.streamres):
457 elif isinstance(rsp, wireprototypes.streamres):
372 gen = rsp.gen
458 gen = rsp.gen
373
459
374 # This code for compression should not be streamres specific. It
460 # This code for compression should not be streamres specific. It
375 # is here because we only compress streamres at the moment.
461 # is here because we only compress streamres at the moment.
376 mediatype, engine, engineopts = _httpresponsetype(
462 mediatype, engine, engineopts = _httpresponsetype(
377 repo.ui, req, rsp.prefer_uncompressed)
463 repo.ui, req, rsp.prefer_uncompressed)
378 gen = engine.compressstream(gen, engineopts)
464 gen = engine.compressstream(gen, engineopts)
379
465
380 if mediatype == HGTYPE2:
466 if mediatype == HGTYPE2:
381 gen = genversion2(gen, engine, engineopts)
467 gen = genversion2(gen, engine, engineopts)
382
468
383 setresponse(HTTP_OK, mediatype, bodygen=gen)
469 setresponse(HTTP_OK, mediatype, bodygen=gen)
384 elif isinstance(rsp, wireprototypes.pushres):
470 elif isinstance(rsp, wireprototypes.pushres):
385 rsp = '%d\n%s' % (rsp.res, rsp.output)
471 rsp = '%d\n%s' % (rsp.res, rsp.output)
386 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
472 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
387 elif isinstance(rsp, wireprototypes.pusherr):
473 elif isinstance(rsp, wireprototypes.pusherr):
388 rsp = '0\n%s\n' % rsp.res
474 rsp = '0\n%s\n' % rsp.res
389 res.drain = True
475 res.drain = True
390 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
476 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
391 elif isinstance(rsp, wireprototypes.ooberror):
477 elif isinstance(rsp, wireprototypes.ooberror):
392 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
478 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
393 else:
479 else:
394 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
480 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
395
481
396 def _sshv1respondbytes(fout, value):
482 def _sshv1respondbytes(fout, value):
397 """Send a bytes response for protocol version 1."""
483 """Send a bytes response for protocol version 1."""
398 fout.write('%d\n' % len(value))
484 fout.write('%d\n' % len(value))
399 fout.write(value)
485 fout.write(value)
400 fout.flush()
486 fout.flush()
401
487
402 def _sshv1respondstream(fout, source):
488 def _sshv1respondstream(fout, source):
403 write = fout.write
489 write = fout.write
404 for chunk in source.gen:
490 for chunk in source.gen:
405 write(chunk)
491 write(chunk)
406 fout.flush()
492 fout.flush()
407
493
408 def _sshv1respondooberror(fout, ferr, rsp):
494 def _sshv1respondooberror(fout, ferr, rsp):
409 ferr.write(b'%s\n-\n' % rsp)
495 ferr.write(b'%s\n-\n' % rsp)
410 ferr.flush()
496 ferr.flush()
411 fout.write(b'\n')
497 fout.write(b'\n')
412 fout.flush()
498 fout.flush()
413
499
414 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
500 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
415 """Handler for requests services via version 1 of SSH protocol."""
501 """Handler for requests services via version 1 of SSH protocol."""
416 def __init__(self, ui, fin, fout):
502 def __init__(self, ui, fin, fout):
417 self._ui = ui
503 self._ui = ui
418 self._fin = fin
504 self._fin = fin
419 self._fout = fout
505 self._fout = fout
420
506
421 @property
507 @property
422 def name(self):
508 def name(self):
423 return wireprototypes.SSHV1
509 return wireprototypes.SSHV1
424
510
425 def getargs(self, args):
511 def getargs(self, args):
426 data = {}
512 data = {}
427 keys = args.split()
513 keys = args.split()
428 for n in xrange(len(keys)):
514 for n in xrange(len(keys)):
429 argline = self._fin.readline()[:-1]
515 argline = self._fin.readline()[:-1]
430 arg, l = argline.split()
516 arg, l = argline.split()
431 if arg not in keys:
517 if arg not in keys:
432 raise error.Abort(_("unexpected parameter %r") % arg)
518 raise error.Abort(_("unexpected parameter %r") % arg)
433 if arg == '*':
519 if arg == '*':
434 star = {}
520 star = {}
435 for k in xrange(int(l)):
521 for k in xrange(int(l)):
436 argline = self._fin.readline()[:-1]
522 argline = self._fin.readline()[:-1]
437 arg, l = argline.split()
523 arg, l = argline.split()
438 val = self._fin.read(int(l))
524 val = self._fin.read(int(l))
439 star[arg] = val
525 star[arg] = val
440 data['*'] = star
526 data['*'] = star
441 else:
527 else:
442 val = self._fin.read(int(l))
528 val = self._fin.read(int(l))
443 data[arg] = val
529 data[arg] = val
444 return [data[k] for k in keys]
530 return [data[k] for k in keys]
445
531
446 def forwardpayload(self, fpout):
532 def forwardpayload(self, fpout):
447 # We initially send an empty response. This tells the client it is
533 # We initially send an empty response. This tells the client it is
448 # OK to start sending data. If a client sees any other response, it
534 # OK to start sending data. If a client sees any other response, it
449 # interprets it as an error.
535 # interprets it as an error.
450 _sshv1respondbytes(self._fout, b'')
536 _sshv1respondbytes(self._fout, b'')
451
537
452 # The file is in the form:
538 # The file is in the form:
453 #
539 #
454 # <chunk size>\n<chunk>
540 # <chunk size>\n<chunk>
455 # ...
541 # ...
456 # 0\n
542 # 0\n
457 count = int(self._fin.readline())
543 count = int(self._fin.readline())
458 while count:
544 while count:
459 fpout.write(self._fin.read(count))
545 fpout.write(self._fin.read(count))
460 count = int(self._fin.readline())
546 count = int(self._fin.readline())
461
547
462 @contextlib.contextmanager
548 @contextlib.contextmanager
463 def mayberedirectstdio(self):
549 def mayberedirectstdio(self):
464 yield None
550 yield None
465
551
466 def client(self):
552 def client(self):
467 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
553 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
468 return 'remote:ssh:' + client
554 return 'remote:ssh:' + client
469
555
470 def addcapabilities(self, repo, caps):
556 def addcapabilities(self, repo, caps):
471 return caps
557 return caps
472
558
473 def checkperm(self, perm):
559 def checkperm(self, perm):
474 pass
560 pass
475
561
476 class sshv2protocolhandler(sshv1protocolhandler):
562 class sshv2protocolhandler(sshv1protocolhandler):
477 """Protocol handler for version 2 of the SSH protocol."""
563 """Protocol handler for version 2 of the SSH protocol."""
478
564
479 @property
565 @property
480 def name(self):
566 def name(self):
481 return wireprototypes.SSHV2
567 return wireprototypes.SSHV2
482
568
483 def _runsshserver(ui, repo, fin, fout, ev):
569 def _runsshserver(ui, repo, fin, fout, ev):
484 # This function operates like a state machine of sorts. The following
570 # This function operates like a state machine of sorts. The following
485 # states are defined:
571 # states are defined:
486 #
572 #
487 # protov1-serving
573 # protov1-serving
488 # Server is in protocol version 1 serving mode. Commands arrive on
574 # Server is in protocol version 1 serving mode. Commands arrive on
489 # new lines. These commands are processed in this state, one command
575 # new lines. These commands are processed in this state, one command
490 # after the other.
576 # after the other.
491 #
577 #
492 # protov2-serving
578 # protov2-serving
493 # Server is in protocol version 2 serving mode.
579 # Server is in protocol version 2 serving mode.
494 #
580 #
495 # upgrade-initial
581 # upgrade-initial
496 # The server is going to process an upgrade request.
582 # The server is going to process an upgrade request.
497 #
583 #
498 # upgrade-v2-filter-legacy-handshake
584 # upgrade-v2-filter-legacy-handshake
499 # The protocol is being upgraded to version 2. The server is expecting
585 # The protocol is being upgraded to version 2. The server is expecting
500 # the legacy handshake from version 1.
586 # the legacy handshake from version 1.
501 #
587 #
502 # upgrade-v2-finish
588 # upgrade-v2-finish
503 # The upgrade to version 2 of the protocol is imminent.
589 # The upgrade to version 2 of the protocol is imminent.
504 #
590 #
505 # shutdown
591 # shutdown
506 # The server is shutting down, possibly in reaction to a client event.
592 # The server is shutting down, possibly in reaction to a client event.
507 #
593 #
508 # And here are their transitions:
594 # And here are their transitions:
509 #
595 #
510 # protov1-serving -> shutdown
596 # protov1-serving -> shutdown
511 # When server receives an empty request or encounters another
597 # When server receives an empty request or encounters another
512 # error.
598 # error.
513 #
599 #
514 # protov1-serving -> upgrade-initial
600 # protov1-serving -> upgrade-initial
515 # An upgrade request line was seen.
601 # An upgrade request line was seen.
516 #
602 #
517 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
603 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
518 # Upgrade to version 2 in progress. Server is expecting to
604 # Upgrade to version 2 in progress. Server is expecting to
519 # process a legacy handshake.
605 # process a legacy handshake.
520 #
606 #
521 # upgrade-v2-filter-legacy-handshake -> shutdown
607 # upgrade-v2-filter-legacy-handshake -> shutdown
522 # Client did not fulfill upgrade handshake requirements.
608 # Client did not fulfill upgrade handshake requirements.
523 #
609 #
524 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
610 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
525 # Client fulfilled version 2 upgrade requirements. Finishing that
611 # Client fulfilled version 2 upgrade requirements. Finishing that
526 # upgrade.
612 # upgrade.
527 #
613 #
528 # upgrade-v2-finish -> protov2-serving
614 # upgrade-v2-finish -> protov2-serving
529 # Protocol upgrade to version 2 complete. Server can now speak protocol
615 # Protocol upgrade to version 2 complete. Server can now speak protocol
530 # version 2.
616 # version 2.
531 #
617 #
532 # protov2-serving -> protov1-serving
618 # protov2-serving -> protov1-serving
533 # Ths happens by default since protocol version 2 is the same as
619 # Ths happens by default since protocol version 2 is the same as
534 # version 1 except for the handshake.
620 # version 1 except for the handshake.
535
621
536 state = 'protov1-serving'
622 state = 'protov1-serving'
537 proto = sshv1protocolhandler(ui, fin, fout)
623 proto = sshv1protocolhandler(ui, fin, fout)
538 protoswitched = False
624 protoswitched = False
539
625
540 while not ev.is_set():
626 while not ev.is_set():
541 if state == 'protov1-serving':
627 if state == 'protov1-serving':
542 # Commands are issued on new lines.
628 # Commands are issued on new lines.
543 request = fin.readline()[:-1]
629 request = fin.readline()[:-1]
544
630
545 # Empty lines signal to terminate the connection.
631 # Empty lines signal to terminate the connection.
546 if not request:
632 if not request:
547 state = 'shutdown'
633 state = 'shutdown'
548 continue
634 continue
549
635
550 # It looks like a protocol upgrade request. Transition state to
636 # It looks like a protocol upgrade request. Transition state to
551 # handle it.
637 # handle it.
552 if request.startswith(b'upgrade '):
638 if request.startswith(b'upgrade '):
553 if protoswitched:
639 if protoswitched:
554 _sshv1respondooberror(fout, ui.ferr,
640 _sshv1respondooberror(fout, ui.ferr,
555 b'cannot upgrade protocols multiple '
641 b'cannot upgrade protocols multiple '
556 b'times')
642 b'times')
557 state = 'shutdown'
643 state = 'shutdown'
558 continue
644 continue
559
645
560 state = 'upgrade-initial'
646 state = 'upgrade-initial'
561 continue
647 continue
562
648
563 available = wireproto.commands.commandavailable(request, proto)
649 available = wireproto.commands.commandavailable(request, proto)
564
650
565 # This command isn't available. Send an empty response and go
651 # This command isn't available. Send an empty response and go
566 # back to waiting for a new command.
652 # back to waiting for a new command.
567 if not available:
653 if not available:
568 _sshv1respondbytes(fout, b'')
654 _sshv1respondbytes(fout, b'')
569 continue
655 continue
570
656
571 rsp = wireproto.dispatch(repo, proto, request)
657 rsp = wireproto.dispatch(repo, proto, request)
572
658
573 if isinstance(rsp, bytes):
659 if isinstance(rsp, bytes):
574 _sshv1respondbytes(fout, rsp)
660 _sshv1respondbytes(fout, rsp)
575 elif isinstance(rsp, wireprototypes.bytesresponse):
661 elif isinstance(rsp, wireprototypes.bytesresponse):
576 _sshv1respondbytes(fout, rsp.data)
662 _sshv1respondbytes(fout, rsp.data)
577 elif isinstance(rsp, wireprototypes.streamres):
663 elif isinstance(rsp, wireprototypes.streamres):
578 _sshv1respondstream(fout, rsp)
664 _sshv1respondstream(fout, rsp)
579 elif isinstance(rsp, wireprototypes.streamreslegacy):
665 elif isinstance(rsp, wireprototypes.streamreslegacy):
580 _sshv1respondstream(fout, rsp)
666 _sshv1respondstream(fout, rsp)
581 elif isinstance(rsp, wireprototypes.pushres):
667 elif isinstance(rsp, wireprototypes.pushres):
582 _sshv1respondbytes(fout, b'')
668 _sshv1respondbytes(fout, b'')
583 _sshv1respondbytes(fout, b'%d' % rsp.res)
669 _sshv1respondbytes(fout, b'%d' % rsp.res)
584 elif isinstance(rsp, wireprototypes.pusherr):
670 elif isinstance(rsp, wireprototypes.pusherr):
585 _sshv1respondbytes(fout, rsp.res)
671 _sshv1respondbytes(fout, rsp.res)
586 elif isinstance(rsp, wireprototypes.ooberror):
672 elif isinstance(rsp, wireprototypes.ooberror):
587 _sshv1respondooberror(fout, ui.ferr, rsp.message)
673 _sshv1respondooberror(fout, ui.ferr, rsp.message)
588 else:
674 else:
589 raise error.ProgrammingError('unhandled response type from '
675 raise error.ProgrammingError('unhandled response type from '
590 'wire protocol command: %s' % rsp)
676 'wire protocol command: %s' % rsp)
591
677
592 # For now, protocol version 2 serving just goes back to version 1.
678 # For now, protocol version 2 serving just goes back to version 1.
593 elif state == 'protov2-serving':
679 elif state == 'protov2-serving':
594 state = 'protov1-serving'
680 state = 'protov1-serving'
595 continue
681 continue
596
682
597 elif state == 'upgrade-initial':
683 elif state == 'upgrade-initial':
598 # We should never transition into this state if we've switched
684 # We should never transition into this state if we've switched
599 # protocols.
685 # protocols.
600 assert not protoswitched
686 assert not protoswitched
601 assert proto.name == wireprototypes.SSHV1
687 assert proto.name == wireprototypes.SSHV1
602
688
603 # Expected: upgrade <token> <capabilities>
689 # Expected: upgrade <token> <capabilities>
604 # If we get something else, the request is malformed. It could be
690 # If we get something else, the request is malformed. It could be
605 # from a future client that has altered the upgrade line content.
691 # from a future client that has altered the upgrade line content.
606 # We treat this as an unknown command.
692 # We treat this as an unknown command.
607 try:
693 try:
608 token, caps = request.split(b' ')[1:]
694 token, caps = request.split(b' ')[1:]
609 except ValueError:
695 except ValueError:
610 _sshv1respondbytes(fout, b'')
696 _sshv1respondbytes(fout, b'')
611 state = 'protov1-serving'
697 state = 'protov1-serving'
612 continue
698 continue
613
699
614 # Send empty response if we don't support upgrading protocols.
700 # Send empty response if we don't support upgrading protocols.
615 if not ui.configbool('experimental', 'sshserver.support-v2'):
701 if not ui.configbool('experimental', 'sshserver.support-v2'):
616 _sshv1respondbytes(fout, b'')
702 _sshv1respondbytes(fout, b'')
617 state = 'protov1-serving'
703 state = 'protov1-serving'
618 continue
704 continue
619
705
620 try:
706 try:
621 caps = urlreq.parseqs(caps)
707 caps = urlreq.parseqs(caps)
622 except ValueError:
708 except ValueError:
623 _sshv1respondbytes(fout, b'')
709 _sshv1respondbytes(fout, b'')
624 state = 'protov1-serving'
710 state = 'protov1-serving'
625 continue
711 continue
626
712
627 # We don't see an upgrade request to protocol version 2. Ignore
713 # We don't see an upgrade request to protocol version 2. Ignore
628 # the upgrade request.
714 # the upgrade request.
629 wantedprotos = caps.get(b'proto', [b''])[0]
715 wantedprotos = caps.get(b'proto', [b''])[0]
630 if SSHV2 not in wantedprotos:
716 if SSHV2 not in wantedprotos:
631 _sshv1respondbytes(fout, b'')
717 _sshv1respondbytes(fout, b'')
632 state = 'protov1-serving'
718 state = 'protov1-serving'
633 continue
719 continue
634
720
635 # It looks like we can honor this upgrade request to protocol 2.
721 # It looks like we can honor this upgrade request to protocol 2.
636 # Filter the rest of the handshake protocol request lines.
722 # Filter the rest of the handshake protocol request lines.
637 state = 'upgrade-v2-filter-legacy-handshake'
723 state = 'upgrade-v2-filter-legacy-handshake'
638 continue
724 continue
639
725
640 elif state == 'upgrade-v2-filter-legacy-handshake':
726 elif state == 'upgrade-v2-filter-legacy-handshake':
641 # Client should have sent legacy handshake after an ``upgrade``
727 # Client should have sent legacy handshake after an ``upgrade``
642 # request. Expected lines:
728 # request. Expected lines:
643 #
729 #
644 # hello
730 # hello
645 # between
731 # between
646 # pairs 81
732 # pairs 81
647 # 0000...-0000...
733 # 0000...-0000...
648
734
649 ok = True
735 ok = True
650 for line in (b'hello', b'between', b'pairs 81'):
736 for line in (b'hello', b'between', b'pairs 81'):
651 request = fin.readline()[:-1]
737 request = fin.readline()[:-1]
652
738
653 if request != line:
739 if request != line:
654 _sshv1respondooberror(fout, ui.ferr,
740 _sshv1respondooberror(fout, ui.ferr,
655 b'malformed handshake protocol: '
741 b'malformed handshake protocol: '
656 b'missing %s' % line)
742 b'missing %s' % line)
657 ok = False
743 ok = False
658 state = 'shutdown'
744 state = 'shutdown'
659 break
745 break
660
746
661 if not ok:
747 if not ok:
662 continue
748 continue
663
749
664 request = fin.read(81)
750 request = fin.read(81)
665 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
751 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
666 _sshv1respondooberror(fout, ui.ferr,
752 _sshv1respondooberror(fout, ui.ferr,
667 b'malformed handshake protocol: '
753 b'malformed handshake protocol: '
668 b'missing between argument value')
754 b'missing between argument value')
669 state = 'shutdown'
755 state = 'shutdown'
670 continue
756 continue
671
757
672 state = 'upgrade-v2-finish'
758 state = 'upgrade-v2-finish'
673 continue
759 continue
674
760
675 elif state == 'upgrade-v2-finish':
761 elif state == 'upgrade-v2-finish':
676 # Send the upgrade response.
762 # Send the upgrade response.
677 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
763 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
678 servercaps = wireproto.capabilities(repo, proto)
764 servercaps = wireproto.capabilities(repo, proto)
679 rsp = b'capabilities: %s' % servercaps.data
765 rsp = b'capabilities: %s' % servercaps.data
680 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
766 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
681 fout.flush()
767 fout.flush()
682
768
683 proto = sshv2protocolhandler(ui, fin, fout)
769 proto = sshv2protocolhandler(ui, fin, fout)
684 protoswitched = True
770 protoswitched = True
685
771
686 state = 'protov2-serving'
772 state = 'protov2-serving'
687 continue
773 continue
688
774
689 elif state == 'shutdown':
775 elif state == 'shutdown':
690 break
776 break
691
777
692 else:
778 else:
693 raise error.ProgrammingError('unhandled ssh server state: %s' %
779 raise error.ProgrammingError('unhandled ssh server state: %s' %
694 state)
780 state)
695
781
696 class sshserver(object):
782 class sshserver(object):
697 def __init__(self, ui, repo, logfh=None):
783 def __init__(self, ui, repo, logfh=None):
698 self._ui = ui
784 self._ui = ui
699 self._repo = repo
785 self._repo = repo
700 self._fin = ui.fin
786 self._fin = ui.fin
701 self._fout = ui.fout
787 self._fout = ui.fout
702
788
703 # Log write I/O to stdout and stderr if configured.
789 # Log write I/O to stdout and stderr if configured.
704 if logfh:
790 if logfh:
705 self._fout = util.makeloggingfileobject(
791 self._fout = util.makeloggingfileobject(
706 logfh, self._fout, 'o', logdata=True)
792 logfh, self._fout, 'o', logdata=True)
707 ui.ferr = util.makeloggingfileobject(
793 ui.ferr = util.makeloggingfileobject(
708 logfh, ui.ferr, 'e', logdata=True)
794 logfh, ui.ferr, 'e', logdata=True)
709
795
710 hook.redirect(True)
796 hook.redirect(True)
711 ui.fout = repo.ui.fout = ui.ferr
797 ui.fout = repo.ui.fout = ui.ferr
712
798
713 # Prevent insertion/deletion of CRs
799 # Prevent insertion/deletion of CRs
714 util.setbinary(self._fin)
800 util.setbinary(self._fin)
715 util.setbinary(self._fout)
801 util.setbinary(self._fout)
716
802
717 def serve_forever(self):
803 def serve_forever(self):
718 self.serveuntil(threading.Event())
804 self.serveuntil(threading.Event())
719 sys.exit(0)
805 sys.exit(0)
720
806
721 def serveuntil(self, ev):
807 def serveuntil(self, ev):
722 """Serve until a threading.Event is set."""
808 """Serve until a threading.Event is set."""
723 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
809 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
@@ -1,65 +1,254 b''
1 $ HTTPV2=exp-http-v2-0001
2
1 $ send() {
3 $ send() {
2 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
4 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
3 > }
5 > }
4
6
7 $ cat > dummycommands.py << EOF
8 > from mercurial import wireprototypes, wireproto
9 > @wireproto.wireprotocommand('customreadonly', permission='pull')
10 > def customreadonly(repo, proto):
11 > return wireprototypes.bytesresponse(b'customreadonly bytes response')
12 > @wireproto.wireprotocommand('customreadwrite', permission='push')
13 > def customreadwrite(repo, proto):
14 > return wireprototypes.bytesresponse(b'customreadwrite bytes response')
15 > EOF
16
17 $ cat >> $HGRCPATH << EOF
18 > [extensions]
19 > dummycommands = $TESTTMP/dummycommands.py
20 > EOF
21
5 $ hg init server
22 $ hg init server
6 $ cat > server/.hg/hgrc << EOF
23 $ cat > server/.hg/hgrc << EOF
7 > [experimental]
24 > [experimental]
8 > web.apiserver = true
25 > web.apiserver = true
9 > EOF
26 > EOF
10 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
27 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
11 $ cat hg.pid > $DAEMON_PIDS
28 $ cat hg.pid > $DAEMON_PIDS
12
29
13 HTTP v2 protocol not enabled by default
30 HTTP v2 protocol not enabled by default
14
31
15 $ send << EOF
32 $ send << EOF
16 > httprequest GET api/exp-http-v2-0001
33 > httprequest GET api/$HTTPV2
17 > user-agent: test
34 > user-agent: test
18 > EOF
35 > EOF
19 using raw connection to peer
36 using raw connection to peer
20 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
37 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
21 s> Accept-Encoding: identity\r\n
38 s> Accept-Encoding: identity\r\n
22 s> user-agent: test\r\n
39 s> user-agent: test\r\n
23 s> host: $LOCALIP:$HGPORT\r\n (glob)
40 s> host: $LOCALIP:$HGPORT\r\n (glob)
24 s> \r\n
41 s> \r\n
25 s> makefile('rb', None)
42 s> makefile('rb', None)
26 s> HTTP/1.1 404 Not Found\r\n
43 s> HTTP/1.1 404 Not Found\r\n
27 s> Server: testing stub value\r\n
44 s> Server: testing stub value\r\n
28 s> Date: $HTTP_DATE$\r\n
45 s> Date: $HTTP_DATE$\r\n
29 s> Content-Type: text/plain\r\n
46 s> Content-Type: text/plain\r\n
30 s> Content-Length: 33\r\n
47 s> Content-Length: 33\r\n
31 s> \r\n
48 s> \r\n
32 s> API exp-http-v2-0001 not enabled\n
49 s> API exp-http-v2-0001 not enabled\n
33
50
34 Restart server with support for HTTP v2 API
51 Restart server with support for HTTP v2 API
35
52
36 $ killdaemons.py
53 $ killdaemons.py
37 $ cat > server/.hg/hgrc << EOF
54 $ cat > server/.hg/hgrc << EOF
38 > [experimental]
55 > [experimental]
39 > web.apiserver = true
56 > web.apiserver = true
40 > web.api.http-v2 = true
57 > web.api.http-v2 = true
41 > EOF
58 > EOF
42
59
43 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
60 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
44 $ cat hg.pid > $DAEMON_PIDS
61 $ cat hg.pid > $DAEMON_PIDS
45
62
46 Requests simply echo their path (for now)
63 Request to read-only command works out of the box
47
64
48 $ send << EOF
65 $ send << EOF
49 > httprequest GET api/exp-http-v2-0001/path1/path2
66 > httprequest GET api/$HTTPV2/ro/customreadonly
50 > user-agent: test
67 > user-agent: test
51 > EOF
68 > EOF
52 using raw connection to peer
69 using raw connection to peer
53 s> GET /api/exp-http-v2-0001/path1/path2 HTTP/1.1\r\n
70 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
54 s> Accept-Encoding: identity\r\n
71 s> Accept-Encoding: identity\r\n
55 s> user-agent: test\r\n
72 s> user-agent: test\r\n
56 s> host: $LOCALIP:$HGPORT\r\n (glob)
73 s> host: $LOCALIP:$HGPORT\r\n (glob)
57 s> \r\n
74 s> \r\n
58 s> makefile('rb', None)
75 s> makefile('rb', None)
59 s> HTTP/1.1 200 OK\r\n
76 s> HTTP/1.1 200 OK\r\n
60 s> Server: testing stub value\r\n
77 s> Server: testing stub value\r\n
61 s> Date: $HTTP_DATE$\r\n
78 s> Date: $HTTP_DATE$\r\n
62 s> Content-Type: text/plain\r\n
79 s> Content-Type: text/plain\r\n
63 s> Content-Length: 12\r\n
80 s> Content-Length: 18\r\n
81 s> \r\n
82 s> ro/customreadonly\n
83
84 Request to unknown command yields 404
85
86 $ send << EOF
87 > httprequest GET api/$HTTPV2/ro/badcommand
88 > user-agent: test
89 > EOF
90 using raw connection to peer
91 s> GET /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
92 s> Accept-Encoding: identity\r\n
93 s> user-agent: test\r\n
94 s> host: $LOCALIP:$HGPORT\r\n (glob)
95 s> \r\n
96 s> makefile('rb', None)
97 s> HTTP/1.1 404 Not Found\r\n
98 s> Server: testing stub value\r\n
99 s> Date: $HTTP_DATE$\r\n
100 s> Content-Type: text/plain\r\n
101 s> Content-Length: 42\r\n
102 s> \r\n
103 s> unknown wire protocol command: badcommand\n
104
105 Request to read-write command fails because server is read-only by default
106
107 GET to read-write request not allowed
108
109 $ send << EOF
110 > httprequest GET api/$HTTPV2/rw/customreadonly
111 > user-agent: test
112 > EOF
113 using raw connection to peer
114 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
115 s> Accept-Encoding: identity\r\n
116 s> user-agent: test\r\n
117 s> host: $LOCALIP:$HGPORT\r\n (glob)
118 s> \r\n
119 s> makefile('rb', None)
120 s> HTTP/1.1 405 push requires POST request\r\n
121 s> Server: testing stub value\r\n
122 s> Date: $HTTP_DATE$\r\n
123 s> Content-Length: 17\r\n
124 s> \r\n
125 s> permission denied
126
127 Even for unknown commands
128
129 $ send << EOF
130 > httprequest GET api/$HTTPV2/rw/badcommand
131 > user-agent: test
132 > EOF
133 using raw connection to peer
134 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
135 s> Accept-Encoding: identity\r\n
136 s> user-agent: test\r\n
137 s> host: $LOCALIP:$HGPORT\r\n (glob)
138 s> \r\n
139 s> makefile('rb', None)
140 s> HTTP/1.1 405 push requires POST request\r\n
141 s> Server: testing stub value\r\n
142 s> Date: $HTTP_DATE$\r\n
143 s> Content-Length: 17\r\n
144 s> \r\n
145 s> permission denied
146
147 SSL required by default
148
149 $ send << EOF
150 > httprequest POST api/$HTTPV2/rw/customreadonly
151 > user-agent: test
152 > EOF
153 using raw connection to peer
154 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
155 s> Accept-Encoding: identity\r\n
156 s> user-agent: test\r\n
157 s> host: $LOCALIP:$HGPORT\r\n (glob)
158 s> \r\n
159 s> makefile('rb', None)
160 s> HTTP/1.1 403 ssl required\r\n
161 s> Server: testing stub value\r\n
162 s> Date: $HTTP_DATE$\r\n
163 s> Content-Length: 17\r\n
64 s> \r\n
164 s> \r\n
65 s> path1/path2\n
165 s> permission denied
166
167 Restart server to allow non-ssl read-write operations
168
169 $ killdaemons.py
170 $ cat > server/.hg/hgrc << EOF
171 > [experimental]
172 > web.apiserver = true
173 > web.api.http-v2 = true
174 > [web]
175 > push_ssl = false
176 > EOF
177
178 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
179 $ cat hg.pid > $DAEMON_PIDS
180
181 Server insists on POST for read-write commands
182
183 $ send << EOF
184 > httprequest GET api/$HTTPV2/rw/customreadonly
185 > user-agent: test
186 > EOF
187 using raw connection to peer
188 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
189 s> Accept-Encoding: identity\r\n
190 s> user-agent: test\r\n
191 s> host: $LOCALIP:$HGPORT\r\n (glob)
192 s> \r\n
193 s> makefile('rb', None)
194 s> HTTP/1.1 405 push requires POST request\r\n
195 s> Server: testing stub value\r\n
196 s> Date: $HTTP_DATE$\r\n
197 s> Content-Length: 17\r\n
198 s> \r\n
199 s> permission denied
200
201 $ killdaemons.py
202 $ cat > server/.hg/hgrc << EOF
203 > [experimental]
204 > web.apiserver = true
205 > web.api.http-v2 = true
206 > [web]
207 > push_ssl = false
208 > allow-push = *
209 > EOF
210
211 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
212 $ cat hg.pid > $DAEMON_PIDS
213
214 Authorized request for valid read-write command works
215
216 $ send << EOF
217 > httprequest POST api/$HTTPV2/rw/customreadonly
218 > user-agent: test
219 > EOF
220 using raw connection to peer
221 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
222 s> Accept-Encoding: identity\r\n
223 s> user-agent: test\r\n
224 s> host: $LOCALIP:$HGPORT\r\n (glob)
225 s> \r\n
226 s> makefile('rb', None)
227 s> HTTP/1.1 200 OK\r\n
228 s> Server: testing stub value\r\n
229 s> Date: $HTTP_DATE$\r\n
230 s> Content-Type: text/plain\r\n
231 s> Content-Length: 18\r\n
232 s> \r\n
233 s> rw/customreadonly\n
234
235 Authorized request for unknown command is rejected
236
237 $ send << EOF
238 > httprequest POST api/$HTTPV2/rw/badcommand
239 > user-agent: test
240 > EOF
241 using raw connection to peer
242 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
243 s> Accept-Encoding: identity\r\n
244 s> user-agent: test\r\n
245 s> host: $LOCALIP:$HGPORT\r\n (glob)
246 s> \r\n
247 s> makefile('rb', None)
248 s> HTTP/1.1 404 Not Found\r\n
249 s> Server: testing stub value\r\n
250 s> Date: $HTTP_DATE$\r\n
251 s> Content-Type: text/plain\r\n
252 s> Content-Length: 42\r\n
253 s> \r\n
254 s> unknown wire protocol command: badcommand\n
General Comments 0
You need to be logged in to leave comments. Login now