##// END OF EJS Templates
wireproto: add streams to frame-based protocol...
Gregory Szorc -
r37304:9bfcbe4f default
parent child Browse files
Show More
@@ -1,3075 +1,3076 b''
1 # debugcommands.py - command processing for debug* commands
1 # debugcommands.py - command processing for debug* commands
2 #
2 #
3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import codecs
10 import codecs
11 import collections
11 import collections
12 import difflib
12 import difflib
13 import errno
13 import errno
14 import operator
14 import operator
15 import os
15 import os
16 import random
16 import random
17 import re
17 import re
18 import socket
18 import socket
19 import ssl
19 import ssl
20 import stat
20 import stat
21 import string
21 import string
22 import subprocess
22 import subprocess
23 import sys
23 import sys
24 import tempfile
24 import tempfile
25 import time
25 import time
26
26
27 from .i18n import _
27 from .i18n import _
28 from .node import (
28 from .node import (
29 bin,
29 bin,
30 hex,
30 hex,
31 nullhex,
31 nullhex,
32 nullid,
32 nullid,
33 nullrev,
33 nullrev,
34 short,
34 short,
35 )
35 )
36 from . import (
36 from . import (
37 bundle2,
37 bundle2,
38 changegroup,
38 changegroup,
39 cmdutil,
39 cmdutil,
40 color,
40 color,
41 context,
41 context,
42 dagparser,
42 dagparser,
43 dagutil,
43 dagutil,
44 encoding,
44 encoding,
45 error,
45 error,
46 exchange,
46 exchange,
47 extensions,
47 extensions,
48 filemerge,
48 filemerge,
49 fileset,
49 fileset,
50 formatter,
50 formatter,
51 hg,
51 hg,
52 httppeer,
52 httppeer,
53 localrepo,
53 localrepo,
54 lock as lockmod,
54 lock as lockmod,
55 logcmdutil,
55 logcmdutil,
56 merge as mergemod,
56 merge as mergemod,
57 obsolete,
57 obsolete,
58 obsutil,
58 obsutil,
59 phases,
59 phases,
60 policy,
60 policy,
61 pvec,
61 pvec,
62 pycompat,
62 pycompat,
63 registrar,
63 registrar,
64 repair,
64 repair,
65 revlog,
65 revlog,
66 revset,
66 revset,
67 revsetlang,
67 revsetlang,
68 scmutil,
68 scmutil,
69 setdiscovery,
69 setdiscovery,
70 simplemerge,
70 simplemerge,
71 smartset,
71 smartset,
72 sshpeer,
72 sshpeer,
73 sslutil,
73 sslutil,
74 streamclone,
74 streamclone,
75 templater,
75 templater,
76 treediscovery,
76 treediscovery,
77 upgrade,
77 upgrade,
78 url as urlmod,
78 url as urlmod,
79 util,
79 util,
80 vfs as vfsmod,
80 vfs as vfsmod,
81 wireprotoframing,
81 wireprotoframing,
82 wireprotoserver,
82 wireprotoserver,
83 )
83 )
84 from .utils import (
84 from .utils import (
85 dateutil,
85 dateutil,
86 procutil,
86 procutil,
87 stringutil,
87 stringutil,
88 )
88 )
89
89
90 release = lockmod.release
90 release = lockmod.release
91
91
92 command = registrar.command()
92 command = registrar.command()
93
93
94 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
94 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
95 def debugancestor(ui, repo, *args):
95 def debugancestor(ui, repo, *args):
96 """find the ancestor revision of two revisions in a given index"""
96 """find the ancestor revision of two revisions in a given index"""
97 if len(args) == 3:
97 if len(args) == 3:
98 index, rev1, rev2 = args
98 index, rev1, rev2 = args
99 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
99 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
100 lookup = r.lookup
100 lookup = r.lookup
101 elif len(args) == 2:
101 elif len(args) == 2:
102 if not repo:
102 if not repo:
103 raise error.Abort(_('there is no Mercurial repository here '
103 raise error.Abort(_('there is no Mercurial repository here '
104 '(.hg not found)'))
104 '(.hg not found)'))
105 rev1, rev2 = args
105 rev1, rev2 = args
106 r = repo.changelog
106 r = repo.changelog
107 lookup = repo.lookup
107 lookup = repo.lookup
108 else:
108 else:
109 raise error.Abort(_('either two or three arguments required'))
109 raise error.Abort(_('either two or three arguments required'))
110 a = r.ancestor(lookup(rev1), lookup(rev2))
110 a = r.ancestor(lookup(rev1), lookup(rev2))
111 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
111 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
112
112
113 @command('debugapplystreamclonebundle', [], 'FILE')
113 @command('debugapplystreamclonebundle', [], 'FILE')
114 def debugapplystreamclonebundle(ui, repo, fname):
114 def debugapplystreamclonebundle(ui, repo, fname):
115 """apply a stream clone bundle file"""
115 """apply a stream clone bundle file"""
116 f = hg.openpath(ui, fname)
116 f = hg.openpath(ui, fname)
117 gen = exchange.readbundle(ui, f, fname)
117 gen = exchange.readbundle(ui, f, fname)
118 gen.apply(repo)
118 gen.apply(repo)
119
119
120 @command('debugbuilddag',
120 @command('debugbuilddag',
121 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
121 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
122 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
122 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
123 ('n', 'new-file', None, _('add new file at each rev'))],
123 ('n', 'new-file', None, _('add new file at each rev'))],
124 _('[OPTION]... [TEXT]'))
124 _('[OPTION]... [TEXT]'))
125 def debugbuilddag(ui, repo, text=None,
125 def debugbuilddag(ui, repo, text=None,
126 mergeable_file=False,
126 mergeable_file=False,
127 overwritten_file=False,
127 overwritten_file=False,
128 new_file=False):
128 new_file=False):
129 """builds a repo with a given DAG from scratch in the current empty repo
129 """builds a repo with a given DAG from scratch in the current empty repo
130
130
131 The description of the DAG is read from stdin if not given on the
131 The description of the DAG is read from stdin if not given on the
132 command line.
132 command line.
133
133
134 Elements:
134 Elements:
135
135
136 - "+n" is a linear run of n nodes based on the current default parent
136 - "+n" is a linear run of n nodes based on the current default parent
137 - "." is a single node based on the current default parent
137 - "." is a single node based on the current default parent
138 - "$" resets the default parent to null (implied at the start);
138 - "$" resets the default parent to null (implied at the start);
139 otherwise the default parent is always the last node created
139 otherwise the default parent is always the last node created
140 - "<p" sets the default parent to the backref p
140 - "<p" sets the default parent to the backref p
141 - "*p" is a fork at parent p, which is a backref
141 - "*p" is a fork at parent p, which is a backref
142 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
142 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
143 - "/p2" is a merge of the preceding node and p2
143 - "/p2" is a merge of the preceding node and p2
144 - ":tag" defines a local tag for the preceding node
144 - ":tag" defines a local tag for the preceding node
145 - "@branch" sets the named branch for subsequent nodes
145 - "@branch" sets the named branch for subsequent nodes
146 - "#...\\n" is a comment up to the end of the line
146 - "#...\\n" is a comment up to the end of the line
147
147
148 Whitespace between the above elements is ignored.
148 Whitespace between the above elements is ignored.
149
149
150 A backref is either
150 A backref is either
151
151
152 - a number n, which references the node curr-n, where curr is the current
152 - a number n, which references the node curr-n, where curr is the current
153 node, or
153 node, or
154 - the name of a local tag you placed earlier using ":tag", or
154 - the name of a local tag you placed earlier using ":tag", or
155 - empty to denote the default parent.
155 - empty to denote the default parent.
156
156
157 All string valued-elements are either strictly alphanumeric, or must
157 All string valued-elements are either strictly alphanumeric, or must
158 be enclosed in double quotes ("..."), with "\\" as escape character.
158 be enclosed in double quotes ("..."), with "\\" as escape character.
159 """
159 """
160
160
161 if text is None:
161 if text is None:
162 ui.status(_("reading DAG from stdin\n"))
162 ui.status(_("reading DAG from stdin\n"))
163 text = ui.fin.read()
163 text = ui.fin.read()
164
164
165 cl = repo.changelog
165 cl = repo.changelog
166 if len(cl) > 0:
166 if len(cl) > 0:
167 raise error.Abort(_('repository is not empty'))
167 raise error.Abort(_('repository is not empty'))
168
168
169 # determine number of revs in DAG
169 # determine number of revs in DAG
170 total = 0
170 total = 0
171 for type, data in dagparser.parsedag(text):
171 for type, data in dagparser.parsedag(text):
172 if type == 'n':
172 if type == 'n':
173 total += 1
173 total += 1
174
174
175 if mergeable_file:
175 if mergeable_file:
176 linesperrev = 2
176 linesperrev = 2
177 # make a file with k lines per rev
177 # make a file with k lines per rev
178 initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)]
178 initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)]
179 initialmergedlines.append("")
179 initialmergedlines.append("")
180
180
181 tags = []
181 tags = []
182
182
183 wlock = lock = tr = None
183 wlock = lock = tr = None
184 try:
184 try:
185 wlock = repo.wlock()
185 wlock = repo.wlock()
186 lock = repo.lock()
186 lock = repo.lock()
187 tr = repo.transaction("builddag")
187 tr = repo.transaction("builddag")
188
188
189 at = -1
189 at = -1
190 atbranch = 'default'
190 atbranch = 'default'
191 nodeids = []
191 nodeids = []
192 id = 0
192 id = 0
193 ui.progress(_('building'), id, unit=_('revisions'), total=total)
193 ui.progress(_('building'), id, unit=_('revisions'), total=total)
194 for type, data in dagparser.parsedag(text):
194 for type, data in dagparser.parsedag(text):
195 if type == 'n':
195 if type == 'n':
196 ui.note(('node %s\n' % pycompat.bytestr(data)))
196 ui.note(('node %s\n' % pycompat.bytestr(data)))
197 id, ps = data
197 id, ps = data
198
198
199 files = []
199 files = []
200 filecontent = {}
200 filecontent = {}
201
201
202 p2 = None
202 p2 = None
203 if mergeable_file:
203 if mergeable_file:
204 fn = "mf"
204 fn = "mf"
205 p1 = repo[ps[0]]
205 p1 = repo[ps[0]]
206 if len(ps) > 1:
206 if len(ps) > 1:
207 p2 = repo[ps[1]]
207 p2 = repo[ps[1]]
208 pa = p1.ancestor(p2)
208 pa = p1.ancestor(p2)
209 base, local, other = [x[fn].data() for x in (pa, p1,
209 base, local, other = [x[fn].data() for x in (pa, p1,
210 p2)]
210 p2)]
211 m3 = simplemerge.Merge3Text(base, local, other)
211 m3 = simplemerge.Merge3Text(base, local, other)
212 ml = [l.strip() for l in m3.merge_lines()]
212 ml = [l.strip() for l in m3.merge_lines()]
213 ml.append("")
213 ml.append("")
214 elif at > 0:
214 elif at > 0:
215 ml = p1[fn].data().split("\n")
215 ml = p1[fn].data().split("\n")
216 else:
216 else:
217 ml = initialmergedlines
217 ml = initialmergedlines
218 ml[id * linesperrev] += " r%i" % id
218 ml[id * linesperrev] += " r%i" % id
219 mergedtext = "\n".join(ml)
219 mergedtext = "\n".join(ml)
220 files.append(fn)
220 files.append(fn)
221 filecontent[fn] = mergedtext
221 filecontent[fn] = mergedtext
222
222
223 if overwritten_file:
223 if overwritten_file:
224 fn = "of"
224 fn = "of"
225 files.append(fn)
225 files.append(fn)
226 filecontent[fn] = "r%i\n" % id
226 filecontent[fn] = "r%i\n" % id
227
227
228 if new_file:
228 if new_file:
229 fn = "nf%i" % id
229 fn = "nf%i" % id
230 files.append(fn)
230 files.append(fn)
231 filecontent[fn] = "r%i\n" % id
231 filecontent[fn] = "r%i\n" % id
232 if len(ps) > 1:
232 if len(ps) > 1:
233 if not p2:
233 if not p2:
234 p2 = repo[ps[1]]
234 p2 = repo[ps[1]]
235 for fn in p2:
235 for fn in p2:
236 if fn.startswith("nf"):
236 if fn.startswith("nf"):
237 files.append(fn)
237 files.append(fn)
238 filecontent[fn] = p2[fn].data()
238 filecontent[fn] = p2[fn].data()
239
239
240 def fctxfn(repo, cx, path):
240 def fctxfn(repo, cx, path):
241 if path in filecontent:
241 if path in filecontent:
242 return context.memfilectx(repo, cx, path,
242 return context.memfilectx(repo, cx, path,
243 filecontent[path])
243 filecontent[path])
244 return None
244 return None
245
245
246 if len(ps) == 0 or ps[0] < 0:
246 if len(ps) == 0 or ps[0] < 0:
247 pars = [None, None]
247 pars = [None, None]
248 elif len(ps) == 1:
248 elif len(ps) == 1:
249 pars = [nodeids[ps[0]], None]
249 pars = [nodeids[ps[0]], None]
250 else:
250 else:
251 pars = [nodeids[p] for p in ps]
251 pars = [nodeids[p] for p in ps]
252 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
252 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
253 date=(id, 0),
253 date=(id, 0),
254 user="debugbuilddag",
254 user="debugbuilddag",
255 extra={'branch': atbranch})
255 extra={'branch': atbranch})
256 nodeid = repo.commitctx(cx)
256 nodeid = repo.commitctx(cx)
257 nodeids.append(nodeid)
257 nodeids.append(nodeid)
258 at = id
258 at = id
259 elif type == 'l':
259 elif type == 'l':
260 id, name = data
260 id, name = data
261 ui.note(('tag %s\n' % name))
261 ui.note(('tag %s\n' % name))
262 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
262 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
263 elif type == 'a':
263 elif type == 'a':
264 ui.note(('branch %s\n' % data))
264 ui.note(('branch %s\n' % data))
265 atbranch = data
265 atbranch = data
266 ui.progress(_('building'), id, unit=_('revisions'), total=total)
266 ui.progress(_('building'), id, unit=_('revisions'), total=total)
267 tr.close()
267 tr.close()
268
268
269 if tags:
269 if tags:
270 repo.vfs.write("localtags", "".join(tags))
270 repo.vfs.write("localtags", "".join(tags))
271 finally:
271 finally:
272 ui.progress(_('building'), None)
272 ui.progress(_('building'), None)
273 release(tr, lock, wlock)
273 release(tr, lock, wlock)
274
274
275 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
275 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
276 indent_string = ' ' * indent
276 indent_string = ' ' * indent
277 if all:
277 if all:
278 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
278 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
279 % indent_string)
279 % indent_string)
280
280
281 def showchunks(named):
281 def showchunks(named):
282 ui.write("\n%s%s\n" % (indent_string, named))
282 ui.write("\n%s%s\n" % (indent_string, named))
283 for deltadata in gen.deltaiter():
283 for deltadata in gen.deltaiter():
284 node, p1, p2, cs, deltabase, delta, flags = deltadata
284 node, p1, p2, cs, deltabase, delta, flags = deltadata
285 ui.write("%s%s %s %s %s %s %d\n" %
285 ui.write("%s%s %s %s %s %s %d\n" %
286 (indent_string, hex(node), hex(p1), hex(p2),
286 (indent_string, hex(node), hex(p1), hex(p2),
287 hex(cs), hex(deltabase), len(delta)))
287 hex(cs), hex(deltabase), len(delta)))
288
288
289 chunkdata = gen.changelogheader()
289 chunkdata = gen.changelogheader()
290 showchunks("changelog")
290 showchunks("changelog")
291 chunkdata = gen.manifestheader()
291 chunkdata = gen.manifestheader()
292 showchunks("manifest")
292 showchunks("manifest")
293 for chunkdata in iter(gen.filelogheader, {}):
293 for chunkdata in iter(gen.filelogheader, {}):
294 fname = chunkdata['filename']
294 fname = chunkdata['filename']
295 showchunks(fname)
295 showchunks(fname)
296 else:
296 else:
297 if isinstance(gen, bundle2.unbundle20):
297 if isinstance(gen, bundle2.unbundle20):
298 raise error.Abort(_('use debugbundle2 for this file'))
298 raise error.Abort(_('use debugbundle2 for this file'))
299 chunkdata = gen.changelogheader()
299 chunkdata = gen.changelogheader()
300 for deltadata in gen.deltaiter():
300 for deltadata in gen.deltaiter():
301 node, p1, p2, cs, deltabase, delta, flags = deltadata
301 node, p1, p2, cs, deltabase, delta, flags = deltadata
302 ui.write("%s%s\n" % (indent_string, hex(node)))
302 ui.write("%s%s\n" % (indent_string, hex(node)))
303
303
304 def _debugobsmarkers(ui, part, indent=0, **opts):
304 def _debugobsmarkers(ui, part, indent=0, **opts):
305 """display version and markers contained in 'data'"""
305 """display version and markers contained in 'data'"""
306 opts = pycompat.byteskwargs(opts)
306 opts = pycompat.byteskwargs(opts)
307 data = part.read()
307 data = part.read()
308 indent_string = ' ' * indent
308 indent_string = ' ' * indent
309 try:
309 try:
310 version, markers = obsolete._readmarkers(data)
310 version, markers = obsolete._readmarkers(data)
311 except error.UnknownVersion as exc:
311 except error.UnknownVersion as exc:
312 msg = "%sunsupported version: %s (%d bytes)\n"
312 msg = "%sunsupported version: %s (%d bytes)\n"
313 msg %= indent_string, exc.version, len(data)
313 msg %= indent_string, exc.version, len(data)
314 ui.write(msg)
314 ui.write(msg)
315 else:
315 else:
316 msg = "%sversion: %d (%d bytes)\n"
316 msg = "%sversion: %d (%d bytes)\n"
317 msg %= indent_string, version, len(data)
317 msg %= indent_string, version, len(data)
318 ui.write(msg)
318 ui.write(msg)
319 fm = ui.formatter('debugobsolete', opts)
319 fm = ui.formatter('debugobsolete', opts)
320 for rawmarker in sorted(markers):
320 for rawmarker in sorted(markers):
321 m = obsutil.marker(None, rawmarker)
321 m = obsutil.marker(None, rawmarker)
322 fm.startitem()
322 fm.startitem()
323 fm.plain(indent_string)
323 fm.plain(indent_string)
324 cmdutil.showmarker(fm, m)
324 cmdutil.showmarker(fm, m)
325 fm.end()
325 fm.end()
326
326
327 def _debugphaseheads(ui, data, indent=0):
327 def _debugphaseheads(ui, data, indent=0):
328 """display version and markers contained in 'data'"""
328 """display version and markers contained in 'data'"""
329 indent_string = ' ' * indent
329 indent_string = ' ' * indent
330 headsbyphase = phases.binarydecode(data)
330 headsbyphase = phases.binarydecode(data)
331 for phase in phases.allphases:
331 for phase in phases.allphases:
332 for head in headsbyphase[phase]:
332 for head in headsbyphase[phase]:
333 ui.write(indent_string)
333 ui.write(indent_string)
334 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
334 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
335
335
336 def _quasirepr(thing):
336 def _quasirepr(thing):
337 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
337 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
338 return '{%s}' % (
338 return '{%s}' % (
339 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
339 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
340 return pycompat.bytestr(repr(thing))
340 return pycompat.bytestr(repr(thing))
341
341
342 def _debugbundle2(ui, gen, all=None, **opts):
342 def _debugbundle2(ui, gen, all=None, **opts):
343 """lists the contents of a bundle2"""
343 """lists the contents of a bundle2"""
344 if not isinstance(gen, bundle2.unbundle20):
344 if not isinstance(gen, bundle2.unbundle20):
345 raise error.Abort(_('not a bundle2 file'))
345 raise error.Abort(_('not a bundle2 file'))
346 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
346 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
347 parttypes = opts.get(r'part_type', [])
347 parttypes = opts.get(r'part_type', [])
348 for part in gen.iterparts():
348 for part in gen.iterparts():
349 if parttypes and part.type not in parttypes:
349 if parttypes and part.type not in parttypes:
350 continue
350 continue
351 ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
351 ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
352 if part.type == 'changegroup':
352 if part.type == 'changegroup':
353 version = part.params.get('version', '01')
353 version = part.params.get('version', '01')
354 cg = changegroup.getunbundler(version, part, 'UN')
354 cg = changegroup.getunbundler(version, part, 'UN')
355 if not ui.quiet:
355 if not ui.quiet:
356 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
356 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
357 if part.type == 'obsmarkers':
357 if part.type == 'obsmarkers':
358 if not ui.quiet:
358 if not ui.quiet:
359 _debugobsmarkers(ui, part, indent=4, **opts)
359 _debugobsmarkers(ui, part, indent=4, **opts)
360 if part.type == 'phase-heads':
360 if part.type == 'phase-heads':
361 if not ui.quiet:
361 if not ui.quiet:
362 _debugphaseheads(ui, part, indent=4)
362 _debugphaseheads(ui, part, indent=4)
363
363
364 @command('debugbundle',
364 @command('debugbundle',
365 [('a', 'all', None, _('show all details')),
365 [('a', 'all', None, _('show all details')),
366 ('', 'part-type', [], _('show only the named part type')),
366 ('', 'part-type', [], _('show only the named part type')),
367 ('', 'spec', None, _('print the bundlespec of the bundle'))],
367 ('', 'spec', None, _('print the bundlespec of the bundle'))],
368 _('FILE'),
368 _('FILE'),
369 norepo=True)
369 norepo=True)
370 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
370 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
371 """lists the contents of a bundle"""
371 """lists the contents of a bundle"""
372 with hg.openpath(ui, bundlepath) as f:
372 with hg.openpath(ui, bundlepath) as f:
373 if spec:
373 if spec:
374 spec = exchange.getbundlespec(ui, f)
374 spec = exchange.getbundlespec(ui, f)
375 ui.write('%s\n' % spec)
375 ui.write('%s\n' % spec)
376 return
376 return
377
377
378 gen = exchange.readbundle(ui, f, bundlepath)
378 gen = exchange.readbundle(ui, f, bundlepath)
379 if isinstance(gen, bundle2.unbundle20):
379 if isinstance(gen, bundle2.unbundle20):
380 return _debugbundle2(ui, gen, all=all, **opts)
380 return _debugbundle2(ui, gen, all=all, **opts)
381 _debugchangegroup(ui, gen, all=all, **opts)
381 _debugchangegroup(ui, gen, all=all, **opts)
382
382
383 @command('debugcapabilities',
383 @command('debugcapabilities',
384 [], _('PATH'),
384 [], _('PATH'),
385 norepo=True)
385 norepo=True)
386 def debugcapabilities(ui, path, **opts):
386 def debugcapabilities(ui, path, **opts):
387 """lists the capabilities of a remote peer"""
387 """lists the capabilities of a remote peer"""
388 opts = pycompat.byteskwargs(opts)
388 opts = pycompat.byteskwargs(opts)
389 peer = hg.peer(ui, opts, path)
389 peer = hg.peer(ui, opts, path)
390 caps = peer.capabilities()
390 caps = peer.capabilities()
391 ui.write(('Main capabilities:\n'))
391 ui.write(('Main capabilities:\n'))
392 for c in sorted(caps):
392 for c in sorted(caps):
393 ui.write((' %s\n') % c)
393 ui.write((' %s\n') % c)
394 b2caps = bundle2.bundle2caps(peer)
394 b2caps = bundle2.bundle2caps(peer)
395 if b2caps:
395 if b2caps:
396 ui.write(('Bundle2 capabilities:\n'))
396 ui.write(('Bundle2 capabilities:\n'))
397 for key, values in sorted(b2caps.iteritems()):
397 for key, values in sorted(b2caps.iteritems()):
398 ui.write((' %s\n') % key)
398 ui.write((' %s\n') % key)
399 for v in values:
399 for v in values:
400 ui.write((' %s\n') % v)
400 ui.write((' %s\n') % v)
401
401
402 @command('debugcheckstate', [], '')
402 @command('debugcheckstate', [], '')
403 def debugcheckstate(ui, repo):
403 def debugcheckstate(ui, repo):
404 """validate the correctness of the current dirstate"""
404 """validate the correctness of the current dirstate"""
405 parent1, parent2 = repo.dirstate.parents()
405 parent1, parent2 = repo.dirstate.parents()
406 m1 = repo[parent1].manifest()
406 m1 = repo[parent1].manifest()
407 m2 = repo[parent2].manifest()
407 m2 = repo[parent2].manifest()
408 errors = 0
408 errors = 0
409 for f in repo.dirstate:
409 for f in repo.dirstate:
410 state = repo.dirstate[f]
410 state = repo.dirstate[f]
411 if state in "nr" and f not in m1:
411 if state in "nr" and f not in m1:
412 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
412 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
413 errors += 1
413 errors += 1
414 if state in "a" and f in m1:
414 if state in "a" and f in m1:
415 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
415 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
416 errors += 1
416 errors += 1
417 if state in "m" and f not in m1 and f not in m2:
417 if state in "m" and f not in m1 and f not in m2:
418 ui.warn(_("%s in state %s, but not in either manifest\n") %
418 ui.warn(_("%s in state %s, but not in either manifest\n") %
419 (f, state))
419 (f, state))
420 errors += 1
420 errors += 1
421 for f in m1:
421 for f in m1:
422 state = repo.dirstate[f]
422 state = repo.dirstate[f]
423 if state not in "nrm":
423 if state not in "nrm":
424 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
424 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
425 errors += 1
425 errors += 1
426 if errors:
426 if errors:
427 error = _(".hg/dirstate inconsistent with current parent's manifest")
427 error = _(".hg/dirstate inconsistent with current parent's manifest")
428 raise error.Abort(error)
428 raise error.Abort(error)
429
429
430 @command('debugcolor',
430 @command('debugcolor',
431 [('', 'style', None, _('show all configured styles'))],
431 [('', 'style', None, _('show all configured styles'))],
432 'hg debugcolor')
432 'hg debugcolor')
433 def debugcolor(ui, repo, **opts):
433 def debugcolor(ui, repo, **opts):
434 """show available color, effects or style"""
434 """show available color, effects or style"""
435 ui.write(('color mode: %s\n') % ui._colormode)
435 ui.write(('color mode: %s\n') % ui._colormode)
436 if opts.get(r'style'):
436 if opts.get(r'style'):
437 return _debugdisplaystyle(ui)
437 return _debugdisplaystyle(ui)
438 else:
438 else:
439 return _debugdisplaycolor(ui)
439 return _debugdisplaycolor(ui)
440
440
441 def _debugdisplaycolor(ui):
441 def _debugdisplaycolor(ui):
442 ui = ui.copy()
442 ui = ui.copy()
443 ui._styles.clear()
443 ui._styles.clear()
444 for effect in color._activeeffects(ui).keys():
444 for effect in color._activeeffects(ui).keys():
445 ui._styles[effect] = effect
445 ui._styles[effect] = effect
446 if ui._terminfoparams:
446 if ui._terminfoparams:
447 for k, v in ui.configitems('color'):
447 for k, v in ui.configitems('color'):
448 if k.startswith('color.'):
448 if k.startswith('color.'):
449 ui._styles[k] = k[6:]
449 ui._styles[k] = k[6:]
450 elif k.startswith('terminfo.'):
450 elif k.startswith('terminfo.'):
451 ui._styles[k] = k[9:]
451 ui._styles[k] = k[9:]
452 ui.write(_('available colors:\n'))
452 ui.write(_('available colors:\n'))
453 # sort label with a '_' after the other to group '_background' entry.
453 # sort label with a '_' after the other to group '_background' entry.
454 items = sorted(ui._styles.items(),
454 items = sorted(ui._styles.items(),
455 key=lambda i: ('_' in i[0], i[0], i[1]))
455 key=lambda i: ('_' in i[0], i[0], i[1]))
456 for colorname, label in items:
456 for colorname, label in items:
457 ui.write(('%s\n') % colorname, label=label)
457 ui.write(('%s\n') % colorname, label=label)
458
458
459 def _debugdisplaystyle(ui):
459 def _debugdisplaystyle(ui):
460 ui.write(_('available style:\n'))
460 ui.write(_('available style:\n'))
461 width = max(len(s) for s in ui._styles)
461 width = max(len(s) for s in ui._styles)
462 for label, effects in sorted(ui._styles.items()):
462 for label, effects in sorted(ui._styles.items()):
463 ui.write('%s' % label, label=label)
463 ui.write('%s' % label, label=label)
464 if effects:
464 if effects:
465 # 50
465 # 50
466 ui.write(': ')
466 ui.write(': ')
467 ui.write(' ' * (max(0, width - len(label))))
467 ui.write(' ' * (max(0, width - len(label))))
468 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
468 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
469 ui.write('\n')
469 ui.write('\n')
470
470
471 @command('debugcreatestreamclonebundle', [], 'FILE')
471 @command('debugcreatestreamclonebundle', [], 'FILE')
472 def debugcreatestreamclonebundle(ui, repo, fname):
472 def debugcreatestreamclonebundle(ui, repo, fname):
473 """create a stream clone bundle file
473 """create a stream clone bundle file
474
474
475 Stream bundles are special bundles that are essentially archives of
475 Stream bundles are special bundles that are essentially archives of
476 revlog files. They are commonly used for cloning very quickly.
476 revlog files. They are commonly used for cloning very quickly.
477 """
477 """
478 # TODO we may want to turn this into an abort when this functionality
478 # TODO we may want to turn this into an abort when this functionality
479 # is moved into `hg bundle`.
479 # is moved into `hg bundle`.
480 if phases.hassecret(repo):
480 if phases.hassecret(repo):
481 ui.warn(_('(warning: stream clone bundle will contain secret '
481 ui.warn(_('(warning: stream clone bundle will contain secret '
482 'revisions)\n'))
482 'revisions)\n'))
483
483
484 requirements, gen = streamclone.generatebundlev1(repo)
484 requirements, gen = streamclone.generatebundlev1(repo)
485 changegroup.writechunks(ui, gen, fname)
485 changegroup.writechunks(ui, gen, fname)
486
486
487 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
487 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
488
488
489 @command('debugdag',
489 @command('debugdag',
490 [('t', 'tags', None, _('use tags as labels')),
490 [('t', 'tags', None, _('use tags as labels')),
491 ('b', 'branches', None, _('annotate with branch names')),
491 ('b', 'branches', None, _('annotate with branch names')),
492 ('', 'dots', None, _('use dots for runs')),
492 ('', 'dots', None, _('use dots for runs')),
493 ('s', 'spaces', None, _('separate elements by spaces'))],
493 ('s', 'spaces', None, _('separate elements by spaces'))],
494 _('[OPTION]... [FILE [REV]...]'),
494 _('[OPTION]... [FILE [REV]...]'),
495 optionalrepo=True)
495 optionalrepo=True)
496 def debugdag(ui, repo, file_=None, *revs, **opts):
496 def debugdag(ui, repo, file_=None, *revs, **opts):
497 """format the changelog or an index DAG as a concise textual description
497 """format the changelog or an index DAG as a concise textual description
498
498
499 If you pass a revlog index, the revlog's DAG is emitted. If you list
499 If you pass a revlog index, the revlog's DAG is emitted. If you list
500 revision numbers, they get labeled in the output as rN.
500 revision numbers, they get labeled in the output as rN.
501
501
502 Otherwise, the changelog DAG of the current repo is emitted.
502 Otherwise, the changelog DAG of the current repo is emitted.
503 """
503 """
504 spaces = opts.get(r'spaces')
504 spaces = opts.get(r'spaces')
505 dots = opts.get(r'dots')
505 dots = opts.get(r'dots')
506 if file_:
506 if file_:
507 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
507 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
508 file_)
508 file_)
509 revs = set((int(r) for r in revs))
509 revs = set((int(r) for r in revs))
510 def events():
510 def events():
511 for r in rlog:
511 for r in rlog:
512 yield 'n', (r, list(p for p in rlog.parentrevs(r)
512 yield 'n', (r, list(p for p in rlog.parentrevs(r)
513 if p != -1))
513 if p != -1))
514 if r in revs:
514 if r in revs:
515 yield 'l', (r, "r%i" % r)
515 yield 'l', (r, "r%i" % r)
516 elif repo:
516 elif repo:
517 cl = repo.changelog
517 cl = repo.changelog
518 tags = opts.get(r'tags')
518 tags = opts.get(r'tags')
519 branches = opts.get(r'branches')
519 branches = opts.get(r'branches')
520 if tags:
520 if tags:
521 labels = {}
521 labels = {}
522 for l, n in repo.tags().items():
522 for l, n in repo.tags().items():
523 labels.setdefault(cl.rev(n), []).append(l)
523 labels.setdefault(cl.rev(n), []).append(l)
524 def events():
524 def events():
525 b = "default"
525 b = "default"
526 for r in cl:
526 for r in cl:
527 if branches:
527 if branches:
528 newb = cl.read(cl.node(r))[5]['branch']
528 newb = cl.read(cl.node(r))[5]['branch']
529 if newb != b:
529 if newb != b:
530 yield 'a', newb
530 yield 'a', newb
531 b = newb
531 b = newb
532 yield 'n', (r, list(p for p in cl.parentrevs(r)
532 yield 'n', (r, list(p for p in cl.parentrevs(r)
533 if p != -1))
533 if p != -1))
534 if tags:
534 if tags:
535 ls = labels.get(r)
535 ls = labels.get(r)
536 if ls:
536 if ls:
537 for l in ls:
537 for l in ls:
538 yield 'l', (r, l)
538 yield 'l', (r, l)
539 else:
539 else:
540 raise error.Abort(_('need repo for changelog dag'))
540 raise error.Abort(_('need repo for changelog dag'))
541
541
542 for line in dagparser.dagtextlines(events(),
542 for line in dagparser.dagtextlines(events(),
543 addspaces=spaces,
543 addspaces=spaces,
544 wraplabels=True,
544 wraplabels=True,
545 wrapannotations=True,
545 wrapannotations=True,
546 wrapnonlinear=dots,
546 wrapnonlinear=dots,
547 usedots=dots,
547 usedots=dots,
548 maxlinewidth=70):
548 maxlinewidth=70):
549 ui.write(line)
549 ui.write(line)
550 ui.write("\n")
550 ui.write("\n")
551
551
552 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
552 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
553 def debugdata(ui, repo, file_, rev=None, **opts):
553 def debugdata(ui, repo, file_, rev=None, **opts):
554 """dump the contents of a data file revision"""
554 """dump the contents of a data file revision"""
555 opts = pycompat.byteskwargs(opts)
555 opts = pycompat.byteskwargs(opts)
556 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
556 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
557 if rev is not None:
557 if rev is not None:
558 raise error.CommandError('debugdata', _('invalid arguments'))
558 raise error.CommandError('debugdata', _('invalid arguments'))
559 file_, rev = None, file_
559 file_, rev = None, file_
560 elif rev is None:
560 elif rev is None:
561 raise error.CommandError('debugdata', _('invalid arguments'))
561 raise error.CommandError('debugdata', _('invalid arguments'))
562 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
562 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
563 try:
563 try:
564 ui.write(r.revision(r.lookup(rev), raw=True))
564 ui.write(r.revision(r.lookup(rev), raw=True))
565 except KeyError:
565 except KeyError:
566 raise error.Abort(_('invalid revision identifier %s') % rev)
566 raise error.Abort(_('invalid revision identifier %s') % rev)
567
567
568 @command('debugdate',
568 @command('debugdate',
569 [('e', 'extended', None, _('try extended date formats'))],
569 [('e', 'extended', None, _('try extended date formats'))],
570 _('[-e] DATE [RANGE]'),
570 _('[-e] DATE [RANGE]'),
571 norepo=True, optionalrepo=True)
571 norepo=True, optionalrepo=True)
572 def debugdate(ui, date, range=None, **opts):
572 def debugdate(ui, date, range=None, **opts):
573 """parse and display a date"""
573 """parse and display a date"""
574 if opts[r"extended"]:
574 if opts[r"extended"]:
575 d = dateutil.parsedate(date, util.extendeddateformats)
575 d = dateutil.parsedate(date, util.extendeddateformats)
576 else:
576 else:
577 d = dateutil.parsedate(date)
577 d = dateutil.parsedate(date)
578 ui.write(("internal: %d %d\n") % d)
578 ui.write(("internal: %d %d\n") % d)
579 ui.write(("standard: %s\n") % dateutil.datestr(d))
579 ui.write(("standard: %s\n") % dateutil.datestr(d))
580 if range:
580 if range:
581 m = dateutil.matchdate(range)
581 m = dateutil.matchdate(range)
582 ui.write(("match: %s\n") % m(d[0]))
582 ui.write(("match: %s\n") % m(d[0]))
583
583
584 @command('debugdeltachain',
584 @command('debugdeltachain',
585 cmdutil.debugrevlogopts + cmdutil.formatteropts,
585 cmdutil.debugrevlogopts + cmdutil.formatteropts,
586 _('-c|-m|FILE'),
586 _('-c|-m|FILE'),
587 optionalrepo=True)
587 optionalrepo=True)
588 def debugdeltachain(ui, repo, file_=None, **opts):
588 def debugdeltachain(ui, repo, file_=None, **opts):
589 """dump information about delta chains in a revlog
589 """dump information about delta chains in a revlog
590
590
591 Output can be templatized. Available template keywords are:
591 Output can be templatized. Available template keywords are:
592
592
593 :``rev``: revision number
593 :``rev``: revision number
594 :``chainid``: delta chain identifier (numbered by unique base)
594 :``chainid``: delta chain identifier (numbered by unique base)
595 :``chainlen``: delta chain length to this revision
595 :``chainlen``: delta chain length to this revision
596 :``prevrev``: previous revision in delta chain
596 :``prevrev``: previous revision in delta chain
597 :``deltatype``: role of delta / how it was computed
597 :``deltatype``: role of delta / how it was computed
598 :``compsize``: compressed size of revision
598 :``compsize``: compressed size of revision
599 :``uncompsize``: uncompressed size of revision
599 :``uncompsize``: uncompressed size of revision
600 :``chainsize``: total size of compressed revisions in chain
600 :``chainsize``: total size of compressed revisions in chain
601 :``chainratio``: total chain size divided by uncompressed revision size
601 :``chainratio``: total chain size divided by uncompressed revision size
602 (new delta chains typically start at ratio 2.00)
602 (new delta chains typically start at ratio 2.00)
603 :``lindist``: linear distance from base revision in delta chain to end
603 :``lindist``: linear distance from base revision in delta chain to end
604 of this revision
604 of this revision
605 :``extradist``: total size of revisions not part of this delta chain from
605 :``extradist``: total size of revisions not part of this delta chain from
606 base of delta chain to end of this revision; a measurement
606 base of delta chain to end of this revision; a measurement
607 of how much extra data we need to read/seek across to read
607 of how much extra data we need to read/seek across to read
608 the delta chain for this revision
608 the delta chain for this revision
609 :``extraratio``: extradist divided by chainsize; another representation of
609 :``extraratio``: extradist divided by chainsize; another representation of
610 how much unrelated data is needed to load this delta chain
610 how much unrelated data is needed to load this delta chain
611
611
612 If the repository is configured to use the sparse read, additional keywords
612 If the repository is configured to use the sparse read, additional keywords
613 are available:
613 are available:
614
614
615 :``readsize``: total size of data read from the disk for a revision
615 :``readsize``: total size of data read from the disk for a revision
616 (sum of the sizes of all the blocks)
616 (sum of the sizes of all the blocks)
617 :``largestblock``: size of the largest block of data read from the disk
617 :``largestblock``: size of the largest block of data read from the disk
618 :``readdensity``: density of useful bytes in the data read from the disk
618 :``readdensity``: density of useful bytes in the data read from the disk
619 :``srchunks``: in how many data hunks the whole revision would be read
619 :``srchunks``: in how many data hunks the whole revision would be read
620
620
621 The sparse read can be enabled with experimental.sparse-read = True
621 The sparse read can be enabled with experimental.sparse-read = True
622 """
622 """
623 opts = pycompat.byteskwargs(opts)
623 opts = pycompat.byteskwargs(opts)
624 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
624 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
625 index = r.index
625 index = r.index
626 generaldelta = r.version & revlog.FLAG_GENERALDELTA
626 generaldelta = r.version & revlog.FLAG_GENERALDELTA
627 withsparseread = getattr(r, '_withsparseread', False)
627 withsparseread = getattr(r, '_withsparseread', False)
628
628
629 def revinfo(rev):
629 def revinfo(rev):
630 e = index[rev]
630 e = index[rev]
631 compsize = e[1]
631 compsize = e[1]
632 uncompsize = e[2]
632 uncompsize = e[2]
633 chainsize = 0
633 chainsize = 0
634
634
635 if generaldelta:
635 if generaldelta:
636 if e[3] == e[5]:
636 if e[3] == e[5]:
637 deltatype = 'p1'
637 deltatype = 'p1'
638 elif e[3] == e[6]:
638 elif e[3] == e[6]:
639 deltatype = 'p2'
639 deltatype = 'p2'
640 elif e[3] == rev - 1:
640 elif e[3] == rev - 1:
641 deltatype = 'prev'
641 deltatype = 'prev'
642 elif e[3] == rev:
642 elif e[3] == rev:
643 deltatype = 'base'
643 deltatype = 'base'
644 else:
644 else:
645 deltatype = 'other'
645 deltatype = 'other'
646 else:
646 else:
647 if e[3] == rev:
647 if e[3] == rev:
648 deltatype = 'base'
648 deltatype = 'base'
649 else:
649 else:
650 deltatype = 'prev'
650 deltatype = 'prev'
651
651
652 chain = r._deltachain(rev)[0]
652 chain = r._deltachain(rev)[0]
653 for iterrev in chain:
653 for iterrev in chain:
654 e = index[iterrev]
654 e = index[iterrev]
655 chainsize += e[1]
655 chainsize += e[1]
656
656
657 return compsize, uncompsize, deltatype, chain, chainsize
657 return compsize, uncompsize, deltatype, chain, chainsize
658
658
659 fm = ui.formatter('debugdeltachain', opts)
659 fm = ui.formatter('debugdeltachain', opts)
660
660
661 fm.plain(' rev chain# chainlen prev delta '
661 fm.plain(' rev chain# chainlen prev delta '
662 'size rawsize chainsize ratio lindist extradist '
662 'size rawsize chainsize ratio lindist extradist '
663 'extraratio')
663 'extraratio')
664 if withsparseread:
664 if withsparseread:
665 fm.plain(' readsize largestblk rddensity srchunks')
665 fm.plain(' readsize largestblk rddensity srchunks')
666 fm.plain('\n')
666 fm.plain('\n')
667
667
668 chainbases = {}
668 chainbases = {}
669 for rev in r:
669 for rev in r:
670 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
670 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
671 chainbase = chain[0]
671 chainbase = chain[0]
672 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
672 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
673 start = r.start
673 start = r.start
674 length = r.length
674 length = r.length
675 basestart = start(chainbase)
675 basestart = start(chainbase)
676 revstart = start(rev)
676 revstart = start(rev)
677 lineardist = revstart + comp - basestart
677 lineardist = revstart + comp - basestart
678 extradist = lineardist - chainsize
678 extradist = lineardist - chainsize
679 try:
679 try:
680 prevrev = chain[-2]
680 prevrev = chain[-2]
681 except IndexError:
681 except IndexError:
682 prevrev = -1
682 prevrev = -1
683
683
684 chainratio = float(chainsize) / float(uncomp)
684 chainratio = float(chainsize) / float(uncomp)
685 extraratio = float(extradist) / float(chainsize)
685 extraratio = float(extradist) / float(chainsize)
686
686
687 fm.startitem()
687 fm.startitem()
688 fm.write('rev chainid chainlen prevrev deltatype compsize '
688 fm.write('rev chainid chainlen prevrev deltatype compsize '
689 'uncompsize chainsize chainratio lindist extradist '
689 'uncompsize chainsize chainratio lindist extradist '
690 'extraratio',
690 'extraratio',
691 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
691 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
692 rev, chainid, len(chain), prevrev, deltatype, comp,
692 rev, chainid, len(chain), prevrev, deltatype, comp,
693 uncomp, chainsize, chainratio, lineardist, extradist,
693 uncomp, chainsize, chainratio, lineardist, extradist,
694 extraratio,
694 extraratio,
695 rev=rev, chainid=chainid, chainlen=len(chain),
695 rev=rev, chainid=chainid, chainlen=len(chain),
696 prevrev=prevrev, deltatype=deltatype, compsize=comp,
696 prevrev=prevrev, deltatype=deltatype, compsize=comp,
697 uncompsize=uncomp, chainsize=chainsize,
697 uncompsize=uncomp, chainsize=chainsize,
698 chainratio=chainratio, lindist=lineardist,
698 chainratio=chainratio, lindist=lineardist,
699 extradist=extradist, extraratio=extraratio)
699 extradist=extradist, extraratio=extraratio)
700 if withsparseread:
700 if withsparseread:
701 readsize = 0
701 readsize = 0
702 largestblock = 0
702 largestblock = 0
703 srchunks = 0
703 srchunks = 0
704
704
705 for revschunk in revlog._slicechunk(r, chain):
705 for revschunk in revlog._slicechunk(r, chain):
706 srchunks += 1
706 srchunks += 1
707 blkend = start(revschunk[-1]) + length(revschunk[-1])
707 blkend = start(revschunk[-1]) + length(revschunk[-1])
708 blksize = blkend - start(revschunk[0])
708 blksize = blkend - start(revschunk[0])
709
709
710 readsize += blksize
710 readsize += blksize
711 if largestblock < blksize:
711 if largestblock < blksize:
712 largestblock = blksize
712 largestblock = blksize
713
713
714 readdensity = float(chainsize) / float(readsize)
714 readdensity = float(chainsize) / float(readsize)
715
715
716 fm.write('readsize largestblock readdensity srchunks',
716 fm.write('readsize largestblock readdensity srchunks',
717 ' %10d %10d %9.5f %8d',
717 ' %10d %10d %9.5f %8d',
718 readsize, largestblock, readdensity, srchunks,
718 readsize, largestblock, readdensity, srchunks,
719 readsize=readsize, largestblock=largestblock,
719 readsize=readsize, largestblock=largestblock,
720 readdensity=readdensity, srchunks=srchunks)
720 readdensity=readdensity, srchunks=srchunks)
721
721
722 fm.plain('\n')
722 fm.plain('\n')
723
723
724 fm.end()
724 fm.end()
725
725
726 @command('debugdirstate|debugstate',
726 @command('debugdirstate|debugstate',
727 [('', 'nodates', None, _('do not display the saved mtime')),
727 [('', 'nodates', None, _('do not display the saved mtime')),
728 ('', 'datesort', None, _('sort by saved mtime'))],
728 ('', 'datesort', None, _('sort by saved mtime'))],
729 _('[OPTION]...'))
729 _('[OPTION]...'))
730 def debugstate(ui, repo, **opts):
730 def debugstate(ui, repo, **opts):
731 """show the contents of the current dirstate"""
731 """show the contents of the current dirstate"""
732
732
733 nodates = opts.get(r'nodates')
733 nodates = opts.get(r'nodates')
734 datesort = opts.get(r'datesort')
734 datesort = opts.get(r'datesort')
735
735
736 timestr = ""
736 timestr = ""
737 if datesort:
737 if datesort:
738 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
738 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
739 else:
739 else:
740 keyfunc = None # sort by filename
740 keyfunc = None # sort by filename
741 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
741 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
742 if ent[3] == -1:
742 if ent[3] == -1:
743 timestr = 'unset '
743 timestr = 'unset '
744 elif nodates:
744 elif nodates:
745 timestr = 'set '
745 timestr = 'set '
746 else:
746 else:
747 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
747 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
748 time.localtime(ent[3]))
748 time.localtime(ent[3]))
749 timestr = encoding.strtolocal(timestr)
749 timestr = encoding.strtolocal(timestr)
750 if ent[1] & 0o20000:
750 if ent[1] & 0o20000:
751 mode = 'lnk'
751 mode = 'lnk'
752 else:
752 else:
753 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
753 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
754 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
754 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
755 for f in repo.dirstate.copies():
755 for f in repo.dirstate.copies():
756 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
756 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
757
757
758 @command('debugdiscovery',
758 @command('debugdiscovery',
759 [('', 'old', None, _('use old-style discovery')),
759 [('', 'old', None, _('use old-style discovery')),
760 ('', 'nonheads', None,
760 ('', 'nonheads', None,
761 _('use old-style discovery with non-heads included')),
761 _('use old-style discovery with non-heads included')),
762 ('', 'rev', [], 'restrict discovery to this set of revs'),
762 ('', 'rev', [], 'restrict discovery to this set of revs'),
763 ] + cmdutil.remoteopts,
763 ] + cmdutil.remoteopts,
764 _('[--rev REV] [OTHER]'))
764 _('[--rev REV] [OTHER]'))
765 def debugdiscovery(ui, repo, remoteurl="default", **opts):
765 def debugdiscovery(ui, repo, remoteurl="default", **opts):
766 """runs the changeset discovery protocol in isolation"""
766 """runs the changeset discovery protocol in isolation"""
767 opts = pycompat.byteskwargs(opts)
767 opts = pycompat.byteskwargs(opts)
768 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
768 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
769 remote = hg.peer(repo, opts, remoteurl)
769 remote = hg.peer(repo, opts, remoteurl)
770 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
770 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
771
771
772 # make sure tests are repeatable
772 # make sure tests are repeatable
773 random.seed(12323)
773 random.seed(12323)
774
774
775 def doit(pushedrevs, remoteheads, remote=remote):
775 def doit(pushedrevs, remoteheads, remote=remote):
776 if opts.get('old'):
776 if opts.get('old'):
777 if not util.safehasattr(remote, 'branches'):
777 if not util.safehasattr(remote, 'branches'):
778 # enable in-client legacy support
778 # enable in-client legacy support
779 remote = localrepo.locallegacypeer(remote.local())
779 remote = localrepo.locallegacypeer(remote.local())
780 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
780 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
781 force=True)
781 force=True)
782 common = set(common)
782 common = set(common)
783 if not opts.get('nonheads'):
783 if not opts.get('nonheads'):
784 ui.write(("unpruned common: %s\n") %
784 ui.write(("unpruned common: %s\n") %
785 " ".join(sorted(short(n) for n in common)))
785 " ".join(sorted(short(n) for n in common)))
786 dag = dagutil.revlogdag(repo.changelog)
786 dag = dagutil.revlogdag(repo.changelog)
787 all = dag.ancestorset(dag.internalizeall(common))
787 all = dag.ancestorset(dag.internalizeall(common))
788 common = dag.externalizeall(dag.headsetofconnecteds(all))
788 common = dag.externalizeall(dag.headsetofconnecteds(all))
789 else:
789 else:
790 nodes = None
790 nodes = None
791 if pushedrevs:
791 if pushedrevs:
792 revs = scmutil.revrange(repo, pushedrevs)
792 revs = scmutil.revrange(repo, pushedrevs)
793 nodes = [repo[r].node() for r in revs]
793 nodes = [repo[r].node() for r in revs]
794 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
794 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
795 ancestorsof=nodes)
795 ancestorsof=nodes)
796 common = set(common)
796 common = set(common)
797 rheads = set(hds)
797 rheads = set(hds)
798 lheads = set(repo.heads())
798 lheads = set(repo.heads())
799 ui.write(("common heads: %s\n") %
799 ui.write(("common heads: %s\n") %
800 " ".join(sorted(short(n) for n in common)))
800 " ".join(sorted(short(n) for n in common)))
801 if lheads <= common:
801 if lheads <= common:
802 ui.write(("local is subset\n"))
802 ui.write(("local is subset\n"))
803 elif rheads <= common:
803 elif rheads <= common:
804 ui.write(("remote is subset\n"))
804 ui.write(("remote is subset\n"))
805
805
806 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
806 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
807 localrevs = opts['rev']
807 localrevs = opts['rev']
808 doit(localrevs, remoterevs)
808 doit(localrevs, remoterevs)
809
809
810 _chunksize = 4 << 10
810 _chunksize = 4 << 10
811
811
812 @command('debugdownload',
812 @command('debugdownload',
813 [
813 [
814 ('o', 'output', '', _('path')),
814 ('o', 'output', '', _('path')),
815 ],
815 ],
816 optionalrepo=True)
816 optionalrepo=True)
817 def debugdownload(ui, repo, url, output=None, **opts):
817 def debugdownload(ui, repo, url, output=None, **opts):
818 """download a resource using Mercurial logic and config
818 """download a resource using Mercurial logic and config
819 """
819 """
820 fh = urlmod.open(ui, url, output)
820 fh = urlmod.open(ui, url, output)
821
821
822 dest = ui
822 dest = ui
823 if output:
823 if output:
824 dest = open(output, "wb", _chunksize)
824 dest = open(output, "wb", _chunksize)
825 try:
825 try:
826 data = fh.read(_chunksize)
826 data = fh.read(_chunksize)
827 while data:
827 while data:
828 dest.write(data)
828 dest.write(data)
829 data = fh.read(_chunksize)
829 data = fh.read(_chunksize)
830 finally:
830 finally:
831 if output:
831 if output:
832 dest.close()
832 dest.close()
833
833
834 @command('debugextensions', cmdutil.formatteropts, [], norepo=True)
834 @command('debugextensions', cmdutil.formatteropts, [], norepo=True)
835 def debugextensions(ui, **opts):
835 def debugextensions(ui, **opts):
836 '''show information about active extensions'''
836 '''show information about active extensions'''
837 opts = pycompat.byteskwargs(opts)
837 opts = pycompat.byteskwargs(opts)
838 exts = extensions.extensions(ui)
838 exts = extensions.extensions(ui)
839 hgver = util.version()
839 hgver = util.version()
840 fm = ui.formatter('debugextensions', opts)
840 fm = ui.formatter('debugextensions', opts)
841 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
841 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
842 isinternal = extensions.ismoduleinternal(extmod)
842 isinternal = extensions.ismoduleinternal(extmod)
843 extsource = pycompat.fsencode(extmod.__file__)
843 extsource = pycompat.fsencode(extmod.__file__)
844 if isinternal:
844 if isinternal:
845 exttestedwith = [] # never expose magic string to users
845 exttestedwith = [] # never expose magic string to users
846 else:
846 else:
847 exttestedwith = getattr(extmod, 'testedwith', '').split()
847 exttestedwith = getattr(extmod, 'testedwith', '').split()
848 extbuglink = getattr(extmod, 'buglink', None)
848 extbuglink = getattr(extmod, 'buglink', None)
849
849
850 fm.startitem()
850 fm.startitem()
851
851
852 if ui.quiet or ui.verbose:
852 if ui.quiet or ui.verbose:
853 fm.write('name', '%s\n', extname)
853 fm.write('name', '%s\n', extname)
854 else:
854 else:
855 fm.write('name', '%s', extname)
855 fm.write('name', '%s', extname)
856 if isinternal or hgver in exttestedwith:
856 if isinternal or hgver in exttestedwith:
857 fm.plain('\n')
857 fm.plain('\n')
858 elif not exttestedwith:
858 elif not exttestedwith:
859 fm.plain(_(' (untested!)\n'))
859 fm.plain(_(' (untested!)\n'))
860 else:
860 else:
861 lasttestedversion = exttestedwith[-1]
861 lasttestedversion = exttestedwith[-1]
862 fm.plain(' (%s!)\n' % lasttestedversion)
862 fm.plain(' (%s!)\n' % lasttestedversion)
863
863
864 fm.condwrite(ui.verbose and extsource, 'source',
864 fm.condwrite(ui.verbose and extsource, 'source',
865 _(' location: %s\n'), extsource or "")
865 _(' location: %s\n'), extsource or "")
866
866
867 if ui.verbose:
867 if ui.verbose:
868 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
868 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
869 fm.data(bundled=isinternal)
869 fm.data(bundled=isinternal)
870
870
871 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
871 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
872 _(' tested with: %s\n'),
872 _(' tested with: %s\n'),
873 fm.formatlist(exttestedwith, name='ver'))
873 fm.formatlist(exttestedwith, name='ver'))
874
874
875 fm.condwrite(ui.verbose and extbuglink, 'buglink',
875 fm.condwrite(ui.verbose and extbuglink, 'buglink',
876 _(' bug reporting: %s\n'), extbuglink or "")
876 _(' bug reporting: %s\n'), extbuglink or "")
877
877
878 fm.end()
878 fm.end()
879
879
880 @command('debugfileset',
880 @command('debugfileset',
881 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
881 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
882 _('[-r REV] FILESPEC'))
882 _('[-r REV] FILESPEC'))
883 def debugfileset(ui, repo, expr, **opts):
883 def debugfileset(ui, repo, expr, **opts):
884 '''parse and apply a fileset specification'''
884 '''parse and apply a fileset specification'''
885 ctx = scmutil.revsingle(repo, opts.get(r'rev'), None)
885 ctx = scmutil.revsingle(repo, opts.get(r'rev'), None)
886 if ui.verbose:
886 if ui.verbose:
887 tree = fileset.parse(expr)
887 tree = fileset.parse(expr)
888 ui.note(fileset.prettyformat(tree), "\n")
888 ui.note(fileset.prettyformat(tree), "\n")
889
889
890 for f in ctx.getfileset(expr):
890 for f in ctx.getfileset(expr):
891 ui.write("%s\n" % f)
891 ui.write("%s\n" % f)
892
892
893 @command('debugformat',
893 @command('debugformat',
894 [] + cmdutil.formatteropts,
894 [] + cmdutil.formatteropts,
895 _(''))
895 _(''))
896 def debugformat(ui, repo, **opts):
896 def debugformat(ui, repo, **opts):
897 """display format information about the current repository
897 """display format information about the current repository
898
898
899 Use --verbose to get extra information about current config value and
899 Use --verbose to get extra information about current config value and
900 Mercurial default."""
900 Mercurial default."""
901 opts = pycompat.byteskwargs(opts)
901 opts = pycompat.byteskwargs(opts)
902 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
902 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
903 maxvariantlength = max(len('format-variant'), maxvariantlength)
903 maxvariantlength = max(len('format-variant'), maxvariantlength)
904
904
905 def makeformatname(name):
905 def makeformatname(name):
906 return '%s:' + (' ' * (maxvariantlength - len(name)))
906 return '%s:' + (' ' * (maxvariantlength - len(name)))
907
907
908 fm = ui.formatter('debugformat', opts)
908 fm = ui.formatter('debugformat', opts)
909 if fm.isplain():
909 if fm.isplain():
910 def formatvalue(value):
910 def formatvalue(value):
911 if util.safehasattr(value, 'startswith'):
911 if util.safehasattr(value, 'startswith'):
912 return value
912 return value
913 if value:
913 if value:
914 return 'yes'
914 return 'yes'
915 else:
915 else:
916 return 'no'
916 return 'no'
917 else:
917 else:
918 formatvalue = pycompat.identity
918 formatvalue = pycompat.identity
919
919
920 fm.plain('format-variant')
920 fm.plain('format-variant')
921 fm.plain(' ' * (maxvariantlength - len('format-variant')))
921 fm.plain(' ' * (maxvariantlength - len('format-variant')))
922 fm.plain(' repo')
922 fm.plain(' repo')
923 if ui.verbose:
923 if ui.verbose:
924 fm.plain(' config default')
924 fm.plain(' config default')
925 fm.plain('\n')
925 fm.plain('\n')
926 for fv in upgrade.allformatvariant:
926 for fv in upgrade.allformatvariant:
927 fm.startitem()
927 fm.startitem()
928 repovalue = fv.fromrepo(repo)
928 repovalue = fv.fromrepo(repo)
929 configvalue = fv.fromconfig(repo)
929 configvalue = fv.fromconfig(repo)
930
930
931 if repovalue != configvalue:
931 if repovalue != configvalue:
932 namelabel = 'formatvariant.name.mismatchconfig'
932 namelabel = 'formatvariant.name.mismatchconfig'
933 repolabel = 'formatvariant.repo.mismatchconfig'
933 repolabel = 'formatvariant.repo.mismatchconfig'
934 elif repovalue != fv.default:
934 elif repovalue != fv.default:
935 namelabel = 'formatvariant.name.mismatchdefault'
935 namelabel = 'formatvariant.name.mismatchdefault'
936 repolabel = 'formatvariant.repo.mismatchdefault'
936 repolabel = 'formatvariant.repo.mismatchdefault'
937 else:
937 else:
938 namelabel = 'formatvariant.name.uptodate'
938 namelabel = 'formatvariant.name.uptodate'
939 repolabel = 'formatvariant.repo.uptodate'
939 repolabel = 'formatvariant.repo.uptodate'
940
940
941 fm.write('name', makeformatname(fv.name), fv.name,
941 fm.write('name', makeformatname(fv.name), fv.name,
942 label=namelabel)
942 label=namelabel)
943 fm.write('repo', ' %3s', formatvalue(repovalue),
943 fm.write('repo', ' %3s', formatvalue(repovalue),
944 label=repolabel)
944 label=repolabel)
945 if fv.default != configvalue:
945 if fv.default != configvalue:
946 configlabel = 'formatvariant.config.special'
946 configlabel = 'formatvariant.config.special'
947 else:
947 else:
948 configlabel = 'formatvariant.config.default'
948 configlabel = 'formatvariant.config.default'
949 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
949 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
950 label=configlabel)
950 label=configlabel)
951 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
951 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
952 label='formatvariant.default')
952 label='formatvariant.default')
953 fm.plain('\n')
953 fm.plain('\n')
954 fm.end()
954 fm.end()
955
955
956 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
956 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
957 def debugfsinfo(ui, path="."):
957 def debugfsinfo(ui, path="."):
958 """show information detected about current filesystem"""
958 """show information detected about current filesystem"""
959 ui.write(('path: %s\n') % path)
959 ui.write(('path: %s\n') % path)
960 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
960 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
961 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
961 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
962 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
962 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
963 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
963 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
964 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
964 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
965 casesensitive = '(unknown)'
965 casesensitive = '(unknown)'
966 try:
966 try:
967 with tempfile.NamedTemporaryFile(prefix='.debugfsinfo', dir=path) as f:
967 with tempfile.NamedTemporaryFile(prefix='.debugfsinfo', dir=path) as f:
968 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
968 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
969 except OSError:
969 except OSError:
970 pass
970 pass
971 ui.write(('case-sensitive: %s\n') % casesensitive)
971 ui.write(('case-sensitive: %s\n') % casesensitive)
972
972
973 @command('debuggetbundle',
973 @command('debuggetbundle',
974 [('H', 'head', [], _('id of head node'), _('ID')),
974 [('H', 'head', [], _('id of head node'), _('ID')),
975 ('C', 'common', [], _('id of common node'), _('ID')),
975 ('C', 'common', [], _('id of common node'), _('ID')),
976 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
976 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
977 _('REPO FILE [-H|-C ID]...'),
977 _('REPO FILE [-H|-C ID]...'),
978 norepo=True)
978 norepo=True)
979 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
979 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
980 """retrieves a bundle from a repo
980 """retrieves a bundle from a repo
981
981
982 Every ID must be a full-length hex node id string. Saves the bundle to the
982 Every ID must be a full-length hex node id string. Saves the bundle to the
983 given file.
983 given file.
984 """
984 """
985 opts = pycompat.byteskwargs(opts)
985 opts = pycompat.byteskwargs(opts)
986 repo = hg.peer(ui, opts, repopath)
986 repo = hg.peer(ui, opts, repopath)
987 if not repo.capable('getbundle'):
987 if not repo.capable('getbundle'):
988 raise error.Abort("getbundle() not supported by target repository")
988 raise error.Abort("getbundle() not supported by target repository")
989 args = {}
989 args = {}
990 if common:
990 if common:
991 args[r'common'] = [bin(s) for s in common]
991 args[r'common'] = [bin(s) for s in common]
992 if head:
992 if head:
993 args[r'heads'] = [bin(s) for s in head]
993 args[r'heads'] = [bin(s) for s in head]
994 # TODO: get desired bundlecaps from command line.
994 # TODO: get desired bundlecaps from command line.
995 args[r'bundlecaps'] = None
995 args[r'bundlecaps'] = None
996 bundle = repo.getbundle('debug', **args)
996 bundle = repo.getbundle('debug', **args)
997
997
998 bundletype = opts.get('type', 'bzip2').lower()
998 bundletype = opts.get('type', 'bzip2').lower()
999 btypes = {'none': 'HG10UN',
999 btypes = {'none': 'HG10UN',
1000 'bzip2': 'HG10BZ',
1000 'bzip2': 'HG10BZ',
1001 'gzip': 'HG10GZ',
1001 'gzip': 'HG10GZ',
1002 'bundle2': 'HG20'}
1002 'bundle2': 'HG20'}
1003 bundletype = btypes.get(bundletype)
1003 bundletype = btypes.get(bundletype)
1004 if bundletype not in bundle2.bundletypes:
1004 if bundletype not in bundle2.bundletypes:
1005 raise error.Abort(_('unknown bundle type specified with --type'))
1005 raise error.Abort(_('unknown bundle type specified with --type'))
1006 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1006 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1007
1007
1008 @command('debugignore', [], '[FILE]')
1008 @command('debugignore', [], '[FILE]')
1009 def debugignore(ui, repo, *files, **opts):
1009 def debugignore(ui, repo, *files, **opts):
1010 """display the combined ignore pattern and information about ignored files
1010 """display the combined ignore pattern and information about ignored files
1011
1011
1012 With no argument display the combined ignore pattern.
1012 With no argument display the combined ignore pattern.
1013
1013
1014 Given space separated file names, shows if the given file is ignored and
1014 Given space separated file names, shows if the given file is ignored and
1015 if so, show the ignore rule (file and line number) that matched it.
1015 if so, show the ignore rule (file and line number) that matched it.
1016 """
1016 """
1017 ignore = repo.dirstate._ignore
1017 ignore = repo.dirstate._ignore
1018 if not files:
1018 if not files:
1019 # Show all the patterns
1019 # Show all the patterns
1020 ui.write("%s\n" % pycompat.byterepr(ignore))
1020 ui.write("%s\n" % pycompat.byterepr(ignore))
1021 else:
1021 else:
1022 m = scmutil.match(repo[None], pats=files)
1022 m = scmutil.match(repo[None], pats=files)
1023 for f in m.files():
1023 for f in m.files():
1024 nf = util.normpath(f)
1024 nf = util.normpath(f)
1025 ignored = None
1025 ignored = None
1026 ignoredata = None
1026 ignoredata = None
1027 if nf != '.':
1027 if nf != '.':
1028 if ignore(nf):
1028 if ignore(nf):
1029 ignored = nf
1029 ignored = nf
1030 ignoredata = repo.dirstate._ignorefileandline(nf)
1030 ignoredata = repo.dirstate._ignorefileandline(nf)
1031 else:
1031 else:
1032 for p in util.finddirs(nf):
1032 for p in util.finddirs(nf):
1033 if ignore(p):
1033 if ignore(p):
1034 ignored = p
1034 ignored = p
1035 ignoredata = repo.dirstate._ignorefileandline(p)
1035 ignoredata = repo.dirstate._ignorefileandline(p)
1036 break
1036 break
1037 if ignored:
1037 if ignored:
1038 if ignored == nf:
1038 if ignored == nf:
1039 ui.write(_("%s is ignored\n") % m.uipath(f))
1039 ui.write(_("%s is ignored\n") % m.uipath(f))
1040 else:
1040 else:
1041 ui.write(_("%s is ignored because of "
1041 ui.write(_("%s is ignored because of "
1042 "containing folder %s\n")
1042 "containing folder %s\n")
1043 % (m.uipath(f), ignored))
1043 % (m.uipath(f), ignored))
1044 ignorefile, lineno, line = ignoredata
1044 ignorefile, lineno, line = ignoredata
1045 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1045 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1046 % (ignorefile, lineno, line))
1046 % (ignorefile, lineno, line))
1047 else:
1047 else:
1048 ui.write(_("%s is not ignored\n") % m.uipath(f))
1048 ui.write(_("%s is not ignored\n") % m.uipath(f))
1049
1049
1050 @command('debugindex', cmdutil.debugrevlogopts +
1050 @command('debugindex', cmdutil.debugrevlogopts +
1051 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1051 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1052 _('[-f FORMAT] -c|-m|FILE'),
1052 _('[-f FORMAT] -c|-m|FILE'),
1053 optionalrepo=True)
1053 optionalrepo=True)
1054 def debugindex(ui, repo, file_=None, **opts):
1054 def debugindex(ui, repo, file_=None, **opts):
1055 """dump the contents of an index file"""
1055 """dump the contents of an index file"""
1056 opts = pycompat.byteskwargs(opts)
1056 opts = pycompat.byteskwargs(opts)
1057 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1057 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1058 format = opts.get('format', 0)
1058 format = opts.get('format', 0)
1059 if format not in (0, 1):
1059 if format not in (0, 1):
1060 raise error.Abort(_("unknown format %d") % format)
1060 raise error.Abort(_("unknown format %d") % format)
1061
1061
1062 if ui.debugflag:
1062 if ui.debugflag:
1063 shortfn = hex
1063 shortfn = hex
1064 else:
1064 else:
1065 shortfn = short
1065 shortfn = short
1066
1066
1067 # There might not be anything in r, so have a sane default
1067 # There might not be anything in r, so have a sane default
1068 idlen = 12
1068 idlen = 12
1069 for i in r:
1069 for i in r:
1070 idlen = len(shortfn(r.node(i)))
1070 idlen = len(shortfn(r.node(i)))
1071 break
1071 break
1072
1072
1073 if format == 0:
1073 if format == 0:
1074 if ui.verbose:
1074 if ui.verbose:
1075 ui.write((" rev offset length linkrev"
1075 ui.write((" rev offset length linkrev"
1076 " %s %s p2\n") % ("nodeid".ljust(idlen),
1076 " %s %s p2\n") % ("nodeid".ljust(idlen),
1077 "p1".ljust(idlen)))
1077 "p1".ljust(idlen)))
1078 else:
1078 else:
1079 ui.write((" rev linkrev %s %s p2\n") % (
1079 ui.write((" rev linkrev %s %s p2\n") % (
1080 "nodeid".ljust(idlen), "p1".ljust(idlen)))
1080 "nodeid".ljust(idlen), "p1".ljust(idlen)))
1081 elif format == 1:
1081 elif format == 1:
1082 if ui.verbose:
1082 if ui.verbose:
1083 ui.write((" rev flag offset length size link p1"
1083 ui.write((" rev flag offset length size link p1"
1084 " p2 %s\n") % "nodeid".rjust(idlen))
1084 " p2 %s\n") % "nodeid".rjust(idlen))
1085 else:
1085 else:
1086 ui.write((" rev flag size link p1 p2 %s\n") %
1086 ui.write((" rev flag size link p1 p2 %s\n") %
1087 "nodeid".rjust(idlen))
1087 "nodeid".rjust(idlen))
1088
1088
1089 for i in r:
1089 for i in r:
1090 node = r.node(i)
1090 node = r.node(i)
1091 if format == 0:
1091 if format == 0:
1092 try:
1092 try:
1093 pp = r.parents(node)
1093 pp = r.parents(node)
1094 except Exception:
1094 except Exception:
1095 pp = [nullid, nullid]
1095 pp = [nullid, nullid]
1096 if ui.verbose:
1096 if ui.verbose:
1097 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
1097 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
1098 i, r.start(i), r.length(i), r.linkrev(i),
1098 i, r.start(i), r.length(i), r.linkrev(i),
1099 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
1099 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
1100 else:
1100 else:
1101 ui.write("% 6d % 7d %s %s %s\n" % (
1101 ui.write("% 6d % 7d %s %s %s\n" % (
1102 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
1102 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
1103 shortfn(pp[1])))
1103 shortfn(pp[1])))
1104 elif format == 1:
1104 elif format == 1:
1105 pr = r.parentrevs(i)
1105 pr = r.parentrevs(i)
1106 if ui.verbose:
1106 if ui.verbose:
1107 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
1107 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
1108 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1108 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1109 r.linkrev(i), pr[0], pr[1], shortfn(node)))
1109 r.linkrev(i), pr[0], pr[1], shortfn(node)))
1110 else:
1110 else:
1111 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
1111 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
1112 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
1112 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
1113 shortfn(node)))
1113 shortfn(node)))
1114
1114
1115 @command('debugindexdot', cmdutil.debugrevlogopts,
1115 @command('debugindexdot', cmdutil.debugrevlogopts,
1116 _('-c|-m|FILE'), optionalrepo=True)
1116 _('-c|-m|FILE'), optionalrepo=True)
1117 def debugindexdot(ui, repo, file_=None, **opts):
1117 def debugindexdot(ui, repo, file_=None, **opts):
1118 """dump an index DAG as a graphviz dot file"""
1118 """dump an index DAG as a graphviz dot file"""
1119 opts = pycompat.byteskwargs(opts)
1119 opts = pycompat.byteskwargs(opts)
1120 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
1120 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
1121 ui.write(("digraph G {\n"))
1121 ui.write(("digraph G {\n"))
1122 for i in r:
1122 for i in r:
1123 node = r.node(i)
1123 node = r.node(i)
1124 pp = r.parents(node)
1124 pp = r.parents(node)
1125 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1125 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1126 if pp[1] != nullid:
1126 if pp[1] != nullid:
1127 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1127 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1128 ui.write("}\n")
1128 ui.write("}\n")
1129
1129
1130 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1130 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1131 def debuginstall(ui, **opts):
1131 def debuginstall(ui, **opts):
1132 '''test Mercurial installation
1132 '''test Mercurial installation
1133
1133
1134 Returns 0 on success.
1134 Returns 0 on success.
1135 '''
1135 '''
1136 opts = pycompat.byteskwargs(opts)
1136 opts = pycompat.byteskwargs(opts)
1137
1137
1138 def writetemp(contents):
1138 def writetemp(contents):
1139 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1139 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1140 f = os.fdopen(fd, r"wb")
1140 f = os.fdopen(fd, r"wb")
1141 f.write(contents)
1141 f.write(contents)
1142 f.close()
1142 f.close()
1143 return name
1143 return name
1144
1144
1145 problems = 0
1145 problems = 0
1146
1146
1147 fm = ui.formatter('debuginstall', opts)
1147 fm = ui.formatter('debuginstall', opts)
1148 fm.startitem()
1148 fm.startitem()
1149
1149
1150 # encoding
1150 # encoding
1151 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1151 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1152 err = None
1152 err = None
1153 try:
1153 try:
1154 codecs.lookup(pycompat.sysstr(encoding.encoding))
1154 codecs.lookup(pycompat.sysstr(encoding.encoding))
1155 except LookupError as inst:
1155 except LookupError as inst:
1156 err = stringutil.forcebytestr(inst)
1156 err = stringutil.forcebytestr(inst)
1157 problems += 1
1157 problems += 1
1158 fm.condwrite(err, 'encodingerror', _(" %s\n"
1158 fm.condwrite(err, 'encodingerror', _(" %s\n"
1159 " (check that your locale is properly set)\n"), err)
1159 " (check that your locale is properly set)\n"), err)
1160
1160
1161 # Python
1161 # Python
1162 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1162 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1163 pycompat.sysexecutable)
1163 pycompat.sysexecutable)
1164 fm.write('pythonver', _("checking Python version (%s)\n"),
1164 fm.write('pythonver', _("checking Python version (%s)\n"),
1165 ("%d.%d.%d" % sys.version_info[:3]))
1165 ("%d.%d.%d" % sys.version_info[:3]))
1166 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1166 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1167 os.path.dirname(pycompat.fsencode(os.__file__)))
1167 os.path.dirname(pycompat.fsencode(os.__file__)))
1168
1168
1169 security = set(sslutil.supportedprotocols)
1169 security = set(sslutil.supportedprotocols)
1170 if sslutil.hassni:
1170 if sslutil.hassni:
1171 security.add('sni')
1171 security.add('sni')
1172
1172
1173 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1173 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1174 fm.formatlist(sorted(security), name='protocol',
1174 fm.formatlist(sorted(security), name='protocol',
1175 fmt='%s', sep=','))
1175 fmt='%s', sep=','))
1176
1176
1177 # These are warnings, not errors. So don't increment problem count. This
1177 # These are warnings, not errors. So don't increment problem count. This
1178 # may change in the future.
1178 # may change in the future.
1179 if 'tls1.2' not in security:
1179 if 'tls1.2' not in security:
1180 fm.plain(_(' TLS 1.2 not supported by Python install; '
1180 fm.plain(_(' TLS 1.2 not supported by Python install; '
1181 'network connections lack modern security\n'))
1181 'network connections lack modern security\n'))
1182 if 'sni' not in security:
1182 if 'sni' not in security:
1183 fm.plain(_(' SNI not supported by Python install; may have '
1183 fm.plain(_(' SNI not supported by Python install; may have '
1184 'connectivity issues with some servers\n'))
1184 'connectivity issues with some servers\n'))
1185
1185
1186 # TODO print CA cert info
1186 # TODO print CA cert info
1187
1187
1188 # hg version
1188 # hg version
1189 hgver = util.version()
1189 hgver = util.version()
1190 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1190 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1191 hgver.split('+')[0])
1191 hgver.split('+')[0])
1192 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1192 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1193 '+'.join(hgver.split('+')[1:]))
1193 '+'.join(hgver.split('+')[1:]))
1194
1194
1195 # compiled modules
1195 # compiled modules
1196 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1196 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1197 policy.policy)
1197 policy.policy)
1198 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1198 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1199 os.path.dirname(pycompat.fsencode(__file__)))
1199 os.path.dirname(pycompat.fsencode(__file__)))
1200
1200
1201 if policy.policy in ('c', 'allow'):
1201 if policy.policy in ('c', 'allow'):
1202 err = None
1202 err = None
1203 try:
1203 try:
1204 from .cext import (
1204 from .cext import (
1205 base85,
1205 base85,
1206 bdiff,
1206 bdiff,
1207 mpatch,
1207 mpatch,
1208 osutil,
1208 osutil,
1209 )
1209 )
1210 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1210 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1211 except Exception as inst:
1211 except Exception as inst:
1212 err = stringutil.forcebytestr(inst)
1212 err = stringutil.forcebytestr(inst)
1213 problems += 1
1213 problems += 1
1214 fm.condwrite(err, 'extensionserror', " %s\n", err)
1214 fm.condwrite(err, 'extensionserror', " %s\n", err)
1215
1215
1216 compengines = util.compengines._engines.values()
1216 compengines = util.compengines._engines.values()
1217 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1217 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1218 fm.formatlist(sorted(e.name() for e in compengines),
1218 fm.formatlist(sorted(e.name() for e in compengines),
1219 name='compengine', fmt='%s', sep=', '))
1219 name='compengine', fmt='%s', sep=', '))
1220 fm.write('compenginesavail', _('checking available compression engines '
1220 fm.write('compenginesavail', _('checking available compression engines '
1221 '(%s)\n'),
1221 '(%s)\n'),
1222 fm.formatlist(sorted(e.name() for e in compengines
1222 fm.formatlist(sorted(e.name() for e in compengines
1223 if e.available()),
1223 if e.available()),
1224 name='compengine', fmt='%s', sep=', '))
1224 name='compengine', fmt='%s', sep=', '))
1225 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1225 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1226 fm.write('compenginesserver', _('checking available compression engines '
1226 fm.write('compenginesserver', _('checking available compression engines '
1227 'for wire protocol (%s)\n'),
1227 'for wire protocol (%s)\n'),
1228 fm.formatlist([e.name() for e in wirecompengines
1228 fm.formatlist([e.name() for e in wirecompengines
1229 if e.wireprotosupport()],
1229 if e.wireprotosupport()],
1230 name='compengine', fmt='%s', sep=', '))
1230 name='compengine', fmt='%s', sep=', '))
1231 re2 = 'missing'
1231 re2 = 'missing'
1232 if util._re2:
1232 if util._re2:
1233 re2 = 'available'
1233 re2 = 'available'
1234 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1234 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1235 fm.data(re2=bool(util._re2))
1235 fm.data(re2=bool(util._re2))
1236
1236
1237 # templates
1237 # templates
1238 p = templater.templatepaths()
1238 p = templater.templatepaths()
1239 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1239 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1240 fm.condwrite(not p, '', _(" no template directories found\n"))
1240 fm.condwrite(not p, '', _(" no template directories found\n"))
1241 if p:
1241 if p:
1242 m = templater.templatepath("map-cmdline.default")
1242 m = templater.templatepath("map-cmdline.default")
1243 if m:
1243 if m:
1244 # template found, check if it is working
1244 # template found, check if it is working
1245 err = None
1245 err = None
1246 try:
1246 try:
1247 templater.templater.frommapfile(m)
1247 templater.templater.frommapfile(m)
1248 except Exception as inst:
1248 except Exception as inst:
1249 err = stringutil.forcebytestr(inst)
1249 err = stringutil.forcebytestr(inst)
1250 p = None
1250 p = None
1251 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1251 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1252 else:
1252 else:
1253 p = None
1253 p = None
1254 fm.condwrite(p, 'defaulttemplate',
1254 fm.condwrite(p, 'defaulttemplate',
1255 _("checking default template (%s)\n"), m)
1255 _("checking default template (%s)\n"), m)
1256 fm.condwrite(not m, 'defaulttemplatenotfound',
1256 fm.condwrite(not m, 'defaulttemplatenotfound',
1257 _(" template '%s' not found\n"), "default")
1257 _(" template '%s' not found\n"), "default")
1258 if not p:
1258 if not p:
1259 problems += 1
1259 problems += 1
1260 fm.condwrite(not p, '',
1260 fm.condwrite(not p, '',
1261 _(" (templates seem to have been installed incorrectly)\n"))
1261 _(" (templates seem to have been installed incorrectly)\n"))
1262
1262
1263 # editor
1263 # editor
1264 editor = ui.geteditor()
1264 editor = ui.geteditor()
1265 editor = util.expandpath(editor)
1265 editor = util.expandpath(editor)
1266 editorbin = procutil.shellsplit(editor)[0]
1266 editorbin = procutil.shellsplit(editor)[0]
1267 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1267 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1268 cmdpath = procutil.findexe(editorbin)
1268 cmdpath = procutil.findexe(editorbin)
1269 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1269 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1270 _(" No commit editor set and can't find %s in PATH\n"
1270 _(" No commit editor set and can't find %s in PATH\n"
1271 " (specify a commit editor in your configuration"
1271 " (specify a commit editor in your configuration"
1272 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1272 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1273 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1273 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1274 _(" Can't find editor '%s' in PATH\n"
1274 _(" Can't find editor '%s' in PATH\n"
1275 " (specify a commit editor in your configuration"
1275 " (specify a commit editor in your configuration"
1276 " file)\n"), not cmdpath and editorbin)
1276 " file)\n"), not cmdpath and editorbin)
1277 if not cmdpath and editor != 'vi':
1277 if not cmdpath and editor != 'vi':
1278 problems += 1
1278 problems += 1
1279
1279
1280 # check username
1280 # check username
1281 username = None
1281 username = None
1282 err = None
1282 err = None
1283 try:
1283 try:
1284 username = ui.username()
1284 username = ui.username()
1285 except error.Abort as e:
1285 except error.Abort as e:
1286 err = stringutil.forcebytestr(e)
1286 err = stringutil.forcebytestr(e)
1287 problems += 1
1287 problems += 1
1288
1288
1289 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1289 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1290 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1290 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1291 " (specify a username in your configuration file)\n"), err)
1291 " (specify a username in your configuration file)\n"), err)
1292
1292
1293 fm.condwrite(not problems, '',
1293 fm.condwrite(not problems, '',
1294 _("no problems detected\n"))
1294 _("no problems detected\n"))
1295 if not problems:
1295 if not problems:
1296 fm.data(problems=problems)
1296 fm.data(problems=problems)
1297 fm.condwrite(problems, 'problems',
1297 fm.condwrite(problems, 'problems',
1298 _("%d problems detected,"
1298 _("%d problems detected,"
1299 " please check your install!\n"), problems)
1299 " please check your install!\n"), problems)
1300 fm.end()
1300 fm.end()
1301
1301
1302 return problems
1302 return problems
1303
1303
1304 @command('debugknown', [], _('REPO ID...'), norepo=True)
1304 @command('debugknown', [], _('REPO ID...'), norepo=True)
1305 def debugknown(ui, repopath, *ids, **opts):
1305 def debugknown(ui, repopath, *ids, **opts):
1306 """test whether node ids are known to a repo
1306 """test whether node ids are known to a repo
1307
1307
1308 Every ID must be a full-length hex node id string. Returns a list of 0s
1308 Every ID must be a full-length hex node id string. Returns a list of 0s
1309 and 1s indicating unknown/known.
1309 and 1s indicating unknown/known.
1310 """
1310 """
1311 opts = pycompat.byteskwargs(opts)
1311 opts = pycompat.byteskwargs(opts)
1312 repo = hg.peer(ui, opts, repopath)
1312 repo = hg.peer(ui, opts, repopath)
1313 if not repo.capable('known'):
1313 if not repo.capable('known'):
1314 raise error.Abort("known() not supported by target repository")
1314 raise error.Abort("known() not supported by target repository")
1315 flags = repo.known([bin(s) for s in ids])
1315 flags = repo.known([bin(s) for s in ids])
1316 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1316 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1317
1317
1318 @command('debuglabelcomplete', [], _('LABEL...'))
1318 @command('debuglabelcomplete', [], _('LABEL...'))
1319 def debuglabelcomplete(ui, repo, *args):
1319 def debuglabelcomplete(ui, repo, *args):
1320 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1320 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1321 debugnamecomplete(ui, repo, *args)
1321 debugnamecomplete(ui, repo, *args)
1322
1322
1323 @command('debuglocks',
1323 @command('debuglocks',
1324 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1324 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1325 ('W', 'force-wlock', None,
1325 ('W', 'force-wlock', None,
1326 _('free the working state lock (DANGEROUS)')),
1326 _('free the working state lock (DANGEROUS)')),
1327 ('s', 'set-lock', None, _('set the store lock until stopped')),
1327 ('s', 'set-lock', None, _('set the store lock until stopped')),
1328 ('S', 'set-wlock', None,
1328 ('S', 'set-wlock', None,
1329 _('set the working state lock until stopped'))],
1329 _('set the working state lock until stopped'))],
1330 _('[OPTION]...'))
1330 _('[OPTION]...'))
1331 def debuglocks(ui, repo, **opts):
1331 def debuglocks(ui, repo, **opts):
1332 """show or modify state of locks
1332 """show or modify state of locks
1333
1333
1334 By default, this command will show which locks are held. This
1334 By default, this command will show which locks are held. This
1335 includes the user and process holding the lock, the amount of time
1335 includes the user and process holding the lock, the amount of time
1336 the lock has been held, and the machine name where the process is
1336 the lock has been held, and the machine name where the process is
1337 running if it's not local.
1337 running if it's not local.
1338
1338
1339 Locks protect the integrity of Mercurial's data, so should be
1339 Locks protect the integrity of Mercurial's data, so should be
1340 treated with care. System crashes or other interruptions may cause
1340 treated with care. System crashes or other interruptions may cause
1341 locks to not be properly released, though Mercurial will usually
1341 locks to not be properly released, though Mercurial will usually
1342 detect and remove such stale locks automatically.
1342 detect and remove such stale locks automatically.
1343
1343
1344 However, detecting stale locks may not always be possible (for
1344 However, detecting stale locks may not always be possible (for
1345 instance, on a shared filesystem). Removing locks may also be
1345 instance, on a shared filesystem). Removing locks may also be
1346 blocked by filesystem permissions.
1346 blocked by filesystem permissions.
1347
1347
1348 Setting a lock will prevent other commands from changing the data.
1348 Setting a lock will prevent other commands from changing the data.
1349 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1349 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1350 The set locks are removed when the command exits.
1350 The set locks are removed when the command exits.
1351
1351
1352 Returns 0 if no locks are held.
1352 Returns 0 if no locks are held.
1353
1353
1354 """
1354 """
1355
1355
1356 if opts.get(r'force_lock'):
1356 if opts.get(r'force_lock'):
1357 repo.svfs.unlink('lock')
1357 repo.svfs.unlink('lock')
1358 if opts.get(r'force_wlock'):
1358 if opts.get(r'force_wlock'):
1359 repo.vfs.unlink('wlock')
1359 repo.vfs.unlink('wlock')
1360 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1360 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1361 return 0
1361 return 0
1362
1362
1363 locks = []
1363 locks = []
1364 try:
1364 try:
1365 if opts.get(r'set_wlock'):
1365 if opts.get(r'set_wlock'):
1366 try:
1366 try:
1367 locks.append(repo.wlock(False))
1367 locks.append(repo.wlock(False))
1368 except error.LockHeld:
1368 except error.LockHeld:
1369 raise error.Abort(_('wlock is already held'))
1369 raise error.Abort(_('wlock is already held'))
1370 if opts.get(r'set_lock'):
1370 if opts.get(r'set_lock'):
1371 try:
1371 try:
1372 locks.append(repo.lock(False))
1372 locks.append(repo.lock(False))
1373 except error.LockHeld:
1373 except error.LockHeld:
1374 raise error.Abort(_('lock is already held'))
1374 raise error.Abort(_('lock is already held'))
1375 if len(locks):
1375 if len(locks):
1376 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1376 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1377 return 0
1377 return 0
1378 finally:
1378 finally:
1379 release(*locks)
1379 release(*locks)
1380
1380
1381 now = time.time()
1381 now = time.time()
1382 held = 0
1382 held = 0
1383
1383
1384 def report(vfs, name, method):
1384 def report(vfs, name, method):
1385 # this causes stale locks to get reaped for more accurate reporting
1385 # this causes stale locks to get reaped for more accurate reporting
1386 try:
1386 try:
1387 l = method(False)
1387 l = method(False)
1388 except error.LockHeld:
1388 except error.LockHeld:
1389 l = None
1389 l = None
1390
1390
1391 if l:
1391 if l:
1392 l.release()
1392 l.release()
1393 else:
1393 else:
1394 try:
1394 try:
1395 st = vfs.lstat(name)
1395 st = vfs.lstat(name)
1396 age = now - st[stat.ST_MTIME]
1396 age = now - st[stat.ST_MTIME]
1397 user = util.username(st.st_uid)
1397 user = util.username(st.st_uid)
1398 locker = vfs.readlock(name)
1398 locker = vfs.readlock(name)
1399 if ":" in locker:
1399 if ":" in locker:
1400 host, pid = locker.split(':')
1400 host, pid = locker.split(':')
1401 if host == socket.gethostname():
1401 if host == socket.gethostname():
1402 locker = 'user %s, process %s' % (user, pid)
1402 locker = 'user %s, process %s' % (user, pid)
1403 else:
1403 else:
1404 locker = 'user %s, process %s, host %s' \
1404 locker = 'user %s, process %s, host %s' \
1405 % (user, pid, host)
1405 % (user, pid, host)
1406 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1406 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1407 return 1
1407 return 1
1408 except OSError as e:
1408 except OSError as e:
1409 if e.errno != errno.ENOENT:
1409 if e.errno != errno.ENOENT:
1410 raise
1410 raise
1411
1411
1412 ui.write(("%-6s free\n") % (name + ":"))
1412 ui.write(("%-6s free\n") % (name + ":"))
1413 return 0
1413 return 0
1414
1414
1415 held += report(repo.svfs, "lock", repo.lock)
1415 held += report(repo.svfs, "lock", repo.lock)
1416 held += report(repo.vfs, "wlock", repo.wlock)
1416 held += report(repo.vfs, "wlock", repo.wlock)
1417
1417
1418 return held
1418 return held
1419
1419
1420 @command('debugmergestate', [], '')
1420 @command('debugmergestate', [], '')
1421 def debugmergestate(ui, repo, *args):
1421 def debugmergestate(ui, repo, *args):
1422 """print merge state
1422 """print merge state
1423
1423
1424 Use --verbose to print out information about whether v1 or v2 merge state
1424 Use --verbose to print out information about whether v1 or v2 merge state
1425 was chosen."""
1425 was chosen."""
1426 def _hashornull(h):
1426 def _hashornull(h):
1427 if h == nullhex:
1427 if h == nullhex:
1428 return 'null'
1428 return 'null'
1429 else:
1429 else:
1430 return h
1430 return h
1431
1431
1432 def printrecords(version):
1432 def printrecords(version):
1433 ui.write(('* version %d records\n') % version)
1433 ui.write(('* version %d records\n') % version)
1434 if version == 1:
1434 if version == 1:
1435 records = v1records
1435 records = v1records
1436 else:
1436 else:
1437 records = v2records
1437 records = v2records
1438
1438
1439 for rtype, record in records:
1439 for rtype, record in records:
1440 # pretty print some record types
1440 # pretty print some record types
1441 if rtype == 'L':
1441 if rtype == 'L':
1442 ui.write(('local: %s\n') % record)
1442 ui.write(('local: %s\n') % record)
1443 elif rtype == 'O':
1443 elif rtype == 'O':
1444 ui.write(('other: %s\n') % record)
1444 ui.write(('other: %s\n') % record)
1445 elif rtype == 'm':
1445 elif rtype == 'm':
1446 driver, mdstate = record.split('\0', 1)
1446 driver, mdstate = record.split('\0', 1)
1447 ui.write(('merge driver: %s (state "%s")\n')
1447 ui.write(('merge driver: %s (state "%s")\n')
1448 % (driver, mdstate))
1448 % (driver, mdstate))
1449 elif rtype in 'FDC':
1449 elif rtype in 'FDC':
1450 r = record.split('\0')
1450 r = record.split('\0')
1451 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1451 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1452 if version == 1:
1452 if version == 1:
1453 onode = 'not stored in v1 format'
1453 onode = 'not stored in v1 format'
1454 flags = r[7]
1454 flags = r[7]
1455 else:
1455 else:
1456 onode, flags = r[7:9]
1456 onode, flags = r[7:9]
1457 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1457 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1458 % (f, rtype, state, _hashornull(hash)))
1458 % (f, rtype, state, _hashornull(hash)))
1459 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1459 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1460 ui.write((' ancestor path: %s (node %s)\n')
1460 ui.write((' ancestor path: %s (node %s)\n')
1461 % (afile, _hashornull(anode)))
1461 % (afile, _hashornull(anode)))
1462 ui.write((' other path: %s (node %s)\n')
1462 ui.write((' other path: %s (node %s)\n')
1463 % (ofile, _hashornull(onode)))
1463 % (ofile, _hashornull(onode)))
1464 elif rtype == 'f':
1464 elif rtype == 'f':
1465 filename, rawextras = record.split('\0', 1)
1465 filename, rawextras = record.split('\0', 1)
1466 extras = rawextras.split('\0')
1466 extras = rawextras.split('\0')
1467 i = 0
1467 i = 0
1468 extrastrings = []
1468 extrastrings = []
1469 while i < len(extras):
1469 while i < len(extras):
1470 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1470 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1471 i += 2
1471 i += 2
1472
1472
1473 ui.write(('file extras: %s (%s)\n')
1473 ui.write(('file extras: %s (%s)\n')
1474 % (filename, ', '.join(extrastrings)))
1474 % (filename, ', '.join(extrastrings)))
1475 elif rtype == 'l':
1475 elif rtype == 'l':
1476 labels = record.split('\0', 2)
1476 labels = record.split('\0', 2)
1477 labels = [l for l in labels if len(l) > 0]
1477 labels = [l for l in labels if len(l) > 0]
1478 ui.write(('labels:\n'))
1478 ui.write(('labels:\n'))
1479 ui.write((' local: %s\n' % labels[0]))
1479 ui.write((' local: %s\n' % labels[0]))
1480 ui.write((' other: %s\n' % labels[1]))
1480 ui.write((' other: %s\n' % labels[1]))
1481 if len(labels) > 2:
1481 if len(labels) > 2:
1482 ui.write((' base: %s\n' % labels[2]))
1482 ui.write((' base: %s\n' % labels[2]))
1483 else:
1483 else:
1484 ui.write(('unrecognized entry: %s\t%s\n')
1484 ui.write(('unrecognized entry: %s\t%s\n')
1485 % (rtype, record.replace('\0', '\t')))
1485 % (rtype, record.replace('\0', '\t')))
1486
1486
1487 # Avoid mergestate.read() since it may raise an exception for unsupported
1487 # Avoid mergestate.read() since it may raise an exception for unsupported
1488 # merge state records. We shouldn't be doing this, but this is OK since this
1488 # merge state records. We shouldn't be doing this, but this is OK since this
1489 # command is pretty low-level.
1489 # command is pretty low-level.
1490 ms = mergemod.mergestate(repo)
1490 ms = mergemod.mergestate(repo)
1491
1491
1492 # sort so that reasonable information is on top
1492 # sort so that reasonable information is on top
1493 v1records = ms._readrecordsv1()
1493 v1records = ms._readrecordsv1()
1494 v2records = ms._readrecordsv2()
1494 v2records = ms._readrecordsv2()
1495 order = 'LOml'
1495 order = 'LOml'
1496 def key(r):
1496 def key(r):
1497 idx = order.find(r[0])
1497 idx = order.find(r[0])
1498 if idx == -1:
1498 if idx == -1:
1499 return (1, r[1])
1499 return (1, r[1])
1500 else:
1500 else:
1501 return (0, idx)
1501 return (0, idx)
1502 v1records.sort(key=key)
1502 v1records.sort(key=key)
1503 v2records.sort(key=key)
1503 v2records.sort(key=key)
1504
1504
1505 if not v1records and not v2records:
1505 if not v1records and not v2records:
1506 ui.write(('no merge state found\n'))
1506 ui.write(('no merge state found\n'))
1507 elif not v2records:
1507 elif not v2records:
1508 ui.note(('no version 2 merge state\n'))
1508 ui.note(('no version 2 merge state\n'))
1509 printrecords(1)
1509 printrecords(1)
1510 elif ms._v1v2match(v1records, v2records):
1510 elif ms._v1v2match(v1records, v2records):
1511 ui.note(('v1 and v2 states match: using v2\n'))
1511 ui.note(('v1 and v2 states match: using v2\n'))
1512 printrecords(2)
1512 printrecords(2)
1513 else:
1513 else:
1514 ui.note(('v1 and v2 states mismatch: using v1\n'))
1514 ui.note(('v1 and v2 states mismatch: using v1\n'))
1515 printrecords(1)
1515 printrecords(1)
1516 if ui.verbose:
1516 if ui.verbose:
1517 printrecords(2)
1517 printrecords(2)
1518
1518
1519 @command('debugnamecomplete', [], _('NAME...'))
1519 @command('debugnamecomplete', [], _('NAME...'))
1520 def debugnamecomplete(ui, repo, *args):
1520 def debugnamecomplete(ui, repo, *args):
1521 '''complete "names" - tags, open branch names, bookmark names'''
1521 '''complete "names" - tags, open branch names, bookmark names'''
1522
1522
1523 names = set()
1523 names = set()
1524 # since we previously only listed open branches, we will handle that
1524 # since we previously only listed open branches, we will handle that
1525 # specially (after this for loop)
1525 # specially (after this for loop)
1526 for name, ns in repo.names.iteritems():
1526 for name, ns in repo.names.iteritems():
1527 if name != 'branches':
1527 if name != 'branches':
1528 names.update(ns.listnames(repo))
1528 names.update(ns.listnames(repo))
1529 names.update(tag for (tag, heads, tip, closed)
1529 names.update(tag for (tag, heads, tip, closed)
1530 in repo.branchmap().iterbranches() if not closed)
1530 in repo.branchmap().iterbranches() if not closed)
1531 completions = set()
1531 completions = set()
1532 if not args:
1532 if not args:
1533 args = ['']
1533 args = ['']
1534 for a in args:
1534 for a in args:
1535 completions.update(n for n in names if n.startswith(a))
1535 completions.update(n for n in names if n.startswith(a))
1536 ui.write('\n'.join(sorted(completions)))
1536 ui.write('\n'.join(sorted(completions)))
1537 ui.write('\n')
1537 ui.write('\n')
1538
1538
1539 @command('debugobsolete',
1539 @command('debugobsolete',
1540 [('', 'flags', 0, _('markers flag')),
1540 [('', 'flags', 0, _('markers flag')),
1541 ('', 'record-parents', False,
1541 ('', 'record-parents', False,
1542 _('record parent information for the precursor')),
1542 _('record parent information for the precursor')),
1543 ('r', 'rev', [], _('display markers relevant to REV')),
1543 ('r', 'rev', [], _('display markers relevant to REV')),
1544 ('', 'exclusive', False, _('restrict display to markers only '
1544 ('', 'exclusive', False, _('restrict display to markers only '
1545 'relevant to REV')),
1545 'relevant to REV')),
1546 ('', 'index', False, _('display index of the marker')),
1546 ('', 'index', False, _('display index of the marker')),
1547 ('', 'delete', [], _('delete markers specified by indices')),
1547 ('', 'delete', [], _('delete markers specified by indices')),
1548 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1548 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1549 _('[OBSOLETED [REPLACEMENT ...]]'))
1549 _('[OBSOLETED [REPLACEMENT ...]]'))
1550 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1550 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1551 """create arbitrary obsolete marker
1551 """create arbitrary obsolete marker
1552
1552
1553 With no arguments, displays the list of obsolescence markers."""
1553 With no arguments, displays the list of obsolescence markers."""
1554
1554
1555 opts = pycompat.byteskwargs(opts)
1555 opts = pycompat.byteskwargs(opts)
1556
1556
1557 def parsenodeid(s):
1557 def parsenodeid(s):
1558 try:
1558 try:
1559 # We do not use revsingle/revrange functions here to accept
1559 # We do not use revsingle/revrange functions here to accept
1560 # arbitrary node identifiers, possibly not present in the
1560 # arbitrary node identifiers, possibly not present in the
1561 # local repository.
1561 # local repository.
1562 n = bin(s)
1562 n = bin(s)
1563 if len(n) != len(nullid):
1563 if len(n) != len(nullid):
1564 raise TypeError()
1564 raise TypeError()
1565 return n
1565 return n
1566 except TypeError:
1566 except TypeError:
1567 raise error.Abort('changeset references must be full hexadecimal '
1567 raise error.Abort('changeset references must be full hexadecimal '
1568 'node identifiers')
1568 'node identifiers')
1569
1569
1570 if opts.get('delete'):
1570 if opts.get('delete'):
1571 indices = []
1571 indices = []
1572 for v in opts.get('delete'):
1572 for v in opts.get('delete'):
1573 try:
1573 try:
1574 indices.append(int(v))
1574 indices.append(int(v))
1575 except ValueError:
1575 except ValueError:
1576 raise error.Abort(_('invalid index value: %r') % v,
1576 raise error.Abort(_('invalid index value: %r') % v,
1577 hint=_('use integers for indices'))
1577 hint=_('use integers for indices'))
1578
1578
1579 if repo.currenttransaction():
1579 if repo.currenttransaction():
1580 raise error.Abort(_('cannot delete obsmarkers in the middle '
1580 raise error.Abort(_('cannot delete obsmarkers in the middle '
1581 'of transaction.'))
1581 'of transaction.'))
1582
1582
1583 with repo.lock():
1583 with repo.lock():
1584 n = repair.deleteobsmarkers(repo.obsstore, indices)
1584 n = repair.deleteobsmarkers(repo.obsstore, indices)
1585 ui.write(_('deleted %i obsolescence markers\n') % n)
1585 ui.write(_('deleted %i obsolescence markers\n') % n)
1586
1586
1587 return
1587 return
1588
1588
1589 if precursor is not None:
1589 if precursor is not None:
1590 if opts['rev']:
1590 if opts['rev']:
1591 raise error.Abort('cannot select revision when creating marker')
1591 raise error.Abort('cannot select revision when creating marker')
1592 metadata = {}
1592 metadata = {}
1593 metadata['user'] = opts['user'] or ui.username()
1593 metadata['user'] = opts['user'] or ui.username()
1594 succs = tuple(parsenodeid(succ) for succ in successors)
1594 succs = tuple(parsenodeid(succ) for succ in successors)
1595 l = repo.lock()
1595 l = repo.lock()
1596 try:
1596 try:
1597 tr = repo.transaction('debugobsolete')
1597 tr = repo.transaction('debugobsolete')
1598 try:
1598 try:
1599 date = opts.get('date')
1599 date = opts.get('date')
1600 if date:
1600 if date:
1601 date = dateutil.parsedate(date)
1601 date = dateutil.parsedate(date)
1602 else:
1602 else:
1603 date = None
1603 date = None
1604 prec = parsenodeid(precursor)
1604 prec = parsenodeid(precursor)
1605 parents = None
1605 parents = None
1606 if opts['record_parents']:
1606 if opts['record_parents']:
1607 if prec not in repo.unfiltered():
1607 if prec not in repo.unfiltered():
1608 raise error.Abort('cannot used --record-parents on '
1608 raise error.Abort('cannot used --record-parents on '
1609 'unknown changesets')
1609 'unknown changesets')
1610 parents = repo.unfiltered()[prec].parents()
1610 parents = repo.unfiltered()[prec].parents()
1611 parents = tuple(p.node() for p in parents)
1611 parents = tuple(p.node() for p in parents)
1612 repo.obsstore.create(tr, prec, succs, opts['flags'],
1612 repo.obsstore.create(tr, prec, succs, opts['flags'],
1613 parents=parents, date=date,
1613 parents=parents, date=date,
1614 metadata=metadata, ui=ui)
1614 metadata=metadata, ui=ui)
1615 tr.close()
1615 tr.close()
1616 except ValueError as exc:
1616 except ValueError as exc:
1617 raise error.Abort(_('bad obsmarker input: %s') %
1617 raise error.Abort(_('bad obsmarker input: %s') %
1618 pycompat.bytestr(exc))
1618 pycompat.bytestr(exc))
1619 finally:
1619 finally:
1620 tr.release()
1620 tr.release()
1621 finally:
1621 finally:
1622 l.release()
1622 l.release()
1623 else:
1623 else:
1624 if opts['rev']:
1624 if opts['rev']:
1625 revs = scmutil.revrange(repo, opts['rev'])
1625 revs = scmutil.revrange(repo, opts['rev'])
1626 nodes = [repo[r].node() for r in revs]
1626 nodes = [repo[r].node() for r in revs]
1627 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1627 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1628 exclusive=opts['exclusive']))
1628 exclusive=opts['exclusive']))
1629 markers.sort(key=lambda x: x._data)
1629 markers.sort(key=lambda x: x._data)
1630 else:
1630 else:
1631 markers = obsutil.getmarkers(repo)
1631 markers = obsutil.getmarkers(repo)
1632
1632
1633 markerstoiter = markers
1633 markerstoiter = markers
1634 isrelevant = lambda m: True
1634 isrelevant = lambda m: True
1635 if opts.get('rev') and opts.get('index'):
1635 if opts.get('rev') and opts.get('index'):
1636 markerstoiter = obsutil.getmarkers(repo)
1636 markerstoiter = obsutil.getmarkers(repo)
1637 markerset = set(markers)
1637 markerset = set(markers)
1638 isrelevant = lambda m: m in markerset
1638 isrelevant = lambda m: m in markerset
1639
1639
1640 fm = ui.formatter('debugobsolete', opts)
1640 fm = ui.formatter('debugobsolete', opts)
1641 for i, m in enumerate(markerstoiter):
1641 for i, m in enumerate(markerstoiter):
1642 if not isrelevant(m):
1642 if not isrelevant(m):
1643 # marker can be irrelevant when we're iterating over a set
1643 # marker can be irrelevant when we're iterating over a set
1644 # of markers (markerstoiter) which is bigger than the set
1644 # of markers (markerstoiter) which is bigger than the set
1645 # of markers we want to display (markers)
1645 # of markers we want to display (markers)
1646 # this can happen if both --index and --rev options are
1646 # this can happen if both --index and --rev options are
1647 # provided and thus we need to iterate over all of the markers
1647 # provided and thus we need to iterate over all of the markers
1648 # to get the correct indices, but only display the ones that
1648 # to get the correct indices, but only display the ones that
1649 # are relevant to --rev value
1649 # are relevant to --rev value
1650 continue
1650 continue
1651 fm.startitem()
1651 fm.startitem()
1652 ind = i if opts.get('index') else None
1652 ind = i if opts.get('index') else None
1653 cmdutil.showmarker(fm, m, index=ind)
1653 cmdutil.showmarker(fm, m, index=ind)
1654 fm.end()
1654 fm.end()
1655
1655
1656 @command('debugpathcomplete',
1656 @command('debugpathcomplete',
1657 [('f', 'full', None, _('complete an entire path')),
1657 [('f', 'full', None, _('complete an entire path')),
1658 ('n', 'normal', None, _('show only normal files')),
1658 ('n', 'normal', None, _('show only normal files')),
1659 ('a', 'added', None, _('show only added files')),
1659 ('a', 'added', None, _('show only added files')),
1660 ('r', 'removed', None, _('show only removed files'))],
1660 ('r', 'removed', None, _('show only removed files'))],
1661 _('FILESPEC...'))
1661 _('FILESPEC...'))
1662 def debugpathcomplete(ui, repo, *specs, **opts):
1662 def debugpathcomplete(ui, repo, *specs, **opts):
1663 '''complete part or all of a tracked path
1663 '''complete part or all of a tracked path
1664
1664
1665 This command supports shells that offer path name completion. It
1665 This command supports shells that offer path name completion. It
1666 currently completes only files already known to the dirstate.
1666 currently completes only files already known to the dirstate.
1667
1667
1668 Completion extends only to the next path segment unless
1668 Completion extends only to the next path segment unless
1669 --full is specified, in which case entire paths are used.'''
1669 --full is specified, in which case entire paths are used.'''
1670
1670
1671 def complete(path, acceptable):
1671 def complete(path, acceptable):
1672 dirstate = repo.dirstate
1672 dirstate = repo.dirstate
1673 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1673 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1674 rootdir = repo.root + pycompat.ossep
1674 rootdir = repo.root + pycompat.ossep
1675 if spec != repo.root and not spec.startswith(rootdir):
1675 if spec != repo.root and not spec.startswith(rootdir):
1676 return [], []
1676 return [], []
1677 if os.path.isdir(spec):
1677 if os.path.isdir(spec):
1678 spec += '/'
1678 spec += '/'
1679 spec = spec[len(rootdir):]
1679 spec = spec[len(rootdir):]
1680 fixpaths = pycompat.ossep != '/'
1680 fixpaths = pycompat.ossep != '/'
1681 if fixpaths:
1681 if fixpaths:
1682 spec = spec.replace(pycompat.ossep, '/')
1682 spec = spec.replace(pycompat.ossep, '/')
1683 speclen = len(spec)
1683 speclen = len(spec)
1684 fullpaths = opts[r'full']
1684 fullpaths = opts[r'full']
1685 files, dirs = set(), set()
1685 files, dirs = set(), set()
1686 adddir, addfile = dirs.add, files.add
1686 adddir, addfile = dirs.add, files.add
1687 for f, st in dirstate.iteritems():
1687 for f, st in dirstate.iteritems():
1688 if f.startswith(spec) and st[0] in acceptable:
1688 if f.startswith(spec) and st[0] in acceptable:
1689 if fixpaths:
1689 if fixpaths:
1690 f = f.replace('/', pycompat.ossep)
1690 f = f.replace('/', pycompat.ossep)
1691 if fullpaths:
1691 if fullpaths:
1692 addfile(f)
1692 addfile(f)
1693 continue
1693 continue
1694 s = f.find(pycompat.ossep, speclen)
1694 s = f.find(pycompat.ossep, speclen)
1695 if s >= 0:
1695 if s >= 0:
1696 adddir(f[:s])
1696 adddir(f[:s])
1697 else:
1697 else:
1698 addfile(f)
1698 addfile(f)
1699 return files, dirs
1699 return files, dirs
1700
1700
1701 acceptable = ''
1701 acceptable = ''
1702 if opts[r'normal']:
1702 if opts[r'normal']:
1703 acceptable += 'nm'
1703 acceptable += 'nm'
1704 if opts[r'added']:
1704 if opts[r'added']:
1705 acceptable += 'a'
1705 acceptable += 'a'
1706 if opts[r'removed']:
1706 if opts[r'removed']:
1707 acceptable += 'r'
1707 acceptable += 'r'
1708 cwd = repo.getcwd()
1708 cwd = repo.getcwd()
1709 if not specs:
1709 if not specs:
1710 specs = ['.']
1710 specs = ['.']
1711
1711
1712 files, dirs = set(), set()
1712 files, dirs = set(), set()
1713 for spec in specs:
1713 for spec in specs:
1714 f, d = complete(spec, acceptable or 'nmar')
1714 f, d = complete(spec, acceptable or 'nmar')
1715 files.update(f)
1715 files.update(f)
1716 dirs.update(d)
1716 dirs.update(d)
1717 files.update(dirs)
1717 files.update(dirs)
1718 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1718 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1719 ui.write('\n')
1719 ui.write('\n')
1720
1720
1721 @command('debugpeer', [], _('PATH'), norepo=True)
1721 @command('debugpeer', [], _('PATH'), norepo=True)
1722 def debugpeer(ui, path):
1722 def debugpeer(ui, path):
1723 """establish a connection to a peer repository"""
1723 """establish a connection to a peer repository"""
1724 # Always enable peer request logging. Requires --debug to display
1724 # Always enable peer request logging. Requires --debug to display
1725 # though.
1725 # though.
1726 overrides = {
1726 overrides = {
1727 ('devel', 'debug.peer-request'): True,
1727 ('devel', 'debug.peer-request'): True,
1728 }
1728 }
1729
1729
1730 with ui.configoverride(overrides):
1730 with ui.configoverride(overrides):
1731 peer = hg.peer(ui, {}, path)
1731 peer = hg.peer(ui, {}, path)
1732
1732
1733 local = peer.local() is not None
1733 local = peer.local() is not None
1734 canpush = peer.canpush()
1734 canpush = peer.canpush()
1735
1735
1736 ui.write(_('url: %s\n') % peer.url())
1736 ui.write(_('url: %s\n') % peer.url())
1737 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1737 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1738 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1738 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1739
1739
1740 @command('debugpickmergetool',
1740 @command('debugpickmergetool',
1741 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1741 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1742 ('', 'changedelete', None, _('emulate merging change and delete')),
1742 ('', 'changedelete', None, _('emulate merging change and delete')),
1743 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1743 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1744 _('[PATTERN]...'),
1744 _('[PATTERN]...'),
1745 inferrepo=True)
1745 inferrepo=True)
1746 def debugpickmergetool(ui, repo, *pats, **opts):
1746 def debugpickmergetool(ui, repo, *pats, **opts):
1747 """examine which merge tool is chosen for specified file
1747 """examine which merge tool is chosen for specified file
1748
1748
1749 As described in :hg:`help merge-tools`, Mercurial examines
1749 As described in :hg:`help merge-tools`, Mercurial examines
1750 configurations below in this order to decide which merge tool is
1750 configurations below in this order to decide which merge tool is
1751 chosen for specified file.
1751 chosen for specified file.
1752
1752
1753 1. ``--tool`` option
1753 1. ``--tool`` option
1754 2. ``HGMERGE`` environment variable
1754 2. ``HGMERGE`` environment variable
1755 3. configurations in ``merge-patterns`` section
1755 3. configurations in ``merge-patterns`` section
1756 4. configuration of ``ui.merge``
1756 4. configuration of ``ui.merge``
1757 5. configurations in ``merge-tools`` section
1757 5. configurations in ``merge-tools`` section
1758 6. ``hgmerge`` tool (for historical reason only)
1758 6. ``hgmerge`` tool (for historical reason only)
1759 7. default tool for fallback (``:merge`` or ``:prompt``)
1759 7. default tool for fallback (``:merge`` or ``:prompt``)
1760
1760
1761 This command writes out examination result in the style below::
1761 This command writes out examination result in the style below::
1762
1762
1763 FILE = MERGETOOL
1763 FILE = MERGETOOL
1764
1764
1765 By default, all files known in the first parent context of the
1765 By default, all files known in the first parent context of the
1766 working directory are examined. Use file patterns and/or -I/-X
1766 working directory are examined. Use file patterns and/or -I/-X
1767 options to limit target files. -r/--rev is also useful to examine
1767 options to limit target files. -r/--rev is also useful to examine
1768 files in another context without actual updating to it.
1768 files in another context without actual updating to it.
1769
1769
1770 With --debug, this command shows warning messages while matching
1770 With --debug, this command shows warning messages while matching
1771 against ``merge-patterns`` and so on, too. It is recommended to
1771 against ``merge-patterns`` and so on, too. It is recommended to
1772 use this option with explicit file patterns and/or -I/-X options,
1772 use this option with explicit file patterns and/or -I/-X options,
1773 because this option increases amount of output per file according
1773 because this option increases amount of output per file according
1774 to configurations in hgrc.
1774 to configurations in hgrc.
1775
1775
1776 With -v/--verbose, this command shows configurations below at
1776 With -v/--verbose, this command shows configurations below at
1777 first (only if specified).
1777 first (only if specified).
1778
1778
1779 - ``--tool`` option
1779 - ``--tool`` option
1780 - ``HGMERGE`` environment variable
1780 - ``HGMERGE`` environment variable
1781 - configuration of ``ui.merge``
1781 - configuration of ``ui.merge``
1782
1782
1783 If merge tool is chosen before matching against
1783 If merge tool is chosen before matching against
1784 ``merge-patterns``, this command can't show any helpful
1784 ``merge-patterns``, this command can't show any helpful
1785 information, even with --debug. In such case, information above is
1785 information, even with --debug. In such case, information above is
1786 useful to know why a merge tool is chosen.
1786 useful to know why a merge tool is chosen.
1787 """
1787 """
1788 opts = pycompat.byteskwargs(opts)
1788 opts = pycompat.byteskwargs(opts)
1789 overrides = {}
1789 overrides = {}
1790 if opts['tool']:
1790 if opts['tool']:
1791 overrides[('ui', 'forcemerge')] = opts['tool']
1791 overrides[('ui', 'forcemerge')] = opts['tool']
1792 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1792 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1793
1793
1794 with ui.configoverride(overrides, 'debugmergepatterns'):
1794 with ui.configoverride(overrides, 'debugmergepatterns'):
1795 hgmerge = encoding.environ.get("HGMERGE")
1795 hgmerge = encoding.environ.get("HGMERGE")
1796 if hgmerge is not None:
1796 if hgmerge is not None:
1797 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1797 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1798 uimerge = ui.config("ui", "merge")
1798 uimerge = ui.config("ui", "merge")
1799 if uimerge:
1799 if uimerge:
1800 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1800 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1801
1801
1802 ctx = scmutil.revsingle(repo, opts.get('rev'))
1802 ctx = scmutil.revsingle(repo, opts.get('rev'))
1803 m = scmutil.match(ctx, pats, opts)
1803 m = scmutil.match(ctx, pats, opts)
1804 changedelete = opts['changedelete']
1804 changedelete = opts['changedelete']
1805 for path in ctx.walk(m):
1805 for path in ctx.walk(m):
1806 fctx = ctx[path]
1806 fctx = ctx[path]
1807 try:
1807 try:
1808 if not ui.debugflag:
1808 if not ui.debugflag:
1809 ui.pushbuffer(error=True)
1809 ui.pushbuffer(error=True)
1810 tool, toolpath = filemerge._picktool(repo, ui, path,
1810 tool, toolpath = filemerge._picktool(repo, ui, path,
1811 fctx.isbinary(),
1811 fctx.isbinary(),
1812 'l' in fctx.flags(),
1812 'l' in fctx.flags(),
1813 changedelete)
1813 changedelete)
1814 finally:
1814 finally:
1815 if not ui.debugflag:
1815 if not ui.debugflag:
1816 ui.popbuffer()
1816 ui.popbuffer()
1817 ui.write(('%s = %s\n') % (path, tool))
1817 ui.write(('%s = %s\n') % (path, tool))
1818
1818
1819 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1819 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1820 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1820 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1821 '''access the pushkey key/value protocol
1821 '''access the pushkey key/value protocol
1822
1822
1823 With two args, list the keys in the given namespace.
1823 With two args, list the keys in the given namespace.
1824
1824
1825 With five args, set a key to new if it currently is set to old.
1825 With five args, set a key to new if it currently is set to old.
1826 Reports success or failure.
1826 Reports success or failure.
1827 '''
1827 '''
1828
1828
1829 target = hg.peer(ui, {}, repopath)
1829 target = hg.peer(ui, {}, repopath)
1830 if keyinfo:
1830 if keyinfo:
1831 key, old, new = keyinfo
1831 key, old, new = keyinfo
1832 r = target.pushkey(namespace, key, old, new)
1832 r = target.pushkey(namespace, key, old, new)
1833 ui.status(pycompat.bytestr(r) + '\n')
1833 ui.status(pycompat.bytestr(r) + '\n')
1834 return not r
1834 return not r
1835 else:
1835 else:
1836 for k, v in sorted(target.listkeys(namespace).iteritems()):
1836 for k, v in sorted(target.listkeys(namespace).iteritems()):
1837 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
1837 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
1838 stringutil.escapestr(v)))
1838 stringutil.escapestr(v)))
1839
1839
1840 @command('debugpvec', [], _('A B'))
1840 @command('debugpvec', [], _('A B'))
1841 def debugpvec(ui, repo, a, b=None):
1841 def debugpvec(ui, repo, a, b=None):
1842 ca = scmutil.revsingle(repo, a)
1842 ca = scmutil.revsingle(repo, a)
1843 cb = scmutil.revsingle(repo, b)
1843 cb = scmutil.revsingle(repo, b)
1844 pa = pvec.ctxpvec(ca)
1844 pa = pvec.ctxpvec(ca)
1845 pb = pvec.ctxpvec(cb)
1845 pb = pvec.ctxpvec(cb)
1846 if pa == pb:
1846 if pa == pb:
1847 rel = "="
1847 rel = "="
1848 elif pa > pb:
1848 elif pa > pb:
1849 rel = ">"
1849 rel = ">"
1850 elif pa < pb:
1850 elif pa < pb:
1851 rel = "<"
1851 rel = "<"
1852 elif pa | pb:
1852 elif pa | pb:
1853 rel = "|"
1853 rel = "|"
1854 ui.write(_("a: %s\n") % pa)
1854 ui.write(_("a: %s\n") % pa)
1855 ui.write(_("b: %s\n") % pb)
1855 ui.write(_("b: %s\n") % pb)
1856 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1856 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1857 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1857 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1858 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1858 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1859 pa.distance(pb), rel))
1859 pa.distance(pb), rel))
1860
1860
1861 @command('debugrebuilddirstate|debugrebuildstate',
1861 @command('debugrebuilddirstate|debugrebuildstate',
1862 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1862 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1863 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1863 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1864 'the working copy parent')),
1864 'the working copy parent')),
1865 ],
1865 ],
1866 _('[-r REV]'))
1866 _('[-r REV]'))
1867 def debugrebuilddirstate(ui, repo, rev, **opts):
1867 def debugrebuilddirstate(ui, repo, rev, **opts):
1868 """rebuild the dirstate as it would look like for the given revision
1868 """rebuild the dirstate as it would look like for the given revision
1869
1869
1870 If no revision is specified the first current parent will be used.
1870 If no revision is specified the first current parent will be used.
1871
1871
1872 The dirstate will be set to the files of the given revision.
1872 The dirstate will be set to the files of the given revision.
1873 The actual working directory content or existing dirstate
1873 The actual working directory content or existing dirstate
1874 information such as adds or removes is not considered.
1874 information such as adds or removes is not considered.
1875
1875
1876 ``minimal`` will only rebuild the dirstate status for files that claim to be
1876 ``minimal`` will only rebuild the dirstate status for files that claim to be
1877 tracked but are not in the parent manifest, or that exist in the parent
1877 tracked but are not in the parent manifest, or that exist in the parent
1878 manifest but are not in the dirstate. It will not change adds, removes, or
1878 manifest but are not in the dirstate. It will not change adds, removes, or
1879 modified files that are in the working copy parent.
1879 modified files that are in the working copy parent.
1880
1880
1881 One use of this command is to make the next :hg:`status` invocation
1881 One use of this command is to make the next :hg:`status` invocation
1882 check the actual file content.
1882 check the actual file content.
1883 """
1883 """
1884 ctx = scmutil.revsingle(repo, rev)
1884 ctx = scmutil.revsingle(repo, rev)
1885 with repo.wlock():
1885 with repo.wlock():
1886 dirstate = repo.dirstate
1886 dirstate = repo.dirstate
1887 changedfiles = None
1887 changedfiles = None
1888 # See command doc for what minimal does.
1888 # See command doc for what minimal does.
1889 if opts.get(r'minimal'):
1889 if opts.get(r'minimal'):
1890 manifestfiles = set(ctx.manifest().keys())
1890 manifestfiles = set(ctx.manifest().keys())
1891 dirstatefiles = set(dirstate)
1891 dirstatefiles = set(dirstate)
1892 manifestonly = manifestfiles - dirstatefiles
1892 manifestonly = manifestfiles - dirstatefiles
1893 dsonly = dirstatefiles - manifestfiles
1893 dsonly = dirstatefiles - manifestfiles
1894 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1894 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1895 changedfiles = manifestonly | dsnotadded
1895 changedfiles = manifestonly | dsnotadded
1896
1896
1897 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1897 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1898
1898
1899 @command('debugrebuildfncache', [], '')
1899 @command('debugrebuildfncache', [], '')
1900 def debugrebuildfncache(ui, repo):
1900 def debugrebuildfncache(ui, repo):
1901 """rebuild the fncache file"""
1901 """rebuild the fncache file"""
1902 repair.rebuildfncache(ui, repo)
1902 repair.rebuildfncache(ui, repo)
1903
1903
1904 @command('debugrename',
1904 @command('debugrename',
1905 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1905 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1906 _('[-r REV] FILE'))
1906 _('[-r REV] FILE'))
1907 def debugrename(ui, repo, file1, *pats, **opts):
1907 def debugrename(ui, repo, file1, *pats, **opts):
1908 """dump rename information"""
1908 """dump rename information"""
1909
1909
1910 opts = pycompat.byteskwargs(opts)
1910 opts = pycompat.byteskwargs(opts)
1911 ctx = scmutil.revsingle(repo, opts.get('rev'))
1911 ctx = scmutil.revsingle(repo, opts.get('rev'))
1912 m = scmutil.match(ctx, (file1,) + pats, opts)
1912 m = scmutil.match(ctx, (file1,) + pats, opts)
1913 for abs in ctx.walk(m):
1913 for abs in ctx.walk(m):
1914 fctx = ctx[abs]
1914 fctx = ctx[abs]
1915 o = fctx.filelog().renamed(fctx.filenode())
1915 o = fctx.filelog().renamed(fctx.filenode())
1916 rel = m.rel(abs)
1916 rel = m.rel(abs)
1917 if o:
1917 if o:
1918 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1918 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1919 else:
1919 else:
1920 ui.write(_("%s not renamed\n") % rel)
1920 ui.write(_("%s not renamed\n") % rel)
1921
1921
1922 @command('debugrevlog', cmdutil.debugrevlogopts +
1922 @command('debugrevlog', cmdutil.debugrevlogopts +
1923 [('d', 'dump', False, _('dump index data'))],
1923 [('d', 'dump', False, _('dump index data'))],
1924 _('-c|-m|FILE'),
1924 _('-c|-m|FILE'),
1925 optionalrepo=True)
1925 optionalrepo=True)
1926 def debugrevlog(ui, repo, file_=None, **opts):
1926 def debugrevlog(ui, repo, file_=None, **opts):
1927 """show data and statistics about a revlog"""
1927 """show data and statistics about a revlog"""
1928 opts = pycompat.byteskwargs(opts)
1928 opts = pycompat.byteskwargs(opts)
1929 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1929 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1930
1930
1931 if opts.get("dump"):
1931 if opts.get("dump"):
1932 numrevs = len(r)
1932 numrevs = len(r)
1933 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1933 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1934 " rawsize totalsize compression heads chainlen\n"))
1934 " rawsize totalsize compression heads chainlen\n"))
1935 ts = 0
1935 ts = 0
1936 heads = set()
1936 heads = set()
1937
1937
1938 for rev in xrange(numrevs):
1938 for rev in xrange(numrevs):
1939 dbase = r.deltaparent(rev)
1939 dbase = r.deltaparent(rev)
1940 if dbase == -1:
1940 if dbase == -1:
1941 dbase = rev
1941 dbase = rev
1942 cbase = r.chainbase(rev)
1942 cbase = r.chainbase(rev)
1943 clen = r.chainlen(rev)
1943 clen = r.chainlen(rev)
1944 p1, p2 = r.parentrevs(rev)
1944 p1, p2 = r.parentrevs(rev)
1945 rs = r.rawsize(rev)
1945 rs = r.rawsize(rev)
1946 ts = ts + rs
1946 ts = ts + rs
1947 heads -= set(r.parentrevs(rev))
1947 heads -= set(r.parentrevs(rev))
1948 heads.add(rev)
1948 heads.add(rev)
1949 try:
1949 try:
1950 compression = ts / r.end(rev)
1950 compression = ts / r.end(rev)
1951 except ZeroDivisionError:
1951 except ZeroDivisionError:
1952 compression = 0
1952 compression = 0
1953 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1953 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1954 "%11d %5d %8d\n" %
1954 "%11d %5d %8d\n" %
1955 (rev, p1, p2, r.start(rev), r.end(rev),
1955 (rev, p1, p2, r.start(rev), r.end(rev),
1956 r.start(dbase), r.start(cbase),
1956 r.start(dbase), r.start(cbase),
1957 r.start(p1), r.start(p2),
1957 r.start(p1), r.start(p2),
1958 rs, ts, compression, len(heads), clen))
1958 rs, ts, compression, len(heads), clen))
1959 return 0
1959 return 0
1960
1960
1961 v = r.version
1961 v = r.version
1962 format = v & 0xFFFF
1962 format = v & 0xFFFF
1963 flags = []
1963 flags = []
1964 gdelta = False
1964 gdelta = False
1965 if v & revlog.FLAG_INLINE_DATA:
1965 if v & revlog.FLAG_INLINE_DATA:
1966 flags.append('inline')
1966 flags.append('inline')
1967 if v & revlog.FLAG_GENERALDELTA:
1967 if v & revlog.FLAG_GENERALDELTA:
1968 gdelta = True
1968 gdelta = True
1969 flags.append('generaldelta')
1969 flags.append('generaldelta')
1970 if not flags:
1970 if not flags:
1971 flags = ['(none)']
1971 flags = ['(none)']
1972
1972
1973 nummerges = 0
1973 nummerges = 0
1974 numfull = 0
1974 numfull = 0
1975 numprev = 0
1975 numprev = 0
1976 nump1 = 0
1976 nump1 = 0
1977 nump2 = 0
1977 nump2 = 0
1978 numother = 0
1978 numother = 0
1979 nump1prev = 0
1979 nump1prev = 0
1980 nump2prev = 0
1980 nump2prev = 0
1981 chainlengths = []
1981 chainlengths = []
1982 chainbases = []
1982 chainbases = []
1983 chainspans = []
1983 chainspans = []
1984
1984
1985 datasize = [None, 0, 0]
1985 datasize = [None, 0, 0]
1986 fullsize = [None, 0, 0]
1986 fullsize = [None, 0, 0]
1987 deltasize = [None, 0, 0]
1987 deltasize = [None, 0, 0]
1988 chunktypecounts = {}
1988 chunktypecounts = {}
1989 chunktypesizes = {}
1989 chunktypesizes = {}
1990
1990
1991 def addsize(size, l):
1991 def addsize(size, l):
1992 if l[0] is None or size < l[0]:
1992 if l[0] is None or size < l[0]:
1993 l[0] = size
1993 l[0] = size
1994 if size > l[1]:
1994 if size > l[1]:
1995 l[1] = size
1995 l[1] = size
1996 l[2] += size
1996 l[2] += size
1997
1997
1998 numrevs = len(r)
1998 numrevs = len(r)
1999 for rev in xrange(numrevs):
1999 for rev in xrange(numrevs):
2000 p1, p2 = r.parentrevs(rev)
2000 p1, p2 = r.parentrevs(rev)
2001 delta = r.deltaparent(rev)
2001 delta = r.deltaparent(rev)
2002 if format > 0:
2002 if format > 0:
2003 addsize(r.rawsize(rev), datasize)
2003 addsize(r.rawsize(rev), datasize)
2004 if p2 != nullrev:
2004 if p2 != nullrev:
2005 nummerges += 1
2005 nummerges += 1
2006 size = r.length(rev)
2006 size = r.length(rev)
2007 if delta == nullrev:
2007 if delta == nullrev:
2008 chainlengths.append(0)
2008 chainlengths.append(0)
2009 chainbases.append(r.start(rev))
2009 chainbases.append(r.start(rev))
2010 chainspans.append(size)
2010 chainspans.append(size)
2011 numfull += 1
2011 numfull += 1
2012 addsize(size, fullsize)
2012 addsize(size, fullsize)
2013 else:
2013 else:
2014 chainlengths.append(chainlengths[delta] + 1)
2014 chainlengths.append(chainlengths[delta] + 1)
2015 baseaddr = chainbases[delta]
2015 baseaddr = chainbases[delta]
2016 revaddr = r.start(rev)
2016 revaddr = r.start(rev)
2017 chainbases.append(baseaddr)
2017 chainbases.append(baseaddr)
2018 chainspans.append((revaddr - baseaddr) + size)
2018 chainspans.append((revaddr - baseaddr) + size)
2019 addsize(size, deltasize)
2019 addsize(size, deltasize)
2020 if delta == rev - 1:
2020 if delta == rev - 1:
2021 numprev += 1
2021 numprev += 1
2022 if delta == p1:
2022 if delta == p1:
2023 nump1prev += 1
2023 nump1prev += 1
2024 elif delta == p2:
2024 elif delta == p2:
2025 nump2prev += 1
2025 nump2prev += 1
2026 elif delta == p1:
2026 elif delta == p1:
2027 nump1 += 1
2027 nump1 += 1
2028 elif delta == p2:
2028 elif delta == p2:
2029 nump2 += 1
2029 nump2 += 1
2030 elif delta != nullrev:
2030 elif delta != nullrev:
2031 numother += 1
2031 numother += 1
2032
2032
2033 # Obtain data on the raw chunks in the revlog.
2033 # Obtain data on the raw chunks in the revlog.
2034 segment = r._getsegmentforrevs(rev, rev)[1]
2034 segment = r._getsegmentforrevs(rev, rev)[1]
2035 if segment:
2035 if segment:
2036 chunktype = bytes(segment[0:1])
2036 chunktype = bytes(segment[0:1])
2037 else:
2037 else:
2038 chunktype = 'empty'
2038 chunktype = 'empty'
2039
2039
2040 if chunktype not in chunktypecounts:
2040 if chunktype not in chunktypecounts:
2041 chunktypecounts[chunktype] = 0
2041 chunktypecounts[chunktype] = 0
2042 chunktypesizes[chunktype] = 0
2042 chunktypesizes[chunktype] = 0
2043
2043
2044 chunktypecounts[chunktype] += 1
2044 chunktypecounts[chunktype] += 1
2045 chunktypesizes[chunktype] += size
2045 chunktypesizes[chunktype] += size
2046
2046
2047 # Adjust size min value for empty cases
2047 # Adjust size min value for empty cases
2048 for size in (datasize, fullsize, deltasize):
2048 for size in (datasize, fullsize, deltasize):
2049 if size[0] is None:
2049 if size[0] is None:
2050 size[0] = 0
2050 size[0] = 0
2051
2051
2052 numdeltas = numrevs - numfull
2052 numdeltas = numrevs - numfull
2053 numoprev = numprev - nump1prev - nump2prev
2053 numoprev = numprev - nump1prev - nump2prev
2054 totalrawsize = datasize[2]
2054 totalrawsize = datasize[2]
2055 datasize[2] /= numrevs
2055 datasize[2] /= numrevs
2056 fulltotal = fullsize[2]
2056 fulltotal = fullsize[2]
2057 fullsize[2] /= numfull
2057 fullsize[2] /= numfull
2058 deltatotal = deltasize[2]
2058 deltatotal = deltasize[2]
2059 if numrevs - numfull > 0:
2059 if numrevs - numfull > 0:
2060 deltasize[2] /= numrevs - numfull
2060 deltasize[2] /= numrevs - numfull
2061 totalsize = fulltotal + deltatotal
2061 totalsize = fulltotal + deltatotal
2062 avgchainlen = sum(chainlengths) / numrevs
2062 avgchainlen = sum(chainlengths) / numrevs
2063 maxchainlen = max(chainlengths)
2063 maxchainlen = max(chainlengths)
2064 maxchainspan = max(chainspans)
2064 maxchainspan = max(chainspans)
2065 compratio = 1
2065 compratio = 1
2066 if totalsize:
2066 if totalsize:
2067 compratio = totalrawsize / totalsize
2067 compratio = totalrawsize / totalsize
2068
2068
2069 basedfmtstr = '%%%dd\n'
2069 basedfmtstr = '%%%dd\n'
2070 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2070 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2071
2071
2072 def dfmtstr(max):
2072 def dfmtstr(max):
2073 return basedfmtstr % len(str(max))
2073 return basedfmtstr % len(str(max))
2074 def pcfmtstr(max, padding=0):
2074 def pcfmtstr(max, padding=0):
2075 return basepcfmtstr % (len(str(max)), ' ' * padding)
2075 return basepcfmtstr % (len(str(max)), ' ' * padding)
2076
2076
2077 def pcfmt(value, total):
2077 def pcfmt(value, total):
2078 if total:
2078 if total:
2079 return (value, 100 * float(value) / total)
2079 return (value, 100 * float(value) / total)
2080 else:
2080 else:
2081 return value, 100.0
2081 return value, 100.0
2082
2082
2083 ui.write(('format : %d\n') % format)
2083 ui.write(('format : %d\n') % format)
2084 ui.write(('flags : %s\n') % ', '.join(flags))
2084 ui.write(('flags : %s\n') % ', '.join(flags))
2085
2085
2086 ui.write('\n')
2086 ui.write('\n')
2087 fmt = pcfmtstr(totalsize)
2087 fmt = pcfmtstr(totalsize)
2088 fmt2 = dfmtstr(totalsize)
2088 fmt2 = dfmtstr(totalsize)
2089 ui.write(('revisions : ') + fmt2 % numrevs)
2089 ui.write(('revisions : ') + fmt2 % numrevs)
2090 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2090 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2091 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2091 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2092 ui.write(('revisions : ') + fmt2 % numrevs)
2092 ui.write(('revisions : ') + fmt2 % numrevs)
2093 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2093 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2094 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2094 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2095 ui.write(('revision size : ') + fmt2 % totalsize)
2095 ui.write(('revision size : ') + fmt2 % totalsize)
2096 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2096 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2097 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2097 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2098
2098
2099 def fmtchunktype(chunktype):
2099 def fmtchunktype(chunktype):
2100 if chunktype == 'empty':
2100 if chunktype == 'empty':
2101 return ' %s : ' % chunktype
2101 return ' %s : ' % chunktype
2102 elif chunktype in pycompat.bytestr(string.ascii_letters):
2102 elif chunktype in pycompat.bytestr(string.ascii_letters):
2103 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2103 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2104 else:
2104 else:
2105 return ' 0x%s : ' % hex(chunktype)
2105 return ' 0x%s : ' % hex(chunktype)
2106
2106
2107 ui.write('\n')
2107 ui.write('\n')
2108 ui.write(('chunks : ') + fmt2 % numrevs)
2108 ui.write(('chunks : ') + fmt2 % numrevs)
2109 for chunktype in sorted(chunktypecounts):
2109 for chunktype in sorted(chunktypecounts):
2110 ui.write(fmtchunktype(chunktype))
2110 ui.write(fmtchunktype(chunktype))
2111 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2111 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2112 ui.write(('chunks size : ') + fmt2 % totalsize)
2112 ui.write(('chunks size : ') + fmt2 % totalsize)
2113 for chunktype in sorted(chunktypecounts):
2113 for chunktype in sorted(chunktypecounts):
2114 ui.write(fmtchunktype(chunktype))
2114 ui.write(fmtchunktype(chunktype))
2115 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2115 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2116
2116
2117 ui.write('\n')
2117 ui.write('\n')
2118 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2118 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2119 ui.write(('avg chain length : ') + fmt % avgchainlen)
2119 ui.write(('avg chain length : ') + fmt % avgchainlen)
2120 ui.write(('max chain length : ') + fmt % maxchainlen)
2120 ui.write(('max chain length : ') + fmt % maxchainlen)
2121 ui.write(('max chain reach : ') + fmt % maxchainspan)
2121 ui.write(('max chain reach : ') + fmt % maxchainspan)
2122 ui.write(('compression ratio : ') + fmt % compratio)
2122 ui.write(('compression ratio : ') + fmt % compratio)
2123
2123
2124 if format > 0:
2124 if format > 0:
2125 ui.write('\n')
2125 ui.write('\n')
2126 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2126 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2127 % tuple(datasize))
2127 % tuple(datasize))
2128 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2128 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2129 % tuple(fullsize))
2129 % tuple(fullsize))
2130 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2130 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2131 % tuple(deltasize))
2131 % tuple(deltasize))
2132
2132
2133 if numdeltas > 0:
2133 if numdeltas > 0:
2134 ui.write('\n')
2134 ui.write('\n')
2135 fmt = pcfmtstr(numdeltas)
2135 fmt = pcfmtstr(numdeltas)
2136 fmt2 = pcfmtstr(numdeltas, 4)
2136 fmt2 = pcfmtstr(numdeltas, 4)
2137 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2137 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2138 if numprev > 0:
2138 if numprev > 0:
2139 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2139 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2140 numprev))
2140 numprev))
2141 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2141 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2142 numprev))
2142 numprev))
2143 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2143 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2144 numprev))
2144 numprev))
2145 if gdelta:
2145 if gdelta:
2146 ui.write(('deltas against p1 : ')
2146 ui.write(('deltas against p1 : ')
2147 + fmt % pcfmt(nump1, numdeltas))
2147 + fmt % pcfmt(nump1, numdeltas))
2148 ui.write(('deltas against p2 : ')
2148 ui.write(('deltas against p2 : ')
2149 + fmt % pcfmt(nump2, numdeltas))
2149 + fmt % pcfmt(nump2, numdeltas))
2150 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2150 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2151 numdeltas))
2151 numdeltas))
2152
2152
2153 @command('debugrevspec',
2153 @command('debugrevspec',
2154 [('', 'optimize', None,
2154 [('', 'optimize', None,
2155 _('print parsed tree after optimizing (DEPRECATED)')),
2155 _('print parsed tree after optimizing (DEPRECATED)')),
2156 ('', 'show-revs', True, _('print list of result revisions (default)')),
2156 ('', 'show-revs', True, _('print list of result revisions (default)')),
2157 ('s', 'show-set', None, _('print internal representation of result set')),
2157 ('s', 'show-set', None, _('print internal representation of result set')),
2158 ('p', 'show-stage', [],
2158 ('p', 'show-stage', [],
2159 _('print parsed tree at the given stage'), _('NAME')),
2159 _('print parsed tree at the given stage'), _('NAME')),
2160 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2160 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2161 ('', 'verify-optimized', False, _('verify optimized result')),
2161 ('', 'verify-optimized', False, _('verify optimized result')),
2162 ],
2162 ],
2163 ('REVSPEC'))
2163 ('REVSPEC'))
2164 def debugrevspec(ui, repo, expr, **opts):
2164 def debugrevspec(ui, repo, expr, **opts):
2165 """parse and apply a revision specification
2165 """parse and apply a revision specification
2166
2166
2167 Use -p/--show-stage option to print the parsed tree at the given stages.
2167 Use -p/--show-stage option to print the parsed tree at the given stages.
2168 Use -p all to print tree at every stage.
2168 Use -p all to print tree at every stage.
2169
2169
2170 Use --no-show-revs option with -s or -p to print only the set
2170 Use --no-show-revs option with -s or -p to print only the set
2171 representation or the parsed tree respectively.
2171 representation or the parsed tree respectively.
2172
2172
2173 Use --verify-optimized to compare the optimized result with the unoptimized
2173 Use --verify-optimized to compare the optimized result with the unoptimized
2174 one. Returns 1 if the optimized result differs.
2174 one. Returns 1 if the optimized result differs.
2175 """
2175 """
2176 opts = pycompat.byteskwargs(opts)
2176 opts = pycompat.byteskwargs(opts)
2177 aliases = ui.configitems('revsetalias')
2177 aliases = ui.configitems('revsetalias')
2178 stages = [
2178 stages = [
2179 ('parsed', lambda tree: tree),
2179 ('parsed', lambda tree: tree),
2180 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2180 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2181 ui.warn)),
2181 ui.warn)),
2182 ('concatenated', revsetlang.foldconcat),
2182 ('concatenated', revsetlang.foldconcat),
2183 ('analyzed', revsetlang.analyze),
2183 ('analyzed', revsetlang.analyze),
2184 ('optimized', revsetlang.optimize),
2184 ('optimized', revsetlang.optimize),
2185 ]
2185 ]
2186 if opts['no_optimized']:
2186 if opts['no_optimized']:
2187 stages = stages[:-1]
2187 stages = stages[:-1]
2188 if opts['verify_optimized'] and opts['no_optimized']:
2188 if opts['verify_optimized'] and opts['no_optimized']:
2189 raise error.Abort(_('cannot use --verify-optimized with '
2189 raise error.Abort(_('cannot use --verify-optimized with '
2190 '--no-optimized'))
2190 '--no-optimized'))
2191 stagenames = set(n for n, f in stages)
2191 stagenames = set(n for n, f in stages)
2192
2192
2193 showalways = set()
2193 showalways = set()
2194 showchanged = set()
2194 showchanged = set()
2195 if ui.verbose and not opts['show_stage']:
2195 if ui.verbose and not opts['show_stage']:
2196 # show parsed tree by --verbose (deprecated)
2196 # show parsed tree by --verbose (deprecated)
2197 showalways.add('parsed')
2197 showalways.add('parsed')
2198 showchanged.update(['expanded', 'concatenated'])
2198 showchanged.update(['expanded', 'concatenated'])
2199 if opts['optimize']:
2199 if opts['optimize']:
2200 showalways.add('optimized')
2200 showalways.add('optimized')
2201 if opts['show_stage'] and opts['optimize']:
2201 if opts['show_stage'] and opts['optimize']:
2202 raise error.Abort(_('cannot use --optimize with --show-stage'))
2202 raise error.Abort(_('cannot use --optimize with --show-stage'))
2203 if opts['show_stage'] == ['all']:
2203 if opts['show_stage'] == ['all']:
2204 showalways.update(stagenames)
2204 showalways.update(stagenames)
2205 else:
2205 else:
2206 for n in opts['show_stage']:
2206 for n in opts['show_stage']:
2207 if n not in stagenames:
2207 if n not in stagenames:
2208 raise error.Abort(_('invalid stage name: %s') % n)
2208 raise error.Abort(_('invalid stage name: %s') % n)
2209 showalways.update(opts['show_stage'])
2209 showalways.update(opts['show_stage'])
2210
2210
2211 treebystage = {}
2211 treebystage = {}
2212 printedtree = None
2212 printedtree = None
2213 tree = revsetlang.parse(expr, lookup=repo.__contains__)
2213 tree = revsetlang.parse(expr, lookup=repo.__contains__)
2214 for n, f in stages:
2214 for n, f in stages:
2215 treebystage[n] = tree = f(tree)
2215 treebystage[n] = tree = f(tree)
2216 if n in showalways or (n in showchanged and tree != printedtree):
2216 if n in showalways or (n in showchanged and tree != printedtree):
2217 if opts['show_stage'] or n != 'parsed':
2217 if opts['show_stage'] or n != 'parsed':
2218 ui.write(("* %s:\n") % n)
2218 ui.write(("* %s:\n") % n)
2219 ui.write(revsetlang.prettyformat(tree), "\n")
2219 ui.write(revsetlang.prettyformat(tree), "\n")
2220 printedtree = tree
2220 printedtree = tree
2221
2221
2222 if opts['verify_optimized']:
2222 if opts['verify_optimized']:
2223 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2223 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2224 brevs = revset.makematcher(treebystage['optimized'])(repo)
2224 brevs = revset.makematcher(treebystage['optimized'])(repo)
2225 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2225 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2226 ui.write(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
2226 ui.write(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
2227 ui.write(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
2227 ui.write(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
2228 arevs = list(arevs)
2228 arevs = list(arevs)
2229 brevs = list(brevs)
2229 brevs = list(brevs)
2230 if arevs == brevs:
2230 if arevs == brevs:
2231 return 0
2231 return 0
2232 ui.write(('--- analyzed\n'), label='diff.file_a')
2232 ui.write(('--- analyzed\n'), label='diff.file_a')
2233 ui.write(('+++ optimized\n'), label='diff.file_b')
2233 ui.write(('+++ optimized\n'), label='diff.file_b')
2234 sm = difflib.SequenceMatcher(None, arevs, brevs)
2234 sm = difflib.SequenceMatcher(None, arevs, brevs)
2235 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2235 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2236 if tag in ('delete', 'replace'):
2236 if tag in ('delete', 'replace'):
2237 for c in arevs[alo:ahi]:
2237 for c in arevs[alo:ahi]:
2238 ui.write('-%s\n' % c, label='diff.deleted')
2238 ui.write('-%s\n' % c, label='diff.deleted')
2239 if tag in ('insert', 'replace'):
2239 if tag in ('insert', 'replace'):
2240 for c in brevs[blo:bhi]:
2240 for c in brevs[blo:bhi]:
2241 ui.write('+%s\n' % c, label='diff.inserted')
2241 ui.write('+%s\n' % c, label='diff.inserted')
2242 if tag == 'equal':
2242 if tag == 'equal':
2243 for c in arevs[alo:ahi]:
2243 for c in arevs[alo:ahi]:
2244 ui.write(' %s\n' % c)
2244 ui.write(' %s\n' % c)
2245 return 1
2245 return 1
2246
2246
2247 func = revset.makematcher(tree)
2247 func = revset.makematcher(tree)
2248 revs = func(repo)
2248 revs = func(repo)
2249 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2249 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2250 ui.write(("* set:\n"), smartset.prettyformat(revs), "\n")
2250 ui.write(("* set:\n"), smartset.prettyformat(revs), "\n")
2251 if not opts['show_revs']:
2251 if not opts['show_revs']:
2252 return
2252 return
2253 for c in revs:
2253 for c in revs:
2254 ui.write("%d\n" % c)
2254 ui.write("%d\n" % c)
2255
2255
2256 @command('debugserve', [
2256 @command('debugserve', [
2257 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2257 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2258 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2258 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2259 ('', 'logiofile', '', _('file to log server I/O to')),
2259 ('', 'logiofile', '', _('file to log server I/O to')),
2260 ], '')
2260 ], '')
2261 def debugserve(ui, repo, **opts):
2261 def debugserve(ui, repo, **opts):
2262 """run a server with advanced settings
2262 """run a server with advanced settings
2263
2263
2264 This command is similar to :hg:`serve`. It exists partially as a
2264 This command is similar to :hg:`serve`. It exists partially as a
2265 workaround to the fact that ``hg serve --stdio`` must have specific
2265 workaround to the fact that ``hg serve --stdio`` must have specific
2266 arguments for security reasons.
2266 arguments for security reasons.
2267 """
2267 """
2268 opts = pycompat.byteskwargs(opts)
2268 opts = pycompat.byteskwargs(opts)
2269
2269
2270 if not opts['sshstdio']:
2270 if not opts['sshstdio']:
2271 raise error.Abort(_('only --sshstdio is currently supported'))
2271 raise error.Abort(_('only --sshstdio is currently supported'))
2272
2272
2273 logfh = None
2273 logfh = None
2274
2274
2275 if opts['logiofd'] and opts['logiofile']:
2275 if opts['logiofd'] and opts['logiofile']:
2276 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2276 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2277
2277
2278 if opts['logiofd']:
2278 if opts['logiofd']:
2279 # Line buffered because output is line based.
2279 # Line buffered because output is line based.
2280 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2280 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2281 elif opts['logiofile']:
2281 elif opts['logiofile']:
2282 logfh = open(opts['logiofile'], 'ab', 1)
2282 logfh = open(opts['logiofile'], 'ab', 1)
2283
2283
2284 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2284 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2285 s.serve_forever()
2285 s.serve_forever()
2286
2286
2287 @command('debugsetparents', [], _('REV1 [REV2]'))
2287 @command('debugsetparents', [], _('REV1 [REV2]'))
2288 def debugsetparents(ui, repo, rev1, rev2=None):
2288 def debugsetparents(ui, repo, rev1, rev2=None):
2289 """manually set the parents of the current working directory
2289 """manually set the parents of the current working directory
2290
2290
2291 This is useful for writing repository conversion tools, but should
2291 This is useful for writing repository conversion tools, but should
2292 be used with care. For example, neither the working directory nor the
2292 be used with care. For example, neither the working directory nor the
2293 dirstate is updated, so file status may be incorrect after running this
2293 dirstate is updated, so file status may be incorrect after running this
2294 command.
2294 command.
2295
2295
2296 Returns 0 on success.
2296 Returns 0 on success.
2297 """
2297 """
2298
2298
2299 node1 = scmutil.revsingle(repo, rev1).node()
2299 node1 = scmutil.revsingle(repo, rev1).node()
2300 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2300 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2301
2301
2302 with repo.wlock():
2302 with repo.wlock():
2303 repo.setparents(node1, node2)
2303 repo.setparents(node1, node2)
2304
2304
2305 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2305 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2306 def debugssl(ui, repo, source=None, **opts):
2306 def debugssl(ui, repo, source=None, **opts):
2307 '''test a secure connection to a server
2307 '''test a secure connection to a server
2308
2308
2309 This builds the certificate chain for the server on Windows, installing the
2309 This builds the certificate chain for the server on Windows, installing the
2310 missing intermediates and trusted root via Windows Update if necessary. It
2310 missing intermediates and trusted root via Windows Update if necessary. It
2311 does nothing on other platforms.
2311 does nothing on other platforms.
2312
2312
2313 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2313 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2314 that server is used. See :hg:`help urls` for more information.
2314 that server is used. See :hg:`help urls` for more information.
2315
2315
2316 If the update succeeds, retry the original operation. Otherwise, the cause
2316 If the update succeeds, retry the original operation. Otherwise, the cause
2317 of the SSL error is likely another issue.
2317 of the SSL error is likely another issue.
2318 '''
2318 '''
2319 if not pycompat.iswindows:
2319 if not pycompat.iswindows:
2320 raise error.Abort(_('certificate chain building is only possible on '
2320 raise error.Abort(_('certificate chain building is only possible on '
2321 'Windows'))
2321 'Windows'))
2322
2322
2323 if not source:
2323 if not source:
2324 if not repo:
2324 if not repo:
2325 raise error.Abort(_("there is no Mercurial repository here, and no "
2325 raise error.Abort(_("there is no Mercurial repository here, and no "
2326 "server specified"))
2326 "server specified"))
2327 source = "default"
2327 source = "default"
2328
2328
2329 source, branches = hg.parseurl(ui.expandpath(source))
2329 source, branches = hg.parseurl(ui.expandpath(source))
2330 url = util.url(source)
2330 url = util.url(source)
2331 addr = None
2331 addr = None
2332
2332
2333 defaultport = {'https': 443, 'ssh': 22}
2333 defaultport = {'https': 443, 'ssh': 22}
2334 if url.scheme in defaultport:
2334 if url.scheme in defaultport:
2335 try:
2335 try:
2336 addr = (url.host, int(url.port or defaultport[url.scheme]))
2336 addr = (url.host, int(url.port or defaultport[url.scheme]))
2337 except ValueError:
2337 except ValueError:
2338 raise error.Abort(_("malformed port number in URL"))
2338 raise error.Abort(_("malformed port number in URL"))
2339 else:
2339 else:
2340 raise error.Abort(_("only https and ssh connections are supported"))
2340 raise error.Abort(_("only https and ssh connections are supported"))
2341
2341
2342 from . import win32
2342 from . import win32
2343
2343
2344 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2344 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2345 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2345 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2346
2346
2347 try:
2347 try:
2348 s.connect(addr)
2348 s.connect(addr)
2349 cert = s.getpeercert(True)
2349 cert = s.getpeercert(True)
2350
2350
2351 ui.status(_('checking the certificate chain for %s\n') % url.host)
2351 ui.status(_('checking the certificate chain for %s\n') % url.host)
2352
2352
2353 complete = win32.checkcertificatechain(cert, build=False)
2353 complete = win32.checkcertificatechain(cert, build=False)
2354
2354
2355 if not complete:
2355 if not complete:
2356 ui.status(_('certificate chain is incomplete, updating... '))
2356 ui.status(_('certificate chain is incomplete, updating... '))
2357
2357
2358 if not win32.checkcertificatechain(cert):
2358 if not win32.checkcertificatechain(cert):
2359 ui.status(_('failed.\n'))
2359 ui.status(_('failed.\n'))
2360 else:
2360 else:
2361 ui.status(_('done.\n'))
2361 ui.status(_('done.\n'))
2362 else:
2362 else:
2363 ui.status(_('full certificate chain is available\n'))
2363 ui.status(_('full certificate chain is available\n'))
2364 finally:
2364 finally:
2365 s.close()
2365 s.close()
2366
2366
2367 @command('debugsub',
2367 @command('debugsub',
2368 [('r', 'rev', '',
2368 [('r', 'rev', '',
2369 _('revision to check'), _('REV'))],
2369 _('revision to check'), _('REV'))],
2370 _('[-r REV] [REV]'))
2370 _('[-r REV] [REV]'))
2371 def debugsub(ui, repo, rev=None):
2371 def debugsub(ui, repo, rev=None):
2372 ctx = scmutil.revsingle(repo, rev, None)
2372 ctx = scmutil.revsingle(repo, rev, None)
2373 for k, v in sorted(ctx.substate.items()):
2373 for k, v in sorted(ctx.substate.items()):
2374 ui.write(('path %s\n') % k)
2374 ui.write(('path %s\n') % k)
2375 ui.write((' source %s\n') % v[0])
2375 ui.write((' source %s\n') % v[0])
2376 ui.write((' revision %s\n') % v[1])
2376 ui.write((' revision %s\n') % v[1])
2377
2377
2378 @command('debugsuccessorssets',
2378 @command('debugsuccessorssets',
2379 [('', 'closest', False, _('return closest successors sets only'))],
2379 [('', 'closest', False, _('return closest successors sets only'))],
2380 _('[REV]'))
2380 _('[REV]'))
2381 def debugsuccessorssets(ui, repo, *revs, **opts):
2381 def debugsuccessorssets(ui, repo, *revs, **opts):
2382 """show set of successors for revision
2382 """show set of successors for revision
2383
2383
2384 A successors set of changeset A is a consistent group of revisions that
2384 A successors set of changeset A is a consistent group of revisions that
2385 succeed A. It contains non-obsolete changesets only unless closests
2385 succeed A. It contains non-obsolete changesets only unless closests
2386 successors set is set.
2386 successors set is set.
2387
2387
2388 In most cases a changeset A has a single successors set containing a single
2388 In most cases a changeset A has a single successors set containing a single
2389 successor (changeset A replaced by A').
2389 successor (changeset A replaced by A').
2390
2390
2391 A changeset that is made obsolete with no successors are called "pruned".
2391 A changeset that is made obsolete with no successors are called "pruned".
2392 Such changesets have no successors sets at all.
2392 Such changesets have no successors sets at all.
2393
2393
2394 A changeset that has been "split" will have a successors set containing
2394 A changeset that has been "split" will have a successors set containing
2395 more than one successor.
2395 more than one successor.
2396
2396
2397 A changeset that has been rewritten in multiple different ways is called
2397 A changeset that has been rewritten in multiple different ways is called
2398 "divergent". Such changesets have multiple successor sets (each of which
2398 "divergent". Such changesets have multiple successor sets (each of which
2399 may also be split, i.e. have multiple successors).
2399 may also be split, i.e. have multiple successors).
2400
2400
2401 Results are displayed as follows::
2401 Results are displayed as follows::
2402
2402
2403 <rev1>
2403 <rev1>
2404 <successors-1A>
2404 <successors-1A>
2405 <rev2>
2405 <rev2>
2406 <successors-2A>
2406 <successors-2A>
2407 <successors-2B1> <successors-2B2> <successors-2B3>
2407 <successors-2B1> <successors-2B2> <successors-2B3>
2408
2408
2409 Here rev2 has two possible (i.e. divergent) successors sets. The first
2409 Here rev2 has two possible (i.e. divergent) successors sets. The first
2410 holds one element, whereas the second holds three (i.e. the changeset has
2410 holds one element, whereas the second holds three (i.e. the changeset has
2411 been split).
2411 been split).
2412 """
2412 """
2413 # passed to successorssets caching computation from one call to another
2413 # passed to successorssets caching computation from one call to another
2414 cache = {}
2414 cache = {}
2415 ctx2str = bytes
2415 ctx2str = bytes
2416 node2str = short
2416 node2str = short
2417 for rev in scmutil.revrange(repo, revs):
2417 for rev in scmutil.revrange(repo, revs):
2418 ctx = repo[rev]
2418 ctx = repo[rev]
2419 ui.write('%s\n'% ctx2str(ctx))
2419 ui.write('%s\n'% ctx2str(ctx))
2420 for succsset in obsutil.successorssets(repo, ctx.node(),
2420 for succsset in obsutil.successorssets(repo, ctx.node(),
2421 closest=opts[r'closest'],
2421 closest=opts[r'closest'],
2422 cache=cache):
2422 cache=cache):
2423 if succsset:
2423 if succsset:
2424 ui.write(' ')
2424 ui.write(' ')
2425 ui.write(node2str(succsset[0]))
2425 ui.write(node2str(succsset[0]))
2426 for node in succsset[1:]:
2426 for node in succsset[1:]:
2427 ui.write(' ')
2427 ui.write(' ')
2428 ui.write(node2str(node))
2428 ui.write(node2str(node))
2429 ui.write('\n')
2429 ui.write('\n')
2430
2430
2431 @command('debugtemplate',
2431 @command('debugtemplate',
2432 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2432 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2433 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2433 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2434 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2434 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2435 optionalrepo=True)
2435 optionalrepo=True)
2436 def debugtemplate(ui, repo, tmpl, **opts):
2436 def debugtemplate(ui, repo, tmpl, **opts):
2437 """parse and apply a template
2437 """parse and apply a template
2438
2438
2439 If -r/--rev is given, the template is processed as a log template and
2439 If -r/--rev is given, the template is processed as a log template and
2440 applied to the given changesets. Otherwise, it is processed as a generic
2440 applied to the given changesets. Otherwise, it is processed as a generic
2441 template.
2441 template.
2442
2442
2443 Use --verbose to print the parsed tree.
2443 Use --verbose to print the parsed tree.
2444 """
2444 """
2445 revs = None
2445 revs = None
2446 if opts[r'rev']:
2446 if opts[r'rev']:
2447 if repo is None:
2447 if repo is None:
2448 raise error.RepoError(_('there is no Mercurial repository here '
2448 raise error.RepoError(_('there is no Mercurial repository here '
2449 '(.hg not found)'))
2449 '(.hg not found)'))
2450 revs = scmutil.revrange(repo, opts[r'rev'])
2450 revs = scmutil.revrange(repo, opts[r'rev'])
2451
2451
2452 props = {}
2452 props = {}
2453 for d in opts[r'define']:
2453 for d in opts[r'define']:
2454 try:
2454 try:
2455 k, v = (e.strip() for e in d.split('=', 1))
2455 k, v = (e.strip() for e in d.split('=', 1))
2456 if not k or k == 'ui':
2456 if not k or k == 'ui':
2457 raise ValueError
2457 raise ValueError
2458 props[k] = v
2458 props[k] = v
2459 except ValueError:
2459 except ValueError:
2460 raise error.Abort(_('malformed keyword definition: %s') % d)
2460 raise error.Abort(_('malformed keyword definition: %s') % d)
2461
2461
2462 if ui.verbose:
2462 if ui.verbose:
2463 aliases = ui.configitems('templatealias')
2463 aliases = ui.configitems('templatealias')
2464 tree = templater.parse(tmpl)
2464 tree = templater.parse(tmpl)
2465 ui.note(templater.prettyformat(tree), '\n')
2465 ui.note(templater.prettyformat(tree), '\n')
2466 newtree = templater.expandaliases(tree, aliases)
2466 newtree = templater.expandaliases(tree, aliases)
2467 if newtree != tree:
2467 if newtree != tree:
2468 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2468 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2469
2469
2470 if revs is None:
2470 if revs is None:
2471 tres = formatter.templateresources(ui, repo)
2471 tres = formatter.templateresources(ui, repo)
2472 t = formatter.maketemplater(ui, tmpl, resources=tres)
2472 t = formatter.maketemplater(ui, tmpl, resources=tres)
2473 ui.write(t.renderdefault(props))
2473 ui.write(t.renderdefault(props))
2474 else:
2474 else:
2475 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2475 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2476 for r in revs:
2476 for r in revs:
2477 displayer.show(repo[r], **pycompat.strkwargs(props))
2477 displayer.show(repo[r], **pycompat.strkwargs(props))
2478 displayer.close()
2478 displayer.close()
2479
2479
2480 @command('debuguigetpass', [
2480 @command('debuguigetpass', [
2481 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2481 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2482 ], _('[-p TEXT]'), norepo=True)
2482 ], _('[-p TEXT]'), norepo=True)
2483 def debuguigetpass(ui, prompt=''):
2483 def debuguigetpass(ui, prompt=''):
2484 """show prompt to type password"""
2484 """show prompt to type password"""
2485 r = ui.getpass(prompt)
2485 r = ui.getpass(prompt)
2486 ui.write(('respose: %s\n') % r)
2486 ui.write(('respose: %s\n') % r)
2487
2487
2488 @command('debuguiprompt', [
2488 @command('debuguiprompt', [
2489 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2489 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2490 ], _('[-p TEXT]'), norepo=True)
2490 ], _('[-p TEXT]'), norepo=True)
2491 def debuguiprompt(ui, prompt=''):
2491 def debuguiprompt(ui, prompt=''):
2492 """show plain prompt"""
2492 """show plain prompt"""
2493 r = ui.prompt(prompt)
2493 r = ui.prompt(prompt)
2494 ui.write(('response: %s\n') % r)
2494 ui.write(('response: %s\n') % r)
2495
2495
2496 @command('debugupdatecaches', [])
2496 @command('debugupdatecaches', [])
2497 def debugupdatecaches(ui, repo, *pats, **opts):
2497 def debugupdatecaches(ui, repo, *pats, **opts):
2498 """warm all known caches in the repository"""
2498 """warm all known caches in the repository"""
2499 with repo.wlock(), repo.lock():
2499 with repo.wlock(), repo.lock():
2500 repo.updatecaches(full=True)
2500 repo.updatecaches(full=True)
2501
2501
2502 @command('debugupgraderepo', [
2502 @command('debugupgraderepo', [
2503 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2503 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2504 ('', 'run', False, _('performs an upgrade')),
2504 ('', 'run', False, _('performs an upgrade')),
2505 ])
2505 ])
2506 def debugupgraderepo(ui, repo, run=False, optimize=None):
2506 def debugupgraderepo(ui, repo, run=False, optimize=None):
2507 """upgrade a repository to use different features
2507 """upgrade a repository to use different features
2508
2508
2509 If no arguments are specified, the repository is evaluated for upgrade
2509 If no arguments are specified, the repository is evaluated for upgrade
2510 and a list of problems and potential optimizations is printed.
2510 and a list of problems and potential optimizations is printed.
2511
2511
2512 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2512 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2513 can be influenced via additional arguments. More details will be provided
2513 can be influenced via additional arguments. More details will be provided
2514 by the command output when run without ``--run``.
2514 by the command output when run without ``--run``.
2515
2515
2516 During the upgrade, the repository will be locked and no writes will be
2516 During the upgrade, the repository will be locked and no writes will be
2517 allowed.
2517 allowed.
2518
2518
2519 At the end of the upgrade, the repository may not be readable while new
2519 At the end of the upgrade, the repository may not be readable while new
2520 repository data is swapped in. This window will be as long as it takes to
2520 repository data is swapped in. This window will be as long as it takes to
2521 rename some directories inside the ``.hg`` directory. On most machines, this
2521 rename some directories inside the ``.hg`` directory. On most machines, this
2522 should complete almost instantaneously and the chances of a consumer being
2522 should complete almost instantaneously and the chances of a consumer being
2523 unable to access the repository should be low.
2523 unable to access the repository should be low.
2524 """
2524 """
2525 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2525 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2526
2526
2527 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2527 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2528 inferrepo=True)
2528 inferrepo=True)
2529 def debugwalk(ui, repo, *pats, **opts):
2529 def debugwalk(ui, repo, *pats, **opts):
2530 """show how files match on given patterns"""
2530 """show how files match on given patterns"""
2531 opts = pycompat.byteskwargs(opts)
2531 opts = pycompat.byteskwargs(opts)
2532 m = scmutil.match(repo[None], pats, opts)
2532 m = scmutil.match(repo[None], pats, opts)
2533 ui.write(('matcher: %r\n' % m))
2533 ui.write(('matcher: %r\n' % m))
2534 items = list(repo[None].walk(m))
2534 items = list(repo[None].walk(m))
2535 if not items:
2535 if not items:
2536 return
2536 return
2537 f = lambda fn: fn
2537 f = lambda fn: fn
2538 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2538 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2539 f = lambda fn: util.normpath(fn)
2539 f = lambda fn: util.normpath(fn)
2540 fmt = 'f %%-%ds %%-%ds %%s' % (
2540 fmt = 'f %%-%ds %%-%ds %%s' % (
2541 max([len(abs) for abs in items]),
2541 max([len(abs) for abs in items]),
2542 max([len(m.rel(abs)) for abs in items]))
2542 max([len(m.rel(abs)) for abs in items]))
2543 for abs in items:
2543 for abs in items:
2544 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2544 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2545 ui.write("%s\n" % line.rstrip())
2545 ui.write("%s\n" % line.rstrip())
2546
2546
2547 @command('debugwhyunstable', [], _('REV'))
2547 @command('debugwhyunstable', [], _('REV'))
2548 def debugwhyunstable(ui, repo, rev):
2548 def debugwhyunstable(ui, repo, rev):
2549 """explain instabilities of a changeset"""
2549 """explain instabilities of a changeset"""
2550 for entry in obsutil.whyunstable(repo, repo[rev]):
2550 for entry in obsutil.whyunstable(repo, repo[rev]):
2551 dnodes = ''
2551 dnodes = ''
2552 if entry.get('divergentnodes'):
2552 if entry.get('divergentnodes'):
2553 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2553 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2554 for ctx in entry['divergentnodes']) + ' '
2554 for ctx in entry['divergentnodes']) + ' '
2555 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2555 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2556 entry['reason'], entry['node']))
2556 entry['reason'], entry['node']))
2557
2557
2558 @command('debugwireargs',
2558 @command('debugwireargs',
2559 [('', 'three', '', 'three'),
2559 [('', 'three', '', 'three'),
2560 ('', 'four', '', 'four'),
2560 ('', 'four', '', 'four'),
2561 ('', 'five', '', 'five'),
2561 ('', 'five', '', 'five'),
2562 ] + cmdutil.remoteopts,
2562 ] + cmdutil.remoteopts,
2563 _('REPO [OPTIONS]... [ONE [TWO]]'),
2563 _('REPO [OPTIONS]... [ONE [TWO]]'),
2564 norepo=True)
2564 norepo=True)
2565 def debugwireargs(ui, repopath, *vals, **opts):
2565 def debugwireargs(ui, repopath, *vals, **opts):
2566 opts = pycompat.byteskwargs(opts)
2566 opts = pycompat.byteskwargs(opts)
2567 repo = hg.peer(ui, opts, repopath)
2567 repo = hg.peer(ui, opts, repopath)
2568 for opt in cmdutil.remoteopts:
2568 for opt in cmdutil.remoteopts:
2569 del opts[opt[1]]
2569 del opts[opt[1]]
2570 args = {}
2570 args = {}
2571 for k, v in opts.iteritems():
2571 for k, v in opts.iteritems():
2572 if v:
2572 if v:
2573 args[k] = v
2573 args[k] = v
2574 args = pycompat.strkwargs(args)
2574 args = pycompat.strkwargs(args)
2575 # run twice to check that we don't mess up the stream for the next command
2575 # run twice to check that we don't mess up the stream for the next command
2576 res1 = repo.debugwireargs(*vals, **args)
2576 res1 = repo.debugwireargs(*vals, **args)
2577 res2 = repo.debugwireargs(*vals, **args)
2577 res2 = repo.debugwireargs(*vals, **args)
2578 ui.write("%s\n" % res1)
2578 ui.write("%s\n" % res1)
2579 if res1 != res2:
2579 if res1 != res2:
2580 ui.warn("%s\n" % res2)
2580 ui.warn("%s\n" % res2)
2581
2581
2582 def _parsewirelangblocks(fh):
2582 def _parsewirelangblocks(fh):
2583 activeaction = None
2583 activeaction = None
2584 blocklines = []
2584 blocklines = []
2585
2585
2586 for line in fh:
2586 for line in fh:
2587 line = line.rstrip()
2587 line = line.rstrip()
2588 if not line:
2588 if not line:
2589 continue
2589 continue
2590
2590
2591 if line.startswith(b'#'):
2591 if line.startswith(b'#'):
2592 continue
2592 continue
2593
2593
2594 if not line.startswith(' '):
2594 if not line.startswith(' '):
2595 # New block. Flush previous one.
2595 # New block. Flush previous one.
2596 if activeaction:
2596 if activeaction:
2597 yield activeaction, blocklines
2597 yield activeaction, blocklines
2598
2598
2599 activeaction = line
2599 activeaction = line
2600 blocklines = []
2600 blocklines = []
2601 continue
2601 continue
2602
2602
2603 # Else we start with an indent.
2603 # Else we start with an indent.
2604
2604
2605 if not activeaction:
2605 if not activeaction:
2606 raise error.Abort(_('indented line outside of block'))
2606 raise error.Abort(_('indented line outside of block'))
2607
2607
2608 blocklines.append(line)
2608 blocklines.append(line)
2609
2609
2610 # Flush last block.
2610 # Flush last block.
2611 if activeaction:
2611 if activeaction:
2612 yield activeaction, blocklines
2612 yield activeaction, blocklines
2613
2613
2614 @command('debugwireproto',
2614 @command('debugwireproto',
2615 [
2615 [
2616 ('', 'localssh', False, _('start an SSH server for this repo')),
2616 ('', 'localssh', False, _('start an SSH server for this repo')),
2617 ('', 'peer', '', _('construct a specific version of the peer')),
2617 ('', 'peer', '', _('construct a specific version of the peer')),
2618 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2618 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2619 ] + cmdutil.remoteopts,
2619 ] + cmdutil.remoteopts,
2620 _('[PATH]'),
2620 _('[PATH]'),
2621 optionalrepo=True)
2621 optionalrepo=True)
2622 def debugwireproto(ui, repo, path=None, **opts):
2622 def debugwireproto(ui, repo, path=None, **opts):
2623 """send wire protocol commands to a server
2623 """send wire protocol commands to a server
2624
2624
2625 This command can be used to issue wire protocol commands to remote
2625 This command can be used to issue wire protocol commands to remote
2626 peers and to debug the raw data being exchanged.
2626 peers and to debug the raw data being exchanged.
2627
2627
2628 ``--localssh`` will start an SSH server against the current repository
2628 ``--localssh`` will start an SSH server against the current repository
2629 and connect to that. By default, the connection will perform a handshake
2629 and connect to that. By default, the connection will perform a handshake
2630 and establish an appropriate peer instance.
2630 and establish an appropriate peer instance.
2631
2631
2632 ``--peer`` can be used to bypass the handshake protocol and construct a
2632 ``--peer`` can be used to bypass the handshake protocol and construct a
2633 peer instance using the specified class type. Valid values are ``raw``,
2633 peer instance using the specified class type. Valid values are ``raw``,
2634 ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data
2634 ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data
2635 payloads and don't support higher-level command actions.
2635 payloads and don't support higher-level command actions.
2636
2636
2637 ``--noreadstderr`` can be used to disable automatic reading from stderr
2637 ``--noreadstderr`` can be used to disable automatic reading from stderr
2638 of the peer (for SSH connections only). Disabling automatic reading of
2638 of the peer (for SSH connections only). Disabling automatic reading of
2639 stderr is useful for making output more deterministic.
2639 stderr is useful for making output more deterministic.
2640
2640
2641 Commands are issued via a mini language which is specified via stdin.
2641 Commands are issued via a mini language which is specified via stdin.
2642 The language consists of individual actions to perform. An action is
2642 The language consists of individual actions to perform. An action is
2643 defined by a block. A block is defined as a line with no leading
2643 defined by a block. A block is defined as a line with no leading
2644 space followed by 0 or more lines with leading space. Blocks are
2644 space followed by 0 or more lines with leading space. Blocks are
2645 effectively a high-level command with additional metadata.
2645 effectively a high-level command with additional metadata.
2646
2646
2647 Lines beginning with ``#`` are ignored.
2647 Lines beginning with ``#`` are ignored.
2648
2648
2649 The following sections denote available actions.
2649 The following sections denote available actions.
2650
2650
2651 raw
2651 raw
2652 ---
2652 ---
2653
2653
2654 Send raw data to the server.
2654 Send raw data to the server.
2655
2655
2656 The block payload contains the raw data to send as one atomic send
2656 The block payload contains the raw data to send as one atomic send
2657 operation. The data may not actually be delivered in a single system
2657 operation. The data may not actually be delivered in a single system
2658 call: it depends on the abilities of the transport being used.
2658 call: it depends on the abilities of the transport being used.
2659
2659
2660 Each line in the block is de-indented and concatenated. Then, that
2660 Each line in the block is de-indented and concatenated. Then, that
2661 value is evaluated as a Python b'' literal. This allows the use of
2661 value is evaluated as a Python b'' literal. This allows the use of
2662 backslash escaping, etc.
2662 backslash escaping, etc.
2663
2663
2664 raw+
2664 raw+
2665 ----
2665 ----
2666
2666
2667 Behaves like ``raw`` except flushes output afterwards.
2667 Behaves like ``raw`` except flushes output afterwards.
2668
2668
2669 command <X>
2669 command <X>
2670 -----------
2670 -----------
2671
2671
2672 Send a request to run a named command, whose name follows the ``command``
2672 Send a request to run a named command, whose name follows the ``command``
2673 string.
2673 string.
2674
2674
2675 Arguments to the command are defined as lines in this block. The format of
2675 Arguments to the command are defined as lines in this block. The format of
2676 each line is ``<key> <value>``. e.g.::
2676 each line is ``<key> <value>``. e.g.::
2677
2677
2678 command listkeys
2678 command listkeys
2679 namespace bookmarks
2679 namespace bookmarks
2680
2680
2681 Values are interpreted as Python b'' literals. This allows encoding
2681 Values are interpreted as Python b'' literals. This allows encoding
2682 special byte sequences via backslash escaping.
2682 special byte sequences via backslash escaping.
2683
2683
2684 The following arguments have special meaning:
2684 The following arguments have special meaning:
2685
2685
2686 ``PUSHFILE``
2686 ``PUSHFILE``
2687 When defined, the *push* mechanism of the peer will be used instead
2687 When defined, the *push* mechanism of the peer will be used instead
2688 of the static request-response mechanism and the content of the
2688 of the static request-response mechanism and the content of the
2689 file specified in the value of this argument will be sent as the
2689 file specified in the value of this argument will be sent as the
2690 command payload.
2690 command payload.
2691
2691
2692 This can be used to submit a local bundle file to the remote.
2692 This can be used to submit a local bundle file to the remote.
2693
2693
2694 batchbegin
2694 batchbegin
2695 ----------
2695 ----------
2696
2696
2697 Instruct the peer to begin a batched send.
2697 Instruct the peer to begin a batched send.
2698
2698
2699 All ``command`` blocks are queued for execution until the next
2699 All ``command`` blocks are queued for execution until the next
2700 ``batchsubmit`` block.
2700 ``batchsubmit`` block.
2701
2701
2702 batchsubmit
2702 batchsubmit
2703 -----------
2703 -----------
2704
2704
2705 Submit previously queued ``command`` blocks as a batch request.
2705 Submit previously queued ``command`` blocks as a batch request.
2706
2706
2707 This action MUST be paired with a ``batchbegin`` action.
2707 This action MUST be paired with a ``batchbegin`` action.
2708
2708
2709 httprequest <method> <path>
2709 httprequest <method> <path>
2710 ---------------------------
2710 ---------------------------
2711
2711
2712 (HTTP peer only)
2712 (HTTP peer only)
2713
2713
2714 Send an HTTP request to the peer.
2714 Send an HTTP request to the peer.
2715
2715
2716 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2716 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2717
2717
2718 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2718 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2719 headers to add to the request. e.g. ``Accept: foo``.
2719 headers to add to the request. e.g. ``Accept: foo``.
2720
2720
2721 The following arguments are special:
2721 The following arguments are special:
2722
2722
2723 ``BODYFILE``
2723 ``BODYFILE``
2724 The content of the file defined as the value to this argument will be
2724 The content of the file defined as the value to this argument will be
2725 transferred verbatim as the HTTP request body.
2725 transferred verbatim as the HTTP request body.
2726
2726
2727 ``frame <type> <flags> <payload>``
2727 ``frame <type> <flags> <payload>``
2728 Send a unified protocol frame as part of the request body.
2728 Send a unified protocol frame as part of the request body.
2729
2729
2730 All frames will be collected and sent as the body to the HTTP
2730 All frames will be collected and sent as the body to the HTTP
2731 request.
2731 request.
2732
2732
2733 close
2733 close
2734 -----
2734 -----
2735
2735
2736 Close the connection to the server.
2736 Close the connection to the server.
2737
2737
2738 flush
2738 flush
2739 -----
2739 -----
2740
2740
2741 Flush data written to the server.
2741 Flush data written to the server.
2742
2742
2743 readavailable
2743 readavailable
2744 -------------
2744 -------------
2745
2745
2746 Close the write end of the connection and read all available data from
2746 Close the write end of the connection and read all available data from
2747 the server.
2747 the server.
2748
2748
2749 If the connection to the server encompasses multiple pipes, we poll both
2749 If the connection to the server encompasses multiple pipes, we poll both
2750 pipes and read available data.
2750 pipes and read available data.
2751
2751
2752 readline
2752 readline
2753 --------
2753 --------
2754
2754
2755 Read a line of output from the server. If there are multiple output
2755 Read a line of output from the server. If there are multiple output
2756 pipes, reads only the main pipe.
2756 pipes, reads only the main pipe.
2757
2757
2758 ereadline
2758 ereadline
2759 ---------
2759 ---------
2760
2760
2761 Like ``readline``, but read from the stderr pipe, if available.
2761 Like ``readline``, but read from the stderr pipe, if available.
2762
2762
2763 read <X>
2763 read <X>
2764 --------
2764 --------
2765
2765
2766 ``read()`` N bytes from the server's main output pipe.
2766 ``read()`` N bytes from the server's main output pipe.
2767
2767
2768 eread <X>
2768 eread <X>
2769 ---------
2769 ---------
2770
2770
2771 ``read()`` N bytes from the server's stderr pipe, if available.
2771 ``read()`` N bytes from the server's stderr pipe, if available.
2772
2772
2773 Specifying Unified Frame-Based Protocol Frames
2773 Specifying Unified Frame-Based Protocol Frames
2774 ----------------------------------------------
2774 ----------------------------------------------
2775
2775
2776 It is possible to emit a *Unified Frame-Based Protocol* by using special
2776 It is possible to emit a *Unified Frame-Based Protocol* by using special
2777 syntax.
2777 syntax.
2778
2778
2779 A frame is composed as a type, flags, and payload. These can be parsed
2779 A frame is composed as a type, flags, and payload. These can be parsed
2780 from a string of the form ``<requestid> <type> <flags> <payload>``. That is,
2780 from a string of the form:
2781 4 space-delimited strings.
2781
2782
2782 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
2783 ``payload`` is the simplest: it is evaluated as a Python byte string
2783
2784 literal.
2784 ``request-id`` and ``stream-id`` are integers defining the request and
2785
2785 stream identifiers.
2786 ``requestid`` is an integer defining the request identifier.
2787
2786
2788 ``type`` can be an integer value for the frame type or the string name
2787 ``type`` can be an integer value for the frame type or the string name
2789 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
2788 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
2790 ``command-name``.
2789 ``command-name``.
2791
2790
2792 ``flags`` is a ``|`` delimited list of flag components. Each component
2791 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
2793 (and there can be just one) can be an integer or a flag name for the
2792 components. Each component (and there can be just one) can be an integer
2794 specified frame type. Values are resolved to integers and then bitwise
2793 or a flag name for stream flags or frame flags, respectively. Values are
2795 OR'd together.
2794 resolved to integers and then bitwise OR'd together.
2795
2796 ``payload`` is is evaluated as a Python byte string literal.
2796 """
2797 """
2797 opts = pycompat.byteskwargs(opts)
2798 opts = pycompat.byteskwargs(opts)
2798
2799
2799 if opts['localssh'] and not repo:
2800 if opts['localssh'] and not repo:
2800 raise error.Abort(_('--localssh requires a repository'))
2801 raise error.Abort(_('--localssh requires a repository'))
2801
2802
2802 if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'):
2803 if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'):
2803 raise error.Abort(_('invalid value for --peer'),
2804 raise error.Abort(_('invalid value for --peer'),
2804 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2805 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2805
2806
2806 if path and opts['localssh']:
2807 if path and opts['localssh']:
2807 raise error.Abort(_('cannot specify --localssh with an explicit '
2808 raise error.Abort(_('cannot specify --localssh with an explicit '
2808 'path'))
2809 'path'))
2809
2810
2810 if ui.interactive():
2811 if ui.interactive():
2811 ui.write(_('(waiting for commands on stdin)\n'))
2812 ui.write(_('(waiting for commands on stdin)\n'))
2812
2813
2813 blocks = list(_parsewirelangblocks(ui.fin))
2814 blocks = list(_parsewirelangblocks(ui.fin))
2814
2815
2815 proc = None
2816 proc = None
2816 stdin = None
2817 stdin = None
2817 stdout = None
2818 stdout = None
2818 stderr = None
2819 stderr = None
2819 opener = None
2820 opener = None
2820
2821
2821 if opts['localssh']:
2822 if opts['localssh']:
2822 # We start the SSH server in its own process so there is process
2823 # We start the SSH server in its own process so there is process
2823 # separation. This prevents a whole class of potential bugs around
2824 # separation. This prevents a whole class of potential bugs around
2824 # shared state from interfering with server operation.
2825 # shared state from interfering with server operation.
2825 args = procutil.hgcmd() + [
2826 args = procutil.hgcmd() + [
2826 '-R', repo.root,
2827 '-R', repo.root,
2827 'debugserve', '--sshstdio',
2828 'debugserve', '--sshstdio',
2828 ]
2829 ]
2829 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
2830 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
2830 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
2831 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
2831 bufsize=0)
2832 bufsize=0)
2832
2833
2833 stdin = proc.stdin
2834 stdin = proc.stdin
2834 stdout = proc.stdout
2835 stdout = proc.stdout
2835 stderr = proc.stderr
2836 stderr = proc.stderr
2836
2837
2837 # We turn the pipes into observers so we can log I/O.
2838 # We turn the pipes into observers so we can log I/O.
2838 if ui.verbose or opts['peer'] == 'raw':
2839 if ui.verbose or opts['peer'] == 'raw':
2839 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
2840 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
2840 logdata=True)
2841 logdata=True)
2841 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
2842 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
2842 logdata=True)
2843 logdata=True)
2843 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
2844 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
2844 logdata=True)
2845 logdata=True)
2845
2846
2846 # --localssh also implies the peer connection settings.
2847 # --localssh also implies the peer connection settings.
2847
2848
2848 url = 'ssh://localserver'
2849 url = 'ssh://localserver'
2849 autoreadstderr = not opts['noreadstderr']
2850 autoreadstderr = not opts['noreadstderr']
2850
2851
2851 if opts['peer'] == 'ssh1':
2852 if opts['peer'] == 'ssh1':
2852 ui.write(_('creating ssh peer for wire protocol version 1\n'))
2853 ui.write(_('creating ssh peer for wire protocol version 1\n'))
2853 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
2854 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
2854 None, autoreadstderr=autoreadstderr)
2855 None, autoreadstderr=autoreadstderr)
2855 elif opts['peer'] == 'ssh2':
2856 elif opts['peer'] == 'ssh2':
2856 ui.write(_('creating ssh peer for wire protocol version 2\n'))
2857 ui.write(_('creating ssh peer for wire protocol version 2\n'))
2857 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
2858 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
2858 None, autoreadstderr=autoreadstderr)
2859 None, autoreadstderr=autoreadstderr)
2859 elif opts['peer'] == 'raw':
2860 elif opts['peer'] == 'raw':
2860 ui.write(_('using raw connection to peer\n'))
2861 ui.write(_('using raw connection to peer\n'))
2861 peer = None
2862 peer = None
2862 else:
2863 else:
2863 ui.write(_('creating ssh peer from handshake results\n'))
2864 ui.write(_('creating ssh peer from handshake results\n'))
2864 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2865 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2865 autoreadstderr=autoreadstderr)
2866 autoreadstderr=autoreadstderr)
2866
2867
2867 elif path:
2868 elif path:
2868 # We bypass hg.peer() so we can proxy the sockets.
2869 # We bypass hg.peer() so we can proxy the sockets.
2869 # TODO consider not doing this because we skip
2870 # TODO consider not doing this because we skip
2870 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
2871 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
2871 u = util.url(path)
2872 u = util.url(path)
2872 if u.scheme != 'http':
2873 if u.scheme != 'http':
2873 raise error.Abort(_('only http:// paths are currently supported'))
2874 raise error.Abort(_('only http:// paths are currently supported'))
2874
2875
2875 url, authinfo = u.authinfo()
2876 url, authinfo = u.authinfo()
2876 openerargs = {}
2877 openerargs = {}
2877
2878
2878 # Turn pipes/sockets into observers so we can log I/O.
2879 # Turn pipes/sockets into observers so we can log I/O.
2879 if ui.verbose:
2880 if ui.verbose:
2880 openerargs = {
2881 openerargs = {
2881 r'loggingfh': ui,
2882 r'loggingfh': ui,
2882 r'loggingname': b's',
2883 r'loggingname': b's',
2883 r'loggingopts': {
2884 r'loggingopts': {
2884 r'logdata': True,
2885 r'logdata': True,
2885 r'logdataapis': False,
2886 r'logdataapis': False,
2886 },
2887 },
2887 }
2888 }
2888
2889
2889 if ui.debugflag:
2890 if ui.debugflag:
2890 openerargs[r'loggingopts'][r'logdataapis'] = True
2891 openerargs[r'loggingopts'][r'logdataapis'] = True
2891
2892
2892 # Don't send default headers when in raw mode. This allows us to
2893 # Don't send default headers when in raw mode. This allows us to
2893 # bypass most of the behavior of our URL handling code so we can
2894 # bypass most of the behavior of our URL handling code so we can
2894 # have near complete control over what's sent on the wire.
2895 # have near complete control over what's sent on the wire.
2895 if opts['peer'] == 'raw':
2896 if opts['peer'] == 'raw':
2896 openerargs[r'sendaccept'] = False
2897 openerargs[r'sendaccept'] = False
2897
2898
2898 opener = urlmod.opener(ui, authinfo, **openerargs)
2899 opener = urlmod.opener(ui, authinfo, **openerargs)
2899
2900
2900 if opts['peer'] == 'raw':
2901 if opts['peer'] == 'raw':
2901 ui.write(_('using raw connection to peer\n'))
2902 ui.write(_('using raw connection to peer\n'))
2902 peer = None
2903 peer = None
2903 elif opts['peer']:
2904 elif opts['peer']:
2904 raise error.Abort(_('--peer %s not supported with HTTP peers') %
2905 raise error.Abort(_('--peer %s not supported with HTTP peers') %
2905 opts['peer'])
2906 opts['peer'])
2906 else:
2907 else:
2907 peer = httppeer.httppeer(ui, path, url, opener)
2908 peer = httppeer.httppeer(ui, path, url, opener)
2908 peer._fetchcaps()
2909 peer._fetchcaps()
2909
2910
2910 # We /could/ populate stdin/stdout with sock.makefile()...
2911 # We /could/ populate stdin/stdout with sock.makefile()...
2911 else:
2912 else:
2912 raise error.Abort(_('unsupported connection configuration'))
2913 raise error.Abort(_('unsupported connection configuration'))
2913
2914
2914 batchedcommands = None
2915 batchedcommands = None
2915
2916
2916 # Now perform actions based on the parsed wire language instructions.
2917 # Now perform actions based on the parsed wire language instructions.
2917 for action, lines in blocks:
2918 for action, lines in blocks:
2918 if action in ('raw', 'raw+'):
2919 if action in ('raw', 'raw+'):
2919 if not stdin:
2920 if not stdin:
2920 raise error.Abort(_('cannot call raw/raw+ on this peer'))
2921 raise error.Abort(_('cannot call raw/raw+ on this peer'))
2921
2922
2922 # Concatenate the data together.
2923 # Concatenate the data together.
2923 data = ''.join(l.lstrip() for l in lines)
2924 data = ''.join(l.lstrip() for l in lines)
2924 data = stringutil.unescapestr(data)
2925 data = stringutil.unescapestr(data)
2925 stdin.write(data)
2926 stdin.write(data)
2926
2927
2927 if action == 'raw+':
2928 if action == 'raw+':
2928 stdin.flush()
2929 stdin.flush()
2929 elif action == 'flush':
2930 elif action == 'flush':
2930 if not stdin:
2931 if not stdin:
2931 raise error.Abort(_('cannot call flush on this peer'))
2932 raise error.Abort(_('cannot call flush on this peer'))
2932 stdin.flush()
2933 stdin.flush()
2933 elif action.startswith('command'):
2934 elif action.startswith('command'):
2934 if not peer:
2935 if not peer:
2935 raise error.Abort(_('cannot send commands unless peer instance '
2936 raise error.Abort(_('cannot send commands unless peer instance '
2936 'is available'))
2937 'is available'))
2937
2938
2938 command = action.split(' ', 1)[1]
2939 command = action.split(' ', 1)[1]
2939
2940
2940 args = {}
2941 args = {}
2941 for line in lines:
2942 for line in lines:
2942 # We need to allow empty values.
2943 # We need to allow empty values.
2943 fields = line.lstrip().split(' ', 1)
2944 fields = line.lstrip().split(' ', 1)
2944 if len(fields) == 1:
2945 if len(fields) == 1:
2945 key = fields[0]
2946 key = fields[0]
2946 value = ''
2947 value = ''
2947 else:
2948 else:
2948 key, value = fields
2949 key, value = fields
2949
2950
2950 args[key] = stringutil.unescapestr(value)
2951 args[key] = stringutil.unescapestr(value)
2951
2952
2952 if batchedcommands is not None:
2953 if batchedcommands is not None:
2953 batchedcommands.append((command, args))
2954 batchedcommands.append((command, args))
2954 continue
2955 continue
2955
2956
2956 ui.status(_('sending %s command\n') % command)
2957 ui.status(_('sending %s command\n') % command)
2957
2958
2958 if 'PUSHFILE' in args:
2959 if 'PUSHFILE' in args:
2959 with open(args['PUSHFILE'], r'rb') as fh:
2960 with open(args['PUSHFILE'], r'rb') as fh:
2960 del args['PUSHFILE']
2961 del args['PUSHFILE']
2961 res, output = peer._callpush(command, fh,
2962 res, output = peer._callpush(command, fh,
2962 **pycompat.strkwargs(args))
2963 **pycompat.strkwargs(args))
2963 ui.status(_('result: %s\n') % stringutil.escapedata(res))
2964 ui.status(_('result: %s\n') % stringutil.escapedata(res))
2964 ui.status(_('remote output: %s\n') %
2965 ui.status(_('remote output: %s\n') %
2965 stringutil.escapedata(output))
2966 stringutil.escapedata(output))
2966 else:
2967 else:
2967 res = peer._call(command, **pycompat.strkwargs(args))
2968 res = peer._call(command, **pycompat.strkwargs(args))
2968 ui.status(_('response: %s\n') % stringutil.escapedata(res))
2969 ui.status(_('response: %s\n') % stringutil.escapedata(res))
2969
2970
2970 elif action == 'batchbegin':
2971 elif action == 'batchbegin':
2971 if batchedcommands is not None:
2972 if batchedcommands is not None:
2972 raise error.Abort(_('nested batchbegin not allowed'))
2973 raise error.Abort(_('nested batchbegin not allowed'))
2973
2974
2974 batchedcommands = []
2975 batchedcommands = []
2975 elif action == 'batchsubmit':
2976 elif action == 'batchsubmit':
2976 # There is a batching API we could go through. But it would be
2977 # There is a batching API we could go through. But it would be
2977 # difficult to normalize requests into function calls. It is easier
2978 # difficult to normalize requests into function calls. It is easier
2978 # to bypass this layer and normalize to commands + args.
2979 # to bypass this layer and normalize to commands + args.
2979 ui.status(_('sending batch with %d sub-commands\n') %
2980 ui.status(_('sending batch with %d sub-commands\n') %
2980 len(batchedcommands))
2981 len(batchedcommands))
2981 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
2982 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
2982 ui.status(_('response #%d: %s\n') %
2983 ui.status(_('response #%d: %s\n') %
2983 (i, stringutil.escapedata(chunk)))
2984 (i, stringutil.escapedata(chunk)))
2984
2985
2985 batchedcommands = None
2986 batchedcommands = None
2986
2987
2987 elif action.startswith('httprequest '):
2988 elif action.startswith('httprequest '):
2988 if not opener:
2989 if not opener:
2989 raise error.Abort(_('cannot use httprequest without an HTTP '
2990 raise error.Abort(_('cannot use httprequest without an HTTP '
2990 'peer'))
2991 'peer'))
2991
2992
2992 request = action.split(' ', 2)
2993 request = action.split(' ', 2)
2993 if len(request) != 3:
2994 if len(request) != 3:
2994 raise error.Abort(_('invalid httprequest: expected format is '
2995 raise error.Abort(_('invalid httprequest: expected format is '
2995 '"httprequest <method> <path>'))
2996 '"httprequest <method> <path>'))
2996
2997
2997 method, httppath = request[1:]
2998 method, httppath = request[1:]
2998 headers = {}
2999 headers = {}
2999 body = None
3000 body = None
3000 frames = []
3001 frames = []
3001 for line in lines:
3002 for line in lines:
3002 line = line.lstrip()
3003 line = line.lstrip()
3003 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3004 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3004 if m:
3005 if m:
3005 headers[m.group(1)] = m.group(2)
3006 headers[m.group(1)] = m.group(2)
3006 continue
3007 continue
3007
3008
3008 if line.startswith(b'BODYFILE '):
3009 if line.startswith(b'BODYFILE '):
3009 with open(line.split(b' ', 1), 'rb') as fh:
3010 with open(line.split(b' ', 1), 'rb') as fh:
3010 body = fh.read()
3011 body = fh.read()
3011 elif line.startswith(b'frame '):
3012 elif line.startswith(b'frame '):
3012 frame = wireprotoframing.makeframefromhumanstring(
3013 frame = wireprotoframing.makeframefromhumanstring(
3013 line[len(b'frame '):])
3014 line[len(b'frame '):])
3014
3015
3015 frames.append(frame)
3016 frames.append(frame)
3016 else:
3017 else:
3017 raise error.Abort(_('unknown argument to httprequest: %s') %
3018 raise error.Abort(_('unknown argument to httprequest: %s') %
3018 line)
3019 line)
3019
3020
3020 url = path + httppath
3021 url = path + httppath
3021
3022
3022 if frames:
3023 if frames:
3023 body = b''.join(bytes(f) for f in frames)
3024 body = b''.join(bytes(f) for f in frames)
3024
3025
3025 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3026 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3026
3027
3027 # urllib.Request insists on using has_data() as a proxy for
3028 # urllib.Request insists on using has_data() as a proxy for
3028 # determining the request method. Override that to use our
3029 # determining the request method. Override that to use our
3029 # explicitly requested method.
3030 # explicitly requested method.
3030 req.get_method = lambda: method
3031 req.get_method = lambda: method
3031
3032
3032 try:
3033 try:
3033 opener.open(req).read()
3034 opener.open(req).read()
3034 except util.urlerr.urlerror as e:
3035 except util.urlerr.urlerror as e:
3035 e.read()
3036 e.read()
3036
3037
3037 elif action == 'close':
3038 elif action == 'close':
3038 peer.close()
3039 peer.close()
3039 elif action == 'readavailable':
3040 elif action == 'readavailable':
3040 if not stdout or not stderr:
3041 if not stdout or not stderr:
3041 raise error.Abort(_('readavailable not available on this peer'))
3042 raise error.Abort(_('readavailable not available on this peer'))
3042
3043
3043 stdin.close()
3044 stdin.close()
3044 stdout.read()
3045 stdout.read()
3045 stderr.read()
3046 stderr.read()
3046
3047
3047 elif action == 'readline':
3048 elif action == 'readline':
3048 if not stdout:
3049 if not stdout:
3049 raise error.Abort(_('readline not available on this peer'))
3050 raise error.Abort(_('readline not available on this peer'))
3050 stdout.readline()
3051 stdout.readline()
3051 elif action == 'ereadline':
3052 elif action == 'ereadline':
3052 if not stderr:
3053 if not stderr:
3053 raise error.Abort(_('ereadline not available on this peer'))
3054 raise error.Abort(_('ereadline not available on this peer'))
3054 stderr.readline()
3055 stderr.readline()
3055 elif action.startswith('read '):
3056 elif action.startswith('read '):
3056 count = int(action.split(' ', 1)[1])
3057 count = int(action.split(' ', 1)[1])
3057 if not stdout:
3058 if not stdout:
3058 raise error.Abort(_('read not available on this peer'))
3059 raise error.Abort(_('read not available on this peer'))
3059 stdout.read(count)
3060 stdout.read(count)
3060 elif action.startswith('eread '):
3061 elif action.startswith('eread '):
3061 count = int(action.split(' ', 1)[1])
3062 count = int(action.split(' ', 1)[1])
3062 if not stderr:
3063 if not stderr:
3063 raise error.Abort(_('eread not available on this peer'))
3064 raise error.Abort(_('eread not available on this peer'))
3064 stderr.read(count)
3065 stderr.read(count)
3065 else:
3066 else:
3066 raise error.Abort(_('unknown action: %s') % action)
3067 raise error.Abort(_('unknown action: %s') % action)
3067
3068
3068 if batchedcommands is not None:
3069 if batchedcommands is not None:
3069 raise error.Abort(_('unclosed "batchbegin" request'))
3070 raise error.Abort(_('unclosed "batchbegin" request'))
3070
3071
3071 if peer:
3072 if peer:
3072 peer.close()
3073 peer.close()
3073
3074
3074 if proc:
3075 if proc:
3075 proc.kill()
3076 proc.kill()
@@ -1,1472 +1,1612 b''
1 The Mercurial wire protocol is a request-response based protocol
1 The Mercurial wire protocol is a request-response based protocol
2 with multiple wire representations.
2 with multiple wire representations.
3
3
4 Each request is modeled as a command name, a dictionary of arguments, and
4 Each request is modeled as a command name, a dictionary of arguments, and
5 optional raw input. Command arguments and their types are intrinsic
5 optional raw input. Command arguments and their types are intrinsic
6 properties of commands. So is the response type of the command. This means
6 properties of commands. So is the response type of the command. This means
7 clients can't always send arbitrary arguments to servers and servers can't
7 clients can't always send arbitrary arguments to servers and servers can't
8 return multiple response types.
8 return multiple response types.
9
9
10 The protocol is synchronous and does not support multiplexing (concurrent
10 The protocol is synchronous and does not support multiplexing (concurrent
11 commands).
11 commands).
12
12
13 Handshake
13 Handshake
14 =========
14 =========
15
15
16 It is required or common for clients to perform a *handshake* when connecting
16 It is required or common for clients to perform a *handshake* when connecting
17 to a server. The handshake serves the following purposes:
17 to a server. The handshake serves the following purposes:
18
18
19 * Negotiating protocol/transport level options
19 * Negotiating protocol/transport level options
20 * Allows the client to learn about server capabilities to influence
20 * Allows the client to learn about server capabilities to influence
21 future requests
21 future requests
22 * Ensures the underlying transport channel is in a *clean* state
22 * Ensures the underlying transport channel is in a *clean* state
23
23
24 An important goal of the handshake is to allow clients to use more modern
24 An important goal of the handshake is to allow clients to use more modern
25 wire protocol features. By default, clients must assume they are talking
25 wire protocol features. By default, clients must assume they are talking
26 to an old version of Mercurial server (possibly even the very first
26 to an old version of Mercurial server (possibly even the very first
27 implementation). So, clients should not attempt to call or utilize modern
27 implementation). So, clients should not attempt to call or utilize modern
28 wire protocol features until they have confirmation that the server
28 wire protocol features until they have confirmation that the server
29 supports them. The handshake implementation is designed to allow both
29 supports them. The handshake implementation is designed to allow both
30 ends to utilize the latest set of features and capabilities with as
30 ends to utilize the latest set of features and capabilities with as
31 few round trips as possible.
31 few round trips as possible.
32
32
33 The handshake mechanism varies by transport and protocol and is documented
33 The handshake mechanism varies by transport and protocol and is documented
34 in the sections below.
34 in the sections below.
35
35
36 HTTP Protocol
36 HTTP Protocol
37 =============
37 =============
38
38
39 Handshake
39 Handshake
40 ---------
40 ---------
41
41
42 The client sends a ``capabilities`` command request (``?cmd=capabilities``)
42 The client sends a ``capabilities`` command request (``?cmd=capabilities``)
43 as soon as HTTP requests may be issued.
43 as soon as HTTP requests may be issued.
44
44
45 The server responds with a capabilities string, which the client parses to
45 The server responds with a capabilities string, which the client parses to
46 learn about the server's abilities.
46 learn about the server's abilities.
47
47
48 HTTP Version 1 Transport
48 HTTP Version 1 Transport
49 ------------------------
49 ------------------------
50
50
51 Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are
51 Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are
52 sent to the base URL of the repository with the command name sent in
52 sent to the base URL of the repository with the command name sent in
53 the ``cmd`` query string parameter. e.g.
53 the ``cmd`` query string parameter. e.g.
54 ``https://example.com/repo?cmd=capabilities``. The HTTP method is ``GET``
54 ``https://example.com/repo?cmd=capabilities``. The HTTP method is ``GET``
55 or ``POST`` depending on the command and whether there is a request
55 or ``POST`` depending on the command and whether there is a request
56 body.
56 body.
57
57
58 Command arguments can be sent multiple ways.
58 Command arguments can be sent multiple ways.
59
59
60 The simplest is part of the URL query string using ``x-www-form-urlencoded``
60 The simplest is part of the URL query string using ``x-www-form-urlencoded``
61 encoding (see Python's ``urllib.urlencode()``. However, many servers impose
61 encoding (see Python's ``urllib.urlencode()``. However, many servers impose
62 length limitations on the URL. So this mechanism is typically only used if
62 length limitations on the URL. So this mechanism is typically only used if
63 the server doesn't support other mechanisms.
63 the server doesn't support other mechanisms.
64
64
65 If the server supports the ``httpheader`` capability, command arguments can
65 If the server supports the ``httpheader`` capability, command arguments can
66 be sent in HTTP request headers named ``X-HgArg-<N>`` where ``<N>`` is an
66 be sent in HTTP request headers named ``X-HgArg-<N>`` where ``<N>`` is an
67 integer starting at 1. A ``x-www-form-urlencoded`` representation of the
67 integer starting at 1. A ``x-www-form-urlencoded`` representation of the
68 arguments is obtained. This full string is then split into chunks and sent
68 arguments is obtained. This full string is then split into chunks and sent
69 in numbered ``X-HgArg-<N>`` headers. The maximum length of each HTTP header
69 in numbered ``X-HgArg-<N>`` headers. The maximum length of each HTTP header
70 is defined by the server in the ``httpheader`` capability value, which defaults
70 is defined by the server in the ``httpheader`` capability value, which defaults
71 to ``1024``. The server reassembles the encoded arguments string by
71 to ``1024``. The server reassembles the encoded arguments string by
72 concatenating the ``X-HgArg-<N>`` headers then URL decodes them into a
72 concatenating the ``X-HgArg-<N>`` headers then URL decodes them into a
73 dictionary.
73 dictionary.
74
74
75 The list of ``X-HgArg-<N>`` headers should be added to the ``Vary`` request
75 The list of ``X-HgArg-<N>`` headers should be added to the ``Vary`` request
76 header to instruct caches to take these headers into consideration when caching
76 header to instruct caches to take these headers into consideration when caching
77 requests.
77 requests.
78
78
79 If the server supports the ``httppostargs`` capability, the client
79 If the server supports the ``httppostargs`` capability, the client
80 may send command arguments in the HTTP request body as part of an
80 may send command arguments in the HTTP request body as part of an
81 HTTP POST request. The command arguments will be URL encoded just like
81 HTTP POST request. The command arguments will be URL encoded just like
82 they would for sending them via HTTP headers. However, no splitting is
82 they would for sending them via HTTP headers. However, no splitting is
83 performed: the raw arguments are included in the HTTP request body.
83 performed: the raw arguments are included in the HTTP request body.
84
84
85 The client sends a ``X-HgArgs-Post`` header with the string length of the
85 The client sends a ``X-HgArgs-Post`` header with the string length of the
86 encoded arguments data. Additional data may be included in the HTTP
86 encoded arguments data. Additional data may be included in the HTTP
87 request body immediately following the argument data. The offset of the
87 request body immediately following the argument data. The offset of the
88 non-argument data is defined by the ``X-HgArgs-Post`` header. The
88 non-argument data is defined by the ``X-HgArgs-Post`` header. The
89 ``X-HgArgs-Post`` header is not required if there is no argument data.
89 ``X-HgArgs-Post`` header is not required if there is no argument data.
90
90
91 Additional command data can be sent as part of the HTTP request body. The
91 Additional command data can be sent as part of the HTTP request body. The
92 default ``Content-Type`` when sending data is ``application/mercurial-0.1``.
92 default ``Content-Type`` when sending data is ``application/mercurial-0.1``.
93 A ``Content-Length`` header is currently always sent.
93 A ``Content-Length`` header is currently always sent.
94
94
95 Example HTTP requests::
95 Example HTTP requests::
96
96
97 GET /repo?cmd=capabilities
97 GET /repo?cmd=capabilities
98 X-HgArg-1: foo=bar&baz=hello%20world
98 X-HgArg-1: foo=bar&baz=hello%20world
99
99
100 The request media type should be chosen based on server support. If the
100 The request media type should be chosen based on server support. If the
101 ``httpmediatype`` server capability is present, the client should send
101 ``httpmediatype`` server capability is present, the client should send
102 the newest mutually supported media type. If this capability is absent,
102 the newest mutually supported media type. If this capability is absent,
103 the client must assume the server only supports the
103 the client must assume the server only supports the
104 ``application/mercurial-0.1`` media type.
104 ``application/mercurial-0.1`` media type.
105
105
106 The ``Content-Type`` HTTP response header identifies the response as coming
106 The ``Content-Type`` HTTP response header identifies the response as coming
107 from Mercurial and can also be used to signal an error has occurred.
107 from Mercurial and can also be used to signal an error has occurred.
108
108
109 The ``application/mercurial-*`` media types indicate a generic Mercurial
109 The ``application/mercurial-*`` media types indicate a generic Mercurial
110 data type.
110 data type.
111
111
112 The ``application/mercurial-0.1`` media type is raw Mercurial data. It is the
112 The ``application/mercurial-0.1`` media type is raw Mercurial data. It is the
113 predecessor of the format below.
113 predecessor of the format below.
114
114
115 The ``application/mercurial-0.2`` media type is compression framed Mercurial
115 The ``application/mercurial-0.2`` media type is compression framed Mercurial
116 data. The first byte of the payload indicates the length of the compression
116 data. The first byte of the payload indicates the length of the compression
117 format identifier that follows. Next are N bytes indicating the compression
117 format identifier that follows. Next are N bytes indicating the compression
118 format. e.g. ``zlib``. The remaining bytes are compressed according to that
118 format. e.g. ``zlib``. The remaining bytes are compressed according to that
119 compression format. The decompressed data behaves the same as with
119 compression format. The decompressed data behaves the same as with
120 ``application/mercurial-0.1``.
120 ``application/mercurial-0.1``.
121
121
122 The ``application/hg-error`` media type indicates a generic error occurred.
122 The ``application/hg-error`` media type indicates a generic error occurred.
123 The content of the HTTP response body typically holds text describing the
123 The content of the HTTP response body typically holds text describing the
124 error.
124 error.
125
125
126 The ``application/hg-changegroup`` media type indicates a changegroup response
126 The ``application/hg-changegroup`` media type indicates a changegroup response
127 type.
127 type.
128
128
129 Clients also accept the ``text/plain`` media type. All other media
129 Clients also accept the ``text/plain`` media type. All other media
130 types should cause the client to error.
130 types should cause the client to error.
131
131
132 Behavior of media types is further described in the ``Content Negotiation``
132 Behavior of media types is further described in the ``Content Negotiation``
133 section below.
133 section below.
134
134
135 Clients should issue a ``User-Agent`` request header that identifies the client.
135 Clients should issue a ``User-Agent`` request header that identifies the client.
136 The server should not use the ``User-Agent`` for feature detection.
136 The server should not use the ``User-Agent`` for feature detection.
137
137
138 A command returning a ``string`` response issues a
138 A command returning a ``string`` response issues a
139 ``application/mercurial-0.*`` media type and the HTTP response body contains
139 ``application/mercurial-0.*`` media type and the HTTP response body contains
140 the raw string value (after compression decoding, if used). A
140 the raw string value (after compression decoding, if used). A
141 ``Content-Length`` header is typically issued, but not required.
141 ``Content-Length`` header is typically issued, but not required.
142
142
143 A command returning a ``stream`` response issues a
143 A command returning a ``stream`` response issues a
144 ``application/mercurial-0.*`` media type and the HTTP response is typically
144 ``application/mercurial-0.*`` media type and the HTTP response is typically
145 using *chunked transfer* (``Transfer-Encoding: chunked``).
145 using *chunked transfer* (``Transfer-Encoding: chunked``).
146
146
147 HTTP Version 2 Transport
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 The command to run is specified in the POST payload as defined by the
206 The command to run is specified in the POST payload as defined by the
207 *Unified Frame-Based Protocol*. This is redundant with data already
207 *Unified Frame-Based Protocol*. This is redundant with data already
208 encoded in the URL. This is by design, so server operators can have
208 encoded in the URL. This is by design, so server operators can have
209 better understanding about server activity from looking merely at
209 better understanding about server activity from looking merely at
210 HTTP access logs.
210 HTTP access logs.
211
211
212 In most circumstances, the command specified in the URL MUST match
212 In most circumstances, the command specified in the URL MUST match
213 the command specified in the frame-based payload or the server will
213 the command specified in the frame-based payload or the server will
214 respond with an error. The exception to this is the special
214 respond with an error. The exception to this is the special
215 ``multirequest`` URL. (See below.) In addition, HTTP requests
215 ``multirequest`` URL. (See below.) In addition, HTTP requests
216 are limited to one command invocation. The exception is the special
216 are limited to one command invocation. The exception is the special
217 ``multirequest`` URL.
217 ``multirequest`` URL.
218
218
219 The ``multirequest`` command endpoints (``ro/multirequest`` and
219 The ``multirequest`` command endpoints (``ro/multirequest`` and
220 ``rw/multirequest``) are special in that they allow the execution of
220 ``rw/multirequest``) are special in that they allow the execution of
221 *any* command and allow the execution of multiple commands. If the
221 *any* command and allow the execution of multiple commands. If the
222 HTTP request issues multiple commands across multiple frames, all
222 HTTP request issues multiple commands across multiple frames, all
223 issued commands will be processed by the server. Per the defined
223 issued commands will be processed by the server. Per the defined
224 behavior of the *Unified Frame-Based Protocol*, commands may be
224 behavior of the *Unified Frame-Based Protocol*, commands may be
225 issued interleaved and responses may come back in a different order
225 issued interleaved and responses may come back in a different order
226 than they were issued. Clients MUST be able to deal with this.
226 than they were issued. Clients MUST be able to deal with this.
227
227
228 SSH Protocol
228 SSH Protocol
229 ============
229 ============
230
230
231 Handshake
231 Handshake
232 ---------
232 ---------
233
233
234 For all clients, the handshake consists of the client sending 1 or more
234 For all clients, the handshake consists of the client sending 1 or more
235 commands to the server using version 1 of the transport. Servers respond
235 commands to the server using version 1 of the transport. Servers respond
236 to commands they know how to respond to and send an empty response (``0\n``)
236 to commands they know how to respond to and send an empty response (``0\n``)
237 for unknown commands (per standard behavior of version 1 of the transport).
237 for unknown commands (per standard behavior of version 1 of the transport).
238 Clients then typically look for a response to the newest sent command to
238 Clients then typically look for a response to the newest sent command to
239 determine which transport version to use and what the available features for
239 determine which transport version to use and what the available features for
240 the connection and server are.
240 the connection and server are.
241
241
242 Preceding any response from client-issued commands, the server may print
242 Preceding any response from client-issued commands, the server may print
243 non-protocol output. It is common for SSH servers to print banners, message
243 non-protocol output. It is common for SSH servers to print banners, message
244 of the day announcements, etc when clients connect. It is assumed that any
244 of the day announcements, etc when clients connect. It is assumed that any
245 such *banner* output will precede any Mercurial server output. So clients
245 such *banner* output will precede any Mercurial server output. So clients
246 must be prepared to handle server output on initial connect that isn't
246 must be prepared to handle server output on initial connect that isn't
247 in response to any client-issued command and doesn't conform to Mercurial's
247 in response to any client-issued command and doesn't conform to Mercurial's
248 wire protocol. This *banner* output should only be on stdout. However,
248 wire protocol. This *banner* output should only be on stdout. However,
249 some servers may send output on stderr.
249 some servers may send output on stderr.
250
250
251 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
251 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
252 having the value
252 having the value
253 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
253 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
254
254
255 The ``between`` command has been supported since the original Mercurial
255 The ``between`` command has been supported since the original Mercurial
256 SSH server. Requesting the empty range will return a ``\n`` string response,
256 SSH server. Requesting the empty range will return a ``\n`` string response,
257 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
257 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
258 followed by the value, which happens to be a newline).
258 followed by the value, which happens to be a newline).
259
259
260 For pre 0.9.1 clients and all servers, the exchange looks like::
260 For pre 0.9.1 clients and all servers, the exchange looks like::
261
261
262 c: between\n
262 c: between\n
263 c: pairs 81\n
263 c: pairs 81\n
264 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
264 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
265 s: 1\n
265 s: 1\n
266 s: \n
266 s: \n
267
267
268 0.9.1+ clients send a ``hello`` command (with no arguments) before the
268 0.9.1+ clients send a ``hello`` command (with no arguments) before the
269 ``between`` command. The response to this command allows clients to
269 ``between`` command. The response to this command allows clients to
270 discover server capabilities and settings.
270 discover server capabilities and settings.
271
271
272 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
272 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
273 like::
273 like::
274
274
275 c: hello\n
275 c: hello\n
276 c: between\n
276 c: between\n
277 c: pairs 81\n
277 c: pairs 81\n
278 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
278 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
279 s: 324\n
279 s: 324\n
280 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
280 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
281 s: 1\n
281 s: 1\n
282 s: \n
282 s: \n
283
283
284 And a similar scenario but with servers sending a banner on connect::
284 And a similar scenario but with servers sending a banner on connect::
285
285
286 c: hello\n
286 c: hello\n
287 c: between\n
287 c: between\n
288 c: pairs 81\n
288 c: pairs 81\n
289 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
289 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
290 s: welcome to the server\n
290 s: welcome to the server\n
291 s: if you find any issues, email someone@somewhere.com\n
291 s: if you find any issues, email someone@somewhere.com\n
292 s: 324\n
292 s: 324\n
293 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
293 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
294 s: 1\n
294 s: 1\n
295 s: \n
295 s: \n
296
296
297 Note that output from the ``hello`` command is terminated by a ``\n``. This is
297 Note that output from the ``hello`` command is terminated by a ``\n``. This is
298 part of the response payload and not part of the wire protocol adding a newline
298 part of the response payload and not part of the wire protocol adding a newline
299 after responses. In other words, the length of the response contains the
299 after responses. In other words, the length of the response contains the
300 trailing ``\n``.
300 trailing ``\n``.
301
301
302 Clients supporting version 2 of the SSH transport send a line beginning
302 Clients supporting version 2 of the SSH transport send a line beginning
303 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
303 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
304 (which isn't a well-formed command line because it doesn't consist of a
304 (which isn't a well-formed command line because it doesn't consist of a
305 single command name) serves to both communicate the client's intent to
305 single command name) serves to both communicate the client's intent to
306 switch to transport version 2 (transports are version 1 by default) as
306 switch to transport version 2 (transports are version 1 by default) as
307 well as to advertise the client's transport-level capabilities so the
307 well as to advertise the client's transport-level capabilities so the
308 server may satisfy that request immediately.
308 server may satisfy that request immediately.
309
309
310 The upgrade line has the form:
310 The upgrade line has the form:
311
311
312 upgrade <token> <transport capabilities>
312 upgrade <token> <transport capabilities>
313
313
314 That is the literal string ``upgrade`` followed by a space, followed by
314 That is the literal string ``upgrade`` followed by a space, followed by
315 a randomly generated string, followed by a space, followed by a string
315 a randomly generated string, followed by a space, followed by a string
316 denoting the client's transport capabilities.
316 denoting the client's transport capabilities.
317
317
318 The token can be anything. However, a random UUID is recommended. (Use
318 The token can be anything. However, a random UUID is recommended. (Use
319 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
319 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
320 client's MAC address.)
320 client's MAC address.)
321
321
322 The transport capabilities string is a URL/percent encoded string
322 The transport capabilities string is a URL/percent encoded string
323 containing key-value pairs defining the client's transport-level
323 containing key-value pairs defining the client's transport-level
324 capabilities. The following capabilities are defined:
324 capabilities. The following capabilities are defined:
325
325
326 proto
326 proto
327 A comma-delimited list of transport protocol versions the client
327 A comma-delimited list of transport protocol versions the client
328 supports. e.g. ``ssh-v2``.
328 supports. e.g. ``ssh-v2``.
329
329
330 If the server does not recognize the ``upgrade`` line, it should issue
330 If the server does not recognize the ``upgrade`` line, it should issue
331 an empty response and continue processing the ``hello`` and ``between``
331 an empty response and continue processing the ``hello`` and ``between``
332 commands. Here is an example handshake between a version 2 aware client
332 commands. Here is an example handshake between a version 2 aware client
333 and a non version 2 aware server:
333 and a non version 2 aware server:
334
334
335 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
335 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
336 c: hello\n
336 c: hello\n
337 c: between\n
337 c: between\n
338 c: pairs 81\n
338 c: pairs 81\n
339 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
339 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
340 s: 0\n
340 s: 0\n
341 s: 324\n
341 s: 324\n
342 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
342 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
343 s: 1\n
343 s: 1\n
344 s: \n
344 s: \n
345
345
346 (The initial ``0\n`` line from the server indicates an empty response to
346 (The initial ``0\n`` line from the server indicates an empty response to
347 the unknown ``upgrade ..`` command/line.)
347 the unknown ``upgrade ..`` command/line.)
348
348
349 If the server recognizes the ``upgrade`` line and is willing to satisfy that
349 If the server recognizes the ``upgrade`` line and is willing to satisfy that
350 upgrade request, it replies to with a payload of the following form:
350 upgrade request, it replies to with a payload of the following form:
351
351
352 upgraded <token> <transport name>\n
352 upgraded <token> <transport name>\n
353
353
354 This line is the literal string ``upgraded``, a space, the token that was
354 This line is the literal string ``upgraded``, a space, the token that was
355 specified by the client in its ``upgrade ...`` request line, a space, and the
355 specified by the client in its ``upgrade ...`` request line, a space, and the
356 name of the transport protocol that was chosen by the server. The transport
356 name of the transport protocol that was chosen by the server. The transport
357 name MUST match one of the names the client specified in the ``proto`` field
357 name MUST match one of the names the client specified in the ``proto`` field
358 of its ``upgrade ...`` request line.
358 of its ``upgrade ...`` request line.
359
359
360 If a server issues an ``upgraded`` response, it MUST also read and ignore
360 If a server issues an ``upgraded`` response, it MUST also read and ignore
361 the lines associated with the ``hello`` and ``between`` command requests
361 the lines associated with the ``hello`` and ``between`` command requests
362 that were issued by the server. It is assumed that the negotiated transport
362 that were issued by the server. It is assumed that the negotiated transport
363 will respond with equivalent requested information following the transport
363 will respond with equivalent requested information following the transport
364 handshake.
364 handshake.
365
365
366 All data following the ``\n`` terminating the ``upgraded`` line is the
366 All data following the ``\n`` terminating the ``upgraded`` line is the
367 domain of the negotiated transport. It is common for the data immediately
367 domain of the negotiated transport. It is common for the data immediately
368 following to contain additional metadata about the state of the transport and
368 following to contain additional metadata about the state of the transport and
369 the server. However, this isn't strictly speaking part of the transport
369 the server. However, this isn't strictly speaking part of the transport
370 handshake and isn't covered by this section.
370 handshake and isn't covered by this section.
371
371
372 Here is an example handshake between a version 2 aware client and a version
372 Here is an example handshake between a version 2 aware client and a version
373 2 aware server:
373 2 aware server:
374
374
375 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
375 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
376 c: hello\n
376 c: hello\n
377 c: between\n
377 c: between\n
378 c: pairs 81\n
378 c: pairs 81\n
379 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
379 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
380 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
380 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
381 s: <additional transport specific data>
381 s: <additional transport specific data>
382
382
383 The client-issued token that is echoed in the response provides a more
383 The client-issued token that is echoed in the response provides a more
384 resilient mechanism for differentiating *banner* output from Mercurial
384 resilient mechanism for differentiating *banner* output from Mercurial
385 output. In version 1, properly formatted banner output could get confused
385 output. In version 1, properly formatted banner output could get confused
386 for Mercurial server output. By submitting a randomly generated token
386 for Mercurial server output. By submitting a randomly generated token
387 that is then present in the response, the client can look for that token
387 that is then present in the response, the client can look for that token
388 in response lines and have reasonable certainty that the line did not
388 in response lines and have reasonable certainty that the line did not
389 originate from a *banner* message.
389 originate from a *banner* message.
390
390
391 SSH Version 1 Transport
391 SSH Version 1 Transport
392 -----------------------
392 -----------------------
393
393
394 The SSH transport (version 1) is a custom text-based protocol suitable for
394 The SSH transport (version 1) is a custom text-based protocol suitable for
395 use over any bi-directional stream transport. It is most commonly used with
395 use over any bi-directional stream transport. It is most commonly used with
396 SSH.
396 SSH.
397
397
398 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
398 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
399 stderr, and stdout file descriptors of the started process are used to exchange
399 stderr, and stdout file descriptors of the started process are used to exchange
400 data. When Mercurial connects to a remote server over SSH, it actually starts
400 data. When Mercurial connects to a remote server over SSH, it actually starts
401 a ``hg serve --stdio`` process on the remote server.
401 a ``hg serve --stdio`` process on the remote server.
402
402
403 Commands are issued by sending the command name followed by a trailing newline
403 Commands are issued by sending the command name followed by a trailing newline
404 ``\n`` to the server. e.g. ``capabilities\n``.
404 ``\n`` to the server. e.g. ``capabilities\n``.
405
405
406 Command arguments are sent in the following format::
406 Command arguments are sent in the following format::
407
407
408 <argument> <length>\n<value>
408 <argument> <length>\n<value>
409
409
410 That is, the argument string name followed by a space followed by the
410 That is, the argument string name followed by a space followed by the
411 integer length of the value (expressed as a string) followed by a newline
411 integer length of the value (expressed as a string) followed by a newline
412 (``\n``) followed by the raw argument value.
412 (``\n``) followed by the raw argument value.
413
413
414 Dictionary arguments are encoded differently::
414 Dictionary arguments are encoded differently::
415
415
416 <argument> <# elements>\n
416 <argument> <# elements>\n
417 <key1> <length1>\n<value1>
417 <key1> <length1>\n<value1>
418 <key2> <length2>\n<value2>
418 <key2> <length2>\n<value2>
419 ...
419 ...
420
420
421 Non-argument data is sent immediately after the final argument value. It is
421 Non-argument data is sent immediately after the final argument value. It is
422 encoded in chunks::
422 encoded in chunks::
423
423
424 <length>\n<data>
424 <length>\n<data>
425
425
426 Each command declares a list of supported arguments and their types. If a
426 Each command declares a list of supported arguments and their types. If a
427 client sends an unknown argument to the server, the server should abort
427 client sends an unknown argument to the server, the server should abort
428 immediately. The special argument ``*`` in a command's definition indicates
428 immediately. The special argument ``*`` in a command's definition indicates
429 that all argument names are allowed.
429 that all argument names are allowed.
430
430
431 The definition of supported arguments and types is initially made when a
431 The definition of supported arguments and types is initially made when a
432 new command is implemented. The client and server must initially independently
432 new command is implemented. The client and server must initially independently
433 agree on the arguments and their types. This initial set of arguments can be
433 agree on the arguments and their types. This initial set of arguments can be
434 supplemented through the presence of *capabilities* advertised by the server.
434 supplemented through the presence of *capabilities* advertised by the server.
435
435
436 Each command has a defined expected response type.
436 Each command has a defined expected response type.
437
437
438 A ``string`` response type is a length framed value. The response consists of
438 A ``string`` response type is a length framed value. The response consists of
439 the string encoded integer length of a value followed by a newline (``\n``)
439 the string encoded integer length of a value followed by a newline (``\n``)
440 followed by the value. Empty values are allowed (and are represented as
440 followed by the value. Empty values are allowed (and are represented as
441 ``0\n``).
441 ``0\n``).
442
442
443 A ``stream`` response type consists of raw bytes of data. There is no framing.
443 A ``stream`` response type consists of raw bytes of data. There is no framing.
444
444
445 A generic error response type is also supported. It consists of a an error
445 A generic error response type is also supported. It consists of a an error
446 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
446 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
447 written to ``stdout``.
447 written to ``stdout``.
448
448
449 If the server receives an unknown command, it will send an empty ``string``
449 If the server receives an unknown command, it will send an empty ``string``
450 response.
450 response.
451
451
452 The server terminates if it receives an empty command (a ``\n`` character).
452 The server terminates if it receives an empty command (a ``\n`` character).
453
453
454 SSH Version 2 Transport
454 SSH Version 2 Transport
455 -----------------------
455 -----------------------
456
456
457 **Experimental and under development**
457 **Experimental and under development**
458
458
459 Version 2 of the SSH transport behaves identically to version 1 of the SSH
459 Version 2 of the SSH transport behaves identically to version 1 of the SSH
460 transport with the exception of handshake semantics. See above for how
460 transport with the exception of handshake semantics. See above for how
461 version 2 of the SSH transport is negotiated.
461 version 2 of the SSH transport is negotiated.
462
462
463 Immediately following the ``upgraded`` line signaling a switch to version
463 Immediately following the ``upgraded`` line signaling a switch to version
464 2 of the SSH protocol, the server automatically sends additional details
464 2 of the SSH protocol, the server automatically sends additional details
465 about the capabilities of the remote server. This has the form:
465 about the capabilities of the remote server. This has the form:
466
466
467 <integer length of value>\n
467 <integer length of value>\n
468 capabilities: ...\n
468 capabilities: ...\n
469
469
470 e.g.
470 e.g.
471
471
472 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
472 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
473 s: 240\n
473 s: 240\n
474 s: capabilities: known getbundle batch ...\n
474 s: capabilities: known getbundle batch ...\n
475
475
476 Following capabilities advertisement, the peers communicate using version
476 Following capabilities advertisement, the peers communicate using version
477 1 of the SSH transport.
477 1 of the SSH transport.
478
478
479 Unified Frame-Based Protocol
479 Unified Frame-Based Protocol
480 ============================
480 ============================
481
481
482 **Experimental and under development**
482 **Experimental and under development**
483
483
484 The *Unified Frame-Based Protocol* is a communications protocol between
484 The *Unified Frame-Based Protocol* is a communications protocol between
485 Mercurial peers. The protocol aims to be mostly transport agnostic
485 Mercurial peers. The protocol aims to be mostly transport agnostic
486 (works similarly on HTTP, SSH, etc).
486 (works similarly on HTTP, SSH, etc).
487
487
488 To operate the protocol, a bi-directional, half-duplex pipe supporting
488 To operate the protocol, a bi-directional, half-duplex pipe supporting
489 ordered sends and receives is required. That is, each peer has one pipe
489 ordered sends and receives is required. That is, each peer has one pipe
490 for sending data and another for receiving.
490 for sending data and another for receiving.
491
491
492 All data is read and written in atomic units called *frames*. These
493 are conceptually similar to TCP packets. Higher-level functionality
494 is built on the exchange and processing of frames.
495
496 All frames are associated with a *stream*. A *stream* provides a
497 unidirectional grouping of frames. Streams facilitate two goals:
498 content encoding and parallelism. There is a dedicated section on
499 streams below.
500
492 The protocol is request-response based: the client issues requests to
501 The protocol is request-response based: the client issues requests to
493 the server, which issues replies to those requests. Server-initiated
502 the server, which issues replies to those requests. Server-initiated
494 messaging is not currently supported, but this specification carves
503 messaging is not currently supported, but this specification carves
495 out room to implement it.
504 out room to implement it.
496
505
497 All data is read and written in atomic units called *frames*. These
498 are conceptually similar to TCP packets. Higher-level functionality
499 is built on the exchange and processing of frames.
500
501 All frames are associated with a numbered request. Frames can thus
506 All frames are associated with a numbered request. Frames can thus
502 be logically grouped by their request ID.
507 be logically grouped by their request ID.
503
508
504 Frames begin with a 6 octet header followed by a variable length
509 Frames begin with an 8 octet header followed by a variable length
505 payload::
510 payload::
506
511
507 +-----------------------------------------------+
512 +------------------------------------------------+
508 | Length (24) |
513 | Length (24) |
509 +---------------------------------+-------------+
514 +--------------------------------+---------------+
510 | Request ID (16) |
515 | Request ID (16) | Stream ID (8) |
511 +----------+-----------+----------+
516 +------------------+-------------+---------------+
512 | Type (4) | Flags (4) |
517 | Stream Flags (8) |
513 +==========+===========+========================================|
518 +-----------+------+
519 | Type (4) |
520 +-----------+
521 | Flags (4) |
522 +===========+===================================================|
514 | Frame Payload (0...) ...
523 | Frame Payload (0...) ...
515 +---------------------------------------------------------------+
524 +---------------------------------------------------------------+
516
525
517 The length of the frame payload is expressed as an unsigned 24 bit
526 The length of the frame payload is expressed as an unsigned 24 bit
518 little endian integer. Values larger than 65535 MUST NOT be used unless
527 little endian integer. Values larger than 65535 MUST NOT be used unless
519 given permission by the server as part of the negotiated capabilities
528 given permission by the server as part of the negotiated capabilities
520 during the handshake. The frame header is not part of the advertised
529 during the handshake. The frame header is not part of the advertised
521 frame length.
530 frame length. The payload length is the over-the-wire length. If there
531 is content encoding applied to the payload as part of the frame's stream,
532 the length is the output of that content encoding, not the input.
522
533
523 The 16-bit ``Request ID`` field denotes the integer request identifier,
534 The 16-bit ``Request ID`` field denotes the integer request identifier,
524 stored as an unsigned little endian integer. Odd numbered requests are
535 stored as an unsigned little endian integer. Odd numbered requests are
525 client-initiated. Even numbered requests are server-initiated. This
536 client-initiated. Even numbered requests are server-initiated. This
526 refers to where the *request* was initiated - not where the *frame* was
537 refers to where the *request* was initiated - not where the *frame* was
527 initiated, so servers will send frames with odd ``Request ID`` in
538 initiated, so servers will send frames with odd ``Request ID`` in
528 response to client-initiated requests. Implementations are advised to
539 response to client-initiated requests. Implementations are advised to
529 start ordering request identifiers at ``1`` and ``0``, increment by
540 start ordering request identifiers at ``1`` and ``0``, increment by
530 ``2``, and wrap around if all available numbers have been exhausted.
541 ``2``, and wrap around if all available numbers have been exhausted.
531
542
532 The 4-bit ``Type`` field denotes the type of message being sent.
543 The 8-bit ``Stream ID`` field denotes the stream that the frame is
544 associated with. Frames belonging to a stream may have content
545 encoding applied and the receiver may need to decode the raw frame
546 payload to obtain the original data. Odd numbered IDs are
547 client-initiated. Even numbered IDs are server-initiated.
548
549 The 8-bit ``Stream Flags`` field defines stream processing semantics.
550 See the section on streams below.
551
552 The 4-bit ``Type`` field denotes the type of frame being sent.
533
553
534 The 4-bit ``Flags`` field defines special, per-type attributes for
554 The 4-bit ``Flags`` field defines special, per-type attributes for
535 the frame.
555 the frame.
536
556
537 The sections below define the frame types and their behavior.
557 The sections below define the frame types and their behavior.
538
558
539 Command Request (``0x01``)
559 Command Request (``0x01``)
540 --------------------------
560 --------------------------
541
561
542 This frame contains a request to run a command.
562 This frame contains a request to run a command.
543
563
544 The name of the command to run constitutes the entirety of the frame
564 The name of the command to run constitutes the entirety of the frame
545 payload.
565 payload.
546
566
547 This frame type MUST ONLY be sent from clients to servers: it is illegal
567 This frame type MUST ONLY be sent from clients to servers: it is illegal
548 for a server to send this frame to a client.
568 for a server to send this frame to a client.
549
569
550 The following flag values are defined for this type:
570 The following flag values are defined for this type:
551
571
552 0x01
572 0x01
553 End of command data. When set, the client will not send any command
573 End of command data. When set, the client will not send any command
554 arguments or additional command data. When set, the command has been
574 arguments or additional command data. When set, the command has been
555 fully issued and the server has the full context to process the command.
575 fully issued and the server has the full context to process the command.
556 The next frame issued by the client is not part of this command.
576 The next frame issued by the client is not part of this command.
557 0x02
577 0x02
558 Command argument frames expected. When set, the client will send
578 Command argument frames expected. When set, the client will send
559 *Command Argument* frames containing command argument data.
579 *Command Argument* frames containing command argument data.
560 0x04
580 0x04
561 Command data frames expected. When set, the client will send
581 Command data frames expected. When set, the client will send
562 *Command Data* frames containing a raw stream of data for this
582 *Command Data* frames containing a raw stream of data for this
563 command.
583 command.
564
584
565 The ``0x01`` flag is mutually exclusive with both the ``0x02`` and ``0x04``
585 The ``0x01`` flag is mutually exclusive with both the ``0x02`` and ``0x04``
566 flags.
586 flags.
567
587
568 Command Argument (``0x02``)
588 Command Argument (``0x02``)
569 ---------------------------
589 ---------------------------
570
590
571 This frame contains a named argument for a command.
591 This frame contains a named argument for a command.
572
592
573 The frame type MUST ONLY be sent from clients to servers: it is illegal
593 The frame type MUST ONLY be sent from clients to servers: it is illegal
574 for a server to send this frame to a client.
594 for a server to send this frame to a client.
575
595
576 The payload consists of:
596 The payload consists of:
577
597
578 * A 16-bit little endian integer denoting the length of the
598 * A 16-bit little endian integer denoting the length of the
579 argument name.
599 argument name.
580 * A 16-bit little endian integer denoting the length of the
600 * A 16-bit little endian integer denoting the length of the
581 argument value.
601 argument value.
582 * N bytes of ASCII data containing the argument name.
602 * N bytes of ASCII data containing the argument name.
583 * N bytes of binary data containing the argument value.
603 * N bytes of binary data containing the argument value.
584
604
585 The payload MUST hold the entirety of the 32-bit header and the
605 The payload MUST hold the entirety of the 32-bit header and the
586 argument name. The argument value MAY span multiple frames. If this
606 argument name. The argument value MAY span multiple frames. If this
587 occurs, the appropriate frame flag should be set to indicate this.
607 occurs, the appropriate frame flag should be set to indicate this.
588
608
589 The following flag values are defined for this type:
609 The following flag values are defined for this type:
590
610
591 0x01
611 0x01
592 Argument data continuation. When set, the data for this argument did
612 Argument data continuation. When set, the data for this argument did
593 not fit in a single frame and the next frame will contain additional
613 not fit in a single frame and the next frame will contain additional
594 argument data.
614 argument data.
595
615
596 0x02
616 0x02
597 End of arguments data. When set, the client will not send any more
617 End of arguments data. When set, the client will not send any more
598 command arguments for the command this frame is associated with.
618 command arguments for the command this frame is associated with.
599 The next frame issued by the client will be command data or
619 The next frame issued by the client will be command data or
600 belong to a separate request.
620 belong to a separate request.
601
621
602 Command Data (``0x03``)
622 Command Data (``0x03``)
603 -----------------------
623 -----------------------
604
624
605 This frame contains raw data for a command.
625 This frame contains raw data for a command.
606
626
607 Most commands can be executed by specifying arguments. However,
627 Most commands can be executed by specifying arguments. However,
608 arguments have an upper bound to their length. For commands that
628 arguments have an upper bound to their length. For commands that
609 accept data that is beyond this length or whose length isn't known
629 accept data that is beyond this length or whose length isn't known
610 when the command is initially sent, they will need to stream
630 when the command is initially sent, they will need to stream
611 arbitrary data to the server. This frame type facilitates the sending
631 arbitrary data to the server. This frame type facilitates the sending
612 of this data.
632 of this data.
613
633
614 The payload of this frame type consists of a stream of raw data to be
634 The payload of this frame type consists of a stream of raw data to be
615 consumed by the command handler on the server. The format of the data
635 consumed by the command handler on the server. The format of the data
616 is command specific.
636 is command specific.
617
637
618 The following flag values are defined for this type:
638 The following flag values are defined for this type:
619
639
620 0x01
640 0x01
621 Command data continuation. When set, the data for this command
641 Command data continuation. When set, the data for this command
622 continues into a subsequent frame.
642 continues into a subsequent frame.
623
643
624 0x02
644 0x02
625 End of data. When set, command data has been fully sent to the
645 End of data. When set, command data has been fully sent to the
626 server. The command has been fully issued and no new data for this
646 server. The command has been fully issued and no new data for this
627 command will be sent. The next frame will belong to a new command.
647 command will be sent. The next frame will belong to a new command.
628
648
629 Bytes Response Data (``0x04``)
649 Bytes Response Data (``0x04``)
630 ------------------------------
650 ------------------------------
631
651
632 This frame contains raw bytes response data to an issued command.
652 This frame contains raw bytes response data to an issued command.
633
653
634 The following flag values are defined for this type:
654 The following flag values are defined for this type:
635
655
636 0x01
656 0x01
637 Data continuation. When set, an additional frame containing raw
657 Data continuation. When set, an additional frame containing raw
638 response data will follow.
658 response data will follow.
639 0x02
659 0x02
640 End of data. When sent, the response data has been fully sent and
660 End of data. When sent, the response data has been fully sent and
641 no additional frames for this response will be sent.
661 no additional frames for this response will be sent.
642
662
643 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
663 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
644
664
645 Error Response (``0x05``)
665 Error Response (``0x05``)
646 -------------------------
666 -------------------------
647
667
648 An error occurred when processing a request. This could indicate
668 An error occurred when processing a request. This could indicate
649 a protocol-level failure or an application level failure depending
669 a protocol-level failure or an application level failure depending
650 on the flags for this message type.
670 on the flags for this message type.
651
671
652 The payload for this type is an error message that should be
672 The payload for this type is an error message that should be
653 displayed to the user.
673 displayed to the user.
654
674
655 The following flag values are defined for this type:
675 The following flag values are defined for this type:
656
676
657 0x01
677 0x01
658 The error occurred at the transport/protocol level. If set, the
678 The error occurred at the transport/protocol level. If set, the
659 connection should be closed.
679 connection should be closed.
660 0x02
680 0x02
661 The error occurred at the application level. e.g. invalid command.
681 The error occurred at the application level. e.g. invalid command.
662
682
663 Human Output Side-Channel (``0x06``)
683 Human Output Side-Channel (``0x06``)
664 ------------------------------------
684 ------------------------------------
665
685
666 This frame contains a message that is intended to be displayed to
686 This frame contains a message that is intended to be displayed to
667 people. Whereas most frames communicate machine readable data, this
687 people. Whereas most frames communicate machine readable data, this
668 frame communicates textual data that is intended to be shown to
688 frame communicates textual data that is intended to be shown to
669 humans.
689 humans.
670
690
671 The frame consists of a series of *formatting requests*. Each formatting
691 The frame consists of a series of *formatting requests*. Each formatting
672 request consists of a formatting string, arguments for that formatting
692 request consists of a formatting string, arguments for that formatting
673 string, and labels to apply to that formatting string.
693 string, and labels to apply to that formatting string.
674
694
675 A formatting string is a printf()-like string that allows variable
695 A formatting string is a printf()-like string that allows variable
676 substitution within the string. Labels allow the rendered text to be
696 substitution within the string. Labels allow the rendered text to be
677 *decorated*. Assuming use of the canonical Mercurial code base, a
697 *decorated*. Assuming use of the canonical Mercurial code base, a
678 formatting string can be the input to the ``i18n._`` function. This
698 formatting string can be the input to the ``i18n._`` function. This
679 allows messages emitted from the server to be localized. So even if
699 allows messages emitted from the server to be localized. So even if
680 the server has different i18n settings, people could see messages in
700 the server has different i18n settings, people could see messages in
681 their *native* settings. Similarly, the use of labels allows
701 their *native* settings. Similarly, the use of labels allows
682 decorations like coloring and underlining to be applied using the
702 decorations like coloring and underlining to be applied using the
683 client's configured rendering settings.
703 client's configured rendering settings.
684
704
685 Formatting strings are similar to ``printf()`` strings or how
705 Formatting strings are similar to ``printf()`` strings or how
686 Python's ``%`` operator works. The only supported formatting sequences
706 Python's ``%`` operator works. The only supported formatting sequences
687 are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
707 are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
688 at that position resolves to. ``%%`` will be replaced by ``%``. All
708 at that position resolves to. ``%%`` will be replaced by ``%``. All
689 other 2-byte sequences beginning with ``%`` represent a literal
709 other 2-byte sequences beginning with ``%`` represent a literal
690 ``%`` followed by that character. However, future versions of the
710 ``%`` followed by that character. However, future versions of the
691 wire protocol reserve the right to allow clients to opt in to receiving
711 wire protocol reserve the right to allow clients to opt in to receiving
692 formatting strings with additional formatters, hence why ``%%`` is
712 formatting strings with additional formatters, hence why ``%%`` is
693 required to represent the literal ``%``.
713 required to represent the literal ``%``.
694
714
695 The raw frame consists of a series of data structures representing
715 The raw frame consists of a series of data structures representing
696 textual atoms to print. Each atom begins with a struct defining the
716 textual atoms to print. Each atom begins with a struct defining the
697 size of the data that follows:
717 size of the data that follows:
698
718
699 * A 16-bit little endian unsigned integer denoting the length of the
719 * A 16-bit little endian unsigned integer denoting the length of the
700 formatting string.
720 formatting string.
701 * An 8-bit unsigned integer denoting the number of label strings
721 * An 8-bit unsigned integer denoting the number of label strings
702 that follow.
722 that follow.
703 * An 8-bit unsigned integer denoting the number of formatting string
723 * An 8-bit unsigned integer denoting the number of formatting string
704 arguments strings that follow.
724 arguments strings that follow.
705 * An array of 8-bit unsigned integers denoting the lengths of
725 * An array of 8-bit unsigned integers denoting the lengths of
706 *labels* data.
726 *labels* data.
707 * An array of 16-bit unsigned integers denoting the lengths of
727 * An array of 16-bit unsigned integers denoting the lengths of
708 formatting strings.
728 formatting strings.
709 * The formatting string, encoded as UTF-8.
729 * The formatting string, encoded as UTF-8.
710 * 0 or more ASCII strings defining labels to apply to this atom.
730 * 0 or more ASCII strings defining labels to apply to this atom.
711 * 0 or more UTF-8 strings that will be used as arguments to the
731 * 0 or more UTF-8 strings that will be used as arguments to the
712 formatting string.
732 formatting string.
713
733
714 TODO use ASCII for formatting string.
734 TODO use ASCII for formatting string.
715
735
716 All data to be printed MUST be encoded into a single frame: this frame
736 All data to be printed MUST be encoded into a single frame: this frame
717 does not support spanning data across multiple frames.
737 does not support spanning data across multiple frames.
718
738
719 All textual data encoded in these frames is assumed to be line delimited.
739 All textual data encoded in these frames is assumed to be line delimited.
720 The last atom in the frame SHOULD end with a newline (``\n``). If it
740 The last atom in the frame SHOULD end with a newline (``\n``). If it
721 doesn't, clients MAY add a newline to facilitate immediate printing.
741 doesn't, clients MAY add a newline to facilitate immediate printing.
722
742
743 Stream Encoding Settings (``0x08``)
744 -----------------------------------
745
746 This frame type holds information defining the content encoding
747 settings for a *stream*.
748
749 This frame type is likely consumed by the protocol layer and is not
750 passed on to applications.
751
752 This frame type MUST ONLY occur on frames having the *Beginning of Stream*
753 ``Stream Flag`` set.
754
755 The payload of this frame defines what content encoding has (possibly)
756 been applied to the payloads of subsequent frames in this stream.
757
758 The payload begins with an 8-bit integer defining the length of the
759 encoding *profile*, followed by the string name of that profile, which
760 must be an ASCII string. All bytes that follow can be used by that
761 profile for supplemental settings definitions. See the section below
762 on defined encoding profiles.
763
764 Stream States and Flags
765 -----------------------
766
767 Streams can be in two states: *open* and *closed*. An *open* stream
768 is active and frames attached to that stream could arrive at any time.
769 A *closed* stream is not active. If a frame attached to a *closed*
770 stream arrives, that frame MUST have an appropriate stream flag
771 set indicating beginning of stream. All streams are in the *closed*
772 state by default.
773
774 The ``Stream Flags`` field denotes a set of bit flags for defining
775 the relationship of this frame within a stream. The following flags
776 are defined:
777
778 0x01
779 Beginning of stream. The first frame in the stream MUST set this
780 flag. When received, the ``Stream ID`` this frame is attached to
781 becomes ``open``.
782
783 0x02
784 End of stream. The last frame in a stream MUST set this flag. When
785 received, the ``Stream ID`` this frame is attached to becomes
786 ``closed``. Any content encoding context associated with this stream
787 can be destroyed after processing the payload of this frame.
788
789 0x04
790 Apply content encoding. When set, any content encoding settings
791 defined by the stream should be applied when attempting to read
792 the frame. When not set, the frame payload isn't encoded.
793
794 Streams
795 -------
796
797 Streams - along with ``Request IDs`` - facilitate grouping of frames.
798 But the purpose of each is quite different and the groupings they
799 constitute are independent.
800
801 A ``Request ID`` is essentially a tag. It tells you which logical
802 request a frame is associated with.
803
804 A *stream* is a sequence of frames grouped for the express purpose
805 of applying a stateful encoding or for denoting sub-groups of frames.
806
807 Unlike ``Request ID``s which span the request and response, a stream
808 is unidirectional and stream IDs are independent from client to
809 server.
810
811 There is no strict hierarchical relationship between ``Request IDs``
812 and *streams*. A stream can contain frames having multiple
813 ``Request IDs``. Frames belonging to the same ``Request ID`` can
814 span multiple streams.
815
816 One goal of streams is to facilitate content encoding. A stream can
817 define an encoding to be applied to frame payloads. For example, the
818 payload transmitted over the wire may contain output from a
819 zstandard compression operation and the receiving end may decompress
820 that payload to obtain the original data.
821
822 The other goal of streams is to facilitate concurrent execution. For
823 example, a server could spawn 4 threads to service a request that can
824 be easily parallelized. Each of those 4 threads could write into its
825 own stream. Those streams could then in turn be delivered to 4 threads
826 on the receiving end, with each thread consuming its stream in near
827 isolation. The *main* thread on both ends merely does I/O and
828 encodes/decodes frame headers: the bulk of the work is done by worker
829 threads.
830
831 In addition, since content encoding is defined per stream, each
832 *worker thread* could perform potentially CPU bound work concurrently
833 with other threads. This approach of applying encoding at the
834 sub-protocol / stream level eliminates a potential resource constraint
835 on the protocol stream as a whole (it is common for the throughput of
836 a compression engine to be smaller than the throughput of a network).
837
838 Having multiple streams - each with their own encoding settings - also
839 facilitates the use of advanced data compression techniques. For
840 example, a transmitter could see that it is generating data faster
841 and slower than the receiving end is consuming it and adjust its
842 compression settings to trade CPU for compression ratio accordingly.
843
844 While streams can define a content encoding, not all frames within
845 that stream must use that content encoding. This can be useful when
846 data is being served from caches and being derived dynamically. A
847 cache could pre-compressed data so the server doesn't have to
848 recompress it. The ability to pick and choose which frames are
849 compressed allows servers to easily send data to the wire without
850 involving potentially expensive encoding overhead.
851
852 Content Encoding Profiles
853 -------------------------
854
855 Streams can have named content encoding *profiles* associated with
856 them. A profile defines a shared understanding of content encoding
857 settings and behavior.
858
859 The following profiles are defined:
860
861 TBD
862
723 Issuing Commands
863 Issuing Commands
724 ----------------
864 ----------------
725
865
726 A client can request that a remote run a command by sending it
866 A client can request that a remote run a command by sending it
727 frames defining that command. This logical stream is composed of
867 frames defining that command. This logical stream is composed of
728 1 ``Command Request`` frame, 0 or more ``Command Argument`` frames,
868 1 ``Command Request`` frame, 0 or more ``Command Argument`` frames,
729 and 0 or more ``Command Data`` frames.
869 and 0 or more ``Command Data`` frames.
730
870
731 All frames composing a single command request MUST be associated with
871 All frames composing a single command request MUST be associated with
732 the same ``Request ID``.
872 the same ``Request ID``.
733
873
734 Clients MAY send additional command requests without waiting on the
874 Clients MAY send additional command requests without waiting on the
735 response to a previous command request. If they do so, they MUST ensure
875 response to a previous command request. If they do so, they MUST ensure
736 that the ``Request ID`` field of outbound frames does not conflict
876 that the ``Request ID`` field of outbound frames does not conflict
737 with that of an active ``Request ID`` whose response has not yet been
877 with that of an active ``Request ID`` whose response has not yet been
738 fully received.
878 fully received.
739
879
740 Servers MAY respond to commands in a different order than they were
880 Servers MAY respond to commands in a different order than they were
741 sent over the wire. Clients MUST be prepared to deal with this. Servers
881 sent over the wire. Clients MUST be prepared to deal with this. Servers
742 also MAY start executing commands in a different order than they were
882 also MAY start executing commands in a different order than they were
743 received, or MAY execute multiple commands concurrently.
883 received, or MAY execute multiple commands concurrently.
744
884
745 If there is a dependency between commands or a race condition between
885 If there is a dependency between commands or a race condition between
746 commands executing (e.g. a read-only command that depends on the results
886 commands executing (e.g. a read-only command that depends on the results
747 of a command that mutates the repository), then clients MUST NOT send
887 of a command that mutates the repository), then clients MUST NOT send
748 frames issuing a command until a response to all dependent commands has
888 frames issuing a command until a response to all dependent commands has
749 been received.
889 been received.
750 TODO think about whether we should express dependencies between commands
890 TODO think about whether we should express dependencies between commands
751 to avoid roundtrip latency.
891 to avoid roundtrip latency.
752
892
753 Argument frames are the recommended mechanism for transferring fixed
893 Argument frames are the recommended mechanism for transferring fixed
754 sets of parameters to a command. Data frames are appropriate for
894 sets of parameters to a command. Data frames are appropriate for
755 transferring variable data. A similar comparison would be to HTTP:
895 transferring variable data. A similar comparison would be to HTTP:
756 argument frames are headers and the message body is data frames.
896 argument frames are headers and the message body is data frames.
757
897
758 It is recommended for servers to delay the dispatch of a command
898 It is recommended for servers to delay the dispatch of a command
759 until all argument frames for that command have been received. Servers
899 until all argument frames for that command have been received. Servers
760 MAY impose limits on the maximum argument size.
900 MAY impose limits on the maximum argument size.
761 TODO define failure mechanism.
901 TODO define failure mechanism.
762
902
763 Servers MAY dispatch to commands immediately once argument data
903 Servers MAY dispatch to commands immediately once argument data
764 is available or delay until command data is received in full.
904 is available or delay until command data is received in full.
765
905
766 Capabilities
906 Capabilities
767 ============
907 ============
768
908
769 Servers advertise supported wire protocol features. This allows clients to
909 Servers advertise supported wire protocol features. This allows clients to
770 probe for server features before blindly calling a command or passing a
910 probe for server features before blindly calling a command or passing a
771 specific argument.
911 specific argument.
772
912
773 The server's features are exposed via a *capabilities* string. This is a
913 The server's features are exposed via a *capabilities* string. This is a
774 space-delimited string of tokens/features. Some features are single words
914 space-delimited string of tokens/features. Some features are single words
775 like ``lookup`` or ``batch``. Others are complicated key-value pairs
915 like ``lookup`` or ``batch``. Others are complicated key-value pairs
776 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
916 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
777 values are used, each feature name can define its own encoding of sub-values.
917 values are used, each feature name can define its own encoding of sub-values.
778 Comma-delimited and ``x-www-form-urlencoded`` values are common.
918 Comma-delimited and ``x-www-form-urlencoded`` values are common.
779
919
780 The following document capabilities defined by the canonical Mercurial server
920 The following document capabilities defined by the canonical Mercurial server
781 implementation.
921 implementation.
782
922
783 batch
923 batch
784 -----
924 -----
785
925
786 Whether the server supports the ``batch`` command.
926 Whether the server supports the ``batch`` command.
787
927
788 This capability/command was introduced in Mercurial 1.9 (released July 2011).
928 This capability/command was introduced in Mercurial 1.9 (released July 2011).
789
929
790 branchmap
930 branchmap
791 ---------
931 ---------
792
932
793 Whether the server supports the ``branchmap`` command.
933 Whether the server supports the ``branchmap`` command.
794
934
795 This capability/command was introduced in Mercurial 1.3 (released July 2009).
935 This capability/command was introduced in Mercurial 1.3 (released July 2009).
796
936
797 bundle2-exp
937 bundle2-exp
798 -----------
938 -----------
799
939
800 Precursor to ``bundle2`` capability that was used before bundle2 was a
940 Precursor to ``bundle2`` capability that was used before bundle2 was a
801 stable feature.
941 stable feature.
802
942
803 This capability was introduced in Mercurial 3.0 behind an experimental
943 This capability was introduced in Mercurial 3.0 behind an experimental
804 flag. This capability should not be observed in the wild.
944 flag. This capability should not be observed in the wild.
805
945
806 bundle2
946 bundle2
807 -------
947 -------
808
948
809 Indicates whether the server supports the ``bundle2`` data exchange format.
949 Indicates whether the server supports the ``bundle2`` data exchange format.
810
950
811 The value of the capability is a URL quoted, newline (``\n``) delimited
951 The value of the capability is a URL quoted, newline (``\n``) delimited
812 list of keys or key-value pairs.
952 list of keys or key-value pairs.
813
953
814 A key is simply a URL encoded string.
954 A key is simply a URL encoded string.
815
955
816 A key-value pair is a URL encoded key separated from a URL encoded value by
956 A key-value pair is a URL encoded key separated from a URL encoded value by
817 an ``=``. If the value is a list, elements are delimited by a ``,`` after
957 an ``=``. If the value is a list, elements are delimited by a ``,`` after
818 URL encoding.
958 URL encoding.
819
959
820 For example, say we have the values::
960 For example, say we have the values::
821
961
822 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
962 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
823
963
824 We would first construct a string::
964 We would first construct a string::
825
965
826 HG20\nchangegroup=01,02\ndigests=sha1,sha512
966 HG20\nchangegroup=01,02\ndigests=sha1,sha512
827
967
828 We would then URL quote this string::
968 We would then URL quote this string::
829
969
830 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
970 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
831
971
832 This capability was introduced in Mercurial 3.4 (released May 2015).
972 This capability was introduced in Mercurial 3.4 (released May 2015).
833
973
834 changegroupsubset
974 changegroupsubset
835 -----------------
975 -----------------
836
976
837 Whether the server supports the ``changegroupsubset`` command.
977 Whether the server supports the ``changegroupsubset`` command.
838
978
839 This capability was introduced in Mercurial 0.9.2 (released December
979 This capability was introduced in Mercurial 0.9.2 (released December
840 2006).
980 2006).
841
981
842 This capability was introduced at the same time as the ``lookup``
982 This capability was introduced at the same time as the ``lookup``
843 capability/command.
983 capability/command.
844
984
845 compression
985 compression
846 -----------
986 -----------
847
987
848 Declares support for negotiating compression formats.
988 Declares support for negotiating compression formats.
849
989
850 Presence of this capability indicates the server supports dynamic selection
990 Presence of this capability indicates the server supports dynamic selection
851 of compression formats based on the client request.
991 of compression formats based on the client request.
852
992
853 Servers advertising this capability are required to support the
993 Servers advertising this capability are required to support the
854 ``application/mercurial-0.2`` media type in response to commands returning
994 ``application/mercurial-0.2`` media type in response to commands returning
855 streams. Servers may support this media type on any command.
995 streams. Servers may support this media type on any command.
856
996
857 The value of the capability is a comma-delimited list of strings declaring
997 The value of the capability is a comma-delimited list of strings declaring
858 supported compression formats. The order of the compression formats is in
998 supported compression formats. The order of the compression formats is in
859 server-preferred order, most preferred first.
999 server-preferred order, most preferred first.
860
1000
861 The identifiers used by the official Mercurial distribution are:
1001 The identifiers used by the official Mercurial distribution are:
862
1002
863 bzip2
1003 bzip2
864 bzip2
1004 bzip2
865 none
1005 none
866 uncompressed / raw data
1006 uncompressed / raw data
867 zlib
1007 zlib
868 zlib (no gzip header)
1008 zlib (no gzip header)
869 zstd
1009 zstd
870 zstd
1010 zstd
871
1011
872 This capability was introduced in Mercurial 4.1 (released February 2017).
1012 This capability was introduced in Mercurial 4.1 (released February 2017).
873
1013
874 getbundle
1014 getbundle
875 ---------
1015 ---------
876
1016
877 Whether the server supports the ``getbundle`` command.
1017 Whether the server supports the ``getbundle`` command.
878
1018
879 This capability was introduced in Mercurial 1.9 (released July 2011).
1019 This capability was introduced in Mercurial 1.9 (released July 2011).
880
1020
881 httpheader
1021 httpheader
882 ----------
1022 ----------
883
1023
884 Whether the server supports receiving command arguments via HTTP request
1024 Whether the server supports receiving command arguments via HTTP request
885 headers.
1025 headers.
886
1026
887 The value of the capability is an integer describing the max header
1027 The value of the capability is an integer describing the max header
888 length that clients should send. Clients should ignore any content after a
1028 length that clients should send. Clients should ignore any content after a
889 comma in the value, as this is reserved for future use.
1029 comma in the value, as this is reserved for future use.
890
1030
891 This capability was introduced in Mercurial 1.9 (released July 2011).
1031 This capability was introduced in Mercurial 1.9 (released July 2011).
892
1032
893 httpmediatype
1033 httpmediatype
894 -------------
1034 -------------
895
1035
896 Indicates which HTTP media types (``Content-Type`` header) the server is
1036 Indicates which HTTP media types (``Content-Type`` header) the server is
897 capable of receiving and sending.
1037 capable of receiving and sending.
898
1038
899 The value of the capability is a comma-delimited list of strings identifying
1039 The value of the capability is a comma-delimited list of strings identifying
900 support for media type and transmission direction. The following strings may
1040 support for media type and transmission direction. The following strings may
901 be present:
1041 be present:
902
1042
903 0.1rx
1043 0.1rx
904 Indicates server support for receiving ``application/mercurial-0.1`` media
1044 Indicates server support for receiving ``application/mercurial-0.1`` media
905 types.
1045 types.
906
1046
907 0.1tx
1047 0.1tx
908 Indicates server support for sending ``application/mercurial-0.1`` media
1048 Indicates server support for sending ``application/mercurial-0.1`` media
909 types.
1049 types.
910
1050
911 0.2rx
1051 0.2rx
912 Indicates server support for receiving ``application/mercurial-0.2`` media
1052 Indicates server support for receiving ``application/mercurial-0.2`` media
913 types.
1053 types.
914
1054
915 0.2tx
1055 0.2tx
916 Indicates server support for sending ``application/mercurial-0.2`` media
1056 Indicates server support for sending ``application/mercurial-0.2`` media
917 types.
1057 types.
918
1058
919 minrx=X
1059 minrx=X
920 Minimum media type version the server is capable of receiving. Value is a
1060 Minimum media type version the server is capable of receiving. Value is a
921 string like ``0.2``.
1061 string like ``0.2``.
922
1062
923 This capability can be used by servers to limit connections from legacy
1063 This capability can be used by servers to limit connections from legacy
924 clients not using the latest supported media type. However, only clients
1064 clients not using the latest supported media type. However, only clients
925 with knowledge of this capability will know to consult this value. This
1065 with knowledge of this capability will know to consult this value. This
926 capability is present so the client may issue a more user-friendly error
1066 capability is present so the client may issue a more user-friendly error
927 when the server has locked out a legacy client.
1067 when the server has locked out a legacy client.
928
1068
929 mintx=X
1069 mintx=X
930 Minimum media type version the server is capable of sending. Value is a
1070 Minimum media type version the server is capable of sending. Value is a
931 string like ``0.1``.
1071 string like ``0.1``.
932
1072
933 Servers advertising support for the ``application/mercurial-0.2`` media type
1073 Servers advertising support for the ``application/mercurial-0.2`` media type
934 should also advertise the ``compression`` capability.
1074 should also advertise the ``compression`` capability.
935
1075
936 This capability was introduced in Mercurial 4.1 (released February 2017).
1076 This capability was introduced in Mercurial 4.1 (released February 2017).
937
1077
938 httppostargs
1078 httppostargs
939 ------------
1079 ------------
940
1080
941 **Experimental**
1081 **Experimental**
942
1082
943 Indicates that the server supports and prefers clients send command arguments
1083 Indicates that the server supports and prefers clients send command arguments
944 via a HTTP POST request as part of the request body.
1084 via a HTTP POST request as part of the request body.
945
1085
946 This capability was introduced in Mercurial 3.8 (released May 2016).
1086 This capability was introduced in Mercurial 3.8 (released May 2016).
947
1087
948 known
1088 known
949 -----
1089 -----
950
1090
951 Whether the server supports the ``known`` command.
1091 Whether the server supports the ``known`` command.
952
1092
953 This capability/command was introduced in Mercurial 1.9 (released July 2011).
1093 This capability/command was introduced in Mercurial 1.9 (released July 2011).
954
1094
955 lookup
1095 lookup
956 ------
1096 ------
957
1097
958 Whether the server supports the ``lookup`` command.
1098 Whether the server supports the ``lookup`` command.
959
1099
960 This capability was introduced in Mercurial 0.9.2 (released December
1100 This capability was introduced in Mercurial 0.9.2 (released December
961 2006).
1101 2006).
962
1102
963 This capability was introduced at the same time as the ``changegroupsubset``
1103 This capability was introduced at the same time as the ``changegroupsubset``
964 capability/command.
1104 capability/command.
965
1105
966 pushkey
1106 pushkey
967 -------
1107 -------
968
1108
969 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
1109 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
970
1110
971 This capability was introduced in Mercurial 1.6 (released July 2010).
1111 This capability was introduced in Mercurial 1.6 (released July 2010).
972
1112
973 standardbundle
1113 standardbundle
974 --------------
1114 --------------
975
1115
976 **Unsupported**
1116 **Unsupported**
977
1117
978 This capability was introduced during the Mercurial 0.9.2 development cycle in
1118 This capability was introduced during the Mercurial 0.9.2 development cycle in
979 2006. It was never present in a release, as it was replaced by the ``unbundle``
1119 2006. It was never present in a release, as it was replaced by the ``unbundle``
980 capability. This capability should not be encountered in the wild.
1120 capability. This capability should not be encountered in the wild.
981
1121
982 stream-preferred
1122 stream-preferred
983 ----------------
1123 ----------------
984
1124
985 If present the server prefers that clients clone using the streaming clone
1125 If present the server prefers that clients clone using the streaming clone
986 protocol (``hg clone --stream``) rather than the standard
1126 protocol (``hg clone --stream``) rather than the standard
987 changegroup/bundle based protocol.
1127 changegroup/bundle based protocol.
988
1128
989 This capability was introduced in Mercurial 2.2 (released May 2012).
1129 This capability was introduced in Mercurial 2.2 (released May 2012).
990
1130
991 streamreqs
1131 streamreqs
992 ----------
1132 ----------
993
1133
994 Indicates whether the server supports *streaming clones* and the *requirements*
1134 Indicates whether the server supports *streaming clones* and the *requirements*
995 that clients must support to receive it.
1135 that clients must support to receive it.
996
1136
997 If present, the server supports the ``stream_out`` command, which transmits
1137 If present, the server supports the ``stream_out`` command, which transmits
998 raw revlogs from the repository instead of changegroups. This provides a faster
1138 raw revlogs from the repository instead of changegroups. This provides a faster
999 cloning mechanism at the expense of more bandwidth used.
1139 cloning mechanism at the expense of more bandwidth used.
1000
1140
1001 The value of this capability is a comma-delimited list of repo format
1141 The value of this capability is a comma-delimited list of repo format
1002 *requirements*. These are requirements that impact the reading of data in
1142 *requirements*. These are requirements that impact the reading of data in
1003 the ``.hg/store`` directory. An example value is
1143 the ``.hg/store`` directory. An example value is
1004 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
1144 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
1005 the ``revlogv1`` and ``generaldelta`` requirements.
1145 the ``revlogv1`` and ``generaldelta`` requirements.
1006
1146
1007 If the only format requirement is ``revlogv1``, the server may expose the
1147 If the only format requirement is ``revlogv1``, the server may expose the
1008 ``stream`` capability instead of the ``streamreqs`` capability.
1148 ``stream`` capability instead of the ``streamreqs`` capability.
1009
1149
1010 This capability was introduced in Mercurial 1.7 (released November 2010).
1150 This capability was introduced in Mercurial 1.7 (released November 2010).
1011
1151
1012 stream
1152 stream
1013 ------
1153 ------
1014
1154
1015 Whether the server supports *streaming clones* from ``revlogv1`` repos.
1155 Whether the server supports *streaming clones* from ``revlogv1`` repos.
1016
1156
1017 If present, the server supports the ``stream_out`` command, which transmits
1157 If present, the server supports the ``stream_out`` command, which transmits
1018 raw revlogs from the repository instead of changegroups. This provides a faster
1158 raw revlogs from the repository instead of changegroups. This provides a faster
1019 cloning mechanism at the expense of more bandwidth used.
1159 cloning mechanism at the expense of more bandwidth used.
1020
1160
1021 This capability was introduced in Mercurial 0.9.1 (released July 2006).
1161 This capability was introduced in Mercurial 0.9.1 (released July 2006).
1022
1162
1023 When initially introduced, the value of the capability was the numeric
1163 When initially introduced, the value of the capability was the numeric
1024 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
1164 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
1025 ``revlogv1``. This simple integer value wasn't powerful enough, so the
1165 ``revlogv1``. This simple integer value wasn't powerful enough, so the
1026 ``streamreqs`` capability was invented to handle cases where the repo
1166 ``streamreqs`` capability was invented to handle cases where the repo
1027 requirements have more than just ``revlogv1``. Newer servers omit the
1167 requirements have more than just ``revlogv1``. Newer servers omit the
1028 ``=1`` since it was the only value supported and the value of ``1`` can
1168 ``=1`` since it was the only value supported and the value of ``1`` can
1029 be implied by clients.
1169 be implied by clients.
1030
1170
1031 unbundlehash
1171 unbundlehash
1032 ------------
1172 ------------
1033
1173
1034 Whether the ``unbundle`` commands supports receiving a hash of all the
1174 Whether the ``unbundle`` commands supports receiving a hash of all the
1035 heads instead of a list.
1175 heads instead of a list.
1036
1176
1037 For more, see the documentation for the ``unbundle`` command.
1177 For more, see the documentation for the ``unbundle`` command.
1038
1178
1039 This capability was introduced in Mercurial 1.9 (released July 2011).
1179 This capability was introduced in Mercurial 1.9 (released July 2011).
1040
1180
1041 unbundle
1181 unbundle
1042 --------
1182 --------
1043
1183
1044 Whether the server supports pushing via the ``unbundle`` command.
1184 Whether the server supports pushing via the ``unbundle`` command.
1045
1185
1046 This capability/command has been present since Mercurial 0.9.1 (released
1186 This capability/command has been present since Mercurial 0.9.1 (released
1047 July 2006).
1187 July 2006).
1048
1188
1049 Mercurial 0.9.2 (released December 2006) added values to the capability
1189 Mercurial 0.9.2 (released December 2006) added values to the capability
1050 indicating which bundle types the server supports receiving. This value is a
1190 indicating which bundle types the server supports receiving. This value is a
1051 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
1191 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
1052 reflects the priority/preference of that type, where the first value is the
1192 reflects the priority/preference of that type, where the first value is the
1053 most preferred type.
1193 most preferred type.
1054
1194
1055 Content Negotiation
1195 Content Negotiation
1056 ===================
1196 ===================
1057
1197
1058 The wire protocol has some mechanisms to help peers determine what content
1198 The wire protocol has some mechanisms to help peers determine what content
1059 types and encoding the other side will accept. Historically, these mechanisms
1199 types and encoding the other side will accept. Historically, these mechanisms
1060 have been built into commands themselves because most commands only send a
1200 have been built into commands themselves because most commands only send a
1061 well-defined response type and only certain commands needed to support
1201 well-defined response type and only certain commands needed to support
1062 functionality like compression.
1202 functionality like compression.
1063
1203
1064 Currently, only the HTTP version 1 transport supports content negotiation
1204 Currently, only the HTTP version 1 transport supports content negotiation
1065 at the protocol layer.
1205 at the protocol layer.
1066
1206
1067 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
1207 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
1068 request header, where ``<N>`` is an integer starting at 1 allowing the logical
1208 request header, where ``<N>`` is an integer starting at 1 allowing the logical
1069 value to span multiple headers. This value consists of a list of
1209 value to span multiple headers. This value consists of a list of
1070 space-delimited parameters. Each parameter denotes a feature or capability.
1210 space-delimited parameters. Each parameter denotes a feature or capability.
1071
1211
1072 The following parameters are defined:
1212 The following parameters are defined:
1073
1213
1074 0.1
1214 0.1
1075 Indicates the client supports receiving ``application/mercurial-0.1``
1215 Indicates the client supports receiving ``application/mercurial-0.1``
1076 responses.
1216 responses.
1077
1217
1078 0.2
1218 0.2
1079 Indicates the client supports receiving ``application/mercurial-0.2``
1219 Indicates the client supports receiving ``application/mercurial-0.2``
1080 responses.
1220 responses.
1081
1221
1082 comp
1222 comp
1083 Indicates compression formats the client can decode. Value is a list of
1223 Indicates compression formats the client can decode. Value is a list of
1084 comma delimited strings identifying compression formats ordered from
1224 comma delimited strings identifying compression formats ordered from
1085 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
1225 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
1086
1226
1087 This parameter does not have an effect if only the ``0.1`` parameter
1227 This parameter does not have an effect if only the ``0.1`` parameter
1088 is defined, as support for ``application/mercurial-0.2`` or greater is
1228 is defined, as support for ``application/mercurial-0.2`` or greater is
1089 required to use arbitrary compression formats.
1229 required to use arbitrary compression formats.
1090
1230
1091 If this parameter is not advertised, the server interprets this as
1231 If this parameter is not advertised, the server interprets this as
1092 equivalent to ``zlib,none``.
1232 equivalent to ``zlib,none``.
1093
1233
1094 Clients may choose to only send this header if the ``httpmediatype``
1234 Clients may choose to only send this header if the ``httpmediatype``
1095 server capability is present, as currently all server-side features
1235 server capability is present, as currently all server-side features
1096 consulting this header require the client to opt in to new protocol features
1236 consulting this header require the client to opt in to new protocol features
1097 advertised via the ``httpmediatype`` capability.
1237 advertised via the ``httpmediatype`` capability.
1098
1238
1099 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
1239 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
1100 value of ``0.1``. This is compatible with legacy clients.
1240 value of ``0.1``. This is compatible with legacy clients.
1101
1241
1102 A server receiving a request indicating support for multiple media type
1242 A server receiving a request indicating support for multiple media type
1103 versions may respond with any of the supported media types. Not all servers
1243 versions may respond with any of the supported media types. Not all servers
1104 may support all media types on all commands.
1244 may support all media types on all commands.
1105
1245
1106 Commands
1246 Commands
1107 ========
1247 ========
1108
1248
1109 This section contains a list of all wire protocol commands implemented by
1249 This section contains a list of all wire protocol commands implemented by
1110 the canonical Mercurial server.
1250 the canonical Mercurial server.
1111
1251
1112 batch
1252 batch
1113 -----
1253 -----
1114
1254
1115 Issue multiple commands while sending a single command request. The purpose
1255 Issue multiple commands while sending a single command request. The purpose
1116 of this command is to allow a client to issue multiple commands while avoiding
1256 of this command is to allow a client to issue multiple commands while avoiding
1117 multiple round trips to the server therefore enabling commands to complete
1257 multiple round trips to the server therefore enabling commands to complete
1118 quicker.
1258 quicker.
1119
1259
1120 The command accepts a ``cmds`` argument that contains a list of commands to
1260 The command accepts a ``cmds`` argument that contains a list of commands to
1121 execute.
1261 execute.
1122
1262
1123 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
1263 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
1124 form ``<command> <arguments>``. That is, the command name followed by a space
1264 form ``<command> <arguments>``. That is, the command name followed by a space
1125 followed by an argument string.
1265 followed by an argument string.
1126
1266
1127 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
1267 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
1128 corresponding to command arguments. Both the argument name and value are
1268 corresponding to command arguments. Both the argument name and value are
1129 escaped using a special substitution map::
1269 escaped using a special substitution map::
1130
1270
1131 : -> :c
1271 : -> :c
1132 , -> :o
1272 , -> :o
1133 ; -> :s
1273 ; -> :s
1134 = -> :e
1274 = -> :e
1135
1275
1136 The response type for this command is ``string``. The value contains a
1276 The response type for this command is ``string``. The value contains a
1137 ``;`` delimited list of responses for each requested command. Each value
1277 ``;`` delimited list of responses for each requested command. Each value
1138 in this list is escaped using the same substitution map used for arguments.
1278 in this list is escaped using the same substitution map used for arguments.
1139
1279
1140 If an error occurs, the generic error response may be sent.
1280 If an error occurs, the generic error response may be sent.
1141
1281
1142 between
1282 between
1143 -------
1283 -------
1144
1284
1145 (Legacy command used for discovery in old clients)
1285 (Legacy command used for discovery in old clients)
1146
1286
1147 Obtain nodes between pairs of nodes.
1287 Obtain nodes between pairs of nodes.
1148
1288
1149 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
1289 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
1150 hex node pairs. e.g.::
1290 hex node pairs. e.g.::
1151
1291
1152 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
1292 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
1153
1293
1154 Return type is a ``string``. Value consists of lines corresponding to each
1294 Return type is a ``string``. Value consists of lines corresponding to each
1155 requested range. Each line contains a space-delimited list of hex nodes.
1295 requested range. Each line contains a space-delimited list of hex nodes.
1156 A newline ``\n`` terminates each line, including the last one.
1296 A newline ``\n`` terminates each line, including the last one.
1157
1297
1158 branchmap
1298 branchmap
1159 ---------
1299 ---------
1160
1300
1161 Obtain heads in named branches.
1301 Obtain heads in named branches.
1162
1302
1163 Accepts no arguments. Return type is a ``string``.
1303 Accepts no arguments. Return type is a ``string``.
1164
1304
1165 Return value contains lines with URL encoded branch names followed by a space
1305 Return value contains lines with URL encoded branch names followed by a space
1166 followed by a space-delimited list of hex nodes of heads on that branch.
1306 followed by a space-delimited list of hex nodes of heads on that branch.
1167 e.g.::
1307 e.g.::
1168
1308
1169 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
1309 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
1170 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
1310 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
1171
1311
1172 There is no trailing newline.
1312 There is no trailing newline.
1173
1313
1174 branches
1314 branches
1175 --------
1315 --------
1176
1316
1177 (Legacy command used for discovery in old clients. Clients with ``getbundle``
1317 (Legacy command used for discovery in old clients. Clients with ``getbundle``
1178 use the ``known`` and ``heads`` commands instead.)
1318 use the ``known`` and ``heads`` commands instead.)
1179
1319
1180 Obtain ancestor changesets of specific nodes back to a branch point.
1320 Obtain ancestor changesets of specific nodes back to a branch point.
1181
1321
1182 Despite the name, this command has nothing to do with Mercurial named branches.
1322 Despite the name, this command has nothing to do with Mercurial named branches.
1183 Instead, it is related to DAG branches.
1323 Instead, it is related to DAG branches.
1184
1324
1185 The command accepts a ``nodes`` argument, which is a string of space-delimited
1325 The command accepts a ``nodes`` argument, which is a string of space-delimited
1186 hex nodes.
1326 hex nodes.
1187
1327
1188 For each node requested, the server will find the first ancestor node that is
1328 For each node requested, the server will find the first ancestor node that is
1189 a DAG root or is a merge.
1329 a DAG root or is a merge.
1190
1330
1191 Return type is a ``string``. Return value contains lines with result data for
1331 Return type is a ``string``. Return value contains lines with result data for
1192 each requested node. Each line contains space-delimited nodes followed by a
1332 each requested node. Each line contains space-delimited nodes followed by a
1193 newline (``\n``). The 4 nodes reported on each line correspond to the requested
1333 newline (``\n``). The 4 nodes reported on each line correspond to the requested
1194 node, the ancestor node found, and its 2 parent nodes (which may be the null
1334 node, the ancestor node found, and its 2 parent nodes (which may be the null
1195 node).
1335 node).
1196
1336
1197 capabilities
1337 capabilities
1198 ------------
1338 ------------
1199
1339
1200 Obtain the capabilities string for the repo.
1340 Obtain the capabilities string for the repo.
1201
1341
1202 Unlike the ``hello`` command, the capabilities string is not prefixed.
1342 Unlike the ``hello`` command, the capabilities string is not prefixed.
1203 There is no trailing newline.
1343 There is no trailing newline.
1204
1344
1205 This command does not accept any arguments. Return type is a ``string``.
1345 This command does not accept any arguments. Return type is a ``string``.
1206
1346
1207 This command was introduced in Mercurial 0.9.1 (released July 2006).
1347 This command was introduced in Mercurial 0.9.1 (released July 2006).
1208
1348
1209 changegroup
1349 changegroup
1210 -----------
1350 -----------
1211
1351
1212 (Legacy command: use ``getbundle`` instead)
1352 (Legacy command: use ``getbundle`` instead)
1213
1353
1214 Obtain a changegroup version 1 with data for changesets that are
1354 Obtain a changegroup version 1 with data for changesets that are
1215 descendants of client-specified changesets.
1355 descendants of client-specified changesets.
1216
1356
1217 The ``roots`` arguments contains a list of space-delimited hex nodes.
1357 The ``roots`` arguments contains a list of space-delimited hex nodes.
1218
1358
1219 The server responds with a changegroup version 1 containing all
1359 The server responds with a changegroup version 1 containing all
1220 changesets between the requested root/base nodes and the repo's head nodes
1360 changesets between the requested root/base nodes and the repo's head nodes
1221 at the time of the request.
1361 at the time of the request.
1222
1362
1223 The return type is a ``stream``.
1363 The return type is a ``stream``.
1224
1364
1225 changegroupsubset
1365 changegroupsubset
1226 -----------------
1366 -----------------
1227
1367
1228 (Legacy command: use ``getbundle`` instead)
1368 (Legacy command: use ``getbundle`` instead)
1229
1369
1230 Obtain a changegroup version 1 with data for changesetsets between
1370 Obtain a changegroup version 1 with data for changesetsets between
1231 client specified base and head nodes.
1371 client specified base and head nodes.
1232
1372
1233 The ``bases`` argument contains a list of space-delimited hex nodes.
1373 The ``bases`` argument contains a list of space-delimited hex nodes.
1234 The ``heads`` argument contains a list of space-delimited hex nodes.
1374 The ``heads`` argument contains a list of space-delimited hex nodes.
1235
1375
1236 The server responds with a changegroup version 1 containing all
1376 The server responds with a changegroup version 1 containing all
1237 changesets between the requested base and head nodes at the time of the
1377 changesets between the requested base and head nodes at the time of the
1238 request.
1378 request.
1239
1379
1240 The return type is a ``stream``.
1380 The return type is a ``stream``.
1241
1381
1242 clonebundles
1382 clonebundles
1243 ------------
1383 ------------
1244
1384
1245 Obtains a manifest of bundle URLs available to seed clones.
1385 Obtains a manifest of bundle URLs available to seed clones.
1246
1386
1247 Each returned line contains a URL followed by metadata. See the
1387 Each returned line contains a URL followed by metadata. See the
1248 documentation in the ``clonebundles`` extension for more.
1388 documentation in the ``clonebundles`` extension for more.
1249
1389
1250 The return type is a ``string``.
1390 The return type is a ``string``.
1251
1391
1252 getbundle
1392 getbundle
1253 ---------
1393 ---------
1254
1394
1255 Obtain a bundle containing repository data.
1395 Obtain a bundle containing repository data.
1256
1396
1257 This command accepts the following arguments:
1397 This command accepts the following arguments:
1258
1398
1259 heads
1399 heads
1260 List of space-delimited hex nodes of heads to retrieve.
1400 List of space-delimited hex nodes of heads to retrieve.
1261 common
1401 common
1262 List of space-delimited hex nodes that the client has in common with the
1402 List of space-delimited hex nodes that the client has in common with the
1263 server.
1403 server.
1264 obsmarkers
1404 obsmarkers
1265 Boolean indicating whether to include obsolescence markers as part
1405 Boolean indicating whether to include obsolescence markers as part
1266 of the response. Only works with bundle2.
1406 of the response. Only works with bundle2.
1267 bundlecaps
1407 bundlecaps
1268 Comma-delimited set of strings defining client bundle capabilities.
1408 Comma-delimited set of strings defining client bundle capabilities.
1269 listkeys
1409 listkeys
1270 Comma-delimited list of strings of ``pushkey`` namespaces. For each
1410 Comma-delimited list of strings of ``pushkey`` namespaces. For each
1271 namespace listed, a bundle2 part will be included with the content of
1411 namespace listed, a bundle2 part will be included with the content of
1272 that namespace.
1412 that namespace.
1273 cg
1413 cg
1274 Boolean indicating whether changegroup data is requested.
1414 Boolean indicating whether changegroup data is requested.
1275 cbattempted
1415 cbattempted
1276 Boolean indicating whether the client attempted to use the *clone bundles*
1416 Boolean indicating whether the client attempted to use the *clone bundles*
1277 feature before performing this request.
1417 feature before performing this request.
1278 bookmarks
1418 bookmarks
1279 Boolean indicating whether bookmark data is requested.
1419 Boolean indicating whether bookmark data is requested.
1280 phases
1420 phases
1281 Boolean indicating whether phases data is requested.
1421 Boolean indicating whether phases data is requested.
1282
1422
1283 The return type on success is a ``stream`` where the value is bundle.
1423 The return type on success is a ``stream`` where the value is bundle.
1284 On the HTTP version 1 transport, the response is zlib compressed.
1424 On the HTTP version 1 transport, the response is zlib compressed.
1285
1425
1286 If an error occurs, a generic error response can be sent.
1426 If an error occurs, a generic error response can be sent.
1287
1427
1288 Unless the client sends a false value for the ``cg`` argument, the returned
1428 Unless the client sends a false value for the ``cg`` argument, the returned
1289 bundle contains a changegroup with the nodes between the specified ``common``
1429 bundle contains a changegroup with the nodes between the specified ``common``
1290 and ``heads`` nodes. Depending on the command arguments, the type and content
1430 and ``heads`` nodes. Depending on the command arguments, the type and content
1291 of the returned bundle can vary significantly.
1431 of the returned bundle can vary significantly.
1292
1432
1293 The default behavior is for the server to send a raw changegroup version
1433 The default behavior is for the server to send a raw changegroup version
1294 ``01`` response.
1434 ``01`` response.
1295
1435
1296 If the ``bundlecaps`` provided by the client contain a value beginning
1436 If the ``bundlecaps`` provided by the client contain a value beginning
1297 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
1437 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
1298 additional repository data, such as ``pushkey`` namespace values.
1438 additional repository data, such as ``pushkey`` namespace values.
1299
1439
1300 heads
1440 heads
1301 -----
1441 -----
1302
1442
1303 Returns a list of space-delimited hex nodes of repository heads followed
1443 Returns a list of space-delimited hex nodes of repository heads followed
1304 by a newline. e.g.
1444 by a newline. e.g.
1305 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
1445 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
1306
1446
1307 This command does not accept any arguments. The return type is a ``string``.
1447 This command does not accept any arguments. The return type is a ``string``.
1308
1448
1309 hello
1449 hello
1310 -----
1450 -----
1311
1451
1312 Returns lines describing interesting things about the server in an RFC-822
1452 Returns lines describing interesting things about the server in an RFC-822
1313 like format.
1453 like format.
1314
1454
1315 Currently, the only line defines the server capabilities. It has the form::
1455 Currently, the only line defines the server capabilities. It has the form::
1316
1456
1317 capabilities: <value>
1457 capabilities: <value>
1318
1458
1319 See above for more about the capabilities string.
1459 See above for more about the capabilities string.
1320
1460
1321 SSH clients typically issue this command as soon as a connection is
1461 SSH clients typically issue this command as soon as a connection is
1322 established.
1462 established.
1323
1463
1324 This command does not accept any arguments. The return type is a ``string``.
1464 This command does not accept any arguments. The return type is a ``string``.
1325
1465
1326 This command was introduced in Mercurial 0.9.1 (released July 2006).
1466 This command was introduced in Mercurial 0.9.1 (released July 2006).
1327
1467
1328 listkeys
1468 listkeys
1329 --------
1469 --------
1330
1470
1331 List values in a specified ``pushkey`` namespace.
1471 List values in a specified ``pushkey`` namespace.
1332
1472
1333 The ``namespace`` argument defines the pushkey namespace to operate on.
1473 The ``namespace`` argument defines the pushkey namespace to operate on.
1334
1474
1335 The return type is a ``string``. The value is an encoded dictionary of keys.
1475 The return type is a ``string``. The value is an encoded dictionary of keys.
1336
1476
1337 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
1477 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
1338 values are separated by a tab (``\t``). Keys and values are both strings.
1478 values are separated by a tab (``\t``). Keys and values are both strings.
1339
1479
1340 lookup
1480 lookup
1341 ------
1481 ------
1342
1482
1343 Try to resolve a value to a known repository revision.
1483 Try to resolve a value to a known repository revision.
1344
1484
1345 The ``key`` argument is converted from bytes to an
1485 The ``key`` argument is converted from bytes to an
1346 ``encoding.localstr`` instance then passed into
1486 ``encoding.localstr`` instance then passed into
1347 ``localrepository.__getitem__`` in an attempt to resolve it.
1487 ``localrepository.__getitem__`` in an attempt to resolve it.
1348
1488
1349 The return type is a ``string``.
1489 The return type is a ``string``.
1350
1490
1351 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
1491 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
1352 returns ``0 <error string>\n``. e.g.::
1492 returns ``0 <error string>\n``. e.g.::
1353
1493
1354 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
1494 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
1355
1495
1356 0 unknown revision 'foo'\n
1496 0 unknown revision 'foo'\n
1357
1497
1358 known
1498 known
1359 -----
1499 -----
1360
1500
1361 Determine whether multiple nodes are known.
1501 Determine whether multiple nodes are known.
1362
1502
1363 The ``nodes`` argument is a list of space-delimited hex nodes to check
1503 The ``nodes`` argument is a list of space-delimited hex nodes to check
1364 for existence.
1504 for existence.
1365
1505
1366 The return type is ``string``.
1506 The return type is ``string``.
1367
1507
1368 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1508 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1369 are known. If the Nth node specified in the ``nodes`` argument is known,
1509 are known. If the Nth node specified in the ``nodes`` argument is known,
1370 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1510 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1371 will be present at byte offset N.
1511 will be present at byte offset N.
1372
1512
1373 There is no trailing newline.
1513 There is no trailing newline.
1374
1514
1375 pushkey
1515 pushkey
1376 -------
1516 -------
1377
1517
1378 Set a value using the ``pushkey`` protocol.
1518 Set a value using the ``pushkey`` protocol.
1379
1519
1380 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1520 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1381 correspond to the pushkey namespace to operate on, the key within that
1521 correspond to the pushkey namespace to operate on, the key within that
1382 namespace to change, the old value (which may be empty), and the new value.
1522 namespace to change, the old value (which may be empty), and the new value.
1383 All arguments are string types.
1523 All arguments are string types.
1384
1524
1385 The return type is a ``string``. The value depends on the transport protocol.
1525 The return type is a ``string``. The value depends on the transport protocol.
1386
1526
1387 The SSH version 1 transport sends a string encoded integer followed by a
1527 The SSH version 1 transport sends a string encoded integer followed by a
1388 newline (``\n``) which indicates operation result. The server may send
1528 newline (``\n``) which indicates operation result. The server may send
1389 additional output on the ``stderr`` stream that should be displayed to the
1529 additional output on the ``stderr`` stream that should be displayed to the
1390 user.
1530 user.
1391
1531
1392 The HTTP version 1 transport sends a string encoded integer followed by a
1532 The HTTP version 1 transport sends a string encoded integer followed by a
1393 newline followed by additional server output that should be displayed to
1533 newline followed by additional server output that should be displayed to
1394 the user. This may include output from hooks, etc.
1534 the user. This may include output from hooks, etc.
1395
1535
1396 The integer result varies by namespace. ``0`` means an error has occurred
1536 The integer result varies by namespace. ``0`` means an error has occurred
1397 and there should be additional output to display to the user.
1537 and there should be additional output to display to the user.
1398
1538
1399 stream_out
1539 stream_out
1400 ----------
1540 ----------
1401
1541
1402 Obtain *streaming clone* data.
1542 Obtain *streaming clone* data.
1403
1543
1404 The return type is either a ``string`` or a ``stream``, depending on
1544 The return type is either a ``string`` or a ``stream``, depending on
1405 whether the request was fulfilled properly.
1545 whether the request was fulfilled properly.
1406
1546
1407 A return value of ``1\n`` indicates the server is not configured to serve
1547 A return value of ``1\n`` indicates the server is not configured to serve
1408 this data. If this is seen by the client, they may not have verified the
1548 this data. If this is seen by the client, they may not have verified the
1409 ``stream`` capability is set before making the request.
1549 ``stream`` capability is set before making the request.
1410
1550
1411 A return value of ``2\n`` indicates the server was unable to lock the
1551 A return value of ``2\n`` indicates the server was unable to lock the
1412 repository to generate data.
1552 repository to generate data.
1413
1553
1414 All other responses are a ``stream`` of bytes. The first line of this data
1554 All other responses are a ``stream`` of bytes. The first line of this data
1415 contains 2 space-delimited integers corresponding to the path count and
1555 contains 2 space-delimited integers corresponding to the path count and
1416 payload size, respectively::
1556 payload size, respectively::
1417
1557
1418 <path count> <payload size>\n
1558 <path count> <payload size>\n
1419
1559
1420 The ``<payload size>`` is the total size of path data: it does not include
1560 The ``<payload size>`` is the total size of path data: it does not include
1421 the size of the per-path header lines.
1561 the size of the per-path header lines.
1422
1562
1423 Following that header are ``<path count>`` entries. Each entry consists of a
1563 Following that header are ``<path count>`` entries. Each entry consists of a
1424 line with metadata followed by raw revlog data. The line consists of::
1564 line with metadata followed by raw revlog data. The line consists of::
1425
1565
1426 <store path>\0<size>\n
1566 <store path>\0<size>\n
1427
1567
1428 The ``<store path>`` is the encoded store path of the data that follows.
1568 The ``<store path>`` is the encoded store path of the data that follows.
1429 ``<size>`` is the amount of data for this store path/revlog that follows the
1569 ``<size>`` is the amount of data for this store path/revlog that follows the
1430 newline.
1570 newline.
1431
1571
1432 There is no trailer to indicate end of data. Instead, the client should stop
1572 There is no trailer to indicate end of data. Instead, the client should stop
1433 reading after ``<path count>`` entries are consumed.
1573 reading after ``<path count>`` entries are consumed.
1434
1574
1435 unbundle
1575 unbundle
1436 --------
1576 --------
1437
1577
1438 Send a bundle containing data (usually changegroup data) to the server.
1578 Send a bundle containing data (usually changegroup data) to the server.
1439
1579
1440 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1580 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1441 corresponding to server repository heads observed by the client. This is used
1581 corresponding to server repository heads observed by the client. This is used
1442 to detect race conditions and abort push operations before a server performs
1582 to detect race conditions and abort push operations before a server performs
1443 too much work or a client transfers too much data.
1583 too much work or a client transfers too much data.
1444
1584
1445 The request payload consists of a bundle to be applied to the repository,
1585 The request payload consists of a bundle to be applied to the repository,
1446 similarly to as if :hg:`unbundle` were called.
1586 similarly to as if :hg:`unbundle` were called.
1447
1587
1448 In most scenarios, a special ``push response`` type is returned. This type
1588 In most scenarios, a special ``push response`` type is returned. This type
1449 contains an integer describing the change in heads as a result of the
1589 contains an integer describing the change in heads as a result of the
1450 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1590 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1451 of heads remained the same. Values ``2`` and larger indicate the number of
1591 of heads remained the same. Values ``2`` and larger indicate the number of
1452 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1592 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1453 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1593 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1454 is 1 fewer head.
1594 is 1 fewer head.
1455
1595
1456 The encoding of the ``push response`` type varies by transport.
1596 The encoding of the ``push response`` type varies by transport.
1457
1597
1458 For the SSH version 1 transport, this type is composed of 2 ``string``
1598 For the SSH version 1 transport, this type is composed of 2 ``string``
1459 responses: an empty response (``0\n``) followed by the integer result value.
1599 responses: an empty response (``0\n``) followed by the integer result value.
1460 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1600 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1461
1601
1462 For the HTTP version 1 transport, the response is a ``string`` type composed
1602 For the HTTP version 1 transport, the response is a ``string`` type composed
1463 of an integer result value followed by a newline (``\n``) followed by string
1603 of an integer result value followed by a newline (``\n``) followed by string
1464 content holding server output that should be displayed on the client (output
1604 content holding server output that should be displayed on the client (output
1465 hooks, etc).
1605 hooks, etc).
1466
1606
1467 In some cases, the server may respond with a ``bundle2`` bundle. In this
1607 In some cases, the server may respond with a ``bundle2`` bundle. In this
1468 case, the response type is ``stream``. For the HTTP version 1 transport, the
1608 case, the response type is ``stream``. For the HTTP version 1 transport, the
1469 response is zlib compressed.
1609 response is zlib compressed.
1470
1610
1471 The server may also respond with a generic error type, which contains a string
1611 The server may also respond with a generic error type, which contains a string
1472 indicating the failure.
1612 indicating the failure.
@@ -1,732 +1,813 b''
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 .thirdparty import (
17 from .thirdparty import (
18 attr,
18 attr,
19 )
19 )
20 from . import (
20 from . import (
21 error,
21 error,
22 util,
22 util,
23 )
23 )
24 from .utils import (
24 from .utils import (
25 stringutil,
25 stringutil,
26 )
26 )
27
27
28 FRAME_HEADER_SIZE = 6
28 FRAME_HEADER_SIZE = 8
29 DEFAULT_MAX_FRAME_SIZE = 32768
29 DEFAULT_MAX_FRAME_SIZE = 32768
30
30
31 STREAM_FLAG_BEGIN_STREAM = 0x01
32 STREAM_FLAG_END_STREAM = 0x02
33 STREAM_FLAG_ENCODING_APPLIED = 0x04
34
35 STREAM_FLAGS = {
36 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
37 b'stream-end': STREAM_FLAG_END_STREAM,
38 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
39 }
40
31 FRAME_TYPE_COMMAND_NAME = 0x01
41 FRAME_TYPE_COMMAND_NAME = 0x01
32 FRAME_TYPE_COMMAND_ARGUMENT = 0x02
42 FRAME_TYPE_COMMAND_ARGUMENT = 0x02
33 FRAME_TYPE_COMMAND_DATA = 0x03
43 FRAME_TYPE_COMMAND_DATA = 0x03
34 FRAME_TYPE_BYTES_RESPONSE = 0x04
44 FRAME_TYPE_BYTES_RESPONSE = 0x04
35 FRAME_TYPE_ERROR_RESPONSE = 0x05
45 FRAME_TYPE_ERROR_RESPONSE = 0x05
36 FRAME_TYPE_TEXT_OUTPUT = 0x06
46 FRAME_TYPE_TEXT_OUTPUT = 0x06
47 FRAME_TYPE_STREAM_SETTINGS = 0x08
37
48
38 FRAME_TYPES = {
49 FRAME_TYPES = {
39 b'command-name': FRAME_TYPE_COMMAND_NAME,
50 b'command-name': FRAME_TYPE_COMMAND_NAME,
40 b'command-argument': FRAME_TYPE_COMMAND_ARGUMENT,
51 b'command-argument': FRAME_TYPE_COMMAND_ARGUMENT,
41 b'command-data': FRAME_TYPE_COMMAND_DATA,
52 b'command-data': FRAME_TYPE_COMMAND_DATA,
42 b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
53 b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
43 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
54 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
44 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
55 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
56 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
45 }
57 }
46
58
47 FLAG_COMMAND_NAME_EOS = 0x01
59 FLAG_COMMAND_NAME_EOS = 0x01
48 FLAG_COMMAND_NAME_HAVE_ARGS = 0x02
60 FLAG_COMMAND_NAME_HAVE_ARGS = 0x02
49 FLAG_COMMAND_NAME_HAVE_DATA = 0x04
61 FLAG_COMMAND_NAME_HAVE_DATA = 0x04
50
62
51 FLAGS_COMMAND = {
63 FLAGS_COMMAND = {
52 b'eos': FLAG_COMMAND_NAME_EOS,
64 b'eos': FLAG_COMMAND_NAME_EOS,
53 b'have-args': FLAG_COMMAND_NAME_HAVE_ARGS,
65 b'have-args': FLAG_COMMAND_NAME_HAVE_ARGS,
54 b'have-data': FLAG_COMMAND_NAME_HAVE_DATA,
66 b'have-data': FLAG_COMMAND_NAME_HAVE_DATA,
55 }
67 }
56
68
57 FLAG_COMMAND_ARGUMENT_CONTINUATION = 0x01
69 FLAG_COMMAND_ARGUMENT_CONTINUATION = 0x01
58 FLAG_COMMAND_ARGUMENT_EOA = 0x02
70 FLAG_COMMAND_ARGUMENT_EOA = 0x02
59
71
60 FLAGS_COMMAND_ARGUMENT = {
72 FLAGS_COMMAND_ARGUMENT = {
61 b'continuation': FLAG_COMMAND_ARGUMENT_CONTINUATION,
73 b'continuation': FLAG_COMMAND_ARGUMENT_CONTINUATION,
62 b'eoa': FLAG_COMMAND_ARGUMENT_EOA,
74 b'eoa': FLAG_COMMAND_ARGUMENT_EOA,
63 }
75 }
64
76
65 FLAG_COMMAND_DATA_CONTINUATION = 0x01
77 FLAG_COMMAND_DATA_CONTINUATION = 0x01
66 FLAG_COMMAND_DATA_EOS = 0x02
78 FLAG_COMMAND_DATA_EOS = 0x02
67
79
68 FLAGS_COMMAND_DATA = {
80 FLAGS_COMMAND_DATA = {
69 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
81 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
70 b'eos': FLAG_COMMAND_DATA_EOS,
82 b'eos': FLAG_COMMAND_DATA_EOS,
71 }
83 }
72
84
73 FLAG_BYTES_RESPONSE_CONTINUATION = 0x01
85 FLAG_BYTES_RESPONSE_CONTINUATION = 0x01
74 FLAG_BYTES_RESPONSE_EOS = 0x02
86 FLAG_BYTES_RESPONSE_EOS = 0x02
75
87
76 FLAGS_BYTES_RESPONSE = {
88 FLAGS_BYTES_RESPONSE = {
77 b'continuation': FLAG_BYTES_RESPONSE_CONTINUATION,
89 b'continuation': FLAG_BYTES_RESPONSE_CONTINUATION,
78 b'eos': FLAG_BYTES_RESPONSE_EOS,
90 b'eos': FLAG_BYTES_RESPONSE_EOS,
79 }
91 }
80
92
81 FLAG_ERROR_RESPONSE_PROTOCOL = 0x01
93 FLAG_ERROR_RESPONSE_PROTOCOL = 0x01
82 FLAG_ERROR_RESPONSE_APPLICATION = 0x02
94 FLAG_ERROR_RESPONSE_APPLICATION = 0x02
83
95
84 FLAGS_ERROR_RESPONSE = {
96 FLAGS_ERROR_RESPONSE = {
85 b'protocol': FLAG_ERROR_RESPONSE_PROTOCOL,
97 b'protocol': FLAG_ERROR_RESPONSE_PROTOCOL,
86 b'application': FLAG_ERROR_RESPONSE_APPLICATION,
98 b'application': FLAG_ERROR_RESPONSE_APPLICATION,
87 }
99 }
88
100
89 # Maps frame types to their available flags.
101 # Maps frame types to their available flags.
90 FRAME_TYPE_FLAGS = {
102 FRAME_TYPE_FLAGS = {
91 FRAME_TYPE_COMMAND_NAME: FLAGS_COMMAND,
103 FRAME_TYPE_COMMAND_NAME: FLAGS_COMMAND,
92 FRAME_TYPE_COMMAND_ARGUMENT: FLAGS_COMMAND_ARGUMENT,
104 FRAME_TYPE_COMMAND_ARGUMENT: FLAGS_COMMAND_ARGUMENT,
93 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
105 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
94 FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
106 FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
95 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
107 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
96 FRAME_TYPE_TEXT_OUTPUT: {},
108 FRAME_TYPE_TEXT_OUTPUT: {},
109 FRAME_TYPE_STREAM_SETTINGS: {},
97 }
110 }
98
111
99 ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH')
112 ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH')
100
113
101 @attr.s(slots=True)
114 @attr.s(slots=True)
102 class frameheader(object):
115 class frameheader(object):
103 """Represents the data in a frame header."""
116 """Represents the data in a frame header."""
104
117
105 length = attr.ib()
118 length = attr.ib()
106 requestid = attr.ib()
119 requestid = attr.ib()
120 streamid = attr.ib()
121 streamflags = attr.ib()
107 typeid = attr.ib()
122 typeid = attr.ib()
108 flags = attr.ib()
123 flags = attr.ib()
109
124
110 @attr.s(slots=True)
125 @attr.s(slots=True)
111 class frame(object):
126 class frame(object):
112 """Represents a parsed frame."""
127 """Represents a parsed frame."""
113
128
114 requestid = attr.ib()
129 requestid = attr.ib()
130 streamid = attr.ib()
131 streamflags = attr.ib()
115 typeid = attr.ib()
132 typeid = attr.ib()
116 flags = attr.ib()
133 flags = attr.ib()
117 payload = attr.ib()
134 payload = attr.ib()
118
135
119 def makeframe(requestid, typeid, flags, payload):
136 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
120 """Assemble a frame into a byte array."""
137 """Assemble a frame into a byte array."""
121 # TODO assert size of payload.
138 # TODO assert size of payload.
122 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
139 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
123
140
124 # 24 bits length
141 # 24 bits length
125 # 16 bits request id
142 # 16 bits request id
143 # 8 bits stream id
144 # 8 bits stream flags
126 # 4 bits type
145 # 4 bits type
127 # 4 bits flags
146 # 4 bits flags
128
147
129 l = struct.pack(r'<I', len(payload))
148 l = struct.pack(r'<I', len(payload))
130 frame[0:3] = l[0:3]
149 frame[0:3] = l[0:3]
131 struct.pack_into(r'<H', frame, 3, requestid)
150 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
132 frame[5] = (typeid << 4) | flags
151 frame[7] = (typeid << 4) | flags
133 frame[6:] = payload
152 frame[8:] = payload
134
153
135 return frame
154 return frame
136
155
137 def makeframefromhumanstring(s):
156 def makeframefromhumanstring(s):
138 """Create a frame from a human readable string
157 """Create a frame from a human readable string
139
158
140 Strings have the form:
159 Strings have the form:
141
160
142 <request-id> <type> <flags> <payload>
161 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
143
162
144 This can be used by user-facing applications and tests for creating
163 This can be used by user-facing applications and tests for creating
145 frames easily without having to type out a bunch of constants.
164 frames easily without having to type out a bunch of constants.
146
165
147 Request ID is an integer.
166 Request ID and stream IDs are integers.
148
167
149 Frame type and flags can be specified by integer or named constant.
168 Stream flags, frame type, and flags can be specified by integer or
169 named constant.
150
170
151 Flags can be delimited by `|` to bitwise OR them together.
171 Flags can be delimited by `|` to bitwise OR them together.
152 """
172 """
153 requestid, frametype, frameflags, payload = s.split(b' ', 3)
173 fields = s.split(b' ', 5)
174 requestid, streamid, streamflags, frametype, frameflags, payload = fields
154
175
155 requestid = int(requestid)
176 requestid = int(requestid)
177 streamid = int(streamid)
178
179 finalstreamflags = 0
180 for flag in streamflags.split(b'|'):
181 if flag in STREAM_FLAGS:
182 finalstreamflags |= STREAM_FLAGS[flag]
183 else:
184 finalstreamflags |= int(flag)
156
185
157 if frametype in FRAME_TYPES:
186 if frametype in FRAME_TYPES:
158 frametype = FRAME_TYPES[frametype]
187 frametype = FRAME_TYPES[frametype]
159 else:
188 else:
160 frametype = int(frametype)
189 frametype = int(frametype)
161
190
162 finalflags = 0
191 finalflags = 0
163 validflags = FRAME_TYPE_FLAGS[frametype]
192 validflags = FRAME_TYPE_FLAGS[frametype]
164 for flag in frameflags.split(b'|'):
193 for flag in frameflags.split(b'|'):
165 if flag in validflags:
194 if flag in validflags:
166 finalflags |= validflags[flag]
195 finalflags |= validflags[flag]
167 else:
196 else:
168 finalflags |= int(flag)
197 finalflags |= int(flag)
169
198
170 payload = stringutil.unescapestr(payload)
199 payload = stringutil.unescapestr(payload)
171
200
172 return makeframe(requestid=requestid, typeid=frametype,
201 return makeframe(requestid=requestid, streamid=streamid,
202 streamflags=finalstreamflags, typeid=frametype,
173 flags=finalflags, payload=payload)
203 flags=finalflags, payload=payload)
174
204
175 def parseheader(data):
205 def parseheader(data):
176 """Parse a unified framing protocol frame header from a buffer.
206 """Parse a unified framing protocol frame header from a buffer.
177
207
178 The header is expected to be in the buffer at offset 0 and the
208 The header is expected to be in the buffer at offset 0 and the
179 buffer is expected to be large enough to hold a full header.
209 buffer is expected to be large enough to hold a full header.
180 """
210 """
181 # 24 bits payload length (little endian)
211 # 24 bits payload length (little endian)
212 # 16 bits request ID
213 # 8 bits stream ID
214 # 8 bits stream flags
182 # 4 bits frame type
215 # 4 bits frame type
183 # 4 bits frame flags
216 # 4 bits frame flags
184 # ... payload
217 # ... payload
185 framelength = data[0] + 256 * data[1] + 16384 * data[2]
218 framelength = data[0] + 256 * data[1] + 16384 * data[2]
186 requestid = struct.unpack_from(r'<H', data, 3)[0]
219 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
187 typeflags = data[5]
220 typeflags = data[7]
188
221
189 frametype = (typeflags & 0xf0) >> 4
222 frametype = (typeflags & 0xf0) >> 4
190 frameflags = typeflags & 0x0f
223 frameflags = typeflags & 0x0f
191
224
192 return frameheader(framelength, requestid, frametype, frameflags)
225 return frameheader(framelength, requestid, streamid, streamflags,
226 frametype, frameflags)
193
227
194 def readframe(fh):
228 def readframe(fh):
195 """Read a unified framing protocol frame from a file object.
229 """Read a unified framing protocol frame from a file object.
196
230
197 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
231 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
198 None if no frame is available. May raise if a malformed frame is
232 None if no frame is available. May raise if a malformed frame is
199 seen.
233 seen.
200 """
234 """
201 header = bytearray(FRAME_HEADER_SIZE)
235 header = bytearray(FRAME_HEADER_SIZE)
202
236
203 readcount = fh.readinto(header)
237 readcount = fh.readinto(header)
204
238
205 if readcount == 0:
239 if readcount == 0:
206 return None
240 return None
207
241
208 if readcount != FRAME_HEADER_SIZE:
242 if readcount != FRAME_HEADER_SIZE:
209 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
243 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
210 (readcount, header))
244 (readcount, header))
211
245
212 h = parseheader(header)
246 h = parseheader(header)
213
247
214 payload = fh.read(h.length)
248 payload = fh.read(h.length)
215 if len(payload) != h.length:
249 if len(payload) != h.length:
216 raise error.Abort(_('frame length error: expected %d; got %d') %
250 raise error.Abort(_('frame length error: expected %d; got %d') %
217 (h.length, len(payload)))
251 (h.length, len(payload)))
218
252
219 return frame(h.requestid, h.typeid, h.flags, payload)
253 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
254 payload)
220
255
221 def createcommandframes(stream, requestid, cmd, args, datafh=None):
256 def createcommandframes(stream, requestid, cmd, args, datafh=None):
222 """Create frames necessary to transmit a request to run a command.
257 """Create frames necessary to transmit a request to run a command.
223
258
224 This is a generator of bytearrays. Each item represents a frame
259 This is a generator of bytearrays. Each item represents a frame
225 ready to be sent over the wire to a peer.
260 ready to be sent over the wire to a peer.
226 """
261 """
227 flags = 0
262 flags = 0
228 if args:
263 if args:
229 flags |= FLAG_COMMAND_NAME_HAVE_ARGS
264 flags |= FLAG_COMMAND_NAME_HAVE_ARGS
230 if datafh:
265 if datafh:
231 flags |= FLAG_COMMAND_NAME_HAVE_DATA
266 flags |= FLAG_COMMAND_NAME_HAVE_DATA
232
267
233 if not flags:
268 if not flags:
234 flags |= FLAG_COMMAND_NAME_EOS
269 flags |= FLAG_COMMAND_NAME_EOS
235
270
236 yield stream.makeframe(requestid=requestid, typeid=FRAME_TYPE_COMMAND_NAME,
271 yield stream.makeframe(requestid=requestid, typeid=FRAME_TYPE_COMMAND_NAME,
237 flags=flags, payload=cmd)
272 flags=flags, payload=cmd)
238
273
239 for i, k in enumerate(sorted(args)):
274 for i, k in enumerate(sorted(args)):
240 v = args[k]
275 v = args[k]
241 last = i == len(args) - 1
276 last = i == len(args) - 1
242
277
243 # TODO handle splitting of argument values across frames.
278 # TODO handle splitting of argument values across frames.
244 payload = bytearray(ARGUMENT_FRAME_HEADER.size + len(k) + len(v))
279 payload = bytearray(ARGUMENT_FRAME_HEADER.size + len(k) + len(v))
245 offset = 0
280 offset = 0
246 ARGUMENT_FRAME_HEADER.pack_into(payload, offset, len(k), len(v))
281 ARGUMENT_FRAME_HEADER.pack_into(payload, offset, len(k), len(v))
247 offset += ARGUMENT_FRAME_HEADER.size
282 offset += ARGUMENT_FRAME_HEADER.size
248 payload[offset:offset + len(k)] = k
283 payload[offset:offset + len(k)] = k
249 offset += len(k)
284 offset += len(k)
250 payload[offset:offset + len(v)] = v
285 payload[offset:offset + len(v)] = v
251
286
252 flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0
287 flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0
253 yield stream.makeframe(requestid=requestid,
288 yield stream.makeframe(requestid=requestid,
254 typeid=FRAME_TYPE_COMMAND_ARGUMENT,
289 typeid=FRAME_TYPE_COMMAND_ARGUMENT,
255 flags=flags,
290 flags=flags,
256 payload=payload)
291 payload=payload)
257
292
258 if datafh:
293 if datafh:
259 while True:
294 while True:
260 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
295 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
261
296
262 done = False
297 done = False
263 if len(data) == DEFAULT_MAX_FRAME_SIZE:
298 if len(data) == DEFAULT_MAX_FRAME_SIZE:
264 flags = FLAG_COMMAND_DATA_CONTINUATION
299 flags = FLAG_COMMAND_DATA_CONTINUATION
265 else:
300 else:
266 flags = FLAG_COMMAND_DATA_EOS
301 flags = FLAG_COMMAND_DATA_EOS
267 assert datafh.read(1) == b''
302 assert datafh.read(1) == b''
268 done = True
303 done = True
269
304
270 yield stream.makeframe(requestid=requestid,
305 yield stream.makeframe(requestid=requestid,
271 typeid=FRAME_TYPE_COMMAND_DATA,
306 typeid=FRAME_TYPE_COMMAND_DATA,
272 flags=flags,
307 flags=flags,
273 payload=data)
308 payload=data)
274
309
275 if done:
310 if done:
276 break
311 break
277
312
278 def createbytesresponseframesfrombytes(stream, requestid, data,
313 def createbytesresponseframesfrombytes(stream, requestid, data,
279 maxframesize=DEFAULT_MAX_FRAME_SIZE):
314 maxframesize=DEFAULT_MAX_FRAME_SIZE):
280 """Create a raw frame to send a bytes response from static bytes input.
315 """Create a raw frame to send a bytes response from static bytes input.
281
316
282 Returns a generator of bytearrays.
317 Returns a generator of bytearrays.
283 """
318 """
284
319
285 # Simple case of a single frame.
320 # Simple case of a single frame.
286 if len(data) <= maxframesize:
321 if len(data) <= maxframesize:
287 yield stream.makeframe(requestid=requestid,
322 yield stream.makeframe(requestid=requestid,
288 typeid=FRAME_TYPE_BYTES_RESPONSE,
323 typeid=FRAME_TYPE_BYTES_RESPONSE,
289 flags=FLAG_BYTES_RESPONSE_EOS,
324 flags=FLAG_BYTES_RESPONSE_EOS,
290 payload=data)
325 payload=data)
291 return
326 return
292
327
293 offset = 0
328 offset = 0
294 while True:
329 while True:
295 chunk = data[offset:offset + maxframesize]
330 chunk = data[offset:offset + maxframesize]
296 offset += len(chunk)
331 offset += len(chunk)
297 done = offset == len(data)
332 done = offset == len(data)
298
333
299 if done:
334 if done:
300 flags = FLAG_BYTES_RESPONSE_EOS
335 flags = FLAG_BYTES_RESPONSE_EOS
301 else:
336 else:
302 flags = FLAG_BYTES_RESPONSE_CONTINUATION
337 flags = FLAG_BYTES_RESPONSE_CONTINUATION
303
338
304 yield stream.makeframe(requestid=requestid,
339 yield stream.makeframe(requestid=requestid,
305 typeid=FRAME_TYPE_BYTES_RESPONSE,
340 typeid=FRAME_TYPE_BYTES_RESPONSE,
306 flags=flags,
341 flags=flags,
307 payload=chunk)
342 payload=chunk)
308
343
309 if done:
344 if done:
310 break
345 break
311
346
312 def createerrorframe(stream, requestid, msg, protocol=False, application=False):
347 def createerrorframe(stream, requestid, msg, protocol=False, application=False):
313 # TODO properly handle frame size limits.
348 # TODO properly handle frame size limits.
314 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
349 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
315
350
316 flags = 0
351 flags = 0
317 if protocol:
352 if protocol:
318 flags |= FLAG_ERROR_RESPONSE_PROTOCOL
353 flags |= FLAG_ERROR_RESPONSE_PROTOCOL
319 if application:
354 if application:
320 flags |= FLAG_ERROR_RESPONSE_APPLICATION
355 flags |= FLAG_ERROR_RESPONSE_APPLICATION
321
356
322 yield stream.makeframe(requestid=requestid,
357 yield stream.makeframe(requestid=requestid,
323 typeid=FRAME_TYPE_ERROR_RESPONSE,
358 typeid=FRAME_TYPE_ERROR_RESPONSE,
324 flags=flags,
359 flags=flags,
325 payload=msg)
360 payload=msg)
326
361
327 def createtextoutputframe(stream, requestid, atoms):
362 def createtextoutputframe(stream, requestid, atoms):
328 """Create a text output frame to render text to people.
363 """Create a text output frame to render text to people.
329
364
330 ``atoms`` is a 3-tuple of (formatting string, args, labels).
365 ``atoms`` is a 3-tuple of (formatting string, args, labels).
331
366
332 The formatting string contains ``%s`` tokens to be replaced by the
367 The formatting string contains ``%s`` tokens to be replaced by the
333 corresponding indexed entry in ``args``. ``labels`` is an iterable of
368 corresponding indexed entry in ``args``. ``labels`` is an iterable of
334 formatters to be applied at rendering time. In terms of the ``ui``
369 formatters to be applied at rendering time. In terms of the ``ui``
335 class, each atom corresponds to a ``ui.write()``.
370 class, each atom corresponds to a ``ui.write()``.
336 """
371 """
337 bytesleft = DEFAULT_MAX_FRAME_SIZE
372 bytesleft = DEFAULT_MAX_FRAME_SIZE
338 atomchunks = []
373 atomchunks = []
339
374
340 for (formatting, args, labels) in atoms:
375 for (formatting, args, labels) in atoms:
341 if len(args) > 255:
376 if len(args) > 255:
342 raise ValueError('cannot use more than 255 formatting arguments')
377 raise ValueError('cannot use more than 255 formatting arguments')
343 if len(labels) > 255:
378 if len(labels) > 255:
344 raise ValueError('cannot use more than 255 labels')
379 raise ValueError('cannot use more than 255 labels')
345
380
346 # TODO look for localstr, other types here?
381 # TODO look for localstr, other types here?
347
382
348 if not isinstance(formatting, bytes):
383 if not isinstance(formatting, bytes):
349 raise ValueError('must use bytes formatting strings')
384 raise ValueError('must use bytes formatting strings')
350 for arg in args:
385 for arg in args:
351 if not isinstance(arg, bytes):
386 if not isinstance(arg, bytes):
352 raise ValueError('must use bytes for arguments')
387 raise ValueError('must use bytes for arguments')
353 for label in labels:
388 for label in labels:
354 if not isinstance(label, bytes):
389 if not isinstance(label, bytes):
355 raise ValueError('must use bytes for labels')
390 raise ValueError('must use bytes for labels')
356
391
357 # Formatting string must be UTF-8.
392 # Formatting string must be UTF-8.
358 formatting = formatting.decode(r'utf-8', r'replace').encode(r'utf-8')
393 formatting = formatting.decode(r'utf-8', r'replace').encode(r'utf-8')
359
394
360 # Arguments must be UTF-8.
395 # Arguments must be UTF-8.
361 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
396 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
362
397
363 # Labels must be ASCII.
398 # Labels must be ASCII.
364 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
399 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
365 for l in labels]
400 for l in labels]
366
401
367 if len(formatting) > 65535:
402 if len(formatting) > 65535:
368 raise ValueError('formatting string cannot be longer than 64k')
403 raise ValueError('formatting string cannot be longer than 64k')
369
404
370 if any(len(a) > 65535 for a in args):
405 if any(len(a) > 65535 for a in args):
371 raise ValueError('argument string cannot be longer than 64k')
406 raise ValueError('argument string cannot be longer than 64k')
372
407
373 if any(len(l) > 255 for l in labels):
408 if any(len(l) > 255 for l in labels):
374 raise ValueError('label string cannot be longer than 255 bytes')
409 raise ValueError('label string cannot be longer than 255 bytes')
375
410
376 chunks = [
411 chunks = [
377 struct.pack(r'<H', len(formatting)),
412 struct.pack(r'<H', len(formatting)),
378 struct.pack(r'<BB', len(labels), len(args)),
413 struct.pack(r'<BB', len(labels), len(args)),
379 struct.pack(r'<' + r'B' * len(labels), *map(len, labels)),
414 struct.pack(r'<' + r'B' * len(labels), *map(len, labels)),
380 struct.pack(r'<' + r'H' * len(args), *map(len, args)),
415 struct.pack(r'<' + r'H' * len(args), *map(len, args)),
381 ]
416 ]
382 chunks.append(formatting)
417 chunks.append(formatting)
383 chunks.extend(labels)
418 chunks.extend(labels)
384 chunks.extend(args)
419 chunks.extend(args)
385
420
386 atom = b''.join(chunks)
421 atom = b''.join(chunks)
387 atomchunks.append(atom)
422 atomchunks.append(atom)
388 bytesleft -= len(atom)
423 bytesleft -= len(atom)
389
424
390 if bytesleft < 0:
425 if bytesleft < 0:
391 raise ValueError('cannot encode data in a single frame')
426 raise ValueError('cannot encode data in a single frame')
392
427
393 yield stream.makeframe(requestid=requestid,
428 yield stream.makeframe(requestid=requestid,
394 typeid=FRAME_TYPE_TEXT_OUTPUT,
429 typeid=FRAME_TYPE_TEXT_OUTPUT,
395 flags=0,
430 flags=0,
396 payload=b''.join(atomchunks))
431 payload=b''.join(atomchunks))
397
432
398 class stream(object):
433 class stream(object):
399 """Represents a logical unidirectional series of frames."""
434 """Represents a logical unidirectional series of frames."""
400
435
436 def __init__(self, streamid, active=False):
437 self.streamid = streamid
438 self._active = False
439
401 def makeframe(self, requestid, typeid, flags, payload):
440 def makeframe(self, requestid, typeid, flags, payload):
402 """Create a frame to be sent out over this stream.
441 """Create a frame to be sent out over this stream.
403
442
404 Only returns the frame instance. Does not actually send it.
443 Only returns the frame instance. Does not actually send it.
405 """
444 """
406 return makeframe(requestid, typeid, flags, payload)
445 streamflags = 0
446 if not self._active:
447 streamflags |= STREAM_FLAG_BEGIN_STREAM
448 self._active = True
449
450 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
451 payload)
452
453 def ensureserverstream(stream):
454 if stream.streamid % 2:
455 raise error.ProgrammingError('server should only write to even '
456 'numbered streams; %d is not even' %
457 stream.streamid)
407
458
408 class serverreactor(object):
459 class serverreactor(object):
409 """Holds state of a server handling frame-based protocol requests.
460 """Holds state of a server handling frame-based protocol requests.
410
461
411 This class is the "brain" of the unified frame-based protocol server
462 This class is the "brain" of the unified frame-based protocol server
412 component. While the protocol is stateless from the perspective of
463 component. While the protocol is stateless from the perspective of
413 requests/commands, something needs to track which frames have been
464 requests/commands, something needs to track which frames have been
414 received, what frames to expect, etc. This class is that thing.
465 received, what frames to expect, etc. This class is that thing.
415
466
416 Instances are modeled as a state machine of sorts. Instances are also
467 Instances are modeled as a state machine of sorts. Instances are also
417 reactionary to external events. The point of this class is to encapsulate
468 reactionary to external events. The point of this class is to encapsulate
418 the state of the connection and the exchange of frames, not to perform
469 the state of the connection and the exchange of frames, not to perform
419 work. Instead, callers tell this class when something occurs, like a
470 work. Instead, callers tell this class when something occurs, like a
420 frame arriving. If that activity is worthy of a follow-up action (say
471 frame arriving. If that activity is worthy of a follow-up action (say
421 *run a command*), the return value of that handler will say so.
472 *run a command*), the return value of that handler will say so.
422
473
423 I/O and CPU intensive operations are purposefully delegated outside of
474 I/O and CPU intensive operations are purposefully delegated outside of
424 this class.
475 this class.
425
476
426 Consumers are expected to tell instances when events occur. They do so by
477 Consumers are expected to tell instances when events occur. They do so by
427 calling the various ``on*`` methods. These methods return a 2-tuple
478 calling the various ``on*`` methods. These methods return a 2-tuple
428 describing any follow-up action(s) to take. The first element is the
479 describing any follow-up action(s) to take. The first element is the
429 name of an action to perform. The second is a data structure (usually
480 name of an action to perform. The second is a data structure (usually
430 a dict) specific to that action that contains more information. e.g.
481 a dict) specific to that action that contains more information. e.g.
431 if the server wants to send frames back to the client, the data structure
482 if the server wants to send frames back to the client, the data structure
432 will contain a reference to those frames.
483 will contain a reference to those frames.
433
484
434 Valid actions that consumers can be instructed to take are:
485 Valid actions that consumers can be instructed to take are:
435
486
436 sendframes
487 sendframes
437 Indicates that frames should be sent to the client. The ``framegen``
488 Indicates that frames should be sent to the client. The ``framegen``
438 key contains a generator of frames that should be sent. The server
489 key contains a generator of frames that should be sent. The server
439 assumes that all frames are sent to the client.
490 assumes that all frames are sent to the client.
440
491
441 error
492 error
442 Indicates that an error occurred. Consumer should probably abort.
493 Indicates that an error occurred. Consumer should probably abort.
443
494
444 runcommand
495 runcommand
445 Indicates that the consumer should run a wire protocol command. Details
496 Indicates that the consumer should run a wire protocol command. Details
446 of the command to run are given in the data structure.
497 of the command to run are given in the data structure.
447
498
448 wantframe
499 wantframe
449 Indicates that nothing of interest happened and the server is waiting on
500 Indicates that nothing of interest happened and the server is waiting on
450 more frames from the client before anything interesting can be done.
501 more frames from the client before anything interesting can be done.
451
502
452 noop
503 noop
453 Indicates no additional action is required.
504 Indicates no additional action is required.
454
505
455 Known Issues
506 Known Issues
456 ------------
507 ------------
457
508
458 There are no limits to the number of partially received commands or their
509 There are no limits to the number of partially received commands or their
459 size. A malicious client could stream command request data and exhaust the
510 size. A malicious client could stream command request data and exhaust the
460 server's memory.
511 server's memory.
461
512
462 Partially received commands are not acted upon when end of input is
513 Partially received commands are not acted upon when end of input is
463 reached. Should the server error if it receives a partial request?
514 reached. Should the server error if it receives a partial request?
464 Should the client send a message to abort a partially transmitted request
515 Should the client send a message to abort a partially transmitted request
465 to facilitate graceful shutdown?
516 to facilitate graceful shutdown?
466
517
467 Active requests that haven't been responded to aren't tracked. This means
518 Active requests that haven't been responded to aren't tracked. This means
468 that if we receive a command and instruct its dispatch, another command
519 that if we receive a command and instruct its dispatch, another command
469 with its request ID can come in over the wire and there will be a race
520 with its request ID can come in over the wire and there will be a race
470 between who responds to what.
521 between who responds to what.
471 """
522 """
472
523
473 def __init__(self, deferoutput=False):
524 def __init__(self, deferoutput=False):
474 """Construct a new server reactor.
525 """Construct a new server reactor.
475
526
476 ``deferoutput`` can be used to indicate that no output frames should be
527 ``deferoutput`` can be used to indicate that no output frames should be
477 instructed to be sent until input has been exhausted. In this mode,
528 instructed to be sent until input has been exhausted. In this mode,
478 events that would normally generate output frames (such as a command
529 events that would normally generate output frames (such as a command
479 response being ready) will instead defer instructing the consumer to
530 response being ready) will instead defer instructing the consumer to
480 send those frames. This is useful for half-duplex transports where the
531 send those frames. This is useful for half-duplex transports where the
481 sender cannot receive until all data has been transmitted.
532 sender cannot receive until all data has been transmitted.
482 """
533 """
483 self._deferoutput = deferoutput
534 self._deferoutput = deferoutput
484 self._state = 'idle'
535 self._state = 'idle'
485 self._bufferedframegens = []
536 self._bufferedframegens = []
537 # stream id -> stream instance for all active streams from the client.
538 self._incomingstreams = {}
486 # request id -> dict of commands that are actively being received.
539 # request id -> dict of commands that are actively being received.
487 self._receivingcommands = {}
540 self._receivingcommands = {}
488 # Request IDs that have been received and are actively being processed.
541 # Request IDs that have been received and are actively being processed.
489 # Once all output for a request has been sent, it is removed from this
542 # Once all output for a request has been sent, it is removed from this
490 # set.
543 # set.
491 self._activecommands = set()
544 self._activecommands = set()
492
545
493 def onframerecv(self, frame):
546 def onframerecv(self, frame):
494 """Process a frame that has been received off the wire.
547 """Process a frame that has been received off the wire.
495
548
496 Returns a dict with an ``action`` key that details what action,
549 Returns a dict with an ``action`` key that details what action,
497 if any, the consumer should take next.
550 if any, the consumer should take next.
498 """
551 """
552 if not frame.streamid % 2:
553 self._state = 'errored'
554 return self._makeerrorresult(
555 _('received frame with even numbered stream ID: %d') %
556 frame.streamid)
557
558 if frame.streamid not in self._incomingstreams:
559 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
560 self._state = 'errored'
561 return self._makeerrorresult(
562 _('received frame on unknown inactive stream without '
563 'beginning of stream flag set'))
564
565 self._incomingstreams[frame.streamid] = stream(frame.streamid)
566
567 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
568 # TODO handle decoding frames
569 self._state = 'errored'
570 raise error.ProgrammingError('support for decoding stream payloads '
571 'not yet implemented')
572
573 if frame.streamflags & STREAM_FLAG_END_STREAM:
574 del self._incomingstreams[frame.streamid]
575
499 handlers = {
576 handlers = {
500 'idle': self._onframeidle,
577 'idle': self._onframeidle,
501 'command-receiving': self._onframecommandreceiving,
578 'command-receiving': self._onframecommandreceiving,
502 'errored': self._onframeerrored,
579 'errored': self._onframeerrored,
503 }
580 }
504
581
505 meth = handlers.get(self._state)
582 meth = handlers.get(self._state)
506 if not meth:
583 if not meth:
507 raise error.ProgrammingError('unhandled state: %s' % self._state)
584 raise error.ProgrammingError('unhandled state: %s' % self._state)
508
585
509 return meth(frame)
586 return meth(frame)
510
587
511 def onbytesresponseready(self, stream, requestid, data):
588 def onbytesresponseready(self, stream, requestid, data):
512 """Signal that a bytes response is ready to be sent to the client.
589 """Signal that a bytes response is ready to be sent to the client.
513
590
514 The raw bytes response is passed as an argument.
591 The raw bytes response is passed as an argument.
515 """
592 """
593 ensureserverstream(stream)
594
516 def sendframes():
595 def sendframes():
517 for frame in createbytesresponseframesfrombytes(stream, requestid,
596 for frame in createbytesresponseframesfrombytes(stream, requestid,
518 data):
597 data):
519 yield frame
598 yield frame
520
599
521 self._activecommands.remove(requestid)
600 self._activecommands.remove(requestid)
522
601
523 result = sendframes()
602 result = sendframes()
524
603
525 if self._deferoutput:
604 if self._deferoutput:
526 self._bufferedframegens.append(result)
605 self._bufferedframegens.append(result)
527 return 'noop', {}
606 return 'noop', {}
528 else:
607 else:
529 return 'sendframes', {
608 return 'sendframes', {
530 'framegen': result,
609 'framegen': result,
531 }
610 }
532
611
533 def oninputeof(self):
612 def oninputeof(self):
534 """Signals that end of input has been received.
613 """Signals that end of input has been received.
535
614
536 No more frames will be received. All pending activity should be
615 No more frames will be received. All pending activity should be
537 completed.
616 completed.
538 """
617 """
539 # TODO should we do anything about in-flight commands?
618 # TODO should we do anything about in-flight commands?
540
619
541 if not self._deferoutput or not self._bufferedframegens:
620 if not self._deferoutput or not self._bufferedframegens:
542 return 'noop', {}
621 return 'noop', {}
543
622
544 # If we buffered all our responses, emit those.
623 # If we buffered all our responses, emit those.
545 def makegen():
624 def makegen():
546 for gen in self._bufferedframegens:
625 for gen in self._bufferedframegens:
547 for frame in gen:
626 for frame in gen:
548 yield frame
627 yield frame
549
628
550 return 'sendframes', {
629 return 'sendframes', {
551 'framegen': makegen(),
630 'framegen': makegen(),
552 }
631 }
553
632
554 def onapplicationerror(self, stream, requestid, msg):
633 def onapplicationerror(self, stream, requestid, msg):
634 ensureserverstream(stream)
635
555 return 'sendframes', {
636 return 'sendframes', {
556 'framegen': createerrorframe(stream, requestid, msg,
637 'framegen': createerrorframe(stream, requestid, msg,
557 application=True),
638 application=True),
558 }
639 }
559
640
560 def _makeerrorresult(self, msg):
641 def _makeerrorresult(self, msg):
561 return 'error', {
642 return 'error', {
562 'message': msg,
643 'message': msg,
563 }
644 }
564
645
565 def _makeruncommandresult(self, requestid):
646 def _makeruncommandresult(self, requestid):
566 entry = self._receivingcommands[requestid]
647 entry = self._receivingcommands[requestid]
567 del self._receivingcommands[requestid]
648 del self._receivingcommands[requestid]
568
649
569 if self._receivingcommands:
650 if self._receivingcommands:
570 self._state = 'command-receiving'
651 self._state = 'command-receiving'
571 else:
652 else:
572 self._state = 'idle'
653 self._state = 'idle'
573
654
574 assert requestid not in self._activecommands
655 assert requestid not in self._activecommands
575 self._activecommands.add(requestid)
656 self._activecommands.add(requestid)
576
657
577 return 'runcommand', {
658 return 'runcommand', {
578 'requestid': requestid,
659 'requestid': requestid,
579 'command': entry['command'],
660 'command': entry['command'],
580 'args': entry['args'],
661 'args': entry['args'],
581 'data': entry['data'].getvalue() if entry['data'] else None,
662 'data': entry['data'].getvalue() if entry['data'] else None,
582 }
663 }
583
664
584 def _makewantframeresult(self):
665 def _makewantframeresult(self):
585 return 'wantframe', {
666 return 'wantframe', {
586 'state': self._state,
667 'state': self._state,
587 }
668 }
588
669
589 def _onframeidle(self, frame):
670 def _onframeidle(self, frame):
590 # The only frame type that should be received in this state is a
671 # The only frame type that should be received in this state is a
591 # command request.
672 # command request.
592 if frame.typeid != FRAME_TYPE_COMMAND_NAME:
673 if frame.typeid != FRAME_TYPE_COMMAND_NAME:
593 self._state = 'errored'
674 self._state = 'errored'
594 return self._makeerrorresult(
675 return self._makeerrorresult(
595 _('expected command frame; got %d') % frame.typeid)
676 _('expected command frame; got %d') % frame.typeid)
596
677
597 if frame.requestid in self._receivingcommands:
678 if frame.requestid in self._receivingcommands:
598 self._state = 'errored'
679 self._state = 'errored'
599 return self._makeerrorresult(
680 return self._makeerrorresult(
600 _('request with ID %d already received') % frame.requestid)
681 _('request with ID %d already received') % frame.requestid)
601
682
602 if frame.requestid in self._activecommands:
683 if frame.requestid in self._activecommands:
603 self._state = 'errored'
684 self._state = 'errored'
604 return self._makeerrorresult((
685 return self._makeerrorresult((
605 _('request with ID %d is already active') % frame.requestid))
686 _('request with ID %d is already active') % frame.requestid))
606
687
607 expectingargs = bool(frame.flags & FLAG_COMMAND_NAME_HAVE_ARGS)
688 expectingargs = bool(frame.flags & FLAG_COMMAND_NAME_HAVE_ARGS)
608 expectingdata = bool(frame.flags & FLAG_COMMAND_NAME_HAVE_DATA)
689 expectingdata = bool(frame.flags & FLAG_COMMAND_NAME_HAVE_DATA)
609
690
610 self._receivingcommands[frame.requestid] = {
691 self._receivingcommands[frame.requestid] = {
611 'command': frame.payload,
692 'command': frame.payload,
612 'args': {},
693 'args': {},
613 'data': None,
694 'data': None,
614 'expectingargs': expectingargs,
695 'expectingargs': expectingargs,
615 'expectingdata': expectingdata,
696 'expectingdata': expectingdata,
616 }
697 }
617
698
618 if frame.flags & FLAG_COMMAND_NAME_EOS:
699 if frame.flags & FLAG_COMMAND_NAME_EOS:
619 return self._makeruncommandresult(frame.requestid)
700 return self._makeruncommandresult(frame.requestid)
620
701
621 if expectingargs or expectingdata:
702 if expectingargs or expectingdata:
622 self._state = 'command-receiving'
703 self._state = 'command-receiving'
623 return self._makewantframeresult()
704 return self._makewantframeresult()
624 else:
705 else:
625 self._state = 'errored'
706 self._state = 'errored'
626 return self._makeerrorresult(_('missing frame flags on '
707 return self._makeerrorresult(_('missing frame flags on '
627 'command frame'))
708 'command frame'))
628
709
629 def _onframecommandreceiving(self, frame):
710 def _onframecommandreceiving(self, frame):
630 # It could be a new command request. Process it as such.
711 # It could be a new command request. Process it as such.
631 if frame.typeid == FRAME_TYPE_COMMAND_NAME:
712 if frame.typeid == FRAME_TYPE_COMMAND_NAME:
632 return self._onframeidle(frame)
713 return self._onframeidle(frame)
633
714
634 # All other frames should be related to a command that is currently
715 # All other frames should be related to a command that is currently
635 # receiving but is not active.
716 # receiving but is not active.
636 if frame.requestid in self._activecommands:
717 if frame.requestid in self._activecommands:
637 self._state = 'errored'
718 self._state = 'errored'
638 return self._makeerrorresult(
719 return self._makeerrorresult(
639 _('received frame for request that is still active: %d') %
720 _('received frame for request that is still active: %d') %
640 frame.requestid)
721 frame.requestid)
641
722
642 if frame.requestid not in self._receivingcommands:
723 if frame.requestid not in self._receivingcommands:
643 self._state = 'errored'
724 self._state = 'errored'
644 return self._makeerrorresult(
725 return self._makeerrorresult(
645 _('received frame for request that is not receiving: %d') %
726 _('received frame for request that is not receiving: %d') %
646 frame.requestid)
727 frame.requestid)
647
728
648 entry = self._receivingcommands[frame.requestid]
729 entry = self._receivingcommands[frame.requestid]
649
730
650 if frame.typeid == FRAME_TYPE_COMMAND_ARGUMENT:
731 if frame.typeid == FRAME_TYPE_COMMAND_ARGUMENT:
651 if not entry['expectingargs']:
732 if not entry['expectingargs']:
652 self._state = 'errored'
733 self._state = 'errored'
653 return self._makeerrorresult(_(
734 return self._makeerrorresult(_(
654 'received command argument frame for request that is not '
735 'received command argument frame for request that is not '
655 'expecting arguments: %d') % frame.requestid)
736 'expecting arguments: %d') % frame.requestid)
656
737
657 return self._handlecommandargsframe(frame, entry)
738 return self._handlecommandargsframe(frame, entry)
658
739
659 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
740 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
660 if not entry['expectingdata']:
741 if not entry['expectingdata']:
661 self._state = 'errored'
742 self._state = 'errored'
662 return self._makeerrorresult(_(
743 return self._makeerrorresult(_(
663 'received command data frame for request that is not '
744 'received command data frame for request that is not '
664 'expecting data: %d') % frame.requestid)
745 'expecting data: %d') % frame.requestid)
665
746
666 if entry['data'] is None:
747 if entry['data'] is None:
667 entry['data'] = util.bytesio()
748 entry['data'] = util.bytesio()
668
749
669 return self._handlecommanddataframe(frame, entry)
750 return self._handlecommanddataframe(frame, entry)
670
751
671 def _handlecommandargsframe(self, frame, entry):
752 def _handlecommandargsframe(self, frame, entry):
672 # The frame and state of command should have already been validated.
753 # The frame and state of command should have already been validated.
673 assert frame.typeid == FRAME_TYPE_COMMAND_ARGUMENT
754 assert frame.typeid == FRAME_TYPE_COMMAND_ARGUMENT
674
755
675 offset = 0
756 offset = 0
676 namesize, valuesize = ARGUMENT_FRAME_HEADER.unpack_from(frame.payload)
757 namesize, valuesize = ARGUMENT_FRAME_HEADER.unpack_from(frame.payload)
677 offset += ARGUMENT_FRAME_HEADER.size
758 offset += ARGUMENT_FRAME_HEADER.size
678
759
679 # The argument name MUST fit inside the frame.
760 # The argument name MUST fit inside the frame.
680 argname = bytes(frame.payload[offset:offset + namesize])
761 argname = bytes(frame.payload[offset:offset + namesize])
681 offset += namesize
762 offset += namesize
682
763
683 if len(argname) != namesize:
764 if len(argname) != namesize:
684 self._state = 'errored'
765 self._state = 'errored'
685 return self._makeerrorresult(_('malformed argument frame: '
766 return self._makeerrorresult(_('malformed argument frame: '
686 'partial argument name'))
767 'partial argument name'))
687
768
688 argvalue = bytes(frame.payload[offset:])
769 argvalue = bytes(frame.payload[offset:])
689
770
690 # Argument value spans multiple frames. Record our active state
771 # Argument value spans multiple frames. Record our active state
691 # and wait for the next frame.
772 # and wait for the next frame.
692 if frame.flags & FLAG_COMMAND_ARGUMENT_CONTINUATION:
773 if frame.flags & FLAG_COMMAND_ARGUMENT_CONTINUATION:
693 raise error.ProgrammingError('not yet implemented')
774 raise error.ProgrammingError('not yet implemented')
694
775
695 # Common case: the argument value is completely contained in this
776 # Common case: the argument value is completely contained in this
696 # frame.
777 # frame.
697
778
698 if len(argvalue) != valuesize:
779 if len(argvalue) != valuesize:
699 self._state = 'errored'
780 self._state = 'errored'
700 return self._makeerrorresult(_('malformed argument frame: '
781 return self._makeerrorresult(_('malformed argument frame: '
701 'partial argument value'))
782 'partial argument value'))
702
783
703 entry['args'][argname] = argvalue
784 entry['args'][argname] = argvalue
704
785
705 if frame.flags & FLAG_COMMAND_ARGUMENT_EOA:
786 if frame.flags & FLAG_COMMAND_ARGUMENT_EOA:
706 if entry['expectingdata']:
787 if entry['expectingdata']:
707 # TODO signal request to run a command once we don't
788 # TODO signal request to run a command once we don't
708 # buffer data frames.
789 # buffer data frames.
709 return self._makewantframeresult()
790 return self._makewantframeresult()
710 else:
791 else:
711 return self._makeruncommandresult(frame.requestid)
792 return self._makeruncommandresult(frame.requestid)
712 else:
793 else:
713 return self._makewantframeresult()
794 return self._makewantframeresult()
714
795
715 def _handlecommanddataframe(self, frame, entry):
796 def _handlecommanddataframe(self, frame, entry):
716 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
797 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
717
798
718 # TODO support streaming data instead of buffering it.
799 # TODO support streaming data instead of buffering it.
719 entry['data'].write(frame.payload)
800 entry['data'].write(frame.payload)
720
801
721 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
802 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
722 return self._makewantframeresult()
803 return self._makewantframeresult()
723 elif frame.flags & FLAG_COMMAND_DATA_EOS:
804 elif frame.flags & FLAG_COMMAND_DATA_EOS:
724 entry['data'].seek(0)
805 entry['data'].seek(0)
725 return self._makeruncommandresult(frame.requestid)
806 return self._makeruncommandresult(frame.requestid)
726 else:
807 else:
727 self._state = 'errored'
808 self._state = 'errored'
728 return self._makeerrorresult(_('command data frame without '
809 return self._makeerrorresult(_('command data frame without '
729 'flags'))
810 'flags'))
730
811
731 def _onframeerrored(self, frame):
812 def _onframeerrored(self, frame):
732 return self._makeerrorresult(_('server already errored'))
813 return self._makeerrorresult(_('server already errored'))
@@ -1,1049 +1,1049 b''
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import struct
10 import struct
11 import sys
11 import sys
12 import threading
12 import threading
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 encoding,
16 encoding,
17 error,
17 error,
18 hook,
18 hook,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 wireproto,
21 wireproto,
22 wireprotoframing,
22 wireprotoframing,
23 wireprototypes,
23 wireprototypes,
24 )
24 )
25 from .utils import (
25 from .utils import (
26 procutil,
26 procutil,
27 )
27 )
28
28
29 stringio = util.stringio
29 stringio = util.stringio
30
30
31 urlerr = util.urlerr
31 urlerr = util.urlerr
32 urlreq = util.urlreq
32 urlreq = util.urlreq
33
33
34 HTTP_OK = 200
34 HTTP_OK = 200
35
35
36 HGTYPE = 'application/mercurial-0.1'
36 HGTYPE = 'application/mercurial-0.1'
37 HGTYPE2 = 'application/mercurial-0.2'
37 HGTYPE2 = 'application/mercurial-0.2'
38 HGERRTYPE = 'application/hg-error'
38 HGERRTYPE = 'application/hg-error'
39 FRAMINGTYPE = b'application/mercurial-exp-framing-0002'
39 FRAMINGTYPE = b'application/mercurial-exp-framing-0002'
40
40
41 HTTPV2 = wireprototypes.HTTPV2
41 HTTPV2 = wireprototypes.HTTPV2
42 SSHV1 = wireprototypes.SSHV1
42 SSHV1 = wireprototypes.SSHV1
43 SSHV2 = wireprototypes.SSHV2
43 SSHV2 = wireprototypes.SSHV2
44
44
45 def decodevaluefromheaders(req, headerprefix):
45 def decodevaluefromheaders(req, headerprefix):
46 """Decode a long value from multiple HTTP request headers.
46 """Decode a long value from multiple HTTP request headers.
47
47
48 Returns the value as a bytes, not a str.
48 Returns the value as a bytes, not a str.
49 """
49 """
50 chunks = []
50 chunks = []
51 i = 1
51 i = 1
52 while True:
52 while True:
53 v = req.headers.get(b'%s-%d' % (headerprefix, i))
53 v = req.headers.get(b'%s-%d' % (headerprefix, i))
54 if v is None:
54 if v is None:
55 break
55 break
56 chunks.append(pycompat.bytesurl(v))
56 chunks.append(pycompat.bytesurl(v))
57 i += 1
57 i += 1
58
58
59 return ''.join(chunks)
59 return ''.join(chunks)
60
60
61 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
61 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
62 def __init__(self, req, ui, checkperm):
62 def __init__(self, req, ui, checkperm):
63 self._req = req
63 self._req = req
64 self._ui = ui
64 self._ui = ui
65 self._checkperm = checkperm
65 self._checkperm = checkperm
66
66
67 @property
67 @property
68 def name(self):
68 def name(self):
69 return 'http-v1'
69 return 'http-v1'
70
70
71 def getargs(self, args):
71 def getargs(self, args):
72 knownargs = self._args()
72 knownargs = self._args()
73 data = {}
73 data = {}
74 keys = args.split()
74 keys = args.split()
75 for k in keys:
75 for k in keys:
76 if k == '*':
76 if k == '*':
77 star = {}
77 star = {}
78 for key in knownargs.keys():
78 for key in knownargs.keys():
79 if key != 'cmd' and key not in keys:
79 if key != 'cmd' and key not in keys:
80 star[key] = knownargs[key][0]
80 star[key] = knownargs[key][0]
81 data['*'] = star
81 data['*'] = star
82 else:
82 else:
83 data[k] = knownargs[k][0]
83 data[k] = knownargs[k][0]
84 return [data[k] for k in keys]
84 return [data[k] for k in keys]
85
85
86 def _args(self):
86 def _args(self):
87 args = self._req.qsparams.asdictoflists()
87 args = self._req.qsparams.asdictoflists()
88 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
88 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
89 if postlen:
89 if postlen:
90 args.update(urlreq.parseqs(
90 args.update(urlreq.parseqs(
91 self._req.bodyfh.read(postlen), keep_blank_values=True))
91 self._req.bodyfh.read(postlen), keep_blank_values=True))
92 return args
92 return args
93
93
94 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
94 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
95 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
95 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
96 return args
96 return args
97
97
98 def forwardpayload(self, fp):
98 def forwardpayload(self, fp):
99 # Existing clients *always* send Content-Length.
99 # Existing clients *always* send Content-Length.
100 length = int(self._req.headers[b'Content-Length'])
100 length = int(self._req.headers[b'Content-Length'])
101
101
102 # If httppostargs is used, we need to read Content-Length
102 # If httppostargs is used, we need to read Content-Length
103 # minus the amount that was consumed by args.
103 # minus the amount that was consumed by args.
104 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
104 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
105 for s in util.filechunkiter(self._req.bodyfh, limit=length):
105 for s in util.filechunkiter(self._req.bodyfh, limit=length):
106 fp.write(s)
106 fp.write(s)
107
107
108 @contextlib.contextmanager
108 @contextlib.contextmanager
109 def mayberedirectstdio(self):
109 def mayberedirectstdio(self):
110 oldout = self._ui.fout
110 oldout = self._ui.fout
111 olderr = self._ui.ferr
111 olderr = self._ui.ferr
112
112
113 out = util.stringio()
113 out = util.stringio()
114
114
115 try:
115 try:
116 self._ui.fout = out
116 self._ui.fout = out
117 self._ui.ferr = out
117 self._ui.ferr = out
118 yield out
118 yield out
119 finally:
119 finally:
120 self._ui.fout = oldout
120 self._ui.fout = oldout
121 self._ui.ferr = olderr
121 self._ui.ferr = olderr
122
122
123 def client(self):
123 def client(self):
124 return 'remote:%s:%s:%s' % (
124 return 'remote:%s:%s:%s' % (
125 self._req.urlscheme,
125 self._req.urlscheme,
126 urlreq.quote(self._req.remotehost or ''),
126 urlreq.quote(self._req.remotehost or ''),
127 urlreq.quote(self._req.remoteuser or ''))
127 urlreq.quote(self._req.remoteuser or ''))
128
128
129 def addcapabilities(self, repo, caps):
129 def addcapabilities(self, repo, caps):
130 caps.append(b'batch')
130 caps.append(b'batch')
131
131
132 caps.append('httpheader=%d' %
132 caps.append('httpheader=%d' %
133 repo.ui.configint('server', 'maxhttpheaderlen'))
133 repo.ui.configint('server', 'maxhttpheaderlen'))
134 if repo.ui.configbool('experimental', 'httppostargs'):
134 if repo.ui.configbool('experimental', 'httppostargs'):
135 caps.append('httppostargs')
135 caps.append('httppostargs')
136
136
137 # FUTURE advertise 0.2rx once support is implemented
137 # FUTURE advertise 0.2rx once support is implemented
138 # FUTURE advertise minrx and mintx after consulting config option
138 # FUTURE advertise minrx and mintx after consulting config option
139 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
139 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
140
140
141 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
141 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
142 if compengines:
142 if compengines:
143 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
143 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
144 for e in compengines)
144 for e in compengines)
145 caps.append('compression=%s' % comptypes)
145 caps.append('compression=%s' % comptypes)
146
146
147 return caps
147 return caps
148
148
149 def checkperm(self, perm):
149 def checkperm(self, perm):
150 return self._checkperm(perm)
150 return self._checkperm(perm)
151
151
152 # This method exists mostly so that extensions like remotefilelog can
152 # This method exists mostly so that extensions like remotefilelog can
153 # disable a kludgey legacy method only over http. As of early 2018,
153 # disable a kludgey legacy method only over http. As of early 2018,
154 # there are no other known users, so with any luck we can discard this
154 # there are no other known users, so with any luck we can discard this
155 # hook if remotefilelog becomes a first-party extension.
155 # hook if remotefilelog becomes a first-party extension.
156 def iscmd(cmd):
156 def iscmd(cmd):
157 return cmd in wireproto.commands
157 return cmd in wireproto.commands
158
158
159 def handlewsgirequest(rctx, req, res, checkperm):
159 def handlewsgirequest(rctx, req, res, checkperm):
160 """Possibly process a wire protocol request.
160 """Possibly process a wire protocol request.
161
161
162 If the current request is a wire protocol request, the request is
162 If the current request is a wire protocol request, the request is
163 processed by this function.
163 processed by this function.
164
164
165 ``req`` is a ``parsedrequest`` instance.
165 ``req`` is a ``parsedrequest`` instance.
166 ``res`` is a ``wsgiresponse`` instance.
166 ``res`` is a ``wsgiresponse`` instance.
167
167
168 Returns a bool indicating if the request was serviced. If set, the caller
168 Returns a bool indicating if the request was serviced. If set, the caller
169 should stop processing the request, as a response has already been issued.
169 should stop processing the request, as a response has already been issued.
170 """
170 """
171 # Avoid cycle involving hg module.
171 # Avoid cycle involving hg module.
172 from .hgweb import common as hgwebcommon
172 from .hgweb import common as hgwebcommon
173
173
174 repo = rctx.repo
174 repo = rctx.repo
175
175
176 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
176 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
177 # string parameter. If it isn't present, this isn't a wire protocol
177 # string parameter. If it isn't present, this isn't a wire protocol
178 # request.
178 # request.
179 if 'cmd' not in req.qsparams:
179 if 'cmd' not in req.qsparams:
180 return False
180 return False
181
181
182 cmd = req.qsparams['cmd']
182 cmd = req.qsparams['cmd']
183
183
184 # The "cmd" request parameter is used by both the wire protocol and hgweb.
184 # The "cmd" request parameter is used by both the wire protocol and hgweb.
185 # While not all wire protocol commands are available for all transports,
185 # While not all wire protocol commands are available for all transports,
186 # if we see a "cmd" value that resembles a known wire protocol command, we
186 # if we see a "cmd" value that resembles a known wire protocol command, we
187 # route it to a protocol handler. This is better than routing possible
187 # route it to a protocol handler. This is better than routing possible
188 # wire protocol requests to hgweb because it prevents hgweb from using
188 # wire protocol requests to hgweb because it prevents hgweb from using
189 # known wire protocol commands and it is less confusing for machine
189 # known wire protocol commands and it is less confusing for machine
190 # clients.
190 # clients.
191 if not iscmd(cmd):
191 if not iscmd(cmd):
192 return False
192 return False
193
193
194 # The "cmd" query string argument is only valid on the root path of the
194 # The "cmd" query string argument is only valid on the root path of the
195 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
195 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
196 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
196 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
197 # in this case. We send an HTTP 404 for backwards compatibility reasons.
197 # in this case. We send an HTTP 404 for backwards compatibility reasons.
198 if req.dispatchpath:
198 if req.dispatchpath:
199 res.status = hgwebcommon.statusmessage(404)
199 res.status = hgwebcommon.statusmessage(404)
200 res.headers['Content-Type'] = HGTYPE
200 res.headers['Content-Type'] = HGTYPE
201 # TODO This is not a good response to issue for this request. This
201 # TODO This is not a good response to issue for this request. This
202 # is mostly for BC for now.
202 # is mostly for BC for now.
203 res.setbodybytes('0\n%s\n' % b'Not Found')
203 res.setbodybytes('0\n%s\n' % b'Not Found')
204 return True
204 return True
205
205
206 proto = httpv1protocolhandler(req, repo.ui,
206 proto = httpv1protocolhandler(req, repo.ui,
207 lambda perm: checkperm(rctx, req, perm))
207 lambda perm: checkperm(rctx, req, perm))
208
208
209 # The permissions checker should be the only thing that can raise an
209 # The permissions checker should be the only thing that can raise an
210 # ErrorResponse. It is kind of a layer violation to catch an hgweb
210 # ErrorResponse. It is kind of a layer violation to catch an hgweb
211 # exception here. So consider refactoring into a exception type that
211 # exception here. So consider refactoring into a exception type that
212 # is associated with the wire protocol.
212 # is associated with the wire protocol.
213 try:
213 try:
214 _callhttp(repo, req, res, proto, cmd)
214 _callhttp(repo, req, res, proto, cmd)
215 except hgwebcommon.ErrorResponse as e:
215 except hgwebcommon.ErrorResponse as e:
216 for k, v in e.headers:
216 for k, v in e.headers:
217 res.headers[k] = v
217 res.headers[k] = v
218 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
218 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
219 # TODO This response body assumes the failed command was
219 # TODO This response body assumes the failed command was
220 # "unbundle." That assumption is not always valid.
220 # "unbundle." That assumption is not always valid.
221 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
221 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
222
222
223 return True
223 return True
224
224
225 def handlewsgiapirequest(rctx, req, res, checkperm):
225 def handlewsgiapirequest(rctx, req, res, checkperm):
226 """Handle requests to /api/*."""
226 """Handle requests to /api/*."""
227 assert req.dispatchparts[0] == b'api'
227 assert req.dispatchparts[0] == b'api'
228
228
229 repo = rctx.repo
229 repo = rctx.repo
230
230
231 # This whole URL space is experimental for now. But we want to
231 # This whole URL space is experimental for now. But we want to
232 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
232 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
233 if not repo.ui.configbool('experimental', 'web.apiserver'):
233 if not repo.ui.configbool('experimental', 'web.apiserver'):
234 res.status = b'404 Not Found'
234 res.status = b'404 Not Found'
235 res.headers[b'Content-Type'] = b'text/plain'
235 res.headers[b'Content-Type'] = b'text/plain'
236 res.setbodybytes(_('Experimental API server endpoint not enabled'))
236 res.setbodybytes(_('Experimental API server endpoint not enabled'))
237 return
237 return
238
238
239 # The URL space is /api/<protocol>/*. The structure of URLs under varies
239 # The URL space is /api/<protocol>/*. The structure of URLs under varies
240 # by <protocol>.
240 # by <protocol>.
241
241
242 # Registered APIs are made available via config options of the name of
242 # Registered APIs are made available via config options of the name of
243 # the protocol.
243 # the protocol.
244 availableapis = set()
244 availableapis = set()
245 for k, v in API_HANDLERS.items():
245 for k, v in API_HANDLERS.items():
246 section, option = v['config']
246 section, option = v['config']
247 if repo.ui.configbool(section, option):
247 if repo.ui.configbool(section, option):
248 availableapis.add(k)
248 availableapis.add(k)
249
249
250 # Requests to /api/ list available APIs.
250 # Requests to /api/ list available APIs.
251 if req.dispatchparts == [b'api']:
251 if req.dispatchparts == [b'api']:
252 res.status = b'200 OK'
252 res.status = b'200 OK'
253 res.headers[b'Content-Type'] = b'text/plain'
253 res.headers[b'Content-Type'] = b'text/plain'
254 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
254 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
255 'one of the following:\n')]
255 'one of the following:\n')]
256 if availableapis:
256 if availableapis:
257 lines.extend(sorted(availableapis))
257 lines.extend(sorted(availableapis))
258 else:
258 else:
259 lines.append(_('(no available APIs)\n'))
259 lines.append(_('(no available APIs)\n'))
260 res.setbodybytes(b'\n'.join(lines))
260 res.setbodybytes(b'\n'.join(lines))
261 return
261 return
262
262
263 proto = req.dispatchparts[1]
263 proto = req.dispatchparts[1]
264
264
265 if proto not in API_HANDLERS:
265 if proto not in API_HANDLERS:
266 res.status = b'404 Not Found'
266 res.status = b'404 Not Found'
267 res.headers[b'Content-Type'] = b'text/plain'
267 res.headers[b'Content-Type'] = b'text/plain'
268 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
268 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
269 proto, b', '.join(sorted(availableapis))))
269 proto, b', '.join(sorted(availableapis))))
270 return
270 return
271
271
272 if proto not in availableapis:
272 if proto not in availableapis:
273 res.status = b'404 Not Found'
273 res.status = b'404 Not Found'
274 res.headers[b'Content-Type'] = b'text/plain'
274 res.headers[b'Content-Type'] = b'text/plain'
275 res.setbodybytes(_('API %s not enabled\n') % proto)
275 res.setbodybytes(_('API %s not enabled\n') % proto)
276 return
276 return
277
277
278 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
278 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
279 req.dispatchparts[2:])
279 req.dispatchparts[2:])
280
280
281 def _handlehttpv2request(rctx, req, res, checkperm, urlparts):
281 def _handlehttpv2request(rctx, req, res, checkperm, urlparts):
282 from .hgweb import common as hgwebcommon
282 from .hgweb import common as hgwebcommon
283
283
284 # URL space looks like: <permissions>/<command>, where <permission> can
284 # URL space looks like: <permissions>/<command>, where <permission> can
285 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
285 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
286
286
287 # Root URL does nothing meaningful... yet.
287 # Root URL does nothing meaningful... yet.
288 if not urlparts:
288 if not urlparts:
289 res.status = b'200 OK'
289 res.status = b'200 OK'
290 res.headers[b'Content-Type'] = b'text/plain'
290 res.headers[b'Content-Type'] = b'text/plain'
291 res.setbodybytes(_('HTTP version 2 API handler'))
291 res.setbodybytes(_('HTTP version 2 API handler'))
292 return
292 return
293
293
294 if len(urlparts) == 1:
294 if len(urlparts) == 1:
295 res.status = b'404 Not Found'
295 res.status = b'404 Not Found'
296 res.headers[b'Content-Type'] = b'text/plain'
296 res.headers[b'Content-Type'] = b'text/plain'
297 res.setbodybytes(_('do not know how to process %s\n') %
297 res.setbodybytes(_('do not know how to process %s\n') %
298 req.dispatchpath)
298 req.dispatchpath)
299 return
299 return
300
300
301 permission, command = urlparts[0:2]
301 permission, command = urlparts[0:2]
302
302
303 if permission not in (b'ro', b'rw'):
303 if permission not in (b'ro', b'rw'):
304 res.status = b'404 Not Found'
304 res.status = b'404 Not Found'
305 res.headers[b'Content-Type'] = b'text/plain'
305 res.headers[b'Content-Type'] = b'text/plain'
306 res.setbodybytes(_('unknown permission: %s') % permission)
306 res.setbodybytes(_('unknown permission: %s') % permission)
307 return
307 return
308
308
309 if req.method != 'POST':
309 if req.method != 'POST':
310 res.status = b'405 Method Not Allowed'
310 res.status = b'405 Method Not Allowed'
311 res.headers[b'Allow'] = b'POST'
311 res.headers[b'Allow'] = b'POST'
312 res.setbodybytes(_('commands require POST requests'))
312 res.setbodybytes(_('commands require POST requests'))
313 return
313 return
314
314
315 # At some point we'll want to use our own API instead of recycling the
315 # At some point we'll want to use our own API instead of recycling the
316 # behavior of version 1 of the wire protocol...
316 # behavior of version 1 of the wire protocol...
317 # TODO return reasonable responses - not responses that overload the
317 # TODO return reasonable responses - not responses that overload the
318 # HTTP status line message for error reporting.
318 # HTTP status line message for error reporting.
319 try:
319 try:
320 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
320 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
321 except hgwebcommon.ErrorResponse as e:
321 except hgwebcommon.ErrorResponse as e:
322 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
322 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
323 for k, v in e.headers:
323 for k, v in e.headers:
324 res.headers[k] = v
324 res.headers[k] = v
325 res.setbodybytes('permission denied')
325 res.setbodybytes('permission denied')
326 return
326 return
327
327
328 # We have a special endpoint to reflect the request back at the client.
328 # We have a special endpoint to reflect the request back at the client.
329 if command == b'debugreflect':
329 if command == b'debugreflect':
330 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
330 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
331 return
331 return
332
332
333 # Extra commands that we handle that aren't really wire protocol
333 # Extra commands that we handle that aren't really wire protocol
334 # commands. Think extra hard before making this hackery available to
334 # commands. Think extra hard before making this hackery available to
335 # extension.
335 # extension.
336 extracommands = {'multirequest'}
336 extracommands = {'multirequest'}
337
337
338 if command not in wireproto.commands and command not in extracommands:
338 if command not in wireproto.commands and command not in extracommands:
339 res.status = b'404 Not Found'
339 res.status = b'404 Not Found'
340 res.headers[b'Content-Type'] = b'text/plain'
340 res.headers[b'Content-Type'] = b'text/plain'
341 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
341 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
342 return
342 return
343
343
344 repo = rctx.repo
344 repo = rctx.repo
345 ui = repo.ui
345 ui = repo.ui
346
346
347 proto = httpv2protocolhandler(req, ui)
347 proto = httpv2protocolhandler(req, ui)
348
348
349 if (not wireproto.commands.commandavailable(command, proto)
349 if (not wireproto.commands.commandavailable(command, proto)
350 and command not in extracommands):
350 and command not in extracommands):
351 res.status = b'404 Not Found'
351 res.status = b'404 Not Found'
352 res.headers[b'Content-Type'] = b'text/plain'
352 res.headers[b'Content-Type'] = b'text/plain'
353 res.setbodybytes(_('invalid wire protocol command: %s') % command)
353 res.setbodybytes(_('invalid wire protocol command: %s') % command)
354 return
354 return
355
355
356 # TODO consider cases where proxies may add additional Accept headers.
356 # TODO consider cases where proxies may add additional Accept headers.
357 if req.headers.get(b'Accept') != FRAMINGTYPE:
357 if req.headers.get(b'Accept') != FRAMINGTYPE:
358 res.status = b'406 Not Acceptable'
358 res.status = b'406 Not Acceptable'
359 res.headers[b'Content-Type'] = b'text/plain'
359 res.headers[b'Content-Type'] = b'text/plain'
360 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
360 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
361 % FRAMINGTYPE)
361 % FRAMINGTYPE)
362 return
362 return
363
363
364 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
364 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
365 res.status = b'415 Unsupported Media Type'
365 res.status = b'415 Unsupported Media Type'
366 # TODO we should send a response with appropriate media type,
366 # TODO we should send a response with appropriate media type,
367 # since client does Accept it.
367 # since client does Accept it.
368 res.headers[b'Content-Type'] = b'text/plain'
368 res.headers[b'Content-Type'] = b'text/plain'
369 res.setbodybytes(_('client MUST send Content-Type header with '
369 res.setbodybytes(_('client MUST send Content-Type header with '
370 'value: %s\n') % FRAMINGTYPE)
370 'value: %s\n') % FRAMINGTYPE)
371 return
371 return
372
372
373 _processhttpv2request(ui, repo, req, res, permission, command, proto)
373 _processhttpv2request(ui, repo, req, res, permission, command, proto)
374
374
375 def _processhttpv2reflectrequest(ui, repo, req, res):
375 def _processhttpv2reflectrequest(ui, repo, req, res):
376 """Reads unified frame protocol request and dumps out state to client.
376 """Reads unified frame protocol request and dumps out state to client.
377
377
378 This special endpoint can be used to help debug the wire protocol.
378 This special endpoint can be used to help debug the wire protocol.
379
379
380 Instead of routing the request through the normal dispatch mechanism,
380 Instead of routing the request through the normal dispatch mechanism,
381 we instead read all frames, decode them, and feed them into our state
381 we instead read all frames, decode them, and feed them into our state
382 tracker. We then dump the log of all that activity back out to the
382 tracker. We then dump the log of all that activity back out to the
383 client.
383 client.
384 """
384 """
385 import json
385 import json
386
386
387 # Reflection APIs have a history of being abused, accidentally disclosing
387 # Reflection APIs have a history of being abused, accidentally disclosing
388 # sensitive data, etc. So we have a config knob.
388 # sensitive data, etc. So we have a config knob.
389 if not ui.configbool('experimental', 'web.api.debugreflect'):
389 if not ui.configbool('experimental', 'web.api.debugreflect'):
390 res.status = b'404 Not Found'
390 res.status = b'404 Not Found'
391 res.headers[b'Content-Type'] = b'text/plain'
391 res.headers[b'Content-Type'] = b'text/plain'
392 res.setbodybytes(_('debugreflect service not available'))
392 res.setbodybytes(_('debugreflect service not available'))
393 return
393 return
394
394
395 # We assume we have a unified framing protocol request body.
395 # We assume we have a unified framing protocol request body.
396
396
397 reactor = wireprotoframing.serverreactor()
397 reactor = wireprotoframing.serverreactor()
398 states = []
398 states = []
399
399
400 while True:
400 while True:
401 frame = wireprotoframing.readframe(req.bodyfh)
401 frame = wireprotoframing.readframe(req.bodyfh)
402
402
403 if not frame:
403 if not frame:
404 states.append(b'received: <no frame>')
404 states.append(b'received: <no frame>')
405 break
405 break
406
406
407 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
407 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
408 frame.requestid,
408 frame.requestid,
409 frame.payload))
409 frame.payload))
410
410
411 action, meta = reactor.onframerecv(frame)
411 action, meta = reactor.onframerecv(frame)
412 states.append(json.dumps((action, meta), sort_keys=True,
412 states.append(json.dumps((action, meta), sort_keys=True,
413 separators=(', ', ': ')))
413 separators=(', ', ': ')))
414
414
415 action, meta = reactor.oninputeof()
415 action, meta = reactor.oninputeof()
416 meta['action'] = action
416 meta['action'] = action
417 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
417 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
418
418
419 res.status = b'200 OK'
419 res.status = b'200 OK'
420 res.headers[b'Content-Type'] = b'text/plain'
420 res.headers[b'Content-Type'] = b'text/plain'
421 res.setbodybytes(b'\n'.join(states))
421 res.setbodybytes(b'\n'.join(states))
422
422
423 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
423 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
424 """Post-validation handler for HTTPv2 requests.
424 """Post-validation handler for HTTPv2 requests.
425
425
426 Called when the HTTP request contains unified frame-based protocol
426 Called when the HTTP request contains unified frame-based protocol
427 frames for evaluation.
427 frames for evaluation.
428 """
428 """
429 # TODO Some HTTP clients are full duplex and can receive data before
429 # TODO Some HTTP clients are full duplex and can receive data before
430 # the entire request is transmitted. Figure out a way to indicate support
430 # the entire request is transmitted. Figure out a way to indicate support
431 # for that so we can opt into full duplex mode.
431 # for that so we can opt into full duplex mode.
432 reactor = wireprotoframing.serverreactor(deferoutput=True)
432 reactor = wireprotoframing.serverreactor(deferoutput=True)
433 seencommand = False
433 seencommand = False
434
434
435 while True:
435 while True:
436 frame = wireprotoframing.readframe(req.bodyfh)
436 frame = wireprotoframing.readframe(req.bodyfh)
437 if not frame:
437 if not frame:
438 break
438 break
439
439
440 action, meta = reactor.onframerecv(frame)
440 action, meta = reactor.onframerecv(frame)
441
441
442 if action == 'wantframe':
442 if action == 'wantframe':
443 # Need more data before we can do anything.
443 # Need more data before we can do anything.
444 continue
444 continue
445 elif action == 'runcommand':
445 elif action == 'runcommand':
446 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
446 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
447 reqcommand, reactor, meta,
447 reqcommand, reactor, meta,
448 issubsequent=seencommand)
448 issubsequent=seencommand)
449
449
450 if sentoutput:
450 if sentoutput:
451 return
451 return
452
452
453 seencommand = True
453 seencommand = True
454
454
455 elif action == 'error':
455 elif action == 'error':
456 # TODO define proper error mechanism.
456 # TODO define proper error mechanism.
457 res.status = b'200 OK'
457 res.status = b'200 OK'
458 res.headers[b'Content-Type'] = b'text/plain'
458 res.headers[b'Content-Type'] = b'text/plain'
459 res.setbodybytes(meta['message'] + b'\n')
459 res.setbodybytes(meta['message'] + b'\n')
460 return
460 return
461 else:
461 else:
462 raise error.ProgrammingError(
462 raise error.ProgrammingError(
463 'unhandled action from frame processor: %s' % action)
463 'unhandled action from frame processor: %s' % action)
464
464
465 action, meta = reactor.oninputeof()
465 action, meta = reactor.oninputeof()
466 if action == 'sendframes':
466 if action == 'sendframes':
467 # We assume we haven't started sending the response yet. If we're
467 # We assume we haven't started sending the response yet. If we're
468 # wrong, the response type will raise an exception.
468 # wrong, the response type will raise an exception.
469 res.status = b'200 OK'
469 res.status = b'200 OK'
470 res.headers[b'Content-Type'] = FRAMINGTYPE
470 res.headers[b'Content-Type'] = FRAMINGTYPE
471 res.setbodygen(meta['framegen'])
471 res.setbodygen(meta['framegen'])
472 elif action == 'noop':
472 elif action == 'noop':
473 pass
473 pass
474 else:
474 else:
475 raise error.ProgrammingError('unhandled action from frame processor: %s'
475 raise error.ProgrammingError('unhandled action from frame processor: %s'
476 % action)
476 % action)
477
477
478 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
478 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
479 command, issubsequent):
479 command, issubsequent):
480 """Dispatch a wire protocol command made from HTTPv2 requests.
480 """Dispatch a wire protocol command made from HTTPv2 requests.
481
481
482 The authenticated permission (``authedperm``) along with the original
482 The authenticated permission (``authedperm``) along with the original
483 command from the URL (``reqcommand``) are passed in.
483 command from the URL (``reqcommand``) are passed in.
484 """
484 """
485 # We already validated that the session has permissions to perform the
485 # We already validated that the session has permissions to perform the
486 # actions in ``authedperm``. In the unified frame protocol, the canonical
486 # actions in ``authedperm``. In the unified frame protocol, the canonical
487 # command to run is expressed in a frame. However, the URL also requested
487 # command to run is expressed in a frame. However, the URL also requested
488 # to run a specific command. We need to be careful that the command we
488 # to run a specific command. We need to be careful that the command we
489 # run doesn't have permissions requirements greater than what was granted
489 # run doesn't have permissions requirements greater than what was granted
490 # by ``authedperm``.
490 # by ``authedperm``.
491 #
491 #
492 # Our rule for this is we only allow one command per HTTP request and
492 # Our rule for this is we only allow one command per HTTP request and
493 # that command must match the command in the URL. However, we make
493 # that command must match the command in the URL. However, we make
494 # an exception for the ``multirequest`` URL. This URL is allowed to
494 # an exception for the ``multirequest`` URL. This URL is allowed to
495 # execute multiple commands. We double check permissions of each command
495 # execute multiple commands. We double check permissions of each command
496 # as it is invoked to ensure there is no privilege escalation.
496 # as it is invoked to ensure there is no privilege escalation.
497 # TODO consider allowing multiple commands to regular command URLs
497 # TODO consider allowing multiple commands to regular command URLs
498 # iff each command is the same.
498 # iff each command is the same.
499
499
500 proto = httpv2protocolhandler(req, ui, args=command['args'])
500 proto = httpv2protocolhandler(req, ui, args=command['args'])
501
501
502 if reqcommand == b'multirequest':
502 if reqcommand == b'multirequest':
503 if not wireproto.commands.commandavailable(command['command'], proto):
503 if not wireproto.commands.commandavailable(command['command'], proto):
504 # TODO proper error mechanism
504 # TODO proper error mechanism
505 res.status = b'200 OK'
505 res.status = b'200 OK'
506 res.headers[b'Content-Type'] = b'text/plain'
506 res.headers[b'Content-Type'] = b'text/plain'
507 res.setbodybytes(_('wire protocol command not available: %s') %
507 res.setbodybytes(_('wire protocol command not available: %s') %
508 command['command'])
508 command['command'])
509 return True
509 return True
510
510
511 # TODO don't use assert here, since it may be elided by -O.
511 # TODO don't use assert here, since it may be elided by -O.
512 assert authedperm in (b'ro', b'rw')
512 assert authedperm in (b'ro', b'rw')
513 wirecommand = wireproto.commands[command['command']]
513 wirecommand = wireproto.commands[command['command']]
514 assert wirecommand.permission in ('push', 'pull')
514 assert wirecommand.permission in ('push', 'pull')
515
515
516 if authedperm == b'ro' and wirecommand.permission != 'pull':
516 if authedperm == b'ro' and wirecommand.permission != 'pull':
517 # TODO proper error mechanism
517 # TODO proper error mechanism
518 res.status = b'403 Forbidden'
518 res.status = b'403 Forbidden'
519 res.headers[b'Content-Type'] = b'text/plain'
519 res.headers[b'Content-Type'] = b'text/plain'
520 res.setbodybytes(_('insufficient permissions to execute '
520 res.setbodybytes(_('insufficient permissions to execute '
521 'command: %s') % command['command'])
521 'command: %s') % command['command'])
522 return True
522 return True
523
523
524 # TODO should we also call checkperm() here? Maybe not if we're going
524 # TODO should we also call checkperm() here? Maybe not if we're going
525 # to overhaul that API. The granted scope from the URL check should
525 # to overhaul that API. The granted scope from the URL check should
526 # be good enough.
526 # be good enough.
527
527
528 else:
528 else:
529 # Don't allow multiple commands outside of ``multirequest`` URL.
529 # Don't allow multiple commands outside of ``multirequest`` URL.
530 if issubsequent:
530 if issubsequent:
531 # TODO proper error mechanism
531 # TODO proper error mechanism
532 res.status = b'200 OK'
532 res.status = b'200 OK'
533 res.headers[b'Content-Type'] = b'text/plain'
533 res.headers[b'Content-Type'] = b'text/plain'
534 res.setbodybytes(_('multiple commands cannot be issued to this '
534 res.setbodybytes(_('multiple commands cannot be issued to this '
535 'URL'))
535 'URL'))
536 return True
536 return True
537
537
538 if reqcommand != command['command']:
538 if reqcommand != command['command']:
539 # TODO define proper error mechanism
539 # TODO define proper error mechanism
540 res.status = b'200 OK'
540 res.status = b'200 OK'
541 res.headers[b'Content-Type'] = b'text/plain'
541 res.headers[b'Content-Type'] = b'text/plain'
542 res.setbodybytes(_('command in frame must match command in URL'))
542 res.setbodybytes(_('command in frame must match command in URL'))
543 return True
543 return True
544
544
545 rsp = wireproto.dispatch(repo, proto, command['command'])
545 rsp = wireproto.dispatch(repo, proto, command['command'])
546
546
547 res.status = b'200 OK'
547 res.status = b'200 OK'
548 res.headers[b'Content-Type'] = FRAMINGTYPE
548 res.headers[b'Content-Type'] = FRAMINGTYPE
549 stream = wireprotoframing.stream()
549 stream = wireprotoframing.stream(2)
550
550
551 if isinstance(rsp, wireprototypes.bytesresponse):
551 if isinstance(rsp, wireprototypes.bytesresponse):
552 action, meta = reactor.onbytesresponseready(stream,
552 action, meta = reactor.onbytesresponseready(stream,
553 command['requestid'],
553 command['requestid'],
554 rsp.data)
554 rsp.data)
555 else:
555 else:
556 action, meta = reactor.onapplicationerror(
556 action, meta = reactor.onapplicationerror(
557 _('unhandled response type from wire proto command'))
557 _('unhandled response type from wire proto command'))
558
558
559 if action == 'sendframes':
559 if action == 'sendframes':
560 res.setbodygen(meta['framegen'])
560 res.setbodygen(meta['framegen'])
561 return True
561 return True
562 elif action == 'noop':
562 elif action == 'noop':
563 return False
563 return False
564 else:
564 else:
565 raise error.ProgrammingError('unhandled event from reactor: %s' %
565 raise error.ProgrammingError('unhandled event from reactor: %s' %
566 action)
566 action)
567
567
568 # Maps API name to metadata so custom API can be registered.
568 # Maps API name to metadata so custom API can be registered.
569 API_HANDLERS = {
569 API_HANDLERS = {
570 HTTPV2: {
570 HTTPV2: {
571 'config': ('experimental', 'web.api.http-v2'),
571 'config': ('experimental', 'web.api.http-v2'),
572 'handler': _handlehttpv2request,
572 'handler': _handlehttpv2request,
573 },
573 },
574 }
574 }
575
575
576 class httpv2protocolhandler(wireprototypes.baseprotocolhandler):
576 class httpv2protocolhandler(wireprototypes.baseprotocolhandler):
577 def __init__(self, req, ui, args=None):
577 def __init__(self, req, ui, args=None):
578 self._req = req
578 self._req = req
579 self._ui = ui
579 self._ui = ui
580 self._args = args
580 self._args = args
581
581
582 @property
582 @property
583 def name(self):
583 def name(self):
584 return HTTPV2
584 return HTTPV2
585
585
586 def getargs(self, args):
586 def getargs(self, args):
587 data = {}
587 data = {}
588 for k in args.split():
588 for k in args.split():
589 if k == '*':
589 if k == '*':
590 raise NotImplementedError('do not support * args')
590 raise NotImplementedError('do not support * args')
591 else:
591 else:
592 data[k] = self._args[k]
592 data[k] = self._args[k]
593
593
594 return [data[k] for k in args.split()]
594 return [data[k] for k in args.split()]
595
595
596 def forwardpayload(self, fp):
596 def forwardpayload(self, fp):
597 raise NotImplementedError
597 raise NotImplementedError
598
598
599 @contextlib.contextmanager
599 @contextlib.contextmanager
600 def mayberedirectstdio(self):
600 def mayberedirectstdio(self):
601 raise NotImplementedError
601 raise NotImplementedError
602
602
603 def client(self):
603 def client(self):
604 raise NotImplementedError
604 raise NotImplementedError
605
605
606 def addcapabilities(self, repo, caps):
606 def addcapabilities(self, repo, caps):
607 return caps
607 return caps
608
608
609 def checkperm(self, perm):
609 def checkperm(self, perm):
610 raise NotImplementedError
610 raise NotImplementedError
611
611
612 def _httpresponsetype(ui, req, prefer_uncompressed):
612 def _httpresponsetype(ui, req, prefer_uncompressed):
613 """Determine the appropriate response type and compression settings.
613 """Determine the appropriate response type and compression settings.
614
614
615 Returns a tuple of (mediatype, compengine, engineopts).
615 Returns a tuple of (mediatype, compengine, engineopts).
616 """
616 """
617 # Determine the response media type and compression engine based
617 # Determine the response media type and compression engine based
618 # on the request parameters.
618 # on the request parameters.
619 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
619 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
620
620
621 if '0.2' in protocaps:
621 if '0.2' in protocaps:
622 # All clients are expected to support uncompressed data.
622 # All clients are expected to support uncompressed data.
623 if prefer_uncompressed:
623 if prefer_uncompressed:
624 return HGTYPE2, util._noopengine(), {}
624 return HGTYPE2, util._noopengine(), {}
625
625
626 # Default as defined by wire protocol spec.
626 # Default as defined by wire protocol spec.
627 compformats = ['zlib', 'none']
627 compformats = ['zlib', 'none']
628 for cap in protocaps:
628 for cap in protocaps:
629 if cap.startswith('comp='):
629 if cap.startswith('comp='):
630 compformats = cap[5:].split(',')
630 compformats = cap[5:].split(',')
631 break
631 break
632
632
633 # Now find an agreed upon compression format.
633 # Now find an agreed upon compression format.
634 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
634 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
635 if engine.wireprotosupport().name in compformats:
635 if engine.wireprotosupport().name in compformats:
636 opts = {}
636 opts = {}
637 level = ui.configint('server', '%slevel' % engine.name())
637 level = ui.configint('server', '%slevel' % engine.name())
638 if level is not None:
638 if level is not None:
639 opts['level'] = level
639 opts['level'] = level
640
640
641 return HGTYPE2, engine, opts
641 return HGTYPE2, engine, opts
642
642
643 # No mutually supported compression format. Fall back to the
643 # No mutually supported compression format. Fall back to the
644 # legacy protocol.
644 # legacy protocol.
645
645
646 # Don't allow untrusted settings because disabling compression or
646 # Don't allow untrusted settings because disabling compression or
647 # setting a very high compression level could lead to flooding
647 # setting a very high compression level could lead to flooding
648 # the server's network or CPU.
648 # the server's network or CPU.
649 opts = {'level': ui.configint('server', 'zliblevel')}
649 opts = {'level': ui.configint('server', 'zliblevel')}
650 return HGTYPE, util.compengines['zlib'], opts
650 return HGTYPE, util.compengines['zlib'], opts
651
651
652 def _callhttp(repo, req, res, proto, cmd):
652 def _callhttp(repo, req, res, proto, cmd):
653 # Avoid cycle involving hg module.
653 # Avoid cycle involving hg module.
654 from .hgweb import common as hgwebcommon
654 from .hgweb import common as hgwebcommon
655
655
656 def genversion2(gen, engine, engineopts):
656 def genversion2(gen, engine, engineopts):
657 # application/mercurial-0.2 always sends a payload header
657 # application/mercurial-0.2 always sends a payload header
658 # identifying the compression engine.
658 # identifying the compression engine.
659 name = engine.wireprotosupport().name
659 name = engine.wireprotosupport().name
660 assert 0 < len(name) < 256
660 assert 0 < len(name) < 256
661 yield struct.pack('B', len(name))
661 yield struct.pack('B', len(name))
662 yield name
662 yield name
663
663
664 for chunk in gen:
664 for chunk in gen:
665 yield chunk
665 yield chunk
666
666
667 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
667 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
668 if code == HTTP_OK:
668 if code == HTTP_OK:
669 res.status = '200 Script output follows'
669 res.status = '200 Script output follows'
670 else:
670 else:
671 res.status = hgwebcommon.statusmessage(code)
671 res.status = hgwebcommon.statusmessage(code)
672
672
673 res.headers['Content-Type'] = contenttype
673 res.headers['Content-Type'] = contenttype
674
674
675 if bodybytes is not None:
675 if bodybytes is not None:
676 res.setbodybytes(bodybytes)
676 res.setbodybytes(bodybytes)
677 if bodygen is not None:
677 if bodygen is not None:
678 res.setbodygen(bodygen)
678 res.setbodygen(bodygen)
679
679
680 if not wireproto.commands.commandavailable(cmd, proto):
680 if not wireproto.commands.commandavailable(cmd, proto):
681 setresponse(HTTP_OK, HGERRTYPE,
681 setresponse(HTTP_OK, HGERRTYPE,
682 _('requested wire protocol command is not available over '
682 _('requested wire protocol command is not available over '
683 'HTTP'))
683 'HTTP'))
684 return
684 return
685
685
686 proto.checkperm(wireproto.commands[cmd].permission)
686 proto.checkperm(wireproto.commands[cmd].permission)
687
687
688 rsp = wireproto.dispatch(repo, proto, cmd)
688 rsp = wireproto.dispatch(repo, proto, cmd)
689
689
690 if isinstance(rsp, bytes):
690 if isinstance(rsp, bytes):
691 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
691 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
692 elif isinstance(rsp, wireprototypes.bytesresponse):
692 elif isinstance(rsp, wireprototypes.bytesresponse):
693 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
693 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
694 elif isinstance(rsp, wireprototypes.streamreslegacy):
694 elif isinstance(rsp, wireprototypes.streamreslegacy):
695 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
695 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
696 elif isinstance(rsp, wireprototypes.streamres):
696 elif isinstance(rsp, wireprototypes.streamres):
697 gen = rsp.gen
697 gen = rsp.gen
698
698
699 # This code for compression should not be streamres specific. It
699 # This code for compression should not be streamres specific. It
700 # is here because we only compress streamres at the moment.
700 # is here because we only compress streamres at the moment.
701 mediatype, engine, engineopts = _httpresponsetype(
701 mediatype, engine, engineopts = _httpresponsetype(
702 repo.ui, req, rsp.prefer_uncompressed)
702 repo.ui, req, rsp.prefer_uncompressed)
703 gen = engine.compressstream(gen, engineopts)
703 gen = engine.compressstream(gen, engineopts)
704
704
705 if mediatype == HGTYPE2:
705 if mediatype == HGTYPE2:
706 gen = genversion2(gen, engine, engineopts)
706 gen = genversion2(gen, engine, engineopts)
707
707
708 setresponse(HTTP_OK, mediatype, bodygen=gen)
708 setresponse(HTTP_OK, mediatype, bodygen=gen)
709 elif isinstance(rsp, wireprototypes.pushres):
709 elif isinstance(rsp, wireprototypes.pushres):
710 rsp = '%d\n%s' % (rsp.res, rsp.output)
710 rsp = '%d\n%s' % (rsp.res, rsp.output)
711 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
711 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
712 elif isinstance(rsp, wireprototypes.pusherr):
712 elif isinstance(rsp, wireprototypes.pusherr):
713 rsp = '0\n%s\n' % rsp.res
713 rsp = '0\n%s\n' % rsp.res
714 res.drain = True
714 res.drain = True
715 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
715 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
716 elif isinstance(rsp, wireprototypes.ooberror):
716 elif isinstance(rsp, wireprototypes.ooberror):
717 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
717 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
718 else:
718 else:
719 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
719 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
720
720
721 def _sshv1respondbytes(fout, value):
721 def _sshv1respondbytes(fout, value):
722 """Send a bytes response for protocol version 1."""
722 """Send a bytes response for protocol version 1."""
723 fout.write('%d\n' % len(value))
723 fout.write('%d\n' % len(value))
724 fout.write(value)
724 fout.write(value)
725 fout.flush()
725 fout.flush()
726
726
727 def _sshv1respondstream(fout, source):
727 def _sshv1respondstream(fout, source):
728 write = fout.write
728 write = fout.write
729 for chunk in source.gen:
729 for chunk in source.gen:
730 write(chunk)
730 write(chunk)
731 fout.flush()
731 fout.flush()
732
732
733 def _sshv1respondooberror(fout, ferr, rsp):
733 def _sshv1respondooberror(fout, ferr, rsp):
734 ferr.write(b'%s\n-\n' % rsp)
734 ferr.write(b'%s\n-\n' % rsp)
735 ferr.flush()
735 ferr.flush()
736 fout.write(b'\n')
736 fout.write(b'\n')
737 fout.flush()
737 fout.flush()
738
738
739 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
739 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
740 """Handler for requests services via version 1 of SSH protocol."""
740 """Handler for requests services via version 1 of SSH protocol."""
741 def __init__(self, ui, fin, fout):
741 def __init__(self, ui, fin, fout):
742 self._ui = ui
742 self._ui = ui
743 self._fin = fin
743 self._fin = fin
744 self._fout = fout
744 self._fout = fout
745
745
746 @property
746 @property
747 def name(self):
747 def name(self):
748 return wireprototypes.SSHV1
748 return wireprototypes.SSHV1
749
749
750 def getargs(self, args):
750 def getargs(self, args):
751 data = {}
751 data = {}
752 keys = args.split()
752 keys = args.split()
753 for n in xrange(len(keys)):
753 for n in xrange(len(keys)):
754 argline = self._fin.readline()[:-1]
754 argline = self._fin.readline()[:-1]
755 arg, l = argline.split()
755 arg, l = argline.split()
756 if arg not in keys:
756 if arg not in keys:
757 raise error.Abort(_("unexpected parameter %r") % arg)
757 raise error.Abort(_("unexpected parameter %r") % arg)
758 if arg == '*':
758 if arg == '*':
759 star = {}
759 star = {}
760 for k in xrange(int(l)):
760 for k in xrange(int(l)):
761 argline = self._fin.readline()[:-1]
761 argline = self._fin.readline()[:-1]
762 arg, l = argline.split()
762 arg, l = argline.split()
763 val = self._fin.read(int(l))
763 val = self._fin.read(int(l))
764 star[arg] = val
764 star[arg] = val
765 data['*'] = star
765 data['*'] = star
766 else:
766 else:
767 val = self._fin.read(int(l))
767 val = self._fin.read(int(l))
768 data[arg] = val
768 data[arg] = val
769 return [data[k] for k in keys]
769 return [data[k] for k in keys]
770
770
771 def forwardpayload(self, fpout):
771 def forwardpayload(self, fpout):
772 # We initially send an empty response. This tells the client it is
772 # We initially send an empty response. This tells the client it is
773 # OK to start sending data. If a client sees any other response, it
773 # OK to start sending data. If a client sees any other response, it
774 # interprets it as an error.
774 # interprets it as an error.
775 _sshv1respondbytes(self._fout, b'')
775 _sshv1respondbytes(self._fout, b'')
776
776
777 # The file is in the form:
777 # The file is in the form:
778 #
778 #
779 # <chunk size>\n<chunk>
779 # <chunk size>\n<chunk>
780 # ...
780 # ...
781 # 0\n
781 # 0\n
782 count = int(self._fin.readline())
782 count = int(self._fin.readline())
783 while count:
783 while count:
784 fpout.write(self._fin.read(count))
784 fpout.write(self._fin.read(count))
785 count = int(self._fin.readline())
785 count = int(self._fin.readline())
786
786
787 @contextlib.contextmanager
787 @contextlib.contextmanager
788 def mayberedirectstdio(self):
788 def mayberedirectstdio(self):
789 yield None
789 yield None
790
790
791 def client(self):
791 def client(self):
792 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
792 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
793 return 'remote:ssh:' + client
793 return 'remote:ssh:' + client
794
794
795 def addcapabilities(self, repo, caps):
795 def addcapabilities(self, repo, caps):
796 caps.append(b'batch')
796 caps.append(b'batch')
797 return caps
797 return caps
798
798
799 def checkperm(self, perm):
799 def checkperm(self, perm):
800 pass
800 pass
801
801
802 class sshv2protocolhandler(sshv1protocolhandler):
802 class sshv2protocolhandler(sshv1protocolhandler):
803 """Protocol handler for version 2 of the SSH protocol."""
803 """Protocol handler for version 2 of the SSH protocol."""
804
804
805 @property
805 @property
806 def name(self):
806 def name(self):
807 return wireprototypes.SSHV2
807 return wireprototypes.SSHV2
808
808
809 def _runsshserver(ui, repo, fin, fout, ev):
809 def _runsshserver(ui, repo, fin, fout, ev):
810 # This function operates like a state machine of sorts. The following
810 # This function operates like a state machine of sorts. The following
811 # states are defined:
811 # states are defined:
812 #
812 #
813 # protov1-serving
813 # protov1-serving
814 # Server is in protocol version 1 serving mode. Commands arrive on
814 # Server is in protocol version 1 serving mode. Commands arrive on
815 # new lines. These commands are processed in this state, one command
815 # new lines. These commands are processed in this state, one command
816 # after the other.
816 # after the other.
817 #
817 #
818 # protov2-serving
818 # protov2-serving
819 # Server is in protocol version 2 serving mode.
819 # Server is in protocol version 2 serving mode.
820 #
820 #
821 # upgrade-initial
821 # upgrade-initial
822 # The server is going to process an upgrade request.
822 # The server is going to process an upgrade request.
823 #
823 #
824 # upgrade-v2-filter-legacy-handshake
824 # upgrade-v2-filter-legacy-handshake
825 # The protocol is being upgraded to version 2. The server is expecting
825 # The protocol is being upgraded to version 2. The server is expecting
826 # the legacy handshake from version 1.
826 # the legacy handshake from version 1.
827 #
827 #
828 # upgrade-v2-finish
828 # upgrade-v2-finish
829 # The upgrade to version 2 of the protocol is imminent.
829 # The upgrade to version 2 of the protocol is imminent.
830 #
830 #
831 # shutdown
831 # shutdown
832 # The server is shutting down, possibly in reaction to a client event.
832 # The server is shutting down, possibly in reaction to a client event.
833 #
833 #
834 # And here are their transitions:
834 # And here are their transitions:
835 #
835 #
836 # protov1-serving -> shutdown
836 # protov1-serving -> shutdown
837 # When server receives an empty request or encounters another
837 # When server receives an empty request or encounters another
838 # error.
838 # error.
839 #
839 #
840 # protov1-serving -> upgrade-initial
840 # protov1-serving -> upgrade-initial
841 # An upgrade request line was seen.
841 # An upgrade request line was seen.
842 #
842 #
843 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
843 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
844 # Upgrade to version 2 in progress. Server is expecting to
844 # Upgrade to version 2 in progress. Server is expecting to
845 # process a legacy handshake.
845 # process a legacy handshake.
846 #
846 #
847 # upgrade-v2-filter-legacy-handshake -> shutdown
847 # upgrade-v2-filter-legacy-handshake -> shutdown
848 # Client did not fulfill upgrade handshake requirements.
848 # Client did not fulfill upgrade handshake requirements.
849 #
849 #
850 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
850 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
851 # Client fulfilled version 2 upgrade requirements. Finishing that
851 # Client fulfilled version 2 upgrade requirements. Finishing that
852 # upgrade.
852 # upgrade.
853 #
853 #
854 # upgrade-v2-finish -> protov2-serving
854 # upgrade-v2-finish -> protov2-serving
855 # Protocol upgrade to version 2 complete. Server can now speak protocol
855 # Protocol upgrade to version 2 complete. Server can now speak protocol
856 # version 2.
856 # version 2.
857 #
857 #
858 # protov2-serving -> protov1-serving
858 # protov2-serving -> protov1-serving
859 # Ths happens by default since protocol version 2 is the same as
859 # Ths happens by default since protocol version 2 is the same as
860 # version 1 except for the handshake.
860 # version 1 except for the handshake.
861
861
862 state = 'protov1-serving'
862 state = 'protov1-serving'
863 proto = sshv1protocolhandler(ui, fin, fout)
863 proto = sshv1protocolhandler(ui, fin, fout)
864 protoswitched = False
864 protoswitched = False
865
865
866 while not ev.is_set():
866 while not ev.is_set():
867 if state == 'protov1-serving':
867 if state == 'protov1-serving':
868 # Commands are issued on new lines.
868 # Commands are issued on new lines.
869 request = fin.readline()[:-1]
869 request = fin.readline()[:-1]
870
870
871 # Empty lines signal to terminate the connection.
871 # Empty lines signal to terminate the connection.
872 if not request:
872 if not request:
873 state = 'shutdown'
873 state = 'shutdown'
874 continue
874 continue
875
875
876 # It looks like a protocol upgrade request. Transition state to
876 # It looks like a protocol upgrade request. Transition state to
877 # handle it.
877 # handle it.
878 if request.startswith(b'upgrade '):
878 if request.startswith(b'upgrade '):
879 if protoswitched:
879 if protoswitched:
880 _sshv1respondooberror(fout, ui.ferr,
880 _sshv1respondooberror(fout, ui.ferr,
881 b'cannot upgrade protocols multiple '
881 b'cannot upgrade protocols multiple '
882 b'times')
882 b'times')
883 state = 'shutdown'
883 state = 'shutdown'
884 continue
884 continue
885
885
886 state = 'upgrade-initial'
886 state = 'upgrade-initial'
887 continue
887 continue
888
888
889 available = wireproto.commands.commandavailable(request, proto)
889 available = wireproto.commands.commandavailable(request, proto)
890
890
891 # This command isn't available. Send an empty response and go
891 # This command isn't available. Send an empty response and go
892 # back to waiting for a new command.
892 # back to waiting for a new command.
893 if not available:
893 if not available:
894 _sshv1respondbytes(fout, b'')
894 _sshv1respondbytes(fout, b'')
895 continue
895 continue
896
896
897 rsp = wireproto.dispatch(repo, proto, request)
897 rsp = wireproto.dispatch(repo, proto, request)
898
898
899 if isinstance(rsp, bytes):
899 if isinstance(rsp, bytes):
900 _sshv1respondbytes(fout, rsp)
900 _sshv1respondbytes(fout, rsp)
901 elif isinstance(rsp, wireprototypes.bytesresponse):
901 elif isinstance(rsp, wireprototypes.bytesresponse):
902 _sshv1respondbytes(fout, rsp.data)
902 _sshv1respondbytes(fout, rsp.data)
903 elif isinstance(rsp, wireprototypes.streamres):
903 elif isinstance(rsp, wireprototypes.streamres):
904 _sshv1respondstream(fout, rsp)
904 _sshv1respondstream(fout, rsp)
905 elif isinstance(rsp, wireprototypes.streamreslegacy):
905 elif isinstance(rsp, wireprototypes.streamreslegacy):
906 _sshv1respondstream(fout, rsp)
906 _sshv1respondstream(fout, rsp)
907 elif isinstance(rsp, wireprototypes.pushres):
907 elif isinstance(rsp, wireprototypes.pushres):
908 _sshv1respondbytes(fout, b'')
908 _sshv1respondbytes(fout, b'')
909 _sshv1respondbytes(fout, b'%d' % rsp.res)
909 _sshv1respondbytes(fout, b'%d' % rsp.res)
910 elif isinstance(rsp, wireprototypes.pusherr):
910 elif isinstance(rsp, wireprototypes.pusherr):
911 _sshv1respondbytes(fout, rsp.res)
911 _sshv1respondbytes(fout, rsp.res)
912 elif isinstance(rsp, wireprototypes.ooberror):
912 elif isinstance(rsp, wireprototypes.ooberror):
913 _sshv1respondooberror(fout, ui.ferr, rsp.message)
913 _sshv1respondooberror(fout, ui.ferr, rsp.message)
914 else:
914 else:
915 raise error.ProgrammingError('unhandled response type from '
915 raise error.ProgrammingError('unhandled response type from '
916 'wire protocol command: %s' % rsp)
916 'wire protocol command: %s' % rsp)
917
917
918 # For now, protocol version 2 serving just goes back to version 1.
918 # For now, protocol version 2 serving just goes back to version 1.
919 elif state == 'protov2-serving':
919 elif state == 'protov2-serving':
920 state = 'protov1-serving'
920 state = 'protov1-serving'
921 continue
921 continue
922
922
923 elif state == 'upgrade-initial':
923 elif state == 'upgrade-initial':
924 # We should never transition into this state if we've switched
924 # We should never transition into this state if we've switched
925 # protocols.
925 # protocols.
926 assert not protoswitched
926 assert not protoswitched
927 assert proto.name == wireprototypes.SSHV1
927 assert proto.name == wireprototypes.SSHV1
928
928
929 # Expected: upgrade <token> <capabilities>
929 # Expected: upgrade <token> <capabilities>
930 # If we get something else, the request is malformed. It could be
930 # If we get something else, the request is malformed. It could be
931 # from a future client that has altered the upgrade line content.
931 # from a future client that has altered the upgrade line content.
932 # We treat this as an unknown command.
932 # We treat this as an unknown command.
933 try:
933 try:
934 token, caps = request.split(b' ')[1:]
934 token, caps = request.split(b' ')[1:]
935 except ValueError:
935 except ValueError:
936 _sshv1respondbytes(fout, b'')
936 _sshv1respondbytes(fout, b'')
937 state = 'protov1-serving'
937 state = 'protov1-serving'
938 continue
938 continue
939
939
940 # Send empty response if we don't support upgrading protocols.
940 # Send empty response if we don't support upgrading protocols.
941 if not ui.configbool('experimental', 'sshserver.support-v2'):
941 if not ui.configbool('experimental', 'sshserver.support-v2'):
942 _sshv1respondbytes(fout, b'')
942 _sshv1respondbytes(fout, b'')
943 state = 'protov1-serving'
943 state = 'protov1-serving'
944 continue
944 continue
945
945
946 try:
946 try:
947 caps = urlreq.parseqs(caps)
947 caps = urlreq.parseqs(caps)
948 except ValueError:
948 except ValueError:
949 _sshv1respondbytes(fout, b'')
949 _sshv1respondbytes(fout, b'')
950 state = 'protov1-serving'
950 state = 'protov1-serving'
951 continue
951 continue
952
952
953 # We don't see an upgrade request to protocol version 2. Ignore
953 # We don't see an upgrade request to protocol version 2. Ignore
954 # the upgrade request.
954 # the upgrade request.
955 wantedprotos = caps.get(b'proto', [b''])[0]
955 wantedprotos = caps.get(b'proto', [b''])[0]
956 if SSHV2 not in wantedprotos:
956 if SSHV2 not in wantedprotos:
957 _sshv1respondbytes(fout, b'')
957 _sshv1respondbytes(fout, b'')
958 state = 'protov1-serving'
958 state = 'protov1-serving'
959 continue
959 continue
960
960
961 # It looks like we can honor this upgrade request to protocol 2.
961 # It looks like we can honor this upgrade request to protocol 2.
962 # Filter the rest of the handshake protocol request lines.
962 # Filter the rest of the handshake protocol request lines.
963 state = 'upgrade-v2-filter-legacy-handshake'
963 state = 'upgrade-v2-filter-legacy-handshake'
964 continue
964 continue
965
965
966 elif state == 'upgrade-v2-filter-legacy-handshake':
966 elif state == 'upgrade-v2-filter-legacy-handshake':
967 # Client should have sent legacy handshake after an ``upgrade``
967 # Client should have sent legacy handshake after an ``upgrade``
968 # request. Expected lines:
968 # request. Expected lines:
969 #
969 #
970 # hello
970 # hello
971 # between
971 # between
972 # pairs 81
972 # pairs 81
973 # 0000...-0000...
973 # 0000...-0000...
974
974
975 ok = True
975 ok = True
976 for line in (b'hello', b'between', b'pairs 81'):
976 for line in (b'hello', b'between', b'pairs 81'):
977 request = fin.readline()[:-1]
977 request = fin.readline()[:-1]
978
978
979 if request != line:
979 if request != line:
980 _sshv1respondooberror(fout, ui.ferr,
980 _sshv1respondooberror(fout, ui.ferr,
981 b'malformed handshake protocol: '
981 b'malformed handshake protocol: '
982 b'missing %s' % line)
982 b'missing %s' % line)
983 ok = False
983 ok = False
984 state = 'shutdown'
984 state = 'shutdown'
985 break
985 break
986
986
987 if not ok:
987 if not ok:
988 continue
988 continue
989
989
990 request = fin.read(81)
990 request = fin.read(81)
991 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
991 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
992 _sshv1respondooberror(fout, ui.ferr,
992 _sshv1respondooberror(fout, ui.ferr,
993 b'malformed handshake protocol: '
993 b'malformed handshake protocol: '
994 b'missing between argument value')
994 b'missing between argument value')
995 state = 'shutdown'
995 state = 'shutdown'
996 continue
996 continue
997
997
998 state = 'upgrade-v2-finish'
998 state = 'upgrade-v2-finish'
999 continue
999 continue
1000
1000
1001 elif state == 'upgrade-v2-finish':
1001 elif state == 'upgrade-v2-finish':
1002 # Send the upgrade response.
1002 # Send the upgrade response.
1003 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
1003 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
1004 servercaps = wireproto.capabilities(repo, proto)
1004 servercaps = wireproto.capabilities(repo, proto)
1005 rsp = b'capabilities: %s' % servercaps.data
1005 rsp = b'capabilities: %s' % servercaps.data
1006 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
1006 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
1007 fout.flush()
1007 fout.flush()
1008
1008
1009 proto = sshv2protocolhandler(ui, fin, fout)
1009 proto = sshv2protocolhandler(ui, fin, fout)
1010 protoswitched = True
1010 protoswitched = True
1011
1011
1012 state = 'protov2-serving'
1012 state = 'protov2-serving'
1013 continue
1013 continue
1014
1014
1015 elif state == 'shutdown':
1015 elif state == 'shutdown':
1016 break
1016 break
1017
1017
1018 else:
1018 else:
1019 raise error.ProgrammingError('unhandled ssh server state: %s' %
1019 raise error.ProgrammingError('unhandled ssh server state: %s' %
1020 state)
1020 state)
1021
1021
1022 class sshserver(object):
1022 class sshserver(object):
1023 def __init__(self, ui, repo, logfh=None):
1023 def __init__(self, ui, repo, logfh=None):
1024 self._ui = ui
1024 self._ui = ui
1025 self._repo = repo
1025 self._repo = repo
1026 self._fin = ui.fin
1026 self._fin = ui.fin
1027 self._fout = ui.fout
1027 self._fout = ui.fout
1028
1028
1029 # Log write I/O to stdout and stderr if configured.
1029 # Log write I/O to stdout and stderr if configured.
1030 if logfh:
1030 if logfh:
1031 self._fout = util.makeloggingfileobject(
1031 self._fout = util.makeloggingfileobject(
1032 logfh, self._fout, 'o', logdata=True)
1032 logfh, self._fout, 'o', logdata=True)
1033 ui.ferr = util.makeloggingfileobject(
1033 ui.ferr = util.makeloggingfileobject(
1034 logfh, ui.ferr, 'e', logdata=True)
1034 logfh, ui.ferr, 'e', logdata=True)
1035
1035
1036 hook.redirect(True)
1036 hook.redirect(True)
1037 ui.fout = repo.ui.fout = ui.ferr
1037 ui.fout = repo.ui.fout = ui.ferr
1038
1038
1039 # Prevent insertion/deletion of CRs
1039 # Prevent insertion/deletion of CRs
1040 procutil.setbinary(self._fin)
1040 procutil.setbinary(self._fin)
1041 procutil.setbinary(self._fout)
1041 procutil.setbinary(self._fout)
1042
1042
1043 def serve_forever(self):
1043 def serve_forever(self):
1044 self.serveuntil(threading.Event())
1044 self.serveuntil(threading.Event())
1045 sys.exit(0)
1045 sys.exit(0)
1046
1046
1047 def serveuntil(self, ev):
1047 def serveuntil(self, ev):
1048 """Serve until a threading.Event is set."""
1048 """Serve until a threading.Event is set."""
1049 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
1049 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
@@ -1,564 +1,564 b''
1 $ HTTPV2=exp-http-v2-0001
1 $ HTTPV2=exp-http-v2-0001
2 $ MEDIATYPE=application/mercurial-exp-framing-0002
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-0002\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-0002\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-0002\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-0002\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 1 command-name eos customreadonly
182 > frame 1 1 stream-begin 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-0002\r\n
187 s> accept: application/mercurial-exp-framing-0002\r\n
188 s> content-type: application/mercurial-exp-framing-0002\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> *\r\n (glob)
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\x01\x00\x11customreadonly
193 s> \x0e\x00\x00\x01\x00\x01\x01\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-0002\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> 23\r\n
201 s> 25\r\n
202 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
202 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly 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 1 command-name eos customreadonly
293 > frame 1 1 stream-begin 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-0002\r\n
298 s> accept: application/mercurial-exp-framing-0002\r\n
299 s> content-type: application/mercurial-exp-framing-0002\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: 20\r\n
301 s> content-length: 22\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\x01\x00\x11customreadonly
304 s> \x0e\x00\x00\x01\x00\x01\x01\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-0002\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> 23\r\n
312 s> 25\r\n
313 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
313 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly 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-0002\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 1 command-name have-args command1
385 > frame 1 1 stream-begin command-name have-args command1
386 > frame 1 command-argument 0 \x03\x00\x04\x00fooval1
386 > frame 1 1 0 command-argument 0 \x03\x00\x04\x00fooval1
387 > frame 1 command-argument eoa \x04\x00\x03\x00bar1val
387 > frame 1 1 0 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-0002\r\n
392 s> accept: application/mercurial-exp-framing-0002\r\n
393 s> content-type: application/mercurial-exp-framing-0002\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: 48\r\n
395 s> content-length: 54\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\x01\x00\x12command1\x0b\x00\x00\x01\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00\x01\x00"\x04\x00\x03\x00bar1val
398 s> \x08\x00\x00\x01\x00\x01\x01\x12command1\x0b\x00\x00\x01\x00\x01\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00\x01\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: 322\r\n
404 s> Content-Length: 322\r\n
405 s> \r\n
405 s> \r\n
406 s> received: 1 2 1 command1\n
406 s> received: 1 2 1 command1\n
407 s> ["wantframe", {"state": "command-receiving"}]\n
407 s> ["wantframe", {"state": "command-receiving"}]\n
408 s> received: 2 0 1 \x03\x00\x04\x00fooval1\n
408 s> received: 2 0 1 \x03\x00\x04\x00fooval1\n
409 s> ["wantframe", {"state": "command-receiving"}]\n
409 s> ["wantframe", {"state": "command-receiving"}]\n
410 s> received: 2 2 1 \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, "requestid": 1}]\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 Multiple requests to regular command URL are not allowed
415 Multiple requests to regular command URL are not allowed
416
416
417 $ send << EOF
417 $ send << EOF
418 > httprequest POST api/$HTTPV2/ro/customreadonly
418 > httprequest POST api/$HTTPV2/ro/customreadonly
419 > accept: $MEDIATYPE
419 > accept: $MEDIATYPE
420 > content-type: $MEDIATYPE
420 > content-type: $MEDIATYPE
421 > user-agent: test
421 > user-agent: test
422 > frame 1 command-name eos customreadonly
422 > frame 1 1 stream-begin command-name eos customreadonly
423 > frame 3 command-name eos customreadonly
423 > frame 3 1 0 command-name eos customreadonly
424 > EOF
424 > EOF
425 using raw connection to peer
425 using raw connection to peer
426 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
426 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
427 s> Accept-Encoding: identity\r\n
427 s> Accept-Encoding: identity\r\n
428 s> accept: application/mercurial-exp-framing-0002\r\n
428 s> accept: application/mercurial-exp-framing-0002\r\n
429 s> content-type: application/mercurial-exp-framing-0002\r\n
429 s> content-type: application/mercurial-exp-framing-0002\r\n
430 s> user-agent: test\r\n
430 s> user-agent: test\r\n
431 s> content-length: 40\r\n
431 s> content-length: 44\r\n
432 s> host: $LOCALIP:$HGPORT\r\n (glob)
432 s> host: $LOCALIP:$HGPORT\r\n (glob)
433 s> \r\n
433 s> \r\n
434 s> \x0e\x00\x00\x01\x00\x11customreadonly\x0e\x00\x00\x03\x00\x11customreadonly
434 s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly
435 s> makefile('rb', None)
435 s> makefile('rb', None)
436 s> HTTP/1.1 200 OK\r\n
436 s> HTTP/1.1 200 OK\r\n
437 s> Server: testing stub value\r\n
437 s> Server: testing stub value\r\n
438 s> Date: $HTTP_DATE$\r\n
438 s> Date: $HTTP_DATE$\r\n
439 s> Content-Type: text/plain\r\n
439 s> Content-Type: text/plain\r\n
440 s> Content-Length: 46\r\n
440 s> Content-Length: 46\r\n
441 s> \r\n
441 s> \r\n
442 s> multiple commands cannot be issued to this URL
442 s> multiple commands cannot be issued to this URL
443
443
444 Multiple requests to "multirequest" URL are allowed
444 Multiple requests to "multirequest" URL are allowed
445
445
446 $ send << EOF
446 $ send << EOF
447 > httprequest POST api/$HTTPV2/ro/multirequest
447 > httprequest POST api/$HTTPV2/ro/multirequest
448 > accept: $MEDIATYPE
448 > accept: $MEDIATYPE
449 > content-type: $MEDIATYPE
449 > content-type: $MEDIATYPE
450 > user-agent: test
450 > user-agent: test
451 > frame 1 command-name eos customreadonly
451 > frame 1 1 stream-begin command-name eos customreadonly
452 > frame 3 command-name eos customreadonly
452 > frame 3 1 0 command-name eos customreadonly
453 > EOF
453 > EOF
454 using raw connection to peer
454 using raw connection to peer
455 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
455 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
456 s> Accept-Encoding: identity\r\n
456 s> Accept-Encoding: identity\r\n
457 s> accept: application/mercurial-exp-framing-0002\r\n
457 s> accept: application/mercurial-exp-framing-0002\r\n
458 s> content-type: application/mercurial-exp-framing-0002\r\n
458 s> content-type: application/mercurial-exp-framing-0002\r\n
459 s> user-agent: test\r\n
459 s> user-agent: test\r\n
460 s> *\r\n (glob)
460 s> *\r\n (glob)
461 s> host: $LOCALIP:$HGPORT\r\n (glob)
461 s> host: $LOCALIP:$HGPORT\r\n (glob)
462 s> \r\n
462 s> \r\n
463 s> \x0e\x00\x00\x01\x00\x11customreadonly\x0e\x00\x00\x03\x00\x11customreadonly
463 s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly
464 s> makefile('rb', None)
464 s> makefile('rb', None)
465 s> HTTP/1.1 200 OK\r\n
465 s> HTTP/1.1 200 OK\r\n
466 s> Server: testing stub value\r\n
466 s> Server: testing stub value\r\n
467 s> Date: $HTTP_DATE$\r\n
467 s> Date: $HTTP_DATE$\r\n
468 s> Content-Type: application/mercurial-exp-framing-0002\r\n
468 s> Content-Type: application/mercurial-exp-framing-0002\r\n
469 s> Transfer-Encoding: chunked\r\n
469 s> Transfer-Encoding: chunked\r\n
470 s> \r\n
470 s> \r\n
471 s> *\r\n (glob)
471 s> *\r\n (glob)
472 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
472 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response
473 s> \r\n
473 s> \r\n
474 s> 23\r\n
474 s> 25\r\n
475 s> \x1d\x00\x00\x03\x00Bcustomreadonly bytes response
475 s> \x1d\x00\x00\x03\x00\x02\x01Bcustomreadonly bytes response
476 s> \r\n
476 s> \r\n
477 s> 0\r\n
477 s> 0\r\n
478 s> \r\n
478 s> \r\n
479
479
480 Interleaved requests to "multirequest" are processed
480 Interleaved requests to "multirequest" are processed
481
481
482 $ send << EOF
482 $ send << EOF
483 > httprequest POST api/$HTTPV2/ro/multirequest
483 > httprequest POST api/$HTTPV2/ro/multirequest
484 > accept: $MEDIATYPE
484 > accept: $MEDIATYPE
485 > content-type: $MEDIATYPE
485 > content-type: $MEDIATYPE
486 > user-agent: test
486 > user-agent: test
487 > frame 1 command-name have-args listkeys
487 > frame 1 1 stream-begin command-name have-args listkeys
488 > frame 3 command-name have-args listkeys
488 > frame 3 1 0 command-name have-args listkeys
489 > frame 3 command-argument eoa \x09\x00\x09\x00namespacebookmarks
489 > frame 3 1 0 command-argument eoa \x09\x00\x09\x00namespacebookmarks
490 > frame 1 command-argument eoa \x09\x00\x0a\x00namespacenamespaces
490 > frame 1 1 0 command-argument eoa \x09\x00\x0a\x00namespacenamespaces
491 > EOF
491 > EOF
492 using raw connection to peer
492 using raw connection to peer
493 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
493 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
494 s> Accept-Encoding: identity\r\n
494 s> Accept-Encoding: identity\r\n
495 s> accept: application/mercurial-exp-framing-0002\r\n
495 s> accept: application/mercurial-exp-framing-0002\r\n
496 s> content-type: application/mercurial-exp-framing-0002\r\n
496 s> content-type: application/mercurial-exp-framing-0002\r\n
497 s> user-agent: test\r\n
497 s> user-agent: test\r\n
498 s> content-length: 85\r\n
498 s> content-length: 93\r\n
499 s> host: $LOCALIP:$HGPORT\r\n (glob)
499 s> host: $LOCALIP:$HGPORT\r\n (glob)
500 s> \r\n
500 s> \r\n
501 s> \x08\x00\x00\x01\x00\x12listkeys\x08\x00\x00\x03\x00\x12listkeys\x16\x00\x00\x03\x00" \x00 \x00namespacebookmarks\x17\x00\x00\x01\x00" \x00\n
501 s> \x08\x00\x00\x01\x00\x01\x01\x12listkeys\x08\x00\x00\x03\x00\x01\x00\x12listkeys\x16\x00\x00\x03\x00\x01\x00" \x00 \x00namespacebookmarks\x17\x00\x00\x01\x00\x01\x00" \x00\n
502 s> \x00namespacenamespaces
502 s> \x00namespacenamespaces
503 s> makefile('rb', None)
503 s> makefile('rb', None)
504 s> HTTP/1.1 200 OK\r\n
504 s> HTTP/1.1 200 OK\r\n
505 s> Server: testing stub value\r\n
505 s> Server: testing stub value\r\n
506 s> Date: $HTTP_DATE$\r\n
506 s> Date: $HTTP_DATE$\r\n
507 s> Content-Type: application/mercurial-exp-framing-0002\r\n
507 s> Content-Type: application/mercurial-exp-framing-0002\r\n
508 s> Transfer-Encoding: chunked\r\n
508 s> Transfer-Encoding: chunked\r\n
509 s> \r\n
509 s> \r\n
510 s> 6\r\n
510 s> 8\r\n
511 s> \x00\x00\x00\x03\x00B
511 s> \x00\x00\x00\x03\x00\x02\x01B
512 s> \r\n
512 s> \r\n
513 s> 24\r\n
513 s> 26\r\n
514 s> \x1e\x00\x00\x01\x00Bbookmarks \n
514 s> \x1e\x00\x00\x01\x00\x02\x01Bbookmarks \n
515 s> namespaces \n
515 s> namespaces \n
516 s> phases
516 s> phases
517 s> \r\n
517 s> \r\n
518 s> 0\r\n
518 s> 0\r\n
519 s> \r\n
519 s> \r\n
520
520
521 Restart server to disable read-write access
521 Restart server to disable read-write access
522
522
523 $ killdaemons.py
523 $ killdaemons.py
524 $ cat > server/.hg/hgrc << EOF
524 $ cat > server/.hg/hgrc << EOF
525 > [experimental]
525 > [experimental]
526 > web.apiserver = true
526 > web.apiserver = true
527 > web.api.debugreflect = true
527 > web.api.debugreflect = true
528 > web.api.http-v2 = true
528 > web.api.http-v2 = true
529 > [web]
529 > [web]
530 > push_ssl = false
530 > push_ssl = false
531 > EOF
531 > EOF
532
532
533 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
533 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
534 $ cat hg.pid > $DAEMON_PIDS
534 $ cat hg.pid > $DAEMON_PIDS
535
535
536 Attempting to run a read-write command via multirequest on read-only URL is not allowed
536 Attempting to run a read-write command via multirequest on read-only URL is not allowed
537
537
538 $ send << EOF
538 $ send << EOF
539 > httprequest POST api/$HTTPV2/ro/multirequest
539 > httprequest POST api/$HTTPV2/ro/multirequest
540 > accept: $MEDIATYPE
540 > accept: $MEDIATYPE
541 > content-type: $MEDIATYPE
541 > content-type: $MEDIATYPE
542 > user-agent: test
542 > user-agent: test
543 > frame 1 command-name eos unbundle
543 > frame 1 1 stream-begin command-name eos unbundle
544 > EOF
544 > EOF
545 using raw connection to peer
545 using raw connection to peer
546 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
546 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
547 s> Accept-Encoding: identity\r\n
547 s> Accept-Encoding: identity\r\n
548 s> accept: application/mercurial-exp-framing-0002\r\n
548 s> accept: application/mercurial-exp-framing-0002\r\n
549 s> content-type: application/mercurial-exp-framing-0002\r\n
549 s> content-type: application/mercurial-exp-framing-0002\r\n
550 s> user-agent: test\r\n
550 s> user-agent: test\r\n
551 s> content-length: 14\r\n
551 s> content-length: 16\r\n
552 s> host: $LOCALIP:$HGPORT\r\n (glob)
552 s> host: $LOCALIP:$HGPORT\r\n (glob)
553 s> \r\n
553 s> \r\n
554 s> \x08\x00\x00\x01\x00\x11unbundle
554 s> \x08\x00\x00\x01\x00\x01\x01\x11unbundle
555 s> makefile('rb', None)
555 s> makefile('rb', None)
556 s> HTTP/1.1 403 Forbidden\r\n
556 s> HTTP/1.1 403 Forbidden\r\n
557 s> Server: testing stub value\r\n
557 s> Server: testing stub value\r\n
558 s> Date: $HTTP_DATE$\r\n
558 s> Date: $HTTP_DATE$\r\n
559 s> Content-Type: text/plain\r\n
559 s> Content-Type: text/plain\r\n
560 s> Content-Length: 53\r\n
560 s> Content-Length: 53\r\n
561 s> \r\n
561 s> \r\n
562 s> insufficient permissions to execute command: unbundle
562 s> insufficient permissions to execute command: unbundle
563
563
564 $ cat error.log
564 $ cat error.log
@@ -1,678 +1,684 b''
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 header = framing.parseheader(frame)
21 header = framing.parseheader(frame)
22 payload = frame[framing.FRAME_HEADER_SIZE:]
22 payload = frame[framing.FRAME_HEADER_SIZE:]
23 assert len(payload) == header.length
23 assert len(payload) == header.length
24
24
25 yield reactor.onframerecv(framing.frame(header.requestid,
25 yield reactor.onframerecv(framing.frame(header.requestid,
26 header.streamid,
27 header.streamflags,
26 header.typeid,
28 header.typeid,
27 header.flags,
29 header.flags,
28 payload))
30 payload))
29
31
30 def sendcommandframes(reactor, stream, rid, cmd, args, datafh=None):
32 def sendcommandframes(reactor, stream, rid, cmd, args, datafh=None):
31 """Generate frames to run a command and send them to a reactor."""
33 """Generate frames to run a command and send them to a reactor."""
32 return sendframes(reactor,
34 return sendframes(reactor,
33 framing.createcommandframes(stream, rid, cmd, args,
35 framing.createcommandframes(stream, rid, cmd, args,
34 datafh))
36 datafh))
35
37
36 class FrameTests(unittest.TestCase):
38 class FrameTests(unittest.TestCase):
37 def testdataexactframesize(self):
39 def testdataexactframesize(self):
38 data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE)
40 data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE)
39
41
40 stream = framing.stream()
42 stream = framing.stream(1)
41 frames = list(framing.createcommandframes(stream, 1, b'command',
43 frames = list(framing.createcommandframes(stream, 1, b'command',
42 {}, data))
44 {}, data))
43 self.assertEqual(frames, [
45 self.assertEqual(frames, [
44 ffs(b'1 command-name have-data command'),
46 ffs(b'1 1 stream-begin command-name have-data command'),
45 ffs(b'1 command-data continuation %s' % data.getvalue()),
47 ffs(b'1 1 0 command-data continuation %s' % data.getvalue()),
46 ffs(b'1 command-data eos ')
48 ffs(b'1 1 0 command-data eos ')
47 ])
49 ])
48
50
49 def testdatamultipleframes(self):
51 def testdatamultipleframes(self):
50 data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1))
52 data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1))
51
53
52 stream = framing.stream()
54 stream = framing.stream(1)
53 frames = list(framing.createcommandframes(stream, 1, b'command', {},
55 frames = list(framing.createcommandframes(stream, 1, b'command', {},
54 data))
56 data))
55 self.assertEqual(frames, [
57 self.assertEqual(frames, [
56 ffs(b'1 command-name have-data command'),
58 ffs(b'1 1 stream-begin command-name have-data command'),
57 ffs(b'1 command-data continuation %s' % (
59 ffs(b'1 1 0 command-data continuation %s' % (
58 b'x' * framing.DEFAULT_MAX_FRAME_SIZE)),
60 b'x' * framing.DEFAULT_MAX_FRAME_SIZE)),
59 ffs(b'1 command-data eos x'),
61 ffs(b'1 1 0 command-data eos x'),
60 ])
62 ])
61
63
62 def testargsanddata(self):
64 def testargsanddata(self):
63 data = util.bytesio(b'x' * 100)
65 data = util.bytesio(b'x' * 100)
64
66
65 stream = framing.stream()
67 stream = framing.stream(1)
66 frames = list(framing.createcommandframes(stream, 1, b'command', {
68 frames = list(framing.createcommandframes(stream, 1, b'command', {
67 b'key1': b'key1value',
69 b'key1': b'key1value',
68 b'key2': b'key2value',
70 b'key2': b'key2value',
69 b'key3': b'key3value',
71 b'key3': b'key3value',
70 }, data))
72 }, data))
71
73
72 self.assertEqual(frames, [
74 self.assertEqual(frames, [
73 ffs(b'1 command-name have-args|have-data command'),
75 ffs(b'1 1 stream-begin command-name have-args|have-data command'),
74 ffs(br'1 command-argument 0 \x04\x00\x09\x00key1key1value'),
76 ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key1key1value'),
75 ffs(br'1 command-argument 0 \x04\x00\x09\x00key2key2value'),
77 ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key2key2value'),
76 ffs(br'1 command-argument eoa \x04\x00\x09\x00key3key3value'),
78 ffs(br'1 1 0 command-argument eoa \x04\x00\x09\x00key3key3value'),
77 ffs(b'1 command-data eos %s' % data.getvalue()),
79 ffs(b'1 1 0 command-data eos %s' % data.getvalue()),
78 ])
80 ])
79
81
80 def testtextoutputexcessiveargs(self):
82 def testtextoutputexcessiveargs(self):
81 """At most 255 formatting arguments are allowed."""
83 """At most 255 formatting arguments are allowed."""
82 with self.assertRaisesRegexp(ValueError,
84 with self.assertRaisesRegexp(ValueError,
83 'cannot use more than 255 formatting'):
85 'cannot use more than 255 formatting'):
84 args = [b'x' for i in range(256)]
86 args = [b'x' for i in range(256)]
85 list(framing.createtextoutputframe(None, 1,
87 list(framing.createtextoutputframe(None, 1,
86 [(b'bleh', args, [])]))
88 [(b'bleh', args, [])]))
87
89
88 def testtextoutputexcessivelabels(self):
90 def testtextoutputexcessivelabels(self):
89 """At most 255 labels are allowed."""
91 """At most 255 labels are allowed."""
90 with self.assertRaisesRegexp(ValueError,
92 with self.assertRaisesRegexp(ValueError,
91 'cannot use more than 255 labels'):
93 'cannot use more than 255 labels'):
92 labels = [b'l' for i in range(256)]
94 labels = [b'l' for i in range(256)]
93 list(framing.createtextoutputframe(None, 1,
95 list(framing.createtextoutputframe(None, 1,
94 [(b'bleh', [], labels)]))
96 [(b'bleh', [], labels)]))
95
97
96 def testtextoutputformattingstringtype(self):
98 def testtextoutputformattingstringtype(self):
97 """Formatting string must be bytes."""
99 """Formatting string must be bytes."""
98 with self.assertRaisesRegexp(ValueError, 'must use bytes formatting '):
100 with self.assertRaisesRegexp(ValueError, 'must use bytes formatting '):
99 list(framing.createtextoutputframe(None, 1, [
101 list(framing.createtextoutputframe(None, 1, [
100 (b'foo'.decode('ascii'), [], [])]))
102 (b'foo'.decode('ascii'), [], [])]))
101
103
102 def testtextoutputargumentbytes(self):
104 def testtextoutputargumentbytes(self):
103 with self.assertRaisesRegexp(ValueError, 'must use bytes for argument'):
105 with self.assertRaisesRegexp(ValueError, 'must use bytes for argument'):
104 list(framing.createtextoutputframe(None, 1, [
106 list(framing.createtextoutputframe(None, 1, [
105 (b'foo', [b'foo'.decode('ascii')], [])]))
107 (b'foo', [b'foo'.decode('ascii')], [])]))
106
108
107 def testtextoutputlabelbytes(self):
109 def testtextoutputlabelbytes(self):
108 with self.assertRaisesRegexp(ValueError, 'must use bytes for labels'):
110 with self.assertRaisesRegexp(ValueError, 'must use bytes for labels'):
109 list(framing.createtextoutputframe(None, 1, [
111 list(framing.createtextoutputframe(None, 1, [
110 (b'foo', [], [b'foo'.decode('ascii')])]))
112 (b'foo', [], [b'foo'.decode('ascii')])]))
111
113
112 def testtextoutputtoolongformatstring(self):
114 def testtextoutputtoolongformatstring(self):
113 with self.assertRaisesRegexp(ValueError,
115 with self.assertRaisesRegexp(ValueError,
114 'formatting string cannot be longer than'):
116 'formatting string cannot be longer than'):
115 list(framing.createtextoutputframe(None, 1, [
117 list(framing.createtextoutputframe(None, 1, [
116 (b'x' * 65536, [], [])]))
118 (b'x' * 65536, [], [])]))
117
119
118 def testtextoutputtoolongargumentstring(self):
120 def testtextoutputtoolongargumentstring(self):
119 with self.assertRaisesRegexp(ValueError,
121 with self.assertRaisesRegexp(ValueError,
120 'argument string cannot be longer than'):
122 'argument string cannot be longer than'):
121 list(framing.createtextoutputframe(None, 1, [
123 list(framing.createtextoutputframe(None, 1, [
122 (b'bleh', [b'x' * 65536], [])]))
124 (b'bleh', [b'x' * 65536], [])]))
123
125
124 def testtextoutputtoolonglabelstring(self):
126 def testtextoutputtoolonglabelstring(self):
125 with self.assertRaisesRegexp(ValueError,
127 with self.assertRaisesRegexp(ValueError,
126 'label string cannot be longer than'):
128 'label string cannot be longer than'):
127 list(framing.createtextoutputframe(None, 1, [
129 list(framing.createtextoutputframe(None, 1, [
128 (b'bleh', [], [b'x' * 65536])]))
130 (b'bleh', [], [b'x' * 65536])]))
129
131
130 def testtextoutput1simpleatom(self):
132 def testtextoutput1simpleatom(self):
131 stream = framing.stream()
133 stream = framing.stream(1)
132 val = list(framing.createtextoutputframe(stream, 1, [
134 val = list(framing.createtextoutputframe(stream, 1, [
133 (b'foo', [], [])]))
135 (b'foo', [], [])]))
134
136
135 self.assertEqual(val, [
137 self.assertEqual(val, [
136 ffs(br'1 text-output 0 \x03\x00\x00\x00foo'),
138 ffs(br'1 1 stream-begin text-output 0 \x03\x00\x00\x00foo'),
137 ])
139 ])
138
140
139 def testtextoutput2simpleatoms(self):
141 def testtextoutput2simpleatoms(self):
140 stream = framing.stream()
142 stream = framing.stream(1)
141 val = list(framing.createtextoutputframe(stream, 1, [
143 val = list(framing.createtextoutputframe(stream, 1, [
142 (b'foo', [], []),
144 (b'foo', [], []),
143 (b'bar', [], []),
145 (b'bar', [], []),
144 ]))
146 ]))
145
147
146 self.assertEqual(val, [
148 self.assertEqual(val, [
147 ffs(br'1 text-output 0 \x03\x00\x00\x00foo\x03\x00\x00\x00bar'),
149 ffs(br'1 1 stream-begin text-output 0 '
150 br'\x03\x00\x00\x00foo\x03\x00\x00\x00bar'),
148 ])
151 ])
149
152
150 def testtextoutput1arg(self):
153 def testtextoutput1arg(self):
151 stream = framing.stream()
154 stream = framing.stream(1)
152 val = list(framing.createtextoutputframe(stream, 1, [
155 val = list(framing.createtextoutputframe(stream, 1, [
153 (b'foo %s', [b'val1'], []),
156 (b'foo %s', [b'val1'], []),
154 ]))
157 ]))
155
158
156 self.assertEqual(val, [
159 self.assertEqual(val, [
157 ffs(br'1 text-output 0 \x06\x00\x00\x01\x04\x00foo %sval1'),
160 ffs(br'1 1 stream-begin text-output 0 '
161 br'\x06\x00\x00\x01\x04\x00foo %sval1'),
158 ])
162 ])
159
163
160 def testtextoutput2arg(self):
164 def testtextoutput2arg(self):
161 stream = framing.stream()
165 stream = framing.stream(1)
162 val = list(framing.createtextoutputframe(stream, 1, [
166 val = list(framing.createtextoutputframe(stream, 1, [
163 (b'foo %s %s', [b'val', b'value'], []),
167 (b'foo %s %s', [b'val', b'value'], []),
164 ]))
168 ]))
165
169
166 self.assertEqual(val, [
170 self.assertEqual(val, [
167 ffs(br'1 text-output 0 \x09\x00\x00\x02\x03\x00\x05\x00'
171 ffs(br'1 1 stream-begin text-output 0 '
168 br'foo %s %svalvalue'),
172 br'\x09\x00\x00\x02\x03\x00\x05\x00foo %s %svalvalue'),
169 ])
173 ])
170
174
171 def testtextoutput1label(self):
175 def testtextoutput1label(self):
172 stream = framing.stream()
176 stream = framing.stream(1)
173 val = list(framing.createtextoutputframe(stream, 1, [
177 val = list(framing.createtextoutputframe(stream, 1, [
174 (b'foo', [], [b'label']),
178 (b'foo', [], [b'label']),
175 ]))
179 ]))
176
180
177 self.assertEqual(val, [
181 self.assertEqual(val, [
178 ffs(br'1 text-output 0 \x03\x00\x01\x00\x05foolabel'),
182 ffs(br'1 1 stream-begin text-output 0 '
183 br'\x03\x00\x01\x00\x05foolabel'),
179 ])
184 ])
180
185
181 def testargandlabel(self):
186 def testargandlabel(self):
182 stream = framing.stream()
187 stream = framing.stream(1)
183 val = list(framing.createtextoutputframe(stream, 1, [
188 val = list(framing.createtextoutputframe(stream, 1, [
184 (b'foo %s', [b'arg'], [b'label']),
189 (b'foo %s', [b'arg'], [b'label']),
185 ]))
190 ]))
186
191
187 self.assertEqual(val, [
192 self.assertEqual(val, [
188 ffs(br'1 text-output 0 \x06\x00\x01\x01\x05\x03\x00foo %slabelarg'),
193 ffs(br'1 1 stream-begin text-output 0 '
194 br'\x06\x00\x01\x01\x05\x03\x00foo %slabelarg'),
189 ])
195 ])
190
196
191 class ServerReactorTests(unittest.TestCase):
197 class ServerReactorTests(unittest.TestCase):
192 def _sendsingleframe(self, reactor, f):
198 def _sendsingleframe(self, reactor, f):
193 results = list(sendframes(reactor, [f]))
199 results = list(sendframes(reactor, [f]))
194 self.assertEqual(len(results), 1)
200 self.assertEqual(len(results), 1)
195
201
196 return results[0]
202 return results[0]
197
203
198 def assertaction(self, res, expected):
204 def assertaction(self, res, expected):
199 self.assertIsInstance(res, tuple)
205 self.assertIsInstance(res, tuple)
200 self.assertEqual(len(res), 2)
206 self.assertEqual(len(res), 2)
201 self.assertIsInstance(res[1], dict)
207 self.assertIsInstance(res[1], dict)
202 self.assertEqual(res[0], expected)
208 self.assertEqual(res[0], expected)
203
209
204 def assertframesequal(self, frames, framestrings):
210 def assertframesequal(self, frames, framestrings):
205 expected = [ffs(s) for s in framestrings]
211 expected = [ffs(s) for s in framestrings]
206 self.assertEqual(list(frames), expected)
212 self.assertEqual(list(frames), expected)
207
213
208 def test1framecommand(self):
214 def test1framecommand(self):
209 """Receiving a command in a single frame yields request to run it."""
215 """Receiving a command in a single frame yields request to run it."""
210 reactor = makereactor()
216 reactor = makereactor()
211 stream = framing.stream()
217 stream = framing.stream(1)
212 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
218 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
213 self.assertEqual(len(results), 1)
219 self.assertEqual(len(results), 1)
214 self.assertaction(results[0], 'runcommand')
220 self.assertaction(results[0], 'runcommand')
215 self.assertEqual(results[0][1], {
221 self.assertEqual(results[0][1], {
216 'requestid': 1,
222 'requestid': 1,
217 'command': b'mycommand',
223 'command': b'mycommand',
218 'args': {},
224 'args': {},
219 'data': None,
225 'data': None,
220 })
226 })
221
227
222 result = reactor.oninputeof()
228 result = reactor.oninputeof()
223 self.assertaction(result, 'noop')
229 self.assertaction(result, 'noop')
224
230
225 def test1argument(self):
231 def test1argument(self):
226 reactor = makereactor()
232 reactor = makereactor()
227 stream = framing.stream()
233 stream = framing.stream(1)
228 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
234 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
229 {b'foo': b'bar'}))
235 {b'foo': b'bar'}))
230 self.assertEqual(len(results), 2)
236 self.assertEqual(len(results), 2)
231 self.assertaction(results[0], 'wantframe')
237 self.assertaction(results[0], 'wantframe')
232 self.assertaction(results[1], 'runcommand')
238 self.assertaction(results[1], 'runcommand')
233 self.assertEqual(results[1][1], {
239 self.assertEqual(results[1][1], {
234 'requestid': 41,
240 'requestid': 41,
235 'command': b'mycommand',
241 'command': b'mycommand',
236 'args': {b'foo': b'bar'},
242 'args': {b'foo': b'bar'},
237 'data': None,
243 'data': None,
238 })
244 })
239
245
240 def testmultiarguments(self):
246 def testmultiarguments(self):
241 reactor = makereactor()
247 reactor = makereactor()
242 stream = framing.stream()
248 stream = framing.stream(1)
243 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
249 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
244 {b'foo': b'bar', b'biz': b'baz'}))
250 {b'foo': b'bar', b'biz': b'baz'}))
245 self.assertEqual(len(results), 3)
251 self.assertEqual(len(results), 3)
246 self.assertaction(results[0], 'wantframe')
252 self.assertaction(results[0], 'wantframe')
247 self.assertaction(results[1], 'wantframe')
253 self.assertaction(results[1], 'wantframe')
248 self.assertaction(results[2], 'runcommand')
254 self.assertaction(results[2], 'runcommand')
249 self.assertEqual(results[2][1], {
255 self.assertEqual(results[2][1], {
250 'requestid': 1,
256 'requestid': 1,
251 'command': b'mycommand',
257 'command': b'mycommand',
252 'args': {b'foo': b'bar', b'biz': b'baz'},
258 'args': {b'foo': b'bar', b'biz': b'baz'},
253 'data': None,
259 'data': None,
254 })
260 })
255
261
256 def testsimplecommanddata(self):
262 def testsimplecommanddata(self):
257 reactor = makereactor()
263 reactor = makereactor()
258 stream = framing.stream()
264 stream = framing.stream(1)
259 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
265 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
260 util.bytesio(b'data!')))
266 util.bytesio(b'data!')))
261 self.assertEqual(len(results), 2)
267 self.assertEqual(len(results), 2)
262 self.assertaction(results[0], 'wantframe')
268 self.assertaction(results[0], 'wantframe')
263 self.assertaction(results[1], 'runcommand')
269 self.assertaction(results[1], 'runcommand')
264 self.assertEqual(results[1][1], {
270 self.assertEqual(results[1][1], {
265 'requestid': 1,
271 'requestid': 1,
266 'command': b'mycommand',
272 'command': b'mycommand',
267 'args': {},
273 'args': {},
268 'data': b'data!',
274 'data': b'data!',
269 })
275 })
270
276
271 def testmultipledataframes(self):
277 def testmultipledataframes(self):
272 frames = [
278 frames = [
273 ffs(b'1 command-name have-data mycommand'),
279 ffs(b'1 1 stream-begin command-name have-data mycommand'),
274 ffs(b'1 command-data continuation data1'),
280 ffs(b'1 1 0 command-data continuation data1'),
275 ffs(b'1 command-data continuation data2'),
281 ffs(b'1 1 0 command-data continuation data2'),
276 ffs(b'1 command-data eos data3'),
282 ffs(b'1 1 0 command-data eos data3'),
277 ]
283 ]
278
284
279 reactor = makereactor()
285 reactor = makereactor()
280 results = list(sendframes(reactor, frames))
286 results = list(sendframes(reactor, frames))
281 self.assertEqual(len(results), 4)
287 self.assertEqual(len(results), 4)
282 for i in range(3):
288 for i in range(3):
283 self.assertaction(results[i], 'wantframe')
289 self.assertaction(results[i], 'wantframe')
284 self.assertaction(results[3], 'runcommand')
290 self.assertaction(results[3], 'runcommand')
285 self.assertEqual(results[3][1], {
291 self.assertEqual(results[3][1], {
286 'requestid': 1,
292 'requestid': 1,
287 'command': b'mycommand',
293 'command': b'mycommand',
288 'args': {},
294 'args': {},
289 'data': b'data1data2data3',
295 'data': b'data1data2data3',
290 })
296 })
291
297
292 def testargumentanddata(self):
298 def testargumentanddata(self):
293 frames = [
299 frames = [
294 ffs(b'1 command-name have-args|have-data command'),
300 ffs(b'1 1 stream-begin command-name have-args|have-data command'),
295 ffs(br'1 command-argument 0 \x03\x00\x03\x00keyval'),
301 ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00keyval'),
296 ffs(br'1 command-argument eoa \x03\x00\x03\x00foobar'),
302 ffs(br'1 1 0 command-argument eoa \x03\x00\x03\x00foobar'),
297 ffs(b'1 command-data continuation value1'),
303 ffs(b'1 1 0 command-data continuation value1'),
298 ffs(b'1 command-data eos value2'),
304 ffs(b'1 1 0 command-data eos value2'),
299 ]
305 ]
300
306
301 reactor = makereactor()
307 reactor = makereactor()
302 results = list(sendframes(reactor, frames))
308 results = list(sendframes(reactor, frames))
303
309
304 self.assertaction(results[-1], 'runcommand')
310 self.assertaction(results[-1], 'runcommand')
305 self.assertEqual(results[-1][1], {
311 self.assertEqual(results[-1][1], {
306 'requestid': 1,
312 'requestid': 1,
307 'command': b'command',
313 'command': b'command',
308 'args': {
314 'args': {
309 b'key': b'val',
315 b'key': b'val',
310 b'foo': b'bar',
316 b'foo': b'bar',
311 },
317 },
312 'data': b'value1value2',
318 'data': b'value1value2',
313 })
319 })
314
320
315 def testunexpectedcommandargument(self):
321 def testunexpectedcommandargument(self):
316 """Command argument frame when not running a command is an error."""
322 """Command argument frame when not running a command is an error."""
317 result = self._sendsingleframe(makereactor(),
323 result = self._sendsingleframe(
318 ffs(b'1 command-argument 0 ignored'))
324 makereactor(), ffs(b'1 1 stream-begin command-argument 0 ignored'))
319 self.assertaction(result, 'error')
325 self.assertaction(result, 'error')
320 self.assertEqual(result[1], {
326 self.assertEqual(result[1], {
321 'message': b'expected command frame; got 2',
327 'message': b'expected command frame; got 2',
322 })
328 })
323
329
324 def testunexpectedcommandargumentreceiving(self):
330 def testunexpectedcommandargumentreceiving(self):
325 """Same as above but the command is receiving."""
331 """Same as above but the command is receiving."""
326 results = list(sendframes(makereactor(), [
332 results = list(sendframes(makereactor(), [
327 ffs(b'1 command-name have-data command'),
333 ffs(b'1 1 stream-begin command-name have-data command'),
328 ffs(b'1 command-argument eoa ignored'),
334 ffs(b'1 1 0 command-argument eoa ignored'),
329 ]))
335 ]))
330
336
331 self.assertaction(results[1], 'error')
337 self.assertaction(results[1], 'error')
332 self.assertEqual(results[1][1], {
338 self.assertEqual(results[1][1], {
333 'message': b'received command argument frame for request that is '
339 'message': b'received command argument frame for request that is '
334 b'not expecting arguments: 1',
340 b'not expecting arguments: 1',
335 })
341 })
336
342
337 def testunexpectedcommanddata(self):
343 def testunexpectedcommanddata(self):
338 """Command argument frame when not running a command is an error."""
344 """Command argument frame when not running a command is an error."""
339 result = self._sendsingleframe(makereactor(),
345 result = self._sendsingleframe(
340 ffs(b'1 command-data 0 ignored'))
346 makereactor(), ffs(b'1 1 stream-begin command-data 0 ignored'))
341 self.assertaction(result, 'error')
347 self.assertaction(result, 'error')
342 self.assertEqual(result[1], {
348 self.assertEqual(result[1], {
343 'message': b'expected command frame; got 3',
349 'message': b'expected command frame; got 3',
344 })
350 })
345
351
346 def testunexpectedcommanddatareceiving(self):
352 def testunexpectedcommanddatareceiving(self):
347 """Same as above except the command is receiving."""
353 """Same as above except the command is receiving."""
348 results = list(sendframes(makereactor(), [
354 results = list(sendframes(makereactor(), [
349 ffs(b'1 command-name have-args command'),
355 ffs(b'1 1 stream-begin command-name have-args command'),
350 ffs(b'1 command-data eos ignored'),
356 ffs(b'1 1 0 command-data eos ignored'),
351 ]))
357 ]))
352
358
353 self.assertaction(results[1], 'error')
359 self.assertaction(results[1], 'error')
354 self.assertEqual(results[1][1], {
360 self.assertEqual(results[1][1], {
355 'message': b'received command data frame for request that is not '
361 'message': b'received command data frame for request that is not '
356 b'expecting data: 1',
362 b'expecting data: 1',
357 })
363 })
358
364
359 def testmissingcommandframeflags(self):
365 def testmissingcommandframeflags(self):
360 """Command name frame must have flags set."""
366 """Command name frame must have flags set."""
361 result = self._sendsingleframe(makereactor(),
367 result = self._sendsingleframe(
362 ffs(b'1 command-name 0 command'))
368 makereactor(), ffs(b'1 1 stream-begin command-name 0 command'))
363 self.assertaction(result, 'error')
369 self.assertaction(result, 'error')
364 self.assertEqual(result[1], {
370 self.assertEqual(result[1], {
365 'message': b'missing frame flags on command frame',
371 'message': b'missing frame flags on command frame',
366 })
372 })
367
373
368 def testconflictingrequestidallowed(self):
374 def testconflictingrequestidallowed(self):
369 """Multiple fully serviced commands with same request ID is allowed."""
375 """Multiple fully serviced commands with same request ID is allowed."""
370 reactor = makereactor()
376 reactor = makereactor()
371 results = []
377 results = []
372 outstream = framing.stream()
378 outstream = framing.stream(2)
373 results.append(self._sendsingleframe(
379 results.append(self._sendsingleframe(
374 reactor, ffs(b'1 command-name eos command')))
380 reactor, ffs(b'1 1 stream-begin command-name eos command')))
375 result = reactor.onbytesresponseready(outstream, 1, b'response1')
381 result = reactor.onbytesresponseready(outstream, 1, b'response1')
376 self.assertaction(result, 'sendframes')
382 self.assertaction(result, 'sendframes')
377 list(result[1]['framegen'])
383 list(result[1]['framegen'])
378 results.append(self._sendsingleframe(
384 results.append(self._sendsingleframe(
379 reactor, ffs(b'1 command-name eos command')))
385 reactor, ffs(b'1 1 0 command-name eos command')))
380 result = reactor.onbytesresponseready(outstream, 1, b'response2')
386 result = reactor.onbytesresponseready(outstream, 1, b'response2')
381 self.assertaction(result, 'sendframes')
387 self.assertaction(result, 'sendframes')
382 list(result[1]['framegen'])
388 list(result[1]['framegen'])
383 results.append(self._sendsingleframe(
389 results.append(self._sendsingleframe(
384 reactor, ffs(b'1 command-name eos command')))
390 reactor, ffs(b'1 1 0 command-name eos command')))
385 result = reactor.onbytesresponseready(outstream, 1, b'response3')
391 result = reactor.onbytesresponseready(outstream, 1, b'response3')
386 self.assertaction(result, 'sendframes')
392 self.assertaction(result, 'sendframes')
387 list(result[1]['framegen'])
393 list(result[1]['framegen'])
388
394
389 for i in range(3):
395 for i in range(3):
390 self.assertaction(results[i], 'runcommand')
396 self.assertaction(results[i], 'runcommand')
391 self.assertEqual(results[i][1], {
397 self.assertEqual(results[i][1], {
392 'requestid': 1,
398 'requestid': 1,
393 'command': b'command',
399 'command': b'command',
394 'args': {},
400 'args': {},
395 'data': None,
401 'data': None,
396 })
402 })
397
403
398 def testconflictingrequestid(self):
404 def testconflictingrequestid(self):
399 """Request ID for new command matching in-flight command is illegal."""
405 """Request ID for new command matching in-flight command is illegal."""
400 results = list(sendframes(makereactor(), [
406 results = list(sendframes(makereactor(), [
401 ffs(b'1 command-name have-args command'),
407 ffs(b'1 1 stream-begin command-name have-args command'),
402 ffs(b'1 command-name eos command'),
408 ffs(b'1 1 0 command-name eos command'),
403 ]))
409 ]))
404
410
405 self.assertaction(results[0], 'wantframe')
411 self.assertaction(results[0], 'wantframe')
406 self.assertaction(results[1], 'error')
412 self.assertaction(results[1], 'error')
407 self.assertEqual(results[1][1], {
413 self.assertEqual(results[1][1], {
408 'message': b'request with ID 1 already received',
414 'message': b'request with ID 1 already received',
409 })
415 })
410
416
411 def testinterleavedcommands(self):
417 def testinterleavedcommands(self):
412 results = list(sendframes(makereactor(), [
418 results = list(sendframes(makereactor(), [
413 ffs(b'1 command-name have-args command1'),
419 ffs(b'1 1 stream-begin command-name have-args command1'),
414 ffs(b'3 command-name have-args command3'),
420 ffs(b'3 1 0 command-name have-args command3'),
415 ffs(br'1 command-argument 0 \x03\x00\x03\x00foobar'),
421 ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00foobar'),
416 ffs(br'3 command-argument 0 \x03\x00\x03\x00bizbaz'),
422 ffs(br'3 1 0 command-argument 0 \x03\x00\x03\x00bizbaz'),
417 ffs(br'3 command-argument eoa \x03\x00\x03\x00keyval'),
423 ffs(br'3 1 0 command-argument eoa \x03\x00\x03\x00keyval'),
418 ffs(br'1 command-argument eoa \x04\x00\x03\x00key1val'),
424 ffs(br'1 1 0 command-argument eoa \x04\x00\x03\x00key1val'),
419 ]))
425 ]))
420
426
421 self.assertEqual([t[0] for t in results], [
427 self.assertEqual([t[0] for t in results], [
422 'wantframe',
428 'wantframe',
423 'wantframe',
429 'wantframe',
424 'wantframe',
430 'wantframe',
425 'wantframe',
431 'wantframe',
426 'runcommand',
432 'runcommand',
427 'runcommand',
433 'runcommand',
428 ])
434 ])
429
435
430 self.assertEqual(results[4][1], {
436 self.assertEqual(results[4][1], {
431 'requestid': 3,
437 'requestid': 3,
432 'command': 'command3',
438 'command': 'command3',
433 'args': {b'biz': b'baz', b'key': b'val'},
439 'args': {b'biz': b'baz', b'key': b'val'},
434 'data': None,
440 'data': None,
435 })
441 })
436 self.assertEqual(results[5][1], {
442 self.assertEqual(results[5][1], {
437 'requestid': 1,
443 'requestid': 1,
438 'command': 'command1',
444 'command': 'command1',
439 'args': {b'foo': b'bar', b'key1': b'val'},
445 'args': {b'foo': b'bar', b'key1': b'val'},
440 'data': None,
446 'data': None,
441 })
447 })
442
448
443 def testmissingargumentframe(self):
449 def testmissingargumentframe(self):
444 # This test attempts to test behavior when reactor has an incomplete
450 # This test attempts to test behavior when reactor has an incomplete
445 # command request waiting on argument data. But it doesn't handle that
451 # command request waiting on argument data. But it doesn't handle that
446 # scenario yet. So this test does nothing of value.
452 # scenario yet. So this test does nothing of value.
447 frames = [
453 frames = [
448 ffs(b'1 command-name have-args command'),
454 ffs(b'1 1 stream-begin command-name have-args command'),
449 ]
455 ]
450
456
451 results = list(sendframes(makereactor(), frames))
457 results = list(sendframes(makereactor(), frames))
452 self.assertaction(results[0], 'wantframe')
458 self.assertaction(results[0], 'wantframe')
453
459
454 def testincompleteargumentname(self):
460 def testincompleteargumentname(self):
455 """Argument frame with incomplete name."""
461 """Argument frame with incomplete name."""
456 frames = [
462 frames = [
457 ffs(b'1 command-name have-args command1'),
463 ffs(b'1 1 stream-begin command-name have-args command1'),
458 ffs(br'1 command-argument eoa \x04\x00\xde\xadfoo'),
464 ffs(br'1 1 0 command-argument eoa \x04\x00\xde\xadfoo'),
459 ]
465 ]
460
466
461 results = list(sendframes(makereactor(), frames))
467 results = list(sendframes(makereactor(), frames))
462 self.assertEqual(len(results), 2)
468 self.assertEqual(len(results), 2)
463 self.assertaction(results[0], 'wantframe')
469 self.assertaction(results[0], 'wantframe')
464 self.assertaction(results[1], 'error')
470 self.assertaction(results[1], 'error')
465 self.assertEqual(results[1][1], {
471 self.assertEqual(results[1][1], {
466 'message': b'malformed argument frame: partial argument name',
472 'message': b'malformed argument frame: partial argument name',
467 })
473 })
468
474
469 def testincompleteargumentvalue(self):
475 def testincompleteargumentvalue(self):
470 """Argument frame with incomplete value."""
476 """Argument frame with incomplete value."""
471 frames = [
477 frames = [
472 ffs(b'1 command-name have-args command'),
478 ffs(b'1 1 stream-begin command-name have-args command'),
473 ffs(br'1 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
479 ffs(br'1 1 0 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
474 ]
480 ]
475
481
476 results = list(sendframes(makereactor(), frames))
482 results = list(sendframes(makereactor(), frames))
477 self.assertEqual(len(results), 2)
483 self.assertEqual(len(results), 2)
478 self.assertaction(results[0], 'wantframe')
484 self.assertaction(results[0], 'wantframe')
479 self.assertaction(results[1], 'error')
485 self.assertaction(results[1], 'error')
480 self.assertEqual(results[1][1], {
486 self.assertEqual(results[1][1], {
481 'message': b'malformed argument frame: partial argument value',
487 'message': b'malformed argument frame: partial argument value',
482 })
488 })
483
489
484 def testmissingcommanddataframe(self):
490 def testmissingcommanddataframe(self):
485 # The reactor doesn't currently handle partially received commands.
491 # The reactor doesn't currently handle partially received commands.
486 # So this test is failing to do anything with request 1.
492 # So this test is failing to do anything with request 1.
487 frames = [
493 frames = [
488 ffs(b'1 command-name have-data command1'),
494 ffs(b'1 1 stream-begin command-name have-data command1'),
489 ffs(b'3 command-name eos command2'),
495 ffs(b'3 1 0 command-name eos command2'),
490 ]
496 ]
491 results = list(sendframes(makereactor(), frames))
497 results = list(sendframes(makereactor(), frames))
492 self.assertEqual(len(results), 2)
498 self.assertEqual(len(results), 2)
493 self.assertaction(results[0], 'wantframe')
499 self.assertaction(results[0], 'wantframe')
494 self.assertaction(results[1], 'runcommand')
500 self.assertaction(results[1], 'runcommand')
495
501
496 def testmissingcommanddataframeflags(self):
502 def testmissingcommanddataframeflags(self):
497 frames = [
503 frames = [
498 ffs(b'1 command-name have-data command1'),
504 ffs(b'1 1 stream-begin command-name have-data command1'),
499 ffs(b'1 command-data 0 data'),
505 ffs(b'1 1 0 command-data 0 data'),
500 ]
506 ]
501 results = list(sendframes(makereactor(), frames))
507 results = list(sendframes(makereactor(), frames))
502 self.assertEqual(len(results), 2)
508 self.assertEqual(len(results), 2)
503 self.assertaction(results[0], 'wantframe')
509 self.assertaction(results[0], 'wantframe')
504 self.assertaction(results[1], 'error')
510 self.assertaction(results[1], 'error')
505 self.assertEqual(results[1][1], {
511 self.assertEqual(results[1][1], {
506 'message': b'command data frame without flags',
512 'message': b'command data frame without flags',
507 })
513 })
508
514
509 def testframefornonreceivingrequest(self):
515 def testframefornonreceivingrequest(self):
510 """Receiving a frame for a command that is not receiving is illegal."""
516 """Receiving a frame for a command that is not receiving is illegal."""
511 results = list(sendframes(makereactor(), [
517 results = list(sendframes(makereactor(), [
512 ffs(b'1 command-name eos command1'),
518 ffs(b'1 1 stream-begin command-name eos command1'),
513 ffs(b'3 command-name have-data command3'),
519 ffs(b'3 1 0 command-name have-data command3'),
514 ffs(b'5 command-argument eoa ignored'),
520 ffs(b'5 1 0 command-argument eoa ignored'),
515 ]))
521 ]))
516 self.assertaction(results[2], 'error')
522 self.assertaction(results[2], 'error')
517 self.assertEqual(results[2][1], {
523 self.assertEqual(results[2][1], {
518 'message': b'received frame for request that is not receiving: 5',
524 'message': b'received frame for request that is not receiving: 5',
519 })
525 })
520
526
521 def testsimpleresponse(self):
527 def testsimpleresponse(self):
522 """Bytes response to command sends result frames."""
528 """Bytes response to command sends result frames."""
523 reactor = makereactor()
529 reactor = makereactor()
524 instream = framing.stream()
530 instream = framing.stream(1)
525 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
531 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
526
532
527 outstream = framing.stream()
533 outstream = framing.stream(2)
528 result = reactor.onbytesresponseready(outstream, 1, b'response')
534 result = reactor.onbytesresponseready(outstream, 1, b'response')
529 self.assertaction(result, 'sendframes')
535 self.assertaction(result, 'sendframes')
530 self.assertframesequal(result[1]['framegen'], [
536 self.assertframesequal(result[1]['framegen'], [
531 b'1 bytes-response eos response',
537 b'1 2 stream-begin bytes-response eos response',
532 ])
538 ])
533
539
534 def testmultiframeresponse(self):
540 def testmultiframeresponse(self):
535 """Bytes response spanning multiple frames is handled."""
541 """Bytes response spanning multiple frames is handled."""
536 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
542 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
537 second = b'y' * 100
543 second = b'y' * 100
538
544
539 reactor = makereactor()
545 reactor = makereactor()
540 instream = framing.stream()
546 instream = framing.stream(1)
541 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
547 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
542
548
543 outstream = framing.stream()
549 outstream = framing.stream(2)
544 result = reactor.onbytesresponseready(outstream, 1, first + second)
550 result = reactor.onbytesresponseready(outstream, 1, first + second)
545 self.assertaction(result, 'sendframes')
551 self.assertaction(result, 'sendframes')
546 self.assertframesequal(result[1]['framegen'], [
552 self.assertframesequal(result[1]['framegen'], [
547 b'1 bytes-response continuation %s' % first,
553 b'1 2 stream-begin bytes-response continuation %s' % first,
548 b'1 bytes-response eos %s' % second,
554 b'1 2 0 bytes-response eos %s' % second,
549 ])
555 ])
550
556
551 def testapplicationerror(self):
557 def testapplicationerror(self):
552 reactor = makereactor()
558 reactor = makereactor()
553 instream = framing.stream()
559 instream = framing.stream(1)
554 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
560 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
555
561
556 outstream = framing.stream()
562 outstream = framing.stream(2)
557 result = reactor.onapplicationerror(outstream, 1, b'some message')
563 result = reactor.onapplicationerror(outstream, 1, b'some message')
558 self.assertaction(result, 'sendframes')
564 self.assertaction(result, 'sendframes')
559 self.assertframesequal(result[1]['framegen'], [
565 self.assertframesequal(result[1]['framegen'], [
560 b'1 error-response application some message',
566 b'1 2 stream-begin error-response application some message',
561 ])
567 ])
562
568
563 def test1commanddeferresponse(self):
569 def test1commanddeferresponse(self):
564 """Responses when in deferred output mode are delayed until EOF."""
570 """Responses when in deferred output mode are delayed until EOF."""
565 reactor = makereactor(deferoutput=True)
571 reactor = makereactor(deferoutput=True)
566 instream = framing.stream()
572 instream = framing.stream(1)
567 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
573 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
568 {}))
574 {}))
569 self.assertEqual(len(results), 1)
575 self.assertEqual(len(results), 1)
570 self.assertaction(results[0], 'runcommand')
576 self.assertaction(results[0], 'runcommand')
571
577
572 outstream = framing.stream()
578 outstream = framing.stream(2)
573 result = reactor.onbytesresponseready(outstream, 1, b'response')
579 result = reactor.onbytesresponseready(outstream, 1, b'response')
574 self.assertaction(result, 'noop')
580 self.assertaction(result, 'noop')
575 result = reactor.oninputeof()
581 result = reactor.oninputeof()
576 self.assertaction(result, 'sendframes')
582 self.assertaction(result, 'sendframes')
577 self.assertframesequal(result[1]['framegen'], [
583 self.assertframesequal(result[1]['framegen'], [
578 b'1 bytes-response eos response',
584 b'1 2 stream-begin bytes-response eos response',
579 ])
585 ])
580
586
581 def testmultiplecommanddeferresponse(self):
587 def testmultiplecommanddeferresponse(self):
582 reactor = makereactor(deferoutput=True)
588 reactor = makereactor(deferoutput=True)
583 instream = framing.stream()
589 instream = framing.stream(1)
584 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
590 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
585 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
591 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
586
592
587 outstream = framing.stream()
593 outstream = framing.stream(2)
588 result = reactor.onbytesresponseready(outstream, 1, b'response1')
594 result = reactor.onbytesresponseready(outstream, 1, b'response1')
589 self.assertaction(result, 'noop')
595 self.assertaction(result, 'noop')
590 result = reactor.onbytesresponseready(outstream, 3, b'response2')
596 result = reactor.onbytesresponseready(outstream, 3, b'response2')
591 self.assertaction(result, 'noop')
597 self.assertaction(result, 'noop')
592 result = reactor.oninputeof()
598 result = reactor.oninputeof()
593 self.assertaction(result, 'sendframes')
599 self.assertaction(result, 'sendframes')
594 self.assertframesequal(result[1]['framegen'], [
600 self.assertframesequal(result[1]['framegen'], [
595 b'1 bytes-response eos response1',
601 b'1 2 stream-begin bytes-response eos response1',
596 b'3 bytes-response eos response2'
602 b'3 2 0 bytes-response eos response2'
597 ])
603 ])
598
604
599 def testrequestidtracking(self):
605 def testrequestidtracking(self):
600 reactor = makereactor(deferoutput=True)
606 reactor = makereactor(deferoutput=True)
601 instream = framing.stream()
607 instream = framing.stream(1)
602 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
608 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
603 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
609 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
604 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
610 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
605
611
606 # Register results for commands out of order.
612 # Register results for commands out of order.
607 outstream = framing.stream()
613 outstream = framing.stream(2)
608 reactor.onbytesresponseready(outstream, 3, b'response3')
614 reactor.onbytesresponseready(outstream, 3, b'response3')
609 reactor.onbytesresponseready(outstream, 1, b'response1')
615 reactor.onbytesresponseready(outstream, 1, b'response1')
610 reactor.onbytesresponseready(outstream, 5, b'response5')
616 reactor.onbytesresponseready(outstream, 5, b'response5')
611
617
612 result = reactor.oninputeof()
618 result = reactor.oninputeof()
613 self.assertaction(result, 'sendframes')
619 self.assertaction(result, 'sendframes')
614 self.assertframesequal(result[1]['framegen'], [
620 self.assertframesequal(result[1]['framegen'], [
615 b'3 bytes-response eos response3',
621 b'3 2 stream-begin bytes-response eos response3',
616 b'1 bytes-response eos response1',
622 b'1 2 0 bytes-response eos response1',
617 b'5 bytes-response eos response5',
623 b'5 2 0 bytes-response eos response5',
618 ])
624 ])
619
625
620 def testduplicaterequestonactivecommand(self):
626 def testduplicaterequestonactivecommand(self):
621 """Receiving a request ID that matches a request that isn't finished."""
627 """Receiving a request ID that matches a request that isn't finished."""
622 reactor = makereactor()
628 reactor = makereactor()
623 stream = framing.stream()
629 stream = framing.stream(1)
624 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
630 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
625 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
631 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
626
632
627 self.assertaction(results[0], 'error')
633 self.assertaction(results[0], 'error')
628 self.assertEqual(results[0][1], {
634 self.assertEqual(results[0][1], {
629 'message': b'request with ID 1 is already active',
635 'message': b'request with ID 1 is already active',
630 })
636 })
631
637
632 def testduplicaterequestonactivecommandnosend(self):
638 def testduplicaterequestonactivecommandnosend(self):
633 """Same as above but we've registered a response but haven't sent it."""
639 """Same as above but we've registered a response but haven't sent it."""
634 reactor = makereactor()
640 reactor = makereactor()
635 instream = framing.stream()
641 instream = framing.stream(1)
636 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
642 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
637 outstream = framing.stream()
643 outstream = framing.stream(2)
638 reactor.onbytesresponseready(outstream, 1, b'response')
644 reactor.onbytesresponseready(outstream, 1, b'response')
639
645
640 # We've registered the response but haven't sent it. From the
646 # We've registered the response but haven't sent it. From the
641 # perspective of the reactor, the command is still active.
647 # perspective of the reactor, the command is still active.
642
648
643 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
649 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
644 self.assertaction(results[0], 'error')
650 self.assertaction(results[0], 'error')
645 self.assertEqual(results[0][1], {
651 self.assertEqual(results[0][1], {
646 'message': b'request with ID 1 is already active',
652 'message': b'request with ID 1 is already active',
647 })
653 })
648
654
649 def testduplicaterequestargumentframe(self):
655 def testduplicaterequestargumentframe(self):
650 """Variant on above except we sent an argument frame instead of name."""
656 """Variant on above except we sent an argument frame instead of name."""
651 reactor = makereactor()
657 reactor = makereactor()
652 stream = framing.stream()
658 stream = framing.stream(1)
653 list(sendcommandframes(reactor, stream, 1, b'command', {}))
659 list(sendcommandframes(reactor, stream, 1, b'command', {}))
654 results = list(sendframes(reactor, [
660 results = list(sendframes(reactor, [
655 ffs(b'3 command-name have-args command'),
661 ffs(b'3 1 stream-begin command-name have-args command'),
656 ffs(b'1 command-argument 0 ignored'),
662 ffs(b'1 1 0 command-argument 0 ignored'),
657 ]))
663 ]))
658 self.assertaction(results[0], 'wantframe')
664 self.assertaction(results[0], 'wantframe')
659 self.assertaction(results[1], 'error')
665 self.assertaction(results[1], 'error')
660 self.assertEqual(results[1][1], {
666 self.assertEqual(results[1][1], {
661 'message': 'received frame for request that is still active: 1',
667 'message': 'received frame for request that is still active: 1',
662 })
668 })
663
669
664 def testduplicaterequestaftersend(self):
670 def testduplicaterequestaftersend(self):
665 """We can use a duplicate request ID after we've sent the response."""
671 """We can use a duplicate request ID after we've sent the response."""
666 reactor = makereactor()
672 reactor = makereactor()
667 instream = framing.stream()
673 instream = framing.stream(1)
668 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
674 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
669 outstream = framing.stream()
675 outstream = framing.stream(2)
670 res = reactor.onbytesresponseready(outstream, 1, b'response')
676 res = reactor.onbytesresponseready(outstream, 1, b'response')
671 list(res[1]['framegen'])
677 list(res[1]['framegen'])
672
678
673 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
679 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
674 self.assertaction(results[0], 'runcommand')
680 self.assertaction(results[0], 'runcommand')
675
681
676 if __name__ == '__main__':
682 if __name__ == '__main__':
677 import silenttestrunner
683 import silenttestrunner
678 silenttestrunner.main(__name__)
684 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now