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