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