##// END OF EJS Templates
wireprotov2peer: stream decoded responses...
Gregory Szorc -
r39597:d06834e0 default
parent child Browse files
Show More
@@ -1,3364 +1,3364 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 time
25 25
26 26 from .i18n import _
27 27 from .node import (
28 28 bin,
29 29 hex,
30 30 nullhex,
31 31 nullid,
32 32 nullrev,
33 33 short,
34 34 )
35 35 from . import (
36 36 bundle2,
37 37 changegroup,
38 38 cmdutil,
39 39 color,
40 40 context,
41 41 dagparser,
42 42 encoding,
43 43 error,
44 44 exchange,
45 45 extensions,
46 46 filemerge,
47 47 filesetlang,
48 48 formatter,
49 49 hg,
50 50 httppeer,
51 51 localrepo,
52 52 lock as lockmod,
53 53 logcmdutil,
54 54 merge as mergemod,
55 55 obsolete,
56 56 obsutil,
57 57 phases,
58 58 policy,
59 59 pvec,
60 60 pycompat,
61 61 registrar,
62 62 repair,
63 63 revlog,
64 64 revset,
65 65 revsetlang,
66 66 scmutil,
67 67 setdiscovery,
68 68 simplemerge,
69 69 sshpeer,
70 70 sslutil,
71 71 streamclone,
72 72 templater,
73 73 treediscovery,
74 74 upgrade,
75 75 url as urlmod,
76 76 util,
77 77 vfs as vfsmod,
78 78 wireprotoframing,
79 79 wireprotoserver,
80 80 wireprotov2peer,
81 81 )
82 82 from .utils import (
83 83 cborutil,
84 84 dateutil,
85 85 procutil,
86 86 stringutil,
87 87 )
88 88
89 89 from .revlogutils import (
90 90 deltas as deltautil
91 91 )
92 92
93 93 release = lockmod.release
94 94
95 95 command = registrar.command()
96 96
97 97 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
98 98 def debugancestor(ui, repo, *args):
99 99 """find the ancestor revision of two revisions in a given index"""
100 100 if len(args) == 3:
101 101 index, rev1, rev2 = args
102 102 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
103 103 lookup = r.lookup
104 104 elif len(args) == 2:
105 105 if not repo:
106 106 raise error.Abort(_('there is no Mercurial repository here '
107 107 '(.hg not found)'))
108 108 rev1, rev2 = args
109 109 r = repo.changelog
110 110 lookup = repo.lookup
111 111 else:
112 112 raise error.Abort(_('either two or three arguments required'))
113 113 a = r.ancestor(lookup(rev1), lookup(rev2))
114 114 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
115 115
116 116 @command('debugapplystreamclonebundle', [], 'FILE')
117 117 def debugapplystreamclonebundle(ui, repo, fname):
118 118 """apply a stream clone bundle file"""
119 119 f = hg.openpath(ui, fname)
120 120 gen = exchange.readbundle(ui, f, fname)
121 121 gen.apply(repo)
122 122
123 123 @command('debugbuilddag',
124 124 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
125 125 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
126 126 ('n', 'new-file', None, _('add new file at each rev'))],
127 127 _('[OPTION]... [TEXT]'))
128 128 def debugbuilddag(ui, repo, text=None,
129 129 mergeable_file=False,
130 130 overwritten_file=False,
131 131 new_file=False):
132 132 """builds a repo with a given DAG from scratch in the current empty repo
133 133
134 134 The description of the DAG is read from stdin if not given on the
135 135 command line.
136 136
137 137 Elements:
138 138
139 139 - "+n" is a linear run of n nodes based on the current default parent
140 140 - "." is a single node based on the current default parent
141 141 - "$" resets the default parent to null (implied at the start);
142 142 otherwise the default parent is always the last node created
143 143 - "<p" sets the default parent to the backref p
144 144 - "*p" is a fork at parent p, which is a backref
145 145 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
146 146 - "/p2" is a merge of the preceding node and p2
147 147 - ":tag" defines a local tag for the preceding node
148 148 - "@branch" sets the named branch for subsequent nodes
149 149 - "#...\\n" is a comment up to the end of the line
150 150
151 151 Whitespace between the above elements is ignored.
152 152
153 153 A backref is either
154 154
155 155 - a number n, which references the node curr-n, where curr is the current
156 156 node, or
157 157 - the name of a local tag you placed earlier using ":tag", or
158 158 - empty to denote the default parent.
159 159
160 160 All string valued-elements are either strictly alphanumeric, or must
161 161 be enclosed in double quotes ("..."), with "\\" as escape character.
162 162 """
163 163
164 164 if text is None:
165 165 ui.status(_("reading DAG from stdin\n"))
166 166 text = ui.fin.read()
167 167
168 168 cl = repo.changelog
169 169 if len(cl) > 0:
170 170 raise error.Abort(_('repository is not empty'))
171 171
172 172 # determine number of revs in DAG
173 173 total = 0
174 174 for type, data in dagparser.parsedag(text):
175 175 if type == 'n':
176 176 total += 1
177 177
178 178 if mergeable_file:
179 179 linesperrev = 2
180 180 # make a file with k lines per rev
181 181 initialmergedlines = ['%d' % i
182 182 for i in pycompat.xrange(0, total * linesperrev)]
183 183 initialmergedlines.append("")
184 184
185 185 tags = []
186 186 progress = ui.makeprogress(_('building'), unit=_('revisions'),
187 187 total=total)
188 188 with progress, repo.wlock(), repo.lock(), repo.transaction("builddag"):
189 189 at = -1
190 190 atbranch = 'default'
191 191 nodeids = []
192 192 id = 0
193 193 progress.update(id)
194 194 for type, data in dagparser.parsedag(text):
195 195 if type == 'n':
196 196 ui.note(('node %s\n' % pycompat.bytestr(data)))
197 197 id, ps = data
198 198
199 199 files = []
200 200 filecontent = {}
201 201
202 202 p2 = None
203 203 if mergeable_file:
204 204 fn = "mf"
205 205 p1 = repo[ps[0]]
206 206 if len(ps) > 1:
207 207 p2 = repo[ps[1]]
208 208 pa = p1.ancestor(p2)
209 209 base, local, other = [x[fn].data() for x in (pa, p1,
210 210 p2)]
211 211 m3 = simplemerge.Merge3Text(base, local, other)
212 212 ml = [l.strip() for l in m3.merge_lines()]
213 213 ml.append("")
214 214 elif at > 0:
215 215 ml = p1[fn].data().split("\n")
216 216 else:
217 217 ml = initialmergedlines
218 218 ml[id * linesperrev] += " r%i" % id
219 219 mergedtext = "\n".join(ml)
220 220 files.append(fn)
221 221 filecontent[fn] = mergedtext
222 222
223 223 if overwritten_file:
224 224 fn = "of"
225 225 files.append(fn)
226 226 filecontent[fn] = "r%i\n" % id
227 227
228 228 if new_file:
229 229 fn = "nf%i" % id
230 230 files.append(fn)
231 231 filecontent[fn] = "r%i\n" % id
232 232 if len(ps) > 1:
233 233 if not p2:
234 234 p2 = repo[ps[1]]
235 235 for fn in p2:
236 236 if fn.startswith("nf"):
237 237 files.append(fn)
238 238 filecontent[fn] = p2[fn].data()
239 239
240 240 def fctxfn(repo, cx, path):
241 241 if path in filecontent:
242 242 return context.memfilectx(repo, cx, path,
243 243 filecontent[path])
244 244 return None
245 245
246 246 if len(ps) == 0 or ps[0] < 0:
247 247 pars = [None, None]
248 248 elif len(ps) == 1:
249 249 pars = [nodeids[ps[0]], None]
250 250 else:
251 251 pars = [nodeids[p] for p in ps]
252 252 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
253 253 date=(id, 0),
254 254 user="debugbuilddag",
255 255 extra={'branch': atbranch})
256 256 nodeid = repo.commitctx(cx)
257 257 nodeids.append(nodeid)
258 258 at = id
259 259 elif type == 'l':
260 260 id, name = data
261 261 ui.note(('tag %s\n' % name))
262 262 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
263 263 elif type == 'a':
264 264 ui.note(('branch %s\n' % data))
265 265 atbranch = data
266 266 progress.update(id)
267 267
268 268 if tags:
269 269 repo.vfs.write("localtags", "".join(tags))
270 270
271 271 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
272 272 indent_string = ' ' * indent
273 273 if all:
274 274 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
275 275 % indent_string)
276 276
277 277 def showchunks(named):
278 278 ui.write("\n%s%s\n" % (indent_string, named))
279 279 for deltadata in gen.deltaiter():
280 280 node, p1, p2, cs, deltabase, delta, flags = deltadata
281 281 ui.write("%s%s %s %s %s %s %d\n" %
282 282 (indent_string, hex(node), hex(p1), hex(p2),
283 283 hex(cs), hex(deltabase), len(delta)))
284 284
285 285 chunkdata = gen.changelogheader()
286 286 showchunks("changelog")
287 287 chunkdata = gen.manifestheader()
288 288 showchunks("manifest")
289 289 for chunkdata in iter(gen.filelogheader, {}):
290 290 fname = chunkdata['filename']
291 291 showchunks(fname)
292 292 else:
293 293 if isinstance(gen, bundle2.unbundle20):
294 294 raise error.Abort(_('use debugbundle2 for this file'))
295 295 chunkdata = gen.changelogheader()
296 296 for deltadata in gen.deltaiter():
297 297 node, p1, p2, cs, deltabase, delta, flags = deltadata
298 298 ui.write("%s%s\n" % (indent_string, hex(node)))
299 299
300 300 def _debugobsmarkers(ui, part, indent=0, **opts):
301 301 """display version and markers contained in 'data'"""
302 302 opts = pycompat.byteskwargs(opts)
303 303 data = part.read()
304 304 indent_string = ' ' * indent
305 305 try:
306 306 version, markers = obsolete._readmarkers(data)
307 307 except error.UnknownVersion as exc:
308 308 msg = "%sunsupported version: %s (%d bytes)\n"
309 309 msg %= indent_string, exc.version, len(data)
310 310 ui.write(msg)
311 311 else:
312 312 msg = "%sversion: %d (%d bytes)\n"
313 313 msg %= indent_string, version, len(data)
314 314 ui.write(msg)
315 315 fm = ui.formatter('debugobsolete', opts)
316 316 for rawmarker in sorted(markers):
317 317 m = obsutil.marker(None, rawmarker)
318 318 fm.startitem()
319 319 fm.plain(indent_string)
320 320 cmdutil.showmarker(fm, m)
321 321 fm.end()
322 322
323 323 def _debugphaseheads(ui, data, indent=0):
324 324 """display version and markers contained in 'data'"""
325 325 indent_string = ' ' * indent
326 326 headsbyphase = phases.binarydecode(data)
327 327 for phase in phases.allphases:
328 328 for head in headsbyphase[phase]:
329 329 ui.write(indent_string)
330 330 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
331 331
332 332 def _quasirepr(thing):
333 333 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
334 334 return '{%s}' % (
335 335 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
336 336 return pycompat.bytestr(repr(thing))
337 337
338 338 def _debugbundle2(ui, gen, all=None, **opts):
339 339 """lists the contents of a bundle2"""
340 340 if not isinstance(gen, bundle2.unbundle20):
341 341 raise error.Abort(_('not a bundle2 file'))
342 342 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
343 343 parttypes = opts.get(r'part_type', [])
344 344 for part in gen.iterparts():
345 345 if parttypes and part.type not in parttypes:
346 346 continue
347 347 msg = '%s -- %s (mandatory: %r)\n'
348 348 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
349 349 if part.type == 'changegroup':
350 350 version = part.params.get('version', '01')
351 351 cg = changegroup.getunbundler(version, part, 'UN')
352 352 if not ui.quiet:
353 353 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
354 354 if part.type == 'obsmarkers':
355 355 if not ui.quiet:
356 356 _debugobsmarkers(ui, part, indent=4, **opts)
357 357 if part.type == 'phase-heads':
358 358 if not ui.quiet:
359 359 _debugphaseheads(ui, part, indent=4)
360 360
361 361 @command('debugbundle',
362 362 [('a', 'all', None, _('show all details')),
363 363 ('', 'part-type', [], _('show only the named part type')),
364 364 ('', 'spec', None, _('print the bundlespec of the bundle'))],
365 365 _('FILE'),
366 366 norepo=True)
367 367 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
368 368 """lists the contents of a bundle"""
369 369 with hg.openpath(ui, bundlepath) as f:
370 370 if spec:
371 371 spec = exchange.getbundlespec(ui, f)
372 372 ui.write('%s\n' % spec)
373 373 return
374 374
375 375 gen = exchange.readbundle(ui, f, bundlepath)
376 376 if isinstance(gen, bundle2.unbundle20):
377 377 return _debugbundle2(ui, gen, all=all, **opts)
378 378 _debugchangegroup(ui, gen, all=all, **opts)
379 379
380 380 @command('debugcapabilities',
381 381 [], _('PATH'),
382 382 norepo=True)
383 383 def debugcapabilities(ui, path, **opts):
384 384 """lists the capabilities of a remote peer"""
385 385 opts = pycompat.byteskwargs(opts)
386 386 peer = hg.peer(ui, opts, path)
387 387 caps = peer.capabilities()
388 388 ui.write(('Main capabilities:\n'))
389 389 for c in sorted(caps):
390 390 ui.write((' %s\n') % c)
391 391 b2caps = bundle2.bundle2caps(peer)
392 392 if b2caps:
393 393 ui.write(('Bundle2 capabilities:\n'))
394 394 for key, values in sorted(b2caps.iteritems()):
395 395 ui.write((' %s\n') % key)
396 396 for v in values:
397 397 ui.write((' %s\n') % v)
398 398
399 399 @command('debugcheckstate', [], '')
400 400 def debugcheckstate(ui, repo):
401 401 """validate the correctness of the current dirstate"""
402 402 parent1, parent2 = repo.dirstate.parents()
403 403 m1 = repo[parent1].manifest()
404 404 m2 = repo[parent2].manifest()
405 405 errors = 0
406 406 for f in repo.dirstate:
407 407 state = repo.dirstate[f]
408 408 if state in "nr" and f not in m1:
409 409 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
410 410 errors += 1
411 411 if state in "a" and f in m1:
412 412 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
413 413 errors += 1
414 414 if state in "m" and f not in m1 and f not in m2:
415 415 ui.warn(_("%s in state %s, but not in either manifest\n") %
416 416 (f, state))
417 417 errors += 1
418 418 for f in m1:
419 419 state = repo.dirstate[f]
420 420 if state not in "nrm":
421 421 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
422 422 errors += 1
423 423 if errors:
424 424 error = _(".hg/dirstate inconsistent with current parent's manifest")
425 425 raise error.Abort(error)
426 426
427 427 @command('debugcolor',
428 428 [('', 'style', None, _('show all configured styles'))],
429 429 'hg debugcolor')
430 430 def debugcolor(ui, repo, **opts):
431 431 """show available color, effects or style"""
432 432 ui.write(('color mode: %s\n') % stringutil.pprint(ui._colormode))
433 433 if opts.get(r'style'):
434 434 return _debugdisplaystyle(ui)
435 435 else:
436 436 return _debugdisplaycolor(ui)
437 437
438 438 def _debugdisplaycolor(ui):
439 439 ui = ui.copy()
440 440 ui._styles.clear()
441 441 for effect in color._activeeffects(ui).keys():
442 442 ui._styles[effect] = effect
443 443 if ui._terminfoparams:
444 444 for k, v in ui.configitems('color'):
445 445 if k.startswith('color.'):
446 446 ui._styles[k] = k[6:]
447 447 elif k.startswith('terminfo.'):
448 448 ui._styles[k] = k[9:]
449 449 ui.write(_('available colors:\n'))
450 450 # sort label with a '_' after the other to group '_background' entry.
451 451 items = sorted(ui._styles.items(),
452 452 key=lambda i: ('_' in i[0], i[0], i[1]))
453 453 for colorname, label in items:
454 454 ui.write(('%s\n') % colorname, label=label)
455 455
456 456 def _debugdisplaystyle(ui):
457 457 ui.write(_('available style:\n'))
458 458 if not ui._styles:
459 459 return
460 460 width = max(len(s) for s in ui._styles)
461 461 for label, effects in sorted(ui._styles.items()):
462 462 ui.write('%s' % label, label=label)
463 463 if effects:
464 464 # 50
465 465 ui.write(': ')
466 466 ui.write(' ' * (max(0, width - len(label))))
467 467 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
468 468 ui.write('\n')
469 469
470 470 @command('debugcreatestreamclonebundle', [], 'FILE')
471 471 def debugcreatestreamclonebundle(ui, repo, fname):
472 472 """create a stream clone bundle file
473 473
474 474 Stream bundles are special bundles that are essentially archives of
475 475 revlog files. They are commonly used for cloning very quickly.
476 476 """
477 477 # TODO we may want to turn this into an abort when this functionality
478 478 # is moved into `hg bundle`.
479 479 if phases.hassecret(repo):
480 480 ui.warn(_('(warning: stream clone bundle will contain secret '
481 481 'revisions)\n'))
482 482
483 483 requirements, gen = streamclone.generatebundlev1(repo)
484 484 changegroup.writechunks(ui, gen, fname)
485 485
486 486 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
487 487
488 488 @command('debugdag',
489 489 [('t', 'tags', None, _('use tags as labels')),
490 490 ('b', 'branches', None, _('annotate with branch names')),
491 491 ('', 'dots', None, _('use dots for runs')),
492 492 ('s', 'spaces', None, _('separate elements by spaces'))],
493 493 _('[OPTION]... [FILE [REV]...]'),
494 494 optionalrepo=True)
495 495 def debugdag(ui, repo, file_=None, *revs, **opts):
496 496 """format the changelog or an index DAG as a concise textual description
497 497
498 498 If you pass a revlog index, the revlog's DAG is emitted. If you list
499 499 revision numbers, they get labeled in the output as rN.
500 500
501 501 Otherwise, the changelog DAG of the current repo is emitted.
502 502 """
503 503 spaces = opts.get(r'spaces')
504 504 dots = opts.get(r'dots')
505 505 if file_:
506 506 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
507 507 file_)
508 508 revs = set((int(r) for r in revs))
509 509 def events():
510 510 for r in rlog:
511 511 yield 'n', (r, list(p for p in rlog.parentrevs(r)
512 512 if p != -1))
513 513 if r in revs:
514 514 yield 'l', (r, "r%i" % r)
515 515 elif repo:
516 516 cl = repo.changelog
517 517 tags = opts.get(r'tags')
518 518 branches = opts.get(r'branches')
519 519 if tags:
520 520 labels = {}
521 521 for l, n in repo.tags().items():
522 522 labels.setdefault(cl.rev(n), []).append(l)
523 523 def events():
524 524 b = "default"
525 525 for r in cl:
526 526 if branches:
527 527 newb = cl.read(cl.node(r))[5]['branch']
528 528 if newb != b:
529 529 yield 'a', newb
530 530 b = newb
531 531 yield 'n', (r, list(p for p in cl.parentrevs(r)
532 532 if p != -1))
533 533 if tags:
534 534 ls = labels.get(r)
535 535 if ls:
536 536 for l in ls:
537 537 yield 'l', (r, l)
538 538 else:
539 539 raise error.Abort(_('need repo for changelog dag'))
540 540
541 541 for line in dagparser.dagtextlines(events(),
542 542 addspaces=spaces,
543 543 wraplabels=True,
544 544 wrapannotations=True,
545 545 wrapnonlinear=dots,
546 546 usedots=dots,
547 547 maxlinewidth=70):
548 548 ui.write(line)
549 549 ui.write("\n")
550 550
551 551 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
552 552 def debugdata(ui, repo, file_, rev=None, **opts):
553 553 """dump the contents of a data file revision"""
554 554 opts = pycompat.byteskwargs(opts)
555 555 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
556 556 if rev is not None:
557 557 raise error.CommandError('debugdata', _('invalid arguments'))
558 558 file_, rev = None, file_
559 559 elif rev is None:
560 560 raise error.CommandError('debugdata', _('invalid arguments'))
561 561 r = cmdutil.openstorage(repo, 'debugdata', file_, opts)
562 562 try:
563 563 ui.write(r.revision(r.lookup(rev), raw=True))
564 564 except KeyError:
565 565 raise error.Abort(_('invalid revision identifier %s') % rev)
566 566
567 567 @command('debugdate',
568 568 [('e', 'extended', None, _('try extended date formats'))],
569 569 _('[-e] DATE [RANGE]'),
570 570 norepo=True, optionalrepo=True)
571 571 def debugdate(ui, date, range=None, **opts):
572 572 """parse and display a date"""
573 573 if opts[r"extended"]:
574 574 d = dateutil.parsedate(date, util.extendeddateformats)
575 575 else:
576 576 d = dateutil.parsedate(date)
577 577 ui.write(("internal: %d %d\n") % d)
578 578 ui.write(("standard: %s\n") % dateutil.datestr(d))
579 579 if range:
580 580 m = dateutil.matchdate(range)
581 581 ui.write(("match: %s\n") % m(d[0]))
582 582
583 583 @command('debugdeltachain',
584 584 cmdutil.debugrevlogopts + cmdutil.formatteropts,
585 585 _('-c|-m|FILE'),
586 586 optionalrepo=True)
587 587 def debugdeltachain(ui, repo, file_=None, **opts):
588 588 """dump information about delta chains in a revlog
589 589
590 590 Output can be templatized. Available template keywords are:
591 591
592 592 :``rev``: revision number
593 593 :``chainid``: delta chain identifier (numbered by unique base)
594 594 :``chainlen``: delta chain length to this revision
595 595 :``prevrev``: previous revision in delta chain
596 596 :``deltatype``: role of delta / how it was computed
597 597 :``compsize``: compressed size of revision
598 598 :``uncompsize``: uncompressed size of revision
599 599 :``chainsize``: total size of compressed revisions in chain
600 600 :``chainratio``: total chain size divided by uncompressed revision size
601 601 (new delta chains typically start at ratio 2.00)
602 602 :``lindist``: linear distance from base revision in delta chain to end
603 603 of this revision
604 604 :``extradist``: total size of revisions not part of this delta chain from
605 605 base of delta chain to end of this revision; a measurement
606 606 of how much extra data we need to read/seek across to read
607 607 the delta chain for this revision
608 608 :``extraratio``: extradist divided by chainsize; another representation of
609 609 how much unrelated data is needed to load this delta chain
610 610
611 611 If the repository is configured to use the sparse read, additional keywords
612 612 are available:
613 613
614 614 :``readsize``: total size of data read from the disk for a revision
615 615 (sum of the sizes of all the blocks)
616 616 :``largestblock``: size of the largest block of data read from the disk
617 617 :``readdensity``: density of useful bytes in the data read from the disk
618 618 :``srchunks``: in how many data hunks the whole revision would be read
619 619
620 620 The sparse read can be enabled with experimental.sparse-read = True
621 621 """
622 622 opts = pycompat.byteskwargs(opts)
623 623 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
624 624 index = r.index
625 625 start = r.start
626 626 length = r.length
627 627 generaldelta = r.version & revlog.FLAG_GENERALDELTA
628 628 withsparseread = getattr(r, '_withsparseread', False)
629 629
630 630 def revinfo(rev):
631 631 e = index[rev]
632 632 compsize = e[1]
633 633 uncompsize = e[2]
634 634 chainsize = 0
635 635
636 636 if generaldelta:
637 637 if e[3] == e[5]:
638 638 deltatype = 'p1'
639 639 elif e[3] == e[6]:
640 640 deltatype = 'p2'
641 641 elif e[3] == rev - 1:
642 642 deltatype = 'prev'
643 643 elif e[3] == rev:
644 644 deltatype = 'base'
645 645 else:
646 646 deltatype = 'other'
647 647 else:
648 648 if e[3] == rev:
649 649 deltatype = 'base'
650 650 else:
651 651 deltatype = 'prev'
652 652
653 653 chain = r._deltachain(rev)[0]
654 654 for iterrev in chain:
655 655 e = index[iterrev]
656 656 chainsize += e[1]
657 657
658 658 return compsize, uncompsize, deltatype, chain, chainsize
659 659
660 660 fm = ui.formatter('debugdeltachain', opts)
661 661
662 662 fm.plain(' rev chain# chainlen prev delta '
663 663 'size rawsize chainsize ratio lindist extradist '
664 664 'extraratio')
665 665 if withsparseread:
666 666 fm.plain(' readsize largestblk rddensity srchunks')
667 667 fm.plain('\n')
668 668
669 669 chainbases = {}
670 670 for rev in r:
671 671 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
672 672 chainbase = chain[0]
673 673 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
674 674 basestart = start(chainbase)
675 675 revstart = start(rev)
676 676 lineardist = revstart + comp - basestart
677 677 extradist = lineardist - chainsize
678 678 try:
679 679 prevrev = chain[-2]
680 680 except IndexError:
681 681 prevrev = -1
682 682
683 683 if uncomp != 0:
684 684 chainratio = float(chainsize) / float(uncomp)
685 685 else:
686 686 chainratio = chainsize
687 687
688 688 if chainsize != 0:
689 689 extraratio = float(extradist) / float(chainsize)
690 690 else:
691 691 extraratio = extradist
692 692
693 693 fm.startitem()
694 694 fm.write('rev chainid chainlen prevrev deltatype compsize '
695 695 'uncompsize chainsize chainratio lindist extradist '
696 696 'extraratio',
697 697 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
698 698 rev, chainid, len(chain), prevrev, deltatype, comp,
699 699 uncomp, chainsize, chainratio, lineardist, extradist,
700 700 extraratio,
701 701 rev=rev, chainid=chainid, chainlen=len(chain),
702 702 prevrev=prevrev, deltatype=deltatype, compsize=comp,
703 703 uncompsize=uncomp, chainsize=chainsize,
704 704 chainratio=chainratio, lindist=lineardist,
705 705 extradist=extradist, extraratio=extraratio)
706 706 if withsparseread:
707 707 readsize = 0
708 708 largestblock = 0
709 709 srchunks = 0
710 710
711 711 for revschunk in deltautil.slicechunk(r, chain):
712 712 srchunks += 1
713 713 blkend = start(revschunk[-1]) + length(revschunk[-1])
714 714 blksize = blkend - start(revschunk[0])
715 715
716 716 readsize += blksize
717 717 if largestblock < blksize:
718 718 largestblock = blksize
719 719
720 720 if readsize:
721 721 readdensity = float(chainsize) / float(readsize)
722 722 else:
723 723 readdensity = 1
724 724
725 725 fm.write('readsize largestblock readdensity srchunks',
726 726 ' %10d %10d %9.5f %8d',
727 727 readsize, largestblock, readdensity, srchunks,
728 728 readsize=readsize, largestblock=largestblock,
729 729 readdensity=readdensity, srchunks=srchunks)
730 730
731 731 fm.plain('\n')
732 732
733 733 fm.end()
734 734
735 735 @command('debugdirstate|debugstate',
736 736 [('', 'nodates', None, _('do not display the saved mtime')),
737 737 ('', 'datesort', None, _('sort by saved mtime'))],
738 738 _('[OPTION]...'))
739 739 def debugstate(ui, repo, **opts):
740 740 """show the contents of the current dirstate"""
741 741
742 742 nodates = opts.get(r'nodates')
743 743 datesort = opts.get(r'datesort')
744 744
745 745 timestr = ""
746 746 if datesort:
747 747 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
748 748 else:
749 749 keyfunc = None # sort by filename
750 750 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
751 751 if ent[3] == -1:
752 752 timestr = 'unset '
753 753 elif nodates:
754 754 timestr = 'set '
755 755 else:
756 756 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
757 757 time.localtime(ent[3]))
758 758 timestr = encoding.strtolocal(timestr)
759 759 if ent[1] & 0o20000:
760 760 mode = 'lnk'
761 761 else:
762 762 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
763 763 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
764 764 for f in repo.dirstate.copies():
765 765 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
766 766
767 767 @command('debugdiscovery',
768 768 [('', 'old', None, _('use old-style discovery')),
769 769 ('', 'nonheads', None,
770 770 _('use old-style discovery with non-heads included')),
771 771 ('', 'rev', [], 'restrict discovery to this set of revs'),
772 772 ] + cmdutil.remoteopts,
773 773 _('[--rev REV] [OTHER]'))
774 774 def debugdiscovery(ui, repo, remoteurl="default", **opts):
775 775 """runs the changeset discovery protocol in isolation"""
776 776 opts = pycompat.byteskwargs(opts)
777 777 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
778 778 remote = hg.peer(repo, opts, remoteurl)
779 779 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
780 780
781 781 # make sure tests are repeatable
782 782 random.seed(12323)
783 783
784 784 def doit(pushedrevs, remoteheads, remote=remote):
785 785 if opts.get('old'):
786 786 if not util.safehasattr(remote, 'branches'):
787 787 # enable in-client legacy support
788 788 remote = localrepo.locallegacypeer(remote.local())
789 789 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
790 790 force=True)
791 791 common = set(common)
792 792 if not opts.get('nonheads'):
793 793 ui.write(("unpruned common: %s\n") %
794 794 " ".join(sorted(short(n) for n in common)))
795 795
796 796 clnode = repo.changelog.node
797 797 common = repo.revs('heads(::%ln)', common)
798 798 common = {clnode(r) for r in common}
799 799 else:
800 800 nodes = None
801 801 if pushedrevs:
802 802 revs = scmutil.revrange(repo, pushedrevs)
803 803 nodes = [repo[r].node() for r in revs]
804 804 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
805 805 ancestorsof=nodes)
806 806 common = set(common)
807 807 rheads = set(hds)
808 808 lheads = set(repo.heads())
809 809 ui.write(("common heads: %s\n") %
810 810 " ".join(sorted(short(n) for n in common)))
811 811 if lheads <= common:
812 812 ui.write(("local is subset\n"))
813 813 elif rheads <= common:
814 814 ui.write(("remote is subset\n"))
815 815
816 816 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
817 817 localrevs = opts['rev']
818 818 doit(localrevs, remoterevs)
819 819
820 820 _chunksize = 4 << 10
821 821
822 822 @command('debugdownload',
823 823 [
824 824 ('o', 'output', '', _('path')),
825 825 ],
826 826 optionalrepo=True)
827 827 def debugdownload(ui, repo, url, output=None, **opts):
828 828 """download a resource using Mercurial logic and config
829 829 """
830 830 fh = urlmod.open(ui, url, output)
831 831
832 832 dest = ui
833 833 if output:
834 834 dest = open(output, "wb", _chunksize)
835 835 try:
836 836 data = fh.read(_chunksize)
837 837 while data:
838 838 dest.write(data)
839 839 data = fh.read(_chunksize)
840 840 finally:
841 841 if output:
842 842 dest.close()
843 843
844 844 @command('debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
845 845 def debugextensions(ui, repo, **opts):
846 846 '''show information about active extensions'''
847 847 opts = pycompat.byteskwargs(opts)
848 848 exts = extensions.extensions(ui)
849 849 hgver = util.version()
850 850 fm = ui.formatter('debugextensions', opts)
851 851 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
852 852 isinternal = extensions.ismoduleinternal(extmod)
853 853 extsource = pycompat.fsencode(extmod.__file__)
854 854 if isinternal:
855 855 exttestedwith = [] # never expose magic string to users
856 856 else:
857 857 exttestedwith = getattr(extmod, 'testedwith', '').split()
858 858 extbuglink = getattr(extmod, 'buglink', None)
859 859
860 860 fm.startitem()
861 861
862 862 if ui.quiet or ui.verbose:
863 863 fm.write('name', '%s\n', extname)
864 864 else:
865 865 fm.write('name', '%s', extname)
866 866 if isinternal or hgver in exttestedwith:
867 867 fm.plain('\n')
868 868 elif not exttestedwith:
869 869 fm.plain(_(' (untested!)\n'))
870 870 else:
871 871 lasttestedversion = exttestedwith[-1]
872 872 fm.plain(' (%s!)\n' % lasttestedversion)
873 873
874 874 fm.condwrite(ui.verbose and extsource, 'source',
875 875 _(' location: %s\n'), extsource or "")
876 876
877 877 if ui.verbose:
878 878 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
879 879 fm.data(bundled=isinternal)
880 880
881 881 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
882 882 _(' tested with: %s\n'),
883 883 fm.formatlist(exttestedwith, name='ver'))
884 884
885 885 fm.condwrite(ui.verbose and extbuglink, 'buglink',
886 886 _(' bug reporting: %s\n'), extbuglink or "")
887 887
888 888 fm.end()
889 889
890 890 @command('debugfileset',
891 891 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV')),
892 892 ('', 'all-files', False,
893 893 _('test files from all revisions and working directory')),
894 894 ('s', 'show-matcher', None,
895 895 _('print internal representation of matcher')),
896 896 ('p', 'show-stage', [],
897 897 _('print parsed tree at the given stage'), _('NAME'))],
898 898 _('[-r REV] [--all-files] [OPTION]... FILESPEC'))
899 899 def debugfileset(ui, repo, expr, **opts):
900 900 '''parse and apply a fileset specification'''
901 901 from . import fileset
902 902 fileset.symbols # force import of fileset so we have predicates to optimize
903 903 opts = pycompat.byteskwargs(opts)
904 904 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
905 905
906 906 stages = [
907 907 ('parsed', pycompat.identity),
908 908 ('analyzed', filesetlang.analyze),
909 909 ('optimized', filesetlang.optimize),
910 910 ]
911 911 stagenames = set(n for n, f in stages)
912 912
913 913 showalways = set()
914 914 if ui.verbose and not opts['show_stage']:
915 915 # show parsed tree by --verbose (deprecated)
916 916 showalways.add('parsed')
917 917 if opts['show_stage'] == ['all']:
918 918 showalways.update(stagenames)
919 919 else:
920 920 for n in opts['show_stage']:
921 921 if n not in stagenames:
922 922 raise error.Abort(_('invalid stage name: %s') % n)
923 923 showalways.update(opts['show_stage'])
924 924
925 925 tree = filesetlang.parse(expr)
926 926 for n, f in stages:
927 927 tree = f(tree)
928 928 if n in showalways:
929 929 if opts['show_stage'] or n != 'parsed':
930 930 ui.write(("* %s:\n") % n)
931 931 ui.write(filesetlang.prettyformat(tree), "\n")
932 932
933 933 files = set()
934 934 if opts['all_files']:
935 935 for r in repo:
936 936 c = repo[r]
937 937 files.update(c.files())
938 938 files.update(c.substate)
939 939 if opts['all_files'] or ctx.rev() is None:
940 940 wctx = repo[None]
941 941 files.update(repo.dirstate.walk(scmutil.matchall(repo),
942 942 subrepos=list(wctx.substate),
943 943 unknown=True, ignored=True))
944 944 files.update(wctx.substate)
945 945 else:
946 946 files.update(ctx.files())
947 947 files.update(ctx.substate)
948 948
949 949 m = ctx.matchfileset(expr)
950 950 if opts['show_matcher'] or (opts['show_matcher'] is None and ui.verbose):
951 951 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
952 952 for f in sorted(files):
953 953 if not m(f):
954 954 continue
955 955 ui.write("%s\n" % f)
956 956
957 957 @command('debugformat',
958 958 [] + cmdutil.formatteropts)
959 959 def debugformat(ui, repo, **opts):
960 960 """display format information about the current repository
961 961
962 962 Use --verbose to get extra information about current config value and
963 963 Mercurial default."""
964 964 opts = pycompat.byteskwargs(opts)
965 965 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
966 966 maxvariantlength = max(len('format-variant'), maxvariantlength)
967 967
968 968 def makeformatname(name):
969 969 return '%s:' + (' ' * (maxvariantlength - len(name)))
970 970
971 971 fm = ui.formatter('debugformat', opts)
972 972 if fm.isplain():
973 973 def formatvalue(value):
974 974 if util.safehasattr(value, 'startswith'):
975 975 return value
976 976 if value:
977 977 return 'yes'
978 978 else:
979 979 return 'no'
980 980 else:
981 981 formatvalue = pycompat.identity
982 982
983 983 fm.plain('format-variant')
984 984 fm.plain(' ' * (maxvariantlength - len('format-variant')))
985 985 fm.plain(' repo')
986 986 if ui.verbose:
987 987 fm.plain(' config default')
988 988 fm.plain('\n')
989 989 for fv in upgrade.allformatvariant:
990 990 fm.startitem()
991 991 repovalue = fv.fromrepo(repo)
992 992 configvalue = fv.fromconfig(repo)
993 993
994 994 if repovalue != configvalue:
995 995 namelabel = 'formatvariant.name.mismatchconfig'
996 996 repolabel = 'formatvariant.repo.mismatchconfig'
997 997 elif repovalue != fv.default:
998 998 namelabel = 'formatvariant.name.mismatchdefault'
999 999 repolabel = 'formatvariant.repo.mismatchdefault'
1000 1000 else:
1001 1001 namelabel = 'formatvariant.name.uptodate'
1002 1002 repolabel = 'formatvariant.repo.uptodate'
1003 1003
1004 1004 fm.write('name', makeformatname(fv.name), fv.name,
1005 1005 label=namelabel)
1006 1006 fm.write('repo', ' %3s', formatvalue(repovalue),
1007 1007 label=repolabel)
1008 1008 if fv.default != configvalue:
1009 1009 configlabel = 'formatvariant.config.special'
1010 1010 else:
1011 1011 configlabel = 'formatvariant.config.default'
1012 1012 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
1013 1013 label=configlabel)
1014 1014 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
1015 1015 label='formatvariant.default')
1016 1016 fm.plain('\n')
1017 1017 fm.end()
1018 1018
1019 1019 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
1020 1020 def debugfsinfo(ui, path="."):
1021 1021 """show information detected about current filesystem"""
1022 1022 ui.write(('path: %s\n') % path)
1023 1023 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
1024 1024 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1025 1025 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
1026 1026 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1027 1027 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
1028 1028 casesensitive = '(unknown)'
1029 1029 try:
1030 1030 with pycompat.namedtempfile(prefix='.debugfsinfo', dir=path) as f:
1031 1031 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
1032 1032 except OSError:
1033 1033 pass
1034 1034 ui.write(('case-sensitive: %s\n') % casesensitive)
1035 1035
1036 1036 @command('debuggetbundle',
1037 1037 [('H', 'head', [], _('id of head node'), _('ID')),
1038 1038 ('C', 'common', [], _('id of common node'), _('ID')),
1039 1039 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1040 1040 _('REPO FILE [-H|-C ID]...'),
1041 1041 norepo=True)
1042 1042 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1043 1043 """retrieves a bundle from a repo
1044 1044
1045 1045 Every ID must be a full-length hex node id string. Saves the bundle to the
1046 1046 given file.
1047 1047 """
1048 1048 opts = pycompat.byteskwargs(opts)
1049 1049 repo = hg.peer(ui, opts, repopath)
1050 1050 if not repo.capable('getbundle'):
1051 1051 raise error.Abort("getbundle() not supported by target repository")
1052 1052 args = {}
1053 1053 if common:
1054 1054 args[r'common'] = [bin(s) for s in common]
1055 1055 if head:
1056 1056 args[r'heads'] = [bin(s) for s in head]
1057 1057 # TODO: get desired bundlecaps from command line.
1058 1058 args[r'bundlecaps'] = None
1059 1059 bundle = repo.getbundle('debug', **args)
1060 1060
1061 1061 bundletype = opts.get('type', 'bzip2').lower()
1062 1062 btypes = {'none': 'HG10UN',
1063 1063 'bzip2': 'HG10BZ',
1064 1064 'gzip': 'HG10GZ',
1065 1065 'bundle2': 'HG20'}
1066 1066 bundletype = btypes.get(bundletype)
1067 1067 if bundletype not in bundle2.bundletypes:
1068 1068 raise error.Abort(_('unknown bundle type specified with --type'))
1069 1069 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1070 1070
1071 1071 @command('debugignore', [], '[FILE]')
1072 1072 def debugignore(ui, repo, *files, **opts):
1073 1073 """display the combined ignore pattern and information about ignored files
1074 1074
1075 1075 With no argument display the combined ignore pattern.
1076 1076
1077 1077 Given space separated file names, shows if the given file is ignored and
1078 1078 if so, show the ignore rule (file and line number) that matched it.
1079 1079 """
1080 1080 ignore = repo.dirstate._ignore
1081 1081 if not files:
1082 1082 # Show all the patterns
1083 1083 ui.write("%s\n" % pycompat.byterepr(ignore))
1084 1084 else:
1085 1085 m = scmutil.match(repo[None], pats=files)
1086 1086 for f in m.files():
1087 1087 nf = util.normpath(f)
1088 1088 ignored = None
1089 1089 ignoredata = None
1090 1090 if nf != '.':
1091 1091 if ignore(nf):
1092 1092 ignored = nf
1093 1093 ignoredata = repo.dirstate._ignorefileandline(nf)
1094 1094 else:
1095 1095 for p in util.finddirs(nf):
1096 1096 if ignore(p):
1097 1097 ignored = p
1098 1098 ignoredata = repo.dirstate._ignorefileandline(p)
1099 1099 break
1100 1100 if ignored:
1101 1101 if ignored == nf:
1102 1102 ui.write(_("%s is ignored\n") % m.uipath(f))
1103 1103 else:
1104 1104 ui.write(_("%s is ignored because of "
1105 1105 "containing folder %s\n")
1106 1106 % (m.uipath(f), ignored))
1107 1107 ignorefile, lineno, line = ignoredata
1108 1108 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1109 1109 % (ignorefile, lineno, line))
1110 1110 else:
1111 1111 ui.write(_("%s is not ignored\n") % m.uipath(f))
1112 1112
1113 1113 @command('debugindex', cmdutil.debugrevlogopts + cmdutil.formatteropts,
1114 1114 _('-c|-m|FILE'))
1115 1115 def debugindex(ui, repo, file_=None, **opts):
1116 1116 """dump index data for a storage primitive"""
1117 1117 opts = pycompat.byteskwargs(opts)
1118 1118 store = cmdutil.openstorage(repo, 'debugindex', file_, opts)
1119 1119
1120 1120 if ui.debugflag:
1121 1121 shortfn = hex
1122 1122 else:
1123 1123 shortfn = short
1124 1124
1125 1125 idlen = 12
1126 1126 for i in store:
1127 1127 idlen = len(shortfn(store.node(i)))
1128 1128 break
1129 1129
1130 1130 fm = ui.formatter('debugindex', opts)
1131 1131 fm.plain(b' rev linkrev %s %s p2\n' % (
1132 1132 b'nodeid'.ljust(idlen),
1133 1133 b'p1'.ljust(idlen)))
1134 1134
1135 1135 for rev in store:
1136 1136 node = store.node(rev)
1137 1137 parents = store.parents(node)
1138 1138
1139 1139 fm.startitem()
1140 1140 fm.write(b'rev', b'%6d ', rev)
1141 1141 fm.write(b'linkrev', '%7d ', store.linkrev(rev))
1142 1142 fm.write(b'node', '%s ', shortfn(node))
1143 1143 fm.write(b'p1', '%s ', shortfn(parents[0]))
1144 1144 fm.write(b'p2', '%s', shortfn(parents[1]))
1145 1145 fm.plain(b'\n')
1146 1146
1147 1147 fm.end()
1148 1148
1149 1149 @command('debugindexdot', cmdutil.debugrevlogopts,
1150 1150 _('-c|-m|FILE'), optionalrepo=True)
1151 1151 def debugindexdot(ui, repo, file_=None, **opts):
1152 1152 """dump an index DAG as a graphviz dot file"""
1153 1153 opts = pycompat.byteskwargs(opts)
1154 1154 r = cmdutil.openstorage(repo, 'debugindexdot', file_, opts)
1155 1155 ui.write(("digraph G {\n"))
1156 1156 for i in r:
1157 1157 node = r.node(i)
1158 1158 pp = r.parents(node)
1159 1159 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1160 1160 if pp[1] != nullid:
1161 1161 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1162 1162 ui.write("}\n")
1163 1163
1164 1164 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1165 1165 def debuginstall(ui, **opts):
1166 1166 '''test Mercurial installation
1167 1167
1168 1168 Returns 0 on success.
1169 1169 '''
1170 1170 opts = pycompat.byteskwargs(opts)
1171 1171
1172 1172 def writetemp(contents):
1173 1173 (fd, name) = pycompat.mkstemp(prefix="hg-debuginstall-")
1174 1174 f = os.fdopen(fd, r"wb")
1175 1175 f.write(contents)
1176 1176 f.close()
1177 1177 return name
1178 1178
1179 1179 problems = 0
1180 1180
1181 1181 fm = ui.formatter('debuginstall', opts)
1182 1182 fm.startitem()
1183 1183
1184 1184 # encoding
1185 1185 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1186 1186 err = None
1187 1187 try:
1188 1188 codecs.lookup(pycompat.sysstr(encoding.encoding))
1189 1189 except LookupError as inst:
1190 1190 err = stringutil.forcebytestr(inst)
1191 1191 problems += 1
1192 1192 fm.condwrite(err, 'encodingerror', _(" %s\n"
1193 1193 " (check that your locale is properly set)\n"), err)
1194 1194
1195 1195 # Python
1196 1196 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1197 1197 pycompat.sysexecutable)
1198 1198 fm.write('pythonver', _("checking Python version (%s)\n"),
1199 1199 ("%d.%d.%d" % sys.version_info[:3]))
1200 1200 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1201 1201 os.path.dirname(pycompat.fsencode(os.__file__)))
1202 1202
1203 1203 security = set(sslutil.supportedprotocols)
1204 1204 if sslutil.hassni:
1205 1205 security.add('sni')
1206 1206
1207 1207 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1208 1208 fm.formatlist(sorted(security), name='protocol',
1209 1209 fmt='%s', sep=','))
1210 1210
1211 1211 # These are warnings, not errors. So don't increment problem count. This
1212 1212 # may change in the future.
1213 1213 if 'tls1.2' not in security:
1214 1214 fm.plain(_(' TLS 1.2 not supported by Python install; '
1215 1215 'network connections lack modern security\n'))
1216 1216 if 'sni' not in security:
1217 1217 fm.plain(_(' SNI not supported by Python install; may have '
1218 1218 'connectivity issues with some servers\n'))
1219 1219
1220 1220 # TODO print CA cert info
1221 1221
1222 1222 # hg version
1223 1223 hgver = util.version()
1224 1224 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1225 1225 hgver.split('+')[0])
1226 1226 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1227 1227 '+'.join(hgver.split('+')[1:]))
1228 1228
1229 1229 # compiled modules
1230 1230 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1231 1231 policy.policy)
1232 1232 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1233 1233 os.path.dirname(pycompat.fsencode(__file__)))
1234 1234
1235 1235 if policy.policy in ('c', 'allow'):
1236 1236 err = None
1237 1237 try:
1238 1238 from .cext import (
1239 1239 base85,
1240 1240 bdiff,
1241 1241 mpatch,
1242 1242 osutil,
1243 1243 )
1244 1244 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1245 1245 except Exception as inst:
1246 1246 err = stringutil.forcebytestr(inst)
1247 1247 problems += 1
1248 1248 fm.condwrite(err, 'extensionserror', " %s\n", err)
1249 1249
1250 1250 compengines = util.compengines._engines.values()
1251 1251 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1252 1252 fm.formatlist(sorted(e.name() for e in compengines),
1253 1253 name='compengine', fmt='%s', sep=', '))
1254 1254 fm.write('compenginesavail', _('checking available compression engines '
1255 1255 '(%s)\n'),
1256 1256 fm.formatlist(sorted(e.name() for e in compengines
1257 1257 if e.available()),
1258 1258 name='compengine', fmt='%s', sep=', '))
1259 1259 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1260 1260 fm.write('compenginesserver', _('checking available compression engines '
1261 1261 'for wire protocol (%s)\n'),
1262 1262 fm.formatlist([e.name() for e in wirecompengines
1263 1263 if e.wireprotosupport()],
1264 1264 name='compengine', fmt='%s', sep=', '))
1265 1265 re2 = 'missing'
1266 1266 if util._re2:
1267 1267 re2 = 'available'
1268 1268 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1269 1269 fm.data(re2=bool(util._re2))
1270 1270
1271 1271 # templates
1272 1272 p = templater.templatepaths()
1273 1273 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1274 1274 fm.condwrite(not p, '', _(" no template directories found\n"))
1275 1275 if p:
1276 1276 m = templater.templatepath("map-cmdline.default")
1277 1277 if m:
1278 1278 # template found, check if it is working
1279 1279 err = None
1280 1280 try:
1281 1281 templater.templater.frommapfile(m)
1282 1282 except Exception as inst:
1283 1283 err = stringutil.forcebytestr(inst)
1284 1284 p = None
1285 1285 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1286 1286 else:
1287 1287 p = None
1288 1288 fm.condwrite(p, 'defaulttemplate',
1289 1289 _("checking default template (%s)\n"), m)
1290 1290 fm.condwrite(not m, 'defaulttemplatenotfound',
1291 1291 _(" template '%s' not found\n"), "default")
1292 1292 if not p:
1293 1293 problems += 1
1294 1294 fm.condwrite(not p, '',
1295 1295 _(" (templates seem to have been installed incorrectly)\n"))
1296 1296
1297 1297 # editor
1298 1298 editor = ui.geteditor()
1299 1299 editor = util.expandpath(editor)
1300 1300 editorbin = procutil.shellsplit(editor)[0]
1301 1301 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1302 1302 cmdpath = procutil.findexe(editorbin)
1303 1303 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1304 1304 _(" No commit editor set and can't find %s in PATH\n"
1305 1305 " (specify a commit editor in your configuration"
1306 1306 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1307 1307 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1308 1308 _(" Can't find editor '%s' in PATH\n"
1309 1309 " (specify a commit editor in your configuration"
1310 1310 " file)\n"), not cmdpath and editorbin)
1311 1311 if not cmdpath and editor != 'vi':
1312 1312 problems += 1
1313 1313
1314 1314 # check username
1315 1315 username = None
1316 1316 err = None
1317 1317 try:
1318 1318 username = ui.username()
1319 1319 except error.Abort as e:
1320 1320 err = stringutil.forcebytestr(e)
1321 1321 problems += 1
1322 1322
1323 1323 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1324 1324 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1325 1325 " (specify a username in your configuration file)\n"), err)
1326 1326
1327 1327 fm.condwrite(not problems, '',
1328 1328 _("no problems detected\n"))
1329 1329 if not problems:
1330 1330 fm.data(problems=problems)
1331 1331 fm.condwrite(problems, 'problems',
1332 1332 _("%d problems detected,"
1333 1333 " please check your install!\n"), problems)
1334 1334 fm.end()
1335 1335
1336 1336 return problems
1337 1337
1338 1338 @command('debugknown', [], _('REPO ID...'), norepo=True)
1339 1339 def debugknown(ui, repopath, *ids, **opts):
1340 1340 """test whether node ids are known to a repo
1341 1341
1342 1342 Every ID must be a full-length hex node id string. Returns a list of 0s
1343 1343 and 1s indicating unknown/known.
1344 1344 """
1345 1345 opts = pycompat.byteskwargs(opts)
1346 1346 repo = hg.peer(ui, opts, repopath)
1347 1347 if not repo.capable('known'):
1348 1348 raise error.Abort("known() not supported by target repository")
1349 1349 flags = repo.known([bin(s) for s in ids])
1350 1350 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1351 1351
1352 1352 @command('debuglabelcomplete', [], _('LABEL...'))
1353 1353 def debuglabelcomplete(ui, repo, *args):
1354 1354 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1355 1355 debugnamecomplete(ui, repo, *args)
1356 1356
1357 1357 @command('debuglocks',
1358 1358 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1359 1359 ('W', 'force-wlock', None,
1360 1360 _('free the working state lock (DANGEROUS)')),
1361 1361 ('s', 'set-lock', None, _('set the store lock until stopped')),
1362 1362 ('S', 'set-wlock', None,
1363 1363 _('set the working state lock until stopped'))],
1364 1364 _('[OPTION]...'))
1365 1365 def debuglocks(ui, repo, **opts):
1366 1366 """show or modify state of locks
1367 1367
1368 1368 By default, this command will show which locks are held. This
1369 1369 includes the user and process holding the lock, the amount of time
1370 1370 the lock has been held, and the machine name where the process is
1371 1371 running if it's not local.
1372 1372
1373 1373 Locks protect the integrity of Mercurial's data, so should be
1374 1374 treated with care. System crashes or other interruptions may cause
1375 1375 locks to not be properly released, though Mercurial will usually
1376 1376 detect and remove such stale locks automatically.
1377 1377
1378 1378 However, detecting stale locks may not always be possible (for
1379 1379 instance, on a shared filesystem). Removing locks may also be
1380 1380 blocked by filesystem permissions.
1381 1381
1382 1382 Setting a lock will prevent other commands from changing the data.
1383 1383 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1384 1384 The set locks are removed when the command exits.
1385 1385
1386 1386 Returns 0 if no locks are held.
1387 1387
1388 1388 """
1389 1389
1390 1390 if opts.get(r'force_lock'):
1391 1391 repo.svfs.unlink('lock')
1392 1392 if opts.get(r'force_wlock'):
1393 1393 repo.vfs.unlink('wlock')
1394 1394 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1395 1395 return 0
1396 1396
1397 1397 locks = []
1398 1398 try:
1399 1399 if opts.get(r'set_wlock'):
1400 1400 try:
1401 1401 locks.append(repo.wlock(False))
1402 1402 except error.LockHeld:
1403 1403 raise error.Abort(_('wlock is already held'))
1404 1404 if opts.get(r'set_lock'):
1405 1405 try:
1406 1406 locks.append(repo.lock(False))
1407 1407 except error.LockHeld:
1408 1408 raise error.Abort(_('lock is already held'))
1409 1409 if len(locks):
1410 1410 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1411 1411 return 0
1412 1412 finally:
1413 1413 release(*locks)
1414 1414
1415 1415 now = time.time()
1416 1416 held = 0
1417 1417
1418 1418 def report(vfs, name, method):
1419 1419 # this causes stale locks to get reaped for more accurate reporting
1420 1420 try:
1421 1421 l = method(False)
1422 1422 except error.LockHeld:
1423 1423 l = None
1424 1424
1425 1425 if l:
1426 1426 l.release()
1427 1427 else:
1428 1428 try:
1429 1429 st = vfs.lstat(name)
1430 1430 age = now - st[stat.ST_MTIME]
1431 1431 user = util.username(st.st_uid)
1432 1432 locker = vfs.readlock(name)
1433 1433 if ":" in locker:
1434 1434 host, pid = locker.split(':')
1435 1435 if host == socket.gethostname():
1436 1436 locker = 'user %s, process %s' % (user, pid)
1437 1437 else:
1438 1438 locker = 'user %s, process %s, host %s' \
1439 1439 % (user, pid, host)
1440 1440 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1441 1441 return 1
1442 1442 except OSError as e:
1443 1443 if e.errno != errno.ENOENT:
1444 1444 raise
1445 1445
1446 1446 ui.write(("%-6s free\n") % (name + ":"))
1447 1447 return 0
1448 1448
1449 1449 held += report(repo.svfs, "lock", repo.lock)
1450 1450 held += report(repo.vfs, "wlock", repo.wlock)
1451 1451
1452 1452 return held
1453 1453
1454 1454 @command('debugmanifestfulltextcache', [
1455 1455 ('', 'clear', False, _('clear the cache')),
1456 1456 ('a', 'add', '', _('add the given manifest node to the cache'),
1457 1457 _('NODE'))
1458 1458 ], '')
1459 1459 def debugmanifestfulltextcache(ui, repo, add=None, **opts):
1460 1460 """show, clear or amend the contents of the manifest fulltext cache"""
1461 1461 with repo.lock():
1462 1462 r = repo.manifestlog.getstorage(b'')
1463 1463 try:
1464 1464 cache = r._fulltextcache
1465 1465 except AttributeError:
1466 1466 ui.warn(_(
1467 1467 "Current revlog implementation doesn't appear to have a "
1468 1468 'manifest fulltext cache\n'))
1469 1469 return
1470 1470
1471 1471 if opts.get(r'clear'):
1472 1472 cache.clear()
1473 1473
1474 1474 if add:
1475 1475 try:
1476 1476 manifest = repo.manifestlog[r.lookup(add)]
1477 1477 except error.LookupError as e:
1478 1478 raise error.Abort(e, hint="Check your manifest node id")
1479 1479 manifest.read() # stores revisision in cache too
1480 1480
1481 1481 if not len(cache):
1482 1482 ui.write(_('Cache empty'))
1483 1483 else:
1484 1484 ui.write(
1485 1485 _('Cache contains %d manifest entries, in order of most to '
1486 1486 'least recent:\n') % (len(cache),))
1487 1487 totalsize = 0
1488 1488 for nodeid in cache:
1489 1489 # Use cache.get to not update the LRU order
1490 1490 data = cache.get(nodeid)
1491 1491 size = len(data)
1492 1492 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
1493 1493 ui.write(_('id: %s, size %s\n') % (
1494 1494 hex(nodeid), util.bytecount(size)))
1495 1495 ondisk = cache._opener.stat('manifestfulltextcache').st_size
1496 1496 ui.write(
1497 1497 _('Total cache data size %s, on-disk %s\n') % (
1498 1498 util.bytecount(totalsize), util.bytecount(ondisk))
1499 1499 )
1500 1500
1501 1501 @command('debugmergestate', [], '')
1502 1502 def debugmergestate(ui, repo, *args):
1503 1503 """print merge state
1504 1504
1505 1505 Use --verbose to print out information about whether v1 or v2 merge state
1506 1506 was chosen."""
1507 1507 def _hashornull(h):
1508 1508 if h == nullhex:
1509 1509 return 'null'
1510 1510 else:
1511 1511 return h
1512 1512
1513 1513 def printrecords(version):
1514 1514 ui.write(('* version %d records\n') % version)
1515 1515 if version == 1:
1516 1516 records = v1records
1517 1517 else:
1518 1518 records = v2records
1519 1519
1520 1520 for rtype, record in records:
1521 1521 # pretty print some record types
1522 1522 if rtype == 'L':
1523 1523 ui.write(('local: %s\n') % record)
1524 1524 elif rtype == 'O':
1525 1525 ui.write(('other: %s\n') % record)
1526 1526 elif rtype == 'm':
1527 1527 driver, mdstate = record.split('\0', 1)
1528 1528 ui.write(('merge driver: %s (state "%s")\n')
1529 1529 % (driver, mdstate))
1530 1530 elif rtype in 'FDC':
1531 1531 r = record.split('\0')
1532 1532 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1533 1533 if version == 1:
1534 1534 onode = 'not stored in v1 format'
1535 1535 flags = r[7]
1536 1536 else:
1537 1537 onode, flags = r[7:9]
1538 1538 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1539 1539 % (f, rtype, state, _hashornull(hash)))
1540 1540 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1541 1541 ui.write((' ancestor path: %s (node %s)\n')
1542 1542 % (afile, _hashornull(anode)))
1543 1543 ui.write((' other path: %s (node %s)\n')
1544 1544 % (ofile, _hashornull(onode)))
1545 1545 elif rtype == 'f':
1546 1546 filename, rawextras = record.split('\0', 1)
1547 1547 extras = rawextras.split('\0')
1548 1548 i = 0
1549 1549 extrastrings = []
1550 1550 while i < len(extras):
1551 1551 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1552 1552 i += 2
1553 1553
1554 1554 ui.write(('file extras: %s (%s)\n')
1555 1555 % (filename, ', '.join(extrastrings)))
1556 1556 elif rtype == 'l':
1557 1557 labels = record.split('\0', 2)
1558 1558 labels = [l for l in labels if len(l) > 0]
1559 1559 ui.write(('labels:\n'))
1560 1560 ui.write((' local: %s\n' % labels[0]))
1561 1561 ui.write((' other: %s\n' % labels[1]))
1562 1562 if len(labels) > 2:
1563 1563 ui.write((' base: %s\n' % labels[2]))
1564 1564 else:
1565 1565 ui.write(('unrecognized entry: %s\t%s\n')
1566 1566 % (rtype, record.replace('\0', '\t')))
1567 1567
1568 1568 # Avoid mergestate.read() since it may raise an exception for unsupported
1569 1569 # merge state records. We shouldn't be doing this, but this is OK since this
1570 1570 # command is pretty low-level.
1571 1571 ms = mergemod.mergestate(repo)
1572 1572
1573 1573 # sort so that reasonable information is on top
1574 1574 v1records = ms._readrecordsv1()
1575 1575 v2records = ms._readrecordsv2()
1576 1576 order = 'LOml'
1577 1577 def key(r):
1578 1578 idx = order.find(r[0])
1579 1579 if idx == -1:
1580 1580 return (1, r[1])
1581 1581 else:
1582 1582 return (0, idx)
1583 1583 v1records.sort(key=key)
1584 1584 v2records.sort(key=key)
1585 1585
1586 1586 if not v1records and not v2records:
1587 1587 ui.write(('no merge state found\n'))
1588 1588 elif not v2records:
1589 1589 ui.note(('no version 2 merge state\n'))
1590 1590 printrecords(1)
1591 1591 elif ms._v1v2match(v1records, v2records):
1592 1592 ui.note(('v1 and v2 states match: using v2\n'))
1593 1593 printrecords(2)
1594 1594 else:
1595 1595 ui.note(('v1 and v2 states mismatch: using v1\n'))
1596 1596 printrecords(1)
1597 1597 if ui.verbose:
1598 1598 printrecords(2)
1599 1599
1600 1600 @command('debugnamecomplete', [], _('NAME...'))
1601 1601 def debugnamecomplete(ui, repo, *args):
1602 1602 '''complete "names" - tags, open branch names, bookmark names'''
1603 1603
1604 1604 names = set()
1605 1605 # since we previously only listed open branches, we will handle that
1606 1606 # specially (after this for loop)
1607 1607 for name, ns in repo.names.iteritems():
1608 1608 if name != 'branches':
1609 1609 names.update(ns.listnames(repo))
1610 1610 names.update(tag for (tag, heads, tip, closed)
1611 1611 in repo.branchmap().iterbranches() if not closed)
1612 1612 completions = set()
1613 1613 if not args:
1614 1614 args = ['']
1615 1615 for a in args:
1616 1616 completions.update(n for n in names if n.startswith(a))
1617 1617 ui.write('\n'.join(sorted(completions)))
1618 1618 ui.write('\n')
1619 1619
1620 1620 @command('debugobsolete',
1621 1621 [('', 'flags', 0, _('markers flag')),
1622 1622 ('', 'record-parents', False,
1623 1623 _('record parent information for the precursor')),
1624 1624 ('r', 'rev', [], _('display markers relevant to REV')),
1625 1625 ('', 'exclusive', False, _('restrict display to markers only '
1626 1626 'relevant to REV')),
1627 1627 ('', 'index', False, _('display index of the marker')),
1628 1628 ('', 'delete', [], _('delete markers specified by indices')),
1629 1629 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1630 1630 _('[OBSOLETED [REPLACEMENT ...]]'))
1631 1631 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1632 1632 """create arbitrary obsolete marker
1633 1633
1634 1634 With no arguments, displays the list of obsolescence markers."""
1635 1635
1636 1636 opts = pycompat.byteskwargs(opts)
1637 1637
1638 1638 def parsenodeid(s):
1639 1639 try:
1640 1640 # We do not use revsingle/revrange functions here to accept
1641 1641 # arbitrary node identifiers, possibly not present in the
1642 1642 # local repository.
1643 1643 n = bin(s)
1644 1644 if len(n) != len(nullid):
1645 1645 raise TypeError()
1646 1646 return n
1647 1647 except TypeError:
1648 1648 raise error.Abort('changeset references must be full hexadecimal '
1649 1649 'node identifiers')
1650 1650
1651 1651 if opts.get('delete'):
1652 1652 indices = []
1653 1653 for v in opts.get('delete'):
1654 1654 try:
1655 1655 indices.append(int(v))
1656 1656 except ValueError:
1657 1657 raise error.Abort(_('invalid index value: %r') % v,
1658 1658 hint=_('use integers for indices'))
1659 1659
1660 1660 if repo.currenttransaction():
1661 1661 raise error.Abort(_('cannot delete obsmarkers in the middle '
1662 1662 'of transaction.'))
1663 1663
1664 1664 with repo.lock():
1665 1665 n = repair.deleteobsmarkers(repo.obsstore, indices)
1666 1666 ui.write(_('deleted %i obsolescence markers\n') % n)
1667 1667
1668 1668 return
1669 1669
1670 1670 if precursor is not None:
1671 1671 if opts['rev']:
1672 1672 raise error.Abort('cannot select revision when creating marker')
1673 1673 metadata = {}
1674 1674 metadata['user'] = encoding.fromlocal(opts['user'] or ui.username())
1675 1675 succs = tuple(parsenodeid(succ) for succ in successors)
1676 1676 l = repo.lock()
1677 1677 try:
1678 1678 tr = repo.transaction('debugobsolete')
1679 1679 try:
1680 1680 date = opts.get('date')
1681 1681 if date:
1682 1682 date = dateutil.parsedate(date)
1683 1683 else:
1684 1684 date = None
1685 1685 prec = parsenodeid(precursor)
1686 1686 parents = None
1687 1687 if opts['record_parents']:
1688 1688 if prec not in repo.unfiltered():
1689 1689 raise error.Abort('cannot used --record-parents on '
1690 1690 'unknown changesets')
1691 1691 parents = repo.unfiltered()[prec].parents()
1692 1692 parents = tuple(p.node() for p in parents)
1693 1693 repo.obsstore.create(tr, prec, succs, opts['flags'],
1694 1694 parents=parents, date=date,
1695 1695 metadata=metadata, ui=ui)
1696 1696 tr.close()
1697 1697 except ValueError as exc:
1698 1698 raise error.Abort(_('bad obsmarker input: %s') %
1699 1699 pycompat.bytestr(exc))
1700 1700 finally:
1701 1701 tr.release()
1702 1702 finally:
1703 1703 l.release()
1704 1704 else:
1705 1705 if opts['rev']:
1706 1706 revs = scmutil.revrange(repo, opts['rev'])
1707 1707 nodes = [repo[r].node() for r in revs]
1708 1708 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1709 1709 exclusive=opts['exclusive']))
1710 1710 markers.sort(key=lambda x: x._data)
1711 1711 else:
1712 1712 markers = obsutil.getmarkers(repo)
1713 1713
1714 1714 markerstoiter = markers
1715 1715 isrelevant = lambda m: True
1716 1716 if opts.get('rev') and opts.get('index'):
1717 1717 markerstoiter = obsutil.getmarkers(repo)
1718 1718 markerset = set(markers)
1719 1719 isrelevant = lambda m: m in markerset
1720 1720
1721 1721 fm = ui.formatter('debugobsolete', opts)
1722 1722 for i, m in enumerate(markerstoiter):
1723 1723 if not isrelevant(m):
1724 1724 # marker can be irrelevant when we're iterating over a set
1725 1725 # of markers (markerstoiter) which is bigger than the set
1726 1726 # of markers we want to display (markers)
1727 1727 # this can happen if both --index and --rev options are
1728 1728 # provided and thus we need to iterate over all of the markers
1729 1729 # to get the correct indices, but only display the ones that
1730 1730 # are relevant to --rev value
1731 1731 continue
1732 1732 fm.startitem()
1733 1733 ind = i if opts.get('index') else None
1734 1734 cmdutil.showmarker(fm, m, index=ind)
1735 1735 fm.end()
1736 1736
1737 1737 @command('debugpathcomplete',
1738 1738 [('f', 'full', None, _('complete an entire path')),
1739 1739 ('n', 'normal', None, _('show only normal files')),
1740 1740 ('a', 'added', None, _('show only added files')),
1741 1741 ('r', 'removed', None, _('show only removed files'))],
1742 1742 _('FILESPEC...'))
1743 1743 def debugpathcomplete(ui, repo, *specs, **opts):
1744 1744 '''complete part or all of a tracked path
1745 1745
1746 1746 This command supports shells that offer path name completion. It
1747 1747 currently completes only files already known to the dirstate.
1748 1748
1749 1749 Completion extends only to the next path segment unless
1750 1750 --full is specified, in which case entire paths are used.'''
1751 1751
1752 1752 def complete(path, acceptable):
1753 1753 dirstate = repo.dirstate
1754 1754 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1755 1755 rootdir = repo.root + pycompat.ossep
1756 1756 if spec != repo.root and not spec.startswith(rootdir):
1757 1757 return [], []
1758 1758 if os.path.isdir(spec):
1759 1759 spec += '/'
1760 1760 spec = spec[len(rootdir):]
1761 1761 fixpaths = pycompat.ossep != '/'
1762 1762 if fixpaths:
1763 1763 spec = spec.replace(pycompat.ossep, '/')
1764 1764 speclen = len(spec)
1765 1765 fullpaths = opts[r'full']
1766 1766 files, dirs = set(), set()
1767 1767 adddir, addfile = dirs.add, files.add
1768 1768 for f, st in dirstate.iteritems():
1769 1769 if f.startswith(spec) and st[0] in acceptable:
1770 1770 if fixpaths:
1771 1771 f = f.replace('/', pycompat.ossep)
1772 1772 if fullpaths:
1773 1773 addfile(f)
1774 1774 continue
1775 1775 s = f.find(pycompat.ossep, speclen)
1776 1776 if s >= 0:
1777 1777 adddir(f[:s])
1778 1778 else:
1779 1779 addfile(f)
1780 1780 return files, dirs
1781 1781
1782 1782 acceptable = ''
1783 1783 if opts[r'normal']:
1784 1784 acceptable += 'nm'
1785 1785 if opts[r'added']:
1786 1786 acceptable += 'a'
1787 1787 if opts[r'removed']:
1788 1788 acceptable += 'r'
1789 1789 cwd = repo.getcwd()
1790 1790 if not specs:
1791 1791 specs = ['.']
1792 1792
1793 1793 files, dirs = set(), set()
1794 1794 for spec in specs:
1795 1795 f, d = complete(spec, acceptable or 'nmar')
1796 1796 files.update(f)
1797 1797 dirs.update(d)
1798 1798 files.update(dirs)
1799 1799 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1800 1800 ui.write('\n')
1801 1801
1802 1802 @command('debugpeer', [], _('PATH'), norepo=True)
1803 1803 def debugpeer(ui, path):
1804 1804 """establish a connection to a peer repository"""
1805 1805 # Always enable peer request logging. Requires --debug to display
1806 1806 # though.
1807 1807 overrides = {
1808 1808 ('devel', 'debug.peer-request'): True,
1809 1809 }
1810 1810
1811 1811 with ui.configoverride(overrides):
1812 1812 peer = hg.peer(ui, {}, path)
1813 1813
1814 1814 local = peer.local() is not None
1815 1815 canpush = peer.canpush()
1816 1816
1817 1817 ui.write(_('url: %s\n') % peer.url())
1818 1818 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1819 1819 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1820 1820
1821 1821 @command('debugpickmergetool',
1822 1822 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1823 1823 ('', 'changedelete', None, _('emulate merging change and delete')),
1824 1824 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1825 1825 _('[PATTERN]...'),
1826 1826 inferrepo=True)
1827 1827 def debugpickmergetool(ui, repo, *pats, **opts):
1828 1828 """examine which merge tool is chosen for specified file
1829 1829
1830 1830 As described in :hg:`help merge-tools`, Mercurial examines
1831 1831 configurations below in this order to decide which merge tool is
1832 1832 chosen for specified file.
1833 1833
1834 1834 1. ``--tool`` option
1835 1835 2. ``HGMERGE`` environment variable
1836 1836 3. configurations in ``merge-patterns`` section
1837 1837 4. configuration of ``ui.merge``
1838 1838 5. configurations in ``merge-tools`` section
1839 1839 6. ``hgmerge`` tool (for historical reason only)
1840 1840 7. default tool for fallback (``:merge`` or ``:prompt``)
1841 1841
1842 1842 This command writes out examination result in the style below::
1843 1843
1844 1844 FILE = MERGETOOL
1845 1845
1846 1846 By default, all files known in the first parent context of the
1847 1847 working directory are examined. Use file patterns and/or -I/-X
1848 1848 options to limit target files. -r/--rev is also useful to examine
1849 1849 files in another context without actual updating to it.
1850 1850
1851 1851 With --debug, this command shows warning messages while matching
1852 1852 against ``merge-patterns`` and so on, too. It is recommended to
1853 1853 use this option with explicit file patterns and/or -I/-X options,
1854 1854 because this option increases amount of output per file according
1855 1855 to configurations in hgrc.
1856 1856
1857 1857 With -v/--verbose, this command shows configurations below at
1858 1858 first (only if specified).
1859 1859
1860 1860 - ``--tool`` option
1861 1861 - ``HGMERGE`` environment variable
1862 1862 - configuration of ``ui.merge``
1863 1863
1864 1864 If merge tool is chosen before matching against
1865 1865 ``merge-patterns``, this command can't show any helpful
1866 1866 information, even with --debug. In such case, information above is
1867 1867 useful to know why a merge tool is chosen.
1868 1868 """
1869 1869 opts = pycompat.byteskwargs(opts)
1870 1870 overrides = {}
1871 1871 if opts['tool']:
1872 1872 overrides[('ui', 'forcemerge')] = opts['tool']
1873 1873 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1874 1874
1875 1875 with ui.configoverride(overrides, 'debugmergepatterns'):
1876 1876 hgmerge = encoding.environ.get("HGMERGE")
1877 1877 if hgmerge is not None:
1878 1878 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1879 1879 uimerge = ui.config("ui", "merge")
1880 1880 if uimerge:
1881 1881 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1882 1882
1883 1883 ctx = scmutil.revsingle(repo, opts.get('rev'))
1884 1884 m = scmutil.match(ctx, pats, opts)
1885 1885 changedelete = opts['changedelete']
1886 1886 for path in ctx.walk(m):
1887 1887 fctx = ctx[path]
1888 1888 try:
1889 1889 if not ui.debugflag:
1890 1890 ui.pushbuffer(error=True)
1891 1891 tool, toolpath = filemerge._picktool(repo, ui, path,
1892 1892 fctx.isbinary(),
1893 1893 'l' in fctx.flags(),
1894 1894 changedelete)
1895 1895 finally:
1896 1896 if not ui.debugflag:
1897 1897 ui.popbuffer()
1898 1898 ui.write(('%s = %s\n') % (path, tool))
1899 1899
1900 1900 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1901 1901 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1902 1902 '''access the pushkey key/value protocol
1903 1903
1904 1904 With two args, list the keys in the given namespace.
1905 1905
1906 1906 With five args, set a key to new if it currently is set to old.
1907 1907 Reports success or failure.
1908 1908 '''
1909 1909
1910 1910 target = hg.peer(ui, {}, repopath)
1911 1911 if keyinfo:
1912 1912 key, old, new = keyinfo
1913 1913 with target.commandexecutor() as e:
1914 1914 r = e.callcommand('pushkey', {
1915 1915 'namespace': namespace,
1916 1916 'key': key,
1917 1917 'old': old,
1918 1918 'new': new,
1919 1919 }).result()
1920 1920
1921 1921 ui.status(pycompat.bytestr(r) + '\n')
1922 1922 return not r
1923 1923 else:
1924 1924 for k, v in sorted(target.listkeys(namespace).iteritems()):
1925 1925 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
1926 1926 stringutil.escapestr(v)))
1927 1927
1928 1928 @command('debugpvec', [], _('A B'))
1929 1929 def debugpvec(ui, repo, a, b=None):
1930 1930 ca = scmutil.revsingle(repo, a)
1931 1931 cb = scmutil.revsingle(repo, b)
1932 1932 pa = pvec.ctxpvec(ca)
1933 1933 pb = pvec.ctxpvec(cb)
1934 1934 if pa == pb:
1935 1935 rel = "="
1936 1936 elif pa > pb:
1937 1937 rel = ">"
1938 1938 elif pa < pb:
1939 1939 rel = "<"
1940 1940 elif pa | pb:
1941 1941 rel = "|"
1942 1942 ui.write(_("a: %s\n") % pa)
1943 1943 ui.write(_("b: %s\n") % pb)
1944 1944 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1945 1945 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1946 1946 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1947 1947 pa.distance(pb), rel))
1948 1948
1949 1949 @command('debugrebuilddirstate|debugrebuildstate',
1950 1950 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1951 1951 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1952 1952 'the working copy parent')),
1953 1953 ],
1954 1954 _('[-r REV]'))
1955 1955 def debugrebuilddirstate(ui, repo, rev, **opts):
1956 1956 """rebuild the dirstate as it would look like for the given revision
1957 1957
1958 1958 If no revision is specified the first current parent will be used.
1959 1959
1960 1960 The dirstate will be set to the files of the given revision.
1961 1961 The actual working directory content or existing dirstate
1962 1962 information such as adds or removes is not considered.
1963 1963
1964 1964 ``minimal`` will only rebuild the dirstate status for files that claim to be
1965 1965 tracked but are not in the parent manifest, or that exist in the parent
1966 1966 manifest but are not in the dirstate. It will not change adds, removes, or
1967 1967 modified files that are in the working copy parent.
1968 1968
1969 1969 One use of this command is to make the next :hg:`status` invocation
1970 1970 check the actual file content.
1971 1971 """
1972 1972 ctx = scmutil.revsingle(repo, rev)
1973 1973 with repo.wlock():
1974 1974 dirstate = repo.dirstate
1975 1975 changedfiles = None
1976 1976 # See command doc for what minimal does.
1977 1977 if opts.get(r'minimal'):
1978 1978 manifestfiles = set(ctx.manifest().keys())
1979 1979 dirstatefiles = set(dirstate)
1980 1980 manifestonly = manifestfiles - dirstatefiles
1981 1981 dsonly = dirstatefiles - manifestfiles
1982 1982 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1983 1983 changedfiles = manifestonly | dsnotadded
1984 1984
1985 1985 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1986 1986
1987 1987 @command('debugrebuildfncache', [], '')
1988 1988 def debugrebuildfncache(ui, repo):
1989 1989 """rebuild the fncache file"""
1990 1990 repair.rebuildfncache(ui, repo)
1991 1991
1992 1992 @command('debugrename',
1993 1993 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1994 1994 _('[-r REV] FILE'))
1995 1995 def debugrename(ui, repo, file1, *pats, **opts):
1996 1996 """dump rename information"""
1997 1997
1998 1998 opts = pycompat.byteskwargs(opts)
1999 1999 ctx = scmutil.revsingle(repo, opts.get('rev'))
2000 2000 m = scmutil.match(ctx, (file1,) + pats, opts)
2001 2001 for abs in ctx.walk(m):
2002 2002 fctx = ctx[abs]
2003 2003 o = fctx.filelog().renamed(fctx.filenode())
2004 2004 rel = m.rel(abs)
2005 2005 if o:
2006 2006 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2007 2007 else:
2008 2008 ui.write(_("%s not renamed\n") % rel)
2009 2009
2010 2010 @command('debugrevlog', cmdutil.debugrevlogopts +
2011 2011 [('d', 'dump', False, _('dump index data'))],
2012 2012 _('-c|-m|FILE'),
2013 2013 optionalrepo=True)
2014 2014 def debugrevlog(ui, repo, file_=None, **opts):
2015 2015 """show data and statistics about a revlog"""
2016 2016 opts = pycompat.byteskwargs(opts)
2017 2017 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2018 2018
2019 2019 if opts.get("dump"):
2020 2020 numrevs = len(r)
2021 2021 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
2022 2022 " rawsize totalsize compression heads chainlen\n"))
2023 2023 ts = 0
2024 2024 heads = set()
2025 2025
2026 2026 for rev in pycompat.xrange(numrevs):
2027 2027 dbase = r.deltaparent(rev)
2028 2028 if dbase == -1:
2029 2029 dbase = rev
2030 2030 cbase = r.chainbase(rev)
2031 2031 clen = r.chainlen(rev)
2032 2032 p1, p2 = r.parentrevs(rev)
2033 2033 rs = r.rawsize(rev)
2034 2034 ts = ts + rs
2035 2035 heads -= set(r.parentrevs(rev))
2036 2036 heads.add(rev)
2037 2037 try:
2038 2038 compression = ts / r.end(rev)
2039 2039 except ZeroDivisionError:
2040 2040 compression = 0
2041 2041 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2042 2042 "%11d %5d %8d\n" %
2043 2043 (rev, p1, p2, r.start(rev), r.end(rev),
2044 2044 r.start(dbase), r.start(cbase),
2045 2045 r.start(p1), r.start(p2),
2046 2046 rs, ts, compression, len(heads), clen))
2047 2047 return 0
2048 2048
2049 2049 v = r.version
2050 2050 format = v & 0xFFFF
2051 2051 flags = []
2052 2052 gdelta = False
2053 2053 if v & revlog.FLAG_INLINE_DATA:
2054 2054 flags.append('inline')
2055 2055 if v & revlog.FLAG_GENERALDELTA:
2056 2056 gdelta = True
2057 2057 flags.append('generaldelta')
2058 2058 if not flags:
2059 2059 flags = ['(none)']
2060 2060
2061 2061 ### tracks merge vs single parent
2062 2062 nummerges = 0
2063 2063
2064 2064 ### tracks ways the "delta" are build
2065 2065 # nodelta
2066 2066 numempty = 0
2067 2067 numemptytext = 0
2068 2068 numemptydelta = 0
2069 2069 # full file content
2070 2070 numfull = 0
2071 2071 # intermediate snapshot against a prior snapshot
2072 2072 numsemi = 0
2073 2073 # snapshot count per depth
2074 2074 numsnapdepth = collections.defaultdict(lambda: 0)
2075 2075 # delta against previous revision
2076 2076 numprev = 0
2077 2077 # delta against first or second parent (not prev)
2078 2078 nump1 = 0
2079 2079 nump2 = 0
2080 2080 # delta against neither prev nor parents
2081 2081 numother = 0
2082 2082 # delta against prev that are also first or second parent
2083 2083 # (details of `numprev`)
2084 2084 nump1prev = 0
2085 2085 nump2prev = 0
2086 2086
2087 2087 # data about delta chain of each revs
2088 2088 chainlengths = []
2089 2089 chainbases = []
2090 2090 chainspans = []
2091 2091
2092 2092 # data about each revision
2093 2093 datasize = [None, 0, 0]
2094 2094 fullsize = [None, 0, 0]
2095 2095 semisize = [None, 0, 0]
2096 2096 # snapshot count per depth
2097 2097 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2098 2098 deltasize = [None, 0, 0]
2099 2099 chunktypecounts = {}
2100 2100 chunktypesizes = {}
2101 2101
2102 2102 def addsize(size, l):
2103 2103 if l[0] is None or size < l[0]:
2104 2104 l[0] = size
2105 2105 if size > l[1]:
2106 2106 l[1] = size
2107 2107 l[2] += size
2108 2108
2109 2109 numrevs = len(r)
2110 2110 for rev in pycompat.xrange(numrevs):
2111 2111 p1, p2 = r.parentrevs(rev)
2112 2112 delta = r.deltaparent(rev)
2113 2113 if format > 0:
2114 2114 addsize(r.rawsize(rev), datasize)
2115 2115 if p2 != nullrev:
2116 2116 nummerges += 1
2117 2117 size = r.length(rev)
2118 2118 if delta == nullrev:
2119 2119 chainlengths.append(0)
2120 2120 chainbases.append(r.start(rev))
2121 2121 chainspans.append(size)
2122 2122 if size == 0:
2123 2123 numempty += 1
2124 2124 numemptytext += 1
2125 2125 else:
2126 2126 numfull += 1
2127 2127 numsnapdepth[0] += 1
2128 2128 addsize(size, fullsize)
2129 2129 addsize(size, snapsizedepth[0])
2130 2130 else:
2131 2131 chainlengths.append(chainlengths[delta] + 1)
2132 2132 baseaddr = chainbases[delta]
2133 2133 revaddr = r.start(rev)
2134 2134 chainbases.append(baseaddr)
2135 2135 chainspans.append((revaddr - baseaddr) + size)
2136 2136 if size == 0:
2137 2137 numempty += 1
2138 2138 numemptydelta += 1
2139 2139 elif r.issnapshot(rev):
2140 2140 addsize(size, semisize)
2141 2141 numsemi += 1
2142 2142 depth = r.snapshotdepth(rev)
2143 2143 numsnapdepth[depth] += 1
2144 2144 addsize(size, snapsizedepth[depth])
2145 2145 else:
2146 2146 addsize(size, deltasize)
2147 2147 if delta == rev - 1:
2148 2148 numprev += 1
2149 2149 if delta == p1:
2150 2150 nump1prev += 1
2151 2151 elif delta == p2:
2152 2152 nump2prev += 1
2153 2153 elif delta == p1:
2154 2154 nump1 += 1
2155 2155 elif delta == p2:
2156 2156 nump2 += 1
2157 2157 elif delta != nullrev:
2158 2158 numother += 1
2159 2159
2160 2160 # Obtain data on the raw chunks in the revlog.
2161 2161 if util.safehasattr(r, '_getsegmentforrevs'):
2162 2162 segment = r._getsegmentforrevs(rev, rev)[1]
2163 2163 else:
2164 2164 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2165 2165 if segment:
2166 2166 chunktype = bytes(segment[0:1])
2167 2167 else:
2168 2168 chunktype = 'empty'
2169 2169
2170 2170 if chunktype not in chunktypecounts:
2171 2171 chunktypecounts[chunktype] = 0
2172 2172 chunktypesizes[chunktype] = 0
2173 2173
2174 2174 chunktypecounts[chunktype] += 1
2175 2175 chunktypesizes[chunktype] += size
2176 2176
2177 2177 # Adjust size min value for empty cases
2178 2178 for size in (datasize, fullsize, semisize, deltasize):
2179 2179 if size[0] is None:
2180 2180 size[0] = 0
2181 2181
2182 2182 numdeltas = numrevs - numfull - numempty - numsemi
2183 2183 numoprev = numprev - nump1prev - nump2prev
2184 2184 totalrawsize = datasize[2]
2185 2185 datasize[2] /= numrevs
2186 2186 fulltotal = fullsize[2]
2187 2187 fullsize[2] /= numfull
2188 2188 semitotal = semisize[2]
2189 2189 snaptotal = {}
2190 2190 if 0 < numsemi:
2191 2191 semisize[2] /= numsemi
2192 2192 for depth in snapsizedepth:
2193 2193 snaptotal[depth] = snapsizedepth[depth][2]
2194 2194 snapsizedepth[depth][2] /= numsnapdepth[depth]
2195 2195
2196 2196 deltatotal = deltasize[2]
2197 2197 if numdeltas > 0:
2198 2198 deltasize[2] /= numdeltas
2199 2199 totalsize = fulltotal + semitotal + deltatotal
2200 2200 avgchainlen = sum(chainlengths) / numrevs
2201 2201 maxchainlen = max(chainlengths)
2202 2202 maxchainspan = max(chainspans)
2203 2203 compratio = 1
2204 2204 if totalsize:
2205 2205 compratio = totalrawsize / totalsize
2206 2206
2207 2207 basedfmtstr = '%%%dd\n'
2208 2208 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2209 2209
2210 2210 def dfmtstr(max):
2211 2211 return basedfmtstr % len(str(max))
2212 2212 def pcfmtstr(max, padding=0):
2213 2213 return basepcfmtstr % (len(str(max)), ' ' * padding)
2214 2214
2215 2215 def pcfmt(value, total):
2216 2216 if total:
2217 2217 return (value, 100 * float(value) / total)
2218 2218 else:
2219 2219 return value, 100.0
2220 2220
2221 2221 ui.write(('format : %d\n') % format)
2222 2222 ui.write(('flags : %s\n') % ', '.join(flags))
2223 2223
2224 2224 ui.write('\n')
2225 2225 fmt = pcfmtstr(totalsize)
2226 2226 fmt2 = dfmtstr(totalsize)
2227 2227 ui.write(('revisions : ') + fmt2 % numrevs)
2228 2228 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2229 2229 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2230 2230 ui.write(('revisions : ') + fmt2 % numrevs)
2231 2231 ui.write((' empty : ') + fmt % pcfmt(numempty, numrevs))
2232 2232 ui.write((' text : ')
2233 2233 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta))
2234 2234 ui.write((' delta : ')
2235 2235 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta))
2236 2236 ui.write((' snapshot : ') + fmt % pcfmt(numfull + numsemi, numrevs))
2237 2237 for depth in sorted(numsnapdepth):
2238 2238 ui.write((' lvl-%-3d : ' % depth)
2239 2239 + fmt % pcfmt(numsnapdepth[depth], numrevs))
2240 2240 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2241 2241 ui.write(('revision size : ') + fmt2 % totalsize)
2242 2242 ui.write((' snapshot : ')
2243 2243 + fmt % pcfmt(fulltotal + semitotal, totalsize))
2244 2244 for depth in sorted(numsnapdepth):
2245 2245 ui.write((' lvl-%-3d : ' % depth)
2246 2246 + fmt % pcfmt(snaptotal[depth], totalsize))
2247 2247 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2248 2248
2249 2249 def fmtchunktype(chunktype):
2250 2250 if chunktype == 'empty':
2251 2251 return ' %s : ' % chunktype
2252 2252 elif chunktype in pycompat.bytestr(string.ascii_letters):
2253 2253 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2254 2254 else:
2255 2255 return ' 0x%s : ' % hex(chunktype)
2256 2256
2257 2257 ui.write('\n')
2258 2258 ui.write(('chunks : ') + fmt2 % numrevs)
2259 2259 for chunktype in sorted(chunktypecounts):
2260 2260 ui.write(fmtchunktype(chunktype))
2261 2261 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2262 2262 ui.write(('chunks size : ') + fmt2 % totalsize)
2263 2263 for chunktype in sorted(chunktypecounts):
2264 2264 ui.write(fmtchunktype(chunktype))
2265 2265 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2266 2266
2267 2267 ui.write('\n')
2268 2268 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2269 2269 ui.write(('avg chain length : ') + fmt % avgchainlen)
2270 2270 ui.write(('max chain length : ') + fmt % maxchainlen)
2271 2271 ui.write(('max chain reach : ') + fmt % maxchainspan)
2272 2272 ui.write(('compression ratio : ') + fmt % compratio)
2273 2273
2274 2274 if format > 0:
2275 2275 ui.write('\n')
2276 2276 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2277 2277 % tuple(datasize))
2278 2278 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2279 2279 % tuple(fullsize))
2280 2280 ui.write(('inter-snapshot size (min/max/avg) : %d / %d / %d\n')
2281 2281 % tuple(semisize))
2282 2282 for depth in sorted(snapsizedepth):
2283 2283 if depth == 0:
2284 2284 continue
2285 2285 ui.write((' level-%-3d (min/max/avg) : %d / %d / %d\n')
2286 2286 % ((depth,) + tuple(snapsizedepth[depth])))
2287 2287 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2288 2288 % tuple(deltasize))
2289 2289
2290 2290 if numdeltas > 0:
2291 2291 ui.write('\n')
2292 2292 fmt = pcfmtstr(numdeltas)
2293 2293 fmt2 = pcfmtstr(numdeltas, 4)
2294 2294 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2295 2295 if numprev > 0:
2296 2296 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2297 2297 numprev))
2298 2298 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2299 2299 numprev))
2300 2300 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2301 2301 numprev))
2302 2302 if gdelta:
2303 2303 ui.write(('deltas against p1 : ')
2304 2304 + fmt % pcfmt(nump1, numdeltas))
2305 2305 ui.write(('deltas against p2 : ')
2306 2306 + fmt % pcfmt(nump2, numdeltas))
2307 2307 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2308 2308 numdeltas))
2309 2309
2310 2310 @command('debugrevlogindex', cmdutil.debugrevlogopts +
2311 2311 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2312 2312 _('[-f FORMAT] -c|-m|FILE'),
2313 2313 optionalrepo=True)
2314 2314 def debugrevlogindex(ui, repo, file_=None, **opts):
2315 2315 """dump the contents of a revlog index"""
2316 2316 opts = pycompat.byteskwargs(opts)
2317 2317 r = cmdutil.openrevlog(repo, 'debugrevlogindex', file_, opts)
2318 2318 format = opts.get('format', 0)
2319 2319 if format not in (0, 1):
2320 2320 raise error.Abort(_("unknown format %d") % format)
2321 2321
2322 2322 if ui.debugflag:
2323 2323 shortfn = hex
2324 2324 else:
2325 2325 shortfn = short
2326 2326
2327 2327 # There might not be anything in r, so have a sane default
2328 2328 idlen = 12
2329 2329 for i in r:
2330 2330 idlen = len(shortfn(r.node(i)))
2331 2331 break
2332 2332
2333 2333 if format == 0:
2334 2334 if ui.verbose:
2335 2335 ui.write((" rev offset length linkrev"
2336 2336 " %s %s p2\n") % ("nodeid".ljust(idlen),
2337 2337 "p1".ljust(idlen)))
2338 2338 else:
2339 2339 ui.write((" rev linkrev %s %s p2\n") % (
2340 2340 "nodeid".ljust(idlen), "p1".ljust(idlen)))
2341 2341 elif format == 1:
2342 2342 if ui.verbose:
2343 2343 ui.write((" rev flag offset length size link p1"
2344 2344 " p2 %s\n") % "nodeid".rjust(idlen))
2345 2345 else:
2346 2346 ui.write((" rev flag size link p1 p2 %s\n") %
2347 2347 "nodeid".rjust(idlen))
2348 2348
2349 2349 for i in r:
2350 2350 node = r.node(i)
2351 2351 if format == 0:
2352 2352 try:
2353 2353 pp = r.parents(node)
2354 2354 except Exception:
2355 2355 pp = [nullid, nullid]
2356 2356 if ui.verbose:
2357 2357 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
2358 2358 i, r.start(i), r.length(i), r.linkrev(i),
2359 2359 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2360 2360 else:
2361 2361 ui.write("% 6d % 7d %s %s %s\n" % (
2362 2362 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
2363 2363 shortfn(pp[1])))
2364 2364 elif format == 1:
2365 2365 pr = r.parentrevs(i)
2366 2366 if ui.verbose:
2367 2367 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
2368 2368 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2369 2369 r.linkrev(i), pr[0], pr[1], shortfn(node)))
2370 2370 else:
2371 2371 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
2372 2372 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
2373 2373 shortfn(node)))
2374 2374
2375 2375 @command('debugrevspec',
2376 2376 [('', 'optimize', None,
2377 2377 _('print parsed tree after optimizing (DEPRECATED)')),
2378 2378 ('', 'show-revs', True, _('print list of result revisions (default)')),
2379 2379 ('s', 'show-set', None, _('print internal representation of result set')),
2380 2380 ('p', 'show-stage', [],
2381 2381 _('print parsed tree at the given stage'), _('NAME')),
2382 2382 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2383 2383 ('', 'verify-optimized', False, _('verify optimized result')),
2384 2384 ],
2385 2385 ('REVSPEC'))
2386 2386 def debugrevspec(ui, repo, expr, **opts):
2387 2387 """parse and apply a revision specification
2388 2388
2389 2389 Use -p/--show-stage option to print the parsed tree at the given stages.
2390 2390 Use -p all to print tree at every stage.
2391 2391
2392 2392 Use --no-show-revs option with -s or -p to print only the set
2393 2393 representation or the parsed tree respectively.
2394 2394
2395 2395 Use --verify-optimized to compare the optimized result with the unoptimized
2396 2396 one. Returns 1 if the optimized result differs.
2397 2397 """
2398 2398 opts = pycompat.byteskwargs(opts)
2399 2399 aliases = ui.configitems('revsetalias')
2400 2400 stages = [
2401 2401 ('parsed', lambda tree: tree),
2402 2402 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2403 2403 ui.warn)),
2404 2404 ('concatenated', revsetlang.foldconcat),
2405 2405 ('analyzed', revsetlang.analyze),
2406 2406 ('optimized', revsetlang.optimize),
2407 2407 ]
2408 2408 if opts['no_optimized']:
2409 2409 stages = stages[:-1]
2410 2410 if opts['verify_optimized'] and opts['no_optimized']:
2411 2411 raise error.Abort(_('cannot use --verify-optimized with '
2412 2412 '--no-optimized'))
2413 2413 stagenames = set(n for n, f in stages)
2414 2414
2415 2415 showalways = set()
2416 2416 showchanged = set()
2417 2417 if ui.verbose and not opts['show_stage']:
2418 2418 # show parsed tree by --verbose (deprecated)
2419 2419 showalways.add('parsed')
2420 2420 showchanged.update(['expanded', 'concatenated'])
2421 2421 if opts['optimize']:
2422 2422 showalways.add('optimized')
2423 2423 if opts['show_stage'] and opts['optimize']:
2424 2424 raise error.Abort(_('cannot use --optimize with --show-stage'))
2425 2425 if opts['show_stage'] == ['all']:
2426 2426 showalways.update(stagenames)
2427 2427 else:
2428 2428 for n in opts['show_stage']:
2429 2429 if n not in stagenames:
2430 2430 raise error.Abort(_('invalid stage name: %s') % n)
2431 2431 showalways.update(opts['show_stage'])
2432 2432
2433 2433 treebystage = {}
2434 2434 printedtree = None
2435 2435 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
2436 2436 for n, f in stages:
2437 2437 treebystage[n] = tree = f(tree)
2438 2438 if n in showalways or (n in showchanged and tree != printedtree):
2439 2439 if opts['show_stage'] or n != 'parsed':
2440 2440 ui.write(("* %s:\n") % n)
2441 2441 ui.write(revsetlang.prettyformat(tree), "\n")
2442 2442 printedtree = tree
2443 2443
2444 2444 if opts['verify_optimized']:
2445 2445 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2446 2446 brevs = revset.makematcher(treebystage['optimized'])(repo)
2447 2447 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2448 2448 ui.write(("* analyzed set:\n"), stringutil.prettyrepr(arevs), "\n")
2449 2449 ui.write(("* optimized set:\n"), stringutil.prettyrepr(brevs), "\n")
2450 2450 arevs = list(arevs)
2451 2451 brevs = list(brevs)
2452 2452 if arevs == brevs:
2453 2453 return 0
2454 2454 ui.write(('--- analyzed\n'), label='diff.file_a')
2455 2455 ui.write(('+++ optimized\n'), label='diff.file_b')
2456 2456 sm = difflib.SequenceMatcher(None, arevs, brevs)
2457 2457 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2458 2458 if tag in ('delete', 'replace'):
2459 2459 for c in arevs[alo:ahi]:
2460 2460 ui.write('-%s\n' % c, label='diff.deleted')
2461 2461 if tag in ('insert', 'replace'):
2462 2462 for c in brevs[blo:bhi]:
2463 2463 ui.write('+%s\n' % c, label='diff.inserted')
2464 2464 if tag == 'equal':
2465 2465 for c in arevs[alo:ahi]:
2466 2466 ui.write(' %s\n' % c)
2467 2467 return 1
2468 2468
2469 2469 func = revset.makematcher(tree)
2470 2470 revs = func(repo)
2471 2471 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2472 2472 ui.write(("* set:\n"), stringutil.prettyrepr(revs), "\n")
2473 2473 if not opts['show_revs']:
2474 2474 return
2475 2475 for c in revs:
2476 2476 ui.write("%d\n" % c)
2477 2477
2478 2478 @command('debugserve', [
2479 2479 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2480 2480 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2481 2481 ('', 'logiofile', '', _('file to log server I/O to')),
2482 2482 ], '')
2483 2483 def debugserve(ui, repo, **opts):
2484 2484 """run a server with advanced settings
2485 2485
2486 2486 This command is similar to :hg:`serve`. It exists partially as a
2487 2487 workaround to the fact that ``hg serve --stdio`` must have specific
2488 2488 arguments for security reasons.
2489 2489 """
2490 2490 opts = pycompat.byteskwargs(opts)
2491 2491
2492 2492 if not opts['sshstdio']:
2493 2493 raise error.Abort(_('only --sshstdio is currently supported'))
2494 2494
2495 2495 logfh = None
2496 2496
2497 2497 if opts['logiofd'] and opts['logiofile']:
2498 2498 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2499 2499
2500 2500 if opts['logiofd']:
2501 2501 # Line buffered because output is line based.
2502 2502 try:
2503 2503 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2504 2504 except OSError as e:
2505 2505 if e.errno != errno.ESPIPE:
2506 2506 raise
2507 2507 # can't seek a pipe, so `ab` mode fails on py3
2508 2508 logfh = os.fdopen(int(opts['logiofd']), r'wb', 1)
2509 2509 elif opts['logiofile']:
2510 2510 logfh = open(opts['logiofile'], 'ab', 1)
2511 2511
2512 2512 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2513 2513 s.serve_forever()
2514 2514
2515 2515 @command('debugsetparents', [], _('REV1 [REV2]'))
2516 2516 def debugsetparents(ui, repo, rev1, rev2=None):
2517 2517 """manually set the parents of the current working directory
2518 2518
2519 2519 This is useful for writing repository conversion tools, but should
2520 2520 be used with care. For example, neither the working directory nor the
2521 2521 dirstate is updated, so file status may be incorrect after running this
2522 2522 command.
2523 2523
2524 2524 Returns 0 on success.
2525 2525 """
2526 2526
2527 2527 node1 = scmutil.revsingle(repo, rev1).node()
2528 2528 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2529 2529
2530 2530 with repo.wlock():
2531 2531 repo.setparents(node1, node2)
2532 2532
2533 2533 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2534 2534 def debugssl(ui, repo, source=None, **opts):
2535 2535 '''test a secure connection to a server
2536 2536
2537 2537 This builds the certificate chain for the server on Windows, installing the
2538 2538 missing intermediates and trusted root via Windows Update if necessary. It
2539 2539 does nothing on other platforms.
2540 2540
2541 2541 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2542 2542 that server is used. See :hg:`help urls` for more information.
2543 2543
2544 2544 If the update succeeds, retry the original operation. Otherwise, the cause
2545 2545 of the SSL error is likely another issue.
2546 2546 '''
2547 2547 if not pycompat.iswindows:
2548 2548 raise error.Abort(_('certificate chain building is only possible on '
2549 2549 'Windows'))
2550 2550
2551 2551 if not source:
2552 2552 if not repo:
2553 2553 raise error.Abort(_("there is no Mercurial repository here, and no "
2554 2554 "server specified"))
2555 2555 source = "default"
2556 2556
2557 2557 source, branches = hg.parseurl(ui.expandpath(source))
2558 2558 url = util.url(source)
2559 2559 addr = None
2560 2560
2561 2561 defaultport = {'https': 443, 'ssh': 22}
2562 2562 if url.scheme in defaultport:
2563 2563 try:
2564 2564 addr = (url.host, int(url.port or defaultport[url.scheme]))
2565 2565 except ValueError:
2566 2566 raise error.Abort(_("malformed port number in URL"))
2567 2567 else:
2568 2568 raise error.Abort(_("only https and ssh connections are supported"))
2569 2569
2570 2570 from . import win32
2571 2571
2572 2572 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2573 2573 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2574 2574
2575 2575 try:
2576 2576 s.connect(addr)
2577 2577 cert = s.getpeercert(True)
2578 2578
2579 2579 ui.status(_('checking the certificate chain for %s\n') % url.host)
2580 2580
2581 2581 complete = win32.checkcertificatechain(cert, build=False)
2582 2582
2583 2583 if not complete:
2584 2584 ui.status(_('certificate chain is incomplete, updating... '))
2585 2585
2586 2586 if not win32.checkcertificatechain(cert):
2587 2587 ui.status(_('failed.\n'))
2588 2588 else:
2589 2589 ui.status(_('done.\n'))
2590 2590 else:
2591 2591 ui.status(_('full certificate chain is available\n'))
2592 2592 finally:
2593 2593 s.close()
2594 2594
2595 2595 @command('debugsub',
2596 2596 [('r', 'rev', '',
2597 2597 _('revision to check'), _('REV'))],
2598 2598 _('[-r REV] [REV]'))
2599 2599 def debugsub(ui, repo, rev=None):
2600 2600 ctx = scmutil.revsingle(repo, rev, None)
2601 2601 for k, v in sorted(ctx.substate.items()):
2602 2602 ui.write(('path %s\n') % k)
2603 2603 ui.write((' source %s\n') % v[0])
2604 2604 ui.write((' revision %s\n') % v[1])
2605 2605
2606 2606 @command('debugsuccessorssets',
2607 2607 [('', 'closest', False, _('return closest successors sets only'))],
2608 2608 _('[REV]'))
2609 2609 def debugsuccessorssets(ui, repo, *revs, **opts):
2610 2610 """show set of successors for revision
2611 2611
2612 2612 A successors set of changeset A is a consistent group of revisions that
2613 2613 succeed A. It contains non-obsolete changesets only unless closests
2614 2614 successors set is set.
2615 2615
2616 2616 In most cases a changeset A has a single successors set containing a single
2617 2617 successor (changeset A replaced by A').
2618 2618
2619 2619 A changeset that is made obsolete with no successors are called "pruned".
2620 2620 Such changesets have no successors sets at all.
2621 2621
2622 2622 A changeset that has been "split" will have a successors set containing
2623 2623 more than one successor.
2624 2624
2625 2625 A changeset that has been rewritten in multiple different ways is called
2626 2626 "divergent". Such changesets have multiple successor sets (each of which
2627 2627 may also be split, i.e. have multiple successors).
2628 2628
2629 2629 Results are displayed as follows::
2630 2630
2631 2631 <rev1>
2632 2632 <successors-1A>
2633 2633 <rev2>
2634 2634 <successors-2A>
2635 2635 <successors-2B1> <successors-2B2> <successors-2B3>
2636 2636
2637 2637 Here rev2 has two possible (i.e. divergent) successors sets. The first
2638 2638 holds one element, whereas the second holds three (i.e. the changeset has
2639 2639 been split).
2640 2640 """
2641 2641 # passed to successorssets caching computation from one call to another
2642 2642 cache = {}
2643 2643 ctx2str = bytes
2644 2644 node2str = short
2645 2645 for rev in scmutil.revrange(repo, revs):
2646 2646 ctx = repo[rev]
2647 2647 ui.write('%s\n'% ctx2str(ctx))
2648 2648 for succsset in obsutil.successorssets(repo, ctx.node(),
2649 2649 closest=opts[r'closest'],
2650 2650 cache=cache):
2651 2651 if succsset:
2652 2652 ui.write(' ')
2653 2653 ui.write(node2str(succsset[0]))
2654 2654 for node in succsset[1:]:
2655 2655 ui.write(' ')
2656 2656 ui.write(node2str(node))
2657 2657 ui.write('\n')
2658 2658
2659 2659 @command('debugtemplate',
2660 2660 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2661 2661 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2662 2662 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2663 2663 optionalrepo=True)
2664 2664 def debugtemplate(ui, repo, tmpl, **opts):
2665 2665 """parse and apply a template
2666 2666
2667 2667 If -r/--rev is given, the template is processed as a log template and
2668 2668 applied to the given changesets. Otherwise, it is processed as a generic
2669 2669 template.
2670 2670
2671 2671 Use --verbose to print the parsed tree.
2672 2672 """
2673 2673 revs = None
2674 2674 if opts[r'rev']:
2675 2675 if repo is None:
2676 2676 raise error.RepoError(_('there is no Mercurial repository here '
2677 2677 '(.hg not found)'))
2678 2678 revs = scmutil.revrange(repo, opts[r'rev'])
2679 2679
2680 2680 props = {}
2681 2681 for d in opts[r'define']:
2682 2682 try:
2683 2683 k, v = (e.strip() for e in d.split('=', 1))
2684 2684 if not k or k == 'ui':
2685 2685 raise ValueError
2686 2686 props[k] = v
2687 2687 except ValueError:
2688 2688 raise error.Abort(_('malformed keyword definition: %s') % d)
2689 2689
2690 2690 if ui.verbose:
2691 2691 aliases = ui.configitems('templatealias')
2692 2692 tree = templater.parse(tmpl)
2693 2693 ui.note(templater.prettyformat(tree), '\n')
2694 2694 newtree = templater.expandaliases(tree, aliases)
2695 2695 if newtree != tree:
2696 2696 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2697 2697
2698 2698 if revs is None:
2699 2699 tres = formatter.templateresources(ui, repo)
2700 2700 t = formatter.maketemplater(ui, tmpl, resources=tres)
2701 2701 if ui.verbose:
2702 2702 kwds, funcs = t.symbolsuseddefault()
2703 2703 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2704 2704 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2705 2705 ui.write(t.renderdefault(props))
2706 2706 else:
2707 2707 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2708 2708 if ui.verbose:
2709 2709 kwds, funcs = displayer.t.symbolsuseddefault()
2710 2710 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2711 2711 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2712 2712 for r in revs:
2713 2713 displayer.show(repo[r], **pycompat.strkwargs(props))
2714 2714 displayer.close()
2715 2715
2716 2716 @command('debuguigetpass', [
2717 2717 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2718 2718 ], _('[-p TEXT]'), norepo=True)
2719 2719 def debuguigetpass(ui, prompt=''):
2720 2720 """show prompt to type password"""
2721 2721 r = ui.getpass(prompt)
2722 2722 ui.write(('respose: %s\n') % r)
2723 2723
2724 2724 @command('debuguiprompt', [
2725 2725 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2726 2726 ], _('[-p TEXT]'), norepo=True)
2727 2727 def debuguiprompt(ui, prompt=''):
2728 2728 """show plain prompt"""
2729 2729 r = ui.prompt(prompt)
2730 2730 ui.write(('response: %s\n') % r)
2731 2731
2732 2732 @command('debugupdatecaches', [])
2733 2733 def debugupdatecaches(ui, repo, *pats, **opts):
2734 2734 """warm all known caches in the repository"""
2735 2735 with repo.wlock(), repo.lock():
2736 2736 repo.updatecaches(full=True)
2737 2737
2738 2738 @command('debugupgraderepo', [
2739 2739 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2740 2740 ('', 'run', False, _('performs an upgrade')),
2741 2741 ])
2742 2742 def debugupgraderepo(ui, repo, run=False, optimize=None):
2743 2743 """upgrade a repository to use different features
2744 2744
2745 2745 If no arguments are specified, the repository is evaluated for upgrade
2746 2746 and a list of problems and potential optimizations is printed.
2747 2747
2748 2748 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2749 2749 can be influenced via additional arguments. More details will be provided
2750 2750 by the command output when run without ``--run``.
2751 2751
2752 2752 During the upgrade, the repository will be locked and no writes will be
2753 2753 allowed.
2754 2754
2755 2755 At the end of the upgrade, the repository may not be readable while new
2756 2756 repository data is swapped in. This window will be as long as it takes to
2757 2757 rename some directories inside the ``.hg`` directory. On most machines, this
2758 2758 should complete almost instantaneously and the chances of a consumer being
2759 2759 unable to access the repository should be low.
2760 2760 """
2761 2761 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2762 2762
2763 2763 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2764 2764 inferrepo=True)
2765 2765 def debugwalk(ui, repo, *pats, **opts):
2766 2766 """show how files match on given patterns"""
2767 2767 opts = pycompat.byteskwargs(opts)
2768 2768 m = scmutil.match(repo[None], pats, opts)
2769 2769 if ui.verbose:
2770 2770 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
2771 2771 items = list(repo[None].walk(m))
2772 2772 if not items:
2773 2773 return
2774 2774 f = lambda fn: fn
2775 2775 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2776 2776 f = lambda fn: util.normpath(fn)
2777 2777 fmt = 'f %%-%ds %%-%ds %%s' % (
2778 2778 max([len(abs) for abs in items]),
2779 2779 max([len(m.rel(abs)) for abs in items]))
2780 2780 for abs in items:
2781 2781 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2782 2782 ui.write("%s\n" % line.rstrip())
2783 2783
2784 2784 @command('debugwhyunstable', [], _('REV'))
2785 2785 def debugwhyunstable(ui, repo, rev):
2786 2786 """explain instabilities of a changeset"""
2787 2787 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
2788 2788 dnodes = ''
2789 2789 if entry.get('divergentnodes'):
2790 2790 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2791 2791 for ctx in entry['divergentnodes']) + ' '
2792 2792 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2793 2793 entry['reason'], entry['node']))
2794 2794
2795 2795 @command('debugwireargs',
2796 2796 [('', 'three', '', 'three'),
2797 2797 ('', 'four', '', 'four'),
2798 2798 ('', 'five', '', 'five'),
2799 2799 ] + cmdutil.remoteopts,
2800 2800 _('REPO [OPTIONS]... [ONE [TWO]]'),
2801 2801 norepo=True)
2802 2802 def debugwireargs(ui, repopath, *vals, **opts):
2803 2803 opts = pycompat.byteskwargs(opts)
2804 2804 repo = hg.peer(ui, opts, repopath)
2805 2805 for opt in cmdutil.remoteopts:
2806 2806 del opts[opt[1]]
2807 2807 args = {}
2808 2808 for k, v in opts.iteritems():
2809 2809 if v:
2810 2810 args[k] = v
2811 2811 args = pycompat.strkwargs(args)
2812 2812 # run twice to check that we don't mess up the stream for the next command
2813 2813 res1 = repo.debugwireargs(*vals, **args)
2814 2814 res2 = repo.debugwireargs(*vals, **args)
2815 2815 ui.write("%s\n" % res1)
2816 2816 if res1 != res2:
2817 2817 ui.warn("%s\n" % res2)
2818 2818
2819 2819 def _parsewirelangblocks(fh):
2820 2820 activeaction = None
2821 2821 blocklines = []
2822 2822
2823 2823 for line in fh:
2824 2824 line = line.rstrip()
2825 2825 if not line:
2826 2826 continue
2827 2827
2828 2828 if line.startswith(b'#'):
2829 2829 continue
2830 2830
2831 2831 if not line.startswith(b' '):
2832 2832 # New block. Flush previous one.
2833 2833 if activeaction:
2834 2834 yield activeaction, blocklines
2835 2835
2836 2836 activeaction = line
2837 2837 blocklines = []
2838 2838 continue
2839 2839
2840 2840 # Else we start with an indent.
2841 2841
2842 2842 if not activeaction:
2843 2843 raise error.Abort(_('indented line outside of block'))
2844 2844
2845 2845 blocklines.append(line)
2846 2846
2847 2847 # Flush last block.
2848 2848 if activeaction:
2849 2849 yield activeaction, blocklines
2850 2850
2851 2851 @command('debugwireproto',
2852 2852 [
2853 2853 ('', 'localssh', False, _('start an SSH server for this repo')),
2854 2854 ('', 'peer', '', _('construct a specific version of the peer')),
2855 2855 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2856 2856 ('', 'nologhandshake', False,
2857 2857 _('do not log I/O related to the peer handshake')),
2858 2858 ] + cmdutil.remoteopts,
2859 2859 _('[PATH]'),
2860 2860 optionalrepo=True)
2861 2861 def debugwireproto(ui, repo, path=None, **opts):
2862 2862 """send wire protocol commands to a server
2863 2863
2864 2864 This command can be used to issue wire protocol commands to remote
2865 2865 peers and to debug the raw data being exchanged.
2866 2866
2867 2867 ``--localssh`` will start an SSH server against the current repository
2868 2868 and connect to that. By default, the connection will perform a handshake
2869 2869 and establish an appropriate peer instance.
2870 2870
2871 2871 ``--peer`` can be used to bypass the handshake protocol and construct a
2872 2872 peer instance using the specified class type. Valid values are ``raw``,
2873 2873 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2874 2874 raw data payloads and don't support higher-level command actions.
2875 2875
2876 2876 ``--noreadstderr`` can be used to disable automatic reading from stderr
2877 2877 of the peer (for SSH connections only). Disabling automatic reading of
2878 2878 stderr is useful for making output more deterministic.
2879 2879
2880 2880 Commands are issued via a mini language which is specified via stdin.
2881 2881 The language consists of individual actions to perform. An action is
2882 2882 defined by a block. A block is defined as a line with no leading
2883 2883 space followed by 0 or more lines with leading space. Blocks are
2884 2884 effectively a high-level command with additional metadata.
2885 2885
2886 2886 Lines beginning with ``#`` are ignored.
2887 2887
2888 2888 The following sections denote available actions.
2889 2889
2890 2890 raw
2891 2891 ---
2892 2892
2893 2893 Send raw data to the server.
2894 2894
2895 2895 The block payload contains the raw data to send as one atomic send
2896 2896 operation. The data may not actually be delivered in a single system
2897 2897 call: it depends on the abilities of the transport being used.
2898 2898
2899 2899 Each line in the block is de-indented and concatenated. Then, that
2900 2900 value is evaluated as a Python b'' literal. This allows the use of
2901 2901 backslash escaping, etc.
2902 2902
2903 2903 raw+
2904 2904 ----
2905 2905
2906 2906 Behaves like ``raw`` except flushes output afterwards.
2907 2907
2908 2908 command <X>
2909 2909 -----------
2910 2910
2911 2911 Send a request to run a named command, whose name follows the ``command``
2912 2912 string.
2913 2913
2914 2914 Arguments to the command are defined as lines in this block. The format of
2915 2915 each line is ``<key> <value>``. e.g.::
2916 2916
2917 2917 command listkeys
2918 2918 namespace bookmarks
2919 2919
2920 2920 If the value begins with ``eval:``, it will be interpreted as a Python
2921 2921 literal expression. Otherwise values are interpreted as Python b'' literals.
2922 2922 This allows sending complex types and encoding special byte sequences via
2923 2923 backslash escaping.
2924 2924
2925 2925 The following arguments have special meaning:
2926 2926
2927 2927 ``PUSHFILE``
2928 2928 When defined, the *push* mechanism of the peer will be used instead
2929 2929 of the static request-response mechanism and the content of the
2930 2930 file specified in the value of this argument will be sent as the
2931 2931 command payload.
2932 2932
2933 2933 This can be used to submit a local bundle file to the remote.
2934 2934
2935 2935 batchbegin
2936 2936 ----------
2937 2937
2938 2938 Instruct the peer to begin a batched send.
2939 2939
2940 2940 All ``command`` blocks are queued for execution until the next
2941 2941 ``batchsubmit`` block.
2942 2942
2943 2943 batchsubmit
2944 2944 -----------
2945 2945
2946 2946 Submit previously queued ``command`` blocks as a batch request.
2947 2947
2948 2948 This action MUST be paired with a ``batchbegin`` action.
2949 2949
2950 2950 httprequest <method> <path>
2951 2951 ---------------------------
2952 2952
2953 2953 (HTTP peer only)
2954 2954
2955 2955 Send an HTTP request to the peer.
2956 2956
2957 2957 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2958 2958
2959 2959 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2960 2960 headers to add to the request. e.g. ``Accept: foo``.
2961 2961
2962 2962 The following arguments are special:
2963 2963
2964 2964 ``BODYFILE``
2965 2965 The content of the file defined as the value to this argument will be
2966 2966 transferred verbatim as the HTTP request body.
2967 2967
2968 2968 ``frame <type> <flags> <payload>``
2969 2969 Send a unified protocol frame as part of the request body.
2970 2970
2971 2971 All frames will be collected and sent as the body to the HTTP
2972 2972 request.
2973 2973
2974 2974 close
2975 2975 -----
2976 2976
2977 2977 Close the connection to the server.
2978 2978
2979 2979 flush
2980 2980 -----
2981 2981
2982 2982 Flush data written to the server.
2983 2983
2984 2984 readavailable
2985 2985 -------------
2986 2986
2987 2987 Close the write end of the connection and read all available data from
2988 2988 the server.
2989 2989
2990 2990 If the connection to the server encompasses multiple pipes, we poll both
2991 2991 pipes and read available data.
2992 2992
2993 2993 readline
2994 2994 --------
2995 2995
2996 2996 Read a line of output from the server. If there are multiple output
2997 2997 pipes, reads only the main pipe.
2998 2998
2999 2999 ereadline
3000 3000 ---------
3001 3001
3002 3002 Like ``readline``, but read from the stderr pipe, if available.
3003 3003
3004 3004 read <X>
3005 3005 --------
3006 3006
3007 3007 ``read()`` N bytes from the server's main output pipe.
3008 3008
3009 3009 eread <X>
3010 3010 ---------
3011 3011
3012 3012 ``read()`` N bytes from the server's stderr pipe, if available.
3013 3013
3014 3014 Specifying Unified Frame-Based Protocol Frames
3015 3015 ----------------------------------------------
3016 3016
3017 3017 It is possible to emit a *Unified Frame-Based Protocol* by using special
3018 3018 syntax.
3019 3019
3020 3020 A frame is composed as a type, flags, and payload. These can be parsed
3021 3021 from a string of the form:
3022 3022
3023 3023 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
3024 3024
3025 3025 ``request-id`` and ``stream-id`` are integers defining the request and
3026 3026 stream identifiers.
3027 3027
3028 3028 ``type`` can be an integer value for the frame type or the string name
3029 3029 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
3030 3030 ``command-name``.
3031 3031
3032 3032 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
3033 3033 components. Each component (and there can be just one) can be an integer
3034 3034 or a flag name for stream flags or frame flags, respectively. Values are
3035 3035 resolved to integers and then bitwise OR'd together.
3036 3036
3037 3037 ``payload`` represents the raw frame payload. If it begins with
3038 3038 ``cbor:``, the following string is evaluated as Python code and the
3039 3039 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
3040 3040 as a Python byte string literal.
3041 3041 """
3042 3042 opts = pycompat.byteskwargs(opts)
3043 3043
3044 3044 if opts['localssh'] and not repo:
3045 3045 raise error.Abort(_('--localssh requires a repository'))
3046 3046
3047 3047 if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'):
3048 3048 raise error.Abort(_('invalid value for --peer'),
3049 3049 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
3050 3050
3051 3051 if path and opts['localssh']:
3052 3052 raise error.Abort(_('cannot specify --localssh with an explicit '
3053 3053 'path'))
3054 3054
3055 3055 if ui.interactive():
3056 3056 ui.write(_('(waiting for commands on stdin)\n'))
3057 3057
3058 3058 blocks = list(_parsewirelangblocks(ui.fin))
3059 3059
3060 3060 proc = None
3061 3061 stdin = None
3062 3062 stdout = None
3063 3063 stderr = None
3064 3064 opener = None
3065 3065
3066 3066 if opts['localssh']:
3067 3067 # We start the SSH server in its own process so there is process
3068 3068 # separation. This prevents a whole class of potential bugs around
3069 3069 # shared state from interfering with server operation.
3070 3070 args = procutil.hgcmd() + [
3071 3071 '-R', repo.root,
3072 3072 'debugserve', '--sshstdio',
3073 3073 ]
3074 3074 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
3075 3075 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
3076 3076 bufsize=0)
3077 3077
3078 3078 stdin = proc.stdin
3079 3079 stdout = proc.stdout
3080 3080 stderr = proc.stderr
3081 3081
3082 3082 # We turn the pipes into observers so we can log I/O.
3083 3083 if ui.verbose or opts['peer'] == 'raw':
3084 3084 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
3085 3085 logdata=True)
3086 3086 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
3087 3087 logdata=True)
3088 3088 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
3089 3089 logdata=True)
3090 3090
3091 3091 # --localssh also implies the peer connection settings.
3092 3092
3093 3093 url = 'ssh://localserver'
3094 3094 autoreadstderr = not opts['noreadstderr']
3095 3095
3096 3096 if opts['peer'] == 'ssh1':
3097 3097 ui.write(_('creating ssh peer for wire protocol version 1\n'))
3098 3098 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
3099 3099 None, autoreadstderr=autoreadstderr)
3100 3100 elif opts['peer'] == 'ssh2':
3101 3101 ui.write(_('creating ssh peer for wire protocol version 2\n'))
3102 3102 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
3103 3103 None, autoreadstderr=autoreadstderr)
3104 3104 elif opts['peer'] == 'raw':
3105 3105 ui.write(_('using raw connection to peer\n'))
3106 3106 peer = None
3107 3107 else:
3108 3108 ui.write(_('creating ssh peer from handshake results\n'))
3109 3109 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
3110 3110 autoreadstderr=autoreadstderr)
3111 3111
3112 3112 elif path:
3113 3113 # We bypass hg.peer() so we can proxy the sockets.
3114 3114 # TODO consider not doing this because we skip
3115 3115 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
3116 3116 u = util.url(path)
3117 3117 if u.scheme != 'http':
3118 3118 raise error.Abort(_('only http:// paths are currently supported'))
3119 3119
3120 3120 url, authinfo = u.authinfo()
3121 3121 openerargs = {
3122 3122 r'useragent': b'Mercurial debugwireproto',
3123 3123 }
3124 3124
3125 3125 # Turn pipes/sockets into observers so we can log I/O.
3126 3126 if ui.verbose:
3127 3127 openerargs.update({
3128 3128 r'loggingfh': ui,
3129 3129 r'loggingname': b's',
3130 3130 r'loggingopts': {
3131 3131 r'logdata': True,
3132 3132 r'logdataapis': False,
3133 3133 },
3134 3134 })
3135 3135
3136 3136 if ui.debugflag:
3137 3137 openerargs[r'loggingopts'][r'logdataapis'] = True
3138 3138
3139 3139 # Don't send default headers when in raw mode. This allows us to
3140 3140 # bypass most of the behavior of our URL handling code so we can
3141 3141 # have near complete control over what's sent on the wire.
3142 3142 if opts['peer'] == 'raw':
3143 3143 openerargs[r'sendaccept'] = False
3144 3144
3145 3145 opener = urlmod.opener(ui, authinfo, **openerargs)
3146 3146
3147 3147 if opts['peer'] == 'http2':
3148 3148 ui.write(_('creating http peer for wire protocol version 2\n'))
3149 3149 # We go through makepeer() because we need an API descriptor for
3150 3150 # the peer instance to be useful.
3151 3151 with ui.configoverride({
3152 3152 ('experimental', 'httppeer.advertise-v2'): True}):
3153 3153 if opts['nologhandshake']:
3154 3154 ui.pushbuffer()
3155 3155
3156 3156 peer = httppeer.makepeer(ui, path, opener=opener)
3157 3157
3158 3158 if opts['nologhandshake']:
3159 3159 ui.popbuffer()
3160 3160
3161 3161 if not isinstance(peer, httppeer.httpv2peer):
3162 3162 raise error.Abort(_('could not instantiate HTTP peer for '
3163 3163 'wire protocol version 2'),
3164 3164 hint=_('the server may not have the feature '
3165 3165 'enabled or is not allowing this '
3166 3166 'client version'))
3167 3167
3168 3168 elif opts['peer'] == 'raw':
3169 3169 ui.write(_('using raw connection to peer\n'))
3170 3170 peer = None
3171 3171 elif opts['peer']:
3172 3172 raise error.Abort(_('--peer %s not supported with HTTP peers') %
3173 3173 opts['peer'])
3174 3174 else:
3175 3175 peer = httppeer.makepeer(ui, path, opener=opener)
3176 3176
3177 3177 # We /could/ populate stdin/stdout with sock.makefile()...
3178 3178 else:
3179 3179 raise error.Abort(_('unsupported connection configuration'))
3180 3180
3181 3181 batchedcommands = None
3182 3182
3183 3183 # Now perform actions based on the parsed wire language instructions.
3184 3184 for action, lines in blocks:
3185 3185 if action in ('raw', 'raw+'):
3186 3186 if not stdin:
3187 3187 raise error.Abort(_('cannot call raw/raw+ on this peer'))
3188 3188
3189 3189 # Concatenate the data together.
3190 3190 data = ''.join(l.lstrip() for l in lines)
3191 3191 data = stringutil.unescapestr(data)
3192 3192 stdin.write(data)
3193 3193
3194 3194 if action == 'raw+':
3195 3195 stdin.flush()
3196 3196 elif action == 'flush':
3197 3197 if not stdin:
3198 3198 raise error.Abort(_('cannot call flush on this peer'))
3199 3199 stdin.flush()
3200 3200 elif action.startswith('command'):
3201 3201 if not peer:
3202 3202 raise error.Abort(_('cannot send commands unless peer instance '
3203 3203 'is available'))
3204 3204
3205 3205 command = action.split(' ', 1)[1]
3206 3206
3207 3207 args = {}
3208 3208 for line in lines:
3209 3209 # We need to allow empty values.
3210 3210 fields = line.lstrip().split(' ', 1)
3211 3211 if len(fields) == 1:
3212 3212 key = fields[0]
3213 3213 value = ''
3214 3214 else:
3215 3215 key, value = fields
3216 3216
3217 3217 if value.startswith('eval:'):
3218 3218 value = stringutil.evalpythonliteral(value[5:])
3219 3219 else:
3220 3220 value = stringutil.unescapestr(value)
3221 3221
3222 3222 args[key] = value
3223 3223
3224 3224 if batchedcommands is not None:
3225 3225 batchedcommands.append((command, args))
3226 3226 continue
3227 3227
3228 3228 ui.status(_('sending %s command\n') % command)
3229 3229
3230 3230 if 'PUSHFILE' in args:
3231 3231 with open(args['PUSHFILE'], r'rb') as fh:
3232 3232 del args['PUSHFILE']
3233 3233 res, output = peer._callpush(command, fh,
3234 3234 **pycompat.strkwargs(args))
3235 3235 ui.status(_('result: %s\n') % stringutil.escapestr(res))
3236 3236 ui.status(_('remote output: %s\n') %
3237 3237 stringutil.escapestr(output))
3238 3238 else:
3239 3239 with peer.commandexecutor() as e:
3240 3240 res = e.callcommand(command, args).result()
3241 3241
3242 3242 if isinstance(res, wireprotov2peer.commandresponse):
3243 val = list(res.cborobjects())
3243 val = res.objects()
3244 3244 ui.status(_('response: %s\n') %
3245 3245 stringutil.pprint(val, bprefix=True, indent=2))
3246 3246 else:
3247 3247 ui.status(_('response: %s\n') %
3248 3248 stringutil.pprint(res, bprefix=True, indent=2))
3249 3249
3250 3250 elif action == 'batchbegin':
3251 3251 if batchedcommands is not None:
3252 3252 raise error.Abort(_('nested batchbegin not allowed'))
3253 3253
3254 3254 batchedcommands = []
3255 3255 elif action == 'batchsubmit':
3256 3256 # There is a batching API we could go through. But it would be
3257 3257 # difficult to normalize requests into function calls. It is easier
3258 3258 # to bypass this layer and normalize to commands + args.
3259 3259 ui.status(_('sending batch with %d sub-commands\n') %
3260 3260 len(batchedcommands))
3261 3261 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
3262 3262 ui.status(_('response #%d: %s\n') %
3263 3263 (i, stringutil.escapestr(chunk)))
3264 3264
3265 3265 batchedcommands = None
3266 3266
3267 3267 elif action.startswith('httprequest '):
3268 3268 if not opener:
3269 3269 raise error.Abort(_('cannot use httprequest without an HTTP '
3270 3270 'peer'))
3271 3271
3272 3272 request = action.split(' ', 2)
3273 3273 if len(request) != 3:
3274 3274 raise error.Abort(_('invalid httprequest: expected format is '
3275 3275 '"httprequest <method> <path>'))
3276 3276
3277 3277 method, httppath = request[1:]
3278 3278 headers = {}
3279 3279 body = None
3280 3280 frames = []
3281 3281 for line in lines:
3282 3282 line = line.lstrip()
3283 3283 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3284 3284 if m:
3285 3285 headers[m.group(1)] = m.group(2)
3286 3286 continue
3287 3287
3288 3288 if line.startswith(b'BODYFILE '):
3289 3289 with open(line.split(b' ', 1), 'rb') as fh:
3290 3290 body = fh.read()
3291 3291 elif line.startswith(b'frame '):
3292 3292 frame = wireprotoframing.makeframefromhumanstring(
3293 3293 line[len(b'frame '):])
3294 3294
3295 3295 frames.append(frame)
3296 3296 else:
3297 3297 raise error.Abort(_('unknown argument to httprequest: %s') %
3298 3298 line)
3299 3299
3300 3300 url = path + httppath
3301 3301
3302 3302 if frames:
3303 3303 body = b''.join(bytes(f) for f in frames)
3304 3304
3305 3305 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3306 3306
3307 3307 # urllib.Request insists on using has_data() as a proxy for
3308 3308 # determining the request method. Override that to use our
3309 3309 # explicitly requested method.
3310 3310 req.get_method = lambda: pycompat.sysstr(method)
3311 3311
3312 3312 try:
3313 3313 res = opener.open(req)
3314 3314 body = res.read()
3315 3315 except util.urlerr.urlerror as e:
3316 3316 # read() method must be called, but only exists in Python 2
3317 3317 getattr(e, 'read', lambda: None)()
3318 3318 continue
3319 3319
3320 3320 if res.headers.get('Content-Type') == 'application/mercurial-cbor':
3321 3321 ui.write(_('cbor> %s\n') %
3322 3322 stringutil.pprint(cborutil.decodeall(body)[0],
3323 3323 bprefix=True,
3324 3324 indent=2))
3325 3325
3326 3326 elif action == 'close':
3327 3327 peer.close()
3328 3328 elif action == 'readavailable':
3329 3329 if not stdout or not stderr:
3330 3330 raise error.Abort(_('readavailable not available on this peer'))
3331 3331
3332 3332 stdin.close()
3333 3333 stdout.read()
3334 3334 stderr.read()
3335 3335
3336 3336 elif action == 'readline':
3337 3337 if not stdout:
3338 3338 raise error.Abort(_('readline not available on this peer'))
3339 3339 stdout.readline()
3340 3340 elif action == 'ereadline':
3341 3341 if not stderr:
3342 3342 raise error.Abort(_('ereadline not available on this peer'))
3343 3343 stderr.readline()
3344 3344 elif action.startswith('read '):
3345 3345 count = int(action.split(' ', 1)[1])
3346 3346 if not stdout:
3347 3347 raise error.Abort(_('read not available on this peer'))
3348 3348 stdout.read(count)
3349 3349 elif action.startswith('eread '):
3350 3350 count = int(action.split(' ', 1)[1])
3351 3351 if not stderr:
3352 3352 raise error.Abort(_('eread not available on this peer'))
3353 3353 stderr.read(count)
3354 3354 else:
3355 3355 raise error.Abort(_('unknown action: %s') % action)
3356 3356
3357 3357 if batchedcommands is not None:
3358 3358 raise error.Abort(_('unclosed "batchbegin" request'))
3359 3359
3360 3360 if peer:
3361 3361 peer.close()
3362 3362
3363 3363 if proc:
3364 3364 proc.kill()
@@ -1,212 +1,293 b''
1 1 # wireprotov2peer.py - client side code for wire protocol version 2
2 2 #
3 3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 import threading
11
10 12 from .i18n import _
11 13 from . import (
12 14 encoding,
13 15 error,
14 util,
15 16 wireprotoframing,
16 17 )
17 18 from .utils import (
18 19 cborutil,
19 20 )
20 21
21 22 def formatrichmessage(atoms):
22 23 """Format an encoded message from the framing protocol."""
23 24
24 25 chunks = []
25 26
26 27 for atom in atoms:
27 28 msg = _(atom[b'msg'])
28 29
29 30 if b'args' in atom:
30 31 msg = msg % tuple(atom[b'args'])
31 32
32 33 chunks.append(msg)
33 34
34 35 return b''.join(chunks)
35 36
36 37 class commandresponse(object):
37 """Represents the response to a command request."""
38 """Represents the response to a command request.
39
40 Instances track the state of the command and hold its results.
41
42 An external entity is required to update the state of the object when
43 events occur.
44 """
38 45
39 46 def __init__(self, requestid, command):
40 47 self.requestid = requestid
41 48 self.command = command
42 49
43 self.b = util.bytesio()
50 # Whether all remote input related to this command has been
51 # received.
52 self._inputcomplete = False
53
54 # We have a lock that is acquired when important object state is
55 # mutated. This is to prevent race conditions between 1 thread
56 # sending us new data and another consuming it.
57 self._lock = threading.RLock()
58
59 # An event is set when state of the object changes. This event
60 # is waited on by the generator emitting objects.
61 self._serviceable = threading.Event()
62
63 self._pendingevents = []
64 self._decoder = cborutil.bufferingdecoder()
65 self._seeninitial = False
66
67 def _oninputcomplete(self):
68 with self._lock:
69 self._inputcomplete = True
70 self._serviceable.set()
71
72 def _onresponsedata(self, data):
73 available, readcount, wanted = self._decoder.decode(data)
74
75 if not available:
76 return
77
78 with self._lock:
79 for o in self._decoder.getavailable():
80 if not self._seeninitial:
81 self._handleinitial(o)
82 continue
83
84 self._pendingevents.append(o)
85
86 self._serviceable.set()
44 87
45 def cborobjects(self):
46 """Obtain decoded CBOR objects from this response."""
47 self.b.seek(0)
88 def _handleinitial(self, o):
89 self._seeninitial = True
90 if o[b'status'] == 'ok':
91 return
92
93 atoms = [{'msg': o[b'error'][b'message']}]
94 if b'args' in o[b'error']:
95 atoms[0]['args'] = o[b'error'][b'args']
96
97 raise error.RepoError(formatrichmessage(atoms))
98
99 def objects(self):
100 """Obtained decoded objects from this response.
101
102 This is a generator of data structures that were decoded from the
103 command response.
104
105 Obtaining the next member of the generator may block due to waiting
106 on external data to become available.
48 107
49 for v in cborutil.decodeall(self.b.getvalue()):
50 yield v
108 If the server encountered an error in the middle of serving the data
109 or if another error occurred, an exception may be raised when
110 advancing the generator.
111 """
112 while True:
113 # TODO this can infinite loop if self._inputcomplete is never
114 # set. We likely want to tie the lifetime of this object/state
115 # to that of the background thread receiving frames and updating
116 # our state.
117 self._serviceable.wait(1.0)
118
119 with self._lock:
120 self._serviceable.clear()
121
122 # Make copies because objects could be mutated during
123 # iteration.
124 stop = self._inputcomplete
125 pending = list(self._pendingevents)
126 self._pendingevents[:] = []
127
128 for o in pending:
129 yield o
130
131 if stop:
132 break
51 133
52 134 class clienthandler(object):
53 135 """Object to handle higher-level client activities.
54 136
55 137 The ``clientreactor`` is used to hold low-level state about the frame-based
56 138 protocol, such as which requests and streams are active. This type is used
57 139 for higher-level operations, such as reading frames from a socket, exposing
58 140 and managing a higher-level primitive for representing command responses,
59 141 etc. This class is what peers should probably use to bridge wire activity
60 142 with the higher-level peer API.
61 143 """
62 144
63 145 def __init__(self, ui, clientreactor):
64 146 self._ui = ui
65 147 self._reactor = clientreactor
66 148 self._requests = {}
67 149 self._futures = {}
68 150 self._responses = {}
69 151
70 152 def callcommand(self, command, args, f):
71 153 """Register a request to call a command.
72 154
73 155 Returns an iterable of frames that should be sent over the wire.
74 156 """
75 157 request, action, meta = self._reactor.callcommand(command, args)
76 158
77 159 if action != 'noop':
78 160 raise error.ProgrammingError('%s not yet supported' % action)
79 161
80 162 rid = request.requestid
81 163 self._requests[rid] = request
82 164 self._futures[rid] = f
165 # TODO we need some kind of lifetime on response instances otherwise
166 # objects() may deadlock.
83 167 self._responses[rid] = commandresponse(rid, command)
84 168
85 169 return iter(())
86 170
87 171 def flushcommands(self):
88 172 """Flush all queued commands.
89 173
90 174 Returns an iterable of frames that should be sent over the wire.
91 175 """
92 176 action, meta = self._reactor.flushcommands()
93 177
94 178 if action != 'sendframes':
95 179 raise error.ProgrammingError('%s not yet supported' % action)
96 180
97 181 return meta['framegen']
98 182
99 183 def readframe(self, fh):
100 184 """Attempt to read and process a frame.
101 185
102 186 Returns None if no frame was read. Presumably this means EOF.
103 187 """
104 188 frame = wireprotoframing.readframe(fh)
105 189 if frame is None:
106 190 # TODO tell reactor?
107 191 return
108 192
109 193 self._ui.note(_('received %r\n') % frame)
110 194 self._processframe(frame)
111 195
112 196 return True
113 197
114 198 def _processframe(self, frame):
115 199 """Process a single read frame."""
116 200
117 201 action, meta = self._reactor.onframerecv(frame)
118 202
119 203 if action == 'error':
120 204 e = error.RepoError(meta['message'])
121 205
206 if frame.requestid in self._responses:
207 self._responses[frame.requestid]._oninputcomplete()
208
122 209 if frame.requestid in self._futures:
123 210 self._futures[frame.requestid].set_exception(e)
211 del self._futures[frame.requestid]
124 212 else:
125 213 raise e
126 214
127 215 return
128 216
129 217 if frame.requestid not in self._requests:
130 218 raise error.ProgrammingError(
131 219 'received frame for unknown request; this is either a bug in '
132 220 'the clientreactor not screening for this or this instance was '
133 221 'never told about this request: %r' % frame)
134 222
135 223 response = self._responses[frame.requestid]
136 224
137 225 if action == 'responsedata':
138 226 # Any failures processing this frame should bubble up to the
139 227 # future tracking the request.
140 228 try:
141 229 self._processresponsedata(frame, meta, response)
142 230 except BaseException as e:
143 231 self._futures[frame.requestid].set_exception(e)
232 del self._futures[frame.requestid]
233 response._oninputcomplete()
144 234 else:
145 235 raise error.ProgrammingError(
146 236 'unhandled action from clientreactor: %s' % action)
147 237
148 238 def _processresponsedata(self, frame, meta, response):
149 # This buffers all data until end of stream is received. This
150 # is bad for performance.
151 # TODO make response data streamable
152 response.b.write(meta['data'])
239 # This can raise. The caller can handle it.
240 response._onresponsedata(meta['data'])
153 241
154 242 if meta['eos']:
155 # If the command has a decoder, resolve the future to the
156 # decoded value. Otherwise resolve to the rich response object.
157 decoder = COMMAND_DECODERS.get(response.command)
158
159 # TODO consider always resolving the overall status map.
160 if decoder:
161 objs = response.cborobjects()
162
163 overall = next(objs)
243 response._oninputcomplete()
244 del self._requests[frame.requestid]
164 245
165 if overall['status'] == 'ok':
166 self._futures[frame.requestid].set_result(decoder(objs))
167 else:
168 atoms = [{'msg': overall['error']['message']}]
169 if 'args' in overall['error']:
170 atoms[0]['args'] = overall['error']['args']
171 e = error.RepoError(formatrichmessage(atoms))
172 self._futures[frame.requestid].set_exception(e)
173 else:
174 self._futures[frame.requestid].set_result(response)
246 # If the command has a decoder, we wait until all input has been
247 # received before resolving the future. Otherwise we resolve the
248 # future immediately.
249 if frame.requestid not in self._futures:
250 return
175 251
176 del self._requests[frame.requestid]
252 if response.command not in COMMAND_DECODERS:
253 self._futures[frame.requestid].set_result(response.objects())
254 del self._futures[frame.requestid]
255 elif response._inputcomplete:
256 decoded = COMMAND_DECODERS[response.command](response.objects())
257 self._futures[frame.requestid].set_result(decoded)
177 258 del self._futures[frame.requestid]
178 259
179 260 def decodebranchmap(objs):
180 261 # Response should be a single CBOR map of branch name to array of nodes.
181 262 bm = next(objs)
182 263
183 264 return {encoding.tolocal(k): v for k, v in bm.items()}
184 265
185 266 def decodeheads(objs):
186 267 # Array of node bytestrings.
187 268 return next(objs)
188 269
189 270 def decodeknown(objs):
190 271 # Bytestring where each byte is a 0 or 1.
191 272 raw = next(objs)
192 273
193 274 return [True if c == '1' else False for c in raw]
194 275
195 276 def decodelistkeys(objs):
196 277 # Map with bytestring keys and values.
197 278 return next(objs)
198 279
199 280 def decodelookup(objs):
200 281 return next(objs)
201 282
202 283 def decodepushkey(objs):
203 284 return next(objs)
204 285
205 286 COMMAND_DECODERS = {
206 287 'branchmap': decodebranchmap,
207 288 'heads': decodeheads,
208 289 'known': decodeknown,
209 290 'listkeys': decodelistkeys,
210 291 'lookup': decodelookup,
211 292 'pushkey': decodepushkey,
212 293 }
@@ -1,622 +1,619 b''
1 1 #require no-chg
2 2
3 3 $ . $TESTDIR/wireprotohelpers.sh
4 4 $ enabledummycommands
5 5
6 6 $ hg init server
7 7 $ cat > server/.hg/hgrc << EOF
8 8 > [experimental]
9 9 > web.apiserver = true
10 10 > EOF
11 11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
12 12 $ cat hg.pid > $DAEMON_PIDS
13 13
14 14 HTTP v2 protocol not enabled by default
15 15
16 16 $ sendhttpraw << EOF
17 17 > httprequest GET api/$HTTPV2
18 18 > user-agent: test
19 19 > EOF
20 20 using raw connection to peer
21 21 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
22 22 s> Accept-Encoding: identity\r\n
23 23 s> user-agent: test\r\n
24 24 s> host: $LOCALIP:$HGPORT\r\n (glob)
25 25 s> \r\n
26 26 s> makefile('rb', None)
27 27 s> HTTP/1.1 404 Not Found\r\n
28 28 s> Server: testing stub value\r\n
29 29 s> Date: $HTTP_DATE$\r\n
30 30 s> Content-Type: text/plain\r\n
31 31 s> Content-Length: 33\r\n
32 32 s> \r\n
33 33 s> API exp-http-v2-0001 not enabled\n
34 34
35 35 Restart server with support for HTTP v2 API
36 36
37 37 $ killdaemons.py
38 38 $ enablehttpv2 server
39 39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
40 40 $ cat hg.pid > $DAEMON_PIDS
41 41
42 42 Request to unknown command yields 404
43 43
44 44 $ sendhttpraw << EOF
45 45 > httprequest POST api/$HTTPV2/ro/badcommand
46 46 > user-agent: test
47 47 > EOF
48 48 using raw connection to peer
49 49 s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
50 50 s> Accept-Encoding: identity\r\n
51 51 s> user-agent: test\r\n
52 52 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 53 s> \r\n
54 54 s> makefile('rb', None)
55 55 s> HTTP/1.1 404 Not Found\r\n
56 56 s> Server: testing stub value\r\n
57 57 s> Date: $HTTP_DATE$\r\n
58 58 s> Content-Type: text/plain\r\n
59 59 s> Content-Length: 42\r\n
60 60 s> \r\n
61 61 s> unknown wire protocol command: badcommand\n
62 62
63 63 GET to read-only command yields a 405
64 64
65 65 $ sendhttpraw << EOF
66 66 > httprequest GET api/$HTTPV2/ro/customreadonly
67 67 > user-agent: test
68 68 > EOF
69 69 using raw connection to peer
70 70 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
71 71 s> Accept-Encoding: identity\r\n
72 72 s> user-agent: test\r\n
73 73 s> host: $LOCALIP:$HGPORT\r\n (glob)
74 74 s> \r\n
75 75 s> makefile('rb', None)
76 76 s> HTTP/1.1 405 Method Not Allowed\r\n
77 77 s> Server: testing stub value\r\n
78 78 s> Date: $HTTP_DATE$\r\n
79 79 s> Allow: POST\r\n
80 80 s> Content-Length: 30\r\n
81 81 s> \r\n
82 82 s> commands require POST requests
83 83
84 84 Missing Accept header results in 406
85 85
86 86 $ sendhttpraw << EOF
87 87 > httprequest POST api/$HTTPV2/ro/customreadonly
88 88 > user-agent: test
89 89 > EOF
90 90 using raw connection to peer
91 91 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
92 92 s> Accept-Encoding: identity\r\n
93 93 s> user-agent: test\r\n
94 94 s> host: $LOCALIP:$HGPORT\r\n (glob)
95 95 s> \r\n
96 96 s> makefile('rb', None)
97 97 s> HTTP/1.1 406 Not Acceptable\r\n
98 98 s> Server: testing stub value\r\n
99 99 s> Date: $HTTP_DATE$\r\n
100 100 s> Content-Type: text/plain\r\n
101 101 s> Content-Length: 85\r\n
102 102 s> \r\n
103 103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
104 104
105 105 Bad Accept header results in 406
106 106
107 107 $ sendhttpraw << EOF
108 108 > httprequest POST api/$HTTPV2/ro/customreadonly
109 109 > accept: invalid
110 110 > user-agent: test
111 111 > EOF
112 112 using raw connection to peer
113 113 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
114 114 s> Accept-Encoding: identity\r\n
115 115 s> accept: invalid\r\n
116 116 s> user-agent: test\r\n
117 117 s> host: $LOCALIP:$HGPORT\r\n (glob)
118 118 s> \r\n
119 119 s> makefile('rb', None)
120 120 s> HTTP/1.1 406 Not Acceptable\r\n
121 121 s> Server: testing stub value\r\n
122 122 s> Date: $HTTP_DATE$\r\n
123 123 s> Content-Type: text/plain\r\n
124 124 s> Content-Length: 85\r\n
125 125 s> \r\n
126 126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
127 127
128 128 Bad Content-Type header results in 415
129 129
130 130 $ sendhttpraw << EOF
131 131 > httprequest POST api/$HTTPV2/ro/customreadonly
132 132 > accept: $MEDIATYPE
133 133 > user-agent: test
134 134 > content-type: badmedia
135 135 > EOF
136 136 using raw connection to peer
137 137 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
138 138 s> Accept-Encoding: identity\r\n
139 139 s> accept: application/mercurial-exp-framing-0005\r\n
140 140 s> content-type: badmedia\r\n
141 141 s> user-agent: test\r\n
142 142 s> host: $LOCALIP:$HGPORT\r\n (glob)
143 143 s> \r\n
144 144 s> makefile('rb', None)
145 145 s> HTTP/1.1 415 Unsupported Media Type\r\n
146 146 s> Server: testing stub value\r\n
147 147 s> Date: $HTTP_DATE$\r\n
148 148 s> Content-Type: text/plain\r\n
149 149 s> Content-Length: 88\r\n
150 150 s> \r\n
151 151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0005\n
152 152
153 153 Request to read-only command works out of the box
154 154
155 155 $ sendhttpraw << EOF
156 156 > httprequest POST api/$HTTPV2/ro/customreadonly
157 157 > accept: $MEDIATYPE
158 158 > content-type: $MEDIATYPE
159 159 > user-agent: test
160 160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
161 161 > EOF
162 162 using raw connection to peer
163 163 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
164 164 s> Accept-Encoding: identity\r\n
165 165 s> *\r\n (glob)
166 166 s> content-type: application/mercurial-exp-framing-0005\r\n
167 167 s> user-agent: test\r\n
168 168 s> content-length: 29\r\n
169 169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 170 s> \r\n
171 171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
172 172 s> makefile('rb', None)
173 173 s> HTTP/1.1 200 OK\r\n
174 174 s> Server: testing stub value\r\n
175 175 s> Date: $HTTP_DATE$\r\n
176 176 s> Content-Type: application/mercurial-exp-framing-0005\r\n
177 177 s> Transfer-Encoding: chunked\r\n
178 178 s> \r\n
179 179 s> 13\r\n
180 180 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
181 181 s> \r\n
182 182 s> 27\r\n
183 183 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
184 184 s> \r\n
185 185 s> 8\r\n
186 186 s> \x00\x00\x00\x01\x00\x02\x002
187 187 s> \r\n
188 188 s> 0\r\n
189 189 s> \r\n
190 190
191 191 $ sendhttpv2peer << EOF
192 192 > command customreadonly
193 193 > EOF
194 194 creating http peer for wire protocol version 2
195 195 sending customreadonly command
196 196 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
197 197 s> Accept-Encoding: identity\r\n
198 198 s> accept: application/mercurial-exp-framing-0005\r\n
199 199 s> content-type: application/mercurial-exp-framing-0005\r\n
200 200 s> content-length: 29\r\n
201 201 s> host: $LOCALIP:$HGPORT\r\n (glob)
202 202 s> user-agent: Mercurial debugwireproto\r\n
203 203 s> \r\n
204 204 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
205 205 s> makefile('rb', None)
206 206 s> HTTP/1.1 200 OK\r\n
207 207 s> Server: testing stub value\r\n
208 208 s> Date: $HTTP_DATE$\r\n
209 209 s> Content-Type: application/mercurial-exp-framing-0005\r\n
210 210 s> Transfer-Encoding: chunked\r\n
211 211 s> \r\n
212 212 s> 13\r\n
213 213 s> \x0b\x00\x00\x01\x00\x02\x011
214 214 s> \xa1FstatusBok
215 215 s> \r\n
216 216 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
217 217 s> 27\r\n
218 218 s> \x1f\x00\x00\x01\x00\x02\x001
219 219 s> X\x1dcustomreadonly bytes response
220 220 s> \r\n
221 221 received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
222 222 s> 8\r\n
223 223 s> \x00\x00\x00\x01\x00\x02\x002
224 224 s> \r\n
225 225 s> 0\r\n
226 226 s> \r\n
227 227 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
228 response: [
229 {
230 b'status': b'ok'
231 },
228 response: gen[
232 229 b'customreadonly bytes response'
233 230 ]
234 231
235 232 Request to read-write command fails because server is read-only by default
236 233
237 234 GET to read-write request yields 405
238 235
239 236 $ sendhttpraw << EOF
240 237 > httprequest GET api/$HTTPV2/rw/customreadonly
241 238 > user-agent: test
242 239 > EOF
243 240 using raw connection to peer
244 241 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
245 242 s> Accept-Encoding: identity\r\n
246 243 s> user-agent: test\r\n
247 244 s> host: $LOCALIP:$HGPORT\r\n (glob)
248 245 s> \r\n
249 246 s> makefile('rb', None)
250 247 s> HTTP/1.1 405 Method Not Allowed\r\n
251 248 s> Server: testing stub value\r\n
252 249 s> Date: $HTTP_DATE$\r\n
253 250 s> Allow: POST\r\n
254 251 s> Content-Length: 30\r\n
255 252 s> \r\n
256 253 s> commands require POST requests
257 254
258 255 Even for unknown commands
259 256
260 257 $ sendhttpraw << EOF
261 258 > httprequest GET api/$HTTPV2/rw/badcommand
262 259 > user-agent: test
263 260 > EOF
264 261 using raw connection to peer
265 262 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
266 263 s> Accept-Encoding: identity\r\n
267 264 s> user-agent: test\r\n
268 265 s> host: $LOCALIP:$HGPORT\r\n (glob)
269 266 s> \r\n
270 267 s> makefile('rb', None)
271 268 s> HTTP/1.1 405 Method Not Allowed\r\n
272 269 s> Server: testing stub value\r\n
273 270 s> Date: $HTTP_DATE$\r\n
274 271 s> Allow: POST\r\n
275 272 s> Content-Length: 30\r\n
276 273 s> \r\n
277 274 s> commands require POST requests
278 275
279 276 SSL required by default
280 277
281 278 $ sendhttpraw << EOF
282 279 > httprequest POST api/$HTTPV2/rw/customreadonly
283 280 > user-agent: test
284 281 > EOF
285 282 using raw connection to peer
286 283 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
287 284 s> Accept-Encoding: identity\r\n
288 285 s> user-agent: test\r\n
289 286 s> host: $LOCALIP:$HGPORT\r\n (glob)
290 287 s> \r\n
291 288 s> makefile('rb', None)
292 289 s> HTTP/1.1 403 ssl required\r\n
293 290 s> Server: testing stub value\r\n
294 291 s> Date: $HTTP_DATE$\r\n
295 292 s> Content-Length: 17\r\n
296 293 s> \r\n
297 294 s> permission denied
298 295
299 296 Restart server to allow non-ssl read-write operations
300 297
301 298 $ killdaemons.py
302 299 $ cat > server/.hg/hgrc << EOF
303 300 > [experimental]
304 301 > web.apiserver = true
305 302 > web.api.http-v2 = true
306 303 > [web]
307 304 > push_ssl = false
308 305 > allow-push = *
309 306 > EOF
310 307
311 308 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
312 309 $ cat hg.pid > $DAEMON_PIDS
313 310
314 311 Authorized request for valid read-write command works
315 312
316 313 $ sendhttpraw << EOF
317 314 > httprequest POST api/$HTTPV2/rw/customreadonly
318 315 > user-agent: test
319 316 > accept: $MEDIATYPE
320 317 > content-type: $MEDIATYPE
321 318 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
322 319 > EOF
323 320 using raw connection to peer
324 321 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
325 322 s> Accept-Encoding: identity\r\n
326 323 s> accept: application/mercurial-exp-framing-0005\r\n
327 324 s> content-type: application/mercurial-exp-framing-0005\r\n
328 325 s> user-agent: test\r\n
329 326 s> content-length: 29\r\n
330 327 s> host: $LOCALIP:$HGPORT\r\n (glob)
331 328 s> \r\n
332 329 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
333 330 s> makefile('rb', None)
334 331 s> HTTP/1.1 200 OK\r\n
335 332 s> Server: testing stub value\r\n
336 333 s> Date: $HTTP_DATE$\r\n
337 334 s> Content-Type: application/mercurial-exp-framing-0005\r\n
338 335 s> Transfer-Encoding: chunked\r\n
339 336 s> \r\n
340 337 s> 13\r\n
341 338 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
342 339 s> \r\n
343 340 s> 27\r\n
344 341 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
345 342 s> \r\n
346 343 s> 8\r\n
347 344 s> \x00\x00\x00\x01\x00\x02\x002
348 345 s> \r\n
349 346 s> 0\r\n
350 347 s> \r\n
351 348
352 349 Authorized request for unknown command is rejected
353 350
354 351 $ sendhttpraw << EOF
355 352 > httprequest POST api/$HTTPV2/rw/badcommand
356 353 > user-agent: test
357 354 > accept: $MEDIATYPE
358 355 > EOF
359 356 using raw connection to peer
360 357 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
361 358 s> Accept-Encoding: identity\r\n
362 359 s> accept: application/mercurial-exp-framing-0005\r\n
363 360 s> user-agent: test\r\n
364 361 s> host: $LOCALIP:$HGPORT\r\n (glob)
365 362 s> \r\n
366 363 s> makefile('rb', None)
367 364 s> HTTP/1.1 404 Not Found\r\n
368 365 s> Server: testing stub value\r\n
369 366 s> Date: $HTTP_DATE$\r\n
370 367 s> Content-Type: text/plain\r\n
371 368 s> Content-Length: 42\r\n
372 369 s> \r\n
373 370 s> unknown wire protocol command: badcommand\n
374 371
375 372 debugreflect isn't enabled by default
376 373
377 374 $ sendhttpraw << EOF
378 375 > httprequest POST api/$HTTPV2/ro/debugreflect
379 376 > user-agent: test
380 377 > EOF
381 378 using raw connection to peer
382 379 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
383 380 s> Accept-Encoding: identity\r\n
384 381 s> user-agent: test\r\n
385 382 s> host: $LOCALIP:$HGPORT\r\n (glob)
386 383 s> \r\n
387 384 s> makefile('rb', None)
388 385 s> HTTP/1.1 404 Not Found\r\n
389 386 s> Server: testing stub value\r\n
390 387 s> Date: $HTTP_DATE$\r\n
391 388 s> Content-Type: text/plain\r\n
392 389 s> Content-Length: 34\r\n
393 390 s> \r\n
394 391 s> debugreflect service not available
395 392
396 393 Restart server to get debugreflect endpoint
397 394
398 395 $ killdaemons.py
399 396 $ cat > server/.hg/hgrc << EOF
400 397 > [experimental]
401 398 > web.apiserver = true
402 399 > web.api.debugreflect = true
403 400 > web.api.http-v2 = true
404 401 > [web]
405 402 > push_ssl = false
406 403 > allow-push = *
407 404 > EOF
408 405
409 406 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
410 407 $ cat hg.pid > $DAEMON_PIDS
411 408
412 409 Command frames can be reflected via debugreflect
413 410
414 411 $ sendhttpraw << EOF
415 412 > httprequest POST api/$HTTPV2/ro/debugreflect
416 413 > accept: $MEDIATYPE
417 414 > content-type: $MEDIATYPE
418 415 > user-agent: test
419 416 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
420 417 > EOF
421 418 using raw connection to peer
422 419 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
423 420 s> Accept-Encoding: identity\r\n
424 421 s> accept: application/mercurial-exp-framing-0005\r\n
425 422 s> content-type: application/mercurial-exp-framing-0005\r\n
426 423 s> user-agent: test\r\n
427 424 s> content-length: 47\r\n
428 425 s> host: $LOCALIP:$HGPORT\r\n (glob)
429 426 s> \r\n
430 427 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
431 428 s> makefile('rb', None)
432 429 s> HTTP/1.1 200 OK\r\n
433 430 s> Server: testing stub value\r\n
434 431 s> Date: $HTTP_DATE$\r\n
435 432 s> Content-Type: text/plain\r\n
436 433 s> Content-Length: 205\r\n
437 434 s> \r\n
438 435 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
439 436 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
440 437 s> received: <no frame>\n
441 438 s> {"action": "noop"}
442 439
443 440 Multiple requests to regular command URL are not allowed
444 441
445 442 $ sendhttpraw << EOF
446 443 > httprequest POST api/$HTTPV2/ro/customreadonly
447 444 > accept: $MEDIATYPE
448 445 > content-type: $MEDIATYPE
449 446 > user-agent: test
450 447 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
451 448 > EOF
452 449 using raw connection to peer
453 450 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
454 451 s> Accept-Encoding: identity\r\n
455 452 s> accept: application/mercurial-exp-framing-0005\r\n
456 453 s> content-type: application/mercurial-exp-framing-0005\r\n
457 454 s> user-agent: test\r\n
458 455 s> content-length: 29\r\n
459 456 s> host: $LOCALIP:$HGPORT\r\n (glob)
460 457 s> \r\n
461 458 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
462 459 s> makefile('rb', None)
463 460 s> HTTP/1.1 200 OK\r\n
464 461 s> Server: testing stub value\r\n
465 462 s> Date: $HTTP_DATE$\r\n
466 463 s> Content-Type: application/mercurial-exp-framing-0005\r\n
467 464 s> Transfer-Encoding: chunked\r\n
468 465 s> \r\n
469 466 s> 13\r\n
470 467 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
471 468 s> \r\n
472 469 s> 27\r\n
473 470 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
474 471 s> \r\n
475 472 s> 8\r\n
476 473 s> \x00\x00\x00\x01\x00\x02\x002
477 474 s> \r\n
478 475 s> 0\r\n
479 476 s> \r\n
480 477
481 478 Multiple requests to "multirequest" URL are allowed
482 479
483 480 $ sendhttpraw << EOF
484 481 > httprequest POST api/$HTTPV2/ro/multirequest
485 482 > accept: $MEDIATYPE
486 483 > content-type: $MEDIATYPE
487 484 > user-agent: test
488 485 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
489 486 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
490 487 > EOF
491 488 using raw connection to peer
492 489 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
493 490 s> Accept-Encoding: identity\r\n
494 491 s> *\r\n (glob)
495 492 s> *\r\n (glob)
496 493 s> user-agent: test\r\n
497 494 s> content-length: 58\r\n
498 495 s> host: $LOCALIP:$HGPORT\r\n (glob)
499 496 s> \r\n
500 497 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
501 498 s> makefile('rb', None)
502 499 s> HTTP/1.1 200 OK\r\n
503 500 s> Server: testing stub value\r\n
504 501 s> Date: $HTTP_DATE$\r\n
505 502 s> Content-Type: application/mercurial-exp-framing-0005\r\n
506 503 s> Transfer-Encoding: chunked\r\n
507 504 s> \r\n
508 505 s> 13\r\n
509 506 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
510 507 s> \r\n
511 508 s> 27\r\n
512 509 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
513 510 s> \r\n
514 511 s> 8\r\n
515 512 s> \x00\x00\x00\x01\x00\x02\x002
516 513 s> \r\n
517 514 s> 13\r\n
518 515 s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
519 516 s> \r\n
520 517 s> 27\r\n
521 518 s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
522 519 s> \r\n
523 520 s> 8\r\n
524 521 s> \x00\x00\x00\x03\x00\x02\x002
525 522 s> \r\n
526 523 s> 0\r\n
527 524 s> \r\n
528 525
529 526 Interleaved requests to "multirequest" are processed
530 527
531 528 $ sendhttpraw << EOF
532 529 > httprequest POST api/$HTTPV2/ro/multirequest
533 530 > accept: $MEDIATYPE
534 531 > content-type: $MEDIATYPE
535 532 > user-agent: test
536 533 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
537 534 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
538 535 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
539 536 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
540 537 > EOF
541 538 using raw connection to peer
542 539 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
543 540 s> Accept-Encoding: identity\r\n
544 541 s> accept: application/mercurial-exp-framing-0005\r\n
545 542 s> content-type: application/mercurial-exp-framing-0005\r\n
546 543 s> user-agent: test\r\n
547 544 s> content-length: 115\r\n
548 545 s> host: $LOCALIP:$HGPORT\r\n (glob)
549 546 s> \r\n
550 547 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
551 548 s> makefile('rb', None)
552 549 s> HTTP/1.1 200 OK\r\n
553 550 s> Server: testing stub value\r\n
554 551 s> Date: $HTTP_DATE$\r\n
555 552 s> Content-Type: application/mercurial-exp-framing-0005\r\n
556 553 s> Transfer-Encoding: chunked\r\n
557 554 s> \r\n
558 555 s> 13\r\n
559 556 s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
560 557 s> \r\n
561 558 s> 28\r\n
562 559 s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
563 560 s> \r\n
564 561 s> 8\r\n
565 562 s> \x00\x00\x00\x03\x00\x02\x002
566 563 s> \r\n
567 564 s> 13\r\n
568 565 s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
569 566 s> \r\n
570 567 s> 9\r\n
571 568 s> \x01\x00\x00\x01\x00\x02\x001\xa0
572 569 s> \r\n
573 570 s> 8\r\n
574 571 s> \x00\x00\x00\x01\x00\x02\x002
575 572 s> \r\n
576 573 s> 0\r\n
577 574 s> \r\n
578 575
579 576 Restart server to disable read-write access
580 577
581 578 $ killdaemons.py
582 579 $ cat > server/.hg/hgrc << EOF
583 580 > [experimental]
584 581 > web.apiserver = true
585 582 > web.api.debugreflect = true
586 583 > web.api.http-v2 = true
587 584 > [web]
588 585 > push_ssl = false
589 586 > EOF
590 587
591 588 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
592 589 $ cat hg.pid > $DAEMON_PIDS
593 590
594 591 Attempting to run a read-write command via multirequest on read-only URL is not allowed
595 592
596 593 $ sendhttpraw << EOF
597 594 > httprequest POST api/$HTTPV2/ro/multirequest
598 595 > accept: $MEDIATYPE
599 596 > content-type: $MEDIATYPE
600 597 > user-agent: test
601 598 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
602 599 > EOF
603 600 using raw connection to peer
604 601 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
605 602 s> Accept-Encoding: identity\r\n
606 603 s> accept: application/mercurial-exp-framing-0005\r\n
607 604 s> content-type: application/mercurial-exp-framing-0005\r\n
608 605 s> user-agent: test\r\n
609 606 s> content-length: 22\r\n
610 607 s> host: $LOCALIP:$HGPORT\r\n (glob)
611 608 s> \r\n
612 609 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
613 610 s> makefile('rb', None)
614 611 s> HTTP/1.1 403 Forbidden\r\n
615 612 s> Server: testing stub value\r\n
616 613 s> Date: $HTTP_DATE$\r\n
617 614 s> Content-Type: text/plain\r\n
618 615 s> Content-Length: 52\r\n
619 616 s> \r\n
620 617 s> insufficient permissions to execute command: pushkey
621 618
622 619 $ cat error.log
@@ -1,431 +1,428 b''
1 1 #require no-chg
2 2
3 3 $ . $TESTDIR/wireprotohelpers.sh
4 4
5 5 $ hg init server
6 6
7 7 zstd isn't present in plain builds. Make tests easier by removing
8 8 zstd from the equation.
9 9
10 10 $ cat >> server/.hg/hgrc << EOF
11 11 > [server]
12 12 > compressionengines = zlib
13 13 > EOF
14 14
15 15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
16 16 $ cat hg.pid > $DAEMON_PIDS
17 17
18 18 A normal capabilities request is serviced for version 1
19 19
20 20 $ sendhttpraw << EOF
21 21 > httprequest GET ?cmd=capabilities
22 22 > user-agent: test
23 23 > EOF
24 24 using raw connection to peer
25 25 s> GET /?cmd=capabilities HTTP/1.1\r\n
26 26 s> Accept-Encoding: identity\r\n
27 27 s> user-agent: test\r\n
28 28 s> host: $LOCALIP:$HGPORT\r\n (glob)
29 29 s> \r\n
30 30 s> makefile('rb', None)
31 31 s> HTTP/1.1 200 Script output follows\r\n
32 32 s> Server: testing stub value\r\n
33 33 s> Date: $HTTP_DATE$\r\n
34 34 s> Content-Type: application/mercurial-0.1\r\n
35 35 s> Content-Length: *\r\n (glob)
36 36 s> \r\n
37 37 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
38 38
39 39 A proper request without the API server enabled returns the legacy response
40 40
41 41 $ sendhttpraw << EOF
42 42 > httprequest GET ?cmd=capabilities
43 43 > user-agent: test
44 44 > x-hgupgrade-1: foo
45 45 > x-hgproto-1: cbor
46 46 > EOF
47 47 using raw connection to peer
48 48 s> GET /?cmd=capabilities HTTP/1.1\r\n
49 49 s> Accept-Encoding: identity\r\n
50 50 s> user-agent: test\r\n
51 51 s> x-hgproto-1: cbor\r\n
52 52 s> x-hgupgrade-1: foo\r\n
53 53 s> host: $LOCALIP:$HGPORT\r\n (glob)
54 54 s> \r\n
55 55 s> makefile('rb', None)
56 56 s> HTTP/1.1 200 Script output follows\r\n
57 57 s> Server: testing stub value\r\n
58 58 s> Date: $HTTP_DATE$\r\n
59 59 s> Content-Type: application/mercurial-0.1\r\n
60 60 s> Content-Length: *\r\n (glob)
61 61 s> \r\n
62 62 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
63 63
64 64 Restart with just API server enabled. This enables serving the new format.
65 65
66 66 $ killdaemons.py
67 67 $ cat error.log
68 68
69 69 $ cat >> server/.hg/hgrc << EOF
70 70 > [experimental]
71 71 > web.apiserver = true
72 72 > EOF
73 73
74 74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
75 75 $ cat hg.pid > $DAEMON_PIDS
76 76
77 77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
78 78
79 79 $ sendhttpraw << EOF
80 80 > httprequest GET ?cmd=capabilities
81 81 > user-agent: test
82 82 > x-hgupgrade-1: foo bar
83 83 > EOF
84 84 using raw connection to peer
85 85 s> GET /?cmd=capabilities HTTP/1.1\r\n
86 86 s> Accept-Encoding: identity\r\n
87 87 s> user-agent: test\r\n
88 88 s> x-hgupgrade-1: foo bar\r\n
89 89 s> host: $LOCALIP:$HGPORT\r\n (glob)
90 90 s> \r\n
91 91 s> makefile('rb', None)
92 92 s> HTTP/1.1 200 Script output follows\r\n
93 93 s> Server: testing stub value\r\n
94 94 s> Date: $HTTP_DATE$\r\n
95 95 s> Content-Type: application/mercurial-0.1\r\n
96 96 s> Content-Length: *\r\n (glob)
97 97 s> \r\n
98 98 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
99 99
100 100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
101 101
102 102 $ sendhttpraw << EOF
103 103 > httprequest GET ?cmd=capabilities
104 104 > user-agent: test
105 105 > x-hgupgrade-1: foo bar
106 106 > x-hgproto-1: some value
107 107 > EOF
108 108 using raw connection to peer
109 109 s> GET /?cmd=capabilities HTTP/1.1\r\n
110 110 s> Accept-Encoding: identity\r\n
111 111 s> user-agent: test\r\n
112 112 s> x-hgproto-1: some value\r\n
113 113 s> x-hgupgrade-1: foo bar\r\n
114 114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 115 s> \r\n
116 116 s> makefile('rb', None)
117 117 s> HTTP/1.1 200 Script output follows\r\n
118 118 s> Server: testing stub value\r\n
119 119 s> Date: $HTTP_DATE$\r\n
120 120 s> Content-Type: application/mercurial-0.1\r\n
121 121 s> Content-Length: *\r\n (glob)
122 122 s> \r\n
123 123 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
124 124
125 125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
126 126
127 127 $ sendhttpraw << EOF
128 128 > httprequest GET ?cmd=capabilities
129 129 > user-agent: test
130 130 > x-hgupgrade-1: foo bar
131 131 > x-hgproto-1: cbor
132 132 > EOF
133 133 using raw connection to peer
134 134 s> GET /?cmd=capabilities HTTP/1.1\r\n
135 135 s> Accept-Encoding: identity\r\n
136 136 s> user-agent: test\r\n
137 137 s> x-hgproto-1: cbor\r\n
138 138 s> x-hgupgrade-1: foo bar\r\n
139 139 s> host: $LOCALIP:$HGPORT\r\n (glob)
140 140 s> \r\n
141 141 s> makefile('rb', None)
142 142 s> HTTP/1.1 200 OK\r\n
143 143 s> Server: testing stub value\r\n
144 144 s> Date: $HTTP_DATE$\r\n
145 145 s> Content-Type: application/mercurial-cbor\r\n
146 146 s> Content-Length: *\r\n (glob)
147 147 s> \r\n
148 148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
149 149 cbor> {
150 150 b'apibase': b'api/',
151 151 b'apis': {},
152 152 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
153 153 }
154 154
155 155 Restart server to enable HTTPv2
156 156
157 157 $ killdaemons.py
158 158 $ enablehttpv2 server
159 159 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
160 160 $ cat hg.pid > $DAEMON_PIDS
161 161
162 162 Only requested API services are returned
163 163
164 164 $ sendhttpraw << EOF
165 165 > httprequest GET ?cmd=capabilities
166 166 > user-agent: test
167 167 > x-hgupgrade-1: foo bar
168 168 > x-hgproto-1: cbor
169 169 > EOF
170 170 using raw connection to peer
171 171 s> GET /?cmd=capabilities HTTP/1.1\r\n
172 172 s> Accept-Encoding: identity\r\n
173 173 s> user-agent: test\r\n
174 174 s> x-hgproto-1: cbor\r\n
175 175 s> x-hgupgrade-1: foo bar\r\n
176 176 s> host: $LOCALIP:$HGPORT\r\n (glob)
177 177 s> \r\n
178 178 s> makefile('rb', None)
179 179 s> HTTP/1.1 200 OK\r\n
180 180 s> Server: testing stub value\r\n
181 181 s> Date: $HTTP_DATE$\r\n
182 182 s> Content-Type: application/mercurial-cbor\r\n
183 183 s> Content-Length: *\r\n (glob)
184 184 s> \r\n
185 185 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
186 186 cbor> {
187 187 b'apibase': b'api/',
188 188 b'apis': {},
189 189 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
190 190 }
191 191
192 192 Request for HTTPv2 service returns information about it
193 193
194 194 $ sendhttpraw << EOF
195 195 > httprequest GET ?cmd=capabilities
196 196 > user-agent: test
197 197 > x-hgupgrade-1: exp-http-v2-0001 foo bar
198 198 > x-hgproto-1: cbor
199 199 > EOF
200 200 using raw connection to peer
201 201 s> GET /?cmd=capabilities HTTP/1.1\r\n
202 202 s> Accept-Encoding: identity\r\n
203 203 s> user-agent: test\r\n
204 204 s> x-hgproto-1: cbor\r\n
205 205 s> x-hgupgrade-1: exp-http-v2-0001 foo bar\r\n
206 206 s> host: $LOCALIP:$HGPORT\r\n (glob)
207 207 s> \r\n
208 208 s> makefile('rb', None)
209 209 s> HTTP/1.1 200 OK\r\n
210 210 s> Server: testing stub value\r\n
211 211 s> Date: $HTTP_DATE$\r\n
212 212 s> Content-Type: application/mercurial-cbor\r\n
213 213 s> Content-Length: *\r\n (glob)
214 214 s> \r\n
215 215 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
216 216 cbor> {
217 217 b'apibase': b'api/',
218 218 b'apis': {
219 219 b'exp-http-v2-0001': {
220 220 b'commands': {
221 221 b'branchmap': {
222 222 b'args': {},
223 223 b'permissions': [
224 224 b'pull'
225 225 ]
226 226 },
227 227 b'capabilities': {
228 228 b'args': {},
229 229 b'permissions': [
230 230 b'pull'
231 231 ]
232 232 },
233 233 b'heads': {
234 234 b'args': {
235 235 b'publiconly': False
236 236 },
237 237 b'permissions': [
238 238 b'pull'
239 239 ]
240 240 },
241 241 b'known': {
242 242 b'args': {
243 243 b'nodes': [
244 244 b'deadbeef'
245 245 ]
246 246 },
247 247 b'permissions': [
248 248 b'pull'
249 249 ]
250 250 },
251 251 b'listkeys': {
252 252 b'args': {
253 253 b'namespace': b'ns'
254 254 },
255 255 b'permissions': [
256 256 b'pull'
257 257 ]
258 258 },
259 259 b'lookup': {
260 260 b'args': {
261 261 b'key': b'foo'
262 262 },
263 263 b'permissions': [
264 264 b'pull'
265 265 ]
266 266 },
267 267 b'pushkey': {
268 268 b'args': {
269 269 b'key': b'key',
270 270 b'namespace': b'ns',
271 271 b'new': b'new',
272 272 b'old': b'old'
273 273 },
274 274 b'permissions': [
275 275 b'push'
276 276 ]
277 277 }
278 278 },
279 279 b'compression': [
280 280 {
281 281 b'name': b'zlib'
282 282 }
283 283 ],
284 284 b'framingmediatypes': [
285 285 b'application/mercurial-exp-framing-0005'
286 286 ],
287 287 b'rawrepoformats': [
288 288 b'generaldelta',
289 289 b'revlogv1'
290 290 ]
291 291 }
292 292 },
293 293 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
294 294 }
295 295
296 296 capabilities command returns expected info
297 297
298 298 $ sendhttpv2peerhandshake << EOF
299 299 > command capabilities
300 300 > EOF
301 301 creating http peer for wire protocol version 2
302 302 s> GET /?cmd=capabilities HTTP/1.1\r\n
303 303 s> Accept-Encoding: identity\r\n
304 304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
305 305 s> x-hgproto-1: cbor\r\n
306 306 s> x-hgupgrade-1: exp-http-v2-0001\r\n
307 307 s> accept: application/mercurial-0.1\r\n
308 308 s> host: $LOCALIP:$HGPORT\r\n (glob)
309 309 s> user-agent: Mercurial debugwireproto\r\n
310 310 s> \r\n
311 311 s> makefile('rb', None)
312 312 s> HTTP/1.1 200 OK\r\n
313 313 s> Server: testing stub value\r\n
314 314 s> Date: $HTTP_DATE$\r\n
315 315 s> Content-Type: application/mercurial-cbor\r\n
316 316 s> Content-Length: *\r\n (glob)
317 317 s> \r\n
318 318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
319 319 sending capabilities command
320 320 s> POST /api/exp-http-v2-0001/ro/capabilities HTTP/1.1\r\n
321 321 s> Accept-Encoding: identity\r\n
322 322 s> *\r\n (glob)
323 323 s> content-type: application/mercurial-exp-framing-0005\r\n
324 324 s> content-length: 27\r\n
325 325 s> host: $LOCALIP:$HGPORT\r\n (glob)
326 326 s> user-agent: Mercurial debugwireproto\r\n
327 327 s> \r\n
328 328 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
329 329 s> makefile('rb', None)
330 330 s> HTTP/1.1 200 OK\r\n
331 331 s> Server: testing stub value\r\n
332 332 s> Date: $HTTP_DATE$\r\n
333 333 s> Content-Type: application/mercurial-exp-framing-0005\r\n
334 334 s> Transfer-Encoding: chunked\r\n
335 335 s> \r\n
336 336 s> 13\r\n
337 337 s> \x0b\x00\x00\x01\x00\x02\x011
338 338 s> \xa1FstatusBok
339 339 s> \r\n
340 340 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
341 341 s> 1cc\r\n
342 342 s> \xc4\x01\x00\x01\x00\x02\x001
343 343 s> \xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1
344 344 s> \r\n
345 345 received frame(size=452; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
346 346 s> 8\r\n
347 347 s> \x00\x00\x00\x01\x00\x02\x002
348 348 s> \r\n
349 349 s> 0\r\n
350 350 s> \r\n
351 351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
352 response: [
353 {
354 b'status': b'ok'
355 },
352 response: gen[
356 353 {
357 354 b'commands': {
358 355 b'branchmap': {
359 356 b'args': {},
360 357 b'permissions': [
361 358 b'pull'
362 359 ]
363 360 },
364 361 b'capabilities': {
365 362 b'args': {},
366 363 b'permissions': [
367 364 b'pull'
368 365 ]
369 366 },
370 367 b'heads': {
371 368 b'args': {
372 369 b'publiconly': False
373 370 },
374 371 b'permissions': [
375 372 b'pull'
376 373 ]
377 374 },
378 375 b'known': {
379 376 b'args': {
380 377 b'nodes': [
381 378 b'deadbeef'
382 379 ]
383 380 },
384 381 b'permissions': [
385 382 b'pull'
386 383 ]
387 384 },
388 385 b'listkeys': {
389 386 b'args': {
390 387 b'namespace': b'ns'
391 388 },
392 389 b'permissions': [
393 390 b'pull'
394 391 ]
395 392 },
396 393 b'lookup': {
397 394 b'args': {
398 395 b'key': b'foo'
399 396 },
400 397 b'permissions': [
401 398 b'pull'
402 399 ]
403 400 },
404 401 b'pushkey': {
405 402 b'args': {
406 403 b'key': b'key',
407 404 b'namespace': b'ns',
408 405 b'new': b'new',
409 406 b'old': b'old'
410 407 },
411 408 b'permissions': [
412 409 b'push'
413 410 ]
414 411 }
415 412 },
416 413 b'compression': [
417 414 {
418 415 b'name': b'zlib'
419 416 }
420 417 ],
421 418 b'framingmediatypes': [
422 419 b'application/mercurial-exp-framing-0005'
423 420 ],
424 421 b'rawrepoformats': [
425 422 b'generaldelta',
426 423 b'revlogv1'
427 424 ]
428 425 }
429 426 ]
430 427
431 428 $ cat error.log
General Comments 0
You need to be logged in to leave comments. Login now