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