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