##// END OF EJS Templates
wireproto: add request IDs to frames...
Gregory Szorc -
r37075:2ec1fb9d default
parent child Browse files
Show More
@@ -1,3060 +1,3062
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 wireprotoframing,
81 wireprotoframing,
82 wireprotoserver,
82 wireprotoserver,
83 )
83 )
84 from .utils import dateutil
84 from .utils import dateutil
85
85
86 release = lockmod.release
86 release = lockmod.release
87
87
88 command = registrar.command()
88 command = registrar.command()
89
89
90 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
90 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
91 def debugancestor(ui, repo, *args):
91 def debugancestor(ui, repo, *args):
92 """find the ancestor revision of two revisions in a given index"""
92 """find the ancestor revision of two revisions in a given index"""
93 if len(args) == 3:
93 if len(args) == 3:
94 index, rev1, rev2 = args
94 index, rev1, rev2 = args
95 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
95 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
96 lookup = r.lookup
96 lookup = r.lookup
97 elif len(args) == 2:
97 elif len(args) == 2:
98 if not repo:
98 if not repo:
99 raise error.Abort(_('there is no Mercurial repository here '
99 raise error.Abort(_('there is no Mercurial repository here '
100 '(.hg not found)'))
100 '(.hg not found)'))
101 rev1, rev2 = args
101 rev1, rev2 = args
102 r = repo.changelog
102 r = repo.changelog
103 lookup = repo.lookup
103 lookup = repo.lookup
104 else:
104 else:
105 raise error.Abort(_('either two or three arguments required'))
105 raise error.Abort(_('either two or three arguments required'))
106 a = r.ancestor(lookup(rev1), lookup(rev2))
106 a = r.ancestor(lookup(rev1), lookup(rev2))
107 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
107 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
108
108
109 @command('debugapplystreamclonebundle', [], 'FILE')
109 @command('debugapplystreamclonebundle', [], 'FILE')
110 def debugapplystreamclonebundle(ui, repo, fname):
110 def debugapplystreamclonebundle(ui, repo, fname):
111 """apply a stream clone bundle file"""
111 """apply a stream clone bundle file"""
112 f = hg.openpath(ui, fname)
112 f = hg.openpath(ui, fname)
113 gen = exchange.readbundle(ui, f, fname)
113 gen = exchange.readbundle(ui, f, fname)
114 gen.apply(repo)
114 gen.apply(repo)
115
115
116 @command('debugbuilddag',
116 @command('debugbuilddag',
117 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
117 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
118 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
118 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
119 ('n', 'new-file', None, _('add new file at each rev'))],
119 ('n', 'new-file', None, _('add new file at each rev'))],
120 _('[OPTION]... [TEXT]'))
120 _('[OPTION]... [TEXT]'))
121 def debugbuilddag(ui, repo, text=None,
121 def debugbuilddag(ui, repo, text=None,
122 mergeable_file=False,
122 mergeable_file=False,
123 overwritten_file=False,
123 overwritten_file=False,
124 new_file=False):
124 new_file=False):
125 """builds a repo with a given DAG from scratch in the current empty repo
125 """builds a repo with a given DAG from scratch in the current empty repo
126
126
127 The description of the DAG is read from stdin if not given on the
127 The description of the DAG is read from stdin if not given on the
128 command line.
128 command line.
129
129
130 Elements:
130 Elements:
131
131
132 - "+n" is a linear run of n nodes based on the current default parent
132 - "+n" is a linear run of n nodes based on the current default parent
133 - "." is a single node based on the current default parent
133 - "." is a single node based on the current default parent
134 - "$" resets the default parent to null (implied at the start);
134 - "$" resets the default parent to null (implied at the start);
135 otherwise the default parent is always the last node created
135 otherwise the default parent is always the last node created
136 - "<p" sets the default parent to the backref p
136 - "<p" sets the default parent to the backref p
137 - "*p" is a fork at parent p, which is a backref
137 - "*p" is a fork at parent p, which is a backref
138 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
138 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
139 - "/p2" is a merge of the preceding node and p2
139 - "/p2" is a merge of the preceding node and p2
140 - ":tag" defines a local tag for the preceding node
140 - ":tag" defines a local tag for the preceding node
141 - "@branch" sets the named branch for subsequent nodes
141 - "@branch" sets the named branch for subsequent nodes
142 - "#...\\n" is a comment up to the end of the line
142 - "#...\\n" is a comment up to the end of the line
143
143
144 Whitespace between the above elements is ignored.
144 Whitespace between the above elements is ignored.
145
145
146 A backref is either
146 A backref is either
147
147
148 - a number n, which references the node curr-n, where curr is the current
148 - a number n, which references the node curr-n, where curr is the current
149 node, or
149 node, or
150 - the name of a local tag you placed earlier using ":tag", or
150 - the name of a local tag you placed earlier using ":tag", or
151 - empty to denote the default parent.
151 - empty to denote the default parent.
152
152
153 All string valued-elements are either strictly alphanumeric, or must
153 All string valued-elements are either strictly alphanumeric, or must
154 be enclosed in double quotes ("..."), with "\\" as escape character.
154 be enclosed in double quotes ("..."), with "\\" as escape character.
155 """
155 """
156
156
157 if text is None:
157 if text is None:
158 ui.status(_("reading DAG from stdin\n"))
158 ui.status(_("reading DAG from stdin\n"))
159 text = ui.fin.read()
159 text = ui.fin.read()
160
160
161 cl = repo.changelog
161 cl = repo.changelog
162 if len(cl) > 0:
162 if len(cl) > 0:
163 raise error.Abort(_('repository is not empty'))
163 raise error.Abort(_('repository is not empty'))
164
164
165 # determine number of revs in DAG
165 # determine number of revs in DAG
166 total = 0
166 total = 0
167 for type, data in dagparser.parsedag(text):
167 for type, data in dagparser.parsedag(text):
168 if type == 'n':
168 if type == 'n':
169 total += 1
169 total += 1
170
170
171 if mergeable_file:
171 if mergeable_file:
172 linesperrev = 2
172 linesperrev = 2
173 # make a file with k lines per rev
173 # make a file with k lines per rev
174 initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)]
174 initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)]
175 initialmergedlines.append("")
175 initialmergedlines.append("")
176
176
177 tags = []
177 tags = []
178
178
179 wlock = lock = tr = None
179 wlock = lock = tr = None
180 try:
180 try:
181 wlock = repo.wlock()
181 wlock = repo.wlock()
182 lock = repo.lock()
182 lock = repo.lock()
183 tr = repo.transaction("builddag")
183 tr = repo.transaction("builddag")
184
184
185 at = -1
185 at = -1
186 atbranch = 'default'
186 atbranch = 'default'
187 nodeids = []
187 nodeids = []
188 id = 0
188 id = 0
189 ui.progress(_('building'), id, unit=_('revisions'), total=total)
189 ui.progress(_('building'), id, unit=_('revisions'), total=total)
190 for type, data in dagparser.parsedag(text):
190 for type, data in dagparser.parsedag(text):
191 if type == 'n':
191 if type == 'n':
192 ui.note(('node %s\n' % pycompat.bytestr(data)))
192 ui.note(('node %s\n' % pycompat.bytestr(data)))
193 id, ps = data
193 id, ps = data
194
194
195 files = []
195 files = []
196 filecontent = {}
196 filecontent = {}
197
197
198 p2 = None
198 p2 = None
199 if mergeable_file:
199 if mergeable_file:
200 fn = "mf"
200 fn = "mf"
201 p1 = repo[ps[0]]
201 p1 = repo[ps[0]]
202 if len(ps) > 1:
202 if len(ps) > 1:
203 p2 = repo[ps[1]]
203 p2 = repo[ps[1]]
204 pa = p1.ancestor(p2)
204 pa = p1.ancestor(p2)
205 base, local, other = [x[fn].data() for x in (pa, p1,
205 base, local, other = [x[fn].data() for x in (pa, p1,
206 p2)]
206 p2)]
207 m3 = simplemerge.Merge3Text(base, local, other)
207 m3 = simplemerge.Merge3Text(base, local, other)
208 ml = [l.strip() for l in m3.merge_lines()]
208 ml = [l.strip() for l in m3.merge_lines()]
209 ml.append("")
209 ml.append("")
210 elif at > 0:
210 elif at > 0:
211 ml = p1[fn].data().split("\n")
211 ml = p1[fn].data().split("\n")
212 else:
212 else:
213 ml = initialmergedlines
213 ml = initialmergedlines
214 ml[id * linesperrev] += " r%i" % id
214 ml[id * linesperrev] += " r%i" % id
215 mergedtext = "\n".join(ml)
215 mergedtext = "\n".join(ml)
216 files.append(fn)
216 files.append(fn)
217 filecontent[fn] = mergedtext
217 filecontent[fn] = mergedtext
218
218
219 if overwritten_file:
219 if overwritten_file:
220 fn = "of"
220 fn = "of"
221 files.append(fn)
221 files.append(fn)
222 filecontent[fn] = "r%i\n" % id
222 filecontent[fn] = "r%i\n" % id
223
223
224 if new_file:
224 if new_file:
225 fn = "nf%i" % id
225 fn = "nf%i" % id
226 files.append(fn)
226 files.append(fn)
227 filecontent[fn] = "r%i\n" % id
227 filecontent[fn] = "r%i\n" % id
228 if len(ps) > 1:
228 if len(ps) > 1:
229 if not p2:
229 if not p2:
230 p2 = repo[ps[1]]
230 p2 = repo[ps[1]]
231 for fn in p2:
231 for fn in p2:
232 if fn.startswith("nf"):
232 if fn.startswith("nf"):
233 files.append(fn)
233 files.append(fn)
234 filecontent[fn] = p2[fn].data()
234 filecontent[fn] = p2[fn].data()
235
235
236 def fctxfn(repo, cx, path):
236 def fctxfn(repo, cx, path):
237 if path in filecontent:
237 if path in filecontent:
238 return context.memfilectx(repo, cx, path,
238 return context.memfilectx(repo, cx, path,
239 filecontent[path])
239 filecontent[path])
240 return None
240 return None
241
241
242 if len(ps) == 0 or ps[0] < 0:
242 if len(ps) == 0 or ps[0] < 0:
243 pars = [None, None]
243 pars = [None, None]
244 elif len(ps) == 1:
244 elif len(ps) == 1:
245 pars = [nodeids[ps[0]], None]
245 pars = [nodeids[ps[0]], None]
246 else:
246 else:
247 pars = [nodeids[p] for p in ps]
247 pars = [nodeids[p] for p in ps]
248 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
248 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
249 date=(id, 0),
249 date=(id, 0),
250 user="debugbuilddag",
250 user="debugbuilddag",
251 extra={'branch': atbranch})
251 extra={'branch': atbranch})
252 nodeid = repo.commitctx(cx)
252 nodeid = repo.commitctx(cx)
253 nodeids.append(nodeid)
253 nodeids.append(nodeid)
254 at = id
254 at = id
255 elif type == 'l':
255 elif type == 'l':
256 id, name = data
256 id, name = data
257 ui.note(('tag %s\n' % name))
257 ui.note(('tag %s\n' % name))
258 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
258 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
259 elif type == 'a':
259 elif type == 'a':
260 ui.note(('branch %s\n' % data))
260 ui.note(('branch %s\n' % data))
261 atbranch = data
261 atbranch = data
262 ui.progress(_('building'), id, unit=_('revisions'), total=total)
262 ui.progress(_('building'), id, unit=_('revisions'), total=total)
263 tr.close()
263 tr.close()
264
264
265 if tags:
265 if tags:
266 repo.vfs.write("localtags", "".join(tags))
266 repo.vfs.write("localtags", "".join(tags))
267 finally:
267 finally:
268 ui.progress(_('building'), None)
268 ui.progress(_('building'), None)
269 release(tr, lock, wlock)
269 release(tr, lock, wlock)
270
270
271 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
271 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
272 indent_string = ' ' * indent
272 indent_string = ' ' * indent
273 if all:
273 if all:
274 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
274 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
275 % indent_string)
275 % indent_string)
276
276
277 def showchunks(named):
277 def showchunks(named):
278 ui.write("\n%s%s\n" % (indent_string, named))
278 ui.write("\n%s%s\n" % (indent_string, named))
279 for deltadata in gen.deltaiter():
279 for deltadata in gen.deltaiter():
280 node, p1, p2, cs, deltabase, delta, flags = deltadata
280 node, p1, p2, cs, deltabase, delta, flags = deltadata
281 ui.write("%s%s %s %s %s %s %d\n" %
281 ui.write("%s%s %s %s %s %s %d\n" %
282 (indent_string, hex(node), hex(p1), hex(p2),
282 (indent_string, hex(node), hex(p1), hex(p2),
283 hex(cs), hex(deltabase), len(delta)))
283 hex(cs), hex(deltabase), len(delta)))
284
284
285 chunkdata = gen.changelogheader()
285 chunkdata = gen.changelogheader()
286 showchunks("changelog")
286 showchunks("changelog")
287 chunkdata = gen.manifestheader()
287 chunkdata = gen.manifestheader()
288 showchunks("manifest")
288 showchunks("manifest")
289 for chunkdata in iter(gen.filelogheader, {}):
289 for chunkdata in iter(gen.filelogheader, {}):
290 fname = chunkdata['filename']
290 fname = chunkdata['filename']
291 showchunks(fname)
291 showchunks(fname)
292 else:
292 else:
293 if isinstance(gen, bundle2.unbundle20):
293 if isinstance(gen, bundle2.unbundle20):
294 raise error.Abort(_('use debugbundle2 for this file'))
294 raise error.Abort(_('use debugbundle2 for this file'))
295 chunkdata = gen.changelogheader()
295 chunkdata = gen.changelogheader()
296 for deltadata in gen.deltaiter():
296 for deltadata in gen.deltaiter():
297 node, p1, p2, cs, deltabase, delta, flags = deltadata
297 node, p1, p2, cs, deltabase, delta, flags = deltadata
298 ui.write("%s%s\n" % (indent_string, hex(node)))
298 ui.write("%s%s\n" % (indent_string, hex(node)))
299
299
300 def _debugobsmarkers(ui, part, indent=0, **opts):
300 def _debugobsmarkers(ui, part, indent=0, **opts):
301 """display version and markers contained in 'data'"""
301 """display version and markers contained in 'data'"""
302 opts = pycompat.byteskwargs(opts)
302 opts = pycompat.byteskwargs(opts)
303 data = part.read()
303 data = part.read()
304 indent_string = ' ' * indent
304 indent_string = ' ' * indent
305 try:
305 try:
306 version, markers = obsolete._readmarkers(data)
306 version, markers = obsolete._readmarkers(data)
307 except error.UnknownVersion as exc:
307 except error.UnknownVersion as exc:
308 msg = "%sunsupported version: %s (%d bytes)\n"
308 msg = "%sunsupported version: %s (%d bytes)\n"
309 msg %= indent_string, exc.version, len(data)
309 msg %= indent_string, exc.version, len(data)
310 ui.write(msg)
310 ui.write(msg)
311 else:
311 else:
312 msg = "%sversion: %d (%d bytes)\n"
312 msg = "%sversion: %d (%d bytes)\n"
313 msg %= indent_string, version, len(data)
313 msg %= indent_string, version, len(data)
314 ui.write(msg)
314 ui.write(msg)
315 fm = ui.formatter('debugobsolete', opts)
315 fm = ui.formatter('debugobsolete', opts)
316 for rawmarker in sorted(markers):
316 for rawmarker in sorted(markers):
317 m = obsutil.marker(None, rawmarker)
317 m = obsutil.marker(None, rawmarker)
318 fm.startitem()
318 fm.startitem()
319 fm.plain(indent_string)
319 fm.plain(indent_string)
320 cmdutil.showmarker(fm, m)
320 cmdutil.showmarker(fm, m)
321 fm.end()
321 fm.end()
322
322
323 def _debugphaseheads(ui, data, indent=0):
323 def _debugphaseheads(ui, data, indent=0):
324 """display version and markers contained in 'data'"""
324 """display version and markers contained in 'data'"""
325 indent_string = ' ' * indent
325 indent_string = ' ' * indent
326 headsbyphase = phases.binarydecode(data)
326 headsbyphase = phases.binarydecode(data)
327 for phase in phases.allphases:
327 for phase in phases.allphases:
328 for head in headsbyphase[phase]:
328 for head in headsbyphase[phase]:
329 ui.write(indent_string)
329 ui.write(indent_string)
330 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
330 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
331
331
332 def _quasirepr(thing):
332 def _quasirepr(thing):
333 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
333 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
334 return '{%s}' % (
334 return '{%s}' % (
335 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
335 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
336 return pycompat.bytestr(repr(thing))
336 return pycompat.bytestr(repr(thing))
337
337
338 def _debugbundle2(ui, gen, all=None, **opts):
338 def _debugbundle2(ui, gen, all=None, **opts):
339 """lists the contents of a bundle2"""
339 """lists the contents of a bundle2"""
340 if not isinstance(gen, bundle2.unbundle20):
340 if not isinstance(gen, bundle2.unbundle20):
341 raise error.Abort(_('not a bundle2 file'))
341 raise error.Abort(_('not a bundle2 file'))
342 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
342 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
343 parttypes = opts.get(r'part_type', [])
343 parttypes = opts.get(r'part_type', [])
344 for part in gen.iterparts():
344 for part in gen.iterparts():
345 if parttypes and part.type not in parttypes:
345 if parttypes and part.type not in parttypes:
346 continue
346 continue
347 ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
347 ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
348 if part.type == 'changegroup':
348 if part.type == 'changegroup':
349 version = part.params.get('version', '01')
349 version = part.params.get('version', '01')
350 cg = changegroup.getunbundler(version, part, 'UN')
350 cg = changegroup.getunbundler(version, part, 'UN')
351 if not ui.quiet:
351 if not ui.quiet:
352 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
352 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
353 if part.type == 'obsmarkers':
353 if part.type == 'obsmarkers':
354 if not ui.quiet:
354 if not ui.quiet:
355 _debugobsmarkers(ui, part, indent=4, **opts)
355 _debugobsmarkers(ui, part, indent=4, **opts)
356 if part.type == 'phase-heads':
356 if part.type == 'phase-heads':
357 if not ui.quiet:
357 if not ui.quiet:
358 _debugphaseheads(ui, part, indent=4)
358 _debugphaseheads(ui, part, indent=4)
359
359
360 @command('debugbundle',
360 @command('debugbundle',
361 [('a', 'all', None, _('show all details')),
361 [('a', 'all', None, _('show all details')),
362 ('', 'part-type', [], _('show only the named part type')),
362 ('', 'part-type', [], _('show only the named part type')),
363 ('', 'spec', None, _('print the bundlespec of the bundle'))],
363 ('', 'spec', None, _('print the bundlespec of the bundle'))],
364 _('FILE'),
364 _('FILE'),
365 norepo=True)
365 norepo=True)
366 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
366 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
367 """lists the contents of a bundle"""
367 """lists the contents of a bundle"""
368 with hg.openpath(ui, bundlepath) as f:
368 with hg.openpath(ui, bundlepath) as f:
369 if spec:
369 if spec:
370 spec = exchange.getbundlespec(ui, f)
370 spec = exchange.getbundlespec(ui, f)
371 ui.write('%s\n' % spec)
371 ui.write('%s\n' % spec)
372 return
372 return
373
373
374 gen = exchange.readbundle(ui, f, bundlepath)
374 gen = exchange.readbundle(ui, f, bundlepath)
375 if isinstance(gen, bundle2.unbundle20):
375 if isinstance(gen, bundle2.unbundle20):
376 return _debugbundle2(ui, gen, all=all, **opts)
376 return _debugbundle2(ui, gen, all=all, **opts)
377 _debugchangegroup(ui, gen, all=all, **opts)
377 _debugchangegroup(ui, gen, all=all, **opts)
378
378
379 @command('debugcapabilities',
379 @command('debugcapabilities',
380 [], _('PATH'),
380 [], _('PATH'),
381 norepo=True)
381 norepo=True)
382 def debugcapabilities(ui, path, **opts):
382 def debugcapabilities(ui, path, **opts):
383 """lists the capabilities of a remote peer"""
383 """lists the capabilities of a remote peer"""
384 opts = pycompat.byteskwargs(opts)
384 opts = pycompat.byteskwargs(opts)
385 peer = hg.peer(ui, opts, path)
385 peer = hg.peer(ui, opts, path)
386 caps = peer.capabilities()
386 caps = peer.capabilities()
387 ui.write(('Main capabilities:\n'))
387 ui.write(('Main capabilities:\n'))
388 for c in sorted(caps):
388 for c in sorted(caps):
389 ui.write((' %s\n') % c)
389 ui.write((' %s\n') % c)
390 b2caps = bundle2.bundle2caps(peer)
390 b2caps = bundle2.bundle2caps(peer)
391 if b2caps:
391 if b2caps:
392 ui.write(('Bundle2 capabilities:\n'))
392 ui.write(('Bundle2 capabilities:\n'))
393 for key, values in sorted(b2caps.iteritems()):
393 for key, values in sorted(b2caps.iteritems()):
394 ui.write((' %s\n') % key)
394 ui.write((' %s\n') % key)
395 for v in values:
395 for v in values:
396 ui.write((' %s\n') % v)
396 ui.write((' %s\n') % v)
397
397
398 @command('debugcheckstate', [], '')
398 @command('debugcheckstate', [], '')
399 def debugcheckstate(ui, repo):
399 def debugcheckstate(ui, repo):
400 """validate the correctness of the current dirstate"""
400 """validate the correctness of the current dirstate"""
401 parent1, parent2 = repo.dirstate.parents()
401 parent1, parent2 = repo.dirstate.parents()
402 m1 = repo[parent1].manifest()
402 m1 = repo[parent1].manifest()
403 m2 = repo[parent2].manifest()
403 m2 = repo[parent2].manifest()
404 errors = 0
404 errors = 0
405 for f in repo.dirstate:
405 for f in repo.dirstate:
406 state = repo.dirstate[f]
406 state = repo.dirstate[f]
407 if state in "nr" and f not in m1:
407 if state in "nr" and f not in m1:
408 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
408 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
409 errors += 1
409 errors += 1
410 if state in "a" and f in m1:
410 if state in "a" and f in m1:
411 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
411 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
412 errors += 1
412 errors += 1
413 if state in "m" and f not in m1 and f not in m2:
413 if state in "m" and f not in m1 and f not in m2:
414 ui.warn(_("%s in state %s, but not in either manifest\n") %
414 ui.warn(_("%s in state %s, but not in either manifest\n") %
415 (f, state))
415 (f, state))
416 errors += 1
416 errors += 1
417 for f in m1:
417 for f in m1:
418 state = repo.dirstate[f]
418 state = repo.dirstate[f]
419 if state not in "nrm":
419 if state not in "nrm":
420 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
420 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
421 errors += 1
421 errors += 1
422 if errors:
422 if errors:
423 error = _(".hg/dirstate inconsistent with current parent's manifest")
423 error = _(".hg/dirstate inconsistent with current parent's manifest")
424 raise error.Abort(error)
424 raise error.Abort(error)
425
425
426 @command('debugcolor',
426 @command('debugcolor',
427 [('', 'style', None, _('show all configured styles'))],
427 [('', 'style', None, _('show all configured styles'))],
428 'hg debugcolor')
428 'hg debugcolor')
429 def debugcolor(ui, repo, **opts):
429 def debugcolor(ui, repo, **opts):
430 """show available color, effects or style"""
430 """show available color, effects or style"""
431 ui.write(('color mode: %s\n') % ui._colormode)
431 ui.write(('color mode: %s\n') % ui._colormode)
432 if opts.get(r'style'):
432 if opts.get(r'style'):
433 return _debugdisplaystyle(ui)
433 return _debugdisplaystyle(ui)
434 else:
434 else:
435 return _debugdisplaycolor(ui)
435 return _debugdisplaycolor(ui)
436
436
437 def _debugdisplaycolor(ui):
437 def _debugdisplaycolor(ui):
438 ui = ui.copy()
438 ui = ui.copy()
439 ui._styles.clear()
439 ui._styles.clear()
440 for effect in color._activeeffects(ui).keys():
440 for effect in color._activeeffects(ui).keys():
441 ui._styles[effect] = effect
441 ui._styles[effect] = effect
442 if ui._terminfoparams:
442 if ui._terminfoparams:
443 for k, v in ui.configitems('color'):
443 for k, v in ui.configitems('color'):
444 if k.startswith('color.'):
444 if k.startswith('color.'):
445 ui._styles[k] = k[6:]
445 ui._styles[k] = k[6:]
446 elif k.startswith('terminfo.'):
446 elif k.startswith('terminfo.'):
447 ui._styles[k] = k[9:]
447 ui._styles[k] = k[9:]
448 ui.write(_('available colors:\n'))
448 ui.write(_('available colors:\n'))
449 # sort label with a '_' after the other to group '_background' entry.
449 # sort label with a '_' after the other to group '_background' entry.
450 items = sorted(ui._styles.items(),
450 items = sorted(ui._styles.items(),
451 key=lambda i: ('_' in i[0], i[0], i[1]))
451 key=lambda i: ('_' in i[0], i[0], i[1]))
452 for colorname, label in items:
452 for colorname, label in items:
453 ui.write(('%s\n') % colorname, label=label)
453 ui.write(('%s\n') % colorname, label=label)
454
454
455 def _debugdisplaystyle(ui):
455 def _debugdisplaystyle(ui):
456 ui.write(_('available style:\n'))
456 ui.write(_('available style:\n'))
457 width = max(len(s) for s in ui._styles)
457 width = max(len(s) for s in ui._styles)
458 for label, effects in sorted(ui._styles.items()):
458 for label, effects in sorted(ui._styles.items()):
459 ui.write('%s' % label, label=label)
459 ui.write('%s' % label, label=label)
460 if effects:
460 if effects:
461 # 50
461 # 50
462 ui.write(': ')
462 ui.write(': ')
463 ui.write(' ' * (max(0, width - len(label))))
463 ui.write(' ' * (max(0, width - len(label))))
464 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
464 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
465 ui.write('\n')
465 ui.write('\n')
466
466
467 @command('debugcreatestreamclonebundle', [], 'FILE')
467 @command('debugcreatestreamclonebundle', [], 'FILE')
468 def debugcreatestreamclonebundle(ui, repo, fname):
468 def debugcreatestreamclonebundle(ui, repo, fname):
469 """create a stream clone bundle file
469 """create a stream clone bundle file
470
470
471 Stream bundles are special bundles that are essentially archives of
471 Stream bundles are special bundles that are essentially archives of
472 revlog files. They are commonly used for cloning very quickly.
472 revlog files. They are commonly used for cloning very quickly.
473 """
473 """
474 # TODO we may want to turn this into an abort when this functionality
474 # TODO we may want to turn this into an abort when this functionality
475 # is moved into `hg bundle`.
475 # is moved into `hg bundle`.
476 if phases.hassecret(repo):
476 if phases.hassecret(repo):
477 ui.warn(_('(warning: stream clone bundle will contain secret '
477 ui.warn(_('(warning: stream clone bundle will contain secret '
478 'revisions)\n'))
478 'revisions)\n'))
479
479
480 requirements, gen = streamclone.generatebundlev1(repo)
480 requirements, gen = streamclone.generatebundlev1(repo)
481 changegroup.writechunks(ui, gen, fname)
481 changegroup.writechunks(ui, gen, fname)
482
482
483 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
483 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
484
484
485 @command('debugdag',
485 @command('debugdag',
486 [('t', 'tags', None, _('use tags as labels')),
486 [('t', 'tags', None, _('use tags as labels')),
487 ('b', 'branches', None, _('annotate with branch names')),
487 ('b', 'branches', None, _('annotate with branch names')),
488 ('', 'dots', None, _('use dots for runs')),
488 ('', 'dots', None, _('use dots for runs')),
489 ('s', 'spaces', None, _('separate elements by spaces'))],
489 ('s', 'spaces', None, _('separate elements by spaces'))],
490 _('[OPTION]... [FILE [REV]...]'),
490 _('[OPTION]... [FILE [REV]...]'),
491 optionalrepo=True)
491 optionalrepo=True)
492 def debugdag(ui, repo, file_=None, *revs, **opts):
492 def debugdag(ui, repo, file_=None, *revs, **opts):
493 """format the changelog or an index DAG as a concise textual description
493 """format the changelog or an index DAG as a concise textual description
494
494
495 If you pass a revlog index, the revlog's DAG is emitted. If you list
495 If you pass a revlog index, the revlog's DAG is emitted. If you list
496 revision numbers, they get labeled in the output as rN.
496 revision numbers, they get labeled in the output as rN.
497
497
498 Otherwise, the changelog DAG of the current repo is emitted.
498 Otherwise, the changelog DAG of the current repo is emitted.
499 """
499 """
500 spaces = opts.get(r'spaces')
500 spaces = opts.get(r'spaces')
501 dots = opts.get(r'dots')
501 dots = opts.get(r'dots')
502 if file_:
502 if file_:
503 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
503 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
504 file_)
504 file_)
505 revs = set((int(r) for r in revs))
505 revs = set((int(r) for r in revs))
506 def events():
506 def events():
507 for r in rlog:
507 for r in rlog:
508 yield 'n', (r, list(p for p in rlog.parentrevs(r)
508 yield 'n', (r, list(p for p in rlog.parentrevs(r)
509 if p != -1))
509 if p != -1))
510 if r in revs:
510 if r in revs:
511 yield 'l', (r, "r%i" % r)
511 yield 'l', (r, "r%i" % r)
512 elif repo:
512 elif repo:
513 cl = repo.changelog
513 cl = repo.changelog
514 tags = opts.get(r'tags')
514 tags = opts.get(r'tags')
515 branches = opts.get(r'branches')
515 branches = opts.get(r'branches')
516 if tags:
516 if tags:
517 labels = {}
517 labels = {}
518 for l, n in repo.tags().items():
518 for l, n in repo.tags().items():
519 labels.setdefault(cl.rev(n), []).append(l)
519 labels.setdefault(cl.rev(n), []).append(l)
520 def events():
520 def events():
521 b = "default"
521 b = "default"
522 for r in cl:
522 for r in cl:
523 if branches:
523 if branches:
524 newb = cl.read(cl.node(r))[5]['branch']
524 newb = cl.read(cl.node(r))[5]['branch']
525 if newb != b:
525 if newb != b:
526 yield 'a', newb
526 yield 'a', newb
527 b = newb
527 b = newb
528 yield 'n', (r, list(p for p in cl.parentrevs(r)
528 yield 'n', (r, list(p for p in cl.parentrevs(r)
529 if p != -1))
529 if p != -1))
530 if tags:
530 if tags:
531 ls = labels.get(r)
531 ls = labels.get(r)
532 if ls:
532 if ls:
533 for l in ls:
533 for l in ls:
534 yield 'l', (r, l)
534 yield 'l', (r, l)
535 else:
535 else:
536 raise error.Abort(_('need repo for changelog dag'))
536 raise error.Abort(_('need repo for changelog dag'))
537
537
538 for line in dagparser.dagtextlines(events(),
538 for line in dagparser.dagtextlines(events(),
539 addspaces=spaces,
539 addspaces=spaces,
540 wraplabels=True,
540 wraplabels=True,
541 wrapannotations=True,
541 wrapannotations=True,
542 wrapnonlinear=dots,
542 wrapnonlinear=dots,
543 usedots=dots,
543 usedots=dots,
544 maxlinewidth=70):
544 maxlinewidth=70):
545 ui.write(line)
545 ui.write(line)
546 ui.write("\n")
546 ui.write("\n")
547
547
548 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
548 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
549 def debugdata(ui, repo, file_, rev=None, **opts):
549 def debugdata(ui, repo, file_, rev=None, **opts):
550 """dump the contents of a data file revision"""
550 """dump the contents of a data file revision"""
551 opts = pycompat.byteskwargs(opts)
551 opts = pycompat.byteskwargs(opts)
552 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
552 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
553 if rev is not None:
553 if rev is not None:
554 raise error.CommandError('debugdata', _('invalid arguments'))
554 raise error.CommandError('debugdata', _('invalid arguments'))
555 file_, rev = None, file_
555 file_, rev = None, file_
556 elif rev is None:
556 elif rev is None:
557 raise error.CommandError('debugdata', _('invalid arguments'))
557 raise error.CommandError('debugdata', _('invalid arguments'))
558 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
558 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
559 try:
559 try:
560 ui.write(r.revision(r.lookup(rev), raw=True))
560 ui.write(r.revision(r.lookup(rev), raw=True))
561 except KeyError:
561 except KeyError:
562 raise error.Abort(_('invalid revision identifier %s') % rev)
562 raise error.Abort(_('invalid revision identifier %s') % rev)
563
563
564 @command('debugdate',
564 @command('debugdate',
565 [('e', 'extended', None, _('try extended date formats'))],
565 [('e', 'extended', None, _('try extended date formats'))],
566 _('[-e] DATE [RANGE]'),
566 _('[-e] DATE [RANGE]'),
567 norepo=True, optionalrepo=True)
567 norepo=True, optionalrepo=True)
568 def debugdate(ui, date, range=None, **opts):
568 def debugdate(ui, date, range=None, **opts):
569 """parse and display a date"""
569 """parse and display a date"""
570 if opts[r"extended"]:
570 if opts[r"extended"]:
571 d = dateutil.parsedate(date, util.extendeddateformats)
571 d = dateutil.parsedate(date, util.extendeddateformats)
572 else:
572 else:
573 d = dateutil.parsedate(date)
573 d = dateutil.parsedate(date)
574 ui.write(("internal: %d %d\n") % d)
574 ui.write(("internal: %d %d\n") % d)
575 ui.write(("standard: %s\n") % dateutil.datestr(d))
575 ui.write(("standard: %s\n") % dateutil.datestr(d))
576 if range:
576 if range:
577 m = dateutil.matchdate(range)
577 m = dateutil.matchdate(range)
578 ui.write(("match: %s\n") % m(d[0]))
578 ui.write(("match: %s\n") % m(d[0]))
579
579
580 @command('debugdeltachain',
580 @command('debugdeltachain',
581 cmdutil.debugrevlogopts + cmdutil.formatteropts,
581 cmdutil.debugrevlogopts + cmdutil.formatteropts,
582 _('-c|-m|FILE'),
582 _('-c|-m|FILE'),
583 optionalrepo=True)
583 optionalrepo=True)
584 def debugdeltachain(ui, repo, file_=None, **opts):
584 def debugdeltachain(ui, repo, file_=None, **opts):
585 """dump information about delta chains in a revlog
585 """dump information about delta chains in a revlog
586
586
587 Output can be templatized. Available template keywords are:
587 Output can be templatized. Available template keywords are:
588
588
589 :``rev``: revision number
589 :``rev``: revision number
590 :``chainid``: delta chain identifier (numbered by unique base)
590 :``chainid``: delta chain identifier (numbered by unique base)
591 :``chainlen``: delta chain length to this revision
591 :``chainlen``: delta chain length to this revision
592 :``prevrev``: previous revision in delta chain
592 :``prevrev``: previous revision in delta chain
593 :``deltatype``: role of delta / how it was computed
593 :``deltatype``: role of delta / how it was computed
594 :``compsize``: compressed size of revision
594 :``compsize``: compressed size of revision
595 :``uncompsize``: uncompressed size of revision
595 :``uncompsize``: uncompressed size of revision
596 :``chainsize``: total size of compressed revisions in chain
596 :``chainsize``: total size of compressed revisions in chain
597 :``chainratio``: total chain size divided by uncompressed revision size
597 :``chainratio``: total chain size divided by uncompressed revision size
598 (new delta chains typically start at ratio 2.00)
598 (new delta chains typically start at ratio 2.00)
599 :``lindist``: linear distance from base revision in delta chain to end
599 :``lindist``: linear distance from base revision in delta chain to end
600 of this revision
600 of this revision
601 :``extradist``: total size of revisions not part of this delta chain from
601 :``extradist``: total size of revisions not part of this delta chain from
602 base of delta chain to end of this revision; a measurement
602 base of delta chain to end of this revision; a measurement
603 of how much extra data we need to read/seek across to read
603 of how much extra data we need to read/seek across to read
604 the delta chain for this revision
604 the delta chain for this revision
605 :``extraratio``: extradist divided by chainsize; another representation of
605 :``extraratio``: extradist divided by chainsize; another representation of
606 how much unrelated data is needed to load this delta chain
606 how much unrelated data is needed to load this delta chain
607
607
608 If the repository is configured to use the sparse read, additional keywords
608 If the repository is configured to use the sparse read, additional keywords
609 are available:
609 are available:
610
610
611 :``readsize``: total size of data read from the disk for a revision
611 :``readsize``: total size of data read from the disk for a revision
612 (sum of the sizes of all the blocks)
612 (sum of the sizes of all the blocks)
613 :``largestblock``: size of the largest block of data read from the disk
613 :``largestblock``: size of the largest block of data read from the disk
614 :``readdensity``: density of useful bytes in the data read from the disk
614 :``readdensity``: density of useful bytes in the data read from the disk
615 :``srchunks``: in how many data hunks the whole revision would be read
615 :``srchunks``: in how many data hunks the whole revision would be read
616
616
617 The sparse read can be enabled with experimental.sparse-read = True
617 The sparse read can be enabled with experimental.sparse-read = True
618 """
618 """
619 opts = pycompat.byteskwargs(opts)
619 opts = pycompat.byteskwargs(opts)
620 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
620 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
621 index = r.index
621 index = r.index
622 generaldelta = r.version & revlog.FLAG_GENERALDELTA
622 generaldelta = r.version & revlog.FLAG_GENERALDELTA
623 withsparseread = getattr(r, '_withsparseread', False)
623 withsparseread = getattr(r, '_withsparseread', False)
624
624
625 def revinfo(rev):
625 def revinfo(rev):
626 e = index[rev]
626 e = index[rev]
627 compsize = e[1]
627 compsize = e[1]
628 uncompsize = e[2]
628 uncompsize = e[2]
629 chainsize = 0
629 chainsize = 0
630
630
631 if generaldelta:
631 if generaldelta:
632 if e[3] == e[5]:
632 if e[3] == e[5]:
633 deltatype = 'p1'
633 deltatype = 'p1'
634 elif e[3] == e[6]:
634 elif e[3] == e[6]:
635 deltatype = 'p2'
635 deltatype = 'p2'
636 elif e[3] == rev - 1:
636 elif e[3] == rev - 1:
637 deltatype = 'prev'
637 deltatype = 'prev'
638 elif e[3] == rev:
638 elif e[3] == rev:
639 deltatype = 'base'
639 deltatype = 'base'
640 else:
640 else:
641 deltatype = 'other'
641 deltatype = 'other'
642 else:
642 else:
643 if e[3] == rev:
643 if e[3] == rev:
644 deltatype = 'base'
644 deltatype = 'base'
645 else:
645 else:
646 deltatype = 'prev'
646 deltatype = 'prev'
647
647
648 chain = r._deltachain(rev)[0]
648 chain = r._deltachain(rev)[0]
649 for iterrev in chain:
649 for iterrev in chain:
650 e = index[iterrev]
650 e = index[iterrev]
651 chainsize += e[1]
651 chainsize += e[1]
652
652
653 return compsize, uncompsize, deltatype, chain, chainsize
653 return compsize, uncompsize, deltatype, chain, chainsize
654
654
655 fm = ui.formatter('debugdeltachain', opts)
655 fm = ui.formatter('debugdeltachain', opts)
656
656
657 fm.plain(' rev chain# chainlen prev delta '
657 fm.plain(' rev chain# chainlen prev delta '
658 'size rawsize chainsize ratio lindist extradist '
658 'size rawsize chainsize ratio lindist extradist '
659 'extraratio')
659 'extraratio')
660 if withsparseread:
660 if withsparseread:
661 fm.plain(' readsize largestblk rddensity srchunks')
661 fm.plain(' readsize largestblk rddensity srchunks')
662 fm.plain('\n')
662 fm.plain('\n')
663
663
664 chainbases = {}
664 chainbases = {}
665 for rev in r:
665 for rev in r:
666 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
666 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
667 chainbase = chain[0]
667 chainbase = chain[0]
668 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
668 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
669 start = r.start
669 start = r.start
670 length = r.length
670 length = r.length
671 basestart = start(chainbase)
671 basestart = start(chainbase)
672 revstart = start(rev)
672 revstart = start(rev)
673 lineardist = revstart + comp - basestart
673 lineardist = revstart + comp - basestart
674 extradist = lineardist - chainsize
674 extradist = lineardist - chainsize
675 try:
675 try:
676 prevrev = chain[-2]
676 prevrev = chain[-2]
677 except IndexError:
677 except IndexError:
678 prevrev = -1
678 prevrev = -1
679
679
680 chainratio = float(chainsize) / float(uncomp)
680 chainratio = float(chainsize) / float(uncomp)
681 extraratio = float(extradist) / float(chainsize)
681 extraratio = float(extradist) / float(chainsize)
682
682
683 fm.startitem()
683 fm.startitem()
684 fm.write('rev chainid chainlen prevrev deltatype compsize '
684 fm.write('rev chainid chainlen prevrev deltatype compsize '
685 'uncompsize chainsize chainratio lindist extradist '
685 'uncompsize chainsize chainratio lindist extradist '
686 'extraratio',
686 'extraratio',
687 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
687 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
688 rev, chainid, len(chain), prevrev, deltatype, comp,
688 rev, chainid, len(chain), prevrev, deltatype, comp,
689 uncomp, chainsize, chainratio, lineardist, extradist,
689 uncomp, chainsize, chainratio, lineardist, extradist,
690 extraratio,
690 extraratio,
691 rev=rev, chainid=chainid, chainlen=len(chain),
691 rev=rev, chainid=chainid, chainlen=len(chain),
692 prevrev=prevrev, deltatype=deltatype, compsize=comp,
692 prevrev=prevrev, deltatype=deltatype, compsize=comp,
693 uncompsize=uncomp, chainsize=chainsize,
693 uncompsize=uncomp, chainsize=chainsize,
694 chainratio=chainratio, lindist=lineardist,
694 chainratio=chainratio, lindist=lineardist,
695 extradist=extradist, extraratio=extraratio)
695 extradist=extradist, extraratio=extraratio)
696 if withsparseread:
696 if withsparseread:
697 readsize = 0
697 readsize = 0
698 largestblock = 0
698 largestblock = 0
699 srchunks = 0
699 srchunks = 0
700
700
701 for revschunk in revlog._slicechunk(r, chain):
701 for revschunk in revlog._slicechunk(r, chain):
702 srchunks += 1
702 srchunks += 1
703 blkend = start(revschunk[-1]) + length(revschunk[-1])
703 blkend = start(revschunk[-1]) + length(revschunk[-1])
704 blksize = blkend - start(revschunk[0])
704 blksize = blkend - start(revschunk[0])
705
705
706 readsize += blksize
706 readsize += blksize
707 if largestblock < blksize:
707 if largestblock < blksize:
708 largestblock = blksize
708 largestblock = blksize
709
709
710 readdensity = float(chainsize) / float(readsize)
710 readdensity = float(chainsize) / float(readsize)
711
711
712 fm.write('readsize largestblock readdensity srchunks',
712 fm.write('readsize largestblock readdensity srchunks',
713 ' %10d %10d %9.5f %8d',
713 ' %10d %10d %9.5f %8d',
714 readsize, largestblock, readdensity, srchunks,
714 readsize, largestblock, readdensity, srchunks,
715 readsize=readsize, largestblock=largestblock,
715 readsize=readsize, largestblock=largestblock,
716 readdensity=readdensity, srchunks=srchunks)
716 readdensity=readdensity, srchunks=srchunks)
717
717
718 fm.plain('\n')
718 fm.plain('\n')
719
719
720 fm.end()
720 fm.end()
721
721
722 @command('debugdirstate|debugstate',
722 @command('debugdirstate|debugstate',
723 [('', 'nodates', None, _('do not display the saved mtime')),
723 [('', 'nodates', None, _('do not display the saved mtime')),
724 ('', 'datesort', None, _('sort by saved mtime'))],
724 ('', 'datesort', None, _('sort by saved mtime'))],
725 _('[OPTION]...'))
725 _('[OPTION]...'))
726 def debugstate(ui, repo, **opts):
726 def debugstate(ui, repo, **opts):
727 """show the contents of the current dirstate"""
727 """show the contents of the current dirstate"""
728
728
729 nodates = opts.get(r'nodates')
729 nodates = opts.get(r'nodates')
730 datesort = opts.get(r'datesort')
730 datesort = opts.get(r'datesort')
731
731
732 timestr = ""
732 timestr = ""
733 if datesort:
733 if datesort:
734 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
734 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
735 else:
735 else:
736 keyfunc = None # sort by filename
736 keyfunc = None # sort by filename
737 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
737 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
738 if ent[3] == -1:
738 if ent[3] == -1:
739 timestr = 'unset '
739 timestr = 'unset '
740 elif nodates:
740 elif nodates:
741 timestr = 'set '
741 timestr = 'set '
742 else:
742 else:
743 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
743 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
744 time.localtime(ent[3]))
744 time.localtime(ent[3]))
745 timestr = encoding.strtolocal(timestr)
745 timestr = encoding.strtolocal(timestr)
746 if ent[1] & 0o20000:
746 if ent[1] & 0o20000:
747 mode = 'lnk'
747 mode = 'lnk'
748 else:
748 else:
749 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
749 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
750 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
750 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
751 for f in repo.dirstate.copies():
751 for f in repo.dirstate.copies():
752 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
752 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
753
753
754 @command('debugdiscovery',
754 @command('debugdiscovery',
755 [('', 'old', None, _('use old-style discovery')),
755 [('', 'old', None, _('use old-style discovery')),
756 ('', 'nonheads', None,
756 ('', 'nonheads', None,
757 _('use old-style discovery with non-heads included')),
757 _('use old-style discovery with non-heads included')),
758 ('', 'rev', [], 'restrict discovery to this set of revs'),
758 ('', 'rev', [], 'restrict discovery to this set of revs'),
759 ] + cmdutil.remoteopts,
759 ] + cmdutil.remoteopts,
760 _('[--rev REV] [OTHER]'))
760 _('[--rev REV] [OTHER]'))
761 def debugdiscovery(ui, repo, remoteurl="default", **opts):
761 def debugdiscovery(ui, repo, remoteurl="default", **opts):
762 """runs the changeset discovery protocol in isolation"""
762 """runs the changeset discovery protocol in isolation"""
763 opts = pycompat.byteskwargs(opts)
763 opts = pycompat.byteskwargs(opts)
764 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
764 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
765 remote = hg.peer(repo, opts, remoteurl)
765 remote = hg.peer(repo, opts, remoteurl)
766 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
766 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
767
767
768 # make sure tests are repeatable
768 # make sure tests are repeatable
769 random.seed(12323)
769 random.seed(12323)
770
770
771 def doit(pushedrevs, remoteheads, remote=remote):
771 def doit(pushedrevs, remoteheads, remote=remote):
772 if opts.get('old'):
772 if opts.get('old'):
773 if not util.safehasattr(remote, 'branches'):
773 if not util.safehasattr(remote, 'branches'):
774 # enable in-client legacy support
774 # enable in-client legacy support
775 remote = localrepo.locallegacypeer(remote.local())
775 remote = localrepo.locallegacypeer(remote.local())
776 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
776 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
777 force=True)
777 force=True)
778 common = set(common)
778 common = set(common)
779 if not opts.get('nonheads'):
779 if not opts.get('nonheads'):
780 ui.write(("unpruned common: %s\n") %
780 ui.write(("unpruned common: %s\n") %
781 " ".join(sorted(short(n) for n in common)))
781 " ".join(sorted(short(n) for n in common)))
782 dag = dagutil.revlogdag(repo.changelog)
782 dag = dagutil.revlogdag(repo.changelog)
783 all = dag.ancestorset(dag.internalizeall(common))
783 all = dag.ancestorset(dag.internalizeall(common))
784 common = dag.externalizeall(dag.headsetofconnecteds(all))
784 common = dag.externalizeall(dag.headsetofconnecteds(all))
785 else:
785 else:
786 nodes = None
786 nodes = None
787 if pushedrevs:
787 if pushedrevs:
788 revs = scmutil.revrange(repo, pushedrevs)
788 revs = scmutil.revrange(repo, pushedrevs)
789 nodes = [repo[r].node() for r in revs]
789 nodes = [repo[r].node() for r in revs]
790 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
790 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
791 ancestorsof=nodes)
791 ancestorsof=nodes)
792 common = set(common)
792 common = set(common)
793 rheads = set(hds)
793 rheads = set(hds)
794 lheads = set(repo.heads())
794 lheads = set(repo.heads())
795 ui.write(("common heads: %s\n") %
795 ui.write(("common heads: %s\n") %
796 " ".join(sorted(short(n) for n in common)))
796 " ".join(sorted(short(n) for n in common)))
797 if lheads <= common:
797 if lheads <= common:
798 ui.write(("local is subset\n"))
798 ui.write(("local is subset\n"))
799 elif rheads <= common:
799 elif rheads <= common:
800 ui.write(("remote is subset\n"))
800 ui.write(("remote is subset\n"))
801
801
802 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
802 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
803 localrevs = opts['rev']
803 localrevs = opts['rev']
804 doit(localrevs, remoterevs)
804 doit(localrevs, remoterevs)
805
805
806 _chunksize = 4 << 10
806 _chunksize = 4 << 10
807
807
808 @command('debugdownload',
808 @command('debugdownload',
809 [
809 [
810 ('o', 'output', '', _('path')),
810 ('o', 'output', '', _('path')),
811 ],
811 ],
812 optionalrepo=True)
812 optionalrepo=True)
813 def debugdownload(ui, repo, url, output=None, **opts):
813 def debugdownload(ui, repo, url, output=None, **opts):
814 """download a resource using Mercurial logic and config
814 """download a resource using Mercurial logic and config
815 """
815 """
816 fh = urlmod.open(ui, url, output)
816 fh = urlmod.open(ui, url, output)
817
817
818 dest = ui
818 dest = ui
819 if output:
819 if output:
820 dest = open(output, "wb", _chunksize)
820 dest = open(output, "wb", _chunksize)
821 try:
821 try:
822 data = fh.read(_chunksize)
822 data = fh.read(_chunksize)
823 while data:
823 while data:
824 dest.write(data)
824 dest.write(data)
825 data = fh.read(_chunksize)
825 data = fh.read(_chunksize)
826 finally:
826 finally:
827 if output:
827 if output:
828 dest.close()
828 dest.close()
829
829
830 @command('debugextensions', cmdutil.formatteropts, [], norepo=True)
830 @command('debugextensions', cmdutil.formatteropts, [], norepo=True)
831 def debugextensions(ui, **opts):
831 def debugextensions(ui, **opts):
832 '''show information about active extensions'''
832 '''show information about active extensions'''
833 opts = pycompat.byteskwargs(opts)
833 opts = pycompat.byteskwargs(opts)
834 exts = extensions.extensions(ui)
834 exts = extensions.extensions(ui)
835 hgver = util.version()
835 hgver = util.version()
836 fm = ui.formatter('debugextensions', opts)
836 fm = ui.formatter('debugextensions', opts)
837 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
837 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
838 isinternal = extensions.ismoduleinternal(extmod)
838 isinternal = extensions.ismoduleinternal(extmod)
839 extsource = pycompat.fsencode(extmod.__file__)
839 extsource = pycompat.fsencode(extmod.__file__)
840 if isinternal:
840 if isinternal:
841 exttestedwith = [] # never expose magic string to users
841 exttestedwith = [] # never expose magic string to users
842 else:
842 else:
843 exttestedwith = getattr(extmod, 'testedwith', '').split()
843 exttestedwith = getattr(extmod, 'testedwith', '').split()
844 extbuglink = getattr(extmod, 'buglink', None)
844 extbuglink = getattr(extmod, 'buglink', None)
845
845
846 fm.startitem()
846 fm.startitem()
847
847
848 if ui.quiet or ui.verbose:
848 if ui.quiet or ui.verbose:
849 fm.write('name', '%s\n', extname)
849 fm.write('name', '%s\n', extname)
850 else:
850 else:
851 fm.write('name', '%s', extname)
851 fm.write('name', '%s', extname)
852 if isinternal or hgver in exttestedwith:
852 if isinternal or hgver in exttestedwith:
853 fm.plain('\n')
853 fm.plain('\n')
854 elif not exttestedwith:
854 elif not exttestedwith:
855 fm.plain(_(' (untested!)\n'))
855 fm.plain(_(' (untested!)\n'))
856 else:
856 else:
857 lasttestedversion = exttestedwith[-1]
857 lasttestedversion = exttestedwith[-1]
858 fm.plain(' (%s!)\n' % lasttestedversion)
858 fm.plain(' (%s!)\n' % lasttestedversion)
859
859
860 fm.condwrite(ui.verbose and extsource, 'source',
860 fm.condwrite(ui.verbose and extsource, 'source',
861 _(' location: %s\n'), extsource or "")
861 _(' location: %s\n'), extsource or "")
862
862
863 if ui.verbose:
863 if ui.verbose:
864 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
864 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
865 fm.data(bundled=isinternal)
865 fm.data(bundled=isinternal)
866
866
867 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
867 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
868 _(' tested with: %s\n'),
868 _(' tested with: %s\n'),
869 fm.formatlist(exttestedwith, name='ver'))
869 fm.formatlist(exttestedwith, name='ver'))
870
870
871 fm.condwrite(ui.verbose and extbuglink, 'buglink',
871 fm.condwrite(ui.verbose and extbuglink, 'buglink',
872 _(' bug reporting: %s\n'), extbuglink or "")
872 _(' bug reporting: %s\n'), extbuglink or "")
873
873
874 fm.end()
874 fm.end()
875
875
876 @command('debugfileset',
876 @command('debugfileset',
877 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
877 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
878 _('[-r REV] FILESPEC'))
878 _('[-r REV] FILESPEC'))
879 def debugfileset(ui, repo, expr, **opts):
879 def debugfileset(ui, repo, expr, **opts):
880 '''parse and apply a fileset specification'''
880 '''parse and apply a fileset specification'''
881 ctx = scmutil.revsingle(repo, opts.get(r'rev'), None)
881 ctx = scmutil.revsingle(repo, opts.get(r'rev'), None)
882 if ui.verbose:
882 if ui.verbose:
883 tree = fileset.parse(expr)
883 tree = fileset.parse(expr)
884 ui.note(fileset.prettyformat(tree), "\n")
884 ui.note(fileset.prettyformat(tree), "\n")
885
885
886 for f in ctx.getfileset(expr):
886 for f in ctx.getfileset(expr):
887 ui.write("%s\n" % f)
887 ui.write("%s\n" % f)
888
888
889 @command('debugformat',
889 @command('debugformat',
890 [] + cmdutil.formatteropts,
890 [] + cmdutil.formatteropts,
891 _(''))
891 _(''))
892 def debugformat(ui, repo, **opts):
892 def debugformat(ui, repo, **opts):
893 """display format information about the current repository
893 """display format information about the current repository
894
894
895 Use --verbose to get extra information about current config value and
895 Use --verbose to get extra information about current config value and
896 Mercurial default."""
896 Mercurial default."""
897 opts = pycompat.byteskwargs(opts)
897 opts = pycompat.byteskwargs(opts)
898 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
898 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
899 maxvariantlength = max(len('format-variant'), maxvariantlength)
899 maxvariantlength = max(len('format-variant'), maxvariantlength)
900
900
901 def makeformatname(name):
901 def makeformatname(name):
902 return '%s:' + (' ' * (maxvariantlength - len(name)))
902 return '%s:' + (' ' * (maxvariantlength - len(name)))
903
903
904 fm = ui.formatter('debugformat', opts)
904 fm = ui.formatter('debugformat', opts)
905 if fm.isplain():
905 if fm.isplain():
906 def formatvalue(value):
906 def formatvalue(value):
907 if util.safehasattr(value, 'startswith'):
907 if util.safehasattr(value, 'startswith'):
908 return value
908 return value
909 if value:
909 if value:
910 return 'yes'
910 return 'yes'
911 else:
911 else:
912 return 'no'
912 return 'no'
913 else:
913 else:
914 formatvalue = pycompat.identity
914 formatvalue = pycompat.identity
915
915
916 fm.plain('format-variant')
916 fm.plain('format-variant')
917 fm.plain(' ' * (maxvariantlength - len('format-variant')))
917 fm.plain(' ' * (maxvariantlength - len('format-variant')))
918 fm.plain(' repo')
918 fm.plain(' repo')
919 if ui.verbose:
919 if ui.verbose:
920 fm.plain(' config default')
920 fm.plain(' config default')
921 fm.plain('\n')
921 fm.plain('\n')
922 for fv in upgrade.allformatvariant:
922 for fv in upgrade.allformatvariant:
923 fm.startitem()
923 fm.startitem()
924 repovalue = fv.fromrepo(repo)
924 repovalue = fv.fromrepo(repo)
925 configvalue = fv.fromconfig(repo)
925 configvalue = fv.fromconfig(repo)
926
926
927 if repovalue != configvalue:
927 if repovalue != configvalue:
928 namelabel = 'formatvariant.name.mismatchconfig'
928 namelabel = 'formatvariant.name.mismatchconfig'
929 repolabel = 'formatvariant.repo.mismatchconfig'
929 repolabel = 'formatvariant.repo.mismatchconfig'
930 elif repovalue != fv.default:
930 elif repovalue != fv.default:
931 namelabel = 'formatvariant.name.mismatchdefault'
931 namelabel = 'formatvariant.name.mismatchdefault'
932 repolabel = 'formatvariant.repo.mismatchdefault'
932 repolabel = 'formatvariant.repo.mismatchdefault'
933 else:
933 else:
934 namelabel = 'formatvariant.name.uptodate'
934 namelabel = 'formatvariant.name.uptodate'
935 repolabel = 'formatvariant.repo.uptodate'
935 repolabel = 'formatvariant.repo.uptodate'
936
936
937 fm.write('name', makeformatname(fv.name), fv.name,
937 fm.write('name', makeformatname(fv.name), fv.name,
938 label=namelabel)
938 label=namelabel)
939 fm.write('repo', ' %3s', formatvalue(repovalue),
939 fm.write('repo', ' %3s', formatvalue(repovalue),
940 label=repolabel)
940 label=repolabel)
941 if fv.default != configvalue:
941 if fv.default != configvalue:
942 configlabel = 'formatvariant.config.special'
942 configlabel = 'formatvariant.config.special'
943 else:
943 else:
944 configlabel = 'formatvariant.config.default'
944 configlabel = 'formatvariant.config.default'
945 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
945 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
946 label=configlabel)
946 label=configlabel)
947 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
947 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
948 label='formatvariant.default')
948 label='formatvariant.default')
949 fm.plain('\n')
949 fm.plain('\n')
950 fm.end()
950 fm.end()
951
951
952 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
952 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
953 def debugfsinfo(ui, path="."):
953 def debugfsinfo(ui, path="."):
954 """show information detected about current filesystem"""
954 """show information detected about current filesystem"""
955 ui.write(('path: %s\n') % path)
955 ui.write(('path: %s\n') % path)
956 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
956 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
957 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
957 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
958 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
958 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
959 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
959 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
960 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
960 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
961 casesensitive = '(unknown)'
961 casesensitive = '(unknown)'
962 try:
962 try:
963 with tempfile.NamedTemporaryFile(prefix='.debugfsinfo', dir=path) as f:
963 with tempfile.NamedTemporaryFile(prefix='.debugfsinfo', dir=path) as f:
964 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
964 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
965 except OSError:
965 except OSError:
966 pass
966 pass
967 ui.write(('case-sensitive: %s\n') % casesensitive)
967 ui.write(('case-sensitive: %s\n') % casesensitive)
968
968
969 @command('debuggetbundle',
969 @command('debuggetbundle',
970 [('H', 'head', [], _('id of head node'), _('ID')),
970 [('H', 'head', [], _('id of head node'), _('ID')),
971 ('C', 'common', [], _('id of common node'), _('ID')),
971 ('C', 'common', [], _('id of common node'), _('ID')),
972 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
972 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
973 _('REPO FILE [-H|-C ID]...'),
973 _('REPO FILE [-H|-C ID]...'),
974 norepo=True)
974 norepo=True)
975 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
975 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
976 """retrieves a bundle from a repo
976 """retrieves a bundle from a repo
977
977
978 Every ID must be a full-length hex node id string. Saves the bundle to the
978 Every ID must be a full-length hex node id string. Saves the bundle to the
979 given file.
979 given file.
980 """
980 """
981 opts = pycompat.byteskwargs(opts)
981 opts = pycompat.byteskwargs(opts)
982 repo = hg.peer(ui, opts, repopath)
982 repo = hg.peer(ui, opts, repopath)
983 if not repo.capable('getbundle'):
983 if not repo.capable('getbundle'):
984 raise error.Abort("getbundle() not supported by target repository")
984 raise error.Abort("getbundle() not supported by target repository")
985 args = {}
985 args = {}
986 if common:
986 if common:
987 args[r'common'] = [bin(s) for s in common]
987 args[r'common'] = [bin(s) for s in common]
988 if head:
988 if head:
989 args[r'heads'] = [bin(s) for s in head]
989 args[r'heads'] = [bin(s) for s in head]
990 # TODO: get desired bundlecaps from command line.
990 # TODO: get desired bundlecaps from command line.
991 args[r'bundlecaps'] = None
991 args[r'bundlecaps'] = None
992 bundle = repo.getbundle('debug', **args)
992 bundle = repo.getbundle('debug', **args)
993
993
994 bundletype = opts.get('type', 'bzip2').lower()
994 bundletype = opts.get('type', 'bzip2').lower()
995 btypes = {'none': 'HG10UN',
995 btypes = {'none': 'HG10UN',
996 'bzip2': 'HG10BZ',
996 'bzip2': 'HG10BZ',
997 'gzip': 'HG10GZ',
997 'gzip': 'HG10GZ',
998 'bundle2': 'HG20'}
998 'bundle2': 'HG20'}
999 bundletype = btypes.get(bundletype)
999 bundletype = btypes.get(bundletype)
1000 if bundletype not in bundle2.bundletypes:
1000 if bundletype not in bundle2.bundletypes:
1001 raise error.Abort(_('unknown bundle type specified with --type'))
1001 raise error.Abort(_('unknown bundle type specified with --type'))
1002 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1002 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1003
1003
1004 @command('debugignore', [], '[FILE]')
1004 @command('debugignore', [], '[FILE]')
1005 def debugignore(ui, repo, *files, **opts):
1005 def debugignore(ui, repo, *files, **opts):
1006 """display the combined ignore pattern and information about ignored files
1006 """display the combined ignore pattern and information about ignored files
1007
1007
1008 With no argument display the combined ignore pattern.
1008 With no argument display the combined ignore pattern.
1009
1009
1010 Given space separated file names, shows if the given file is ignored and
1010 Given space separated file names, shows if the given file is ignored and
1011 if so, show the ignore rule (file and line number) that matched it.
1011 if so, show the ignore rule (file and line number) that matched it.
1012 """
1012 """
1013 ignore = repo.dirstate._ignore
1013 ignore = repo.dirstate._ignore
1014 if not files:
1014 if not files:
1015 # Show all the patterns
1015 # Show all the patterns
1016 ui.write("%s\n" % pycompat.byterepr(ignore))
1016 ui.write("%s\n" % pycompat.byterepr(ignore))
1017 else:
1017 else:
1018 m = scmutil.match(repo[None], pats=files)
1018 m = scmutil.match(repo[None], pats=files)
1019 for f in m.files():
1019 for f in m.files():
1020 nf = util.normpath(f)
1020 nf = util.normpath(f)
1021 ignored = None
1021 ignored = None
1022 ignoredata = None
1022 ignoredata = None
1023 if nf != '.':
1023 if nf != '.':
1024 if ignore(nf):
1024 if ignore(nf):
1025 ignored = nf
1025 ignored = nf
1026 ignoredata = repo.dirstate._ignorefileandline(nf)
1026 ignoredata = repo.dirstate._ignorefileandline(nf)
1027 else:
1027 else:
1028 for p in util.finddirs(nf):
1028 for p in util.finddirs(nf):
1029 if ignore(p):
1029 if ignore(p):
1030 ignored = p
1030 ignored = p
1031 ignoredata = repo.dirstate._ignorefileandline(p)
1031 ignoredata = repo.dirstate._ignorefileandline(p)
1032 break
1032 break
1033 if ignored:
1033 if ignored:
1034 if ignored == nf:
1034 if ignored == nf:
1035 ui.write(_("%s is ignored\n") % m.uipath(f))
1035 ui.write(_("%s is ignored\n") % m.uipath(f))
1036 else:
1036 else:
1037 ui.write(_("%s is ignored because of "
1037 ui.write(_("%s is ignored because of "
1038 "containing folder %s\n")
1038 "containing folder %s\n")
1039 % (m.uipath(f), ignored))
1039 % (m.uipath(f), ignored))
1040 ignorefile, lineno, line = ignoredata
1040 ignorefile, lineno, line = ignoredata
1041 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1041 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1042 % (ignorefile, lineno, line))
1042 % (ignorefile, lineno, line))
1043 else:
1043 else:
1044 ui.write(_("%s is not ignored\n") % m.uipath(f))
1044 ui.write(_("%s is not ignored\n") % m.uipath(f))
1045
1045
1046 @command('debugindex', cmdutil.debugrevlogopts +
1046 @command('debugindex', cmdutil.debugrevlogopts +
1047 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1047 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1048 _('[-f FORMAT] -c|-m|FILE'),
1048 _('[-f FORMAT] -c|-m|FILE'),
1049 optionalrepo=True)
1049 optionalrepo=True)
1050 def debugindex(ui, repo, file_=None, **opts):
1050 def debugindex(ui, repo, file_=None, **opts):
1051 """dump the contents of an index file"""
1051 """dump the contents of an index file"""
1052 opts = pycompat.byteskwargs(opts)
1052 opts = pycompat.byteskwargs(opts)
1053 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1053 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1054 format = opts.get('format', 0)
1054 format = opts.get('format', 0)
1055 if format not in (0, 1):
1055 if format not in (0, 1):
1056 raise error.Abort(_("unknown format %d") % format)
1056 raise error.Abort(_("unknown format %d") % format)
1057
1057
1058 generaldelta = r.version & revlog.FLAG_GENERALDELTA
1058 generaldelta = r.version & revlog.FLAG_GENERALDELTA
1059 if generaldelta:
1059 if generaldelta:
1060 basehdr = ' delta'
1060 basehdr = ' delta'
1061 else:
1061 else:
1062 basehdr = ' base'
1062 basehdr = ' base'
1063
1063
1064 if ui.debugflag:
1064 if ui.debugflag:
1065 shortfn = hex
1065 shortfn = hex
1066 else:
1066 else:
1067 shortfn = short
1067 shortfn = short
1068
1068
1069 # There might not be anything in r, so have a sane default
1069 # There might not be anything in r, so have a sane default
1070 idlen = 12
1070 idlen = 12
1071 for i in r:
1071 for i in r:
1072 idlen = len(shortfn(r.node(i)))
1072 idlen = len(shortfn(r.node(i)))
1073 break
1073 break
1074
1074
1075 if format == 0:
1075 if format == 0:
1076 ui.write((" rev offset length " + basehdr + " linkrev"
1076 ui.write((" rev offset length " + basehdr + " linkrev"
1077 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
1077 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
1078 elif format == 1:
1078 elif format == 1:
1079 ui.write((" rev flag offset length"
1079 ui.write((" rev flag offset length"
1080 " size " + basehdr + " link p1 p2"
1080 " size " + basehdr + " link p1 p2"
1081 " %s\n") % "nodeid".rjust(idlen))
1081 " %s\n") % "nodeid".rjust(idlen))
1082
1082
1083 for i in r:
1083 for i in r:
1084 node = r.node(i)
1084 node = r.node(i)
1085 if generaldelta:
1085 if generaldelta:
1086 base = r.deltaparent(i)
1086 base = r.deltaparent(i)
1087 else:
1087 else:
1088 base = r.chainbase(i)
1088 base = r.chainbase(i)
1089 if format == 0:
1089 if format == 0:
1090 try:
1090 try:
1091 pp = r.parents(node)
1091 pp = r.parents(node)
1092 except Exception:
1092 except Exception:
1093 pp = [nullid, nullid]
1093 pp = [nullid, nullid]
1094 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1094 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1095 i, r.start(i), r.length(i), base, r.linkrev(i),
1095 i, r.start(i), r.length(i), base, r.linkrev(i),
1096 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
1096 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
1097 elif format == 1:
1097 elif format == 1:
1098 pr = r.parentrevs(i)
1098 pr = r.parentrevs(i)
1099 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1099 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1100 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1100 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1101 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
1101 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
1102
1102
1103 @command('debugindexdot', cmdutil.debugrevlogopts,
1103 @command('debugindexdot', cmdutil.debugrevlogopts,
1104 _('-c|-m|FILE'), optionalrepo=True)
1104 _('-c|-m|FILE'), optionalrepo=True)
1105 def debugindexdot(ui, repo, file_=None, **opts):
1105 def debugindexdot(ui, repo, file_=None, **opts):
1106 """dump an index DAG as a graphviz dot file"""
1106 """dump an index DAG as a graphviz dot file"""
1107 opts = pycompat.byteskwargs(opts)
1107 opts = pycompat.byteskwargs(opts)
1108 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
1108 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
1109 ui.write(("digraph G {\n"))
1109 ui.write(("digraph G {\n"))
1110 for i in r:
1110 for i in r:
1111 node = r.node(i)
1111 node = r.node(i)
1112 pp = r.parents(node)
1112 pp = r.parents(node)
1113 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1113 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1114 if pp[1] != nullid:
1114 if pp[1] != nullid:
1115 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1115 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1116 ui.write("}\n")
1116 ui.write("}\n")
1117
1117
1118 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1118 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1119 def debuginstall(ui, **opts):
1119 def debuginstall(ui, **opts):
1120 '''test Mercurial installation
1120 '''test Mercurial installation
1121
1121
1122 Returns 0 on success.
1122 Returns 0 on success.
1123 '''
1123 '''
1124 opts = pycompat.byteskwargs(opts)
1124 opts = pycompat.byteskwargs(opts)
1125
1125
1126 def writetemp(contents):
1126 def writetemp(contents):
1127 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1127 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1128 f = os.fdopen(fd, r"wb")
1128 f = os.fdopen(fd, r"wb")
1129 f.write(contents)
1129 f.write(contents)
1130 f.close()
1130 f.close()
1131 return name
1131 return name
1132
1132
1133 problems = 0
1133 problems = 0
1134
1134
1135 fm = ui.formatter('debuginstall', opts)
1135 fm = ui.formatter('debuginstall', opts)
1136 fm.startitem()
1136 fm.startitem()
1137
1137
1138 # encoding
1138 # encoding
1139 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1139 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1140 err = None
1140 err = None
1141 try:
1141 try:
1142 codecs.lookup(pycompat.sysstr(encoding.encoding))
1142 codecs.lookup(pycompat.sysstr(encoding.encoding))
1143 except LookupError as inst:
1143 except LookupError as inst:
1144 err = util.forcebytestr(inst)
1144 err = util.forcebytestr(inst)
1145 problems += 1
1145 problems += 1
1146 fm.condwrite(err, 'encodingerror', _(" %s\n"
1146 fm.condwrite(err, 'encodingerror', _(" %s\n"
1147 " (check that your locale is properly set)\n"), err)
1147 " (check that your locale is properly set)\n"), err)
1148
1148
1149 # Python
1149 # Python
1150 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1150 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1151 pycompat.sysexecutable)
1151 pycompat.sysexecutable)
1152 fm.write('pythonver', _("checking Python version (%s)\n"),
1152 fm.write('pythonver', _("checking Python version (%s)\n"),
1153 ("%d.%d.%d" % sys.version_info[:3]))
1153 ("%d.%d.%d" % sys.version_info[:3]))
1154 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1154 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1155 os.path.dirname(pycompat.fsencode(os.__file__)))
1155 os.path.dirname(pycompat.fsencode(os.__file__)))
1156
1156
1157 security = set(sslutil.supportedprotocols)
1157 security = set(sslutil.supportedprotocols)
1158 if sslutil.hassni:
1158 if sslutil.hassni:
1159 security.add('sni')
1159 security.add('sni')
1160
1160
1161 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1161 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1162 fm.formatlist(sorted(security), name='protocol',
1162 fm.formatlist(sorted(security), name='protocol',
1163 fmt='%s', sep=','))
1163 fmt='%s', sep=','))
1164
1164
1165 # These are warnings, not errors. So don't increment problem count. This
1165 # These are warnings, not errors. So don't increment problem count. This
1166 # may change in the future.
1166 # may change in the future.
1167 if 'tls1.2' not in security:
1167 if 'tls1.2' not in security:
1168 fm.plain(_(' TLS 1.2 not supported by Python install; '
1168 fm.plain(_(' TLS 1.2 not supported by Python install; '
1169 'network connections lack modern security\n'))
1169 'network connections lack modern security\n'))
1170 if 'sni' not in security:
1170 if 'sni' not in security:
1171 fm.plain(_(' SNI not supported by Python install; may have '
1171 fm.plain(_(' SNI not supported by Python install; may have '
1172 'connectivity issues with some servers\n'))
1172 'connectivity issues with some servers\n'))
1173
1173
1174 # TODO print CA cert info
1174 # TODO print CA cert info
1175
1175
1176 # hg version
1176 # hg version
1177 hgver = util.version()
1177 hgver = util.version()
1178 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1178 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1179 hgver.split('+')[0])
1179 hgver.split('+')[0])
1180 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1180 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1181 '+'.join(hgver.split('+')[1:]))
1181 '+'.join(hgver.split('+')[1:]))
1182
1182
1183 # compiled modules
1183 # compiled modules
1184 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1184 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1185 policy.policy)
1185 policy.policy)
1186 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1186 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1187 os.path.dirname(pycompat.fsencode(__file__)))
1187 os.path.dirname(pycompat.fsencode(__file__)))
1188
1188
1189 if policy.policy in ('c', 'allow'):
1189 if policy.policy in ('c', 'allow'):
1190 err = None
1190 err = None
1191 try:
1191 try:
1192 from .cext import (
1192 from .cext import (
1193 base85,
1193 base85,
1194 bdiff,
1194 bdiff,
1195 mpatch,
1195 mpatch,
1196 osutil,
1196 osutil,
1197 )
1197 )
1198 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1198 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1199 except Exception as inst:
1199 except Exception as inst:
1200 err = util.forcebytestr(inst)
1200 err = util.forcebytestr(inst)
1201 problems += 1
1201 problems += 1
1202 fm.condwrite(err, 'extensionserror', " %s\n", err)
1202 fm.condwrite(err, 'extensionserror', " %s\n", err)
1203
1203
1204 compengines = util.compengines._engines.values()
1204 compengines = util.compengines._engines.values()
1205 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1205 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1206 fm.formatlist(sorted(e.name() for e in compengines),
1206 fm.formatlist(sorted(e.name() for e in compengines),
1207 name='compengine', fmt='%s', sep=', '))
1207 name='compengine', fmt='%s', sep=', '))
1208 fm.write('compenginesavail', _('checking available compression engines '
1208 fm.write('compenginesavail', _('checking available compression engines '
1209 '(%s)\n'),
1209 '(%s)\n'),
1210 fm.formatlist(sorted(e.name() for e in compengines
1210 fm.formatlist(sorted(e.name() for e in compengines
1211 if e.available()),
1211 if e.available()),
1212 name='compengine', fmt='%s', sep=', '))
1212 name='compengine', fmt='%s', sep=', '))
1213 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1213 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1214 fm.write('compenginesserver', _('checking available compression engines '
1214 fm.write('compenginesserver', _('checking available compression engines '
1215 'for wire protocol (%s)\n'),
1215 'for wire protocol (%s)\n'),
1216 fm.formatlist([e.name() for e in wirecompengines
1216 fm.formatlist([e.name() for e in wirecompengines
1217 if e.wireprotosupport()],
1217 if e.wireprotosupport()],
1218 name='compengine', fmt='%s', sep=', '))
1218 name='compengine', fmt='%s', sep=', '))
1219 re2 = 'missing'
1219 re2 = 'missing'
1220 if util._re2:
1220 if util._re2:
1221 re2 = 'available'
1221 re2 = 'available'
1222 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1222 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1223 fm.data(re2=bool(util._re2))
1223 fm.data(re2=bool(util._re2))
1224
1224
1225 # templates
1225 # templates
1226 p = templater.templatepaths()
1226 p = templater.templatepaths()
1227 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1227 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1228 fm.condwrite(not p, '', _(" no template directories found\n"))
1228 fm.condwrite(not p, '', _(" no template directories found\n"))
1229 if p:
1229 if p:
1230 m = templater.templatepath("map-cmdline.default")
1230 m = templater.templatepath("map-cmdline.default")
1231 if m:
1231 if m:
1232 # template found, check if it is working
1232 # template found, check if it is working
1233 err = None
1233 err = None
1234 try:
1234 try:
1235 templater.templater.frommapfile(m)
1235 templater.templater.frommapfile(m)
1236 except Exception as inst:
1236 except Exception as inst:
1237 err = util.forcebytestr(inst)
1237 err = util.forcebytestr(inst)
1238 p = None
1238 p = None
1239 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1239 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1240 else:
1240 else:
1241 p = None
1241 p = None
1242 fm.condwrite(p, 'defaulttemplate',
1242 fm.condwrite(p, 'defaulttemplate',
1243 _("checking default template (%s)\n"), m)
1243 _("checking default template (%s)\n"), m)
1244 fm.condwrite(not m, 'defaulttemplatenotfound',
1244 fm.condwrite(not m, 'defaulttemplatenotfound',
1245 _(" template '%s' not found\n"), "default")
1245 _(" template '%s' not found\n"), "default")
1246 if not p:
1246 if not p:
1247 problems += 1
1247 problems += 1
1248 fm.condwrite(not p, '',
1248 fm.condwrite(not p, '',
1249 _(" (templates seem to have been installed incorrectly)\n"))
1249 _(" (templates seem to have been installed incorrectly)\n"))
1250
1250
1251 # editor
1251 # editor
1252 editor = ui.geteditor()
1252 editor = ui.geteditor()
1253 editor = util.expandpath(editor)
1253 editor = util.expandpath(editor)
1254 editorbin = util.shellsplit(editor)[0]
1254 editorbin = util.shellsplit(editor)[0]
1255 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1255 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1256 cmdpath = util.findexe(editorbin)
1256 cmdpath = util.findexe(editorbin)
1257 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1257 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1258 _(" No commit editor set and can't find %s in PATH\n"
1258 _(" No commit editor set and can't find %s in PATH\n"
1259 " (specify a commit editor in your configuration"
1259 " (specify a commit editor in your configuration"
1260 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1260 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1261 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1261 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1262 _(" Can't find editor '%s' in PATH\n"
1262 _(" Can't find editor '%s' in PATH\n"
1263 " (specify a commit editor in your configuration"
1263 " (specify a commit editor in your configuration"
1264 " file)\n"), not cmdpath and editorbin)
1264 " file)\n"), not cmdpath and editorbin)
1265 if not cmdpath and editor != 'vi':
1265 if not cmdpath and editor != 'vi':
1266 problems += 1
1266 problems += 1
1267
1267
1268 # check username
1268 # check username
1269 username = None
1269 username = None
1270 err = None
1270 err = None
1271 try:
1271 try:
1272 username = ui.username()
1272 username = ui.username()
1273 except error.Abort as e:
1273 except error.Abort as e:
1274 err = util.forcebytestr(e)
1274 err = util.forcebytestr(e)
1275 problems += 1
1275 problems += 1
1276
1276
1277 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1277 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1278 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1278 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1279 " (specify a username in your configuration file)\n"), err)
1279 " (specify a username in your configuration file)\n"), err)
1280
1280
1281 fm.condwrite(not problems, '',
1281 fm.condwrite(not problems, '',
1282 _("no problems detected\n"))
1282 _("no problems detected\n"))
1283 if not problems:
1283 if not problems:
1284 fm.data(problems=problems)
1284 fm.data(problems=problems)
1285 fm.condwrite(problems, 'problems',
1285 fm.condwrite(problems, 'problems',
1286 _("%d problems detected,"
1286 _("%d problems detected,"
1287 " please check your install!\n"), problems)
1287 " please check your install!\n"), problems)
1288 fm.end()
1288 fm.end()
1289
1289
1290 return problems
1290 return problems
1291
1291
1292 @command('debugknown', [], _('REPO ID...'), norepo=True)
1292 @command('debugknown', [], _('REPO ID...'), norepo=True)
1293 def debugknown(ui, repopath, *ids, **opts):
1293 def debugknown(ui, repopath, *ids, **opts):
1294 """test whether node ids are known to a repo
1294 """test whether node ids are known to a repo
1295
1295
1296 Every ID must be a full-length hex node id string. Returns a list of 0s
1296 Every ID must be a full-length hex node id string. Returns a list of 0s
1297 and 1s indicating unknown/known.
1297 and 1s indicating unknown/known.
1298 """
1298 """
1299 opts = pycompat.byteskwargs(opts)
1299 opts = pycompat.byteskwargs(opts)
1300 repo = hg.peer(ui, opts, repopath)
1300 repo = hg.peer(ui, opts, repopath)
1301 if not repo.capable('known'):
1301 if not repo.capable('known'):
1302 raise error.Abort("known() not supported by target repository")
1302 raise error.Abort("known() not supported by target repository")
1303 flags = repo.known([bin(s) for s in ids])
1303 flags = repo.known([bin(s) for s in ids])
1304 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1304 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1305
1305
1306 @command('debuglabelcomplete', [], _('LABEL...'))
1306 @command('debuglabelcomplete', [], _('LABEL...'))
1307 def debuglabelcomplete(ui, repo, *args):
1307 def debuglabelcomplete(ui, repo, *args):
1308 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1308 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1309 debugnamecomplete(ui, repo, *args)
1309 debugnamecomplete(ui, repo, *args)
1310
1310
1311 @command('debuglocks',
1311 @command('debuglocks',
1312 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1312 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1313 ('W', 'force-wlock', None,
1313 ('W', 'force-wlock', None,
1314 _('free the working state lock (DANGEROUS)')),
1314 _('free the working state lock (DANGEROUS)')),
1315 ('s', 'set-lock', None, _('set the store lock until stopped')),
1315 ('s', 'set-lock', None, _('set the store lock until stopped')),
1316 ('S', 'set-wlock', None,
1316 ('S', 'set-wlock', None,
1317 _('set the working state lock until stopped'))],
1317 _('set the working state lock until stopped'))],
1318 _('[OPTION]...'))
1318 _('[OPTION]...'))
1319 def debuglocks(ui, repo, **opts):
1319 def debuglocks(ui, repo, **opts):
1320 """show or modify state of locks
1320 """show or modify state of locks
1321
1321
1322 By default, this command will show which locks are held. This
1322 By default, this command will show which locks are held. This
1323 includes the user and process holding the lock, the amount of time
1323 includes the user and process holding the lock, the amount of time
1324 the lock has been held, and the machine name where the process is
1324 the lock has been held, and the machine name where the process is
1325 running if it's not local.
1325 running if it's not local.
1326
1326
1327 Locks protect the integrity of Mercurial's data, so should be
1327 Locks protect the integrity of Mercurial's data, so should be
1328 treated with care. System crashes or other interruptions may cause
1328 treated with care. System crashes or other interruptions may cause
1329 locks to not be properly released, though Mercurial will usually
1329 locks to not be properly released, though Mercurial will usually
1330 detect and remove such stale locks automatically.
1330 detect and remove such stale locks automatically.
1331
1331
1332 However, detecting stale locks may not always be possible (for
1332 However, detecting stale locks may not always be possible (for
1333 instance, on a shared filesystem). Removing locks may also be
1333 instance, on a shared filesystem). Removing locks may also be
1334 blocked by filesystem permissions.
1334 blocked by filesystem permissions.
1335
1335
1336 Setting a lock will prevent other commands from changing the data.
1336 Setting a lock will prevent other commands from changing the data.
1337 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1337 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1338 The set locks are removed when the command exits.
1338 The set locks are removed when the command exits.
1339
1339
1340 Returns 0 if no locks are held.
1340 Returns 0 if no locks are held.
1341
1341
1342 """
1342 """
1343
1343
1344 if opts.get(r'force_lock'):
1344 if opts.get(r'force_lock'):
1345 repo.svfs.unlink('lock')
1345 repo.svfs.unlink('lock')
1346 if opts.get(r'force_wlock'):
1346 if opts.get(r'force_wlock'):
1347 repo.vfs.unlink('wlock')
1347 repo.vfs.unlink('wlock')
1348 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1348 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1349 return 0
1349 return 0
1350
1350
1351 locks = []
1351 locks = []
1352 try:
1352 try:
1353 if opts.get(r'set_wlock'):
1353 if opts.get(r'set_wlock'):
1354 try:
1354 try:
1355 locks.append(repo.wlock(False))
1355 locks.append(repo.wlock(False))
1356 except error.LockHeld:
1356 except error.LockHeld:
1357 raise error.Abort(_('wlock is already held'))
1357 raise error.Abort(_('wlock is already held'))
1358 if opts.get(r'set_lock'):
1358 if opts.get(r'set_lock'):
1359 try:
1359 try:
1360 locks.append(repo.lock(False))
1360 locks.append(repo.lock(False))
1361 except error.LockHeld:
1361 except error.LockHeld:
1362 raise error.Abort(_('lock is already held'))
1362 raise error.Abort(_('lock is already held'))
1363 if len(locks):
1363 if len(locks):
1364 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1364 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1365 return 0
1365 return 0
1366 finally:
1366 finally:
1367 release(*locks)
1367 release(*locks)
1368
1368
1369 now = time.time()
1369 now = time.time()
1370 held = 0
1370 held = 0
1371
1371
1372 def report(vfs, name, method):
1372 def report(vfs, name, method):
1373 # this causes stale locks to get reaped for more accurate reporting
1373 # this causes stale locks to get reaped for more accurate reporting
1374 try:
1374 try:
1375 l = method(False)
1375 l = method(False)
1376 except error.LockHeld:
1376 except error.LockHeld:
1377 l = None
1377 l = None
1378
1378
1379 if l:
1379 if l:
1380 l.release()
1380 l.release()
1381 else:
1381 else:
1382 try:
1382 try:
1383 st = vfs.lstat(name)
1383 st = vfs.lstat(name)
1384 age = now - st[stat.ST_MTIME]
1384 age = now - st[stat.ST_MTIME]
1385 user = util.username(st.st_uid)
1385 user = util.username(st.st_uid)
1386 locker = vfs.readlock(name)
1386 locker = vfs.readlock(name)
1387 if ":" in locker:
1387 if ":" in locker:
1388 host, pid = locker.split(':')
1388 host, pid = locker.split(':')
1389 if host == socket.gethostname():
1389 if host == socket.gethostname():
1390 locker = 'user %s, process %s' % (user, pid)
1390 locker = 'user %s, process %s' % (user, pid)
1391 else:
1391 else:
1392 locker = 'user %s, process %s, host %s' \
1392 locker = 'user %s, process %s, host %s' \
1393 % (user, pid, host)
1393 % (user, pid, host)
1394 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1394 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1395 return 1
1395 return 1
1396 except OSError as e:
1396 except OSError as e:
1397 if e.errno != errno.ENOENT:
1397 if e.errno != errno.ENOENT:
1398 raise
1398 raise
1399
1399
1400 ui.write(("%-6s free\n") % (name + ":"))
1400 ui.write(("%-6s free\n") % (name + ":"))
1401 return 0
1401 return 0
1402
1402
1403 held += report(repo.svfs, "lock", repo.lock)
1403 held += report(repo.svfs, "lock", repo.lock)
1404 held += report(repo.vfs, "wlock", repo.wlock)
1404 held += report(repo.vfs, "wlock", repo.wlock)
1405
1405
1406 return held
1406 return held
1407
1407
1408 @command('debugmergestate', [], '')
1408 @command('debugmergestate', [], '')
1409 def debugmergestate(ui, repo, *args):
1409 def debugmergestate(ui, repo, *args):
1410 """print merge state
1410 """print merge state
1411
1411
1412 Use --verbose to print out information about whether v1 or v2 merge state
1412 Use --verbose to print out information about whether v1 or v2 merge state
1413 was chosen."""
1413 was chosen."""
1414 def _hashornull(h):
1414 def _hashornull(h):
1415 if h == nullhex:
1415 if h == nullhex:
1416 return 'null'
1416 return 'null'
1417 else:
1417 else:
1418 return h
1418 return h
1419
1419
1420 def printrecords(version):
1420 def printrecords(version):
1421 ui.write(('* version %d records\n') % version)
1421 ui.write(('* version %d records\n') % version)
1422 if version == 1:
1422 if version == 1:
1423 records = v1records
1423 records = v1records
1424 else:
1424 else:
1425 records = v2records
1425 records = v2records
1426
1426
1427 for rtype, record in records:
1427 for rtype, record in records:
1428 # pretty print some record types
1428 # pretty print some record types
1429 if rtype == 'L':
1429 if rtype == 'L':
1430 ui.write(('local: %s\n') % record)
1430 ui.write(('local: %s\n') % record)
1431 elif rtype == 'O':
1431 elif rtype == 'O':
1432 ui.write(('other: %s\n') % record)
1432 ui.write(('other: %s\n') % record)
1433 elif rtype == 'm':
1433 elif rtype == 'm':
1434 driver, mdstate = record.split('\0', 1)
1434 driver, mdstate = record.split('\0', 1)
1435 ui.write(('merge driver: %s (state "%s")\n')
1435 ui.write(('merge driver: %s (state "%s")\n')
1436 % (driver, mdstate))
1436 % (driver, mdstate))
1437 elif rtype in 'FDC':
1437 elif rtype in 'FDC':
1438 r = record.split('\0')
1438 r = record.split('\0')
1439 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1439 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1440 if version == 1:
1440 if version == 1:
1441 onode = 'not stored in v1 format'
1441 onode = 'not stored in v1 format'
1442 flags = r[7]
1442 flags = r[7]
1443 else:
1443 else:
1444 onode, flags = r[7:9]
1444 onode, flags = r[7:9]
1445 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1445 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1446 % (f, rtype, state, _hashornull(hash)))
1446 % (f, rtype, state, _hashornull(hash)))
1447 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1447 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1448 ui.write((' ancestor path: %s (node %s)\n')
1448 ui.write((' ancestor path: %s (node %s)\n')
1449 % (afile, _hashornull(anode)))
1449 % (afile, _hashornull(anode)))
1450 ui.write((' other path: %s (node %s)\n')
1450 ui.write((' other path: %s (node %s)\n')
1451 % (ofile, _hashornull(onode)))
1451 % (ofile, _hashornull(onode)))
1452 elif rtype == 'f':
1452 elif rtype == 'f':
1453 filename, rawextras = record.split('\0', 1)
1453 filename, rawextras = record.split('\0', 1)
1454 extras = rawextras.split('\0')
1454 extras = rawextras.split('\0')
1455 i = 0
1455 i = 0
1456 extrastrings = []
1456 extrastrings = []
1457 while i < len(extras):
1457 while i < len(extras):
1458 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1458 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1459 i += 2
1459 i += 2
1460
1460
1461 ui.write(('file extras: %s (%s)\n')
1461 ui.write(('file extras: %s (%s)\n')
1462 % (filename, ', '.join(extrastrings)))
1462 % (filename, ', '.join(extrastrings)))
1463 elif rtype == 'l':
1463 elif rtype == 'l':
1464 labels = record.split('\0', 2)
1464 labels = record.split('\0', 2)
1465 labels = [l for l in labels if len(l) > 0]
1465 labels = [l for l in labels if len(l) > 0]
1466 ui.write(('labels:\n'))
1466 ui.write(('labels:\n'))
1467 ui.write((' local: %s\n' % labels[0]))
1467 ui.write((' local: %s\n' % labels[0]))
1468 ui.write((' other: %s\n' % labels[1]))
1468 ui.write((' other: %s\n' % labels[1]))
1469 if len(labels) > 2:
1469 if len(labels) > 2:
1470 ui.write((' base: %s\n' % labels[2]))
1470 ui.write((' base: %s\n' % labels[2]))
1471 else:
1471 else:
1472 ui.write(('unrecognized entry: %s\t%s\n')
1472 ui.write(('unrecognized entry: %s\t%s\n')
1473 % (rtype, record.replace('\0', '\t')))
1473 % (rtype, record.replace('\0', '\t')))
1474
1474
1475 # Avoid mergestate.read() since it may raise an exception for unsupported
1475 # Avoid mergestate.read() since it may raise an exception for unsupported
1476 # merge state records. We shouldn't be doing this, but this is OK since this
1476 # merge state records. We shouldn't be doing this, but this is OK since this
1477 # command is pretty low-level.
1477 # command is pretty low-level.
1478 ms = mergemod.mergestate(repo)
1478 ms = mergemod.mergestate(repo)
1479
1479
1480 # sort so that reasonable information is on top
1480 # sort so that reasonable information is on top
1481 v1records = ms._readrecordsv1()
1481 v1records = ms._readrecordsv1()
1482 v2records = ms._readrecordsv2()
1482 v2records = ms._readrecordsv2()
1483 order = 'LOml'
1483 order = 'LOml'
1484 def key(r):
1484 def key(r):
1485 idx = order.find(r[0])
1485 idx = order.find(r[0])
1486 if idx == -1:
1486 if idx == -1:
1487 return (1, r[1])
1487 return (1, r[1])
1488 else:
1488 else:
1489 return (0, idx)
1489 return (0, idx)
1490 v1records.sort(key=key)
1490 v1records.sort(key=key)
1491 v2records.sort(key=key)
1491 v2records.sort(key=key)
1492
1492
1493 if not v1records and not v2records:
1493 if not v1records and not v2records:
1494 ui.write(('no merge state found\n'))
1494 ui.write(('no merge state found\n'))
1495 elif not v2records:
1495 elif not v2records:
1496 ui.note(('no version 2 merge state\n'))
1496 ui.note(('no version 2 merge state\n'))
1497 printrecords(1)
1497 printrecords(1)
1498 elif ms._v1v2match(v1records, v2records):
1498 elif ms._v1v2match(v1records, v2records):
1499 ui.note(('v1 and v2 states match: using v2\n'))
1499 ui.note(('v1 and v2 states match: using v2\n'))
1500 printrecords(2)
1500 printrecords(2)
1501 else:
1501 else:
1502 ui.note(('v1 and v2 states mismatch: using v1\n'))
1502 ui.note(('v1 and v2 states mismatch: using v1\n'))
1503 printrecords(1)
1503 printrecords(1)
1504 if ui.verbose:
1504 if ui.verbose:
1505 printrecords(2)
1505 printrecords(2)
1506
1506
1507 @command('debugnamecomplete', [], _('NAME...'))
1507 @command('debugnamecomplete', [], _('NAME...'))
1508 def debugnamecomplete(ui, repo, *args):
1508 def debugnamecomplete(ui, repo, *args):
1509 '''complete "names" - tags, open branch names, bookmark names'''
1509 '''complete "names" - tags, open branch names, bookmark names'''
1510
1510
1511 names = set()
1511 names = set()
1512 # since we previously only listed open branches, we will handle that
1512 # since we previously only listed open branches, we will handle that
1513 # specially (after this for loop)
1513 # specially (after this for loop)
1514 for name, ns in repo.names.iteritems():
1514 for name, ns in repo.names.iteritems():
1515 if name != 'branches':
1515 if name != 'branches':
1516 names.update(ns.listnames(repo))
1516 names.update(ns.listnames(repo))
1517 names.update(tag for (tag, heads, tip, closed)
1517 names.update(tag for (tag, heads, tip, closed)
1518 in repo.branchmap().iterbranches() if not closed)
1518 in repo.branchmap().iterbranches() if not closed)
1519 completions = set()
1519 completions = set()
1520 if not args:
1520 if not args:
1521 args = ['']
1521 args = ['']
1522 for a in args:
1522 for a in args:
1523 completions.update(n for n in names if n.startswith(a))
1523 completions.update(n for n in names if n.startswith(a))
1524 ui.write('\n'.join(sorted(completions)))
1524 ui.write('\n'.join(sorted(completions)))
1525 ui.write('\n')
1525 ui.write('\n')
1526
1526
1527 @command('debugobsolete',
1527 @command('debugobsolete',
1528 [('', 'flags', 0, _('markers flag')),
1528 [('', 'flags', 0, _('markers flag')),
1529 ('', 'record-parents', False,
1529 ('', 'record-parents', False,
1530 _('record parent information for the precursor')),
1530 _('record parent information for the precursor')),
1531 ('r', 'rev', [], _('display markers relevant to REV')),
1531 ('r', 'rev', [], _('display markers relevant to REV')),
1532 ('', 'exclusive', False, _('restrict display to markers only '
1532 ('', 'exclusive', False, _('restrict display to markers only '
1533 'relevant to REV')),
1533 'relevant to REV')),
1534 ('', 'index', False, _('display index of the marker')),
1534 ('', 'index', False, _('display index of the marker')),
1535 ('', 'delete', [], _('delete markers specified by indices')),
1535 ('', 'delete', [], _('delete markers specified by indices')),
1536 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1536 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1537 _('[OBSOLETED [REPLACEMENT ...]]'))
1537 _('[OBSOLETED [REPLACEMENT ...]]'))
1538 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1538 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1539 """create arbitrary obsolete marker
1539 """create arbitrary obsolete marker
1540
1540
1541 With no arguments, displays the list of obsolescence markers."""
1541 With no arguments, displays the list of obsolescence markers."""
1542
1542
1543 opts = pycompat.byteskwargs(opts)
1543 opts = pycompat.byteskwargs(opts)
1544
1544
1545 def parsenodeid(s):
1545 def parsenodeid(s):
1546 try:
1546 try:
1547 # We do not use revsingle/revrange functions here to accept
1547 # We do not use revsingle/revrange functions here to accept
1548 # arbitrary node identifiers, possibly not present in the
1548 # arbitrary node identifiers, possibly not present in the
1549 # local repository.
1549 # local repository.
1550 n = bin(s)
1550 n = bin(s)
1551 if len(n) != len(nullid):
1551 if len(n) != len(nullid):
1552 raise TypeError()
1552 raise TypeError()
1553 return n
1553 return n
1554 except TypeError:
1554 except TypeError:
1555 raise error.Abort('changeset references must be full hexadecimal '
1555 raise error.Abort('changeset references must be full hexadecimal '
1556 'node identifiers')
1556 'node identifiers')
1557
1557
1558 if opts.get('delete'):
1558 if opts.get('delete'):
1559 indices = []
1559 indices = []
1560 for v in opts.get('delete'):
1560 for v in opts.get('delete'):
1561 try:
1561 try:
1562 indices.append(int(v))
1562 indices.append(int(v))
1563 except ValueError:
1563 except ValueError:
1564 raise error.Abort(_('invalid index value: %r') % v,
1564 raise error.Abort(_('invalid index value: %r') % v,
1565 hint=_('use integers for indices'))
1565 hint=_('use integers for indices'))
1566
1566
1567 if repo.currenttransaction():
1567 if repo.currenttransaction():
1568 raise error.Abort(_('cannot delete obsmarkers in the middle '
1568 raise error.Abort(_('cannot delete obsmarkers in the middle '
1569 'of transaction.'))
1569 'of transaction.'))
1570
1570
1571 with repo.lock():
1571 with repo.lock():
1572 n = repair.deleteobsmarkers(repo.obsstore, indices)
1572 n = repair.deleteobsmarkers(repo.obsstore, indices)
1573 ui.write(_('deleted %i obsolescence markers\n') % n)
1573 ui.write(_('deleted %i obsolescence markers\n') % n)
1574
1574
1575 return
1575 return
1576
1576
1577 if precursor is not None:
1577 if precursor is not None:
1578 if opts['rev']:
1578 if opts['rev']:
1579 raise error.Abort('cannot select revision when creating marker')
1579 raise error.Abort('cannot select revision when creating marker')
1580 metadata = {}
1580 metadata = {}
1581 metadata['user'] = opts['user'] or ui.username()
1581 metadata['user'] = opts['user'] or ui.username()
1582 succs = tuple(parsenodeid(succ) for succ in successors)
1582 succs = tuple(parsenodeid(succ) for succ in successors)
1583 l = repo.lock()
1583 l = repo.lock()
1584 try:
1584 try:
1585 tr = repo.transaction('debugobsolete')
1585 tr = repo.transaction('debugobsolete')
1586 try:
1586 try:
1587 date = opts.get('date')
1587 date = opts.get('date')
1588 if date:
1588 if date:
1589 date = dateutil.parsedate(date)
1589 date = dateutil.parsedate(date)
1590 else:
1590 else:
1591 date = None
1591 date = None
1592 prec = parsenodeid(precursor)
1592 prec = parsenodeid(precursor)
1593 parents = None
1593 parents = None
1594 if opts['record_parents']:
1594 if opts['record_parents']:
1595 if prec not in repo.unfiltered():
1595 if prec not in repo.unfiltered():
1596 raise error.Abort('cannot used --record-parents on '
1596 raise error.Abort('cannot used --record-parents on '
1597 'unknown changesets')
1597 'unknown changesets')
1598 parents = repo.unfiltered()[prec].parents()
1598 parents = repo.unfiltered()[prec].parents()
1599 parents = tuple(p.node() for p in parents)
1599 parents = tuple(p.node() for p in parents)
1600 repo.obsstore.create(tr, prec, succs, opts['flags'],
1600 repo.obsstore.create(tr, prec, succs, opts['flags'],
1601 parents=parents, date=date,
1601 parents=parents, date=date,
1602 metadata=metadata, ui=ui)
1602 metadata=metadata, ui=ui)
1603 tr.close()
1603 tr.close()
1604 except ValueError as exc:
1604 except ValueError as exc:
1605 raise error.Abort(_('bad obsmarker input: %s') %
1605 raise error.Abort(_('bad obsmarker input: %s') %
1606 pycompat.bytestr(exc))
1606 pycompat.bytestr(exc))
1607 finally:
1607 finally:
1608 tr.release()
1608 tr.release()
1609 finally:
1609 finally:
1610 l.release()
1610 l.release()
1611 else:
1611 else:
1612 if opts['rev']:
1612 if opts['rev']:
1613 revs = scmutil.revrange(repo, opts['rev'])
1613 revs = scmutil.revrange(repo, opts['rev'])
1614 nodes = [repo[r].node() for r in revs]
1614 nodes = [repo[r].node() for r in revs]
1615 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1615 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1616 exclusive=opts['exclusive']))
1616 exclusive=opts['exclusive']))
1617 markers.sort(key=lambda x: x._data)
1617 markers.sort(key=lambda x: x._data)
1618 else:
1618 else:
1619 markers = obsutil.getmarkers(repo)
1619 markers = obsutil.getmarkers(repo)
1620
1620
1621 markerstoiter = markers
1621 markerstoiter = markers
1622 isrelevant = lambda m: True
1622 isrelevant = lambda m: True
1623 if opts.get('rev') and opts.get('index'):
1623 if opts.get('rev') and opts.get('index'):
1624 markerstoiter = obsutil.getmarkers(repo)
1624 markerstoiter = obsutil.getmarkers(repo)
1625 markerset = set(markers)
1625 markerset = set(markers)
1626 isrelevant = lambda m: m in markerset
1626 isrelevant = lambda m: m in markerset
1627
1627
1628 fm = ui.formatter('debugobsolete', opts)
1628 fm = ui.formatter('debugobsolete', opts)
1629 for i, m in enumerate(markerstoiter):
1629 for i, m in enumerate(markerstoiter):
1630 if not isrelevant(m):
1630 if not isrelevant(m):
1631 # marker can be irrelevant when we're iterating over a set
1631 # marker can be irrelevant when we're iterating over a set
1632 # of markers (markerstoiter) which is bigger than the set
1632 # of markers (markerstoiter) which is bigger than the set
1633 # of markers we want to display (markers)
1633 # of markers we want to display (markers)
1634 # this can happen if both --index and --rev options are
1634 # this can happen if both --index and --rev options are
1635 # provided and thus we need to iterate over all of the markers
1635 # provided and thus we need to iterate over all of the markers
1636 # to get the correct indices, but only display the ones that
1636 # to get the correct indices, but only display the ones that
1637 # are relevant to --rev value
1637 # are relevant to --rev value
1638 continue
1638 continue
1639 fm.startitem()
1639 fm.startitem()
1640 ind = i if opts.get('index') else None
1640 ind = i if opts.get('index') else None
1641 cmdutil.showmarker(fm, m, index=ind)
1641 cmdutil.showmarker(fm, m, index=ind)
1642 fm.end()
1642 fm.end()
1643
1643
1644 @command('debugpathcomplete',
1644 @command('debugpathcomplete',
1645 [('f', 'full', None, _('complete an entire path')),
1645 [('f', 'full', None, _('complete an entire path')),
1646 ('n', 'normal', None, _('show only normal files')),
1646 ('n', 'normal', None, _('show only normal files')),
1647 ('a', 'added', None, _('show only added files')),
1647 ('a', 'added', None, _('show only added files')),
1648 ('r', 'removed', None, _('show only removed files'))],
1648 ('r', 'removed', None, _('show only removed files'))],
1649 _('FILESPEC...'))
1649 _('FILESPEC...'))
1650 def debugpathcomplete(ui, repo, *specs, **opts):
1650 def debugpathcomplete(ui, repo, *specs, **opts):
1651 '''complete part or all of a tracked path
1651 '''complete part or all of a tracked path
1652
1652
1653 This command supports shells that offer path name completion. It
1653 This command supports shells that offer path name completion. It
1654 currently completes only files already known to the dirstate.
1654 currently completes only files already known to the dirstate.
1655
1655
1656 Completion extends only to the next path segment unless
1656 Completion extends only to the next path segment unless
1657 --full is specified, in which case entire paths are used.'''
1657 --full is specified, in which case entire paths are used.'''
1658
1658
1659 def complete(path, acceptable):
1659 def complete(path, acceptable):
1660 dirstate = repo.dirstate
1660 dirstate = repo.dirstate
1661 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1661 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1662 rootdir = repo.root + pycompat.ossep
1662 rootdir = repo.root + pycompat.ossep
1663 if spec != repo.root and not spec.startswith(rootdir):
1663 if spec != repo.root and not spec.startswith(rootdir):
1664 return [], []
1664 return [], []
1665 if os.path.isdir(spec):
1665 if os.path.isdir(spec):
1666 spec += '/'
1666 spec += '/'
1667 spec = spec[len(rootdir):]
1667 spec = spec[len(rootdir):]
1668 fixpaths = pycompat.ossep != '/'
1668 fixpaths = pycompat.ossep != '/'
1669 if fixpaths:
1669 if fixpaths:
1670 spec = spec.replace(pycompat.ossep, '/')
1670 spec = spec.replace(pycompat.ossep, '/')
1671 speclen = len(spec)
1671 speclen = len(spec)
1672 fullpaths = opts[r'full']
1672 fullpaths = opts[r'full']
1673 files, dirs = set(), set()
1673 files, dirs = set(), set()
1674 adddir, addfile = dirs.add, files.add
1674 adddir, addfile = dirs.add, files.add
1675 for f, st in dirstate.iteritems():
1675 for f, st in dirstate.iteritems():
1676 if f.startswith(spec) and st[0] in acceptable:
1676 if f.startswith(spec) and st[0] in acceptable:
1677 if fixpaths:
1677 if fixpaths:
1678 f = f.replace('/', pycompat.ossep)
1678 f = f.replace('/', pycompat.ossep)
1679 if fullpaths:
1679 if fullpaths:
1680 addfile(f)
1680 addfile(f)
1681 continue
1681 continue
1682 s = f.find(pycompat.ossep, speclen)
1682 s = f.find(pycompat.ossep, speclen)
1683 if s >= 0:
1683 if s >= 0:
1684 adddir(f[:s])
1684 adddir(f[:s])
1685 else:
1685 else:
1686 addfile(f)
1686 addfile(f)
1687 return files, dirs
1687 return files, dirs
1688
1688
1689 acceptable = ''
1689 acceptable = ''
1690 if opts[r'normal']:
1690 if opts[r'normal']:
1691 acceptable += 'nm'
1691 acceptable += 'nm'
1692 if opts[r'added']:
1692 if opts[r'added']:
1693 acceptable += 'a'
1693 acceptable += 'a'
1694 if opts[r'removed']:
1694 if opts[r'removed']:
1695 acceptable += 'r'
1695 acceptable += 'r'
1696 cwd = repo.getcwd()
1696 cwd = repo.getcwd()
1697 if not specs:
1697 if not specs:
1698 specs = ['.']
1698 specs = ['.']
1699
1699
1700 files, dirs = set(), set()
1700 files, dirs = set(), set()
1701 for spec in specs:
1701 for spec in specs:
1702 f, d = complete(spec, acceptable or 'nmar')
1702 f, d = complete(spec, acceptable or 'nmar')
1703 files.update(f)
1703 files.update(f)
1704 dirs.update(d)
1704 dirs.update(d)
1705 files.update(dirs)
1705 files.update(dirs)
1706 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1706 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1707 ui.write('\n')
1707 ui.write('\n')
1708
1708
1709 @command('debugpeer', [], _('PATH'), norepo=True)
1709 @command('debugpeer', [], _('PATH'), norepo=True)
1710 def debugpeer(ui, path):
1710 def debugpeer(ui, path):
1711 """establish a connection to a peer repository"""
1711 """establish a connection to a peer repository"""
1712 # Always enable peer request logging. Requires --debug to display
1712 # Always enable peer request logging. Requires --debug to display
1713 # though.
1713 # though.
1714 overrides = {
1714 overrides = {
1715 ('devel', 'debug.peer-request'): True,
1715 ('devel', 'debug.peer-request'): True,
1716 }
1716 }
1717
1717
1718 with ui.configoverride(overrides):
1718 with ui.configoverride(overrides):
1719 peer = hg.peer(ui, {}, path)
1719 peer = hg.peer(ui, {}, path)
1720
1720
1721 local = peer.local() is not None
1721 local = peer.local() is not None
1722 canpush = peer.canpush()
1722 canpush = peer.canpush()
1723
1723
1724 ui.write(_('url: %s\n') % peer.url())
1724 ui.write(_('url: %s\n') % peer.url())
1725 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1725 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1726 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1726 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1727
1727
1728 @command('debugpickmergetool',
1728 @command('debugpickmergetool',
1729 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1729 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1730 ('', 'changedelete', None, _('emulate merging change and delete')),
1730 ('', 'changedelete', None, _('emulate merging change and delete')),
1731 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1731 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1732 _('[PATTERN]...'),
1732 _('[PATTERN]...'),
1733 inferrepo=True)
1733 inferrepo=True)
1734 def debugpickmergetool(ui, repo, *pats, **opts):
1734 def debugpickmergetool(ui, repo, *pats, **opts):
1735 """examine which merge tool is chosen for specified file
1735 """examine which merge tool is chosen for specified file
1736
1736
1737 As described in :hg:`help merge-tools`, Mercurial examines
1737 As described in :hg:`help merge-tools`, Mercurial examines
1738 configurations below in this order to decide which merge tool is
1738 configurations below in this order to decide which merge tool is
1739 chosen for specified file.
1739 chosen for specified file.
1740
1740
1741 1. ``--tool`` option
1741 1. ``--tool`` option
1742 2. ``HGMERGE`` environment variable
1742 2. ``HGMERGE`` environment variable
1743 3. configurations in ``merge-patterns`` section
1743 3. configurations in ``merge-patterns`` section
1744 4. configuration of ``ui.merge``
1744 4. configuration of ``ui.merge``
1745 5. configurations in ``merge-tools`` section
1745 5. configurations in ``merge-tools`` section
1746 6. ``hgmerge`` tool (for historical reason only)
1746 6. ``hgmerge`` tool (for historical reason only)
1747 7. default tool for fallback (``:merge`` or ``:prompt``)
1747 7. default tool for fallback (``:merge`` or ``:prompt``)
1748
1748
1749 This command writes out examination result in the style below::
1749 This command writes out examination result in the style below::
1750
1750
1751 FILE = MERGETOOL
1751 FILE = MERGETOOL
1752
1752
1753 By default, all files known in the first parent context of the
1753 By default, all files known in the first parent context of the
1754 working directory are examined. Use file patterns and/or -I/-X
1754 working directory are examined. Use file patterns and/or -I/-X
1755 options to limit target files. -r/--rev is also useful to examine
1755 options to limit target files. -r/--rev is also useful to examine
1756 files in another context without actual updating to it.
1756 files in another context without actual updating to it.
1757
1757
1758 With --debug, this command shows warning messages while matching
1758 With --debug, this command shows warning messages while matching
1759 against ``merge-patterns`` and so on, too. It is recommended to
1759 against ``merge-patterns`` and so on, too. It is recommended to
1760 use this option with explicit file patterns and/or -I/-X options,
1760 use this option with explicit file patterns and/or -I/-X options,
1761 because this option increases amount of output per file according
1761 because this option increases amount of output per file according
1762 to configurations in hgrc.
1762 to configurations in hgrc.
1763
1763
1764 With -v/--verbose, this command shows configurations below at
1764 With -v/--verbose, this command shows configurations below at
1765 first (only if specified).
1765 first (only if specified).
1766
1766
1767 - ``--tool`` option
1767 - ``--tool`` option
1768 - ``HGMERGE`` environment variable
1768 - ``HGMERGE`` environment variable
1769 - configuration of ``ui.merge``
1769 - configuration of ``ui.merge``
1770
1770
1771 If merge tool is chosen before matching against
1771 If merge tool is chosen before matching against
1772 ``merge-patterns``, this command can't show any helpful
1772 ``merge-patterns``, this command can't show any helpful
1773 information, even with --debug. In such case, information above is
1773 information, even with --debug. In such case, information above is
1774 useful to know why a merge tool is chosen.
1774 useful to know why a merge tool is chosen.
1775 """
1775 """
1776 opts = pycompat.byteskwargs(opts)
1776 opts = pycompat.byteskwargs(opts)
1777 overrides = {}
1777 overrides = {}
1778 if opts['tool']:
1778 if opts['tool']:
1779 overrides[('ui', 'forcemerge')] = opts['tool']
1779 overrides[('ui', 'forcemerge')] = opts['tool']
1780 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1780 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1781
1781
1782 with ui.configoverride(overrides, 'debugmergepatterns'):
1782 with ui.configoverride(overrides, 'debugmergepatterns'):
1783 hgmerge = encoding.environ.get("HGMERGE")
1783 hgmerge = encoding.environ.get("HGMERGE")
1784 if hgmerge is not None:
1784 if hgmerge is not None:
1785 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1785 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1786 uimerge = ui.config("ui", "merge")
1786 uimerge = ui.config("ui", "merge")
1787 if uimerge:
1787 if uimerge:
1788 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1788 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1789
1789
1790 ctx = scmutil.revsingle(repo, opts.get('rev'))
1790 ctx = scmutil.revsingle(repo, opts.get('rev'))
1791 m = scmutil.match(ctx, pats, opts)
1791 m = scmutil.match(ctx, pats, opts)
1792 changedelete = opts['changedelete']
1792 changedelete = opts['changedelete']
1793 for path in ctx.walk(m):
1793 for path in ctx.walk(m):
1794 fctx = ctx[path]
1794 fctx = ctx[path]
1795 try:
1795 try:
1796 if not ui.debugflag:
1796 if not ui.debugflag:
1797 ui.pushbuffer(error=True)
1797 ui.pushbuffer(error=True)
1798 tool, toolpath = filemerge._picktool(repo, ui, path,
1798 tool, toolpath = filemerge._picktool(repo, ui, path,
1799 fctx.isbinary(),
1799 fctx.isbinary(),
1800 'l' in fctx.flags(),
1800 'l' in fctx.flags(),
1801 changedelete)
1801 changedelete)
1802 finally:
1802 finally:
1803 if not ui.debugflag:
1803 if not ui.debugflag:
1804 ui.popbuffer()
1804 ui.popbuffer()
1805 ui.write(('%s = %s\n') % (path, tool))
1805 ui.write(('%s = %s\n') % (path, tool))
1806
1806
1807 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1807 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1808 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1808 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1809 '''access the pushkey key/value protocol
1809 '''access the pushkey key/value protocol
1810
1810
1811 With two args, list the keys in the given namespace.
1811 With two args, list the keys in the given namespace.
1812
1812
1813 With five args, set a key to new if it currently is set to old.
1813 With five args, set a key to new if it currently is set to old.
1814 Reports success or failure.
1814 Reports success or failure.
1815 '''
1815 '''
1816
1816
1817 target = hg.peer(ui, {}, repopath)
1817 target = hg.peer(ui, {}, repopath)
1818 if keyinfo:
1818 if keyinfo:
1819 key, old, new = keyinfo
1819 key, old, new = keyinfo
1820 r = target.pushkey(namespace, key, old, new)
1820 r = target.pushkey(namespace, key, old, new)
1821 ui.status(pycompat.bytestr(r) + '\n')
1821 ui.status(pycompat.bytestr(r) + '\n')
1822 return not r
1822 return not r
1823 else:
1823 else:
1824 for k, v in sorted(target.listkeys(namespace).iteritems()):
1824 for k, v in sorted(target.listkeys(namespace).iteritems()):
1825 ui.write("%s\t%s\n" % (util.escapestr(k),
1825 ui.write("%s\t%s\n" % (util.escapestr(k),
1826 util.escapestr(v)))
1826 util.escapestr(v)))
1827
1827
1828 @command('debugpvec', [], _('A B'))
1828 @command('debugpvec', [], _('A B'))
1829 def debugpvec(ui, repo, a, b=None):
1829 def debugpvec(ui, repo, a, b=None):
1830 ca = scmutil.revsingle(repo, a)
1830 ca = scmutil.revsingle(repo, a)
1831 cb = scmutil.revsingle(repo, b)
1831 cb = scmutil.revsingle(repo, b)
1832 pa = pvec.ctxpvec(ca)
1832 pa = pvec.ctxpvec(ca)
1833 pb = pvec.ctxpvec(cb)
1833 pb = pvec.ctxpvec(cb)
1834 if pa == pb:
1834 if pa == pb:
1835 rel = "="
1835 rel = "="
1836 elif pa > pb:
1836 elif pa > pb:
1837 rel = ">"
1837 rel = ">"
1838 elif pa < pb:
1838 elif pa < pb:
1839 rel = "<"
1839 rel = "<"
1840 elif pa | pb:
1840 elif pa | pb:
1841 rel = "|"
1841 rel = "|"
1842 ui.write(_("a: %s\n") % pa)
1842 ui.write(_("a: %s\n") % pa)
1843 ui.write(_("b: %s\n") % pb)
1843 ui.write(_("b: %s\n") % pb)
1844 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1844 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1845 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1845 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1846 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1846 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1847 pa.distance(pb), rel))
1847 pa.distance(pb), rel))
1848
1848
1849 @command('debugrebuilddirstate|debugrebuildstate',
1849 @command('debugrebuilddirstate|debugrebuildstate',
1850 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1850 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1851 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1851 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1852 'the working copy parent')),
1852 'the working copy parent')),
1853 ],
1853 ],
1854 _('[-r REV]'))
1854 _('[-r REV]'))
1855 def debugrebuilddirstate(ui, repo, rev, **opts):
1855 def debugrebuilddirstate(ui, repo, rev, **opts):
1856 """rebuild the dirstate as it would look like for the given revision
1856 """rebuild the dirstate as it would look like for the given revision
1857
1857
1858 If no revision is specified the first current parent will be used.
1858 If no revision is specified the first current parent will be used.
1859
1859
1860 The dirstate will be set to the files of the given revision.
1860 The dirstate will be set to the files of the given revision.
1861 The actual working directory content or existing dirstate
1861 The actual working directory content or existing dirstate
1862 information such as adds or removes is not considered.
1862 information such as adds or removes is not considered.
1863
1863
1864 ``minimal`` will only rebuild the dirstate status for files that claim to be
1864 ``minimal`` will only rebuild the dirstate status for files that claim to be
1865 tracked but are not in the parent manifest, or that exist in the parent
1865 tracked but are not in the parent manifest, or that exist in the parent
1866 manifest but are not in the dirstate. It will not change adds, removes, or
1866 manifest but are not in the dirstate. It will not change adds, removes, or
1867 modified files that are in the working copy parent.
1867 modified files that are in the working copy parent.
1868
1868
1869 One use of this command is to make the next :hg:`status` invocation
1869 One use of this command is to make the next :hg:`status` invocation
1870 check the actual file content.
1870 check the actual file content.
1871 """
1871 """
1872 ctx = scmutil.revsingle(repo, rev)
1872 ctx = scmutil.revsingle(repo, rev)
1873 with repo.wlock():
1873 with repo.wlock():
1874 dirstate = repo.dirstate
1874 dirstate = repo.dirstate
1875 changedfiles = None
1875 changedfiles = None
1876 # See command doc for what minimal does.
1876 # See command doc for what minimal does.
1877 if opts.get(r'minimal'):
1877 if opts.get(r'minimal'):
1878 manifestfiles = set(ctx.manifest().keys())
1878 manifestfiles = set(ctx.manifest().keys())
1879 dirstatefiles = set(dirstate)
1879 dirstatefiles = set(dirstate)
1880 manifestonly = manifestfiles - dirstatefiles
1880 manifestonly = manifestfiles - dirstatefiles
1881 dsonly = dirstatefiles - manifestfiles
1881 dsonly = dirstatefiles - manifestfiles
1882 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1882 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1883 changedfiles = manifestonly | dsnotadded
1883 changedfiles = manifestonly | dsnotadded
1884
1884
1885 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1885 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1886
1886
1887 @command('debugrebuildfncache', [], '')
1887 @command('debugrebuildfncache', [], '')
1888 def debugrebuildfncache(ui, repo):
1888 def debugrebuildfncache(ui, repo):
1889 """rebuild the fncache file"""
1889 """rebuild the fncache file"""
1890 repair.rebuildfncache(ui, repo)
1890 repair.rebuildfncache(ui, repo)
1891
1891
1892 @command('debugrename',
1892 @command('debugrename',
1893 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1893 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1894 _('[-r REV] FILE'))
1894 _('[-r REV] FILE'))
1895 def debugrename(ui, repo, file1, *pats, **opts):
1895 def debugrename(ui, repo, file1, *pats, **opts):
1896 """dump rename information"""
1896 """dump rename information"""
1897
1897
1898 opts = pycompat.byteskwargs(opts)
1898 opts = pycompat.byteskwargs(opts)
1899 ctx = scmutil.revsingle(repo, opts.get('rev'))
1899 ctx = scmutil.revsingle(repo, opts.get('rev'))
1900 m = scmutil.match(ctx, (file1,) + pats, opts)
1900 m = scmutil.match(ctx, (file1,) + pats, opts)
1901 for abs in ctx.walk(m):
1901 for abs in ctx.walk(m):
1902 fctx = ctx[abs]
1902 fctx = ctx[abs]
1903 o = fctx.filelog().renamed(fctx.filenode())
1903 o = fctx.filelog().renamed(fctx.filenode())
1904 rel = m.rel(abs)
1904 rel = m.rel(abs)
1905 if o:
1905 if o:
1906 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1906 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1907 else:
1907 else:
1908 ui.write(_("%s not renamed\n") % rel)
1908 ui.write(_("%s not renamed\n") % rel)
1909
1909
1910 @command('debugrevlog', cmdutil.debugrevlogopts +
1910 @command('debugrevlog', cmdutil.debugrevlogopts +
1911 [('d', 'dump', False, _('dump index data'))],
1911 [('d', 'dump', False, _('dump index data'))],
1912 _('-c|-m|FILE'),
1912 _('-c|-m|FILE'),
1913 optionalrepo=True)
1913 optionalrepo=True)
1914 def debugrevlog(ui, repo, file_=None, **opts):
1914 def debugrevlog(ui, repo, file_=None, **opts):
1915 """show data and statistics about a revlog"""
1915 """show data and statistics about a revlog"""
1916 opts = pycompat.byteskwargs(opts)
1916 opts = pycompat.byteskwargs(opts)
1917 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1917 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1918
1918
1919 if opts.get("dump"):
1919 if opts.get("dump"):
1920 numrevs = len(r)
1920 numrevs = len(r)
1921 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1921 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1922 " rawsize totalsize compression heads chainlen\n"))
1922 " rawsize totalsize compression heads chainlen\n"))
1923 ts = 0
1923 ts = 0
1924 heads = set()
1924 heads = set()
1925
1925
1926 for rev in xrange(numrevs):
1926 for rev in xrange(numrevs):
1927 dbase = r.deltaparent(rev)
1927 dbase = r.deltaparent(rev)
1928 if dbase == -1:
1928 if dbase == -1:
1929 dbase = rev
1929 dbase = rev
1930 cbase = r.chainbase(rev)
1930 cbase = r.chainbase(rev)
1931 clen = r.chainlen(rev)
1931 clen = r.chainlen(rev)
1932 p1, p2 = r.parentrevs(rev)
1932 p1, p2 = r.parentrevs(rev)
1933 rs = r.rawsize(rev)
1933 rs = r.rawsize(rev)
1934 ts = ts + rs
1934 ts = ts + rs
1935 heads -= set(r.parentrevs(rev))
1935 heads -= set(r.parentrevs(rev))
1936 heads.add(rev)
1936 heads.add(rev)
1937 try:
1937 try:
1938 compression = ts / r.end(rev)
1938 compression = ts / r.end(rev)
1939 except ZeroDivisionError:
1939 except ZeroDivisionError:
1940 compression = 0
1940 compression = 0
1941 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1941 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1942 "%11d %5d %8d\n" %
1942 "%11d %5d %8d\n" %
1943 (rev, p1, p2, r.start(rev), r.end(rev),
1943 (rev, p1, p2, r.start(rev), r.end(rev),
1944 r.start(dbase), r.start(cbase),
1944 r.start(dbase), r.start(cbase),
1945 r.start(p1), r.start(p2),
1945 r.start(p1), r.start(p2),
1946 rs, ts, compression, len(heads), clen))
1946 rs, ts, compression, len(heads), clen))
1947 return 0
1947 return 0
1948
1948
1949 v = r.version
1949 v = r.version
1950 format = v & 0xFFFF
1950 format = v & 0xFFFF
1951 flags = []
1951 flags = []
1952 gdelta = False
1952 gdelta = False
1953 if v & revlog.FLAG_INLINE_DATA:
1953 if v & revlog.FLAG_INLINE_DATA:
1954 flags.append('inline')
1954 flags.append('inline')
1955 if v & revlog.FLAG_GENERALDELTA:
1955 if v & revlog.FLAG_GENERALDELTA:
1956 gdelta = True
1956 gdelta = True
1957 flags.append('generaldelta')
1957 flags.append('generaldelta')
1958 if not flags:
1958 if not flags:
1959 flags = ['(none)']
1959 flags = ['(none)']
1960
1960
1961 nummerges = 0
1961 nummerges = 0
1962 numfull = 0
1962 numfull = 0
1963 numprev = 0
1963 numprev = 0
1964 nump1 = 0
1964 nump1 = 0
1965 nump2 = 0
1965 nump2 = 0
1966 numother = 0
1966 numother = 0
1967 nump1prev = 0
1967 nump1prev = 0
1968 nump2prev = 0
1968 nump2prev = 0
1969 chainlengths = []
1969 chainlengths = []
1970 chainbases = []
1970 chainbases = []
1971 chainspans = []
1971 chainspans = []
1972
1972
1973 datasize = [None, 0, 0]
1973 datasize = [None, 0, 0]
1974 fullsize = [None, 0, 0]
1974 fullsize = [None, 0, 0]
1975 deltasize = [None, 0, 0]
1975 deltasize = [None, 0, 0]
1976 chunktypecounts = {}
1976 chunktypecounts = {}
1977 chunktypesizes = {}
1977 chunktypesizes = {}
1978
1978
1979 def addsize(size, l):
1979 def addsize(size, l):
1980 if l[0] is None or size < l[0]:
1980 if l[0] is None or size < l[0]:
1981 l[0] = size
1981 l[0] = size
1982 if size > l[1]:
1982 if size > l[1]:
1983 l[1] = size
1983 l[1] = size
1984 l[2] += size
1984 l[2] += size
1985
1985
1986 numrevs = len(r)
1986 numrevs = len(r)
1987 for rev in xrange(numrevs):
1987 for rev in xrange(numrevs):
1988 p1, p2 = r.parentrevs(rev)
1988 p1, p2 = r.parentrevs(rev)
1989 delta = r.deltaparent(rev)
1989 delta = r.deltaparent(rev)
1990 if format > 0:
1990 if format > 0:
1991 addsize(r.rawsize(rev), datasize)
1991 addsize(r.rawsize(rev), datasize)
1992 if p2 != nullrev:
1992 if p2 != nullrev:
1993 nummerges += 1
1993 nummerges += 1
1994 size = r.length(rev)
1994 size = r.length(rev)
1995 if delta == nullrev:
1995 if delta == nullrev:
1996 chainlengths.append(0)
1996 chainlengths.append(0)
1997 chainbases.append(r.start(rev))
1997 chainbases.append(r.start(rev))
1998 chainspans.append(size)
1998 chainspans.append(size)
1999 numfull += 1
1999 numfull += 1
2000 addsize(size, fullsize)
2000 addsize(size, fullsize)
2001 else:
2001 else:
2002 chainlengths.append(chainlengths[delta] + 1)
2002 chainlengths.append(chainlengths[delta] + 1)
2003 baseaddr = chainbases[delta]
2003 baseaddr = chainbases[delta]
2004 revaddr = r.start(rev)
2004 revaddr = r.start(rev)
2005 chainbases.append(baseaddr)
2005 chainbases.append(baseaddr)
2006 chainspans.append((revaddr - baseaddr) + size)
2006 chainspans.append((revaddr - baseaddr) + size)
2007 addsize(size, deltasize)
2007 addsize(size, deltasize)
2008 if delta == rev - 1:
2008 if delta == rev - 1:
2009 numprev += 1
2009 numprev += 1
2010 if delta == p1:
2010 if delta == p1:
2011 nump1prev += 1
2011 nump1prev += 1
2012 elif delta == p2:
2012 elif delta == p2:
2013 nump2prev += 1
2013 nump2prev += 1
2014 elif delta == p1:
2014 elif delta == p1:
2015 nump1 += 1
2015 nump1 += 1
2016 elif delta == p2:
2016 elif delta == p2:
2017 nump2 += 1
2017 nump2 += 1
2018 elif delta != nullrev:
2018 elif delta != nullrev:
2019 numother += 1
2019 numother += 1
2020
2020
2021 # Obtain data on the raw chunks in the revlog.
2021 # Obtain data on the raw chunks in the revlog.
2022 segment = r._getsegmentforrevs(rev, rev)[1]
2022 segment = r._getsegmentforrevs(rev, rev)[1]
2023 if segment:
2023 if segment:
2024 chunktype = bytes(segment[0:1])
2024 chunktype = bytes(segment[0:1])
2025 else:
2025 else:
2026 chunktype = 'empty'
2026 chunktype = 'empty'
2027
2027
2028 if chunktype not in chunktypecounts:
2028 if chunktype not in chunktypecounts:
2029 chunktypecounts[chunktype] = 0
2029 chunktypecounts[chunktype] = 0
2030 chunktypesizes[chunktype] = 0
2030 chunktypesizes[chunktype] = 0
2031
2031
2032 chunktypecounts[chunktype] += 1
2032 chunktypecounts[chunktype] += 1
2033 chunktypesizes[chunktype] += size
2033 chunktypesizes[chunktype] += size
2034
2034
2035 # Adjust size min value for empty cases
2035 # Adjust size min value for empty cases
2036 for size in (datasize, fullsize, deltasize):
2036 for size in (datasize, fullsize, deltasize):
2037 if size[0] is None:
2037 if size[0] is None:
2038 size[0] = 0
2038 size[0] = 0
2039
2039
2040 numdeltas = numrevs - numfull
2040 numdeltas = numrevs - numfull
2041 numoprev = numprev - nump1prev - nump2prev
2041 numoprev = numprev - nump1prev - nump2prev
2042 totalrawsize = datasize[2]
2042 totalrawsize = datasize[2]
2043 datasize[2] /= numrevs
2043 datasize[2] /= numrevs
2044 fulltotal = fullsize[2]
2044 fulltotal = fullsize[2]
2045 fullsize[2] /= numfull
2045 fullsize[2] /= numfull
2046 deltatotal = deltasize[2]
2046 deltatotal = deltasize[2]
2047 if numrevs - numfull > 0:
2047 if numrevs - numfull > 0:
2048 deltasize[2] /= numrevs - numfull
2048 deltasize[2] /= numrevs - numfull
2049 totalsize = fulltotal + deltatotal
2049 totalsize = fulltotal + deltatotal
2050 avgchainlen = sum(chainlengths) / numrevs
2050 avgchainlen = sum(chainlengths) / numrevs
2051 maxchainlen = max(chainlengths)
2051 maxchainlen = max(chainlengths)
2052 maxchainspan = max(chainspans)
2052 maxchainspan = max(chainspans)
2053 compratio = 1
2053 compratio = 1
2054 if totalsize:
2054 if totalsize:
2055 compratio = totalrawsize / totalsize
2055 compratio = totalrawsize / totalsize
2056
2056
2057 basedfmtstr = '%%%dd\n'
2057 basedfmtstr = '%%%dd\n'
2058 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2058 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2059
2059
2060 def dfmtstr(max):
2060 def dfmtstr(max):
2061 return basedfmtstr % len(str(max))
2061 return basedfmtstr % len(str(max))
2062 def pcfmtstr(max, padding=0):
2062 def pcfmtstr(max, padding=0):
2063 return basepcfmtstr % (len(str(max)), ' ' * padding)
2063 return basepcfmtstr % (len(str(max)), ' ' * padding)
2064
2064
2065 def pcfmt(value, total):
2065 def pcfmt(value, total):
2066 if total:
2066 if total:
2067 return (value, 100 * float(value) / total)
2067 return (value, 100 * float(value) / total)
2068 else:
2068 else:
2069 return value, 100.0
2069 return value, 100.0
2070
2070
2071 ui.write(('format : %d\n') % format)
2071 ui.write(('format : %d\n') % format)
2072 ui.write(('flags : %s\n') % ', '.join(flags))
2072 ui.write(('flags : %s\n') % ', '.join(flags))
2073
2073
2074 ui.write('\n')
2074 ui.write('\n')
2075 fmt = pcfmtstr(totalsize)
2075 fmt = pcfmtstr(totalsize)
2076 fmt2 = dfmtstr(totalsize)
2076 fmt2 = dfmtstr(totalsize)
2077 ui.write(('revisions : ') + fmt2 % numrevs)
2077 ui.write(('revisions : ') + fmt2 % numrevs)
2078 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2078 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2079 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2079 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2080 ui.write(('revisions : ') + fmt2 % numrevs)
2080 ui.write(('revisions : ') + fmt2 % numrevs)
2081 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2081 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2082 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2082 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2083 ui.write(('revision size : ') + fmt2 % totalsize)
2083 ui.write(('revision size : ') + fmt2 % totalsize)
2084 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2084 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2085 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2085 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2086
2086
2087 def fmtchunktype(chunktype):
2087 def fmtchunktype(chunktype):
2088 if chunktype == 'empty':
2088 if chunktype == 'empty':
2089 return ' %s : ' % chunktype
2089 return ' %s : ' % chunktype
2090 elif chunktype in pycompat.bytestr(string.ascii_letters):
2090 elif chunktype in pycompat.bytestr(string.ascii_letters):
2091 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2091 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2092 else:
2092 else:
2093 return ' 0x%s : ' % hex(chunktype)
2093 return ' 0x%s : ' % hex(chunktype)
2094
2094
2095 ui.write('\n')
2095 ui.write('\n')
2096 ui.write(('chunks : ') + fmt2 % numrevs)
2096 ui.write(('chunks : ') + fmt2 % numrevs)
2097 for chunktype in sorted(chunktypecounts):
2097 for chunktype in sorted(chunktypecounts):
2098 ui.write(fmtchunktype(chunktype))
2098 ui.write(fmtchunktype(chunktype))
2099 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2099 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2100 ui.write(('chunks size : ') + fmt2 % totalsize)
2100 ui.write(('chunks size : ') + fmt2 % totalsize)
2101 for chunktype in sorted(chunktypecounts):
2101 for chunktype in sorted(chunktypecounts):
2102 ui.write(fmtchunktype(chunktype))
2102 ui.write(fmtchunktype(chunktype))
2103 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2103 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2104
2104
2105 ui.write('\n')
2105 ui.write('\n')
2106 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2106 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2107 ui.write(('avg chain length : ') + fmt % avgchainlen)
2107 ui.write(('avg chain length : ') + fmt % avgchainlen)
2108 ui.write(('max chain length : ') + fmt % maxchainlen)
2108 ui.write(('max chain length : ') + fmt % maxchainlen)
2109 ui.write(('max chain reach : ') + fmt % maxchainspan)
2109 ui.write(('max chain reach : ') + fmt % maxchainspan)
2110 ui.write(('compression ratio : ') + fmt % compratio)
2110 ui.write(('compression ratio : ') + fmt % compratio)
2111
2111
2112 if format > 0:
2112 if format > 0:
2113 ui.write('\n')
2113 ui.write('\n')
2114 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2114 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2115 % tuple(datasize))
2115 % tuple(datasize))
2116 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2116 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2117 % tuple(fullsize))
2117 % tuple(fullsize))
2118 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2118 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2119 % tuple(deltasize))
2119 % tuple(deltasize))
2120
2120
2121 if numdeltas > 0:
2121 if numdeltas > 0:
2122 ui.write('\n')
2122 ui.write('\n')
2123 fmt = pcfmtstr(numdeltas)
2123 fmt = pcfmtstr(numdeltas)
2124 fmt2 = pcfmtstr(numdeltas, 4)
2124 fmt2 = pcfmtstr(numdeltas, 4)
2125 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2125 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2126 if numprev > 0:
2126 if numprev > 0:
2127 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2127 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2128 numprev))
2128 numprev))
2129 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2129 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2130 numprev))
2130 numprev))
2131 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2131 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2132 numprev))
2132 numprev))
2133 if gdelta:
2133 if gdelta:
2134 ui.write(('deltas against p1 : ')
2134 ui.write(('deltas against p1 : ')
2135 + fmt % pcfmt(nump1, numdeltas))
2135 + fmt % pcfmt(nump1, numdeltas))
2136 ui.write(('deltas against p2 : ')
2136 ui.write(('deltas against p2 : ')
2137 + fmt % pcfmt(nump2, numdeltas))
2137 + fmt % pcfmt(nump2, numdeltas))
2138 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2138 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2139 numdeltas))
2139 numdeltas))
2140
2140
2141 @command('debugrevspec',
2141 @command('debugrevspec',
2142 [('', 'optimize', None,
2142 [('', 'optimize', None,
2143 _('print parsed tree after optimizing (DEPRECATED)')),
2143 _('print parsed tree after optimizing (DEPRECATED)')),
2144 ('', 'show-revs', True, _('print list of result revisions (default)')),
2144 ('', 'show-revs', True, _('print list of result revisions (default)')),
2145 ('s', 'show-set', None, _('print internal representation of result set')),
2145 ('s', 'show-set', None, _('print internal representation of result set')),
2146 ('p', 'show-stage', [],
2146 ('p', 'show-stage', [],
2147 _('print parsed tree at the given stage'), _('NAME')),
2147 _('print parsed tree at the given stage'), _('NAME')),
2148 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2148 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2149 ('', 'verify-optimized', False, _('verify optimized result')),
2149 ('', 'verify-optimized', False, _('verify optimized result')),
2150 ],
2150 ],
2151 ('REVSPEC'))
2151 ('REVSPEC'))
2152 def debugrevspec(ui, repo, expr, **opts):
2152 def debugrevspec(ui, repo, expr, **opts):
2153 """parse and apply a revision specification
2153 """parse and apply a revision specification
2154
2154
2155 Use -p/--show-stage option to print the parsed tree at the given stages.
2155 Use -p/--show-stage option to print the parsed tree at the given stages.
2156 Use -p all to print tree at every stage.
2156 Use -p all to print tree at every stage.
2157
2157
2158 Use --no-show-revs option with -s or -p to print only the set
2158 Use --no-show-revs option with -s or -p to print only the set
2159 representation or the parsed tree respectively.
2159 representation or the parsed tree respectively.
2160
2160
2161 Use --verify-optimized to compare the optimized result with the unoptimized
2161 Use --verify-optimized to compare the optimized result with the unoptimized
2162 one. Returns 1 if the optimized result differs.
2162 one. Returns 1 if the optimized result differs.
2163 """
2163 """
2164 opts = pycompat.byteskwargs(opts)
2164 opts = pycompat.byteskwargs(opts)
2165 aliases = ui.configitems('revsetalias')
2165 aliases = ui.configitems('revsetalias')
2166 stages = [
2166 stages = [
2167 ('parsed', lambda tree: tree),
2167 ('parsed', lambda tree: tree),
2168 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2168 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2169 ui.warn)),
2169 ui.warn)),
2170 ('concatenated', revsetlang.foldconcat),
2170 ('concatenated', revsetlang.foldconcat),
2171 ('analyzed', revsetlang.analyze),
2171 ('analyzed', revsetlang.analyze),
2172 ('optimized', revsetlang.optimize),
2172 ('optimized', revsetlang.optimize),
2173 ]
2173 ]
2174 if opts['no_optimized']:
2174 if opts['no_optimized']:
2175 stages = stages[:-1]
2175 stages = stages[:-1]
2176 if opts['verify_optimized'] and opts['no_optimized']:
2176 if opts['verify_optimized'] and opts['no_optimized']:
2177 raise error.Abort(_('cannot use --verify-optimized with '
2177 raise error.Abort(_('cannot use --verify-optimized with '
2178 '--no-optimized'))
2178 '--no-optimized'))
2179 stagenames = set(n for n, f in stages)
2179 stagenames = set(n for n, f in stages)
2180
2180
2181 showalways = set()
2181 showalways = set()
2182 showchanged = set()
2182 showchanged = set()
2183 if ui.verbose and not opts['show_stage']:
2183 if ui.verbose and not opts['show_stage']:
2184 # show parsed tree by --verbose (deprecated)
2184 # show parsed tree by --verbose (deprecated)
2185 showalways.add('parsed')
2185 showalways.add('parsed')
2186 showchanged.update(['expanded', 'concatenated'])
2186 showchanged.update(['expanded', 'concatenated'])
2187 if opts['optimize']:
2187 if opts['optimize']:
2188 showalways.add('optimized')
2188 showalways.add('optimized')
2189 if opts['show_stage'] and opts['optimize']:
2189 if opts['show_stage'] and opts['optimize']:
2190 raise error.Abort(_('cannot use --optimize with --show-stage'))
2190 raise error.Abort(_('cannot use --optimize with --show-stage'))
2191 if opts['show_stage'] == ['all']:
2191 if opts['show_stage'] == ['all']:
2192 showalways.update(stagenames)
2192 showalways.update(stagenames)
2193 else:
2193 else:
2194 for n in opts['show_stage']:
2194 for n in opts['show_stage']:
2195 if n not in stagenames:
2195 if n not in stagenames:
2196 raise error.Abort(_('invalid stage name: %s') % n)
2196 raise error.Abort(_('invalid stage name: %s') % n)
2197 showalways.update(opts['show_stage'])
2197 showalways.update(opts['show_stage'])
2198
2198
2199 treebystage = {}
2199 treebystage = {}
2200 printedtree = None
2200 printedtree = None
2201 tree = revsetlang.parse(expr, lookup=repo.__contains__)
2201 tree = revsetlang.parse(expr, lookup=repo.__contains__)
2202 for n, f in stages:
2202 for n, f in stages:
2203 treebystage[n] = tree = f(tree)
2203 treebystage[n] = tree = f(tree)
2204 if n in showalways or (n in showchanged and tree != printedtree):
2204 if n in showalways or (n in showchanged and tree != printedtree):
2205 if opts['show_stage'] or n != 'parsed':
2205 if opts['show_stage'] or n != 'parsed':
2206 ui.write(("* %s:\n") % n)
2206 ui.write(("* %s:\n") % n)
2207 ui.write(revsetlang.prettyformat(tree), "\n")
2207 ui.write(revsetlang.prettyformat(tree), "\n")
2208 printedtree = tree
2208 printedtree = tree
2209
2209
2210 if opts['verify_optimized']:
2210 if opts['verify_optimized']:
2211 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2211 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2212 brevs = revset.makematcher(treebystage['optimized'])(repo)
2212 brevs = revset.makematcher(treebystage['optimized'])(repo)
2213 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2213 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2214 ui.write(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
2214 ui.write(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
2215 ui.write(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
2215 ui.write(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
2216 arevs = list(arevs)
2216 arevs = list(arevs)
2217 brevs = list(brevs)
2217 brevs = list(brevs)
2218 if arevs == brevs:
2218 if arevs == brevs:
2219 return 0
2219 return 0
2220 ui.write(('--- analyzed\n'), label='diff.file_a')
2220 ui.write(('--- analyzed\n'), label='diff.file_a')
2221 ui.write(('+++ optimized\n'), label='diff.file_b')
2221 ui.write(('+++ optimized\n'), label='diff.file_b')
2222 sm = difflib.SequenceMatcher(None, arevs, brevs)
2222 sm = difflib.SequenceMatcher(None, arevs, brevs)
2223 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2223 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2224 if tag in ('delete', 'replace'):
2224 if tag in ('delete', 'replace'):
2225 for c in arevs[alo:ahi]:
2225 for c in arevs[alo:ahi]:
2226 ui.write('-%s\n' % c, label='diff.deleted')
2226 ui.write('-%s\n' % c, label='diff.deleted')
2227 if tag in ('insert', 'replace'):
2227 if tag in ('insert', 'replace'):
2228 for c in brevs[blo:bhi]:
2228 for c in brevs[blo:bhi]:
2229 ui.write('+%s\n' % c, label='diff.inserted')
2229 ui.write('+%s\n' % c, label='diff.inserted')
2230 if tag == 'equal':
2230 if tag == 'equal':
2231 for c in arevs[alo:ahi]:
2231 for c in arevs[alo:ahi]:
2232 ui.write(' %s\n' % c)
2232 ui.write(' %s\n' % c)
2233 return 1
2233 return 1
2234
2234
2235 func = revset.makematcher(tree)
2235 func = revset.makematcher(tree)
2236 revs = func(repo)
2236 revs = func(repo)
2237 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2237 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2238 ui.write(("* set:\n"), smartset.prettyformat(revs), "\n")
2238 ui.write(("* set:\n"), smartset.prettyformat(revs), "\n")
2239 if not opts['show_revs']:
2239 if not opts['show_revs']:
2240 return
2240 return
2241 for c in revs:
2241 for c in revs:
2242 ui.write("%d\n" % c)
2242 ui.write("%d\n" % c)
2243
2243
2244 @command('debugserve', [
2244 @command('debugserve', [
2245 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2245 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2246 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2246 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2247 ('', 'logiofile', '', _('file to log server I/O to')),
2247 ('', 'logiofile', '', _('file to log server I/O to')),
2248 ], '')
2248 ], '')
2249 def debugserve(ui, repo, **opts):
2249 def debugserve(ui, repo, **opts):
2250 """run a server with advanced settings
2250 """run a server with advanced settings
2251
2251
2252 This command is similar to :hg:`serve`. It exists partially as a
2252 This command is similar to :hg:`serve`. It exists partially as a
2253 workaround to the fact that ``hg serve --stdio`` must have specific
2253 workaround to the fact that ``hg serve --stdio`` must have specific
2254 arguments for security reasons.
2254 arguments for security reasons.
2255 """
2255 """
2256 opts = pycompat.byteskwargs(opts)
2256 opts = pycompat.byteskwargs(opts)
2257
2257
2258 if not opts['sshstdio']:
2258 if not opts['sshstdio']:
2259 raise error.Abort(_('only --sshstdio is currently supported'))
2259 raise error.Abort(_('only --sshstdio is currently supported'))
2260
2260
2261 logfh = None
2261 logfh = None
2262
2262
2263 if opts['logiofd'] and opts['logiofile']:
2263 if opts['logiofd'] and opts['logiofile']:
2264 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2264 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2265
2265
2266 if opts['logiofd']:
2266 if opts['logiofd']:
2267 # Line buffered because output is line based.
2267 # Line buffered because output is line based.
2268 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2268 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2269 elif opts['logiofile']:
2269 elif opts['logiofile']:
2270 logfh = open(opts['logiofile'], 'ab', 1)
2270 logfh = open(opts['logiofile'], 'ab', 1)
2271
2271
2272 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2272 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2273 s.serve_forever()
2273 s.serve_forever()
2274
2274
2275 @command('debugsetparents', [], _('REV1 [REV2]'))
2275 @command('debugsetparents', [], _('REV1 [REV2]'))
2276 def debugsetparents(ui, repo, rev1, rev2=None):
2276 def debugsetparents(ui, repo, rev1, rev2=None):
2277 """manually set the parents of the current working directory
2277 """manually set the parents of the current working directory
2278
2278
2279 This is useful for writing repository conversion tools, but should
2279 This is useful for writing repository conversion tools, but should
2280 be used with care. For example, neither the working directory nor the
2280 be used with care. For example, neither the working directory nor the
2281 dirstate is updated, so file status may be incorrect after running this
2281 dirstate is updated, so file status may be incorrect after running this
2282 command.
2282 command.
2283
2283
2284 Returns 0 on success.
2284 Returns 0 on success.
2285 """
2285 """
2286
2286
2287 r1 = scmutil.revsingle(repo, rev1).node()
2287 r1 = scmutil.revsingle(repo, rev1).node()
2288 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2288 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2289
2289
2290 with repo.wlock():
2290 with repo.wlock():
2291 repo.setparents(r1, r2)
2291 repo.setparents(r1, r2)
2292
2292
2293 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2293 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2294 def debugssl(ui, repo, source=None, **opts):
2294 def debugssl(ui, repo, source=None, **opts):
2295 '''test a secure connection to a server
2295 '''test a secure connection to a server
2296
2296
2297 This builds the certificate chain for the server on Windows, installing the
2297 This builds the certificate chain for the server on Windows, installing the
2298 missing intermediates and trusted root via Windows Update if necessary. It
2298 missing intermediates and trusted root via Windows Update if necessary. It
2299 does nothing on other platforms.
2299 does nothing on other platforms.
2300
2300
2301 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2301 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2302 that server is used. See :hg:`help urls` for more information.
2302 that server is used. See :hg:`help urls` for more information.
2303
2303
2304 If the update succeeds, retry the original operation. Otherwise, the cause
2304 If the update succeeds, retry the original operation. Otherwise, the cause
2305 of the SSL error is likely another issue.
2305 of the SSL error is likely another issue.
2306 '''
2306 '''
2307 if not pycompat.iswindows:
2307 if not pycompat.iswindows:
2308 raise error.Abort(_('certificate chain building is only possible on '
2308 raise error.Abort(_('certificate chain building is only possible on '
2309 'Windows'))
2309 'Windows'))
2310
2310
2311 if not source:
2311 if not source:
2312 if not repo:
2312 if not repo:
2313 raise error.Abort(_("there is no Mercurial repository here, and no "
2313 raise error.Abort(_("there is no Mercurial repository here, and no "
2314 "server specified"))
2314 "server specified"))
2315 source = "default"
2315 source = "default"
2316
2316
2317 source, branches = hg.parseurl(ui.expandpath(source))
2317 source, branches = hg.parseurl(ui.expandpath(source))
2318 url = util.url(source)
2318 url = util.url(source)
2319 addr = None
2319 addr = None
2320
2320
2321 defaultport = {'https': 443, 'ssh': 22}
2321 defaultport = {'https': 443, 'ssh': 22}
2322 if url.scheme in defaultport:
2322 if url.scheme in defaultport:
2323 try:
2323 try:
2324 addr = (url.host, int(url.port or defaultport[url.scheme]))
2324 addr = (url.host, int(url.port or defaultport[url.scheme]))
2325 except ValueError:
2325 except ValueError:
2326 raise error.Abort(_("malformed port number in URL"))
2326 raise error.Abort(_("malformed port number in URL"))
2327 else:
2327 else:
2328 raise error.Abort(_("only https and ssh connections are supported"))
2328 raise error.Abort(_("only https and ssh connections are supported"))
2329
2329
2330 from . import win32
2330 from . import win32
2331
2331
2332 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2332 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2333 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2333 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2334
2334
2335 try:
2335 try:
2336 s.connect(addr)
2336 s.connect(addr)
2337 cert = s.getpeercert(True)
2337 cert = s.getpeercert(True)
2338
2338
2339 ui.status(_('checking the certificate chain for %s\n') % url.host)
2339 ui.status(_('checking the certificate chain for %s\n') % url.host)
2340
2340
2341 complete = win32.checkcertificatechain(cert, build=False)
2341 complete = win32.checkcertificatechain(cert, build=False)
2342
2342
2343 if not complete:
2343 if not complete:
2344 ui.status(_('certificate chain is incomplete, updating... '))
2344 ui.status(_('certificate chain is incomplete, updating... '))
2345
2345
2346 if not win32.checkcertificatechain(cert):
2346 if not win32.checkcertificatechain(cert):
2347 ui.status(_('failed.\n'))
2347 ui.status(_('failed.\n'))
2348 else:
2348 else:
2349 ui.status(_('done.\n'))
2349 ui.status(_('done.\n'))
2350 else:
2350 else:
2351 ui.status(_('full certificate chain is available\n'))
2351 ui.status(_('full certificate chain is available\n'))
2352 finally:
2352 finally:
2353 s.close()
2353 s.close()
2354
2354
2355 @command('debugsub',
2355 @command('debugsub',
2356 [('r', 'rev', '',
2356 [('r', 'rev', '',
2357 _('revision to check'), _('REV'))],
2357 _('revision to check'), _('REV'))],
2358 _('[-r REV] [REV]'))
2358 _('[-r REV] [REV]'))
2359 def debugsub(ui, repo, rev=None):
2359 def debugsub(ui, repo, rev=None):
2360 ctx = scmutil.revsingle(repo, rev, None)
2360 ctx = scmutil.revsingle(repo, rev, None)
2361 for k, v in sorted(ctx.substate.items()):
2361 for k, v in sorted(ctx.substate.items()):
2362 ui.write(('path %s\n') % k)
2362 ui.write(('path %s\n') % k)
2363 ui.write((' source %s\n') % v[0])
2363 ui.write((' source %s\n') % v[0])
2364 ui.write((' revision %s\n') % v[1])
2364 ui.write((' revision %s\n') % v[1])
2365
2365
2366 @command('debugsuccessorssets',
2366 @command('debugsuccessorssets',
2367 [('', 'closest', False, _('return closest successors sets only'))],
2367 [('', 'closest', False, _('return closest successors sets only'))],
2368 _('[REV]'))
2368 _('[REV]'))
2369 def debugsuccessorssets(ui, repo, *revs, **opts):
2369 def debugsuccessorssets(ui, repo, *revs, **opts):
2370 """show set of successors for revision
2370 """show set of successors for revision
2371
2371
2372 A successors set of changeset A is a consistent group of revisions that
2372 A successors set of changeset A is a consistent group of revisions that
2373 succeed A. It contains non-obsolete changesets only unless closests
2373 succeed A. It contains non-obsolete changesets only unless closests
2374 successors set is set.
2374 successors set is set.
2375
2375
2376 In most cases a changeset A has a single successors set containing a single
2376 In most cases a changeset A has a single successors set containing a single
2377 successor (changeset A replaced by A').
2377 successor (changeset A replaced by A').
2378
2378
2379 A changeset that is made obsolete with no successors are called "pruned".
2379 A changeset that is made obsolete with no successors are called "pruned".
2380 Such changesets have no successors sets at all.
2380 Such changesets have no successors sets at all.
2381
2381
2382 A changeset that has been "split" will have a successors set containing
2382 A changeset that has been "split" will have a successors set containing
2383 more than one successor.
2383 more than one successor.
2384
2384
2385 A changeset that has been rewritten in multiple different ways is called
2385 A changeset that has been rewritten in multiple different ways is called
2386 "divergent". Such changesets have multiple successor sets (each of which
2386 "divergent". Such changesets have multiple successor sets (each of which
2387 may also be split, i.e. have multiple successors).
2387 may also be split, i.e. have multiple successors).
2388
2388
2389 Results are displayed as follows::
2389 Results are displayed as follows::
2390
2390
2391 <rev1>
2391 <rev1>
2392 <successors-1A>
2392 <successors-1A>
2393 <rev2>
2393 <rev2>
2394 <successors-2A>
2394 <successors-2A>
2395 <successors-2B1> <successors-2B2> <successors-2B3>
2395 <successors-2B1> <successors-2B2> <successors-2B3>
2396
2396
2397 Here rev2 has two possible (i.e. divergent) successors sets. The first
2397 Here rev2 has two possible (i.e. divergent) successors sets. The first
2398 holds one element, whereas the second holds three (i.e. the changeset has
2398 holds one element, whereas the second holds three (i.e. the changeset has
2399 been split).
2399 been split).
2400 """
2400 """
2401 # passed to successorssets caching computation from one call to another
2401 # passed to successorssets caching computation from one call to another
2402 cache = {}
2402 cache = {}
2403 ctx2str = bytes
2403 ctx2str = bytes
2404 node2str = short
2404 node2str = short
2405 for rev in scmutil.revrange(repo, revs):
2405 for rev in scmutil.revrange(repo, revs):
2406 ctx = repo[rev]
2406 ctx = repo[rev]
2407 ui.write('%s\n'% ctx2str(ctx))
2407 ui.write('%s\n'% ctx2str(ctx))
2408 for succsset in obsutil.successorssets(repo, ctx.node(),
2408 for succsset in obsutil.successorssets(repo, ctx.node(),
2409 closest=opts[r'closest'],
2409 closest=opts[r'closest'],
2410 cache=cache):
2410 cache=cache):
2411 if succsset:
2411 if succsset:
2412 ui.write(' ')
2412 ui.write(' ')
2413 ui.write(node2str(succsset[0]))
2413 ui.write(node2str(succsset[0]))
2414 for node in succsset[1:]:
2414 for node in succsset[1:]:
2415 ui.write(' ')
2415 ui.write(' ')
2416 ui.write(node2str(node))
2416 ui.write(node2str(node))
2417 ui.write('\n')
2417 ui.write('\n')
2418
2418
2419 @command('debugtemplate',
2419 @command('debugtemplate',
2420 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2420 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2421 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2421 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2422 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2422 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2423 optionalrepo=True)
2423 optionalrepo=True)
2424 def debugtemplate(ui, repo, tmpl, **opts):
2424 def debugtemplate(ui, repo, tmpl, **opts):
2425 """parse and apply a template
2425 """parse and apply a template
2426
2426
2427 If -r/--rev is given, the template is processed as a log template and
2427 If -r/--rev is given, the template is processed as a log template and
2428 applied to the given changesets. Otherwise, it is processed as a generic
2428 applied to the given changesets. Otherwise, it is processed as a generic
2429 template.
2429 template.
2430
2430
2431 Use --verbose to print the parsed tree.
2431 Use --verbose to print the parsed tree.
2432 """
2432 """
2433 revs = None
2433 revs = None
2434 if opts[r'rev']:
2434 if opts[r'rev']:
2435 if repo is None:
2435 if repo is None:
2436 raise error.RepoError(_('there is no Mercurial repository here '
2436 raise error.RepoError(_('there is no Mercurial repository here '
2437 '(.hg not found)'))
2437 '(.hg not found)'))
2438 revs = scmutil.revrange(repo, opts[r'rev'])
2438 revs = scmutil.revrange(repo, opts[r'rev'])
2439
2439
2440 props = {}
2440 props = {}
2441 for d in opts[r'define']:
2441 for d in opts[r'define']:
2442 try:
2442 try:
2443 k, v = (e.strip() for e in d.split('=', 1))
2443 k, v = (e.strip() for e in d.split('=', 1))
2444 if not k or k == 'ui':
2444 if not k or k == 'ui':
2445 raise ValueError
2445 raise ValueError
2446 props[k] = v
2446 props[k] = v
2447 except ValueError:
2447 except ValueError:
2448 raise error.Abort(_('malformed keyword definition: %s') % d)
2448 raise error.Abort(_('malformed keyword definition: %s') % d)
2449
2449
2450 if ui.verbose:
2450 if ui.verbose:
2451 aliases = ui.configitems('templatealias')
2451 aliases = ui.configitems('templatealias')
2452 tree = templater.parse(tmpl)
2452 tree = templater.parse(tmpl)
2453 ui.note(templater.prettyformat(tree), '\n')
2453 ui.note(templater.prettyformat(tree), '\n')
2454 newtree = templater.expandaliases(tree, aliases)
2454 newtree = templater.expandaliases(tree, aliases)
2455 if newtree != tree:
2455 if newtree != tree:
2456 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2456 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2457
2457
2458 if revs is None:
2458 if revs is None:
2459 tres = formatter.templateresources(ui, repo)
2459 tres = formatter.templateresources(ui, repo)
2460 t = formatter.maketemplater(ui, tmpl, resources=tres)
2460 t = formatter.maketemplater(ui, tmpl, resources=tres)
2461 ui.write(t.renderdefault(props))
2461 ui.write(t.renderdefault(props))
2462 else:
2462 else:
2463 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2463 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2464 for r in revs:
2464 for r in revs:
2465 displayer.show(repo[r], **pycompat.strkwargs(props))
2465 displayer.show(repo[r], **pycompat.strkwargs(props))
2466 displayer.close()
2466 displayer.close()
2467
2467
2468 @command('debuguigetpass', [
2468 @command('debuguigetpass', [
2469 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2469 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2470 ], _('[-p TEXT]'), norepo=True)
2470 ], _('[-p TEXT]'), norepo=True)
2471 def debuguigetpass(ui, prompt=''):
2471 def debuguigetpass(ui, prompt=''):
2472 """show prompt to type password"""
2472 """show prompt to type password"""
2473 r = ui.getpass(prompt)
2473 r = ui.getpass(prompt)
2474 ui.write(('respose: %s\n') % r)
2474 ui.write(('respose: %s\n') % r)
2475
2475
2476 @command('debuguiprompt', [
2476 @command('debuguiprompt', [
2477 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2477 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2478 ], _('[-p TEXT]'), norepo=True)
2478 ], _('[-p TEXT]'), norepo=True)
2479 def debuguiprompt(ui, prompt=''):
2479 def debuguiprompt(ui, prompt=''):
2480 """show plain prompt"""
2480 """show plain prompt"""
2481 r = ui.prompt(prompt)
2481 r = ui.prompt(prompt)
2482 ui.write(('response: %s\n') % r)
2482 ui.write(('response: %s\n') % r)
2483
2483
2484 @command('debugupdatecaches', [])
2484 @command('debugupdatecaches', [])
2485 def debugupdatecaches(ui, repo, *pats, **opts):
2485 def debugupdatecaches(ui, repo, *pats, **opts):
2486 """warm all known caches in the repository"""
2486 """warm all known caches in the repository"""
2487 with repo.wlock(), repo.lock():
2487 with repo.wlock(), repo.lock():
2488 repo.updatecaches(full=True)
2488 repo.updatecaches(full=True)
2489
2489
2490 @command('debugupgraderepo', [
2490 @command('debugupgraderepo', [
2491 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2491 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2492 ('', 'run', False, _('performs an upgrade')),
2492 ('', 'run', False, _('performs an upgrade')),
2493 ])
2493 ])
2494 def debugupgraderepo(ui, repo, run=False, optimize=None):
2494 def debugupgraderepo(ui, repo, run=False, optimize=None):
2495 """upgrade a repository to use different features
2495 """upgrade a repository to use different features
2496
2496
2497 If no arguments are specified, the repository is evaluated for upgrade
2497 If no arguments are specified, the repository is evaluated for upgrade
2498 and a list of problems and potential optimizations is printed.
2498 and a list of problems and potential optimizations is printed.
2499
2499
2500 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2500 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2501 can be influenced via additional arguments. More details will be provided
2501 can be influenced via additional arguments. More details will be provided
2502 by the command output when run without ``--run``.
2502 by the command output when run without ``--run``.
2503
2503
2504 During the upgrade, the repository will be locked and no writes will be
2504 During the upgrade, the repository will be locked and no writes will be
2505 allowed.
2505 allowed.
2506
2506
2507 At the end of the upgrade, the repository may not be readable while new
2507 At the end of the upgrade, the repository may not be readable while new
2508 repository data is swapped in. This window will be as long as it takes to
2508 repository data is swapped in. This window will be as long as it takes to
2509 rename some directories inside the ``.hg`` directory. On most machines, this
2509 rename some directories inside the ``.hg`` directory. On most machines, this
2510 should complete almost instantaneously and the chances of a consumer being
2510 should complete almost instantaneously and the chances of a consumer being
2511 unable to access the repository should be low.
2511 unable to access the repository should be low.
2512 """
2512 """
2513 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2513 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2514
2514
2515 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2515 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2516 inferrepo=True)
2516 inferrepo=True)
2517 def debugwalk(ui, repo, *pats, **opts):
2517 def debugwalk(ui, repo, *pats, **opts):
2518 """show how files match on given patterns"""
2518 """show how files match on given patterns"""
2519 opts = pycompat.byteskwargs(opts)
2519 opts = pycompat.byteskwargs(opts)
2520 m = scmutil.match(repo[None], pats, opts)
2520 m = scmutil.match(repo[None], pats, opts)
2521 ui.write(('matcher: %r\n' % m))
2521 ui.write(('matcher: %r\n' % m))
2522 items = list(repo[None].walk(m))
2522 items = list(repo[None].walk(m))
2523 if not items:
2523 if not items:
2524 return
2524 return
2525 f = lambda fn: fn
2525 f = lambda fn: fn
2526 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2526 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2527 f = lambda fn: util.normpath(fn)
2527 f = lambda fn: util.normpath(fn)
2528 fmt = 'f %%-%ds %%-%ds %%s' % (
2528 fmt = 'f %%-%ds %%-%ds %%s' % (
2529 max([len(abs) for abs in items]),
2529 max([len(abs) for abs in items]),
2530 max([len(m.rel(abs)) for abs in items]))
2530 max([len(m.rel(abs)) for abs in items]))
2531 for abs in items:
2531 for abs in items:
2532 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2532 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2533 ui.write("%s\n" % line.rstrip())
2533 ui.write("%s\n" % line.rstrip())
2534
2534
2535 @command('debugwhyunstable', [], _('REV'))
2535 @command('debugwhyunstable', [], _('REV'))
2536 def debugwhyunstable(ui, repo, rev):
2536 def debugwhyunstable(ui, repo, rev):
2537 """explain instabilities of a changeset"""
2537 """explain instabilities of a changeset"""
2538 for entry in obsutil.whyunstable(repo, repo[rev]):
2538 for entry in obsutil.whyunstable(repo, repo[rev]):
2539 dnodes = ''
2539 dnodes = ''
2540 if entry.get('divergentnodes'):
2540 if entry.get('divergentnodes'):
2541 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2541 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2542 for ctx in entry['divergentnodes']) + ' '
2542 for ctx in entry['divergentnodes']) + ' '
2543 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2543 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2544 entry['reason'], entry['node']))
2544 entry['reason'], entry['node']))
2545
2545
2546 @command('debugwireargs',
2546 @command('debugwireargs',
2547 [('', 'three', '', 'three'),
2547 [('', 'three', '', 'three'),
2548 ('', 'four', '', 'four'),
2548 ('', 'four', '', 'four'),
2549 ('', 'five', '', 'five'),
2549 ('', 'five', '', 'five'),
2550 ] + cmdutil.remoteopts,
2550 ] + cmdutil.remoteopts,
2551 _('REPO [OPTIONS]... [ONE [TWO]]'),
2551 _('REPO [OPTIONS]... [ONE [TWO]]'),
2552 norepo=True)
2552 norepo=True)
2553 def debugwireargs(ui, repopath, *vals, **opts):
2553 def debugwireargs(ui, repopath, *vals, **opts):
2554 opts = pycompat.byteskwargs(opts)
2554 opts = pycompat.byteskwargs(opts)
2555 repo = hg.peer(ui, opts, repopath)
2555 repo = hg.peer(ui, opts, repopath)
2556 for opt in cmdutil.remoteopts:
2556 for opt in cmdutil.remoteopts:
2557 del opts[opt[1]]
2557 del opts[opt[1]]
2558 args = {}
2558 args = {}
2559 for k, v in opts.iteritems():
2559 for k, v in opts.iteritems():
2560 if v:
2560 if v:
2561 args[k] = v
2561 args[k] = v
2562 args = pycompat.strkwargs(args)
2562 args = pycompat.strkwargs(args)
2563 # run twice to check that we don't mess up the stream for the next command
2563 # run twice to check that we don't mess up the stream for the next command
2564 res1 = repo.debugwireargs(*vals, **args)
2564 res1 = repo.debugwireargs(*vals, **args)
2565 res2 = repo.debugwireargs(*vals, **args)
2565 res2 = repo.debugwireargs(*vals, **args)
2566 ui.write("%s\n" % res1)
2566 ui.write("%s\n" % res1)
2567 if res1 != res2:
2567 if res1 != res2:
2568 ui.warn("%s\n" % res2)
2568 ui.warn("%s\n" % res2)
2569
2569
2570 def _parsewirelangblocks(fh):
2570 def _parsewirelangblocks(fh):
2571 activeaction = None
2571 activeaction = None
2572 blocklines = []
2572 blocklines = []
2573
2573
2574 for line in fh:
2574 for line in fh:
2575 line = line.rstrip()
2575 line = line.rstrip()
2576 if not line:
2576 if not line:
2577 continue
2577 continue
2578
2578
2579 if line.startswith(b'#'):
2579 if line.startswith(b'#'):
2580 continue
2580 continue
2581
2581
2582 if not line.startswith(' '):
2582 if not line.startswith(' '):
2583 # New block. Flush previous one.
2583 # New block. Flush previous one.
2584 if activeaction:
2584 if activeaction:
2585 yield activeaction, blocklines
2585 yield activeaction, blocklines
2586
2586
2587 activeaction = line
2587 activeaction = line
2588 blocklines = []
2588 blocklines = []
2589 continue
2589 continue
2590
2590
2591 # Else we start with an indent.
2591 # Else we start with an indent.
2592
2592
2593 if not activeaction:
2593 if not activeaction:
2594 raise error.Abort(_('indented line outside of block'))
2594 raise error.Abort(_('indented line outside of block'))
2595
2595
2596 blocklines.append(line)
2596 blocklines.append(line)
2597
2597
2598 # Flush last block.
2598 # Flush last block.
2599 if activeaction:
2599 if activeaction:
2600 yield activeaction, blocklines
2600 yield activeaction, blocklines
2601
2601
2602 @command('debugwireproto',
2602 @command('debugwireproto',
2603 [
2603 [
2604 ('', 'localssh', False, _('start an SSH server for this repo')),
2604 ('', 'localssh', False, _('start an SSH server for this repo')),
2605 ('', 'peer', '', _('construct a specific version of the peer')),
2605 ('', 'peer', '', _('construct a specific version of the peer')),
2606 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2606 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2607 ] + cmdutil.remoteopts,
2607 ] + cmdutil.remoteopts,
2608 _('[PATH]'),
2608 _('[PATH]'),
2609 optionalrepo=True)
2609 optionalrepo=True)
2610 def debugwireproto(ui, repo, path=None, **opts):
2610 def debugwireproto(ui, repo, path=None, **opts):
2611 """send wire protocol commands to a server
2611 """send wire protocol commands to a server
2612
2612
2613 This command can be used to issue wire protocol commands to remote
2613 This command can be used to issue wire protocol commands to remote
2614 peers and to debug the raw data being exchanged.
2614 peers and to debug the raw data being exchanged.
2615
2615
2616 ``--localssh`` will start an SSH server against the current repository
2616 ``--localssh`` will start an SSH server against the current repository
2617 and connect to that. By default, the connection will perform a handshake
2617 and connect to that. By default, the connection will perform a handshake
2618 and establish an appropriate peer instance.
2618 and establish an appropriate peer instance.
2619
2619
2620 ``--peer`` can be used to bypass the handshake protocol and construct a
2620 ``--peer`` can be used to bypass the handshake protocol and construct a
2621 peer instance using the specified class type. Valid values are ``raw``,
2621 peer instance using the specified class type. Valid values are ``raw``,
2622 ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data
2622 ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data
2623 payloads and don't support higher-level command actions.
2623 payloads and don't support higher-level command actions.
2624
2624
2625 ``--noreadstderr`` can be used to disable automatic reading from stderr
2625 ``--noreadstderr`` can be used to disable automatic reading from stderr
2626 of the peer (for SSH connections only). Disabling automatic reading of
2626 of the peer (for SSH connections only). Disabling automatic reading of
2627 stderr is useful for making output more deterministic.
2627 stderr is useful for making output more deterministic.
2628
2628
2629 Commands are issued via a mini language which is specified via stdin.
2629 Commands are issued via a mini language which is specified via stdin.
2630 The language consists of individual actions to perform. An action is
2630 The language consists of individual actions to perform. An action is
2631 defined by a block. A block is defined as a line with no leading
2631 defined by a block. A block is defined as a line with no leading
2632 space followed by 0 or more lines with leading space. Blocks are
2632 space followed by 0 or more lines with leading space. Blocks are
2633 effectively a high-level command with additional metadata.
2633 effectively a high-level command with additional metadata.
2634
2634
2635 Lines beginning with ``#`` are ignored.
2635 Lines beginning with ``#`` are ignored.
2636
2636
2637 The following sections denote available actions.
2637 The following sections denote available actions.
2638
2638
2639 raw
2639 raw
2640 ---
2640 ---
2641
2641
2642 Send raw data to the server.
2642 Send raw data to the server.
2643
2643
2644 The block payload contains the raw data to send as one atomic send
2644 The block payload contains the raw data to send as one atomic send
2645 operation. The data may not actually be delivered in a single system
2645 operation. The data may not actually be delivered in a single system
2646 call: it depends on the abilities of the transport being used.
2646 call: it depends on the abilities of the transport being used.
2647
2647
2648 Each line in the block is de-indented and concatenated. Then, that
2648 Each line in the block is de-indented and concatenated. Then, that
2649 value is evaluated as a Python b'' literal. This allows the use of
2649 value is evaluated as a Python b'' literal. This allows the use of
2650 backslash escaping, etc.
2650 backslash escaping, etc.
2651
2651
2652 raw+
2652 raw+
2653 ----
2653 ----
2654
2654
2655 Behaves like ``raw`` except flushes output afterwards.
2655 Behaves like ``raw`` except flushes output afterwards.
2656
2656
2657 command <X>
2657 command <X>
2658 -----------
2658 -----------
2659
2659
2660 Send a request to run a named command, whose name follows the ``command``
2660 Send a request to run a named command, whose name follows the ``command``
2661 string.
2661 string.
2662
2662
2663 Arguments to the command are defined as lines in this block. The format of
2663 Arguments to the command are defined as lines in this block. The format of
2664 each line is ``<key> <value>``. e.g.::
2664 each line is ``<key> <value>``. e.g.::
2665
2665
2666 command listkeys
2666 command listkeys
2667 namespace bookmarks
2667 namespace bookmarks
2668
2668
2669 Values are interpreted as Python b'' literals. This allows encoding
2669 Values are interpreted as Python b'' literals. This allows encoding
2670 special byte sequences via backslash escaping.
2670 special byte sequences via backslash escaping.
2671
2671
2672 The following arguments have special meaning:
2672 The following arguments have special meaning:
2673
2673
2674 ``PUSHFILE``
2674 ``PUSHFILE``
2675 When defined, the *push* mechanism of the peer will be used instead
2675 When defined, the *push* mechanism of the peer will be used instead
2676 of the static request-response mechanism and the content of the
2676 of the static request-response mechanism and the content of the
2677 file specified in the value of this argument will be sent as the
2677 file specified in the value of this argument will be sent as the
2678 command payload.
2678 command payload.
2679
2679
2680 This can be used to submit a local bundle file to the remote.
2680 This can be used to submit a local bundle file to the remote.
2681
2681
2682 batchbegin
2682 batchbegin
2683 ----------
2683 ----------
2684
2684
2685 Instruct the peer to begin a batched send.
2685 Instruct the peer to begin a batched send.
2686
2686
2687 All ``command`` blocks are queued for execution until the next
2687 All ``command`` blocks are queued for execution until the next
2688 ``batchsubmit`` block.
2688 ``batchsubmit`` block.
2689
2689
2690 batchsubmit
2690 batchsubmit
2691 -----------
2691 -----------
2692
2692
2693 Submit previously queued ``command`` blocks as a batch request.
2693 Submit previously queued ``command`` blocks as a batch request.
2694
2694
2695 This action MUST be paired with a ``batchbegin`` action.
2695 This action MUST be paired with a ``batchbegin`` action.
2696
2696
2697 httprequest <method> <path>
2697 httprequest <method> <path>
2698 ---------------------------
2698 ---------------------------
2699
2699
2700 (HTTP peer only)
2700 (HTTP peer only)
2701
2701
2702 Send an HTTP request to the peer.
2702 Send an HTTP request to the peer.
2703
2703
2704 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2704 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2705
2705
2706 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2706 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2707 headers to add to the request. e.g. ``Accept: foo``.
2707 headers to add to the request. e.g. ``Accept: foo``.
2708
2708
2709 The following arguments are special:
2709 The following arguments are special:
2710
2710
2711 ``BODYFILE``
2711 ``BODYFILE``
2712 The content of the file defined as the value to this argument will be
2712 The content of the file defined as the value to this argument will be
2713 transferred verbatim as the HTTP request body.
2713 transferred verbatim as the HTTP request body.
2714
2714
2715 ``frame <type> <flags> <payload>``
2715 ``frame <type> <flags> <payload>``
2716 Send a unified protocol frame as part of the request body.
2716 Send a unified protocol frame as part of the request body.
2717
2717
2718 All frames will be collected and sent as the body to the HTTP
2718 All frames will be collected and sent as the body to the HTTP
2719 request.
2719 request.
2720
2720
2721 close
2721 close
2722 -----
2722 -----
2723
2723
2724 Close the connection to the server.
2724 Close the connection to the server.
2725
2725
2726 flush
2726 flush
2727 -----
2727 -----
2728
2728
2729 Flush data written to the server.
2729 Flush data written to the server.
2730
2730
2731 readavailable
2731 readavailable
2732 -------------
2732 -------------
2733
2733
2734 Close the write end of the connection and read all available data from
2734 Close the write end of the connection and read all available data from
2735 the server.
2735 the server.
2736
2736
2737 If the connection to the server encompasses multiple pipes, we poll both
2737 If the connection to the server encompasses multiple pipes, we poll both
2738 pipes and read available data.
2738 pipes and read available data.
2739
2739
2740 readline
2740 readline
2741 --------
2741 --------
2742
2742
2743 Read a line of output from the server. If there are multiple output
2743 Read a line of output from the server. If there are multiple output
2744 pipes, reads only the main pipe.
2744 pipes, reads only the main pipe.
2745
2745
2746 ereadline
2746 ereadline
2747 ---------
2747 ---------
2748
2748
2749 Like ``readline``, but read from the stderr pipe, if available.
2749 Like ``readline``, but read from the stderr pipe, if available.
2750
2750
2751 read <X>
2751 read <X>
2752 --------
2752 --------
2753
2753
2754 ``read()`` N bytes from the server's main output pipe.
2754 ``read()`` N bytes from the server's main output pipe.
2755
2755
2756 eread <X>
2756 eread <X>
2757 ---------
2757 ---------
2758
2758
2759 ``read()`` N bytes from the server's stderr pipe, if available.
2759 ``read()`` N bytes from the server's stderr pipe, if available.
2760
2760
2761 Specifying Unified Frame-Based Protocol Frames
2761 Specifying Unified Frame-Based Protocol Frames
2762 ----------------------------------------------
2762 ----------------------------------------------
2763
2763
2764 It is possible to emit a *Unified Frame-Based Protocol* by using special
2764 It is possible to emit a *Unified Frame-Based Protocol* by using special
2765 syntax.
2765 syntax.
2766
2766
2767 A frame is composed as a type, flags, and payload. These can be parsed
2767 A frame is composed as a type, flags, and payload. These can be parsed
2768 from a string of the form ``<type> <flags> <payload>``. That is, 3
2768 from a string of the form ``<requestid> <type> <flags> <payload>``. That is,
2769 space-delimited strings.
2769 4 space-delimited strings.
2770
2770
2771 ``payload`` is the simplest: it is evaluated as a Python byte string
2771 ``payload`` is the simplest: it is evaluated as a Python byte string
2772 literal.
2772 literal.
2773
2773
2774 ``requestid`` is an integer defining the request identifier.
2775
2774 ``type`` can be an integer value for the frame type or the string name
2776 ``type`` can be an integer value for the frame type or the string name
2775 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
2777 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
2776 ``command-name``.
2778 ``command-name``.
2777
2779
2778 ``flags`` is a ``|`` delimited list of flag components. Each component
2780 ``flags`` is a ``|`` delimited list of flag components. Each component
2779 (and there can be just one) can be an integer or a flag name for the
2781 (and there can be just one) can be an integer or a flag name for the
2780 specified frame type. Values are resolved to integers and then bitwise
2782 specified frame type. Values are resolved to integers and then bitwise
2781 OR'd together.
2783 OR'd together.
2782 """
2784 """
2783 opts = pycompat.byteskwargs(opts)
2785 opts = pycompat.byteskwargs(opts)
2784
2786
2785 if opts['localssh'] and not repo:
2787 if opts['localssh'] and not repo:
2786 raise error.Abort(_('--localssh requires a repository'))
2788 raise error.Abort(_('--localssh requires a repository'))
2787
2789
2788 if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'):
2790 if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'):
2789 raise error.Abort(_('invalid value for --peer'),
2791 raise error.Abort(_('invalid value for --peer'),
2790 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2792 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2791
2793
2792 if path and opts['localssh']:
2794 if path and opts['localssh']:
2793 raise error.Abort(_('cannot specify --localssh with an explicit '
2795 raise error.Abort(_('cannot specify --localssh with an explicit '
2794 'path'))
2796 'path'))
2795
2797
2796 if ui.interactive():
2798 if ui.interactive():
2797 ui.write(_('(waiting for commands on stdin)\n'))
2799 ui.write(_('(waiting for commands on stdin)\n'))
2798
2800
2799 blocks = list(_parsewirelangblocks(ui.fin))
2801 blocks = list(_parsewirelangblocks(ui.fin))
2800
2802
2801 proc = None
2803 proc = None
2802 stdin = None
2804 stdin = None
2803 stdout = None
2805 stdout = None
2804 stderr = None
2806 stderr = None
2805 opener = None
2807 opener = None
2806
2808
2807 if opts['localssh']:
2809 if opts['localssh']:
2808 # We start the SSH server in its own process so there is process
2810 # We start the SSH server in its own process so there is process
2809 # separation. This prevents a whole class of potential bugs around
2811 # separation. This prevents a whole class of potential bugs around
2810 # shared state from interfering with server operation.
2812 # shared state from interfering with server operation.
2811 args = util.hgcmd() + [
2813 args = util.hgcmd() + [
2812 '-R', repo.root,
2814 '-R', repo.root,
2813 'debugserve', '--sshstdio',
2815 'debugserve', '--sshstdio',
2814 ]
2816 ]
2815 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
2817 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
2816 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
2818 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
2817 bufsize=0)
2819 bufsize=0)
2818
2820
2819 stdin = proc.stdin
2821 stdin = proc.stdin
2820 stdout = proc.stdout
2822 stdout = proc.stdout
2821 stderr = proc.stderr
2823 stderr = proc.stderr
2822
2824
2823 # We turn the pipes into observers so we can log I/O.
2825 # We turn the pipes into observers so we can log I/O.
2824 if ui.verbose or opts['peer'] == 'raw':
2826 if ui.verbose or opts['peer'] == 'raw':
2825 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
2827 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
2826 logdata=True)
2828 logdata=True)
2827 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
2829 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
2828 logdata=True)
2830 logdata=True)
2829 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
2831 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
2830 logdata=True)
2832 logdata=True)
2831
2833
2832 # --localssh also implies the peer connection settings.
2834 # --localssh also implies the peer connection settings.
2833
2835
2834 url = 'ssh://localserver'
2836 url = 'ssh://localserver'
2835 autoreadstderr = not opts['noreadstderr']
2837 autoreadstderr = not opts['noreadstderr']
2836
2838
2837 if opts['peer'] == 'ssh1':
2839 if opts['peer'] == 'ssh1':
2838 ui.write(_('creating ssh peer for wire protocol version 1\n'))
2840 ui.write(_('creating ssh peer for wire protocol version 1\n'))
2839 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
2841 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
2840 None, autoreadstderr=autoreadstderr)
2842 None, autoreadstderr=autoreadstderr)
2841 elif opts['peer'] == 'ssh2':
2843 elif opts['peer'] == 'ssh2':
2842 ui.write(_('creating ssh peer for wire protocol version 2\n'))
2844 ui.write(_('creating ssh peer for wire protocol version 2\n'))
2843 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
2845 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
2844 None, autoreadstderr=autoreadstderr)
2846 None, autoreadstderr=autoreadstderr)
2845 elif opts['peer'] == 'raw':
2847 elif opts['peer'] == 'raw':
2846 ui.write(_('using raw connection to peer\n'))
2848 ui.write(_('using raw connection to peer\n'))
2847 peer = None
2849 peer = None
2848 else:
2850 else:
2849 ui.write(_('creating ssh peer from handshake results\n'))
2851 ui.write(_('creating ssh peer from handshake results\n'))
2850 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2852 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2851 autoreadstderr=autoreadstderr)
2853 autoreadstderr=autoreadstderr)
2852
2854
2853 elif path:
2855 elif path:
2854 # We bypass hg.peer() so we can proxy the sockets.
2856 # We bypass hg.peer() so we can proxy the sockets.
2855 # TODO consider not doing this because we skip
2857 # TODO consider not doing this because we skip
2856 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
2858 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
2857 u = util.url(path)
2859 u = util.url(path)
2858 if u.scheme != 'http':
2860 if u.scheme != 'http':
2859 raise error.Abort(_('only http:// paths are currently supported'))
2861 raise error.Abort(_('only http:// paths are currently supported'))
2860
2862
2861 url, authinfo = u.authinfo()
2863 url, authinfo = u.authinfo()
2862 openerargs = {}
2864 openerargs = {}
2863
2865
2864 # Turn pipes/sockets into observers so we can log I/O.
2866 # Turn pipes/sockets into observers so we can log I/O.
2865 if ui.verbose:
2867 if ui.verbose:
2866 openerargs = {
2868 openerargs = {
2867 r'loggingfh': ui,
2869 r'loggingfh': ui,
2868 r'loggingname': b's',
2870 r'loggingname': b's',
2869 r'loggingopts': {
2871 r'loggingopts': {
2870 r'logdata': True,
2872 r'logdata': True,
2871 r'logdataapis': False,
2873 r'logdataapis': False,
2872 },
2874 },
2873 }
2875 }
2874
2876
2875 if ui.debugflag:
2877 if ui.debugflag:
2876 openerargs[r'loggingopts'][r'logdataapis'] = True
2878 openerargs[r'loggingopts'][r'logdataapis'] = True
2877
2879
2878 # Don't send default headers when in raw mode. This allows us to
2880 # Don't send default headers when in raw mode. This allows us to
2879 # bypass most of the behavior of our URL handling code so we can
2881 # bypass most of the behavior of our URL handling code so we can
2880 # have near complete control over what's sent on the wire.
2882 # have near complete control over what's sent on the wire.
2881 if opts['peer'] == 'raw':
2883 if opts['peer'] == 'raw':
2882 openerargs[r'sendaccept'] = False
2884 openerargs[r'sendaccept'] = False
2883
2885
2884 opener = urlmod.opener(ui, authinfo, **openerargs)
2886 opener = urlmod.opener(ui, authinfo, **openerargs)
2885
2887
2886 if opts['peer'] == 'raw':
2888 if opts['peer'] == 'raw':
2887 ui.write(_('using raw connection to peer\n'))
2889 ui.write(_('using raw connection to peer\n'))
2888 peer = None
2890 peer = None
2889 elif opts['peer']:
2891 elif opts['peer']:
2890 raise error.Abort(_('--peer %s not supported with HTTP peers') %
2892 raise error.Abort(_('--peer %s not supported with HTTP peers') %
2891 opts['peer'])
2893 opts['peer'])
2892 else:
2894 else:
2893 peer = httppeer.httppeer(ui, path, url, opener)
2895 peer = httppeer.httppeer(ui, path, url, opener)
2894 peer._fetchcaps()
2896 peer._fetchcaps()
2895
2897
2896 # We /could/ populate stdin/stdout with sock.makefile()...
2898 # We /could/ populate stdin/stdout with sock.makefile()...
2897 else:
2899 else:
2898 raise error.Abort(_('unsupported connection configuration'))
2900 raise error.Abort(_('unsupported connection configuration'))
2899
2901
2900 batchedcommands = None
2902 batchedcommands = None
2901
2903
2902 # Now perform actions based on the parsed wire language instructions.
2904 # Now perform actions based on the parsed wire language instructions.
2903 for action, lines in blocks:
2905 for action, lines in blocks:
2904 if action in ('raw', 'raw+'):
2906 if action in ('raw', 'raw+'):
2905 if not stdin:
2907 if not stdin:
2906 raise error.Abort(_('cannot call raw/raw+ on this peer'))
2908 raise error.Abort(_('cannot call raw/raw+ on this peer'))
2907
2909
2908 # Concatenate the data together.
2910 # Concatenate the data together.
2909 data = ''.join(l.lstrip() for l in lines)
2911 data = ''.join(l.lstrip() for l in lines)
2910 data = util.unescapestr(data)
2912 data = util.unescapestr(data)
2911 stdin.write(data)
2913 stdin.write(data)
2912
2914
2913 if action == 'raw+':
2915 if action == 'raw+':
2914 stdin.flush()
2916 stdin.flush()
2915 elif action == 'flush':
2917 elif action == 'flush':
2916 if not stdin:
2918 if not stdin:
2917 raise error.Abort(_('cannot call flush on this peer'))
2919 raise error.Abort(_('cannot call flush on this peer'))
2918 stdin.flush()
2920 stdin.flush()
2919 elif action.startswith('command'):
2921 elif action.startswith('command'):
2920 if not peer:
2922 if not peer:
2921 raise error.Abort(_('cannot send commands unless peer instance '
2923 raise error.Abort(_('cannot send commands unless peer instance '
2922 'is available'))
2924 'is available'))
2923
2925
2924 command = action.split(' ', 1)[1]
2926 command = action.split(' ', 1)[1]
2925
2927
2926 args = {}
2928 args = {}
2927 for line in lines:
2929 for line in lines:
2928 # We need to allow empty values.
2930 # We need to allow empty values.
2929 fields = line.lstrip().split(' ', 1)
2931 fields = line.lstrip().split(' ', 1)
2930 if len(fields) == 1:
2932 if len(fields) == 1:
2931 key = fields[0]
2933 key = fields[0]
2932 value = ''
2934 value = ''
2933 else:
2935 else:
2934 key, value = fields
2936 key, value = fields
2935
2937
2936 args[key] = util.unescapestr(value)
2938 args[key] = util.unescapestr(value)
2937
2939
2938 if batchedcommands is not None:
2940 if batchedcommands is not None:
2939 batchedcommands.append((command, args))
2941 batchedcommands.append((command, args))
2940 continue
2942 continue
2941
2943
2942 ui.status(_('sending %s command\n') % command)
2944 ui.status(_('sending %s command\n') % command)
2943
2945
2944 if 'PUSHFILE' in args:
2946 if 'PUSHFILE' in args:
2945 with open(args['PUSHFILE'], r'rb') as fh:
2947 with open(args['PUSHFILE'], r'rb') as fh:
2946 del args['PUSHFILE']
2948 del args['PUSHFILE']
2947 res, output = peer._callpush(command, fh,
2949 res, output = peer._callpush(command, fh,
2948 **pycompat.strkwargs(args))
2950 **pycompat.strkwargs(args))
2949 ui.status(_('result: %s\n') % util.escapedata(res))
2951 ui.status(_('result: %s\n') % util.escapedata(res))
2950 ui.status(_('remote output: %s\n') %
2952 ui.status(_('remote output: %s\n') %
2951 util.escapedata(output))
2953 util.escapedata(output))
2952 else:
2954 else:
2953 res = peer._call(command, **pycompat.strkwargs(args))
2955 res = peer._call(command, **pycompat.strkwargs(args))
2954 ui.status(_('response: %s\n') % util.escapedata(res))
2956 ui.status(_('response: %s\n') % util.escapedata(res))
2955
2957
2956 elif action == 'batchbegin':
2958 elif action == 'batchbegin':
2957 if batchedcommands is not None:
2959 if batchedcommands is not None:
2958 raise error.Abort(_('nested batchbegin not allowed'))
2960 raise error.Abort(_('nested batchbegin not allowed'))
2959
2961
2960 batchedcommands = []
2962 batchedcommands = []
2961 elif action == 'batchsubmit':
2963 elif action == 'batchsubmit':
2962 # There is a batching API we could go through. But it would be
2964 # There is a batching API we could go through. But it would be
2963 # difficult to normalize requests into function calls. It is easier
2965 # difficult to normalize requests into function calls. It is easier
2964 # to bypass this layer and normalize to commands + args.
2966 # to bypass this layer and normalize to commands + args.
2965 ui.status(_('sending batch with %d sub-commands\n') %
2967 ui.status(_('sending batch with %d sub-commands\n') %
2966 len(batchedcommands))
2968 len(batchedcommands))
2967 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
2969 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
2968 ui.status(_('response #%d: %s\n') % (i, util.escapedata(chunk)))
2970 ui.status(_('response #%d: %s\n') % (i, util.escapedata(chunk)))
2969
2971
2970 batchedcommands = None
2972 batchedcommands = None
2971
2973
2972 elif action.startswith('httprequest '):
2974 elif action.startswith('httprequest '):
2973 if not opener:
2975 if not opener:
2974 raise error.Abort(_('cannot use httprequest without an HTTP '
2976 raise error.Abort(_('cannot use httprequest without an HTTP '
2975 'peer'))
2977 'peer'))
2976
2978
2977 request = action.split(' ', 2)
2979 request = action.split(' ', 2)
2978 if len(request) != 3:
2980 if len(request) != 3:
2979 raise error.Abort(_('invalid httprequest: expected format is '
2981 raise error.Abort(_('invalid httprequest: expected format is '
2980 '"httprequest <method> <path>'))
2982 '"httprequest <method> <path>'))
2981
2983
2982 method, httppath = request[1:]
2984 method, httppath = request[1:]
2983 headers = {}
2985 headers = {}
2984 body = None
2986 body = None
2985 frames = []
2987 frames = []
2986 for line in lines:
2988 for line in lines:
2987 line = line.lstrip()
2989 line = line.lstrip()
2988 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
2990 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
2989 if m:
2991 if m:
2990 headers[m.group(1)] = m.group(2)
2992 headers[m.group(1)] = m.group(2)
2991 continue
2993 continue
2992
2994
2993 if line.startswith(b'BODYFILE '):
2995 if line.startswith(b'BODYFILE '):
2994 with open(line.split(b' ', 1), 'rb') as fh:
2996 with open(line.split(b' ', 1), 'rb') as fh:
2995 body = fh.read()
2997 body = fh.read()
2996 elif line.startswith(b'frame '):
2998 elif line.startswith(b'frame '):
2997 frame = wireprotoframing.makeframefromhumanstring(
2999 frame = wireprotoframing.makeframefromhumanstring(
2998 line[len(b'frame '):])
3000 line[len(b'frame '):])
2999
3001
3000 frames.append(frame)
3002 frames.append(frame)
3001 else:
3003 else:
3002 raise error.Abort(_('unknown argument to httprequest: %s') %
3004 raise error.Abort(_('unknown argument to httprequest: %s') %
3003 line)
3005 line)
3004
3006
3005 url = path + httppath
3007 url = path + httppath
3006
3008
3007 if frames:
3009 if frames:
3008 body = b''.join(bytes(f) for f in frames)
3010 body = b''.join(bytes(f) for f in frames)
3009
3011
3010 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3012 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3011
3013
3012 # urllib.Request insists on using has_data() as a proxy for
3014 # urllib.Request insists on using has_data() as a proxy for
3013 # determining the request method. Override that to use our
3015 # determining the request method. Override that to use our
3014 # explicitly requested method.
3016 # explicitly requested method.
3015 req.get_method = lambda: method
3017 req.get_method = lambda: method
3016
3018
3017 try:
3019 try:
3018 opener.open(req).read()
3020 opener.open(req).read()
3019 except util.urlerr.urlerror as e:
3021 except util.urlerr.urlerror as e:
3020 e.read()
3022 e.read()
3021
3023
3022 elif action == 'close':
3024 elif action == 'close':
3023 peer.close()
3025 peer.close()
3024 elif action == 'readavailable':
3026 elif action == 'readavailable':
3025 if not stdout or not stderr:
3027 if not stdout or not stderr:
3026 raise error.Abort(_('readavailable not available on this peer'))
3028 raise error.Abort(_('readavailable not available on this peer'))
3027
3029
3028 stdin.close()
3030 stdin.close()
3029 stdout.read()
3031 stdout.read()
3030 stderr.read()
3032 stderr.read()
3031
3033
3032 elif action == 'readline':
3034 elif action == 'readline':
3033 if not stdout:
3035 if not stdout:
3034 raise error.Abort(_('readline not available on this peer'))
3036 raise error.Abort(_('readline not available on this peer'))
3035 stdout.readline()
3037 stdout.readline()
3036 elif action == 'ereadline':
3038 elif action == 'ereadline':
3037 if not stderr:
3039 if not stderr:
3038 raise error.Abort(_('ereadline not available on this peer'))
3040 raise error.Abort(_('ereadline not available on this peer'))
3039 stderr.readline()
3041 stderr.readline()
3040 elif action.startswith('read '):
3042 elif action.startswith('read '):
3041 count = int(action.split(' ', 1)[1])
3043 count = int(action.split(' ', 1)[1])
3042 if not stdout:
3044 if not stdout:
3043 raise error.Abort(_('read not available on this peer'))
3045 raise error.Abort(_('read not available on this peer'))
3044 stdout.read(count)
3046 stdout.read(count)
3045 elif action.startswith('eread '):
3047 elif action.startswith('eread '):
3046 count = int(action.split(' ', 1)[1])
3048 count = int(action.split(' ', 1)[1])
3047 if not stderr:
3049 if not stderr:
3048 raise error.Abort(_('eread not available on this peer'))
3050 raise error.Abort(_('eread not available on this peer'))
3049 stderr.read(count)
3051 stderr.read(count)
3050 else:
3052 else:
3051 raise error.Abort(_('unknown action: %s') % action)
3053 raise error.Abort(_('unknown action: %s') % action)
3052
3054
3053 if batchedcommands is not None:
3055 if batchedcommands is not None:
3054 raise error.Abort(_('unclosed "batchbegin" request'))
3056 raise error.Abort(_('unclosed "batchbegin" request'))
3055
3057
3056 if peer:
3058 if peer:
3057 peer.close()
3059 peer.close()
3058
3060
3059 if proc:
3061 if proc:
3060 proc.kill()
3062 proc.kill()
@@ -1,1355 +1,1390
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
147 HTTP Version 2 Transport
148 ------------------------
148 ------------------------
149
149
150 **Experimental - feature under active development**
150 **Experimental - feature under active development**
151
151
152 Version 2 of the HTTP protocol is exposed under the ``/api/*`` URL space.
152 Version 2 of the HTTP protocol is exposed under the ``/api/*`` URL space.
153 It's final API name is not yet formalized.
153 It's final API name is not yet formalized.
154
154
155 Commands are triggered by sending HTTP POST requests against URLs of the
155 Commands are triggered by sending HTTP POST requests against URLs of the
156 form ``<permission>/<command>``, where ``<permission>`` is ``ro`` or
156 form ``<permission>/<command>``, where ``<permission>`` is ``ro`` or
157 ``rw``, meaning read-only and read-write, respectively and ``<command>``
157 ``rw``, meaning read-only and read-write, respectively and ``<command>``
158 is a named wire protocol command.
158 is a named wire protocol command.
159
159
160 Non-POST request methods MUST be rejected by the server with an HTTP
160 Non-POST request methods MUST be rejected by the server with an HTTP
161 405 response.
161 405 response.
162
162
163 Commands that modify repository state in meaningful ways MUST NOT be
163 Commands that modify repository state in meaningful ways MUST NOT be
164 exposed under the ``ro`` URL prefix. All available commands MUST be
164 exposed under the ``ro`` URL prefix. All available commands MUST be
165 available under the ``rw`` URL prefix.
165 available under the ``rw`` URL prefix.
166
166
167 Server adminstrators MAY implement blanket HTTP authentication keyed
167 Server adminstrators MAY implement blanket HTTP authentication keyed
168 off the URL prefix. For example, a server may require authentication
168 off the URL prefix. For example, a server may require authentication
169 for all ``rw/*`` URLs and let unauthenticated requests to ``ro/*``
169 for all ``rw/*`` URLs and let unauthenticated requests to ``ro/*``
170 URL proceed. A server MAY issue an HTTP 401, 403, or 407 response
170 URL proceed. A server MAY issue an HTTP 401, 403, or 407 response
171 in accordance with RFC 7235. Clients SHOULD recognize the HTTP Basic
171 in accordance with RFC 7235. Clients SHOULD recognize the HTTP Basic
172 (RFC 7617) and Digest (RFC 7616) authentication schemes. Clients SHOULD
172 (RFC 7617) and Digest (RFC 7616) authentication schemes. Clients SHOULD
173 make an attempt to recognize unknown schemes using the
173 make an attempt to recognize unknown schemes using the
174 ``WWW-Authenticate`` response header on a 401 response, as defined by
174 ``WWW-Authenticate`` response header on a 401 response, as defined by
175 RFC 7235.
175 RFC 7235.
176
176
177 Read-only commands are accessible under ``rw/*`` URLs so clients can
177 Read-only commands are accessible under ``rw/*`` URLs so clients can
178 signal the intent of the operation very early in the connection
178 signal the intent of the operation very early in the connection
179 lifecycle. For example, a ``push`` operation - which consists of
179 lifecycle. For example, a ``push`` operation - which consists of
180 various read-only commands mixed with at least one read-write command -
180 various read-only commands mixed with at least one read-write command -
181 can perform all commands against ``rw/*`` URLs so that any server-side
181 can perform all commands against ``rw/*`` URLs so that any server-side
182 authentication requirements are discovered upon attempting the first
182 authentication requirements are discovered upon attempting the first
183 command - not potentially several commands into the exchange. This
183 command - not potentially several commands into the exchange. This
184 allows clients to fail faster or prompt for credentials as soon as the
184 allows clients to fail faster or prompt for credentials as soon as the
185 exchange takes place. This provides a better end-user experience.
185 exchange takes place. This provides a better end-user experience.
186
186
187 Requests to unknown commands or URLS result in an HTTP 404.
187 Requests to unknown commands or URLS result in an HTTP 404.
188 TODO formally define response type, how error is communicated, etc.
188 TODO formally define response type, how error is communicated, etc.
189
189
190 HTTP request and response bodies use the *Unified Frame-Based Protocol*
190 HTTP request and response bodies use the *Unified Frame-Based Protocol*
191 (defined below) for media exchange. The entirety of the HTTP message
191 (defined below) for media exchange. The entirety of the HTTP message
192 body is 0 or more frames as defined by this protocol.
192 body is 0 or more frames as defined by this protocol.
193
193
194 Clients and servers MUST advertise the ``TBD`` media type via the
194 Clients and servers MUST advertise the ``TBD`` media type via the
195 ``Content-Type`` request and response headers. In addition, clients MUST
195 ``Content-Type`` request and response headers. In addition, clients MUST
196 advertise this media type value in their ``Accept`` request header in all
196 advertise this media type value in their ``Accept`` request header in all
197 requests.
197 requests.
198 TODO finalize the media type. For now, it is defined in wireprotoserver.py.
198 TODO finalize the media type. For now, it is defined in wireprotoserver.py.
199
199
200 Servers receiving requests without an ``Accept`` header SHOULD respond with
200 Servers receiving requests without an ``Accept`` header SHOULD respond with
201 an HTTP 406.
201 an HTTP 406.
202
202
203 Servers receiving requests with an invalid ``Content-Type`` header SHOULD
203 Servers receiving requests with an invalid ``Content-Type`` header SHOULD
204 respond with an HTTP 415.
204 respond with an HTTP 415.
205
205
206 SSH Protocol
206 SSH Protocol
207 ============
207 ============
208
208
209 Handshake
209 Handshake
210 ---------
210 ---------
211
211
212 For all clients, the handshake consists of the client sending 1 or more
212 For all clients, the handshake consists of the client sending 1 or more
213 commands to the server using version 1 of the transport. Servers respond
213 commands to the server using version 1 of the transport. Servers respond
214 to commands they know how to respond to and send an empty response (``0\n``)
214 to commands they know how to respond to and send an empty response (``0\n``)
215 for unknown commands (per standard behavior of version 1 of the transport).
215 for unknown commands (per standard behavior of version 1 of the transport).
216 Clients then typically look for a response to the newest sent command to
216 Clients then typically look for a response to the newest sent command to
217 determine which transport version to use and what the available features for
217 determine which transport version to use and what the available features for
218 the connection and server are.
218 the connection and server are.
219
219
220 Preceding any response from client-issued commands, the server may print
220 Preceding any response from client-issued commands, the server may print
221 non-protocol output. It is common for SSH servers to print banners, message
221 non-protocol output. It is common for SSH servers to print banners, message
222 of the day announcements, etc when clients connect. It is assumed that any
222 of the day announcements, etc when clients connect. It is assumed that any
223 such *banner* output will precede any Mercurial server output. So clients
223 such *banner* output will precede any Mercurial server output. So clients
224 must be prepared to handle server output on initial connect that isn't
224 must be prepared to handle server output on initial connect that isn't
225 in response to any client-issued command and doesn't conform to Mercurial's
225 in response to any client-issued command and doesn't conform to Mercurial's
226 wire protocol. This *banner* output should only be on stdout. However,
226 wire protocol. This *banner* output should only be on stdout. However,
227 some servers may send output on stderr.
227 some servers may send output on stderr.
228
228
229 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
229 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
230 having the value
230 having the value
231 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
231 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
232
232
233 The ``between`` command has been supported since the original Mercurial
233 The ``between`` command has been supported since the original Mercurial
234 SSH server. Requesting the empty range will return a ``\n`` string response,
234 SSH server. Requesting the empty range will return a ``\n`` string response,
235 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
235 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
236 followed by the value, which happens to be a newline).
236 followed by the value, which happens to be a newline).
237
237
238 For pre 0.9.1 clients and all servers, the exchange looks like::
238 For pre 0.9.1 clients and all servers, the exchange looks like::
239
239
240 c: between\n
240 c: between\n
241 c: pairs 81\n
241 c: pairs 81\n
242 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
242 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
243 s: 1\n
243 s: 1\n
244 s: \n
244 s: \n
245
245
246 0.9.1+ clients send a ``hello`` command (with no arguments) before the
246 0.9.1+ clients send a ``hello`` command (with no arguments) before the
247 ``between`` command. The response to this command allows clients to
247 ``between`` command. The response to this command allows clients to
248 discover server capabilities and settings.
248 discover server capabilities and settings.
249
249
250 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
250 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
251 like::
251 like::
252
252
253 c: hello\n
253 c: hello\n
254 c: between\n
254 c: between\n
255 c: pairs 81\n
255 c: pairs 81\n
256 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
256 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
257 s: 324\n
257 s: 324\n
258 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
258 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
259 s: 1\n
259 s: 1\n
260 s: \n
260 s: \n
261
261
262 And a similar scenario but with servers sending a banner on connect::
262 And a similar scenario but with servers sending a banner on connect::
263
263
264 c: hello\n
264 c: hello\n
265 c: between\n
265 c: between\n
266 c: pairs 81\n
266 c: pairs 81\n
267 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
267 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
268 s: welcome to the server\n
268 s: welcome to the server\n
269 s: if you find any issues, email someone@somewhere.com\n
269 s: if you find any issues, email someone@somewhere.com\n
270 s: 324\n
270 s: 324\n
271 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
271 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
272 s: 1\n
272 s: 1\n
273 s: \n
273 s: \n
274
274
275 Note that output from the ``hello`` command is terminated by a ``\n``. This is
275 Note that output from the ``hello`` command is terminated by a ``\n``. This is
276 part of the response payload and not part of the wire protocol adding a newline
276 part of the response payload and not part of the wire protocol adding a newline
277 after responses. In other words, the length of the response contains the
277 after responses. In other words, the length of the response contains the
278 trailing ``\n``.
278 trailing ``\n``.
279
279
280 Clients supporting version 2 of the SSH transport send a line beginning
280 Clients supporting version 2 of the SSH transport send a line beginning
281 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
281 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
282 (which isn't a well-formed command line because it doesn't consist of a
282 (which isn't a well-formed command line because it doesn't consist of a
283 single command name) serves to both communicate the client's intent to
283 single command name) serves to both communicate the client's intent to
284 switch to transport version 2 (transports are version 1 by default) as
284 switch to transport version 2 (transports are version 1 by default) as
285 well as to advertise the client's transport-level capabilities so the
285 well as to advertise the client's transport-level capabilities so the
286 server may satisfy that request immediately.
286 server may satisfy that request immediately.
287
287
288 The upgrade line has the form:
288 The upgrade line has the form:
289
289
290 upgrade <token> <transport capabilities>
290 upgrade <token> <transport capabilities>
291
291
292 That is the literal string ``upgrade`` followed by a space, followed by
292 That is the literal string ``upgrade`` followed by a space, followed by
293 a randomly generated string, followed by a space, followed by a string
293 a randomly generated string, followed by a space, followed by a string
294 denoting the client's transport capabilities.
294 denoting the client's transport capabilities.
295
295
296 The token can be anything. However, a random UUID is recommended. (Use
296 The token can be anything. However, a random UUID is recommended. (Use
297 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
297 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
298 client's MAC address.)
298 client's MAC address.)
299
299
300 The transport capabilities string is a URL/percent encoded string
300 The transport capabilities string is a URL/percent encoded string
301 containing key-value pairs defining the client's transport-level
301 containing key-value pairs defining the client's transport-level
302 capabilities. The following capabilities are defined:
302 capabilities. The following capabilities are defined:
303
303
304 proto
304 proto
305 A comma-delimited list of transport protocol versions the client
305 A comma-delimited list of transport protocol versions the client
306 supports. e.g. ``ssh-v2``.
306 supports. e.g. ``ssh-v2``.
307
307
308 If the server does not recognize the ``upgrade`` line, it should issue
308 If the server does not recognize the ``upgrade`` line, it should issue
309 an empty response and continue processing the ``hello`` and ``between``
309 an empty response and continue processing the ``hello`` and ``between``
310 commands. Here is an example handshake between a version 2 aware client
310 commands. Here is an example handshake between a version 2 aware client
311 and a non version 2 aware server:
311 and a non version 2 aware server:
312
312
313 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
313 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
314 c: hello\n
314 c: hello\n
315 c: between\n
315 c: between\n
316 c: pairs 81\n
316 c: pairs 81\n
317 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
317 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
318 s: 0\n
318 s: 0\n
319 s: 324\n
319 s: 324\n
320 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
320 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
321 s: 1\n
321 s: 1\n
322 s: \n
322 s: \n
323
323
324 (The initial ``0\n`` line from the server indicates an empty response to
324 (The initial ``0\n`` line from the server indicates an empty response to
325 the unknown ``upgrade ..`` command/line.)
325 the unknown ``upgrade ..`` command/line.)
326
326
327 If the server recognizes the ``upgrade`` line and is willing to satisfy that
327 If the server recognizes the ``upgrade`` line and is willing to satisfy that
328 upgrade request, it replies to with a payload of the following form:
328 upgrade request, it replies to with a payload of the following form:
329
329
330 upgraded <token> <transport name>\n
330 upgraded <token> <transport name>\n
331
331
332 This line is the literal string ``upgraded``, a space, the token that was
332 This line is the literal string ``upgraded``, a space, the token that was
333 specified by the client in its ``upgrade ...`` request line, a space, and the
333 specified by the client in its ``upgrade ...`` request line, a space, and the
334 name of the transport protocol that was chosen by the server. The transport
334 name of the transport protocol that was chosen by the server. The transport
335 name MUST match one of the names the client specified in the ``proto`` field
335 name MUST match one of the names the client specified in the ``proto`` field
336 of its ``upgrade ...`` request line.
336 of its ``upgrade ...`` request line.
337
337
338 If a server issues an ``upgraded`` response, it MUST also read and ignore
338 If a server issues an ``upgraded`` response, it MUST also read and ignore
339 the lines associated with the ``hello`` and ``between`` command requests
339 the lines associated with the ``hello`` and ``between`` command requests
340 that were issued by the server. It is assumed that the negotiated transport
340 that were issued by the server. It is assumed that the negotiated transport
341 will respond with equivalent requested information following the transport
341 will respond with equivalent requested information following the transport
342 handshake.
342 handshake.
343
343
344 All data following the ``\n`` terminating the ``upgraded`` line is the
344 All data following the ``\n`` terminating the ``upgraded`` line is the
345 domain of the negotiated transport. It is common for the data immediately
345 domain of the negotiated transport. It is common for the data immediately
346 following to contain additional metadata about the state of the transport and
346 following to contain additional metadata about the state of the transport and
347 the server. However, this isn't strictly speaking part of the transport
347 the server. However, this isn't strictly speaking part of the transport
348 handshake and isn't covered by this section.
348 handshake and isn't covered by this section.
349
349
350 Here is an example handshake between a version 2 aware client and a version
350 Here is an example handshake between a version 2 aware client and a version
351 2 aware server:
351 2 aware server:
352
352
353 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
353 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
354 c: hello\n
354 c: hello\n
355 c: between\n
355 c: between\n
356 c: pairs 81\n
356 c: pairs 81\n
357 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
357 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
358 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
358 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
359 s: <additional transport specific data>
359 s: <additional transport specific data>
360
360
361 The client-issued token that is echoed in the response provides a more
361 The client-issued token that is echoed in the response provides a more
362 resilient mechanism for differentiating *banner* output from Mercurial
362 resilient mechanism for differentiating *banner* output from Mercurial
363 output. In version 1, properly formatted banner output could get confused
363 output. In version 1, properly formatted banner output could get confused
364 for Mercurial server output. By submitting a randomly generated token
364 for Mercurial server output. By submitting a randomly generated token
365 that is then present in the response, the client can look for that token
365 that is then present in the response, the client can look for that token
366 in response lines and have reasonable certainty that the line did not
366 in response lines and have reasonable certainty that the line did not
367 originate from a *banner* message.
367 originate from a *banner* message.
368
368
369 SSH Version 1 Transport
369 SSH Version 1 Transport
370 -----------------------
370 -----------------------
371
371
372 The SSH transport (version 1) is a custom text-based protocol suitable for
372 The SSH transport (version 1) is a custom text-based protocol suitable for
373 use over any bi-directional stream transport. It is most commonly used with
373 use over any bi-directional stream transport. It is most commonly used with
374 SSH.
374 SSH.
375
375
376 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
376 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
377 stderr, and stdout file descriptors of the started process are used to exchange
377 stderr, and stdout file descriptors of the started process are used to exchange
378 data. When Mercurial connects to a remote server over SSH, it actually starts
378 data. When Mercurial connects to a remote server over SSH, it actually starts
379 a ``hg serve --stdio`` process on the remote server.
379 a ``hg serve --stdio`` process on the remote server.
380
380
381 Commands are issued by sending the command name followed by a trailing newline
381 Commands are issued by sending the command name followed by a trailing newline
382 ``\n`` to the server. e.g. ``capabilities\n``.
382 ``\n`` to the server. e.g. ``capabilities\n``.
383
383
384 Command arguments are sent in the following format::
384 Command arguments are sent in the following format::
385
385
386 <argument> <length>\n<value>
386 <argument> <length>\n<value>
387
387
388 That is, the argument string name followed by a space followed by the
388 That is, the argument string name followed by a space followed by the
389 integer length of the value (expressed as a string) followed by a newline
389 integer length of the value (expressed as a string) followed by a newline
390 (``\n``) followed by the raw argument value.
390 (``\n``) followed by the raw argument value.
391
391
392 Dictionary arguments are encoded differently::
392 Dictionary arguments are encoded differently::
393
393
394 <argument> <# elements>\n
394 <argument> <# elements>\n
395 <key1> <length1>\n<value1>
395 <key1> <length1>\n<value1>
396 <key2> <length2>\n<value2>
396 <key2> <length2>\n<value2>
397 ...
397 ...
398
398
399 Non-argument data is sent immediately after the final argument value. It is
399 Non-argument data is sent immediately after the final argument value. It is
400 encoded in chunks::
400 encoded in chunks::
401
401
402 <length>\n<data>
402 <length>\n<data>
403
403
404 Each command declares a list of supported arguments and their types. If a
404 Each command declares a list of supported arguments and their types. If a
405 client sends an unknown argument to the server, the server should abort
405 client sends an unknown argument to the server, the server should abort
406 immediately. The special argument ``*`` in a command's definition indicates
406 immediately. The special argument ``*`` in a command's definition indicates
407 that all argument names are allowed.
407 that all argument names are allowed.
408
408
409 The definition of supported arguments and types is initially made when a
409 The definition of supported arguments and types is initially made when a
410 new command is implemented. The client and server must initially independently
410 new command is implemented. The client and server must initially independently
411 agree on the arguments and their types. This initial set of arguments can be
411 agree on the arguments and their types. This initial set of arguments can be
412 supplemented through the presence of *capabilities* advertised by the server.
412 supplemented through the presence of *capabilities* advertised by the server.
413
413
414 Each command has a defined expected response type.
414 Each command has a defined expected response type.
415
415
416 A ``string`` response type is a length framed value. The response consists of
416 A ``string`` response type is a length framed value. The response consists of
417 the string encoded integer length of a value followed by a newline (``\n``)
417 the string encoded integer length of a value followed by a newline (``\n``)
418 followed by the value. Empty values are allowed (and are represented as
418 followed by the value. Empty values are allowed (and are represented as
419 ``0\n``).
419 ``0\n``).
420
420
421 A ``stream`` response type consists of raw bytes of data. There is no framing.
421 A ``stream`` response type consists of raw bytes of data. There is no framing.
422
422
423 A generic error response type is also supported. It consists of a an error
423 A generic error response type is also supported. It consists of a an error
424 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
424 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
425 written to ``stdout``.
425 written to ``stdout``.
426
426
427 If the server receives an unknown command, it will send an empty ``string``
427 If the server receives an unknown command, it will send an empty ``string``
428 response.
428 response.
429
429
430 The server terminates if it receives an empty command (a ``\n`` character).
430 The server terminates if it receives an empty command (a ``\n`` character).
431
431
432 SSH Version 2 Transport
432 SSH Version 2 Transport
433 -----------------------
433 -----------------------
434
434
435 **Experimental and under development**
435 **Experimental and under development**
436
436
437 Version 2 of the SSH transport behaves identically to version 1 of the SSH
437 Version 2 of the SSH transport behaves identically to version 1 of the SSH
438 transport with the exception of handshake semantics. See above for how
438 transport with the exception of handshake semantics. See above for how
439 version 2 of the SSH transport is negotiated.
439 version 2 of the SSH transport is negotiated.
440
440
441 Immediately following the ``upgraded`` line signaling a switch to version
441 Immediately following the ``upgraded`` line signaling a switch to version
442 2 of the SSH protocol, the server automatically sends additional details
442 2 of the SSH protocol, the server automatically sends additional details
443 about the capabilities of the remote server. This has the form:
443 about the capabilities of the remote server. This has the form:
444
444
445 <integer length of value>\n
445 <integer length of value>\n
446 capabilities: ...\n
446 capabilities: ...\n
447
447
448 e.g.
448 e.g.
449
449
450 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
450 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
451 s: 240\n
451 s: 240\n
452 s: capabilities: known getbundle batch ...\n
452 s: capabilities: known getbundle batch ...\n
453
453
454 Following capabilities advertisement, the peers communicate using version
454 Following capabilities advertisement, the peers communicate using version
455 1 of the SSH transport.
455 1 of the SSH transport.
456
456
457 Unified Frame-Based Protocol
457 Unified Frame-Based Protocol
458 ============================
458 ============================
459
459
460 **Experimental and under development**
460 **Experimental and under development**
461
461
462 The *Unified Frame-Based Protocol* is a communications protocol between
462 The *Unified Frame-Based Protocol* is a communications protocol between
463 Mercurial peers. The protocol aims to be mostly transport agnostic
463 Mercurial peers. The protocol aims to be mostly transport agnostic
464 (works similarly on HTTP, SSH, etc).
464 (works similarly on HTTP, SSH, etc).
465
465
466 To operate the protocol, a bi-directional, half-duplex pipe supporting
466 To operate the protocol, a bi-directional, half-duplex pipe supporting
467 ordered sends and receives is required. That is, each peer has one pipe
467 ordered sends and receives is required. That is, each peer has one pipe
468 for sending data and another for receiving.
468 for sending data and another for receiving.
469
469
470 The protocol is request-response based: the client issues requests to
470 The protocol is request-response based: the client issues requests to
471 the server, which issues replies to those requests. Server-initiated
471 the server, which issues replies to those requests. Server-initiated
472 messaging is not supported.
472 messaging is not currently supported, but this specification carves
473 out room to implement it.
473
474
474 All data is read and written in atomic units called *frames*. These
475 All data is read and written in atomic units called *frames*. These
475 are conceptually similar to TCP packets. Higher-level functionality
476 are conceptually similar to TCP packets. Higher-level functionality
476 is built on the exchange and processing of frames.
477 is built on the exchange and processing of frames.
477
478
478 Frames begin with a 4 octet header followed by a variable length
479 All frames are associated with a numbered request. Frames can thus
480 be logically grouped by their request ID.
481
482 Frames begin with a 6 octet header followed by a variable length
479 payload::
483 payload::
480
484
481 +-----------------------------------------------+
485 +-----------------------------------------------+
482 | Length (24) |
486 | Length (24) |
483 +-----------+-----------------------------------+
487 +---------------------------------+-------------+
484 | Type (4) |
488 | Request ID (16) |
485 +-----------+
489 +----------+-----------+----------+
486 | Flags (4) |
490 | Type (4) | Flags (4) |
487 +===========+===================================================|
491 +==========+===========+========================================|
488 | Frame Payload (0...) ...
492 | Frame Payload (0...) ...
489 +---------------------------------------------------------------+
493 +---------------------------------------------------------------+
490
494
491 The length of the frame payload is expressed as an unsigned 24 bit
495 The length of the frame payload is expressed as an unsigned 24 bit
492 little endian integer. Values larger than 65535 MUST NOT be used unless
496 little endian integer. Values larger than 65535 MUST NOT be used unless
493 given permission by the server as part of the negotiated capabilities
497 given permission by the server as part of the negotiated capabilities
494 during the handshake. The frame header is not part of the advertised
498 during the handshake. The frame header is not part of the advertised
495 frame length.
499 frame length.
496
500
501 The 16-bit ``Request ID`` field denotes the integer request identifier,
502 stored as an unsigned little endian integer. Odd numbered requests are
503 client-initiated. Even numbered requests are server-initiated. This
504 refers to where the *request* was initiated - not where the *frame* was
505 initiated, so servers will send frames with odd ``Request ID`` in
506 response to client-initiated requests. Implementations are advised to
507 start ordering request identifiers at ``1`` and ``0``, increment by
508 ``2``, and wrap around if all available numbers have been exhausted.
509
497 The 4-bit ``Type`` field denotes the type of message being sent.
510 The 4-bit ``Type`` field denotes the type of message being sent.
498
511
499 The 4-bit ``Flags`` field defines special, per-type attributes for
512 The 4-bit ``Flags`` field defines special, per-type attributes for
500 the frame.
513 the frame.
501
514
502 The sections below define the frame types and their behavior.
515 The sections below define the frame types and their behavior.
503
516
504 Command Request (``0x01``)
517 Command Request (``0x01``)
505 --------------------------
518 --------------------------
506
519
507 This frame contains a request to run a command.
520 This frame contains a request to run a command.
508
521
509 The name of the command to run constitutes the entirety of the frame
522 The name of the command to run constitutes the entirety of the frame
510 payload.
523 payload.
511
524
512 This frame type MUST ONLY be sent from clients to servers: it is illegal
525 This frame type MUST ONLY be sent from clients to servers: it is illegal
513 for a server to send this frame to a client.
526 for a server to send this frame to a client.
514
527
515 The following flag values are defined for this type:
528 The following flag values are defined for this type:
516
529
517 0x01
530 0x01
518 End of command data. When set, the client will not send any command
531 End of command data. When set, the client will not send any command
519 arguments or additional command data. When set, the command has been
532 arguments or additional command data. When set, the command has been
520 fully issued and the server has the full context to process the command.
533 fully issued and the server has the full context to process the command.
521 The next frame issued by the client is not part of this command.
534 The next frame issued by the client is not part of this command.
522 0x02
535 0x02
523 Command argument frames expected. When set, the client will send
536 Command argument frames expected. When set, the client will send
524 *Command Argument* frames containing command argument data.
537 *Command Argument* frames containing command argument data.
525 0x04
538 0x04
526 Command data frames expected. When set, the client will send
539 Command data frames expected. When set, the client will send
527 *Command Data* frames containing a raw stream of data for this
540 *Command Data* frames containing a raw stream of data for this
528 command.
541 command.
529
542
530 The ``0x01`` flag is mutually exclusive with both the ``0x02`` and ``0x04``
543 The ``0x01`` flag is mutually exclusive with both the ``0x02`` and ``0x04``
531 flags.
544 flags.
532
545
533 Command Argument (``0x02``)
546 Command Argument (``0x02``)
534 ---------------------------
547 ---------------------------
535
548
536 This frame contains a named argument for a command.
549 This frame contains a named argument for a command.
537
550
538 The frame type MUST ONLY be sent from clients to servers: it is illegal
551 The frame type MUST ONLY be sent from clients to servers: it is illegal
539 for a server to send this frame to a client.
552 for a server to send this frame to a client.
540
553
541 The payload consists of:
554 The payload consists of:
542
555
543 * A 16-bit little endian integer denoting the length of the
556 * A 16-bit little endian integer denoting the length of the
544 argument name.
557 argument name.
545 * A 16-bit little endian integer denoting the length of the
558 * A 16-bit little endian integer denoting the length of the
546 argument value.
559 argument value.
547 * N bytes of ASCII data containing the argument name.
560 * N bytes of ASCII data containing the argument name.
548 * N bytes of binary data containing the argument value.
561 * N bytes of binary data containing the argument value.
549
562
550 The payload MUST hold the entirety of the 32-bit header and the
563 The payload MUST hold the entirety of the 32-bit header and the
551 argument name. The argument value MAY span multiple frames. If this
564 argument name. The argument value MAY span multiple frames. If this
552 occurs, the appropriate frame flag should be set to indicate this.
565 occurs, the appropriate frame flag should be set to indicate this.
553
566
554 The following flag values are defined for this type:
567 The following flag values are defined for this type:
555
568
556 0x01
569 0x01
557 Argument data continuation. When set, the data for this argument did
570 Argument data continuation. When set, the data for this argument did
558 not fit in a single frame and the next frame will contain additional
571 not fit in a single frame and the next frame will contain additional
559 argument data.
572 argument data.
560
573
561 0x02
574 0x02
562 End of arguments data. When set, the client will not send any more
575 End of arguments data. When set, the client will not send any more
563 command arguments for the command this frame is associated with.
576 command arguments for the command this frame is associated with.
564 The next frame issued by the client will be command data or
577 The next frame issued by the client will be command data or
565 belong to a separate request.
578 belong to a separate request.
566
579
567 Command Data (``0x03``)
580 Command Data (``0x03``)
568 -----------------------
581 -----------------------
569
582
570 This frame contains raw data for a command.
583 This frame contains raw data for a command.
571
584
572 Most commands can be executed by specifying arguments. However,
585 Most commands can be executed by specifying arguments. However,
573 arguments have an upper bound to their length. For commands that
586 arguments have an upper bound to their length. For commands that
574 accept data that is beyond this length or whose length isn't known
587 accept data that is beyond this length or whose length isn't known
575 when the command is initially sent, they will need to stream
588 when the command is initially sent, they will need to stream
576 arbitrary data to the server. This frame type facilitates the sending
589 arbitrary data to the server. This frame type facilitates the sending
577 of this data.
590 of this data.
578
591
579 The payload of this frame type consists of a stream of raw data to be
592 The payload of this frame type consists of a stream of raw data to be
580 consumed by the command handler on the server. The format of the data
593 consumed by the command handler on the server. The format of the data
581 is command specific.
594 is command specific.
582
595
583 The following flag values are defined for this type:
596 The following flag values are defined for this type:
584
597
585 0x01
598 0x01
586 Command data continuation. When set, the data for this command
599 Command data continuation. When set, the data for this command
587 continues into a subsequent frame.
600 continues into a subsequent frame.
588
601
589 0x02
602 0x02
590 End of data. When set, command data has been fully sent to the
603 End of data. When set, command data has been fully sent to the
591 server. The command has been fully issued and no new data for this
604 server. The command has been fully issued and no new data for this
592 command will be sent. The next frame will belong to a new command.
605 command will be sent. The next frame will belong to a new command.
593
606
594 Bytes Response Data (``0x04``)
607 Bytes Response Data (``0x04``)
595 ------------------------------
608 ------------------------------
596
609
597 This frame contains raw bytes response data to an issued command.
610 This frame contains raw bytes response data to an issued command.
598
611
599 The following flag values are defined for this type:
612 The following flag values are defined for this type:
600
613
601 0x01
614 0x01
602 Data continuation. When set, an additional frame containing raw
615 Data continuation. When set, an additional frame containing raw
603 response data will follow.
616 response data will follow.
604 0x02
617 0x02
605 End of data. When sent, the response data has been fully sent and
618 End of data. When sent, the response data has been fully sent and
606 no additional frames for this response will be sent.
619 no additional frames for this response will be sent.
607
620
608 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
621 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
609
622
610 Error Response (``0x05``)
623 Error Response (``0x05``)
611 -------------------------
624 -------------------------
612
625
613 An error occurred when processing a request. This could indicate
626 An error occurred when processing a request. This could indicate
614 a protocol-level failure or an application level failure depending
627 a protocol-level failure or an application level failure depending
615 on the flags for this message type.
628 on the flags for this message type.
616
629
617 The payload for this type is an error message that should be
630 The payload for this type is an error message that should be
618 displayed to the user.
631 displayed to the user.
619
632
620 The following flag values are defined for this type:
633 The following flag values are defined for this type:
621
634
622 0x01
635 0x01
623 The error occurred at the transport/protocol level. If set, the
636 The error occurred at the transport/protocol level. If set, the
624 connection should be closed.
637 connection should be closed.
625 0x02
638 0x02
626 The error occurred at the application level. e.g. invalid command.
639 The error occurred at the application level. e.g. invalid command.
627
640
628 Issuing Commands
641 Issuing Commands
629 ----------------
642 ----------------
630
643
631 A client can request that a remote run a command by sending it
644 A client can request that a remote run a command by sending it
632 frames defining that command. This logical stream is composed of
645 frames defining that command. This logical stream is composed of
633 1 ``Command Request`` frame, 0 or more ``Command Argument`` frames,
646 1 ``Command Request`` frame, 0 or more ``Command Argument`` frames,
634 and 0 or more ``Command Data`` frames.
647 and 0 or more ``Command Data`` frames.
635
648
649 All frames composing a single command request MUST be associated with
650 the same ``Request ID``.
651
652 Clients MAY send additional command requests without waiting on the
653 response to a previous command request. If they do so, they MUST ensure
654 that the ``Request ID`` field of outbound frames does not conflict
655 with that of an active ``Request ID`` whose response has not yet been
656 fully received.
657
658 Servers MAY respond to commands in a different order than they were
659 sent over the wire. Clients MUST be prepared to deal with this. Servers
660 also MAY start executing commands in a different order than they were
661 received, or MAY execute multiple commands concurrently.
662
663 If there is a dependency between commands or a race condition between
664 commands executing (e.g. a read-only command that depends on the results
665 of a command that mutates the repository), then clients MUST NOT send
666 frames issuing a command until a response to all dependent commands has
667 been received.
668 TODO think about whether we should express dependencies between commands
669 to avoid roundtrip latency.
670
636 Argument frames are the recommended mechanism for transferring fixed
671 Argument frames are the recommended mechanism for transferring fixed
637 sets of parameters to a command. Data frames are appropriate for
672 sets of parameters to a command. Data frames are appropriate for
638 transferring variable data. A similar comparison would be to HTTP:
673 transferring variable data. A similar comparison would be to HTTP:
639 argument frames are headers and the message body is data frames.
674 argument frames are headers and the message body is data frames.
640
675
641 It is recommended for servers to delay the dispatch of a command
676 It is recommended for servers to delay the dispatch of a command
642 until all argument frames for that command have been received. Servers
677 until all argument frames for that command have been received. Servers
643 MAY impose limits on the maximum argument size.
678 MAY impose limits on the maximum argument size.
644 TODO define failure mechanism.
679 TODO define failure mechanism.
645
680
646 Servers MAY dispatch to commands immediately once argument data
681 Servers MAY dispatch to commands immediately once argument data
647 is available or delay until command data is received in full.
682 is available or delay until command data is received in full.
648
683
649 Capabilities
684 Capabilities
650 ============
685 ============
651
686
652 Servers advertise supported wire protocol features. This allows clients to
687 Servers advertise supported wire protocol features. This allows clients to
653 probe for server features before blindly calling a command or passing a
688 probe for server features before blindly calling a command or passing a
654 specific argument.
689 specific argument.
655
690
656 The server's features are exposed via a *capabilities* string. This is a
691 The server's features are exposed via a *capabilities* string. This is a
657 space-delimited string of tokens/features. Some features are single words
692 space-delimited string of tokens/features. Some features are single words
658 like ``lookup`` or ``batch``. Others are complicated key-value pairs
693 like ``lookup`` or ``batch``. Others are complicated key-value pairs
659 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
694 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
660 values are used, each feature name can define its own encoding of sub-values.
695 values are used, each feature name can define its own encoding of sub-values.
661 Comma-delimited and ``x-www-form-urlencoded`` values are common.
696 Comma-delimited and ``x-www-form-urlencoded`` values are common.
662
697
663 The following document capabilities defined by the canonical Mercurial server
698 The following document capabilities defined by the canonical Mercurial server
664 implementation.
699 implementation.
665
700
666 batch
701 batch
667 -----
702 -----
668
703
669 Whether the server supports the ``batch`` command.
704 Whether the server supports the ``batch`` command.
670
705
671 This capability/command was introduced in Mercurial 1.9 (released July 2011).
706 This capability/command was introduced in Mercurial 1.9 (released July 2011).
672
707
673 branchmap
708 branchmap
674 ---------
709 ---------
675
710
676 Whether the server supports the ``branchmap`` command.
711 Whether the server supports the ``branchmap`` command.
677
712
678 This capability/command was introduced in Mercurial 1.3 (released July 2009).
713 This capability/command was introduced in Mercurial 1.3 (released July 2009).
679
714
680 bundle2-exp
715 bundle2-exp
681 -----------
716 -----------
682
717
683 Precursor to ``bundle2`` capability that was used before bundle2 was a
718 Precursor to ``bundle2`` capability that was used before bundle2 was a
684 stable feature.
719 stable feature.
685
720
686 This capability was introduced in Mercurial 3.0 behind an experimental
721 This capability was introduced in Mercurial 3.0 behind an experimental
687 flag. This capability should not be observed in the wild.
722 flag. This capability should not be observed in the wild.
688
723
689 bundle2
724 bundle2
690 -------
725 -------
691
726
692 Indicates whether the server supports the ``bundle2`` data exchange format.
727 Indicates whether the server supports the ``bundle2`` data exchange format.
693
728
694 The value of the capability is a URL quoted, newline (``\n``) delimited
729 The value of the capability is a URL quoted, newline (``\n``) delimited
695 list of keys or key-value pairs.
730 list of keys or key-value pairs.
696
731
697 A key is simply a URL encoded string.
732 A key is simply a URL encoded string.
698
733
699 A key-value pair is a URL encoded key separated from a URL encoded value by
734 A key-value pair is a URL encoded key separated from a URL encoded value by
700 an ``=``. If the value is a list, elements are delimited by a ``,`` after
735 an ``=``. If the value is a list, elements are delimited by a ``,`` after
701 URL encoding.
736 URL encoding.
702
737
703 For example, say we have the values::
738 For example, say we have the values::
704
739
705 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
740 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
706
741
707 We would first construct a string::
742 We would first construct a string::
708
743
709 HG20\nchangegroup=01,02\ndigests=sha1,sha512
744 HG20\nchangegroup=01,02\ndigests=sha1,sha512
710
745
711 We would then URL quote this string::
746 We would then URL quote this string::
712
747
713 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
748 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
714
749
715 This capability was introduced in Mercurial 3.4 (released May 2015).
750 This capability was introduced in Mercurial 3.4 (released May 2015).
716
751
717 changegroupsubset
752 changegroupsubset
718 -----------------
753 -----------------
719
754
720 Whether the server supports the ``changegroupsubset`` command.
755 Whether the server supports the ``changegroupsubset`` command.
721
756
722 This capability was introduced in Mercurial 0.9.2 (released December
757 This capability was introduced in Mercurial 0.9.2 (released December
723 2006).
758 2006).
724
759
725 This capability was introduced at the same time as the ``lookup``
760 This capability was introduced at the same time as the ``lookup``
726 capability/command.
761 capability/command.
727
762
728 compression
763 compression
729 -----------
764 -----------
730
765
731 Declares support for negotiating compression formats.
766 Declares support for negotiating compression formats.
732
767
733 Presence of this capability indicates the server supports dynamic selection
768 Presence of this capability indicates the server supports dynamic selection
734 of compression formats based on the client request.
769 of compression formats based on the client request.
735
770
736 Servers advertising this capability are required to support the
771 Servers advertising this capability are required to support the
737 ``application/mercurial-0.2`` media type in response to commands returning
772 ``application/mercurial-0.2`` media type in response to commands returning
738 streams. Servers may support this media type on any command.
773 streams. Servers may support this media type on any command.
739
774
740 The value of the capability is a comma-delimited list of strings declaring
775 The value of the capability is a comma-delimited list of strings declaring
741 supported compression formats. The order of the compression formats is in
776 supported compression formats. The order of the compression formats is in
742 server-preferred order, most preferred first.
777 server-preferred order, most preferred first.
743
778
744 The identifiers used by the official Mercurial distribution are:
779 The identifiers used by the official Mercurial distribution are:
745
780
746 bzip2
781 bzip2
747 bzip2
782 bzip2
748 none
783 none
749 uncompressed / raw data
784 uncompressed / raw data
750 zlib
785 zlib
751 zlib (no gzip header)
786 zlib (no gzip header)
752 zstd
787 zstd
753 zstd
788 zstd
754
789
755 This capability was introduced in Mercurial 4.1 (released February 2017).
790 This capability was introduced in Mercurial 4.1 (released February 2017).
756
791
757 getbundle
792 getbundle
758 ---------
793 ---------
759
794
760 Whether the server supports the ``getbundle`` command.
795 Whether the server supports the ``getbundle`` command.
761
796
762 This capability was introduced in Mercurial 1.9 (released July 2011).
797 This capability was introduced in Mercurial 1.9 (released July 2011).
763
798
764 httpheader
799 httpheader
765 ----------
800 ----------
766
801
767 Whether the server supports receiving command arguments via HTTP request
802 Whether the server supports receiving command arguments via HTTP request
768 headers.
803 headers.
769
804
770 The value of the capability is an integer describing the max header
805 The value of the capability is an integer describing the max header
771 length that clients should send. Clients should ignore any content after a
806 length that clients should send. Clients should ignore any content after a
772 comma in the value, as this is reserved for future use.
807 comma in the value, as this is reserved for future use.
773
808
774 This capability was introduced in Mercurial 1.9 (released July 2011).
809 This capability was introduced in Mercurial 1.9 (released July 2011).
775
810
776 httpmediatype
811 httpmediatype
777 -------------
812 -------------
778
813
779 Indicates which HTTP media types (``Content-Type`` header) the server is
814 Indicates which HTTP media types (``Content-Type`` header) the server is
780 capable of receiving and sending.
815 capable of receiving and sending.
781
816
782 The value of the capability is a comma-delimited list of strings identifying
817 The value of the capability is a comma-delimited list of strings identifying
783 support for media type and transmission direction. The following strings may
818 support for media type and transmission direction. The following strings may
784 be present:
819 be present:
785
820
786 0.1rx
821 0.1rx
787 Indicates server support for receiving ``application/mercurial-0.1`` media
822 Indicates server support for receiving ``application/mercurial-0.1`` media
788 types.
823 types.
789
824
790 0.1tx
825 0.1tx
791 Indicates server support for sending ``application/mercurial-0.1`` media
826 Indicates server support for sending ``application/mercurial-0.1`` media
792 types.
827 types.
793
828
794 0.2rx
829 0.2rx
795 Indicates server support for receiving ``application/mercurial-0.2`` media
830 Indicates server support for receiving ``application/mercurial-0.2`` media
796 types.
831 types.
797
832
798 0.2tx
833 0.2tx
799 Indicates server support for sending ``application/mercurial-0.2`` media
834 Indicates server support for sending ``application/mercurial-0.2`` media
800 types.
835 types.
801
836
802 minrx=X
837 minrx=X
803 Minimum media type version the server is capable of receiving. Value is a
838 Minimum media type version the server is capable of receiving. Value is a
804 string like ``0.2``.
839 string like ``0.2``.
805
840
806 This capability can be used by servers to limit connections from legacy
841 This capability can be used by servers to limit connections from legacy
807 clients not using the latest supported media type. However, only clients
842 clients not using the latest supported media type. However, only clients
808 with knowledge of this capability will know to consult this value. This
843 with knowledge of this capability will know to consult this value. This
809 capability is present so the client may issue a more user-friendly error
844 capability is present so the client may issue a more user-friendly error
810 when the server has locked out a legacy client.
845 when the server has locked out a legacy client.
811
846
812 mintx=X
847 mintx=X
813 Minimum media type version the server is capable of sending. Value is a
848 Minimum media type version the server is capable of sending. Value is a
814 string like ``0.1``.
849 string like ``0.1``.
815
850
816 Servers advertising support for the ``application/mercurial-0.2`` media type
851 Servers advertising support for the ``application/mercurial-0.2`` media type
817 should also advertise the ``compression`` capability.
852 should also advertise the ``compression`` capability.
818
853
819 This capability was introduced in Mercurial 4.1 (released February 2017).
854 This capability was introduced in Mercurial 4.1 (released February 2017).
820
855
821 httppostargs
856 httppostargs
822 ------------
857 ------------
823
858
824 **Experimental**
859 **Experimental**
825
860
826 Indicates that the server supports and prefers clients send command arguments
861 Indicates that the server supports and prefers clients send command arguments
827 via a HTTP POST request as part of the request body.
862 via a HTTP POST request as part of the request body.
828
863
829 This capability was introduced in Mercurial 3.8 (released May 2016).
864 This capability was introduced in Mercurial 3.8 (released May 2016).
830
865
831 known
866 known
832 -----
867 -----
833
868
834 Whether the server supports the ``known`` command.
869 Whether the server supports the ``known`` command.
835
870
836 This capability/command was introduced in Mercurial 1.9 (released July 2011).
871 This capability/command was introduced in Mercurial 1.9 (released July 2011).
837
872
838 lookup
873 lookup
839 ------
874 ------
840
875
841 Whether the server supports the ``lookup`` command.
876 Whether the server supports the ``lookup`` command.
842
877
843 This capability was introduced in Mercurial 0.9.2 (released December
878 This capability was introduced in Mercurial 0.9.2 (released December
844 2006).
879 2006).
845
880
846 This capability was introduced at the same time as the ``changegroupsubset``
881 This capability was introduced at the same time as the ``changegroupsubset``
847 capability/command.
882 capability/command.
848
883
849 pushkey
884 pushkey
850 -------
885 -------
851
886
852 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
887 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
853
888
854 This capability was introduced in Mercurial 1.6 (released July 2010).
889 This capability was introduced in Mercurial 1.6 (released July 2010).
855
890
856 standardbundle
891 standardbundle
857 --------------
892 --------------
858
893
859 **Unsupported**
894 **Unsupported**
860
895
861 This capability was introduced during the Mercurial 0.9.2 development cycle in
896 This capability was introduced during the Mercurial 0.9.2 development cycle in
862 2006. It was never present in a release, as it was replaced by the ``unbundle``
897 2006. It was never present in a release, as it was replaced by the ``unbundle``
863 capability. This capability should not be encountered in the wild.
898 capability. This capability should not be encountered in the wild.
864
899
865 stream-preferred
900 stream-preferred
866 ----------------
901 ----------------
867
902
868 If present the server prefers that clients clone using the streaming clone
903 If present the server prefers that clients clone using the streaming clone
869 protocol (``hg clone --stream``) rather than the standard
904 protocol (``hg clone --stream``) rather than the standard
870 changegroup/bundle based protocol.
905 changegroup/bundle based protocol.
871
906
872 This capability was introduced in Mercurial 2.2 (released May 2012).
907 This capability was introduced in Mercurial 2.2 (released May 2012).
873
908
874 streamreqs
909 streamreqs
875 ----------
910 ----------
876
911
877 Indicates whether the server supports *streaming clones* and the *requirements*
912 Indicates whether the server supports *streaming clones* and the *requirements*
878 that clients must support to receive it.
913 that clients must support to receive it.
879
914
880 If present, the server supports the ``stream_out`` command, which transmits
915 If present, the server supports the ``stream_out`` command, which transmits
881 raw revlogs from the repository instead of changegroups. This provides a faster
916 raw revlogs from the repository instead of changegroups. This provides a faster
882 cloning mechanism at the expense of more bandwidth used.
917 cloning mechanism at the expense of more bandwidth used.
883
918
884 The value of this capability is a comma-delimited list of repo format
919 The value of this capability is a comma-delimited list of repo format
885 *requirements*. These are requirements that impact the reading of data in
920 *requirements*. These are requirements that impact the reading of data in
886 the ``.hg/store`` directory. An example value is
921 the ``.hg/store`` directory. An example value is
887 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
922 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
888 the ``revlogv1`` and ``generaldelta`` requirements.
923 the ``revlogv1`` and ``generaldelta`` requirements.
889
924
890 If the only format requirement is ``revlogv1``, the server may expose the
925 If the only format requirement is ``revlogv1``, the server may expose the
891 ``stream`` capability instead of the ``streamreqs`` capability.
926 ``stream`` capability instead of the ``streamreqs`` capability.
892
927
893 This capability was introduced in Mercurial 1.7 (released November 2010).
928 This capability was introduced in Mercurial 1.7 (released November 2010).
894
929
895 stream
930 stream
896 ------
931 ------
897
932
898 Whether the server supports *streaming clones* from ``revlogv1`` repos.
933 Whether the server supports *streaming clones* from ``revlogv1`` repos.
899
934
900 If present, the server supports the ``stream_out`` command, which transmits
935 If present, the server supports the ``stream_out`` command, which transmits
901 raw revlogs from the repository instead of changegroups. This provides a faster
936 raw revlogs from the repository instead of changegroups. This provides a faster
902 cloning mechanism at the expense of more bandwidth used.
937 cloning mechanism at the expense of more bandwidth used.
903
938
904 This capability was introduced in Mercurial 0.9.1 (released July 2006).
939 This capability was introduced in Mercurial 0.9.1 (released July 2006).
905
940
906 When initially introduced, the value of the capability was the numeric
941 When initially introduced, the value of the capability was the numeric
907 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
942 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
908 ``revlogv1``. This simple integer value wasn't powerful enough, so the
943 ``revlogv1``. This simple integer value wasn't powerful enough, so the
909 ``streamreqs`` capability was invented to handle cases where the repo
944 ``streamreqs`` capability was invented to handle cases where the repo
910 requirements have more than just ``revlogv1``. Newer servers omit the
945 requirements have more than just ``revlogv1``. Newer servers omit the
911 ``=1`` since it was the only value supported and the value of ``1`` can
946 ``=1`` since it was the only value supported and the value of ``1`` can
912 be implied by clients.
947 be implied by clients.
913
948
914 unbundlehash
949 unbundlehash
915 ------------
950 ------------
916
951
917 Whether the ``unbundle`` commands supports receiving a hash of all the
952 Whether the ``unbundle`` commands supports receiving a hash of all the
918 heads instead of a list.
953 heads instead of a list.
919
954
920 For more, see the documentation for the ``unbundle`` command.
955 For more, see the documentation for the ``unbundle`` command.
921
956
922 This capability was introduced in Mercurial 1.9 (released July 2011).
957 This capability was introduced in Mercurial 1.9 (released July 2011).
923
958
924 unbundle
959 unbundle
925 --------
960 --------
926
961
927 Whether the server supports pushing via the ``unbundle`` command.
962 Whether the server supports pushing via the ``unbundle`` command.
928
963
929 This capability/command has been present since Mercurial 0.9.1 (released
964 This capability/command has been present since Mercurial 0.9.1 (released
930 July 2006).
965 July 2006).
931
966
932 Mercurial 0.9.2 (released December 2006) added values to the capability
967 Mercurial 0.9.2 (released December 2006) added values to the capability
933 indicating which bundle types the server supports receiving. This value is a
968 indicating which bundle types the server supports receiving. This value is a
934 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
969 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
935 reflects the priority/preference of that type, where the first value is the
970 reflects the priority/preference of that type, where the first value is the
936 most preferred type.
971 most preferred type.
937
972
938 Content Negotiation
973 Content Negotiation
939 ===================
974 ===================
940
975
941 The wire protocol has some mechanisms to help peers determine what content
976 The wire protocol has some mechanisms to help peers determine what content
942 types and encoding the other side will accept. Historically, these mechanisms
977 types and encoding the other side will accept. Historically, these mechanisms
943 have been built into commands themselves because most commands only send a
978 have been built into commands themselves because most commands only send a
944 well-defined response type and only certain commands needed to support
979 well-defined response type and only certain commands needed to support
945 functionality like compression.
980 functionality like compression.
946
981
947 Currently, only the HTTP version 1 transport supports content negotiation
982 Currently, only the HTTP version 1 transport supports content negotiation
948 at the protocol layer.
983 at the protocol layer.
949
984
950 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
985 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
951 request header, where ``<N>`` is an integer starting at 1 allowing the logical
986 request header, where ``<N>`` is an integer starting at 1 allowing the logical
952 value to span multiple headers. This value consists of a list of
987 value to span multiple headers. This value consists of a list of
953 space-delimited parameters. Each parameter denotes a feature or capability.
988 space-delimited parameters. Each parameter denotes a feature or capability.
954
989
955 The following parameters are defined:
990 The following parameters are defined:
956
991
957 0.1
992 0.1
958 Indicates the client supports receiving ``application/mercurial-0.1``
993 Indicates the client supports receiving ``application/mercurial-0.1``
959 responses.
994 responses.
960
995
961 0.2
996 0.2
962 Indicates the client supports receiving ``application/mercurial-0.2``
997 Indicates the client supports receiving ``application/mercurial-0.2``
963 responses.
998 responses.
964
999
965 comp
1000 comp
966 Indicates compression formats the client can decode. Value is a list of
1001 Indicates compression formats the client can decode. Value is a list of
967 comma delimited strings identifying compression formats ordered from
1002 comma delimited strings identifying compression formats ordered from
968 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
1003 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
969
1004
970 This parameter does not have an effect if only the ``0.1`` parameter
1005 This parameter does not have an effect if only the ``0.1`` parameter
971 is defined, as support for ``application/mercurial-0.2`` or greater is
1006 is defined, as support for ``application/mercurial-0.2`` or greater is
972 required to use arbitrary compression formats.
1007 required to use arbitrary compression formats.
973
1008
974 If this parameter is not advertised, the server interprets this as
1009 If this parameter is not advertised, the server interprets this as
975 equivalent to ``zlib,none``.
1010 equivalent to ``zlib,none``.
976
1011
977 Clients may choose to only send this header if the ``httpmediatype``
1012 Clients may choose to only send this header if the ``httpmediatype``
978 server capability is present, as currently all server-side features
1013 server capability is present, as currently all server-side features
979 consulting this header require the client to opt in to new protocol features
1014 consulting this header require the client to opt in to new protocol features
980 advertised via the ``httpmediatype`` capability.
1015 advertised via the ``httpmediatype`` capability.
981
1016
982 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
1017 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
983 value of ``0.1``. This is compatible with legacy clients.
1018 value of ``0.1``. This is compatible with legacy clients.
984
1019
985 A server receiving a request indicating support for multiple media type
1020 A server receiving a request indicating support for multiple media type
986 versions may respond with any of the supported media types. Not all servers
1021 versions may respond with any of the supported media types. Not all servers
987 may support all media types on all commands.
1022 may support all media types on all commands.
988
1023
989 Commands
1024 Commands
990 ========
1025 ========
991
1026
992 This section contains a list of all wire protocol commands implemented by
1027 This section contains a list of all wire protocol commands implemented by
993 the canonical Mercurial server.
1028 the canonical Mercurial server.
994
1029
995 batch
1030 batch
996 -----
1031 -----
997
1032
998 Issue multiple commands while sending a single command request. The purpose
1033 Issue multiple commands while sending a single command request. The purpose
999 of this command is to allow a client to issue multiple commands while avoiding
1034 of this command is to allow a client to issue multiple commands while avoiding
1000 multiple round trips to the server therefore enabling commands to complete
1035 multiple round trips to the server therefore enabling commands to complete
1001 quicker.
1036 quicker.
1002
1037
1003 The command accepts a ``cmds`` argument that contains a list of commands to
1038 The command accepts a ``cmds`` argument that contains a list of commands to
1004 execute.
1039 execute.
1005
1040
1006 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
1041 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
1007 form ``<command> <arguments>``. That is, the command name followed by a space
1042 form ``<command> <arguments>``. That is, the command name followed by a space
1008 followed by an argument string.
1043 followed by an argument string.
1009
1044
1010 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
1045 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
1011 corresponding to command arguments. Both the argument name and value are
1046 corresponding to command arguments. Both the argument name and value are
1012 escaped using a special substitution map::
1047 escaped using a special substitution map::
1013
1048
1014 : -> :c
1049 : -> :c
1015 , -> :o
1050 , -> :o
1016 ; -> :s
1051 ; -> :s
1017 = -> :e
1052 = -> :e
1018
1053
1019 The response type for this command is ``string``. The value contains a
1054 The response type for this command is ``string``. The value contains a
1020 ``;`` delimited list of responses for each requested command. Each value
1055 ``;`` delimited list of responses for each requested command. Each value
1021 in this list is escaped using the same substitution map used for arguments.
1056 in this list is escaped using the same substitution map used for arguments.
1022
1057
1023 If an error occurs, the generic error response may be sent.
1058 If an error occurs, the generic error response may be sent.
1024
1059
1025 between
1060 between
1026 -------
1061 -------
1027
1062
1028 (Legacy command used for discovery in old clients)
1063 (Legacy command used for discovery in old clients)
1029
1064
1030 Obtain nodes between pairs of nodes.
1065 Obtain nodes between pairs of nodes.
1031
1066
1032 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
1067 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
1033 hex node pairs. e.g.::
1068 hex node pairs. e.g.::
1034
1069
1035 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
1070 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
1036
1071
1037 Return type is a ``string``. Value consists of lines corresponding to each
1072 Return type is a ``string``. Value consists of lines corresponding to each
1038 requested range. Each line contains a space-delimited list of hex nodes.
1073 requested range. Each line contains a space-delimited list of hex nodes.
1039 A newline ``\n`` terminates each line, including the last one.
1074 A newline ``\n`` terminates each line, including the last one.
1040
1075
1041 branchmap
1076 branchmap
1042 ---------
1077 ---------
1043
1078
1044 Obtain heads in named branches.
1079 Obtain heads in named branches.
1045
1080
1046 Accepts no arguments. Return type is a ``string``.
1081 Accepts no arguments. Return type is a ``string``.
1047
1082
1048 Return value contains lines with URL encoded branch names followed by a space
1083 Return value contains lines with URL encoded branch names followed by a space
1049 followed by a space-delimited list of hex nodes of heads on that branch.
1084 followed by a space-delimited list of hex nodes of heads on that branch.
1050 e.g.::
1085 e.g.::
1051
1086
1052 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
1087 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
1053 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
1088 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
1054
1089
1055 There is no trailing newline.
1090 There is no trailing newline.
1056
1091
1057 branches
1092 branches
1058 --------
1093 --------
1059
1094
1060 (Legacy command used for discovery in old clients. Clients with ``getbundle``
1095 (Legacy command used for discovery in old clients. Clients with ``getbundle``
1061 use the ``known`` and ``heads`` commands instead.)
1096 use the ``known`` and ``heads`` commands instead.)
1062
1097
1063 Obtain ancestor changesets of specific nodes back to a branch point.
1098 Obtain ancestor changesets of specific nodes back to a branch point.
1064
1099
1065 Despite the name, this command has nothing to do with Mercurial named branches.
1100 Despite the name, this command has nothing to do with Mercurial named branches.
1066 Instead, it is related to DAG branches.
1101 Instead, it is related to DAG branches.
1067
1102
1068 The command accepts a ``nodes`` argument, which is a string of space-delimited
1103 The command accepts a ``nodes`` argument, which is a string of space-delimited
1069 hex nodes.
1104 hex nodes.
1070
1105
1071 For each node requested, the server will find the first ancestor node that is
1106 For each node requested, the server will find the first ancestor node that is
1072 a DAG root or is a merge.
1107 a DAG root or is a merge.
1073
1108
1074 Return type is a ``string``. Return value contains lines with result data for
1109 Return type is a ``string``. Return value contains lines with result data for
1075 each requested node. Each line contains space-delimited nodes followed by a
1110 each requested node. Each line contains space-delimited nodes followed by a
1076 newline (``\n``). The 4 nodes reported on each line correspond to the requested
1111 newline (``\n``). The 4 nodes reported on each line correspond to the requested
1077 node, the ancestor node found, and its 2 parent nodes (which may be the null
1112 node, the ancestor node found, and its 2 parent nodes (which may be the null
1078 node).
1113 node).
1079
1114
1080 capabilities
1115 capabilities
1081 ------------
1116 ------------
1082
1117
1083 Obtain the capabilities string for the repo.
1118 Obtain the capabilities string for the repo.
1084
1119
1085 Unlike the ``hello`` command, the capabilities string is not prefixed.
1120 Unlike the ``hello`` command, the capabilities string is not prefixed.
1086 There is no trailing newline.
1121 There is no trailing newline.
1087
1122
1088 This command does not accept any arguments. Return type is a ``string``.
1123 This command does not accept any arguments. Return type is a ``string``.
1089
1124
1090 This command was introduced in Mercurial 0.9.1 (released July 2006).
1125 This command was introduced in Mercurial 0.9.1 (released July 2006).
1091
1126
1092 changegroup
1127 changegroup
1093 -----------
1128 -----------
1094
1129
1095 (Legacy command: use ``getbundle`` instead)
1130 (Legacy command: use ``getbundle`` instead)
1096
1131
1097 Obtain a changegroup version 1 with data for changesets that are
1132 Obtain a changegroup version 1 with data for changesets that are
1098 descendants of client-specified changesets.
1133 descendants of client-specified changesets.
1099
1134
1100 The ``roots`` arguments contains a list of space-delimited hex nodes.
1135 The ``roots`` arguments contains a list of space-delimited hex nodes.
1101
1136
1102 The server responds with a changegroup version 1 containing all
1137 The server responds with a changegroup version 1 containing all
1103 changesets between the requested root/base nodes and the repo's head nodes
1138 changesets between the requested root/base nodes and the repo's head nodes
1104 at the time of the request.
1139 at the time of the request.
1105
1140
1106 The return type is a ``stream``.
1141 The return type is a ``stream``.
1107
1142
1108 changegroupsubset
1143 changegroupsubset
1109 -----------------
1144 -----------------
1110
1145
1111 (Legacy command: use ``getbundle`` instead)
1146 (Legacy command: use ``getbundle`` instead)
1112
1147
1113 Obtain a changegroup version 1 with data for changesetsets between
1148 Obtain a changegroup version 1 with data for changesetsets between
1114 client specified base and head nodes.
1149 client specified base and head nodes.
1115
1150
1116 The ``bases`` argument contains a list of space-delimited hex nodes.
1151 The ``bases`` argument contains a list of space-delimited hex nodes.
1117 The ``heads`` argument contains a list of space-delimited hex nodes.
1152 The ``heads`` argument contains a list of space-delimited hex nodes.
1118
1153
1119 The server responds with a changegroup version 1 containing all
1154 The server responds with a changegroup version 1 containing all
1120 changesets between the requested base and head nodes at the time of the
1155 changesets between the requested base and head nodes at the time of the
1121 request.
1156 request.
1122
1157
1123 The return type is a ``stream``.
1158 The return type is a ``stream``.
1124
1159
1125 clonebundles
1160 clonebundles
1126 ------------
1161 ------------
1127
1162
1128 Obtains a manifest of bundle URLs available to seed clones.
1163 Obtains a manifest of bundle URLs available to seed clones.
1129
1164
1130 Each returned line contains a URL followed by metadata. See the
1165 Each returned line contains a URL followed by metadata. See the
1131 documentation in the ``clonebundles`` extension for more.
1166 documentation in the ``clonebundles`` extension for more.
1132
1167
1133 The return type is a ``string``.
1168 The return type is a ``string``.
1134
1169
1135 getbundle
1170 getbundle
1136 ---------
1171 ---------
1137
1172
1138 Obtain a bundle containing repository data.
1173 Obtain a bundle containing repository data.
1139
1174
1140 This command accepts the following arguments:
1175 This command accepts the following arguments:
1141
1176
1142 heads
1177 heads
1143 List of space-delimited hex nodes of heads to retrieve.
1178 List of space-delimited hex nodes of heads to retrieve.
1144 common
1179 common
1145 List of space-delimited hex nodes that the client has in common with the
1180 List of space-delimited hex nodes that the client has in common with the
1146 server.
1181 server.
1147 obsmarkers
1182 obsmarkers
1148 Boolean indicating whether to include obsolescence markers as part
1183 Boolean indicating whether to include obsolescence markers as part
1149 of the response. Only works with bundle2.
1184 of the response. Only works with bundle2.
1150 bundlecaps
1185 bundlecaps
1151 Comma-delimited set of strings defining client bundle capabilities.
1186 Comma-delimited set of strings defining client bundle capabilities.
1152 listkeys
1187 listkeys
1153 Comma-delimited list of strings of ``pushkey`` namespaces. For each
1188 Comma-delimited list of strings of ``pushkey`` namespaces. For each
1154 namespace listed, a bundle2 part will be included with the content of
1189 namespace listed, a bundle2 part will be included with the content of
1155 that namespace.
1190 that namespace.
1156 cg
1191 cg
1157 Boolean indicating whether changegroup data is requested.
1192 Boolean indicating whether changegroup data is requested.
1158 cbattempted
1193 cbattempted
1159 Boolean indicating whether the client attempted to use the *clone bundles*
1194 Boolean indicating whether the client attempted to use the *clone bundles*
1160 feature before performing this request.
1195 feature before performing this request.
1161 bookmarks
1196 bookmarks
1162 Boolean indicating whether bookmark data is requested.
1197 Boolean indicating whether bookmark data is requested.
1163 phases
1198 phases
1164 Boolean indicating whether phases data is requested.
1199 Boolean indicating whether phases data is requested.
1165
1200
1166 The return type on success is a ``stream`` where the value is bundle.
1201 The return type on success is a ``stream`` where the value is bundle.
1167 On the HTTP version 1 transport, the response is zlib compressed.
1202 On the HTTP version 1 transport, the response is zlib compressed.
1168
1203
1169 If an error occurs, a generic error response can be sent.
1204 If an error occurs, a generic error response can be sent.
1170
1205
1171 Unless the client sends a false value for the ``cg`` argument, the returned
1206 Unless the client sends a false value for the ``cg`` argument, the returned
1172 bundle contains a changegroup with the nodes between the specified ``common``
1207 bundle contains a changegroup with the nodes between the specified ``common``
1173 and ``heads`` nodes. Depending on the command arguments, the type and content
1208 and ``heads`` nodes. Depending on the command arguments, the type and content
1174 of the returned bundle can vary significantly.
1209 of the returned bundle can vary significantly.
1175
1210
1176 The default behavior is for the server to send a raw changegroup version
1211 The default behavior is for the server to send a raw changegroup version
1177 ``01`` response.
1212 ``01`` response.
1178
1213
1179 If the ``bundlecaps`` provided by the client contain a value beginning
1214 If the ``bundlecaps`` provided by the client contain a value beginning
1180 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
1215 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
1181 additional repository data, such as ``pushkey`` namespace values.
1216 additional repository data, such as ``pushkey`` namespace values.
1182
1217
1183 heads
1218 heads
1184 -----
1219 -----
1185
1220
1186 Returns a list of space-delimited hex nodes of repository heads followed
1221 Returns a list of space-delimited hex nodes of repository heads followed
1187 by a newline. e.g.
1222 by a newline. e.g.
1188 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
1223 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
1189
1224
1190 This command does not accept any arguments. The return type is a ``string``.
1225 This command does not accept any arguments. The return type is a ``string``.
1191
1226
1192 hello
1227 hello
1193 -----
1228 -----
1194
1229
1195 Returns lines describing interesting things about the server in an RFC-822
1230 Returns lines describing interesting things about the server in an RFC-822
1196 like format.
1231 like format.
1197
1232
1198 Currently, the only line defines the server capabilities. It has the form::
1233 Currently, the only line defines the server capabilities. It has the form::
1199
1234
1200 capabilities: <value>
1235 capabilities: <value>
1201
1236
1202 See above for more about the capabilities string.
1237 See above for more about the capabilities string.
1203
1238
1204 SSH clients typically issue this command as soon as a connection is
1239 SSH clients typically issue this command as soon as a connection is
1205 established.
1240 established.
1206
1241
1207 This command does not accept any arguments. The return type is a ``string``.
1242 This command does not accept any arguments. The return type is a ``string``.
1208
1243
1209 This command was introduced in Mercurial 0.9.1 (released July 2006).
1244 This command was introduced in Mercurial 0.9.1 (released July 2006).
1210
1245
1211 listkeys
1246 listkeys
1212 --------
1247 --------
1213
1248
1214 List values in a specified ``pushkey`` namespace.
1249 List values in a specified ``pushkey`` namespace.
1215
1250
1216 The ``namespace`` argument defines the pushkey namespace to operate on.
1251 The ``namespace`` argument defines the pushkey namespace to operate on.
1217
1252
1218 The return type is a ``string``. The value is an encoded dictionary of keys.
1253 The return type is a ``string``. The value is an encoded dictionary of keys.
1219
1254
1220 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
1255 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
1221 values are separated by a tab (``\t``). Keys and values are both strings.
1256 values are separated by a tab (``\t``). Keys and values are both strings.
1222
1257
1223 lookup
1258 lookup
1224 ------
1259 ------
1225
1260
1226 Try to resolve a value to a known repository revision.
1261 Try to resolve a value to a known repository revision.
1227
1262
1228 The ``key`` argument is converted from bytes to an
1263 The ``key`` argument is converted from bytes to an
1229 ``encoding.localstr`` instance then passed into
1264 ``encoding.localstr`` instance then passed into
1230 ``localrepository.__getitem__`` in an attempt to resolve it.
1265 ``localrepository.__getitem__`` in an attempt to resolve it.
1231
1266
1232 The return type is a ``string``.
1267 The return type is a ``string``.
1233
1268
1234 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
1269 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
1235 returns ``0 <error string>\n``. e.g.::
1270 returns ``0 <error string>\n``. e.g.::
1236
1271
1237 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
1272 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
1238
1273
1239 0 unknown revision 'foo'\n
1274 0 unknown revision 'foo'\n
1240
1275
1241 known
1276 known
1242 -----
1277 -----
1243
1278
1244 Determine whether multiple nodes are known.
1279 Determine whether multiple nodes are known.
1245
1280
1246 The ``nodes`` argument is a list of space-delimited hex nodes to check
1281 The ``nodes`` argument is a list of space-delimited hex nodes to check
1247 for existence.
1282 for existence.
1248
1283
1249 The return type is ``string``.
1284 The return type is ``string``.
1250
1285
1251 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1286 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1252 are known. If the Nth node specified in the ``nodes`` argument is known,
1287 are known. If the Nth node specified in the ``nodes`` argument is known,
1253 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1288 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1254 will be present at byte offset N.
1289 will be present at byte offset N.
1255
1290
1256 There is no trailing newline.
1291 There is no trailing newline.
1257
1292
1258 pushkey
1293 pushkey
1259 -------
1294 -------
1260
1295
1261 Set a value using the ``pushkey`` protocol.
1296 Set a value using the ``pushkey`` protocol.
1262
1297
1263 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1298 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1264 correspond to the pushkey namespace to operate on, the key within that
1299 correspond to the pushkey namespace to operate on, the key within that
1265 namespace to change, the old value (which may be empty), and the new value.
1300 namespace to change, the old value (which may be empty), and the new value.
1266 All arguments are string types.
1301 All arguments are string types.
1267
1302
1268 The return type is a ``string``. The value depends on the transport protocol.
1303 The return type is a ``string``. The value depends on the transport protocol.
1269
1304
1270 The SSH version 1 transport sends a string encoded integer followed by a
1305 The SSH version 1 transport sends a string encoded integer followed by a
1271 newline (``\n``) which indicates operation result. The server may send
1306 newline (``\n``) which indicates operation result. The server may send
1272 additional output on the ``stderr`` stream that should be displayed to the
1307 additional output on the ``stderr`` stream that should be displayed to the
1273 user.
1308 user.
1274
1309
1275 The HTTP version 1 transport sends a string encoded integer followed by a
1310 The HTTP version 1 transport sends a string encoded integer followed by a
1276 newline followed by additional server output that should be displayed to
1311 newline followed by additional server output that should be displayed to
1277 the user. This may include output from hooks, etc.
1312 the user. This may include output from hooks, etc.
1278
1313
1279 The integer result varies by namespace. ``0`` means an error has occurred
1314 The integer result varies by namespace. ``0`` means an error has occurred
1280 and there should be additional output to display to the user.
1315 and there should be additional output to display to the user.
1281
1316
1282 stream_out
1317 stream_out
1283 ----------
1318 ----------
1284
1319
1285 Obtain *streaming clone* data.
1320 Obtain *streaming clone* data.
1286
1321
1287 The return type is either a ``string`` or a ``stream``, depending on
1322 The return type is either a ``string`` or a ``stream``, depending on
1288 whether the request was fulfilled properly.
1323 whether the request was fulfilled properly.
1289
1324
1290 A return value of ``1\n`` indicates the server is not configured to serve
1325 A return value of ``1\n`` indicates the server is not configured to serve
1291 this data. If this is seen by the client, they may not have verified the
1326 this data. If this is seen by the client, they may not have verified the
1292 ``stream`` capability is set before making the request.
1327 ``stream`` capability is set before making the request.
1293
1328
1294 A return value of ``2\n`` indicates the server was unable to lock the
1329 A return value of ``2\n`` indicates the server was unable to lock the
1295 repository to generate data.
1330 repository to generate data.
1296
1331
1297 All other responses are a ``stream`` of bytes. The first line of this data
1332 All other responses are a ``stream`` of bytes. The first line of this data
1298 contains 2 space-delimited integers corresponding to the path count and
1333 contains 2 space-delimited integers corresponding to the path count and
1299 payload size, respectively::
1334 payload size, respectively::
1300
1335
1301 <path count> <payload size>\n
1336 <path count> <payload size>\n
1302
1337
1303 The ``<payload size>`` is the total size of path data: it does not include
1338 The ``<payload size>`` is the total size of path data: it does not include
1304 the size of the per-path header lines.
1339 the size of the per-path header lines.
1305
1340
1306 Following that header are ``<path count>`` entries. Each entry consists of a
1341 Following that header are ``<path count>`` entries. Each entry consists of a
1307 line with metadata followed by raw revlog data. The line consists of::
1342 line with metadata followed by raw revlog data. The line consists of::
1308
1343
1309 <store path>\0<size>\n
1344 <store path>\0<size>\n
1310
1345
1311 The ``<store path>`` is the encoded store path of the data that follows.
1346 The ``<store path>`` is the encoded store path of the data that follows.
1312 ``<size>`` is the amount of data for this store path/revlog that follows the
1347 ``<size>`` is the amount of data for this store path/revlog that follows the
1313 newline.
1348 newline.
1314
1349
1315 There is no trailer to indicate end of data. Instead, the client should stop
1350 There is no trailer to indicate end of data. Instead, the client should stop
1316 reading after ``<path count>`` entries are consumed.
1351 reading after ``<path count>`` entries are consumed.
1317
1352
1318 unbundle
1353 unbundle
1319 --------
1354 --------
1320
1355
1321 Send a bundle containing data (usually changegroup data) to the server.
1356 Send a bundle containing data (usually changegroup data) to the server.
1322
1357
1323 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1358 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1324 corresponding to server repository heads observed by the client. This is used
1359 corresponding to server repository heads observed by the client. This is used
1325 to detect race conditions and abort push operations before a server performs
1360 to detect race conditions and abort push operations before a server performs
1326 too much work or a client transfers too much data.
1361 too much work or a client transfers too much data.
1327
1362
1328 The request payload consists of a bundle to be applied to the repository,
1363 The request payload consists of a bundle to be applied to the repository,
1329 similarly to as if :hg:`unbundle` were called.
1364 similarly to as if :hg:`unbundle` were called.
1330
1365
1331 In most scenarios, a special ``push response`` type is returned. This type
1366 In most scenarios, a special ``push response`` type is returned. This type
1332 contains an integer describing the change in heads as a result of the
1367 contains an integer describing the change in heads as a result of the
1333 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1368 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1334 of heads remained the same. Values ``2`` and larger indicate the number of
1369 of heads remained the same. Values ``2`` and larger indicate the number of
1335 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1370 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1336 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1371 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1337 is 1 fewer head.
1372 is 1 fewer head.
1338
1373
1339 The encoding of the ``push response`` type varies by transport.
1374 The encoding of the ``push response`` type varies by transport.
1340
1375
1341 For the SSH version 1 transport, this type is composed of 2 ``string``
1376 For the SSH version 1 transport, this type is composed of 2 ``string``
1342 responses: an empty response (``0\n``) followed by the integer result value.
1377 responses: an empty response (``0\n``) followed by the integer result value.
1343 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1378 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1344
1379
1345 For the HTTP version 1 transport, the response is a ``string`` type composed
1380 For the HTTP version 1 transport, the response is a ``string`` type composed
1346 of an integer result value followed by a newline (``\n``) followed by string
1381 of an integer result value followed by a newline (``\n``) followed by string
1347 content holding server output that should be displayed on the client (output
1382 content holding server output that should be displayed on the client (output
1348 hooks, etc).
1383 hooks, etc).
1349
1384
1350 In some cases, the server may respond with a ``bundle2`` bundle. In this
1385 In some cases, the server may respond with a ``bundle2`` bundle. In this
1351 case, the response type is ``stream``. For the HTTP version 1 transport, the
1386 case, the response type is ``stream``. For the HTTP version 1 transport, the
1352 response is zlib compressed.
1387 response is zlib compressed.
1353
1388
1354 The server may also respond with a generic error type, which contains a string
1389 The server may also respond with a generic error type, which contains a string
1355 indicating the failure.
1390 indicating the failure.
@@ -1,516 +1,535
1 # wireprotoframing.py - unified framing protocol for wire protocol
1 # wireprotoframing.py - unified framing protocol for wire protocol
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # This file contains functionality to support the unified frame-based wire
8 # This file contains functionality to support the unified frame-based wire
9 # protocol. For details about the protocol, see
9 # protocol. For details about the protocol, see
10 # `hg help internals.wireprotocol`.
10 # `hg help internals.wireprotocol`.
11
11
12 from __future__ import absolute_import
12 from __future__ import absolute_import
13
13
14 import struct
14 import struct
15
15
16 from .i18n import _
16 from .i18n import _
17 from . import (
17 from . import (
18 error,
18 error,
19 util,
19 util,
20 )
20 )
21
21
22 FRAME_HEADER_SIZE = 4
22 FRAME_HEADER_SIZE = 6
23 DEFAULT_MAX_FRAME_SIZE = 32768
23 DEFAULT_MAX_FRAME_SIZE = 32768
24
24
25 FRAME_TYPE_COMMAND_NAME = 0x01
25 FRAME_TYPE_COMMAND_NAME = 0x01
26 FRAME_TYPE_COMMAND_ARGUMENT = 0x02
26 FRAME_TYPE_COMMAND_ARGUMENT = 0x02
27 FRAME_TYPE_COMMAND_DATA = 0x03
27 FRAME_TYPE_COMMAND_DATA = 0x03
28 FRAME_TYPE_BYTES_RESPONSE = 0x04
28 FRAME_TYPE_BYTES_RESPONSE = 0x04
29 FRAME_TYPE_ERROR_RESPONSE = 0x05
29 FRAME_TYPE_ERROR_RESPONSE = 0x05
30
30
31 FRAME_TYPES = {
31 FRAME_TYPES = {
32 b'command-name': FRAME_TYPE_COMMAND_NAME,
32 b'command-name': FRAME_TYPE_COMMAND_NAME,
33 b'command-argument': FRAME_TYPE_COMMAND_ARGUMENT,
33 b'command-argument': FRAME_TYPE_COMMAND_ARGUMENT,
34 b'command-data': FRAME_TYPE_COMMAND_DATA,
34 b'command-data': FRAME_TYPE_COMMAND_DATA,
35 b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
35 b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
36 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
36 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
37 }
37 }
38
38
39 FLAG_COMMAND_NAME_EOS = 0x01
39 FLAG_COMMAND_NAME_EOS = 0x01
40 FLAG_COMMAND_NAME_HAVE_ARGS = 0x02
40 FLAG_COMMAND_NAME_HAVE_ARGS = 0x02
41 FLAG_COMMAND_NAME_HAVE_DATA = 0x04
41 FLAG_COMMAND_NAME_HAVE_DATA = 0x04
42
42
43 FLAGS_COMMAND = {
43 FLAGS_COMMAND = {
44 b'eos': FLAG_COMMAND_NAME_EOS,
44 b'eos': FLAG_COMMAND_NAME_EOS,
45 b'have-args': FLAG_COMMAND_NAME_HAVE_ARGS,
45 b'have-args': FLAG_COMMAND_NAME_HAVE_ARGS,
46 b'have-data': FLAG_COMMAND_NAME_HAVE_DATA,
46 b'have-data': FLAG_COMMAND_NAME_HAVE_DATA,
47 }
47 }
48
48
49 FLAG_COMMAND_ARGUMENT_CONTINUATION = 0x01
49 FLAG_COMMAND_ARGUMENT_CONTINUATION = 0x01
50 FLAG_COMMAND_ARGUMENT_EOA = 0x02
50 FLAG_COMMAND_ARGUMENT_EOA = 0x02
51
51
52 FLAGS_COMMAND_ARGUMENT = {
52 FLAGS_COMMAND_ARGUMENT = {
53 b'continuation': FLAG_COMMAND_ARGUMENT_CONTINUATION,
53 b'continuation': FLAG_COMMAND_ARGUMENT_CONTINUATION,
54 b'eoa': FLAG_COMMAND_ARGUMENT_EOA,
54 b'eoa': FLAG_COMMAND_ARGUMENT_EOA,
55 }
55 }
56
56
57 FLAG_COMMAND_DATA_CONTINUATION = 0x01
57 FLAG_COMMAND_DATA_CONTINUATION = 0x01
58 FLAG_COMMAND_DATA_EOS = 0x02
58 FLAG_COMMAND_DATA_EOS = 0x02
59
59
60 FLAGS_COMMAND_DATA = {
60 FLAGS_COMMAND_DATA = {
61 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
61 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
62 b'eos': FLAG_COMMAND_DATA_EOS,
62 b'eos': FLAG_COMMAND_DATA_EOS,
63 }
63 }
64
64
65 FLAG_BYTES_RESPONSE_CONTINUATION = 0x01
65 FLAG_BYTES_RESPONSE_CONTINUATION = 0x01
66 FLAG_BYTES_RESPONSE_EOS = 0x02
66 FLAG_BYTES_RESPONSE_EOS = 0x02
67
67
68 FLAGS_BYTES_RESPONSE = {
68 FLAGS_BYTES_RESPONSE = {
69 b'continuation': FLAG_BYTES_RESPONSE_CONTINUATION,
69 b'continuation': FLAG_BYTES_RESPONSE_CONTINUATION,
70 b'eos': FLAG_BYTES_RESPONSE_EOS,
70 b'eos': FLAG_BYTES_RESPONSE_EOS,
71 }
71 }
72
72
73 FLAG_ERROR_RESPONSE_PROTOCOL = 0x01
73 FLAG_ERROR_RESPONSE_PROTOCOL = 0x01
74 FLAG_ERROR_RESPONSE_APPLICATION = 0x02
74 FLAG_ERROR_RESPONSE_APPLICATION = 0x02
75
75
76 FLAGS_ERROR_RESPONSE = {
76 FLAGS_ERROR_RESPONSE = {
77 b'protocol': FLAG_ERROR_RESPONSE_PROTOCOL,
77 b'protocol': FLAG_ERROR_RESPONSE_PROTOCOL,
78 b'application': FLAG_ERROR_RESPONSE_APPLICATION,
78 b'application': FLAG_ERROR_RESPONSE_APPLICATION,
79 }
79 }
80
80
81 # Maps frame types to their available flags.
81 # Maps frame types to their available flags.
82 FRAME_TYPE_FLAGS = {
82 FRAME_TYPE_FLAGS = {
83 FRAME_TYPE_COMMAND_NAME: FLAGS_COMMAND,
83 FRAME_TYPE_COMMAND_NAME: FLAGS_COMMAND,
84 FRAME_TYPE_COMMAND_ARGUMENT: FLAGS_COMMAND_ARGUMENT,
84 FRAME_TYPE_COMMAND_ARGUMENT: FLAGS_COMMAND_ARGUMENT,
85 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
85 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
86 FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
86 FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
87 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
87 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
88 }
88 }
89
89
90 ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH')
90 ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH')
91
91
92 def makeframe(frametype, frameflags, payload):
92 def makeframe(requestid, frametype, frameflags, payload):
93 """Assemble a frame into a byte array."""
93 """Assemble a frame into a byte array."""
94 # TODO assert size of payload.
94 # TODO assert size of payload.
95 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
95 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
96
96
97 # 24 bits length
98 # 16 bits request id
99 # 4 bits type
100 # 4 bits flags
101
97 l = struct.pack(r'<I', len(payload))
102 l = struct.pack(r'<I', len(payload))
98 frame[0:3] = l[0:3]
103 frame[0:3] = l[0:3]
99 frame[3] = (frametype << 4) | frameflags
104 struct.pack_into(r'<H', frame, 3, requestid)
100 frame[4:] = payload
105 frame[5] = (frametype << 4) | frameflags
106 frame[6:] = payload
101
107
102 return frame
108 return frame
103
109
104 def makeframefromhumanstring(s):
110 def makeframefromhumanstring(s):
105 """Given a string of the form: <type> <flags> <payload>, creates a frame.
111 """Create a frame from a human readable string
112
113 Strings have the form:
114
115 <request-id> <type> <flags> <payload>
106
116
107 This can be used by user-facing applications and tests for creating
117 This can be used by user-facing applications and tests for creating
108 frames easily without having to type out a bunch of constants.
118 frames easily without having to type out a bunch of constants.
109
119
120 Request ID is an integer.
121
110 Frame type and flags can be specified by integer or named constant.
122 Frame type and flags can be specified by integer or named constant.
123
111 Flags can be delimited by `|` to bitwise OR them together.
124 Flags can be delimited by `|` to bitwise OR them together.
112 """
125 """
113 frametype, frameflags, payload = s.split(b' ', 2)
126 requestid, frametype, frameflags, payload = s.split(b' ', 3)
127
128 requestid = int(requestid)
114
129
115 if frametype in FRAME_TYPES:
130 if frametype in FRAME_TYPES:
116 frametype = FRAME_TYPES[frametype]
131 frametype = FRAME_TYPES[frametype]
117 else:
132 else:
118 frametype = int(frametype)
133 frametype = int(frametype)
119
134
120 finalflags = 0
135 finalflags = 0
121 validflags = FRAME_TYPE_FLAGS[frametype]
136 validflags = FRAME_TYPE_FLAGS[frametype]
122 for flag in frameflags.split(b'|'):
137 for flag in frameflags.split(b'|'):
123 if flag in validflags:
138 if flag in validflags:
124 finalflags |= validflags[flag]
139 finalflags |= validflags[flag]
125 else:
140 else:
126 finalflags |= int(flag)
141 finalflags |= int(flag)
127
142
128 payload = util.unescapestr(payload)
143 payload = util.unescapestr(payload)
129
144
130 return makeframe(frametype, finalflags, payload)
145 return makeframe(requestid, frametype, finalflags, payload)
131
146
132 def parseheader(data):
147 def parseheader(data):
133 """Parse a unified framing protocol frame header from a buffer.
148 """Parse a unified framing protocol frame header from a buffer.
134
149
135 The header is expected to be in the buffer at offset 0 and the
150 The header is expected to be in the buffer at offset 0 and the
136 buffer is expected to be large enough to hold a full header.
151 buffer is expected to be large enough to hold a full header.
137 """
152 """
138 # 24 bits payload length (little endian)
153 # 24 bits payload length (little endian)
139 # 4 bits frame type
154 # 4 bits frame type
140 # 4 bits frame flags
155 # 4 bits frame flags
141 # ... payload
156 # ... payload
142 framelength = data[0] + 256 * data[1] + 16384 * data[2]
157 framelength = data[0] + 256 * data[1] + 16384 * data[2]
143 typeflags = data[3]
158 requestid = struct.unpack_from(r'<H', data, 3)[0]
159 typeflags = data[5]
144
160
145 frametype = (typeflags & 0xf0) >> 4
161 frametype = (typeflags & 0xf0) >> 4
146 frameflags = typeflags & 0x0f
162 frameflags = typeflags & 0x0f
147
163
148 return frametype, frameflags, framelength
164 return requestid, frametype, frameflags, framelength
149
165
150 def readframe(fh):
166 def readframe(fh):
151 """Read a unified framing protocol frame from a file object.
167 """Read a unified framing protocol frame from a file object.
152
168
153 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
169 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
154 None if no frame is available. May raise if a malformed frame is
170 None if no frame is available. May raise if a malformed frame is
155 seen.
171 seen.
156 """
172 """
157 header = bytearray(FRAME_HEADER_SIZE)
173 header = bytearray(FRAME_HEADER_SIZE)
158
174
159 readcount = fh.readinto(header)
175 readcount = fh.readinto(header)
160
176
161 if readcount == 0:
177 if readcount == 0:
162 return None
178 return None
163
179
164 if readcount != FRAME_HEADER_SIZE:
180 if readcount != FRAME_HEADER_SIZE:
165 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
181 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
166 (readcount, header))
182 (readcount, header))
167
183
168 frametype, frameflags, framelength = parseheader(header)
184 requestid, frametype, frameflags, framelength = parseheader(header)
169
185
170 payload = fh.read(framelength)
186 payload = fh.read(framelength)
171 if len(payload) != framelength:
187 if len(payload) != framelength:
172 raise error.Abort(_('frame length error: expected %d; got %d') %
188 raise error.Abort(_('frame length error: expected %d; got %d') %
173 (framelength, len(payload)))
189 (framelength, len(payload)))
174
190
175 return frametype, frameflags, payload
191 return requestid, frametype, frameflags, payload
176
192
177 def createcommandframes(cmd, args, datafh=None):
193 def createcommandframes(requestid, cmd, args, datafh=None):
178 """Create frames necessary to transmit a request to run a command.
194 """Create frames necessary to transmit a request to run a command.
179
195
180 This is a generator of bytearrays. Each item represents a frame
196 This is a generator of bytearrays. Each item represents a frame
181 ready to be sent over the wire to a peer.
197 ready to be sent over the wire to a peer.
182 """
198 """
183 flags = 0
199 flags = 0
184 if args:
200 if args:
185 flags |= FLAG_COMMAND_NAME_HAVE_ARGS
201 flags |= FLAG_COMMAND_NAME_HAVE_ARGS
186 if datafh:
202 if datafh:
187 flags |= FLAG_COMMAND_NAME_HAVE_DATA
203 flags |= FLAG_COMMAND_NAME_HAVE_DATA
188
204
189 if not flags:
205 if not flags:
190 flags |= FLAG_COMMAND_NAME_EOS
206 flags |= FLAG_COMMAND_NAME_EOS
191
207
192 yield makeframe(FRAME_TYPE_COMMAND_NAME, flags, cmd)
208 yield makeframe(requestid, FRAME_TYPE_COMMAND_NAME, flags, cmd)
193
209
194 for i, k in enumerate(sorted(args)):
210 for i, k in enumerate(sorted(args)):
195 v = args[k]
211 v = args[k]
196 last = i == len(args) - 1
212 last = i == len(args) - 1
197
213
198 # TODO handle splitting of argument values across frames.
214 # TODO handle splitting of argument values across frames.
199 payload = bytearray(ARGUMENT_FRAME_HEADER.size + len(k) + len(v))
215 payload = bytearray(ARGUMENT_FRAME_HEADER.size + len(k) + len(v))
200 offset = 0
216 offset = 0
201 ARGUMENT_FRAME_HEADER.pack_into(payload, offset, len(k), len(v))
217 ARGUMENT_FRAME_HEADER.pack_into(payload, offset, len(k), len(v))
202 offset += ARGUMENT_FRAME_HEADER.size
218 offset += ARGUMENT_FRAME_HEADER.size
203 payload[offset:offset + len(k)] = k
219 payload[offset:offset + len(k)] = k
204 offset += len(k)
220 offset += len(k)
205 payload[offset:offset + len(v)] = v
221 payload[offset:offset + len(v)] = v
206
222
207 flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0
223 flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0
208 yield makeframe(FRAME_TYPE_COMMAND_ARGUMENT, flags, payload)
224 yield makeframe(requestid, FRAME_TYPE_COMMAND_ARGUMENT, flags, payload)
209
225
210 if datafh:
226 if datafh:
211 while True:
227 while True:
212 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
228 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
213
229
214 done = False
230 done = False
215 if len(data) == DEFAULT_MAX_FRAME_SIZE:
231 if len(data) == DEFAULT_MAX_FRAME_SIZE:
216 flags = FLAG_COMMAND_DATA_CONTINUATION
232 flags = FLAG_COMMAND_DATA_CONTINUATION
217 else:
233 else:
218 flags = FLAG_COMMAND_DATA_EOS
234 flags = FLAG_COMMAND_DATA_EOS
219 assert datafh.read(1) == b''
235 assert datafh.read(1) == b''
220 done = True
236 done = True
221
237
222 yield makeframe(FRAME_TYPE_COMMAND_DATA, flags, data)
238 yield makeframe(requestid, FRAME_TYPE_COMMAND_DATA, flags, data)
223
239
224 if done:
240 if done:
225 break
241 break
226
242
227 def createbytesresponseframesfrombytes(data,
243 def createbytesresponseframesfrombytes(requestid, data,
228 maxframesize=DEFAULT_MAX_FRAME_SIZE):
244 maxframesize=DEFAULT_MAX_FRAME_SIZE):
229 """Create a raw frame to send a bytes response from static bytes input.
245 """Create a raw frame to send a bytes response from static bytes input.
230
246
231 Returns a generator of bytearrays.
247 Returns a generator of bytearrays.
232 """
248 """
233
249
234 # Simple case of a single frame.
250 # Simple case of a single frame.
235 if len(data) <= maxframesize:
251 if len(data) <= maxframesize:
236 yield makeframe(FRAME_TYPE_BYTES_RESPONSE,
252 yield makeframe(requestid, FRAME_TYPE_BYTES_RESPONSE,
237 FLAG_BYTES_RESPONSE_EOS, data)
253 FLAG_BYTES_RESPONSE_EOS, data)
238 return
254 return
239
255
240 offset = 0
256 offset = 0
241 while True:
257 while True:
242 chunk = data[offset:offset + maxframesize]
258 chunk = data[offset:offset + maxframesize]
243 offset += len(chunk)
259 offset += len(chunk)
244 done = offset == len(data)
260 done = offset == len(data)
245
261
246 if done:
262 if done:
247 flags = FLAG_BYTES_RESPONSE_EOS
263 flags = FLAG_BYTES_RESPONSE_EOS
248 else:
264 else:
249 flags = FLAG_BYTES_RESPONSE_CONTINUATION
265 flags = FLAG_BYTES_RESPONSE_CONTINUATION
250
266
251 yield makeframe(FRAME_TYPE_BYTES_RESPONSE, flags, chunk)
267 yield makeframe(requestid, FRAME_TYPE_BYTES_RESPONSE, flags, chunk)
252
268
253 if done:
269 if done:
254 break
270 break
255
271
256 def createerrorframe(msg, protocol=False, application=False):
272 def createerrorframe(requestid, msg, protocol=False, application=False):
257 # TODO properly handle frame size limits.
273 # TODO properly handle frame size limits.
258 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
274 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
259
275
260 flags = 0
276 flags = 0
261 if protocol:
277 if protocol:
262 flags |= FLAG_ERROR_RESPONSE_PROTOCOL
278 flags |= FLAG_ERROR_RESPONSE_PROTOCOL
263 if application:
279 if application:
264 flags |= FLAG_ERROR_RESPONSE_APPLICATION
280 flags |= FLAG_ERROR_RESPONSE_APPLICATION
265
281
266 yield makeframe(FRAME_TYPE_ERROR_RESPONSE, flags, msg)
282 yield makeframe(requestid, FRAME_TYPE_ERROR_RESPONSE, flags, msg)
267
283
268 class serverreactor(object):
284 class serverreactor(object):
269 """Holds state of a server handling frame-based protocol requests.
285 """Holds state of a server handling frame-based protocol requests.
270
286
271 This class is the "brain" of the unified frame-based protocol server
287 This class is the "brain" of the unified frame-based protocol server
272 component. While the protocol is stateless from the perspective of
288 component. While the protocol is stateless from the perspective of
273 requests/commands, something needs to track which frames have been
289 requests/commands, something needs to track which frames have been
274 received, what frames to expect, etc. This class is that thing.
290 received, what frames to expect, etc. This class is that thing.
275
291
276 Instances are modeled as a state machine of sorts. Instances are also
292 Instances are modeled as a state machine of sorts. Instances are also
277 reactionary to external events. The point of this class is to encapsulate
293 reactionary to external events. The point of this class is to encapsulate
278 the state of the connection and the exchange of frames, not to perform
294 the state of the connection and the exchange of frames, not to perform
279 work. Instead, callers tell this class when something occurs, like a
295 work. Instead, callers tell this class when something occurs, like a
280 frame arriving. If that activity is worthy of a follow-up action (say
296 frame arriving. If that activity is worthy of a follow-up action (say
281 *run a command*), the return value of that handler will say so.
297 *run a command*), the return value of that handler will say so.
282
298
283 I/O and CPU intensive operations are purposefully delegated outside of
299 I/O and CPU intensive operations are purposefully delegated outside of
284 this class.
300 this class.
285
301
286 Consumers are expected to tell instances when events occur. They do so by
302 Consumers are expected to tell instances when events occur. They do so by
287 calling the various ``on*`` methods. These methods return a 2-tuple
303 calling the various ``on*`` methods. These methods return a 2-tuple
288 describing any follow-up action(s) to take. The first element is the
304 describing any follow-up action(s) to take. The first element is the
289 name of an action to perform. The second is a data structure (usually
305 name of an action to perform. The second is a data structure (usually
290 a dict) specific to that action that contains more information. e.g.
306 a dict) specific to that action that contains more information. e.g.
291 if the server wants to send frames back to the client, the data structure
307 if the server wants to send frames back to the client, the data structure
292 will contain a reference to those frames.
308 will contain a reference to those frames.
293
309
294 Valid actions that consumers can be instructed to take are:
310 Valid actions that consumers can be instructed to take are:
295
311
296 sendframes
312 sendframes
297 Indicates that frames should be sent to the client. The ``framegen``
313 Indicates that frames should be sent to the client. The ``framegen``
298 key contains a generator of frames that should be sent. The server
314 key contains a generator of frames that should be sent. The server
299 assumes that all frames are sent to the client.
315 assumes that all frames are sent to the client.
300
316
301 error
317 error
302 Indicates that an error occurred. Consumer should probably abort.
318 Indicates that an error occurred. Consumer should probably abort.
303
319
304 runcommand
320 runcommand
305 Indicates that the consumer should run a wire protocol command. Details
321 Indicates that the consumer should run a wire protocol command. Details
306 of the command to run are given in the data structure.
322 of the command to run are given in the data structure.
307
323
308 wantframe
324 wantframe
309 Indicates that nothing of interest happened and the server is waiting on
325 Indicates that nothing of interest happened and the server is waiting on
310 more frames from the client before anything interesting can be done.
326 more frames from the client before anything interesting can be done.
311
327
312 noop
328 noop
313 Indicates no additional action is required.
329 Indicates no additional action is required.
314 """
330 """
315
331
316 def __init__(self, deferoutput=False):
332 def __init__(self, deferoutput=False):
317 """Construct a new server reactor.
333 """Construct a new server reactor.
318
334
319 ``deferoutput`` can be used to indicate that no output frames should be
335 ``deferoutput`` can be used to indicate that no output frames should be
320 instructed to be sent until input has been exhausted. In this mode,
336 instructed to be sent until input has been exhausted. In this mode,
321 events that would normally generate output frames (such as a command
337 events that would normally generate output frames (such as a command
322 response being ready) will instead defer instructing the consumer to
338 response being ready) will instead defer instructing the consumer to
323 send those frames. This is useful for half-duplex transports where the
339 send those frames. This is useful for half-duplex transports where the
324 sender cannot receive until all data has been transmitted.
340 sender cannot receive until all data has been transmitted.
325 """
341 """
326 self._deferoutput = deferoutput
342 self._deferoutput = deferoutput
327 self._state = 'idle'
343 self._state = 'idle'
328 self._bufferedframegens = []
344 self._bufferedframegens = []
345 self._activerequestid = None
329 self._activecommand = None
346 self._activecommand = None
330 self._activeargs = None
347 self._activeargs = None
331 self._activedata = None
348 self._activedata = None
332 self._expectingargs = None
349 self._expectingargs = None
333 self._expectingdata = None
350 self._expectingdata = None
334 self._activeargname = None
351 self._activeargname = None
335 self._activeargchunks = None
352 self._activeargchunks = None
336
353
337 def onframerecv(self, frametype, frameflags, payload):
354 def onframerecv(self, requestid, frametype, frameflags, payload):
338 """Process a frame that has been received off the wire.
355 """Process a frame that has been received off the wire.
339
356
340 Returns a dict with an ``action`` key that details what action,
357 Returns a dict with an ``action`` key that details what action,
341 if any, the consumer should take next.
358 if any, the consumer should take next.
342 """
359 """
343 handlers = {
360 handlers = {
344 'idle': self._onframeidle,
361 'idle': self._onframeidle,
345 'command-receiving-args': self._onframereceivingargs,
362 'command-receiving-args': self._onframereceivingargs,
346 'command-receiving-data': self._onframereceivingdata,
363 'command-receiving-data': self._onframereceivingdata,
347 'errored': self._onframeerrored,
364 'errored': self._onframeerrored,
348 }
365 }
349
366
350 meth = handlers.get(self._state)
367 meth = handlers.get(self._state)
351 if not meth:
368 if not meth:
352 raise error.ProgrammingError('unhandled state: %s' % self._state)
369 raise error.ProgrammingError('unhandled state: %s' % self._state)
353
370
354 return meth(frametype, frameflags, payload)
371 return meth(requestid, frametype, frameflags, payload)
355
372
356 def onbytesresponseready(self, data):
373 def onbytesresponseready(self, requestid, data):
357 """Signal that a bytes response is ready to be sent to the client.
374 """Signal that a bytes response is ready to be sent to the client.
358
375
359 The raw bytes response is passed as an argument.
376 The raw bytes response is passed as an argument.
360 """
377 """
361 framegen = createbytesresponseframesfrombytes(data)
378 framegen = createbytesresponseframesfrombytes(requestid, data)
362
379
363 if self._deferoutput:
380 if self._deferoutput:
364 self._bufferedframegens.append(framegen)
381 self._bufferedframegens.append(framegen)
365 return 'noop', {}
382 return 'noop', {}
366 else:
383 else:
367 return 'sendframes', {
384 return 'sendframes', {
368 'framegen': framegen,
385 'framegen': framegen,
369 }
386 }
370
387
371 def oninputeof(self):
388 def oninputeof(self):
372 """Signals that end of input has been received.
389 """Signals that end of input has been received.
373
390
374 No more frames will be received. All pending activity should be
391 No more frames will be received. All pending activity should be
375 completed.
392 completed.
376 """
393 """
377 if not self._deferoutput or not self._bufferedframegens:
394 if not self._deferoutput or not self._bufferedframegens:
378 return 'noop', {}
395 return 'noop', {}
379
396
380 # If we buffered all our responses, emit those.
397 # If we buffered all our responses, emit those.
381 def makegen():
398 def makegen():
382 for gen in self._bufferedframegens:
399 for gen in self._bufferedframegens:
383 for frame in gen:
400 for frame in gen:
384 yield frame
401 yield frame
385
402
386 return 'sendframes', {
403 return 'sendframes', {
387 'framegen': makegen(),
404 'framegen': makegen(),
388 }
405 }
389
406
390 def onapplicationerror(self, msg):
407 def onapplicationerror(self, requestid, msg):
391 return 'sendframes', {
408 return 'sendframes', {
392 'framegen': createerrorframe(msg, application=True),
409 'framegen': createerrorframe(requestid, msg, application=True),
393 }
410 }
394
411
395 def _makeerrorresult(self, msg):
412 def _makeerrorresult(self, msg):
396 return 'error', {
413 return 'error', {
397 'message': msg,
414 'message': msg,
398 }
415 }
399
416
400 def _makeruncommandresult(self):
417 def _makeruncommandresult(self):
401 return 'runcommand', {
418 return 'runcommand', {
419 'requestid': self._activerequestid,
402 'command': self._activecommand,
420 'command': self._activecommand,
403 'args': self._activeargs,
421 'args': self._activeargs,
404 'data': self._activedata.getvalue() if self._activedata else None,
422 'data': self._activedata.getvalue() if self._activedata else None,
405 }
423 }
406
424
407 def _makewantframeresult(self):
425 def _makewantframeresult(self):
408 return 'wantframe', {
426 return 'wantframe', {
409 'state': self._state,
427 'state': self._state,
410 }
428 }
411
429
412 def _onframeidle(self, frametype, frameflags, payload):
430 def _onframeidle(self, requestid, frametype, frameflags, payload):
413 # The only frame type that should be received in this state is a
431 # The only frame type that should be received in this state is a
414 # command request.
432 # command request.
415 if frametype != FRAME_TYPE_COMMAND_NAME:
433 if frametype != FRAME_TYPE_COMMAND_NAME:
416 self._state = 'errored'
434 self._state = 'errored'
417 return self._makeerrorresult(
435 return self._makeerrorresult(
418 _('expected command frame; got %d') % frametype)
436 _('expected command frame; got %d') % frametype)
419
437
438 self._activerequestid = requestid
420 self._activecommand = payload
439 self._activecommand = payload
421 self._activeargs = {}
440 self._activeargs = {}
422 self._activedata = None
441 self._activedata = None
423
442
424 if frameflags & FLAG_COMMAND_NAME_EOS:
443 if frameflags & FLAG_COMMAND_NAME_EOS:
425 return self._makeruncommandresult()
444 return self._makeruncommandresult()
426
445
427 self._expectingargs = bool(frameflags & FLAG_COMMAND_NAME_HAVE_ARGS)
446 self._expectingargs = bool(frameflags & FLAG_COMMAND_NAME_HAVE_ARGS)
428 self._expectingdata = bool(frameflags & FLAG_COMMAND_NAME_HAVE_DATA)
447 self._expectingdata = bool(frameflags & FLAG_COMMAND_NAME_HAVE_DATA)
429
448
430 if self._expectingargs:
449 if self._expectingargs:
431 self._state = 'command-receiving-args'
450 self._state = 'command-receiving-args'
432 return self._makewantframeresult()
451 return self._makewantframeresult()
433 elif self._expectingdata:
452 elif self._expectingdata:
434 self._activedata = util.bytesio()
453 self._activedata = util.bytesio()
435 self._state = 'command-receiving-data'
454 self._state = 'command-receiving-data'
436 return self._makewantframeresult()
455 return self._makewantframeresult()
437 else:
456 else:
438 self._state = 'errored'
457 self._state = 'errored'
439 return self._makeerrorresult(_('missing frame flags on '
458 return self._makeerrorresult(_('missing frame flags on '
440 'command frame'))
459 'command frame'))
441
460
442 def _onframereceivingargs(self, frametype, frameflags, payload):
461 def _onframereceivingargs(self, requestid, frametype, frameflags, payload):
443 if frametype != FRAME_TYPE_COMMAND_ARGUMENT:
462 if frametype != FRAME_TYPE_COMMAND_ARGUMENT:
444 self._state = 'errored'
463 self._state = 'errored'
445 return self._makeerrorresult(_('expected command argument '
464 return self._makeerrorresult(_('expected command argument '
446 'frame; got %d') % frametype)
465 'frame; got %d') % frametype)
447
466
448 offset = 0
467 offset = 0
449 namesize, valuesize = ARGUMENT_FRAME_HEADER.unpack_from(payload)
468 namesize, valuesize = ARGUMENT_FRAME_HEADER.unpack_from(payload)
450 offset += ARGUMENT_FRAME_HEADER.size
469 offset += ARGUMENT_FRAME_HEADER.size
451
470
452 # The argument name MUST fit inside the frame.
471 # The argument name MUST fit inside the frame.
453 argname = bytes(payload[offset:offset + namesize])
472 argname = bytes(payload[offset:offset + namesize])
454 offset += namesize
473 offset += namesize
455
474
456 if len(argname) != namesize:
475 if len(argname) != namesize:
457 self._state = 'errored'
476 self._state = 'errored'
458 return self._makeerrorresult(_('malformed argument frame: '
477 return self._makeerrorresult(_('malformed argument frame: '
459 'partial argument name'))
478 'partial argument name'))
460
479
461 argvalue = bytes(payload[offset:])
480 argvalue = bytes(payload[offset:])
462
481
463 # Argument value spans multiple frames. Record our active state
482 # Argument value spans multiple frames. Record our active state
464 # and wait for the next frame.
483 # and wait for the next frame.
465 if frameflags & FLAG_COMMAND_ARGUMENT_CONTINUATION:
484 if frameflags & FLAG_COMMAND_ARGUMENT_CONTINUATION:
466 raise error.ProgrammingError('not yet implemented')
485 raise error.ProgrammingError('not yet implemented')
467 self._activeargname = argname
486 self._activeargname = argname
468 self._activeargchunks = [argvalue]
487 self._activeargchunks = [argvalue]
469 self._state = 'command-arg-continuation'
488 self._state = 'command-arg-continuation'
470 return self._makewantframeresult()
489 return self._makewantframeresult()
471
490
472 # Common case: the argument value is completely contained in this
491 # Common case: the argument value is completely contained in this
473 # frame.
492 # frame.
474
493
475 if len(argvalue) != valuesize:
494 if len(argvalue) != valuesize:
476 self._state = 'errored'
495 self._state = 'errored'
477 return self._makeerrorresult(_('malformed argument frame: '
496 return self._makeerrorresult(_('malformed argument frame: '
478 'partial argument value'))
497 'partial argument value'))
479
498
480 self._activeargs[argname] = argvalue
499 self._activeargs[argname] = argvalue
481
500
482 if frameflags & FLAG_COMMAND_ARGUMENT_EOA:
501 if frameflags & FLAG_COMMAND_ARGUMENT_EOA:
483 if self._expectingdata:
502 if self._expectingdata:
484 self._state = 'command-receiving-data'
503 self._state = 'command-receiving-data'
485 self._activedata = util.bytesio()
504 self._activedata = util.bytesio()
486 # TODO signal request to run a command once we don't
505 # TODO signal request to run a command once we don't
487 # buffer data frames.
506 # buffer data frames.
488 return self._makewantframeresult()
507 return self._makewantframeresult()
489 else:
508 else:
490 self._state = 'waiting'
509 self._state = 'waiting'
491 return self._makeruncommandresult()
510 return self._makeruncommandresult()
492 else:
511 else:
493 return self._makewantframeresult()
512 return self._makewantframeresult()
494
513
495 def _onframereceivingdata(self, frametype, frameflags, payload):
514 def _onframereceivingdata(self, requestid, frametype, frameflags, payload):
496 if frametype != FRAME_TYPE_COMMAND_DATA:
515 if frametype != FRAME_TYPE_COMMAND_DATA:
497 self._state = 'errored'
516 self._state = 'errored'
498 return self._makeerrorresult(_('expected command data frame; '
517 return self._makeerrorresult(_('expected command data frame; '
499 'got %d') % frametype)
518 'got %d') % frametype)
500
519
501 # TODO support streaming data instead of buffering it.
520 # TODO support streaming data instead of buffering it.
502 self._activedata.write(payload)
521 self._activedata.write(payload)
503
522
504 if frameflags & FLAG_COMMAND_DATA_CONTINUATION:
523 if frameflags & FLAG_COMMAND_DATA_CONTINUATION:
505 return self._makewantframeresult()
524 return self._makewantframeresult()
506 elif frameflags & FLAG_COMMAND_DATA_EOS:
525 elif frameflags & FLAG_COMMAND_DATA_EOS:
507 self._activedata.seek(0)
526 self._activedata.seek(0)
508 self._state = 'idle'
527 self._state = 'idle'
509 return self._makeruncommandresult()
528 return self._makeruncommandresult()
510 else:
529 else:
511 self._state = 'errored'
530 self._state = 'errored'
512 return self._makeerrorresult(_('command data frame without '
531 return self._makeerrorresult(_('command data frame without '
513 'flags'))
532 'flags'))
514
533
515 def _onframeerrored(self, frametype, frameflags, payload):
534 def _onframeerrored(self, requestid, frametype, frameflags, payload):
516 return self._makeerrorresult(_('server already errored'))
535 return self._makeerrorresult(_('server already errored'))
@@ -1,1014 +1,1017
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 wireprotoframing,
22 wireprotoframing,
23 wireprototypes,
23 wireprototypes,
24 )
24 )
25
25
26 stringio = util.stringio
26 stringio = util.stringio
27
27
28 urlerr = util.urlerr
28 urlerr = util.urlerr
29 urlreq = util.urlreq
29 urlreq = util.urlreq
30
30
31 HTTP_OK = 200
31 HTTP_OK = 200
32
32
33 HGTYPE = 'application/mercurial-0.1'
33 HGTYPE = 'application/mercurial-0.1'
34 HGTYPE2 = 'application/mercurial-0.2'
34 HGTYPE2 = 'application/mercurial-0.2'
35 HGERRTYPE = 'application/hg-error'
35 HGERRTYPE = 'application/hg-error'
36 FRAMINGTYPE = b'application/mercurial-exp-framing-0001'
36 FRAMINGTYPE = b'application/mercurial-exp-framing-0002'
37
37
38 HTTPV2 = wireprototypes.HTTPV2
38 HTTPV2 = wireprototypes.HTTPV2
39 SSHV1 = wireprototypes.SSHV1
39 SSHV1 = wireprototypes.SSHV1
40 SSHV2 = wireprototypes.SSHV2
40 SSHV2 = wireprototypes.SSHV2
41
41
42 def decodevaluefromheaders(req, headerprefix):
42 def decodevaluefromheaders(req, headerprefix):
43 """Decode a long value from multiple HTTP request headers.
43 """Decode a long value from multiple HTTP request headers.
44
44
45 Returns the value as a bytes, not a str.
45 Returns the value as a bytes, not a str.
46 """
46 """
47 chunks = []
47 chunks = []
48 i = 1
48 i = 1
49 while True:
49 while True:
50 v = req.headers.get(b'%s-%d' % (headerprefix, i))
50 v = req.headers.get(b'%s-%d' % (headerprefix, i))
51 if v is None:
51 if v is None:
52 break
52 break
53 chunks.append(pycompat.bytesurl(v))
53 chunks.append(pycompat.bytesurl(v))
54 i += 1
54 i += 1
55
55
56 return ''.join(chunks)
56 return ''.join(chunks)
57
57
58 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
58 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
59 def __init__(self, req, ui, checkperm):
59 def __init__(self, req, ui, checkperm):
60 self._req = req
60 self._req = req
61 self._ui = ui
61 self._ui = ui
62 self._checkperm = checkperm
62 self._checkperm = checkperm
63
63
64 @property
64 @property
65 def name(self):
65 def name(self):
66 return 'http-v1'
66 return 'http-v1'
67
67
68 def getargs(self, args):
68 def getargs(self, args):
69 knownargs = self._args()
69 knownargs = self._args()
70 data = {}
70 data = {}
71 keys = args.split()
71 keys = args.split()
72 for k in keys:
72 for k in keys:
73 if k == '*':
73 if k == '*':
74 star = {}
74 star = {}
75 for key in knownargs.keys():
75 for key in knownargs.keys():
76 if key != 'cmd' and key not in keys:
76 if key != 'cmd' and key not in keys:
77 star[key] = knownargs[key][0]
77 star[key] = knownargs[key][0]
78 data['*'] = star
78 data['*'] = star
79 else:
79 else:
80 data[k] = knownargs[k][0]
80 data[k] = knownargs[k][0]
81 return [data[k] for k in keys]
81 return [data[k] for k in keys]
82
82
83 def _args(self):
83 def _args(self):
84 args = self._req.qsparams.asdictoflists()
84 args = self._req.qsparams.asdictoflists()
85 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
85 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
86 if postlen:
86 if postlen:
87 args.update(urlreq.parseqs(
87 args.update(urlreq.parseqs(
88 self._req.bodyfh.read(postlen), keep_blank_values=True))
88 self._req.bodyfh.read(postlen), keep_blank_values=True))
89 return args
89 return args
90
90
91 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
91 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
92 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
92 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
93 return args
93 return args
94
94
95 def forwardpayload(self, fp):
95 def forwardpayload(self, fp):
96 # Existing clients *always* send Content-Length.
96 # Existing clients *always* send Content-Length.
97 length = int(self._req.headers[b'Content-Length'])
97 length = int(self._req.headers[b'Content-Length'])
98
98
99 # If httppostargs is used, we need to read Content-Length
99 # If httppostargs is used, we need to read Content-Length
100 # minus the amount that was consumed by args.
100 # minus the amount that was consumed by args.
101 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
101 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
102 for s in util.filechunkiter(self._req.bodyfh, limit=length):
102 for s in util.filechunkiter(self._req.bodyfh, limit=length):
103 fp.write(s)
103 fp.write(s)
104
104
105 @contextlib.contextmanager
105 @contextlib.contextmanager
106 def mayberedirectstdio(self):
106 def mayberedirectstdio(self):
107 oldout = self._ui.fout
107 oldout = self._ui.fout
108 olderr = self._ui.ferr
108 olderr = self._ui.ferr
109
109
110 out = util.stringio()
110 out = util.stringio()
111
111
112 try:
112 try:
113 self._ui.fout = out
113 self._ui.fout = out
114 self._ui.ferr = out
114 self._ui.ferr = out
115 yield out
115 yield out
116 finally:
116 finally:
117 self._ui.fout = oldout
117 self._ui.fout = oldout
118 self._ui.ferr = olderr
118 self._ui.ferr = olderr
119
119
120 def client(self):
120 def client(self):
121 return 'remote:%s:%s:%s' % (
121 return 'remote:%s:%s:%s' % (
122 self._req.urlscheme,
122 self._req.urlscheme,
123 urlreq.quote(self._req.remotehost or ''),
123 urlreq.quote(self._req.remotehost or ''),
124 urlreq.quote(self._req.remoteuser or ''))
124 urlreq.quote(self._req.remoteuser or ''))
125
125
126 def addcapabilities(self, repo, caps):
126 def addcapabilities(self, repo, caps):
127 caps.append(b'batch')
127 caps.append(b'batch')
128
128
129 caps.append('httpheader=%d' %
129 caps.append('httpheader=%d' %
130 repo.ui.configint('server', 'maxhttpheaderlen'))
130 repo.ui.configint('server', 'maxhttpheaderlen'))
131 if repo.ui.configbool('experimental', 'httppostargs'):
131 if repo.ui.configbool('experimental', 'httppostargs'):
132 caps.append('httppostargs')
132 caps.append('httppostargs')
133
133
134 # FUTURE advertise 0.2rx once support is implemented
134 # FUTURE advertise 0.2rx once support is implemented
135 # FUTURE advertise minrx and mintx after consulting config option
135 # FUTURE advertise minrx and mintx after consulting config option
136 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
136 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
137
137
138 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
138 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
139 if compengines:
139 if compengines:
140 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
140 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
141 for e in compengines)
141 for e in compengines)
142 caps.append('compression=%s' % comptypes)
142 caps.append('compression=%s' % comptypes)
143
143
144 return caps
144 return caps
145
145
146 def checkperm(self, perm):
146 def checkperm(self, perm):
147 return self._checkperm(perm)
147 return self._checkperm(perm)
148
148
149 # This method exists mostly so that extensions like remotefilelog can
149 # This method exists mostly so that extensions like remotefilelog can
150 # disable a kludgey legacy method only over http. As of early 2018,
150 # disable a kludgey legacy method only over http. As of early 2018,
151 # there are no other known users, so with any luck we can discard this
151 # there are no other known users, so with any luck we can discard this
152 # hook if remotefilelog becomes a first-party extension.
152 # hook if remotefilelog becomes a first-party extension.
153 def iscmd(cmd):
153 def iscmd(cmd):
154 return cmd in wireproto.commands
154 return cmd in wireproto.commands
155
155
156 def handlewsgirequest(rctx, req, res, checkperm):
156 def handlewsgirequest(rctx, req, res, checkperm):
157 """Possibly process a wire protocol request.
157 """Possibly process a wire protocol request.
158
158
159 If the current request is a wire protocol request, the request is
159 If the current request is a wire protocol request, the request is
160 processed by this function.
160 processed by this function.
161
161
162 ``req`` is a ``parsedrequest`` instance.
162 ``req`` is a ``parsedrequest`` instance.
163 ``res`` is a ``wsgiresponse`` instance.
163 ``res`` is a ``wsgiresponse`` instance.
164
164
165 Returns a bool indicating if the request was serviced. If set, the caller
165 Returns a bool indicating if the request was serviced. If set, the caller
166 should stop processing the request, as a response has already been issued.
166 should stop processing the request, as a response has already been issued.
167 """
167 """
168 # Avoid cycle involving hg module.
168 # Avoid cycle involving hg module.
169 from .hgweb import common as hgwebcommon
169 from .hgweb import common as hgwebcommon
170
170
171 repo = rctx.repo
171 repo = rctx.repo
172
172
173 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
173 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
174 # string parameter. If it isn't present, this isn't a wire protocol
174 # string parameter. If it isn't present, this isn't a wire protocol
175 # request.
175 # request.
176 if 'cmd' not in req.qsparams:
176 if 'cmd' not in req.qsparams:
177 return False
177 return False
178
178
179 cmd = req.qsparams['cmd']
179 cmd = req.qsparams['cmd']
180
180
181 # The "cmd" request parameter is used by both the wire protocol and hgweb.
181 # The "cmd" request parameter is used by both the wire protocol and hgweb.
182 # While not all wire protocol commands are available for all transports,
182 # While not all wire protocol commands are available for all transports,
183 # if we see a "cmd" value that resembles a known wire protocol command, we
183 # if we see a "cmd" value that resembles a known wire protocol command, we
184 # route it to a protocol handler. This is better than routing possible
184 # route it to a protocol handler. This is better than routing possible
185 # wire protocol requests to hgweb because it prevents hgweb from using
185 # wire protocol requests to hgweb because it prevents hgweb from using
186 # known wire protocol commands and it is less confusing for machine
186 # known wire protocol commands and it is less confusing for machine
187 # clients.
187 # clients.
188 if not iscmd(cmd):
188 if not iscmd(cmd):
189 return False
189 return False
190
190
191 # The "cmd" query string argument is only valid on the root path of the
191 # The "cmd" query string argument is only valid on the root path of the
192 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
192 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
193 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
193 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
194 # in this case. We send an HTTP 404 for backwards compatibility reasons.
194 # in this case. We send an HTTP 404 for backwards compatibility reasons.
195 if req.dispatchpath:
195 if req.dispatchpath:
196 res.status = hgwebcommon.statusmessage(404)
196 res.status = hgwebcommon.statusmessage(404)
197 res.headers['Content-Type'] = HGTYPE
197 res.headers['Content-Type'] = HGTYPE
198 # TODO This is not a good response to issue for this request. This
198 # TODO This is not a good response to issue for this request. This
199 # is mostly for BC for now.
199 # is mostly for BC for now.
200 res.setbodybytes('0\n%s\n' % b'Not Found')
200 res.setbodybytes('0\n%s\n' % b'Not Found')
201 return True
201 return True
202
202
203 proto = httpv1protocolhandler(req, repo.ui,
203 proto = httpv1protocolhandler(req, repo.ui,
204 lambda perm: checkperm(rctx, req, perm))
204 lambda perm: checkperm(rctx, req, perm))
205
205
206 # The permissions checker should be the only thing that can raise an
206 # The permissions checker should be the only thing that can raise an
207 # ErrorResponse. It is kind of a layer violation to catch an hgweb
207 # ErrorResponse. It is kind of a layer violation to catch an hgweb
208 # exception here. So consider refactoring into a exception type that
208 # exception here. So consider refactoring into a exception type that
209 # is associated with the wire protocol.
209 # is associated with the wire protocol.
210 try:
210 try:
211 _callhttp(repo, req, res, proto, cmd)
211 _callhttp(repo, req, res, proto, cmd)
212 except hgwebcommon.ErrorResponse as e:
212 except hgwebcommon.ErrorResponse as e:
213 for k, v in e.headers:
213 for k, v in e.headers:
214 res.headers[k] = v
214 res.headers[k] = v
215 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
215 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
216 # TODO This response body assumes the failed command was
216 # TODO This response body assumes the failed command was
217 # "unbundle." That assumption is not always valid.
217 # "unbundle." That assumption is not always valid.
218 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
218 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
219
219
220 return True
220 return True
221
221
222 def handlewsgiapirequest(rctx, req, res, checkperm):
222 def handlewsgiapirequest(rctx, req, res, checkperm):
223 """Handle requests to /api/*."""
223 """Handle requests to /api/*."""
224 assert req.dispatchparts[0] == b'api'
224 assert req.dispatchparts[0] == b'api'
225
225
226 repo = rctx.repo
226 repo = rctx.repo
227
227
228 # This whole URL space is experimental for now. But we want to
228 # This whole URL space is experimental for now. But we want to
229 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
229 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
230 if not repo.ui.configbool('experimental', 'web.apiserver'):
230 if not repo.ui.configbool('experimental', 'web.apiserver'):
231 res.status = b'404 Not Found'
231 res.status = b'404 Not Found'
232 res.headers[b'Content-Type'] = b'text/plain'
232 res.headers[b'Content-Type'] = b'text/plain'
233 res.setbodybytes(_('Experimental API server endpoint not enabled'))
233 res.setbodybytes(_('Experimental API server endpoint not enabled'))
234 return
234 return
235
235
236 # The URL space is /api/<protocol>/*. The structure of URLs under varies
236 # The URL space is /api/<protocol>/*. The structure of URLs under varies
237 # by <protocol>.
237 # by <protocol>.
238
238
239 # Registered APIs are made available via config options of the name of
239 # Registered APIs are made available via config options of the name of
240 # the protocol.
240 # the protocol.
241 availableapis = set()
241 availableapis = set()
242 for k, v in API_HANDLERS.items():
242 for k, v in API_HANDLERS.items():
243 section, option = v['config']
243 section, option = v['config']
244 if repo.ui.configbool(section, option):
244 if repo.ui.configbool(section, option):
245 availableapis.add(k)
245 availableapis.add(k)
246
246
247 # Requests to /api/ list available APIs.
247 # Requests to /api/ list available APIs.
248 if req.dispatchparts == [b'api']:
248 if req.dispatchparts == [b'api']:
249 res.status = b'200 OK'
249 res.status = b'200 OK'
250 res.headers[b'Content-Type'] = b'text/plain'
250 res.headers[b'Content-Type'] = b'text/plain'
251 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
251 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
252 'one of the following:\n')]
252 'one of the following:\n')]
253 if availableapis:
253 if availableapis:
254 lines.extend(sorted(availableapis))
254 lines.extend(sorted(availableapis))
255 else:
255 else:
256 lines.append(_('(no available APIs)\n'))
256 lines.append(_('(no available APIs)\n'))
257 res.setbodybytes(b'\n'.join(lines))
257 res.setbodybytes(b'\n'.join(lines))
258 return
258 return
259
259
260 proto = req.dispatchparts[1]
260 proto = req.dispatchparts[1]
261
261
262 if proto not in API_HANDLERS:
262 if proto not in API_HANDLERS:
263 res.status = b'404 Not Found'
263 res.status = b'404 Not Found'
264 res.headers[b'Content-Type'] = b'text/plain'
264 res.headers[b'Content-Type'] = b'text/plain'
265 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
265 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
266 proto, b', '.join(sorted(availableapis))))
266 proto, b', '.join(sorted(availableapis))))
267 return
267 return
268
268
269 if proto not in availableapis:
269 if proto not in availableapis:
270 res.status = b'404 Not Found'
270 res.status = b'404 Not Found'
271 res.headers[b'Content-Type'] = b'text/plain'
271 res.headers[b'Content-Type'] = b'text/plain'
272 res.setbodybytes(_('API %s not enabled\n') % proto)
272 res.setbodybytes(_('API %s not enabled\n') % proto)
273 return
273 return
274
274
275 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
275 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
276 req.dispatchparts[2:])
276 req.dispatchparts[2:])
277
277
278 def _handlehttpv2request(rctx, req, res, checkperm, urlparts):
278 def _handlehttpv2request(rctx, req, res, checkperm, urlparts):
279 from .hgweb import common as hgwebcommon
279 from .hgweb import common as hgwebcommon
280
280
281 # URL space looks like: <permissions>/<command>, where <permission> can
281 # URL space looks like: <permissions>/<command>, where <permission> can
282 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
282 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
283
283
284 # Root URL does nothing meaningful... yet.
284 # Root URL does nothing meaningful... yet.
285 if not urlparts:
285 if not urlparts:
286 res.status = b'200 OK'
286 res.status = b'200 OK'
287 res.headers[b'Content-Type'] = b'text/plain'
287 res.headers[b'Content-Type'] = b'text/plain'
288 res.setbodybytes(_('HTTP version 2 API handler'))
288 res.setbodybytes(_('HTTP version 2 API handler'))
289 return
289 return
290
290
291 if len(urlparts) == 1:
291 if len(urlparts) == 1:
292 res.status = b'404 Not Found'
292 res.status = b'404 Not Found'
293 res.headers[b'Content-Type'] = b'text/plain'
293 res.headers[b'Content-Type'] = b'text/plain'
294 res.setbodybytes(_('do not know how to process %s\n') %
294 res.setbodybytes(_('do not know how to process %s\n') %
295 req.dispatchpath)
295 req.dispatchpath)
296 return
296 return
297
297
298 permission, command = urlparts[0:2]
298 permission, command = urlparts[0:2]
299
299
300 if permission not in (b'ro', b'rw'):
300 if permission not in (b'ro', b'rw'):
301 res.status = b'404 Not Found'
301 res.status = b'404 Not Found'
302 res.headers[b'Content-Type'] = b'text/plain'
302 res.headers[b'Content-Type'] = b'text/plain'
303 res.setbodybytes(_('unknown permission: %s') % permission)
303 res.setbodybytes(_('unknown permission: %s') % permission)
304 return
304 return
305
305
306 if req.method != 'POST':
306 if req.method != 'POST':
307 res.status = b'405 Method Not Allowed'
307 res.status = b'405 Method Not Allowed'
308 res.headers[b'Allow'] = b'POST'
308 res.headers[b'Allow'] = b'POST'
309 res.setbodybytes(_('commands require POST requests'))
309 res.setbodybytes(_('commands require POST requests'))
310 return
310 return
311
311
312 # At some point we'll want to use our own API instead of recycling the
312 # At some point we'll want to use our own API instead of recycling the
313 # behavior of version 1 of the wire protocol...
313 # behavior of version 1 of the wire protocol...
314 # TODO return reasonable responses - not responses that overload the
314 # TODO return reasonable responses - not responses that overload the
315 # HTTP status line message for error reporting.
315 # HTTP status line message for error reporting.
316 try:
316 try:
317 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
317 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
318 except hgwebcommon.ErrorResponse as e:
318 except hgwebcommon.ErrorResponse as e:
319 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
319 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
320 for k, v in e.headers:
320 for k, v in e.headers:
321 res.headers[k] = v
321 res.headers[k] = v
322 res.setbodybytes('permission denied')
322 res.setbodybytes('permission denied')
323 return
323 return
324
324
325 # We have a special endpoint to reflect the request back at the client.
325 # We have a special endpoint to reflect the request back at the client.
326 if command == b'debugreflect':
326 if command == b'debugreflect':
327 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
327 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
328 return
328 return
329
329
330 if command not in wireproto.commands:
330 if command not in wireproto.commands:
331 res.status = b'404 Not Found'
331 res.status = b'404 Not Found'
332 res.headers[b'Content-Type'] = b'text/plain'
332 res.headers[b'Content-Type'] = b'text/plain'
333 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
333 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
334 return
334 return
335
335
336 repo = rctx.repo
336 repo = rctx.repo
337 ui = repo.ui
337 ui = repo.ui
338
338
339 proto = httpv2protocolhandler(req, ui)
339 proto = httpv2protocolhandler(req, ui)
340
340
341 if not wireproto.commands.commandavailable(command, proto):
341 if not wireproto.commands.commandavailable(command, proto):
342 res.status = b'404 Not Found'
342 res.status = b'404 Not Found'
343 res.headers[b'Content-Type'] = b'text/plain'
343 res.headers[b'Content-Type'] = b'text/plain'
344 res.setbodybytes(_('invalid wire protocol command: %s') % command)
344 res.setbodybytes(_('invalid wire protocol command: %s') % command)
345 return
345 return
346
346
347 if req.headers.get(b'Accept') != FRAMINGTYPE:
347 if req.headers.get(b'Accept') != FRAMINGTYPE:
348 res.status = b'406 Not Acceptable'
348 res.status = b'406 Not Acceptable'
349 res.headers[b'Content-Type'] = b'text/plain'
349 res.headers[b'Content-Type'] = b'text/plain'
350 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
350 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
351 % FRAMINGTYPE)
351 % FRAMINGTYPE)
352 return
352 return
353
353
354 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
354 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
355 res.status = b'415 Unsupported Media Type'
355 res.status = b'415 Unsupported Media Type'
356 # TODO we should send a response with appropriate media type,
356 # TODO we should send a response with appropriate media type,
357 # since client does Accept it.
357 # since client does Accept it.
358 res.headers[b'Content-Type'] = b'text/plain'
358 res.headers[b'Content-Type'] = b'text/plain'
359 res.setbodybytes(_('client MUST send Content-Type header with '
359 res.setbodybytes(_('client MUST send Content-Type header with '
360 'value: %s\n') % FRAMINGTYPE)
360 'value: %s\n') % FRAMINGTYPE)
361 return
361 return
362
362
363 _processhttpv2request(ui, repo, req, res, permission, command, proto)
363 _processhttpv2request(ui, repo, req, res, permission, command, proto)
364
364
365 def _processhttpv2reflectrequest(ui, repo, req, res):
365 def _processhttpv2reflectrequest(ui, repo, req, res):
366 """Reads unified frame protocol request and dumps out state to client.
366 """Reads unified frame protocol request and dumps out state to client.
367
367
368 This special endpoint can be used to help debug the wire protocol.
368 This special endpoint can be used to help debug the wire protocol.
369
369
370 Instead of routing the request through the normal dispatch mechanism,
370 Instead of routing the request through the normal dispatch mechanism,
371 we instead read all frames, decode them, and feed them into our state
371 we instead read all frames, decode them, and feed them into our state
372 tracker. We then dump the log of all that activity back out to the
372 tracker. We then dump the log of all that activity back out to the
373 client.
373 client.
374 """
374 """
375 import json
375 import json
376
376
377 # Reflection APIs have a history of being abused, accidentally disclosing
377 # Reflection APIs have a history of being abused, accidentally disclosing
378 # sensitive data, etc. So we have a config knob.
378 # sensitive data, etc. So we have a config knob.
379 if not ui.configbool('experimental', 'web.api.debugreflect'):
379 if not ui.configbool('experimental', 'web.api.debugreflect'):
380 res.status = b'404 Not Found'
380 res.status = b'404 Not Found'
381 res.headers[b'Content-Type'] = b'text/plain'
381 res.headers[b'Content-Type'] = b'text/plain'
382 res.setbodybytes(_('debugreflect service not available'))
382 res.setbodybytes(_('debugreflect service not available'))
383 return
383 return
384
384
385 # We assume we have a unified framing protocol request body.
385 # We assume we have a unified framing protocol request body.
386
386
387 reactor = wireprotoframing.serverreactor()
387 reactor = wireprotoframing.serverreactor()
388 states = []
388 states = []
389
389
390 while True:
390 while True:
391 frame = wireprotoframing.readframe(req.bodyfh)
391 frame = wireprotoframing.readframe(req.bodyfh)
392
392
393 if not frame:
393 if not frame:
394 states.append(b'received: <no frame>')
394 states.append(b'received: <no frame>')
395 break
395 break
396
396
397 frametype, frameflags, payload = frame
397 requestid, frametype, frameflags, payload = frame
398 states.append(b'received: %d %d %s' % (frametype, frameflags, payload))
398 states.append(b'received: %d %d %d %s' % (frametype, frameflags,
399 requestid, payload))
399
400
400 action, meta = reactor.onframerecv(frametype, frameflags, payload)
401 action, meta = reactor.onframerecv(requestid, frametype, frameflags,
402 payload)
401 states.append(json.dumps((action, meta), sort_keys=True,
403 states.append(json.dumps((action, meta), sort_keys=True,
402 separators=(', ', ': ')))
404 separators=(', ', ': ')))
403
405
404 action, meta = reactor.oninputeof()
406 action, meta = reactor.oninputeof()
405 meta['action'] = action
407 meta['action'] = action
406 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
408 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
407
409
408 res.status = b'200 OK'
410 res.status = b'200 OK'
409 res.headers[b'Content-Type'] = b'text/plain'
411 res.headers[b'Content-Type'] = b'text/plain'
410 res.setbodybytes(b'\n'.join(states))
412 res.setbodybytes(b'\n'.join(states))
411
413
412 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
414 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
413 """Post-validation handler for HTTPv2 requests.
415 """Post-validation handler for HTTPv2 requests.
414
416
415 Called when the HTTP request contains unified frame-based protocol
417 Called when the HTTP request contains unified frame-based protocol
416 frames for evaluation.
418 frames for evaluation.
417 """
419 """
418 # TODO Some HTTP clients are full duplex and can receive data before
420 # TODO Some HTTP clients are full duplex and can receive data before
419 # the entire request is transmitted. Figure out a way to indicate support
421 # the entire request is transmitted. Figure out a way to indicate support
420 # for that so we can opt into full duplex mode.
422 # for that so we can opt into full duplex mode.
421 reactor = wireprotoframing.serverreactor(deferoutput=True)
423 reactor = wireprotoframing.serverreactor(deferoutput=True)
422 seencommand = False
424 seencommand = False
423
425
424 while True:
426 while True:
425 frame = wireprotoframing.readframe(req.bodyfh)
427 frame = wireprotoframing.readframe(req.bodyfh)
426 if not frame:
428 if not frame:
427 break
429 break
428
430
429 action, meta = reactor.onframerecv(*frame)
431 action, meta = reactor.onframerecv(*frame)
430
432
431 if action == 'wantframe':
433 if action == 'wantframe':
432 # Need more data before we can do anything.
434 # Need more data before we can do anything.
433 continue
435 continue
434 elif action == 'runcommand':
436 elif action == 'runcommand':
435 # We currently only support running a single command per
437 # We currently only support running a single command per
436 # HTTP request.
438 # HTTP request.
437 if seencommand:
439 if seencommand:
438 # TODO define proper error mechanism.
440 # TODO define proper error mechanism.
439 res.status = b'200 OK'
441 res.status = b'200 OK'
440 res.headers[b'Content-Type'] = b'text/plain'
442 res.headers[b'Content-Type'] = b'text/plain'
441 res.setbodybytes(_('support for multiple commands per request '
443 res.setbodybytes(_('support for multiple commands per request '
442 'not yet implemented'))
444 'not yet implemented'))
443 return
445 return
444
446
445 _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand,
447 _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand,
446 reactor, meta)
448 reactor, meta)
447
449
448 elif action == 'error':
450 elif action == 'error':
449 # TODO define proper error mechanism.
451 # TODO define proper error mechanism.
450 res.status = b'200 OK'
452 res.status = b'200 OK'
451 res.headers[b'Content-Type'] = b'text/plain'
453 res.headers[b'Content-Type'] = b'text/plain'
452 res.setbodybytes(meta['message'] + b'\n')
454 res.setbodybytes(meta['message'] + b'\n')
453 return
455 return
454 else:
456 else:
455 raise error.ProgrammingError(
457 raise error.ProgrammingError(
456 'unhandled action from frame processor: %s' % action)
458 'unhandled action from frame processor: %s' % action)
457
459
458 action, meta = reactor.oninputeof()
460 action, meta = reactor.oninputeof()
459 if action == 'sendframes':
461 if action == 'sendframes':
460 # We assume we haven't started sending the response yet. If we're
462 # We assume we haven't started sending the response yet. If we're
461 # wrong, the response type will raise an exception.
463 # wrong, the response type will raise an exception.
462 res.status = b'200 OK'
464 res.status = b'200 OK'
463 res.headers[b'Content-Type'] = FRAMINGTYPE
465 res.headers[b'Content-Type'] = FRAMINGTYPE
464 res.setbodygen(meta['framegen'])
466 res.setbodygen(meta['framegen'])
465 elif action == 'noop':
467 elif action == 'noop':
466 pass
468 pass
467 else:
469 else:
468 raise error.ProgrammingError('unhandled action from frame processor: %s'
470 raise error.ProgrammingError('unhandled action from frame processor: %s'
469 % action)
471 % action)
470
472
471 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
473 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
472 command):
474 command):
473 """Dispatch a wire protocol command made from HTTPv2 requests.
475 """Dispatch a wire protocol command made from HTTPv2 requests.
474
476
475 The authenticated permission (``authedperm``) along with the original
477 The authenticated permission (``authedperm``) along with the original
476 command from the URL (``reqcommand``) are passed in.
478 command from the URL (``reqcommand``) are passed in.
477 """
479 """
478 # We already validated that the session has permissions to perform the
480 # We already validated that the session has permissions to perform the
479 # actions in ``authedperm``. In the unified frame protocol, the canonical
481 # actions in ``authedperm``. In the unified frame protocol, the canonical
480 # command to run is expressed in a frame. However, the URL also requested
482 # command to run is expressed in a frame. However, the URL also requested
481 # to run a specific command. We need to be careful that the command we
483 # to run a specific command. We need to be careful that the command we
482 # run doesn't have permissions requirements greater than what was granted
484 # run doesn't have permissions requirements greater than what was granted
483 # by ``authedperm``.
485 # by ``authedperm``.
484 #
486 #
485 # For now, this is no big deal, as we only allow a single command per
487 # For now, this is no big deal, as we only allow a single command per
486 # request and that command must match the command in the URL. But when
488 # request and that command must match the command in the URL. But when
487 # things change, we need to watch out...
489 # things change, we need to watch out...
488 if reqcommand != command['command']:
490 if reqcommand != command['command']:
489 # TODO define proper error mechanism
491 # TODO define proper error mechanism
490 res.status = b'200 OK'
492 res.status = b'200 OK'
491 res.headers[b'Content-Type'] = b'text/plain'
493 res.headers[b'Content-Type'] = b'text/plain'
492 res.setbodybytes(_('command in frame must match command in URL'))
494 res.setbodybytes(_('command in frame must match command in URL'))
493 return
495 return
494
496
495 # TODO once we get rid of the command==URL restriction, we'll need to
497 # TODO once we get rid of the command==URL restriction, we'll need to
496 # revalidate command validity and auth here. checkperm,
498 # revalidate command validity and auth here. checkperm,
497 # wireproto.commands.commandavailable(), etc.
499 # wireproto.commands.commandavailable(), etc.
498
500
499 proto = httpv2protocolhandler(req, ui, args=command['args'])
501 proto = httpv2protocolhandler(req, ui, args=command['args'])
500 assert wireproto.commands.commandavailable(command['command'], proto)
502 assert wireproto.commands.commandavailable(command['command'], proto)
501 wirecommand = wireproto.commands[command['command']]
503 wirecommand = wireproto.commands[command['command']]
502
504
503 assert authedperm in (b'ro', b'rw')
505 assert authedperm in (b'ro', b'rw')
504 assert wirecommand.permission in ('push', 'pull')
506 assert wirecommand.permission in ('push', 'pull')
505
507
506 # We already checked this as part of the URL==command check, but
508 # We already checked this as part of the URL==command check, but
507 # permissions are important, so do it again.
509 # permissions are important, so do it again.
508 if authedperm == b'ro':
510 if authedperm == b'ro':
509 assert wirecommand.permission == 'pull'
511 assert wirecommand.permission == 'pull'
510 elif authedperm == b'rw':
512 elif authedperm == b'rw':
511 # We are allowed to access read-only commands under the rw URL.
513 # We are allowed to access read-only commands under the rw URL.
512 assert wirecommand.permission in ('push', 'pull')
514 assert wirecommand.permission in ('push', 'pull')
513
515
514 rsp = wireproto.dispatch(repo, proto, command['command'])
516 rsp = wireproto.dispatch(repo, proto, command['command'])
515
517
516 res.status = b'200 OK'
518 res.status = b'200 OK'
517 res.headers[b'Content-Type'] = FRAMINGTYPE
519 res.headers[b'Content-Type'] = FRAMINGTYPE
518
520
519 if isinstance(rsp, wireprototypes.bytesresponse):
521 if isinstance(rsp, wireprototypes.bytesresponse):
520 action, meta = reactor.onbytesresponseready(rsp.data)
522 action, meta = reactor.onbytesresponseready(command['requestid'],
523 rsp.data)
521 else:
524 else:
522 action, meta = reactor.onapplicationerror(
525 action, meta = reactor.onapplicationerror(
523 _('unhandled response type from wire proto command'))
526 _('unhandled response type from wire proto command'))
524
527
525 if action == 'sendframes':
528 if action == 'sendframes':
526 res.setbodygen(meta['framegen'])
529 res.setbodygen(meta['framegen'])
527 elif action == 'noop':
530 elif action == 'noop':
528 pass
531 pass
529 else:
532 else:
530 raise error.ProgrammingError('unhandled event from reactor: %s' %
533 raise error.ProgrammingError('unhandled event from reactor: %s' %
531 action)
534 action)
532
535
533 # Maps API name to metadata so custom API can be registered.
536 # Maps API name to metadata so custom API can be registered.
534 API_HANDLERS = {
537 API_HANDLERS = {
535 HTTPV2: {
538 HTTPV2: {
536 'config': ('experimental', 'web.api.http-v2'),
539 'config': ('experimental', 'web.api.http-v2'),
537 'handler': _handlehttpv2request,
540 'handler': _handlehttpv2request,
538 },
541 },
539 }
542 }
540
543
541 class httpv2protocolhandler(wireprototypes.baseprotocolhandler):
544 class httpv2protocolhandler(wireprototypes.baseprotocolhandler):
542 def __init__(self, req, ui, args=None):
545 def __init__(self, req, ui, args=None):
543 self._req = req
546 self._req = req
544 self._ui = ui
547 self._ui = ui
545 self._args = args
548 self._args = args
546
549
547 @property
550 @property
548 def name(self):
551 def name(self):
549 return HTTPV2
552 return HTTPV2
550
553
551 def getargs(self, args):
554 def getargs(self, args):
552 data = {}
555 data = {}
553 for k in args.split():
556 for k in args.split():
554 if k == '*':
557 if k == '*':
555 raise NotImplementedError('do not support * args')
558 raise NotImplementedError('do not support * args')
556 else:
559 else:
557 data[k] = self._args[k]
560 data[k] = self._args[k]
558
561
559 return [data[k] for k in args.split()]
562 return [data[k] for k in args.split()]
560
563
561 def forwardpayload(self, fp):
564 def forwardpayload(self, fp):
562 raise NotImplementedError
565 raise NotImplementedError
563
566
564 @contextlib.contextmanager
567 @contextlib.contextmanager
565 def mayberedirectstdio(self):
568 def mayberedirectstdio(self):
566 raise NotImplementedError
569 raise NotImplementedError
567
570
568 def client(self):
571 def client(self):
569 raise NotImplementedError
572 raise NotImplementedError
570
573
571 def addcapabilities(self, repo, caps):
574 def addcapabilities(self, repo, caps):
572 return caps
575 return caps
573
576
574 def checkperm(self, perm):
577 def checkperm(self, perm):
575 raise NotImplementedError
578 raise NotImplementedError
576
579
577 def _httpresponsetype(ui, req, prefer_uncompressed):
580 def _httpresponsetype(ui, req, prefer_uncompressed):
578 """Determine the appropriate response type and compression settings.
581 """Determine the appropriate response type and compression settings.
579
582
580 Returns a tuple of (mediatype, compengine, engineopts).
583 Returns a tuple of (mediatype, compengine, engineopts).
581 """
584 """
582 # Determine the response media type and compression engine based
585 # Determine the response media type and compression engine based
583 # on the request parameters.
586 # on the request parameters.
584 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
587 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
585
588
586 if '0.2' in protocaps:
589 if '0.2' in protocaps:
587 # All clients are expected to support uncompressed data.
590 # All clients are expected to support uncompressed data.
588 if prefer_uncompressed:
591 if prefer_uncompressed:
589 return HGTYPE2, util._noopengine(), {}
592 return HGTYPE2, util._noopengine(), {}
590
593
591 # Default as defined by wire protocol spec.
594 # Default as defined by wire protocol spec.
592 compformats = ['zlib', 'none']
595 compformats = ['zlib', 'none']
593 for cap in protocaps:
596 for cap in protocaps:
594 if cap.startswith('comp='):
597 if cap.startswith('comp='):
595 compformats = cap[5:].split(',')
598 compformats = cap[5:].split(',')
596 break
599 break
597
600
598 # Now find an agreed upon compression format.
601 # Now find an agreed upon compression format.
599 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
602 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
600 if engine.wireprotosupport().name in compformats:
603 if engine.wireprotosupport().name in compformats:
601 opts = {}
604 opts = {}
602 level = ui.configint('server', '%slevel' % engine.name())
605 level = ui.configint('server', '%slevel' % engine.name())
603 if level is not None:
606 if level is not None:
604 opts['level'] = level
607 opts['level'] = level
605
608
606 return HGTYPE2, engine, opts
609 return HGTYPE2, engine, opts
607
610
608 # No mutually supported compression format. Fall back to the
611 # No mutually supported compression format. Fall back to the
609 # legacy protocol.
612 # legacy protocol.
610
613
611 # Don't allow untrusted settings because disabling compression or
614 # Don't allow untrusted settings because disabling compression or
612 # setting a very high compression level could lead to flooding
615 # setting a very high compression level could lead to flooding
613 # the server's network or CPU.
616 # the server's network or CPU.
614 opts = {'level': ui.configint('server', 'zliblevel')}
617 opts = {'level': ui.configint('server', 'zliblevel')}
615 return HGTYPE, util.compengines['zlib'], opts
618 return HGTYPE, util.compengines['zlib'], opts
616
619
617 def _callhttp(repo, req, res, proto, cmd):
620 def _callhttp(repo, req, res, proto, cmd):
618 # Avoid cycle involving hg module.
621 # Avoid cycle involving hg module.
619 from .hgweb import common as hgwebcommon
622 from .hgweb import common as hgwebcommon
620
623
621 def genversion2(gen, engine, engineopts):
624 def genversion2(gen, engine, engineopts):
622 # application/mercurial-0.2 always sends a payload header
625 # application/mercurial-0.2 always sends a payload header
623 # identifying the compression engine.
626 # identifying the compression engine.
624 name = engine.wireprotosupport().name
627 name = engine.wireprotosupport().name
625 assert 0 < len(name) < 256
628 assert 0 < len(name) < 256
626 yield struct.pack('B', len(name))
629 yield struct.pack('B', len(name))
627 yield name
630 yield name
628
631
629 for chunk in gen:
632 for chunk in gen:
630 yield chunk
633 yield chunk
631
634
632 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
635 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
633 if code == HTTP_OK:
636 if code == HTTP_OK:
634 res.status = '200 Script output follows'
637 res.status = '200 Script output follows'
635 else:
638 else:
636 res.status = hgwebcommon.statusmessage(code)
639 res.status = hgwebcommon.statusmessage(code)
637
640
638 res.headers['Content-Type'] = contenttype
641 res.headers['Content-Type'] = contenttype
639
642
640 if bodybytes is not None:
643 if bodybytes is not None:
641 res.setbodybytes(bodybytes)
644 res.setbodybytes(bodybytes)
642 if bodygen is not None:
645 if bodygen is not None:
643 res.setbodygen(bodygen)
646 res.setbodygen(bodygen)
644
647
645 if not wireproto.commands.commandavailable(cmd, proto):
648 if not wireproto.commands.commandavailable(cmd, proto):
646 setresponse(HTTP_OK, HGERRTYPE,
649 setresponse(HTTP_OK, HGERRTYPE,
647 _('requested wire protocol command is not available over '
650 _('requested wire protocol command is not available over '
648 'HTTP'))
651 'HTTP'))
649 return
652 return
650
653
651 proto.checkperm(wireproto.commands[cmd].permission)
654 proto.checkperm(wireproto.commands[cmd].permission)
652
655
653 rsp = wireproto.dispatch(repo, proto, cmd)
656 rsp = wireproto.dispatch(repo, proto, cmd)
654
657
655 if isinstance(rsp, bytes):
658 if isinstance(rsp, bytes):
656 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
659 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
657 elif isinstance(rsp, wireprototypes.bytesresponse):
660 elif isinstance(rsp, wireprototypes.bytesresponse):
658 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
661 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
659 elif isinstance(rsp, wireprototypes.streamreslegacy):
662 elif isinstance(rsp, wireprototypes.streamreslegacy):
660 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
663 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
661 elif isinstance(rsp, wireprototypes.streamres):
664 elif isinstance(rsp, wireprototypes.streamres):
662 gen = rsp.gen
665 gen = rsp.gen
663
666
664 # This code for compression should not be streamres specific. It
667 # This code for compression should not be streamres specific. It
665 # is here because we only compress streamres at the moment.
668 # is here because we only compress streamres at the moment.
666 mediatype, engine, engineopts = _httpresponsetype(
669 mediatype, engine, engineopts = _httpresponsetype(
667 repo.ui, req, rsp.prefer_uncompressed)
670 repo.ui, req, rsp.prefer_uncompressed)
668 gen = engine.compressstream(gen, engineopts)
671 gen = engine.compressstream(gen, engineopts)
669
672
670 if mediatype == HGTYPE2:
673 if mediatype == HGTYPE2:
671 gen = genversion2(gen, engine, engineopts)
674 gen = genversion2(gen, engine, engineopts)
672
675
673 setresponse(HTTP_OK, mediatype, bodygen=gen)
676 setresponse(HTTP_OK, mediatype, bodygen=gen)
674 elif isinstance(rsp, wireprototypes.pushres):
677 elif isinstance(rsp, wireprototypes.pushres):
675 rsp = '%d\n%s' % (rsp.res, rsp.output)
678 rsp = '%d\n%s' % (rsp.res, rsp.output)
676 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
679 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
677 elif isinstance(rsp, wireprototypes.pusherr):
680 elif isinstance(rsp, wireprototypes.pusherr):
678 rsp = '0\n%s\n' % rsp.res
681 rsp = '0\n%s\n' % rsp.res
679 res.drain = True
682 res.drain = True
680 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
683 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
681 elif isinstance(rsp, wireprototypes.ooberror):
684 elif isinstance(rsp, wireprototypes.ooberror):
682 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
685 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
683 else:
686 else:
684 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
687 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
685
688
686 def _sshv1respondbytes(fout, value):
689 def _sshv1respondbytes(fout, value):
687 """Send a bytes response for protocol version 1."""
690 """Send a bytes response for protocol version 1."""
688 fout.write('%d\n' % len(value))
691 fout.write('%d\n' % len(value))
689 fout.write(value)
692 fout.write(value)
690 fout.flush()
693 fout.flush()
691
694
692 def _sshv1respondstream(fout, source):
695 def _sshv1respondstream(fout, source):
693 write = fout.write
696 write = fout.write
694 for chunk in source.gen:
697 for chunk in source.gen:
695 write(chunk)
698 write(chunk)
696 fout.flush()
699 fout.flush()
697
700
698 def _sshv1respondooberror(fout, ferr, rsp):
701 def _sshv1respondooberror(fout, ferr, rsp):
699 ferr.write(b'%s\n-\n' % rsp)
702 ferr.write(b'%s\n-\n' % rsp)
700 ferr.flush()
703 ferr.flush()
701 fout.write(b'\n')
704 fout.write(b'\n')
702 fout.flush()
705 fout.flush()
703
706
704 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
707 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
705 """Handler for requests services via version 1 of SSH protocol."""
708 """Handler for requests services via version 1 of SSH protocol."""
706 def __init__(self, ui, fin, fout):
709 def __init__(self, ui, fin, fout):
707 self._ui = ui
710 self._ui = ui
708 self._fin = fin
711 self._fin = fin
709 self._fout = fout
712 self._fout = fout
710
713
711 @property
714 @property
712 def name(self):
715 def name(self):
713 return wireprototypes.SSHV1
716 return wireprototypes.SSHV1
714
717
715 def getargs(self, args):
718 def getargs(self, args):
716 data = {}
719 data = {}
717 keys = args.split()
720 keys = args.split()
718 for n in xrange(len(keys)):
721 for n in xrange(len(keys)):
719 argline = self._fin.readline()[:-1]
722 argline = self._fin.readline()[:-1]
720 arg, l = argline.split()
723 arg, l = argline.split()
721 if arg not in keys:
724 if arg not in keys:
722 raise error.Abort(_("unexpected parameter %r") % arg)
725 raise error.Abort(_("unexpected parameter %r") % arg)
723 if arg == '*':
726 if arg == '*':
724 star = {}
727 star = {}
725 for k in xrange(int(l)):
728 for k in xrange(int(l)):
726 argline = self._fin.readline()[:-1]
729 argline = self._fin.readline()[:-1]
727 arg, l = argline.split()
730 arg, l = argline.split()
728 val = self._fin.read(int(l))
731 val = self._fin.read(int(l))
729 star[arg] = val
732 star[arg] = val
730 data['*'] = star
733 data['*'] = star
731 else:
734 else:
732 val = self._fin.read(int(l))
735 val = self._fin.read(int(l))
733 data[arg] = val
736 data[arg] = val
734 return [data[k] for k in keys]
737 return [data[k] for k in keys]
735
738
736 def forwardpayload(self, fpout):
739 def forwardpayload(self, fpout):
737 # We initially send an empty response. This tells the client it is
740 # We initially send an empty response. This tells the client it is
738 # OK to start sending data. If a client sees any other response, it
741 # OK to start sending data. If a client sees any other response, it
739 # interprets it as an error.
742 # interprets it as an error.
740 _sshv1respondbytes(self._fout, b'')
743 _sshv1respondbytes(self._fout, b'')
741
744
742 # The file is in the form:
745 # The file is in the form:
743 #
746 #
744 # <chunk size>\n<chunk>
747 # <chunk size>\n<chunk>
745 # ...
748 # ...
746 # 0\n
749 # 0\n
747 count = int(self._fin.readline())
750 count = int(self._fin.readline())
748 while count:
751 while count:
749 fpout.write(self._fin.read(count))
752 fpout.write(self._fin.read(count))
750 count = int(self._fin.readline())
753 count = int(self._fin.readline())
751
754
752 @contextlib.contextmanager
755 @contextlib.contextmanager
753 def mayberedirectstdio(self):
756 def mayberedirectstdio(self):
754 yield None
757 yield None
755
758
756 def client(self):
759 def client(self):
757 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
760 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
758 return 'remote:ssh:' + client
761 return 'remote:ssh:' + client
759
762
760 def addcapabilities(self, repo, caps):
763 def addcapabilities(self, repo, caps):
761 caps.append(b'batch')
764 caps.append(b'batch')
762 return caps
765 return caps
763
766
764 def checkperm(self, perm):
767 def checkperm(self, perm):
765 pass
768 pass
766
769
767 class sshv2protocolhandler(sshv1protocolhandler):
770 class sshv2protocolhandler(sshv1protocolhandler):
768 """Protocol handler for version 2 of the SSH protocol."""
771 """Protocol handler for version 2 of the SSH protocol."""
769
772
770 @property
773 @property
771 def name(self):
774 def name(self):
772 return wireprototypes.SSHV2
775 return wireprototypes.SSHV2
773
776
774 def _runsshserver(ui, repo, fin, fout, ev):
777 def _runsshserver(ui, repo, fin, fout, ev):
775 # This function operates like a state machine of sorts. The following
778 # This function operates like a state machine of sorts. The following
776 # states are defined:
779 # states are defined:
777 #
780 #
778 # protov1-serving
781 # protov1-serving
779 # Server is in protocol version 1 serving mode. Commands arrive on
782 # Server is in protocol version 1 serving mode. Commands arrive on
780 # new lines. These commands are processed in this state, one command
783 # new lines. These commands are processed in this state, one command
781 # after the other.
784 # after the other.
782 #
785 #
783 # protov2-serving
786 # protov2-serving
784 # Server is in protocol version 2 serving mode.
787 # Server is in protocol version 2 serving mode.
785 #
788 #
786 # upgrade-initial
789 # upgrade-initial
787 # The server is going to process an upgrade request.
790 # The server is going to process an upgrade request.
788 #
791 #
789 # upgrade-v2-filter-legacy-handshake
792 # upgrade-v2-filter-legacy-handshake
790 # The protocol is being upgraded to version 2. The server is expecting
793 # The protocol is being upgraded to version 2. The server is expecting
791 # the legacy handshake from version 1.
794 # the legacy handshake from version 1.
792 #
795 #
793 # upgrade-v2-finish
796 # upgrade-v2-finish
794 # The upgrade to version 2 of the protocol is imminent.
797 # The upgrade to version 2 of the protocol is imminent.
795 #
798 #
796 # shutdown
799 # shutdown
797 # The server is shutting down, possibly in reaction to a client event.
800 # The server is shutting down, possibly in reaction to a client event.
798 #
801 #
799 # And here are their transitions:
802 # And here are their transitions:
800 #
803 #
801 # protov1-serving -> shutdown
804 # protov1-serving -> shutdown
802 # When server receives an empty request or encounters another
805 # When server receives an empty request or encounters another
803 # error.
806 # error.
804 #
807 #
805 # protov1-serving -> upgrade-initial
808 # protov1-serving -> upgrade-initial
806 # An upgrade request line was seen.
809 # An upgrade request line was seen.
807 #
810 #
808 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
811 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
809 # Upgrade to version 2 in progress. Server is expecting to
812 # Upgrade to version 2 in progress. Server is expecting to
810 # process a legacy handshake.
813 # process a legacy handshake.
811 #
814 #
812 # upgrade-v2-filter-legacy-handshake -> shutdown
815 # upgrade-v2-filter-legacy-handshake -> shutdown
813 # Client did not fulfill upgrade handshake requirements.
816 # Client did not fulfill upgrade handshake requirements.
814 #
817 #
815 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
818 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
816 # Client fulfilled version 2 upgrade requirements. Finishing that
819 # Client fulfilled version 2 upgrade requirements. Finishing that
817 # upgrade.
820 # upgrade.
818 #
821 #
819 # upgrade-v2-finish -> protov2-serving
822 # upgrade-v2-finish -> protov2-serving
820 # Protocol upgrade to version 2 complete. Server can now speak protocol
823 # Protocol upgrade to version 2 complete. Server can now speak protocol
821 # version 2.
824 # version 2.
822 #
825 #
823 # protov2-serving -> protov1-serving
826 # protov2-serving -> protov1-serving
824 # Ths happens by default since protocol version 2 is the same as
827 # Ths happens by default since protocol version 2 is the same as
825 # version 1 except for the handshake.
828 # version 1 except for the handshake.
826
829
827 state = 'protov1-serving'
830 state = 'protov1-serving'
828 proto = sshv1protocolhandler(ui, fin, fout)
831 proto = sshv1protocolhandler(ui, fin, fout)
829 protoswitched = False
832 protoswitched = False
830
833
831 while not ev.is_set():
834 while not ev.is_set():
832 if state == 'protov1-serving':
835 if state == 'protov1-serving':
833 # Commands are issued on new lines.
836 # Commands are issued on new lines.
834 request = fin.readline()[:-1]
837 request = fin.readline()[:-1]
835
838
836 # Empty lines signal to terminate the connection.
839 # Empty lines signal to terminate the connection.
837 if not request:
840 if not request:
838 state = 'shutdown'
841 state = 'shutdown'
839 continue
842 continue
840
843
841 # It looks like a protocol upgrade request. Transition state to
844 # It looks like a protocol upgrade request. Transition state to
842 # handle it.
845 # handle it.
843 if request.startswith(b'upgrade '):
846 if request.startswith(b'upgrade '):
844 if protoswitched:
847 if protoswitched:
845 _sshv1respondooberror(fout, ui.ferr,
848 _sshv1respondooberror(fout, ui.ferr,
846 b'cannot upgrade protocols multiple '
849 b'cannot upgrade protocols multiple '
847 b'times')
850 b'times')
848 state = 'shutdown'
851 state = 'shutdown'
849 continue
852 continue
850
853
851 state = 'upgrade-initial'
854 state = 'upgrade-initial'
852 continue
855 continue
853
856
854 available = wireproto.commands.commandavailable(request, proto)
857 available = wireproto.commands.commandavailable(request, proto)
855
858
856 # This command isn't available. Send an empty response and go
859 # This command isn't available. Send an empty response and go
857 # back to waiting for a new command.
860 # back to waiting for a new command.
858 if not available:
861 if not available:
859 _sshv1respondbytes(fout, b'')
862 _sshv1respondbytes(fout, b'')
860 continue
863 continue
861
864
862 rsp = wireproto.dispatch(repo, proto, request)
865 rsp = wireproto.dispatch(repo, proto, request)
863
866
864 if isinstance(rsp, bytes):
867 if isinstance(rsp, bytes):
865 _sshv1respondbytes(fout, rsp)
868 _sshv1respondbytes(fout, rsp)
866 elif isinstance(rsp, wireprototypes.bytesresponse):
869 elif isinstance(rsp, wireprototypes.bytesresponse):
867 _sshv1respondbytes(fout, rsp.data)
870 _sshv1respondbytes(fout, rsp.data)
868 elif isinstance(rsp, wireprototypes.streamres):
871 elif isinstance(rsp, wireprototypes.streamres):
869 _sshv1respondstream(fout, rsp)
872 _sshv1respondstream(fout, rsp)
870 elif isinstance(rsp, wireprototypes.streamreslegacy):
873 elif isinstance(rsp, wireprototypes.streamreslegacy):
871 _sshv1respondstream(fout, rsp)
874 _sshv1respondstream(fout, rsp)
872 elif isinstance(rsp, wireprototypes.pushres):
875 elif isinstance(rsp, wireprototypes.pushres):
873 _sshv1respondbytes(fout, b'')
876 _sshv1respondbytes(fout, b'')
874 _sshv1respondbytes(fout, b'%d' % rsp.res)
877 _sshv1respondbytes(fout, b'%d' % rsp.res)
875 elif isinstance(rsp, wireprototypes.pusherr):
878 elif isinstance(rsp, wireprototypes.pusherr):
876 _sshv1respondbytes(fout, rsp.res)
879 _sshv1respondbytes(fout, rsp.res)
877 elif isinstance(rsp, wireprototypes.ooberror):
880 elif isinstance(rsp, wireprototypes.ooberror):
878 _sshv1respondooberror(fout, ui.ferr, rsp.message)
881 _sshv1respondooberror(fout, ui.ferr, rsp.message)
879 else:
882 else:
880 raise error.ProgrammingError('unhandled response type from '
883 raise error.ProgrammingError('unhandled response type from '
881 'wire protocol command: %s' % rsp)
884 'wire protocol command: %s' % rsp)
882
885
883 # For now, protocol version 2 serving just goes back to version 1.
886 # For now, protocol version 2 serving just goes back to version 1.
884 elif state == 'protov2-serving':
887 elif state == 'protov2-serving':
885 state = 'protov1-serving'
888 state = 'protov1-serving'
886 continue
889 continue
887
890
888 elif state == 'upgrade-initial':
891 elif state == 'upgrade-initial':
889 # We should never transition into this state if we've switched
892 # We should never transition into this state if we've switched
890 # protocols.
893 # protocols.
891 assert not protoswitched
894 assert not protoswitched
892 assert proto.name == wireprototypes.SSHV1
895 assert proto.name == wireprototypes.SSHV1
893
896
894 # Expected: upgrade <token> <capabilities>
897 # Expected: upgrade <token> <capabilities>
895 # If we get something else, the request is malformed. It could be
898 # If we get something else, the request is malformed. It could be
896 # from a future client that has altered the upgrade line content.
899 # from a future client that has altered the upgrade line content.
897 # We treat this as an unknown command.
900 # We treat this as an unknown command.
898 try:
901 try:
899 token, caps = request.split(b' ')[1:]
902 token, caps = request.split(b' ')[1:]
900 except ValueError:
903 except ValueError:
901 _sshv1respondbytes(fout, b'')
904 _sshv1respondbytes(fout, b'')
902 state = 'protov1-serving'
905 state = 'protov1-serving'
903 continue
906 continue
904
907
905 # Send empty response if we don't support upgrading protocols.
908 # Send empty response if we don't support upgrading protocols.
906 if not ui.configbool('experimental', 'sshserver.support-v2'):
909 if not ui.configbool('experimental', 'sshserver.support-v2'):
907 _sshv1respondbytes(fout, b'')
910 _sshv1respondbytes(fout, b'')
908 state = 'protov1-serving'
911 state = 'protov1-serving'
909 continue
912 continue
910
913
911 try:
914 try:
912 caps = urlreq.parseqs(caps)
915 caps = urlreq.parseqs(caps)
913 except ValueError:
916 except ValueError:
914 _sshv1respondbytes(fout, b'')
917 _sshv1respondbytes(fout, b'')
915 state = 'protov1-serving'
918 state = 'protov1-serving'
916 continue
919 continue
917
920
918 # We don't see an upgrade request to protocol version 2. Ignore
921 # We don't see an upgrade request to protocol version 2. Ignore
919 # the upgrade request.
922 # the upgrade request.
920 wantedprotos = caps.get(b'proto', [b''])[0]
923 wantedprotos = caps.get(b'proto', [b''])[0]
921 if SSHV2 not in wantedprotos:
924 if SSHV2 not in wantedprotos:
922 _sshv1respondbytes(fout, b'')
925 _sshv1respondbytes(fout, b'')
923 state = 'protov1-serving'
926 state = 'protov1-serving'
924 continue
927 continue
925
928
926 # It looks like we can honor this upgrade request to protocol 2.
929 # It looks like we can honor this upgrade request to protocol 2.
927 # Filter the rest of the handshake protocol request lines.
930 # Filter the rest of the handshake protocol request lines.
928 state = 'upgrade-v2-filter-legacy-handshake'
931 state = 'upgrade-v2-filter-legacy-handshake'
929 continue
932 continue
930
933
931 elif state == 'upgrade-v2-filter-legacy-handshake':
934 elif state == 'upgrade-v2-filter-legacy-handshake':
932 # Client should have sent legacy handshake after an ``upgrade``
935 # Client should have sent legacy handshake after an ``upgrade``
933 # request. Expected lines:
936 # request. Expected lines:
934 #
937 #
935 # hello
938 # hello
936 # between
939 # between
937 # pairs 81
940 # pairs 81
938 # 0000...-0000...
941 # 0000...-0000...
939
942
940 ok = True
943 ok = True
941 for line in (b'hello', b'between', b'pairs 81'):
944 for line in (b'hello', b'between', b'pairs 81'):
942 request = fin.readline()[:-1]
945 request = fin.readline()[:-1]
943
946
944 if request != line:
947 if request != line:
945 _sshv1respondooberror(fout, ui.ferr,
948 _sshv1respondooberror(fout, ui.ferr,
946 b'malformed handshake protocol: '
949 b'malformed handshake protocol: '
947 b'missing %s' % line)
950 b'missing %s' % line)
948 ok = False
951 ok = False
949 state = 'shutdown'
952 state = 'shutdown'
950 break
953 break
951
954
952 if not ok:
955 if not ok:
953 continue
956 continue
954
957
955 request = fin.read(81)
958 request = fin.read(81)
956 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
959 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
957 _sshv1respondooberror(fout, ui.ferr,
960 _sshv1respondooberror(fout, ui.ferr,
958 b'malformed handshake protocol: '
961 b'malformed handshake protocol: '
959 b'missing between argument value')
962 b'missing between argument value')
960 state = 'shutdown'
963 state = 'shutdown'
961 continue
964 continue
962
965
963 state = 'upgrade-v2-finish'
966 state = 'upgrade-v2-finish'
964 continue
967 continue
965
968
966 elif state == 'upgrade-v2-finish':
969 elif state == 'upgrade-v2-finish':
967 # Send the upgrade response.
970 # Send the upgrade response.
968 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
971 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
969 servercaps = wireproto.capabilities(repo, proto)
972 servercaps = wireproto.capabilities(repo, proto)
970 rsp = b'capabilities: %s' % servercaps.data
973 rsp = b'capabilities: %s' % servercaps.data
971 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
974 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
972 fout.flush()
975 fout.flush()
973
976
974 proto = sshv2protocolhandler(ui, fin, fout)
977 proto = sshv2protocolhandler(ui, fin, fout)
975 protoswitched = True
978 protoswitched = True
976
979
977 state = 'protov2-serving'
980 state = 'protov2-serving'
978 continue
981 continue
979
982
980 elif state == 'shutdown':
983 elif state == 'shutdown':
981 break
984 break
982
985
983 else:
986 else:
984 raise error.ProgrammingError('unhandled ssh server state: %s' %
987 raise error.ProgrammingError('unhandled ssh server state: %s' %
985 state)
988 state)
986
989
987 class sshserver(object):
990 class sshserver(object):
988 def __init__(self, ui, repo, logfh=None):
991 def __init__(self, ui, repo, logfh=None):
989 self._ui = ui
992 self._ui = ui
990 self._repo = repo
993 self._repo = repo
991 self._fin = ui.fin
994 self._fin = ui.fin
992 self._fout = ui.fout
995 self._fout = ui.fout
993
996
994 # Log write I/O to stdout and stderr if configured.
997 # Log write I/O to stdout and stderr if configured.
995 if logfh:
998 if logfh:
996 self._fout = util.makeloggingfileobject(
999 self._fout = util.makeloggingfileobject(
997 logfh, self._fout, 'o', logdata=True)
1000 logfh, self._fout, 'o', logdata=True)
998 ui.ferr = util.makeloggingfileobject(
1001 ui.ferr = util.makeloggingfileobject(
999 logfh, ui.ferr, 'e', logdata=True)
1002 logfh, ui.ferr, 'e', logdata=True)
1000
1003
1001 hook.redirect(True)
1004 hook.redirect(True)
1002 ui.fout = repo.ui.fout = ui.ferr
1005 ui.fout = repo.ui.fout = ui.ferr
1003
1006
1004 # Prevent insertion/deletion of CRs
1007 # Prevent insertion/deletion of CRs
1005 util.setbinary(self._fin)
1008 util.setbinary(self._fin)
1006 util.setbinary(self._fout)
1009 util.setbinary(self._fout)
1007
1010
1008 def serve_forever(self):
1011 def serve_forever(self):
1009 self.serveuntil(threading.Event())
1012 self.serveuntil(threading.Event())
1010 sys.exit(0)
1013 sys.exit(0)
1011
1014
1012 def serveuntil(self, ev):
1015 def serveuntil(self, ev):
1013 """Serve until a threading.Event is set."""
1016 """Serve until a threading.Event is set."""
1014 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
1017 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
@@ -1,415 +1,415
1 $ HTTPV2=exp-http-v2-0001
1 $ HTTPV2=exp-http-v2-0001
2 $ MEDIATYPE=application/mercurial-exp-framing-0001
2 $ MEDIATYPE=application/mercurial-exp-framing-0002
3
3
4 $ send() {
4 $ send() {
5 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
5 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
6 > }
6 > }
7
7
8 $ cat > dummycommands.py << EOF
8 $ cat > dummycommands.py << EOF
9 > from mercurial import wireprototypes, wireproto
9 > from mercurial import wireprototypes, wireproto
10 > @wireproto.wireprotocommand('customreadonly', permission='pull')
10 > @wireproto.wireprotocommand('customreadonly', permission='pull')
11 > def customreadonly(repo, proto):
11 > def customreadonly(repo, proto):
12 > return wireprototypes.bytesresponse(b'customreadonly bytes response')
12 > return wireprototypes.bytesresponse(b'customreadonly bytes response')
13 > @wireproto.wireprotocommand('customreadwrite', permission='push')
13 > @wireproto.wireprotocommand('customreadwrite', permission='push')
14 > def customreadwrite(repo, proto):
14 > def customreadwrite(repo, proto):
15 > return wireprototypes.bytesresponse(b'customreadwrite bytes response')
15 > return wireprototypes.bytesresponse(b'customreadwrite bytes response')
16 > EOF
16 > EOF
17
17
18 $ cat >> $HGRCPATH << EOF
18 $ cat >> $HGRCPATH << EOF
19 > [extensions]
19 > [extensions]
20 > dummycommands = $TESTTMP/dummycommands.py
20 > dummycommands = $TESTTMP/dummycommands.py
21 > EOF
21 > EOF
22
22
23 $ hg init server
23 $ hg init server
24 $ cat > server/.hg/hgrc << EOF
24 $ cat > server/.hg/hgrc << EOF
25 > [experimental]
25 > [experimental]
26 > web.apiserver = true
26 > web.apiserver = true
27 > EOF
27 > EOF
28 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
28 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
29 $ cat hg.pid > $DAEMON_PIDS
29 $ cat hg.pid > $DAEMON_PIDS
30
30
31 HTTP v2 protocol not enabled by default
31 HTTP v2 protocol not enabled by default
32
32
33 $ send << EOF
33 $ send << EOF
34 > httprequest GET api/$HTTPV2
34 > httprequest GET api/$HTTPV2
35 > user-agent: test
35 > user-agent: test
36 > EOF
36 > EOF
37 using raw connection to peer
37 using raw connection to peer
38 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
38 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
39 s> Accept-Encoding: identity\r\n
39 s> Accept-Encoding: identity\r\n
40 s> user-agent: test\r\n
40 s> user-agent: test\r\n
41 s> host: $LOCALIP:$HGPORT\r\n (glob)
41 s> host: $LOCALIP:$HGPORT\r\n (glob)
42 s> \r\n
42 s> \r\n
43 s> makefile('rb', None)
43 s> makefile('rb', None)
44 s> HTTP/1.1 404 Not Found\r\n
44 s> HTTP/1.1 404 Not Found\r\n
45 s> Server: testing stub value\r\n
45 s> Server: testing stub value\r\n
46 s> Date: $HTTP_DATE$\r\n
46 s> Date: $HTTP_DATE$\r\n
47 s> Content-Type: text/plain\r\n
47 s> Content-Type: text/plain\r\n
48 s> Content-Length: 33\r\n
48 s> Content-Length: 33\r\n
49 s> \r\n
49 s> \r\n
50 s> API exp-http-v2-0001 not enabled\n
50 s> API exp-http-v2-0001 not enabled\n
51
51
52 Restart server with support for HTTP v2 API
52 Restart server with support for HTTP v2 API
53
53
54 $ killdaemons.py
54 $ killdaemons.py
55 $ cat > server/.hg/hgrc << EOF
55 $ cat > server/.hg/hgrc << EOF
56 > [experimental]
56 > [experimental]
57 > web.apiserver = true
57 > web.apiserver = true
58 > web.api.http-v2 = true
58 > web.api.http-v2 = true
59 > EOF
59 > EOF
60
60
61 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
61 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
62 $ cat hg.pid > $DAEMON_PIDS
62 $ cat hg.pid > $DAEMON_PIDS
63
63
64 Request to unknown command yields 404
64 Request to unknown command yields 404
65
65
66 $ send << EOF
66 $ send << EOF
67 > httprequest POST api/$HTTPV2/ro/badcommand
67 > httprequest POST api/$HTTPV2/ro/badcommand
68 > user-agent: test
68 > user-agent: test
69 > EOF
69 > EOF
70 using raw connection to peer
70 using raw connection to peer
71 s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
71 s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
72 s> Accept-Encoding: identity\r\n
72 s> Accept-Encoding: identity\r\n
73 s> user-agent: test\r\n
73 s> user-agent: test\r\n
74 s> host: $LOCALIP:$HGPORT\r\n (glob)
74 s> host: $LOCALIP:$HGPORT\r\n (glob)
75 s> \r\n
75 s> \r\n
76 s> makefile('rb', None)
76 s> makefile('rb', None)
77 s> HTTP/1.1 404 Not Found\r\n
77 s> HTTP/1.1 404 Not Found\r\n
78 s> Server: testing stub value\r\n
78 s> Server: testing stub value\r\n
79 s> Date: $HTTP_DATE$\r\n
79 s> Date: $HTTP_DATE$\r\n
80 s> Content-Type: text/plain\r\n
80 s> Content-Type: text/plain\r\n
81 s> Content-Length: 42\r\n
81 s> Content-Length: 42\r\n
82 s> \r\n
82 s> \r\n
83 s> unknown wire protocol command: badcommand\n
83 s> unknown wire protocol command: badcommand\n
84
84
85 GET to read-only command yields a 405
85 GET to read-only command yields a 405
86
86
87 $ send << EOF
87 $ send << EOF
88 > httprequest GET api/$HTTPV2/ro/customreadonly
88 > httprequest GET api/$HTTPV2/ro/customreadonly
89 > user-agent: test
89 > user-agent: test
90 > EOF
90 > EOF
91 using raw connection to peer
91 using raw connection to peer
92 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
92 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
93 s> Accept-Encoding: identity\r\n
93 s> Accept-Encoding: identity\r\n
94 s> user-agent: test\r\n
94 s> user-agent: test\r\n
95 s> host: $LOCALIP:$HGPORT\r\n (glob)
95 s> host: $LOCALIP:$HGPORT\r\n (glob)
96 s> \r\n
96 s> \r\n
97 s> makefile('rb', None)
97 s> makefile('rb', None)
98 s> HTTP/1.1 405 Method Not Allowed\r\n
98 s> HTTP/1.1 405 Method Not Allowed\r\n
99 s> Server: testing stub value\r\n
99 s> Server: testing stub value\r\n
100 s> Date: $HTTP_DATE$\r\n
100 s> Date: $HTTP_DATE$\r\n
101 s> Allow: POST\r\n
101 s> Allow: POST\r\n
102 s> Content-Length: 30\r\n
102 s> Content-Length: 30\r\n
103 s> \r\n
103 s> \r\n
104 s> commands require POST requests
104 s> commands require POST requests
105
105
106 Missing Accept header results in 406
106 Missing Accept header results in 406
107
107
108 $ send << EOF
108 $ send << EOF
109 > httprequest POST api/$HTTPV2/ro/customreadonly
109 > httprequest POST api/$HTTPV2/ro/customreadonly
110 > user-agent: test
110 > user-agent: test
111 > EOF
111 > EOF
112 using raw connection to peer
112 using raw connection to peer
113 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
113 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
114 s> Accept-Encoding: identity\r\n
114 s> Accept-Encoding: identity\r\n
115 s> user-agent: test\r\n
115 s> user-agent: test\r\n
116 s> host: $LOCALIP:$HGPORT\r\n (glob)
116 s> host: $LOCALIP:$HGPORT\r\n (glob)
117 s> \r\n
117 s> \r\n
118 s> makefile('rb', None)
118 s> makefile('rb', None)
119 s> HTTP/1.1 406 Not Acceptable\r\n
119 s> HTTP/1.1 406 Not Acceptable\r\n
120 s> Server: testing stub value\r\n
120 s> Server: testing stub value\r\n
121 s> Date: $HTTP_DATE$\r\n
121 s> Date: $HTTP_DATE$\r\n
122 s> Content-Type: text/plain\r\n
122 s> Content-Type: text/plain\r\n
123 s> Content-Length: 85\r\n
123 s> Content-Length: 85\r\n
124 s> \r\n
124 s> \r\n
125 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0001\n
125 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n
126
126
127 Bad Accept header results in 406
127 Bad Accept header results in 406
128
128
129 $ send << EOF
129 $ send << EOF
130 > httprequest POST api/$HTTPV2/ro/customreadonly
130 > httprequest POST api/$HTTPV2/ro/customreadonly
131 > accept: invalid
131 > accept: invalid
132 > user-agent: test
132 > user-agent: test
133 > EOF
133 > EOF
134 using raw connection to peer
134 using raw connection to peer
135 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
135 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
136 s> Accept-Encoding: identity\r\n
136 s> Accept-Encoding: identity\r\n
137 s> accept: invalid\r\n
137 s> accept: invalid\r\n
138 s> user-agent: test\r\n
138 s> user-agent: test\r\n
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
140 s> \r\n
140 s> \r\n
141 s> makefile('rb', None)
141 s> makefile('rb', None)
142 s> HTTP/1.1 406 Not Acceptable\r\n
142 s> HTTP/1.1 406 Not Acceptable\r\n
143 s> Server: testing stub value\r\n
143 s> Server: testing stub value\r\n
144 s> Date: $HTTP_DATE$\r\n
144 s> Date: $HTTP_DATE$\r\n
145 s> Content-Type: text/plain\r\n
145 s> Content-Type: text/plain\r\n
146 s> Content-Length: 85\r\n
146 s> Content-Length: 85\r\n
147 s> \r\n
147 s> \r\n
148 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0001\n
148 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n
149
149
150 Bad Content-Type header results in 415
150 Bad Content-Type header results in 415
151
151
152 $ send << EOF
152 $ send << EOF
153 > httprequest POST api/$HTTPV2/ro/customreadonly
153 > httprequest POST api/$HTTPV2/ro/customreadonly
154 > accept: $MEDIATYPE
154 > accept: $MEDIATYPE
155 > user-agent: test
155 > user-agent: test
156 > content-type: badmedia
156 > content-type: badmedia
157 > EOF
157 > EOF
158 using raw connection to peer
158 using raw connection to peer
159 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
159 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
160 s> Accept-Encoding: identity\r\n
160 s> Accept-Encoding: identity\r\n
161 s> accept: application/mercurial-exp-framing-0001\r\n
161 s> accept: application/mercurial-exp-framing-0002\r\n
162 s> content-type: badmedia\r\n
162 s> content-type: badmedia\r\n
163 s> user-agent: test\r\n
163 s> user-agent: test\r\n
164 s> host: $LOCALIP:$HGPORT\r\n (glob)
164 s> host: $LOCALIP:$HGPORT\r\n (glob)
165 s> \r\n
165 s> \r\n
166 s> makefile('rb', None)
166 s> makefile('rb', None)
167 s> HTTP/1.1 415 Unsupported Media Type\r\n
167 s> HTTP/1.1 415 Unsupported Media Type\r\n
168 s> Server: testing stub value\r\n
168 s> Server: testing stub value\r\n
169 s> Date: $HTTP_DATE$\r\n
169 s> Date: $HTTP_DATE$\r\n
170 s> Content-Type: text/plain\r\n
170 s> Content-Type: text/plain\r\n
171 s> Content-Length: 88\r\n
171 s> Content-Length: 88\r\n
172 s> \r\n
172 s> \r\n
173 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0001\n
173 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0002\n
174
174
175 Request to read-only command works out of the box
175 Request to read-only command works out of the box
176
176
177 $ send << EOF
177 $ send << EOF
178 > httprequest POST api/$HTTPV2/ro/customreadonly
178 > httprequest POST api/$HTTPV2/ro/customreadonly
179 > accept: $MEDIATYPE
179 > accept: $MEDIATYPE
180 > content-type: $MEDIATYPE
180 > content-type: $MEDIATYPE
181 > user-agent: test
181 > user-agent: test
182 > frame command-name eos customreadonly
182 > frame 1 command-name eos customreadonly
183 > EOF
183 > EOF
184 using raw connection to peer
184 using raw connection to peer
185 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
185 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
186 s> Accept-Encoding: identity\r\n
186 s> Accept-Encoding: identity\r\n
187 s> accept: application/mercurial-exp-framing-0001\r\n
187 s> accept: application/mercurial-exp-framing-0002\r\n
188 s> content-type: application/mercurial-exp-framing-0001\r\n
188 s> content-type: application/mercurial-exp-framing-0002\r\n
189 s> user-agent: test\r\n
189 s> user-agent: test\r\n
190 s> content-length: 18\r\n
190 s> *\r\n (glob)
191 s> host: $LOCALIP:$HGPORT\r\n (glob)
191 s> host: $LOCALIP:$HGPORT\r\n (glob)
192 s> \r\n
192 s> \r\n
193 s> \x0e\x00\x00\x11customreadonly
193 s> \x0e\x00\x00\x01\x00\x11customreadonly
194 s> makefile('rb', None)
194 s> makefile('rb', None)
195 s> HTTP/1.1 200 OK\r\n
195 s> HTTP/1.1 200 OK\r\n
196 s> Server: testing stub value\r\n
196 s> Server: testing stub value\r\n
197 s> Date: $HTTP_DATE$\r\n
197 s> Date: $HTTP_DATE$\r\n
198 s> Content-Type: application/mercurial-exp-framing-0001\r\n
198 s> Content-Type: application/mercurial-exp-framing-0002\r\n
199 s> Transfer-Encoding: chunked\r\n
199 s> Transfer-Encoding: chunked\r\n
200 s> \r\n
200 s> \r\n
201 s> 21\r\n
201 s> 23\r\n
202 s> \x1d\x00\x00Bcustomreadonly bytes response
202 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
203 s> \r\n
203 s> \r\n
204 s> 0\r\n
204 s> 0\r\n
205 s> \r\n
205 s> \r\n
206
206
207 Request to read-write command fails because server is read-only by default
207 Request to read-write command fails because server is read-only by default
208
208
209 GET to read-write request yields 405
209 GET to read-write request yields 405
210
210
211 $ send << EOF
211 $ send << EOF
212 > httprequest GET api/$HTTPV2/rw/customreadonly
212 > httprequest GET api/$HTTPV2/rw/customreadonly
213 > user-agent: test
213 > user-agent: test
214 > EOF
214 > EOF
215 using raw connection to peer
215 using raw connection to peer
216 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
216 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
217 s> Accept-Encoding: identity\r\n
217 s> Accept-Encoding: identity\r\n
218 s> user-agent: test\r\n
218 s> user-agent: test\r\n
219 s> host: $LOCALIP:$HGPORT\r\n (glob)
219 s> host: $LOCALIP:$HGPORT\r\n (glob)
220 s> \r\n
220 s> \r\n
221 s> makefile('rb', None)
221 s> makefile('rb', None)
222 s> HTTP/1.1 405 Method Not Allowed\r\n
222 s> HTTP/1.1 405 Method Not Allowed\r\n
223 s> Server: testing stub value\r\n
223 s> Server: testing stub value\r\n
224 s> Date: $HTTP_DATE$\r\n
224 s> Date: $HTTP_DATE$\r\n
225 s> Allow: POST\r\n
225 s> Allow: POST\r\n
226 s> Content-Length: 30\r\n
226 s> Content-Length: 30\r\n
227 s> \r\n
227 s> \r\n
228 s> commands require POST requests
228 s> commands require POST requests
229
229
230 Even for unknown commands
230 Even for unknown commands
231
231
232 $ send << EOF
232 $ send << EOF
233 > httprequest GET api/$HTTPV2/rw/badcommand
233 > httprequest GET api/$HTTPV2/rw/badcommand
234 > user-agent: test
234 > user-agent: test
235 > EOF
235 > EOF
236 using raw connection to peer
236 using raw connection to peer
237 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
237 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
238 s> Accept-Encoding: identity\r\n
238 s> Accept-Encoding: identity\r\n
239 s> user-agent: test\r\n
239 s> user-agent: test\r\n
240 s> host: $LOCALIP:$HGPORT\r\n (glob)
240 s> host: $LOCALIP:$HGPORT\r\n (glob)
241 s> \r\n
241 s> \r\n
242 s> makefile('rb', None)
242 s> makefile('rb', None)
243 s> HTTP/1.1 405 Method Not Allowed\r\n
243 s> HTTP/1.1 405 Method Not Allowed\r\n
244 s> Server: testing stub value\r\n
244 s> Server: testing stub value\r\n
245 s> Date: $HTTP_DATE$\r\n
245 s> Date: $HTTP_DATE$\r\n
246 s> Allow: POST\r\n
246 s> Allow: POST\r\n
247 s> Content-Length: 30\r\n
247 s> Content-Length: 30\r\n
248 s> \r\n
248 s> \r\n
249 s> commands require POST requests
249 s> commands require POST requests
250
250
251 SSL required by default
251 SSL required by default
252
252
253 $ send << EOF
253 $ send << EOF
254 > httprequest POST api/$HTTPV2/rw/customreadonly
254 > httprequest POST api/$HTTPV2/rw/customreadonly
255 > user-agent: test
255 > user-agent: test
256 > EOF
256 > EOF
257 using raw connection to peer
257 using raw connection to peer
258 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
258 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
259 s> Accept-Encoding: identity\r\n
259 s> Accept-Encoding: identity\r\n
260 s> user-agent: test\r\n
260 s> user-agent: test\r\n
261 s> host: $LOCALIP:$HGPORT\r\n (glob)
261 s> host: $LOCALIP:$HGPORT\r\n (glob)
262 s> \r\n
262 s> \r\n
263 s> makefile('rb', None)
263 s> makefile('rb', None)
264 s> HTTP/1.1 403 ssl required\r\n
264 s> HTTP/1.1 403 ssl required\r\n
265 s> Server: testing stub value\r\n
265 s> Server: testing stub value\r\n
266 s> Date: $HTTP_DATE$\r\n
266 s> Date: $HTTP_DATE$\r\n
267 s> Content-Length: 17\r\n
267 s> Content-Length: 17\r\n
268 s> \r\n
268 s> \r\n
269 s> permission denied
269 s> permission denied
270
270
271 Restart server to allow non-ssl read-write operations
271 Restart server to allow non-ssl read-write operations
272
272
273 $ killdaemons.py
273 $ killdaemons.py
274 $ cat > server/.hg/hgrc << EOF
274 $ cat > server/.hg/hgrc << EOF
275 > [experimental]
275 > [experimental]
276 > web.apiserver = true
276 > web.apiserver = true
277 > web.api.http-v2 = true
277 > web.api.http-v2 = true
278 > [web]
278 > [web]
279 > push_ssl = false
279 > push_ssl = false
280 > allow-push = *
280 > allow-push = *
281 > EOF
281 > EOF
282
282
283 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
283 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
284 $ cat hg.pid > $DAEMON_PIDS
284 $ cat hg.pid > $DAEMON_PIDS
285
285
286 Authorized request for valid read-write command works
286 Authorized request for valid read-write command works
287
287
288 $ send << EOF
288 $ send << EOF
289 > httprequest POST api/$HTTPV2/rw/customreadonly
289 > httprequest POST api/$HTTPV2/rw/customreadonly
290 > user-agent: test
290 > user-agent: test
291 > accept: $MEDIATYPE
291 > accept: $MEDIATYPE
292 > content-type: $MEDIATYPE
292 > content-type: $MEDIATYPE
293 > frame command-name eos customreadonly
293 > frame 1 command-name eos customreadonly
294 > EOF
294 > EOF
295 using raw connection to peer
295 using raw connection to peer
296 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
296 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
297 s> Accept-Encoding: identity\r\n
297 s> Accept-Encoding: identity\r\n
298 s> accept: application/mercurial-exp-framing-0001\r\n
298 s> accept: application/mercurial-exp-framing-0002\r\n
299 s> content-type: application/mercurial-exp-framing-0001\r\n
299 s> content-type: application/mercurial-exp-framing-0002\r\n
300 s> user-agent: test\r\n
300 s> user-agent: test\r\n
301 s> content-length: 18\r\n
301 s> content-length: 20\r\n
302 s> host: $LOCALIP:$HGPORT\r\n (glob)
302 s> host: $LOCALIP:$HGPORT\r\n (glob)
303 s> \r\n
303 s> \r\n
304 s> \x0e\x00\x00\x11customreadonly
304 s> \x0e\x00\x00\x01\x00\x11customreadonly
305 s> makefile('rb', None)
305 s> makefile('rb', None)
306 s> HTTP/1.1 200 OK\r\n
306 s> HTTP/1.1 200 OK\r\n
307 s> Server: testing stub value\r\n
307 s> Server: testing stub value\r\n
308 s> Date: $HTTP_DATE$\r\n
308 s> Date: $HTTP_DATE$\r\n
309 s> Content-Type: application/mercurial-exp-framing-0001\r\n
309 s> Content-Type: application/mercurial-exp-framing-0002\r\n
310 s> Transfer-Encoding: chunked\r\n
310 s> Transfer-Encoding: chunked\r\n
311 s> \r\n
311 s> \r\n
312 s> 21\r\n
312 s> 23\r\n
313 s> \x1d\x00\x00Bcustomreadonly bytes response
313 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
314 s> \r\n
314 s> \r\n
315 s> 0\r\n
315 s> 0\r\n
316 s> \r\n
316 s> \r\n
317
317
318 Authorized request for unknown command is rejected
318 Authorized request for unknown command is rejected
319
319
320 $ send << EOF
320 $ send << EOF
321 > httprequest POST api/$HTTPV2/rw/badcommand
321 > httprequest POST api/$HTTPV2/rw/badcommand
322 > user-agent: test
322 > user-agent: test
323 > accept: $MEDIATYPE
323 > accept: $MEDIATYPE
324 > EOF
324 > EOF
325 using raw connection to peer
325 using raw connection to peer
326 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
326 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
327 s> Accept-Encoding: identity\r\n
327 s> Accept-Encoding: identity\r\n
328 s> accept: application/mercurial-exp-framing-0001\r\n
328 s> accept: application/mercurial-exp-framing-0002\r\n
329 s> user-agent: test\r\n
329 s> user-agent: test\r\n
330 s> host: $LOCALIP:$HGPORT\r\n (glob)
330 s> host: $LOCALIP:$HGPORT\r\n (glob)
331 s> \r\n
331 s> \r\n
332 s> makefile('rb', None)
332 s> makefile('rb', None)
333 s> HTTP/1.1 404 Not Found\r\n
333 s> HTTP/1.1 404 Not Found\r\n
334 s> Server: testing stub value\r\n
334 s> Server: testing stub value\r\n
335 s> Date: $HTTP_DATE$\r\n
335 s> Date: $HTTP_DATE$\r\n
336 s> Content-Type: text/plain\r\n
336 s> Content-Type: text/plain\r\n
337 s> Content-Length: 42\r\n
337 s> Content-Length: 42\r\n
338 s> \r\n
338 s> \r\n
339 s> unknown wire protocol command: badcommand\n
339 s> unknown wire protocol command: badcommand\n
340
340
341 debugreflect isn't enabled by default
341 debugreflect isn't enabled by default
342
342
343 $ send << EOF
343 $ send << EOF
344 > httprequest POST api/$HTTPV2/ro/debugreflect
344 > httprequest POST api/$HTTPV2/ro/debugreflect
345 > user-agent: test
345 > user-agent: test
346 > EOF
346 > EOF
347 using raw connection to peer
347 using raw connection to peer
348 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
348 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
349 s> Accept-Encoding: identity\r\n
349 s> Accept-Encoding: identity\r\n
350 s> user-agent: test\r\n
350 s> user-agent: test\r\n
351 s> host: $LOCALIP:$HGPORT\r\n (glob)
351 s> host: $LOCALIP:$HGPORT\r\n (glob)
352 s> \r\n
352 s> \r\n
353 s> makefile('rb', None)
353 s> makefile('rb', None)
354 s> HTTP/1.1 404 Not Found\r\n
354 s> HTTP/1.1 404 Not Found\r\n
355 s> Server: testing stub value\r\n
355 s> Server: testing stub value\r\n
356 s> Date: $HTTP_DATE$\r\n
356 s> Date: $HTTP_DATE$\r\n
357 s> Content-Type: text/plain\r\n
357 s> Content-Type: text/plain\r\n
358 s> Content-Length: 34\r\n
358 s> Content-Length: 34\r\n
359 s> \r\n
359 s> \r\n
360 s> debugreflect service not available
360 s> debugreflect service not available
361
361
362 Restart server to get debugreflect endpoint
362 Restart server to get debugreflect endpoint
363
363
364 $ killdaemons.py
364 $ killdaemons.py
365 $ cat > server/.hg/hgrc << EOF
365 $ cat > server/.hg/hgrc << EOF
366 > [experimental]
366 > [experimental]
367 > web.apiserver = true
367 > web.apiserver = true
368 > web.api.debugreflect = true
368 > web.api.debugreflect = true
369 > web.api.http-v2 = true
369 > web.api.http-v2 = true
370 > [web]
370 > [web]
371 > push_ssl = false
371 > push_ssl = false
372 > allow-push = *
372 > allow-push = *
373 > EOF
373 > EOF
374
374
375 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
375 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
376 $ cat hg.pid > $DAEMON_PIDS
376 $ cat hg.pid > $DAEMON_PIDS
377
377
378 Command frames can be reflected via debugreflect
378 Command frames can be reflected via debugreflect
379
379
380 $ send << EOF
380 $ send << EOF
381 > httprequest POST api/$HTTPV2/ro/debugreflect
381 > httprequest POST api/$HTTPV2/ro/debugreflect
382 > accept: $MEDIATYPE
382 > accept: $MEDIATYPE
383 > content-type: $MEDIATYPE
383 > content-type: $MEDIATYPE
384 > user-agent: test
384 > user-agent: test
385 > frame command-name have-args command1
385 > frame 1 command-name have-args command1
386 > frame command-argument 0 \x03\x00\x04\x00fooval1
386 > frame 1 command-argument 0 \x03\x00\x04\x00fooval1
387 > frame command-argument eoa \x04\x00\x03\x00bar1val
387 > frame 1 command-argument eoa \x04\x00\x03\x00bar1val
388 > EOF
388 > EOF
389 using raw connection to peer
389 using raw connection to peer
390 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
390 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
391 s> Accept-Encoding: identity\r\n
391 s> Accept-Encoding: identity\r\n
392 s> accept: application/mercurial-exp-framing-0001\r\n
392 s> accept: application/mercurial-exp-framing-0002\r\n
393 s> content-type: application/mercurial-exp-framing-0001\r\n
393 s> content-type: application/mercurial-exp-framing-0002\r\n
394 s> user-agent: test\r\n
394 s> user-agent: test\r\n
395 s> content-length: 42\r\n
395 s> content-length: 48\r\n
396 s> host: $LOCALIP:$HGPORT\r\n (glob)
396 s> host: $LOCALIP:$HGPORT\r\n (glob)
397 s> \r\n
397 s> \r\n
398 s> \x08\x00\x00\x12command1\x0b\x00\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00"\x04\x00\x03\x00bar1val
398 s> \x08\x00\x00\x01\x00\x12command1\x0b\x00\x00\x01\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00\x01\x00"\x04\x00\x03\x00bar1val
399 s> makefile('rb', None)
399 s> makefile('rb', None)
400 s> HTTP/1.1 200 OK\r\n
400 s> HTTP/1.1 200 OK\r\n
401 s> Server: testing stub value\r\n
401 s> Server: testing stub value\r\n
402 s> Date: $HTTP_DATE$\r\n
402 s> Date: $HTTP_DATE$\r\n
403 s> Content-Type: text/plain\r\n
403 s> Content-Type: text/plain\r\n
404 s> Content-Length: 310\r\n
404 s> Content-Length: 332\r\n
405 s> \r\n
405 s> \r\n
406 s> received: 1 2 command1\n
406 s> received: 1 2 1 command1\n
407 s> ["wantframe", {"state": "command-receiving-args"}]\n
407 s> ["wantframe", {"state": "command-receiving-args"}]\n
408 s> received: 2 0 \x03\x00\x04\x00fooval1\n
408 s> received: 2 0 1 \x03\x00\x04\x00fooval1\n
409 s> ["wantframe", {"state": "command-receiving-args"}]\n
409 s> ["wantframe", {"state": "command-receiving-args"}]\n
410 s> received: 2 2 \x04\x00\x03\x00bar1val\n
410 s> received: 2 2 1 \x04\x00\x03\x00bar1val\n
411 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null}]\n
411 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
412 s> received: <no frame>\n
412 s> received: <no frame>\n
413 s> {"action": "noop"}
413 s> {"action": "noop"}
414
414
415 $ cat error.log
415 $ cat error.log
@@ -1,349 +1,375
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2
2
3 import unittest
3 import unittest
4
4
5 from mercurial import (
5 from mercurial import (
6 util,
6 util,
7 wireprotoframing as framing,
7 wireprotoframing as framing,
8 )
8 )
9
9
10 ffs = framing.makeframefromhumanstring
10 ffs = framing.makeframefromhumanstring
11
11
12 def makereactor(deferoutput=False):
12 def makereactor(deferoutput=False):
13 return framing.serverreactor(deferoutput=deferoutput)
13 return framing.serverreactor(deferoutput=deferoutput)
14
14
15 def sendframes(reactor, gen):
15 def sendframes(reactor, gen):
16 """Send a generator of frame bytearray to a reactor.
16 """Send a generator of frame bytearray to a reactor.
17
17
18 Emits a generator of results from ``onframerecv()`` calls.
18 Emits a generator of results from ``onframerecv()`` calls.
19 """
19 """
20 for frame in gen:
20 for frame in gen:
21 frametype, frameflags, framelength = framing.parseheader(frame)
21 rid, frametype, frameflags, framelength = framing.parseheader(frame)
22 payload = frame[framing.FRAME_HEADER_SIZE:]
22 payload = frame[framing.FRAME_HEADER_SIZE:]
23 assert len(payload) == framelength
23 assert len(payload) == framelength
24
24
25 yield reactor.onframerecv(frametype, frameflags, payload)
25 yield reactor.onframerecv(rid, frametype, frameflags, payload)
26
26
27 def sendcommandframes(reactor, cmd, args, datafh=None):
27 def sendcommandframes(reactor, rid, cmd, args, datafh=None):
28 """Generate frames to run a command and send them to a reactor."""
28 """Generate frames to run a command and send them to a reactor."""
29 return sendframes(reactor, framing.createcommandframes(cmd, args, datafh))
29 return sendframes(reactor,
30 framing.createcommandframes(rid, cmd, args, datafh))
30
31
31 class FrameTests(unittest.TestCase):
32 class FrameTests(unittest.TestCase):
32 def testdataexactframesize(self):
33 def testdataexactframesize(self):
33 data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE)
34 data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE)
34
35
35 frames = list(framing.createcommandframes(b'command', {}, data))
36 frames = list(framing.createcommandframes(1, b'command', {}, data))
36 self.assertEqual(frames, [
37 self.assertEqual(frames, [
37 ffs(b'command-name have-data command'),
38 ffs(b'1 command-name have-data command'),
38 ffs(b'command-data continuation %s' % data.getvalue()),
39 ffs(b'1 command-data continuation %s' % data.getvalue()),
39 ffs(b'command-data eos ')
40 ffs(b'1 command-data eos ')
40 ])
41 ])
41
42
42 def testdatamultipleframes(self):
43 def testdatamultipleframes(self):
43 data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1))
44 data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1))
44 frames = list(framing.createcommandframes(b'command', {}, data))
45 frames = list(framing.createcommandframes(1, b'command', {}, data))
45 self.assertEqual(frames, [
46 self.assertEqual(frames, [
46 ffs(b'command-name have-data command'),
47 ffs(b'1 command-name have-data command'),
47 ffs(b'command-data continuation %s' % (
48 ffs(b'1 command-data continuation %s' % (
48 b'x' * framing.DEFAULT_MAX_FRAME_SIZE)),
49 b'x' * framing.DEFAULT_MAX_FRAME_SIZE)),
49 ffs(b'command-data eos x'),
50 ffs(b'1 command-data eos x'),
50 ])
51 ])
51
52
52 def testargsanddata(self):
53 def testargsanddata(self):
53 data = util.bytesio(b'x' * 100)
54 data = util.bytesio(b'x' * 100)
54
55
55 frames = list(framing.createcommandframes(b'command', {
56 frames = list(framing.createcommandframes(1, b'command', {
56 b'key1': b'key1value',
57 b'key1': b'key1value',
57 b'key2': b'key2value',
58 b'key2': b'key2value',
58 b'key3': b'key3value',
59 b'key3': b'key3value',
59 }, data))
60 }, data))
60
61
61 self.assertEqual(frames, [
62 self.assertEqual(frames, [
62 ffs(b'command-name have-args|have-data command'),
63 ffs(b'1 command-name have-args|have-data command'),
63 ffs(br'command-argument 0 \x04\x00\x09\x00key1key1value'),
64 ffs(br'1 command-argument 0 \x04\x00\x09\x00key1key1value'),
64 ffs(br'command-argument 0 \x04\x00\x09\x00key2key2value'),
65 ffs(br'1 command-argument 0 \x04\x00\x09\x00key2key2value'),
65 ffs(br'command-argument eoa \x04\x00\x09\x00key3key3value'),
66 ffs(br'1 command-argument eoa \x04\x00\x09\x00key3key3value'),
66 ffs(b'command-data eos %s' % data.getvalue()),
67 ffs(b'1 command-data eos %s' % data.getvalue()),
67 ])
68 ])
68
69
69 class ServerReactorTests(unittest.TestCase):
70 class ServerReactorTests(unittest.TestCase):
70 def _sendsingleframe(self, reactor, s):
71 def _sendsingleframe(self, reactor, s):
71 results = list(sendframes(reactor, [ffs(s)]))
72 results = list(sendframes(reactor, [ffs(s)]))
72 self.assertEqual(len(results), 1)
73 self.assertEqual(len(results), 1)
73
74
74 return results[0]
75 return results[0]
75
76
76 def assertaction(self, res, expected):
77 def assertaction(self, res, expected):
77 self.assertIsInstance(res, tuple)
78 self.assertIsInstance(res, tuple)
78 self.assertEqual(len(res), 2)
79 self.assertEqual(len(res), 2)
79 self.assertIsInstance(res[1], dict)
80 self.assertIsInstance(res[1], dict)
80 self.assertEqual(res[0], expected)
81 self.assertEqual(res[0], expected)
81
82
82 def assertframesequal(self, frames, framestrings):
83 def assertframesequal(self, frames, framestrings):
83 expected = [ffs(s) for s in framestrings]
84 expected = [ffs(s) for s in framestrings]
84 self.assertEqual(list(frames), expected)
85 self.assertEqual(list(frames), expected)
85
86
86 def test1framecommand(self):
87 def test1framecommand(self):
87 """Receiving a command in a single frame yields request to run it."""
88 """Receiving a command in a single frame yields request to run it."""
88 reactor = makereactor()
89 reactor = makereactor()
89 results = list(sendcommandframes(reactor, b'mycommand', {}))
90 results = list(sendcommandframes(reactor, 1, b'mycommand', {}))
90 self.assertEqual(len(results), 1)
91 self.assertEqual(len(results), 1)
91 self.assertaction(results[0], 'runcommand')
92 self.assertaction(results[0], 'runcommand')
92 self.assertEqual(results[0][1], {
93 self.assertEqual(results[0][1], {
94 'requestid': 1,
93 'command': b'mycommand',
95 'command': b'mycommand',
94 'args': {},
96 'args': {},
95 'data': None,
97 'data': None,
96 })
98 })
97
99
98 result = reactor.oninputeof()
100 result = reactor.oninputeof()
99 self.assertaction(result, 'noop')
101 self.assertaction(result, 'noop')
100
102
101 def test1argument(self):
103 def test1argument(self):
102 reactor = makereactor()
104 reactor = makereactor()
103 results = list(sendcommandframes(reactor, b'mycommand',
105 results = list(sendcommandframes(reactor, 41, b'mycommand',
104 {b'foo': b'bar'}))
106 {b'foo': b'bar'}))
105 self.assertEqual(len(results), 2)
107 self.assertEqual(len(results), 2)
106 self.assertaction(results[0], 'wantframe')
108 self.assertaction(results[0], 'wantframe')
107 self.assertaction(results[1], 'runcommand')
109 self.assertaction(results[1], 'runcommand')
108 self.assertEqual(results[1][1], {
110 self.assertEqual(results[1][1], {
111 'requestid': 41,
109 'command': b'mycommand',
112 'command': b'mycommand',
110 'args': {b'foo': b'bar'},
113 'args': {b'foo': b'bar'},
111 'data': None,
114 'data': None,
112 })
115 })
113
116
114 def testmultiarguments(self):
117 def testmultiarguments(self):
115 reactor = makereactor()
118 reactor = makereactor()
116 results = list(sendcommandframes(reactor, b'mycommand',
119 results = list(sendcommandframes(reactor, 1, b'mycommand',
117 {b'foo': b'bar', b'biz': b'baz'}))
120 {b'foo': b'bar', b'biz': b'baz'}))
118 self.assertEqual(len(results), 3)
121 self.assertEqual(len(results), 3)
119 self.assertaction(results[0], 'wantframe')
122 self.assertaction(results[0], 'wantframe')
120 self.assertaction(results[1], 'wantframe')
123 self.assertaction(results[1], 'wantframe')
121 self.assertaction(results[2], 'runcommand')
124 self.assertaction(results[2], 'runcommand')
122 self.assertEqual(results[2][1], {
125 self.assertEqual(results[2][1], {
126 'requestid': 1,
123 'command': b'mycommand',
127 'command': b'mycommand',
124 'args': {b'foo': b'bar', b'biz': b'baz'},
128 'args': {b'foo': b'bar', b'biz': b'baz'},
125 'data': None,
129 'data': None,
126 })
130 })
127
131
128 def testsimplecommanddata(self):
132 def testsimplecommanddata(self):
129 reactor = makereactor()
133 reactor = makereactor()
130 results = list(sendcommandframes(reactor, b'mycommand', {},
134 results = list(sendcommandframes(reactor, 1, b'mycommand', {},
131 util.bytesio(b'data!')))
135 util.bytesio(b'data!')))
132 self.assertEqual(len(results), 2)
136 self.assertEqual(len(results), 2)
133 self.assertaction(results[0], 'wantframe')
137 self.assertaction(results[0], 'wantframe')
134 self.assertaction(results[1], 'runcommand')
138 self.assertaction(results[1], 'runcommand')
135 self.assertEqual(results[1][1], {
139 self.assertEqual(results[1][1], {
140 'requestid': 1,
136 'command': b'mycommand',
141 'command': b'mycommand',
137 'args': {},
142 'args': {},
138 'data': b'data!',
143 'data': b'data!',
139 })
144 })
140
145
141 def testmultipledataframes(self):
146 def testmultipledataframes(self):
142 frames = [
147 frames = [
143 ffs(b'command-name have-data mycommand'),
148 ffs(b'1 command-name have-data mycommand'),
144 ffs(b'command-data continuation data1'),
149 ffs(b'1 command-data continuation data1'),
145 ffs(b'command-data continuation data2'),
150 ffs(b'1 command-data continuation data2'),
146 ffs(b'command-data eos data3'),
151 ffs(b'1 command-data eos data3'),
147 ]
152 ]
148
153
149 reactor = makereactor()
154 reactor = makereactor()
150 results = list(sendframes(reactor, frames))
155 results = list(sendframes(reactor, frames))
151 self.assertEqual(len(results), 4)
156 self.assertEqual(len(results), 4)
152 for i in range(3):
157 for i in range(3):
153 self.assertaction(results[i], 'wantframe')
158 self.assertaction(results[i], 'wantframe')
154 self.assertaction(results[3], 'runcommand')
159 self.assertaction(results[3], 'runcommand')
155 self.assertEqual(results[3][1], {
160 self.assertEqual(results[3][1], {
161 'requestid': 1,
156 'command': b'mycommand',
162 'command': b'mycommand',
157 'args': {},
163 'args': {},
158 'data': b'data1data2data3',
164 'data': b'data1data2data3',
159 })
165 })
160
166
161 def testargumentanddata(self):
167 def testargumentanddata(self):
162 frames = [
168 frames = [
163 ffs(b'command-name have-args|have-data command'),
169 ffs(b'1 command-name have-args|have-data command'),
164 ffs(br'command-argument 0 \x03\x00\x03\x00keyval'),
170 ffs(br'1 command-argument 0 \x03\x00\x03\x00keyval'),
165 ffs(br'command-argument eoa \x03\x00\x03\x00foobar'),
171 ffs(br'1 command-argument eoa \x03\x00\x03\x00foobar'),
166 ffs(b'command-data continuation value1'),
172 ffs(b'1 command-data continuation value1'),
167 ffs(b'command-data eos value2'),
173 ffs(b'1 command-data eos value2'),
168 ]
174 ]
169
175
170 reactor = makereactor()
176 reactor = makereactor()
171 results = list(sendframes(reactor, frames))
177 results = list(sendframes(reactor, frames))
172
178
173 self.assertaction(results[-1], 'runcommand')
179 self.assertaction(results[-1], 'runcommand')
174 self.assertEqual(results[-1][1], {
180 self.assertEqual(results[-1][1], {
181 'requestid': 1,
175 'command': b'command',
182 'command': b'command',
176 'args': {
183 'args': {
177 b'key': b'val',
184 b'key': b'val',
178 b'foo': b'bar',
185 b'foo': b'bar',
179 },
186 },
180 'data': b'value1value2',
187 'data': b'value1value2',
181 })
188 })
182
189
183 def testunexpectedcommandargument(self):
190 def testunexpectedcommandargument(self):
184 """Command argument frame when not running a command is an error."""
191 """Command argument frame when not running a command is an error."""
185 result = self._sendsingleframe(makereactor(),
192 result = self._sendsingleframe(makereactor(),
186 b'command-argument 0 ignored')
193 b'1 command-argument 0 ignored')
187 self.assertaction(result, 'error')
194 self.assertaction(result, 'error')
188 self.assertEqual(result[1], {
195 self.assertEqual(result[1], {
189 'message': b'expected command frame; got 2',
196 'message': b'expected command frame; got 2',
190 })
197 })
191
198
192 def testunexpectedcommanddata(self):
199 def testunexpectedcommanddata(self):
193 """Command argument frame when not running a command is an error."""
200 """Command argument frame when not running a command is an error."""
194 result = self._sendsingleframe(makereactor(),
201 result = self._sendsingleframe(makereactor(),
195 b'command-data 0 ignored')
202 b'1 command-data 0 ignored')
196 self.assertaction(result, 'error')
203 self.assertaction(result, 'error')
197 self.assertEqual(result[1], {
204 self.assertEqual(result[1], {
198 'message': b'expected command frame; got 3',
205 'message': b'expected command frame; got 3',
199 })
206 })
200
207
201 def testmissingcommandframeflags(self):
208 def testmissingcommandframeflags(self):
202 """Command name frame must have flags set."""
209 """Command name frame must have flags set."""
203 result = self._sendsingleframe(makereactor(),
210 result = self._sendsingleframe(makereactor(),
204 b'command-name 0 command')
211 b'1 command-name 0 command')
205 self.assertaction(result, 'error')
212 self.assertaction(result, 'error')
206 self.assertEqual(result[1], {
213 self.assertEqual(result[1], {
207 'message': b'missing frame flags on command frame',
214 'message': b'missing frame flags on command frame',
208 })
215 })
209
216
210 def testmissingargumentframe(self):
217 def testmissingargumentframe(self):
211 frames = [
218 frames = [
212 ffs(b'command-name have-args command'),
219 ffs(b'1 command-name have-args command'),
213 ffs(b'command-name 0 ignored'),
220 ffs(b'1 command-name 0 ignored'),
214 ]
221 ]
215
222
216 results = list(sendframes(makereactor(), frames))
223 results = list(sendframes(makereactor(), frames))
217 self.assertEqual(len(results), 2)
224 self.assertEqual(len(results), 2)
218 self.assertaction(results[0], 'wantframe')
225 self.assertaction(results[0], 'wantframe')
219 self.assertaction(results[1], 'error')
226 self.assertaction(results[1], 'error')
220 self.assertEqual(results[1][1], {
227 self.assertEqual(results[1][1], {
221 'message': b'expected command argument frame; got 1',
228 'message': b'expected command argument frame; got 1',
222 })
229 })
223
230
224 def testincompleteargumentname(self):
231 def testincompleteargumentname(self):
225 """Argument frame with incomplete name."""
232 """Argument frame with incomplete name."""
226 frames = [
233 frames = [
227 ffs(b'command-name have-args command1'),
234 ffs(b'1 command-name have-args command1'),
228 ffs(br'command-argument eoa \x04\x00\xde\xadfoo'),
235 ffs(br'1 command-argument eoa \x04\x00\xde\xadfoo'),
229 ]
236 ]
230
237
231 results = list(sendframes(makereactor(), frames))
238 results = list(sendframes(makereactor(), frames))
232 self.assertEqual(len(results), 2)
239 self.assertEqual(len(results), 2)
233 self.assertaction(results[0], 'wantframe')
240 self.assertaction(results[0], 'wantframe')
234 self.assertaction(results[1], 'error')
241 self.assertaction(results[1], 'error')
235 self.assertEqual(results[1][1], {
242 self.assertEqual(results[1][1], {
236 'message': b'malformed argument frame: partial argument name',
243 'message': b'malformed argument frame: partial argument name',
237 })
244 })
238
245
239 def testincompleteargumentvalue(self):
246 def testincompleteargumentvalue(self):
240 """Argument frame with incomplete value."""
247 """Argument frame with incomplete value."""
241 frames = [
248 frames = [
242 ffs(b'command-name have-args command'),
249 ffs(b'1 command-name have-args command'),
243 ffs(br'command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
250 ffs(br'1 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
244 ]
251 ]
245
252
246 results = list(sendframes(makereactor(), frames))
253 results = list(sendframes(makereactor(), frames))
247 self.assertEqual(len(results), 2)
254 self.assertEqual(len(results), 2)
248 self.assertaction(results[0], 'wantframe')
255 self.assertaction(results[0], 'wantframe')
249 self.assertaction(results[1], 'error')
256 self.assertaction(results[1], 'error')
250 self.assertEqual(results[1][1], {
257 self.assertEqual(results[1][1], {
251 'message': b'malformed argument frame: partial argument value',
258 'message': b'malformed argument frame: partial argument value',
252 })
259 })
253
260
254 def testmissingcommanddataframe(self):
261 def testmissingcommanddataframe(self):
255 frames = [
262 frames = [
256 ffs(b'command-name have-data command1'),
263 ffs(b'1 command-name have-data command1'),
257 ffs(b'command-name eos command2'),
264 ffs(b'1 command-name eos command2'),
258 ]
265 ]
259 results = list(sendframes(makereactor(), frames))
266 results = list(sendframes(makereactor(), frames))
260 self.assertEqual(len(results), 2)
267 self.assertEqual(len(results), 2)
261 self.assertaction(results[0], 'wantframe')
268 self.assertaction(results[0], 'wantframe')
262 self.assertaction(results[1], 'error')
269 self.assertaction(results[1], 'error')
263 self.assertEqual(results[1][1], {
270 self.assertEqual(results[1][1], {
264 'message': b'expected command data frame; got 1',
271 'message': b'expected command data frame; got 1',
265 })
272 })
266
273
267 def testmissingcommanddataframeflags(self):
274 def testmissingcommanddataframeflags(self):
268 frames = [
275 frames = [
269 ffs(b'command-name have-data command1'),
276 ffs(b'1 command-name have-data command1'),
270 ffs(b'command-data 0 data'),
277 ffs(b'1 command-data 0 data'),
271 ]
278 ]
272 results = list(sendframes(makereactor(), frames))
279 results = list(sendframes(makereactor(), frames))
273 self.assertEqual(len(results), 2)
280 self.assertEqual(len(results), 2)
274 self.assertaction(results[0], 'wantframe')
281 self.assertaction(results[0], 'wantframe')
275 self.assertaction(results[1], 'error')
282 self.assertaction(results[1], 'error')
276 self.assertEqual(results[1][1], {
283 self.assertEqual(results[1][1], {
277 'message': b'command data frame without flags',
284 'message': b'command data frame without flags',
278 })
285 })
279
286
280 def testsimpleresponse(self):
287 def testsimpleresponse(self):
281 """Bytes response to command sends result frames."""
288 """Bytes response to command sends result frames."""
282 reactor = makereactor()
289 reactor = makereactor()
283 list(sendcommandframes(reactor, b'mycommand', {}))
290 list(sendcommandframes(reactor, 1, b'mycommand', {}))
284
291
285 result = reactor.onbytesresponseready(b'response')
292 result = reactor.onbytesresponseready(1, b'response')
286 self.assertaction(result, 'sendframes')
293 self.assertaction(result, 'sendframes')
287 self.assertframesequal(result[1]['framegen'], [
294 self.assertframesequal(result[1]['framegen'], [
288 b'bytes-response eos response',
295 b'1 bytes-response eos response',
289 ])
296 ])
290
297
291 def testmultiframeresponse(self):
298 def testmultiframeresponse(self):
292 """Bytes response spanning multiple frames is handled."""
299 """Bytes response spanning multiple frames is handled."""
293 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
300 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
294 second = b'y' * 100
301 second = b'y' * 100
295
302
296 reactor = makereactor()
303 reactor = makereactor()
297 list(sendcommandframes(reactor, b'mycommand', {}))
304 list(sendcommandframes(reactor, 1, b'mycommand', {}))
298
305
299 result = reactor.onbytesresponseready(first + second)
306 result = reactor.onbytesresponseready(1, first + second)
300 self.assertaction(result, 'sendframes')
307 self.assertaction(result, 'sendframes')
301 self.assertframesequal(result[1]['framegen'], [
308 self.assertframesequal(result[1]['framegen'], [
302 b'bytes-response continuation %s' % first,
309 b'1 bytes-response continuation %s' % first,
303 b'bytes-response eos %s' % second,
310 b'1 bytes-response eos %s' % second,
304 ])
311 ])
305
312
306 def testapplicationerror(self):
313 def testapplicationerror(self):
307 reactor = makereactor()
314 reactor = makereactor()
308 list(sendcommandframes(reactor, b'mycommand', {}))
315 list(sendcommandframes(reactor, 1, b'mycommand', {}))
309
316
310 result = reactor.onapplicationerror(b'some message')
317 result = reactor.onapplicationerror(1, b'some message')
311 self.assertaction(result, 'sendframes')
318 self.assertaction(result, 'sendframes')
312 self.assertframesequal(result[1]['framegen'], [
319 self.assertframesequal(result[1]['framegen'], [
313 b'error-response application some message',
320 b'1 error-response application some message',
314 ])
321 ])
315
322
316 def test1commanddeferresponse(self):
323 def test1commanddeferresponse(self):
317 """Responses when in deferred output mode are delayed until EOF."""
324 """Responses when in deferred output mode are delayed until EOF."""
318 reactor = makereactor(deferoutput=True)
325 reactor = makereactor(deferoutput=True)
319 results = list(sendcommandframes(reactor, b'mycommand', {}))
326 results = list(sendcommandframes(reactor, 1, b'mycommand', {}))
320 self.assertEqual(len(results), 1)
327 self.assertEqual(len(results), 1)
321 self.assertaction(results[0], 'runcommand')
328 self.assertaction(results[0], 'runcommand')
322
329
323 result = reactor.onbytesresponseready(b'response')
330 result = reactor.onbytesresponseready(1, b'response')
324 self.assertaction(result, 'noop')
331 self.assertaction(result, 'noop')
325 result = reactor.oninputeof()
332 result = reactor.oninputeof()
326 self.assertaction(result, 'sendframes')
333 self.assertaction(result, 'sendframes')
327 self.assertframesequal(result[1]['framegen'], [
334 self.assertframesequal(result[1]['framegen'], [
328 b'bytes-response eos response',
335 b'1 bytes-response eos response',
329 ])
336 ])
330
337
331 def testmultiplecommanddeferresponse(self):
338 def testmultiplecommanddeferresponse(self):
332 reactor = makereactor(deferoutput=True)
339 reactor = makereactor(deferoutput=True)
333 list(sendcommandframes(reactor, b'command1', {}))
340 list(sendcommandframes(reactor, 1, b'command1', {}))
334 list(sendcommandframes(reactor, b'command2', {}))
341 list(sendcommandframes(reactor, 3, b'command2', {}))
335
342
336 result = reactor.onbytesresponseready(b'response1')
343 result = reactor.onbytesresponseready(1, b'response1')
337 self.assertaction(result, 'noop')
344 self.assertaction(result, 'noop')
338 result = reactor.onbytesresponseready(b'response2')
345 result = reactor.onbytesresponseready(3, b'response2')
339 self.assertaction(result, 'noop')
346 self.assertaction(result, 'noop')
340 result = reactor.oninputeof()
347 result = reactor.oninputeof()
341 self.assertaction(result, 'sendframes')
348 self.assertaction(result, 'sendframes')
342 self.assertframesequal(result[1]['framegen'], [
349 self.assertframesequal(result[1]['framegen'], [
343 b'bytes-response eos response1',
350 b'1 bytes-response eos response1',
344 b'bytes-response eos response2'
351 b'3 bytes-response eos response2'
352 ])
353
354 def testrequestidtracking(self):
355 reactor = makereactor(deferoutput=True)
356 list(sendcommandframes(reactor, 1, b'command1', {}))
357 list(sendcommandframes(reactor, 3, b'command2', {}))
358 list(sendcommandframes(reactor, 5, b'command3', {}))
359
360 # Register results for commands out of order.
361 reactor.onbytesresponseready(3, b'response3')
362 reactor.onbytesresponseready(1, b'response1')
363 reactor.onbytesresponseready(5, b'response5')
364
365 result = reactor.oninputeof()
366 self.assertaction(result, 'sendframes')
367 self.assertframesequal(result[1]['framegen'], [
368 b'3 bytes-response eos response3',
369 b'1 bytes-response eos response1',
370 b'5 bytes-response eos response5',
345 ])
371 ])
346
372
347 if __name__ == '__main__':
373 if __name__ == '__main__':
348 import silenttestrunner
374 import silenttestrunner
349 silenttestrunner.main(__name__)
375 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now