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