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