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