##// END OF EJS Templates
profiling: add --no-profile to disable profiling enabled via config...
Kyle Lippincott -
r47787:a2bf93ab default
parent child Browse files
Show More
@@ -1,1380 +1,1390 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@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, print_function
9 9
10 10 import errno
11 11 import getopt
12 12 import io
13 13 import os
14 14 import pdb
15 15 import re
16 16 import signal
17 17 import sys
18 18 import traceback
19 19
20 20
21 21 from .i18n import _
22 22 from .pycompat import getattr
23 23
24 24 from hgdemandimport import tracing
25 25
26 26 from . import (
27 27 cmdutil,
28 28 color,
29 29 commands,
30 30 demandimport,
31 31 encoding,
32 32 error,
33 33 extensions,
34 34 fancyopts,
35 35 help,
36 36 hg,
37 37 hook,
38 38 localrepo,
39 39 profiling,
40 40 pycompat,
41 41 rcutil,
42 42 registrar,
43 43 requirements as requirementsmod,
44 44 scmutil,
45 45 ui as uimod,
46 46 util,
47 47 vfs,
48 48 )
49 49
50 50 from .utils import (
51 51 procutil,
52 52 stringutil,
53 53 urlutil,
54 54 )
55 55
56 56
57 57 class request(object):
58 58 def __init__(
59 59 self,
60 60 args,
61 61 ui=None,
62 62 repo=None,
63 63 fin=None,
64 64 fout=None,
65 65 ferr=None,
66 66 fmsg=None,
67 67 prereposetups=None,
68 68 ):
69 69 self.args = args
70 70 self.ui = ui
71 71 self.repo = repo
72 72
73 73 # input/output/error streams
74 74 self.fin = fin
75 75 self.fout = fout
76 76 self.ferr = ferr
77 77 # separate stream for status/error messages
78 78 self.fmsg = fmsg
79 79
80 80 # remember options pre-parsed by _earlyparseopts()
81 81 self.earlyoptions = {}
82 82
83 83 # reposetups which run before extensions, useful for chg to pre-fill
84 84 # low-level repo state (for example, changelog) before extensions.
85 85 self.prereposetups = prereposetups or []
86 86
87 87 # store the parsed and canonical command
88 88 self.canonical_command = None
89 89
90 90 def _runexithandlers(self):
91 91 exc = None
92 92 handlers = self.ui._exithandlers
93 93 try:
94 94 while handlers:
95 95 func, args, kwargs = handlers.pop()
96 96 try:
97 97 func(*args, **kwargs)
98 98 except: # re-raises below
99 99 if exc is None:
100 100 exc = sys.exc_info()[1]
101 101 self.ui.warnnoi18n(b'error in exit handlers:\n')
102 102 self.ui.traceback(force=True)
103 103 finally:
104 104 if exc is not None:
105 105 raise exc
106 106
107 107
108 108 def _flushstdio(ui, err):
109 109 status = None
110 110 # In all cases we try to flush stdio streams.
111 111 if util.safehasattr(ui, b'fout'):
112 112 assert ui is not None # help pytype
113 113 assert ui.fout is not None # help pytype
114 114 try:
115 115 ui.fout.flush()
116 116 except IOError as e:
117 117 err = e
118 118 status = -1
119 119
120 120 if util.safehasattr(ui, b'ferr'):
121 121 assert ui is not None # help pytype
122 122 assert ui.ferr is not None # help pytype
123 123 try:
124 124 if err is not None and err.errno != errno.EPIPE:
125 125 ui.ferr.write(
126 126 b'abort: %s\n' % encoding.strtolocal(err.strerror)
127 127 )
128 128 ui.ferr.flush()
129 129 # There's not much we can do about an I/O error here. So (possibly)
130 130 # change the status code and move on.
131 131 except IOError:
132 132 status = -1
133 133
134 134 return status
135 135
136 136
137 137 def run():
138 138 """run the command in sys.argv"""
139 139 try:
140 140 initstdio()
141 141 with tracing.log('parse args into request'):
142 142 req = request(pycompat.sysargv[1:])
143 143
144 144 status = dispatch(req)
145 145 _silencestdio()
146 146 except KeyboardInterrupt:
147 147 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
148 148 # be printed to console to avoid another IOError/KeyboardInterrupt.
149 149 status = -1
150 150 sys.exit(status & 255)
151 151
152 152
153 153 if pycompat.ispy3:
154 154
155 155 def initstdio():
156 156 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
157 157 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
158 158 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
159 159 # instances, which write to the underlying stdio file descriptor in binary
160 160 # mode. ui.write() uses \n for line endings and no line ending normalization
161 161 # is attempted through this interface. This "just works," even if the system
162 162 # preferred line ending is not \n.
163 163 #
164 164 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
165 165 # and sys.stderr. They will inherit the line ending normalization settings,
166 166 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
167 167 # "just work," here we change the sys.* streams to disable line ending
168 168 # normalization, ensuring compatibility with our ui type.
169 169
170 170 if sys.stdout is not None:
171 171 # write_through is new in Python 3.7.
172 172 kwargs = {
173 173 "newline": "\n",
174 174 "line_buffering": sys.stdout.line_buffering,
175 175 }
176 176 if util.safehasattr(sys.stdout, "write_through"):
177 177 # pytype: disable=attribute-error
178 178 kwargs["write_through"] = sys.stdout.write_through
179 179 # pytype: enable=attribute-error
180 180 sys.stdout = io.TextIOWrapper(
181 181 sys.stdout.buffer,
182 182 sys.stdout.encoding,
183 183 sys.stdout.errors,
184 184 **kwargs
185 185 )
186 186
187 187 if sys.stderr is not None:
188 188 kwargs = {
189 189 "newline": "\n",
190 190 "line_buffering": sys.stderr.line_buffering,
191 191 }
192 192 if util.safehasattr(sys.stderr, "write_through"):
193 193 # pytype: disable=attribute-error
194 194 kwargs["write_through"] = sys.stderr.write_through
195 195 # pytype: enable=attribute-error
196 196 sys.stderr = io.TextIOWrapper(
197 197 sys.stderr.buffer,
198 198 sys.stderr.encoding,
199 199 sys.stderr.errors,
200 200 **kwargs
201 201 )
202 202
203 203 if sys.stdin is not None:
204 204 # No write_through on read-only stream.
205 205 sys.stdin = io.TextIOWrapper(
206 206 sys.stdin.buffer,
207 207 sys.stdin.encoding,
208 208 sys.stdin.errors,
209 209 # None is universal newlines mode.
210 210 newline=None,
211 211 line_buffering=sys.stdin.line_buffering,
212 212 )
213 213
214 214 def _silencestdio():
215 215 for fp in (sys.stdout, sys.stderr):
216 216 if fp is None:
217 217 continue
218 218 # Check if the file is okay
219 219 try:
220 220 fp.flush()
221 221 continue
222 222 except IOError:
223 223 pass
224 224 # Otherwise mark it as closed to silence "Exception ignored in"
225 225 # message emitted by the interpreter finalizer.
226 226 try:
227 227 fp.close()
228 228 except IOError:
229 229 pass
230 230
231 231
232 232 else:
233 233
234 234 def initstdio():
235 235 for fp in (sys.stdin, sys.stdout, sys.stderr):
236 236 procutil.setbinary(fp)
237 237
238 238 def _silencestdio():
239 239 pass
240 240
241 241
242 242 def _formatargs(args):
243 243 return b' '.join(procutil.shellquote(a) for a in args)
244 244
245 245
246 246 def dispatch(req):
247 247 """run the command specified in req.args; returns an integer status code"""
248 248 err = None
249 249 try:
250 250 status = _rundispatch(req)
251 251 except error.StdioError as e:
252 252 err = e
253 253 status = -1
254 254
255 255 ret = _flushstdio(req.ui, err)
256 256 if ret:
257 257 status = ret
258 258 return status
259 259
260 260
261 261 def _rundispatch(req):
262 262 with tracing.log('dispatch._rundispatch'):
263 263 if req.ferr:
264 264 ferr = req.ferr
265 265 elif req.ui:
266 266 ferr = req.ui.ferr
267 267 else:
268 268 ferr = procutil.stderr
269 269
270 270 try:
271 271 if not req.ui:
272 272 req.ui = uimod.ui.load()
273 273 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
274 274 if req.earlyoptions[b'traceback']:
275 275 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
276 276
277 277 # set ui streams from the request
278 278 if req.fin:
279 279 req.ui.fin = req.fin
280 280 if req.fout:
281 281 req.ui.fout = req.fout
282 282 if req.ferr:
283 283 req.ui.ferr = req.ferr
284 284 if req.fmsg:
285 285 req.ui.fmsg = req.fmsg
286 286 except error.Abort as inst:
287 287 ferr.write(inst.format())
288 288 return -1
289 289
290 290 msg = _formatargs(req.args)
291 291 starttime = util.timer()
292 292 ret = 1 # default of Python exit code on unhandled exception
293 293 try:
294 294 ret = _runcatch(req) or 0
295 295 except error.ProgrammingError as inst:
296 296 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
297 297 if inst.hint:
298 298 req.ui.error(_(b'** (%s)\n') % inst.hint)
299 299 raise
300 300 except KeyboardInterrupt as inst:
301 301 try:
302 302 if isinstance(inst, error.SignalInterrupt):
303 303 msg = _(b"killed!\n")
304 304 else:
305 305 msg = _(b"interrupted!\n")
306 306 req.ui.error(msg)
307 307 except error.SignalInterrupt:
308 308 # maybe pager would quit without consuming all the output, and
309 309 # SIGPIPE was raised. we cannot print anything in this case.
310 310 pass
311 311 except IOError as inst:
312 312 if inst.errno != errno.EPIPE:
313 313 raise
314 314 ret = -1
315 315 finally:
316 316 duration = util.timer() - starttime
317 317 req.ui.flush() # record blocked times
318 318 if req.ui.logblockedtimes:
319 319 req.ui._blockedtimes[b'command_duration'] = duration * 1000
320 320 req.ui.log(
321 321 b'uiblocked',
322 322 b'ui blocked ms\n',
323 323 **pycompat.strkwargs(req.ui._blockedtimes)
324 324 )
325 325 return_code = ret & 255
326 326 req.ui.log(
327 327 b"commandfinish",
328 328 b"%s exited %d after %0.2f seconds\n",
329 329 msg,
330 330 return_code,
331 331 duration,
332 332 return_code=return_code,
333 333 duration=duration,
334 334 canonical_command=req.canonical_command,
335 335 )
336 336 try:
337 337 req._runexithandlers()
338 338 except: # exiting, so no re-raises
339 339 ret = ret or -1
340 340 # do flush again since ui.log() and exit handlers may write to ui
341 341 req.ui.flush()
342 342 return ret
343 343
344 344
345 345 def _runcatch(req):
346 346 with tracing.log('dispatch._runcatch'):
347 347
348 348 def catchterm(*args):
349 349 raise error.SignalInterrupt
350 350
351 351 ui = req.ui
352 352 try:
353 353 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
354 354 num = getattr(signal, name, None)
355 355 if num:
356 356 signal.signal(num, catchterm)
357 357 except ValueError:
358 358 pass # happens if called in a thread
359 359
360 360 def _runcatchfunc():
361 361 realcmd = None
362 362 try:
363 363 cmdargs = fancyopts.fancyopts(
364 364 req.args[:], commands.globalopts, {}
365 365 )
366 366 cmd = cmdargs[0]
367 367 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
368 368 realcmd = aliases[0]
369 369 except (
370 370 error.UnknownCommand,
371 371 error.AmbiguousCommand,
372 372 IndexError,
373 373 getopt.GetoptError,
374 374 ):
375 375 # Don't handle this here. We know the command is
376 376 # invalid, but all we're worried about for now is that
377 377 # it's not a command that server operators expect to
378 378 # be safe to offer to users in a sandbox.
379 379 pass
380 380 if realcmd == b'serve' and b'--stdio' in cmdargs:
381 381 # We want to constrain 'hg serve --stdio' instances pretty
382 382 # closely, as many shared-ssh access tools want to grant
383 383 # access to run *only* 'hg -R $repo serve --stdio'. We
384 384 # restrict to exactly that set of arguments, and prohibit
385 385 # any repo name that starts with '--' to prevent
386 386 # shenanigans wherein a user does something like pass
387 387 # --debugger or --config=ui.debugger=1 as a repo
388 388 # name. This used to actually run the debugger.
389 389 if (
390 390 len(req.args) != 4
391 391 or req.args[0] != b'-R'
392 392 or req.args[1].startswith(b'--')
393 393 or req.args[2] != b'serve'
394 394 or req.args[3] != b'--stdio'
395 395 ):
396 396 raise error.Abort(
397 397 _(b'potentially unsafe serve --stdio invocation: %s')
398 398 % (stringutil.pprint(req.args),)
399 399 )
400 400
401 401 try:
402 402 debugger = b'pdb'
403 403 debugtrace = {b'pdb': pdb.set_trace}
404 404 debugmortem = {b'pdb': pdb.post_mortem}
405 405
406 406 # read --config before doing anything else
407 407 # (e.g. to change trust settings for reading .hg/hgrc)
408 408 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
409 409
410 410 if req.repo:
411 411 # copy configs that were passed on the cmdline (--config) to
412 412 # the repo ui
413 413 for sec, name, val in cfgs:
414 414 req.repo.ui.setconfig(
415 415 sec, name, val, source=b'--config'
416 416 )
417 417
418 418 # developer config: ui.debugger
419 419 debugger = ui.config(b"ui", b"debugger")
420 420 debugmod = pdb
421 421 if not debugger or ui.plain():
422 422 # if we are in HGPLAIN mode, then disable custom debugging
423 423 debugger = b'pdb'
424 424 elif req.earlyoptions[b'debugger']:
425 425 # This import can be slow for fancy debuggers, so only
426 426 # do it when absolutely necessary, i.e. when actual
427 427 # debugging has been requested
428 428 with demandimport.deactivated():
429 429 try:
430 430 debugmod = __import__(debugger)
431 431 except ImportError:
432 432 pass # Leave debugmod = pdb
433 433
434 434 debugtrace[debugger] = debugmod.set_trace
435 435 debugmortem[debugger] = debugmod.post_mortem
436 436
437 437 # enter the debugger before command execution
438 438 if req.earlyoptions[b'debugger']:
439 439 ui.warn(
440 440 _(
441 441 b"entering debugger - "
442 442 b"type c to continue starting hg or h for help\n"
443 443 )
444 444 )
445 445
446 446 if (
447 447 debugger != b'pdb'
448 448 and debugtrace[debugger] == debugtrace[b'pdb']
449 449 ):
450 450 ui.warn(
451 451 _(
452 452 b"%s debugger specified "
453 453 b"but its module was not found\n"
454 454 )
455 455 % debugger
456 456 )
457 457 with demandimport.deactivated():
458 458 debugtrace[debugger]()
459 459 try:
460 460 return _dispatch(req)
461 461 finally:
462 462 ui.flush()
463 463 except: # re-raises
464 464 # enter the debugger when we hit an exception
465 465 if req.earlyoptions[b'debugger']:
466 466 traceback.print_exc()
467 467 debugmortem[debugger](sys.exc_info()[2])
468 468 raise
469 469
470 470 return _callcatch(ui, _runcatchfunc)
471 471
472 472
473 473 def _callcatch(ui, func):
474 474 """like scmutil.callcatch but handles more high-level exceptions about
475 475 config parsing and commands. besides, use handlecommandexception to handle
476 476 uncaught exceptions.
477 477 """
478 478 detailed_exit_code = -1
479 479 try:
480 480 return scmutil.callcatch(ui, func)
481 481 except error.AmbiguousCommand as inst:
482 482 detailed_exit_code = 10
483 483 ui.warn(
484 484 _(b"hg: command '%s' is ambiguous:\n %s\n")
485 485 % (inst.prefix, b" ".join(inst.matches))
486 486 )
487 487 except error.CommandError as inst:
488 488 detailed_exit_code = 10
489 489 if inst.command:
490 490 ui.pager(b'help')
491 491 msgbytes = pycompat.bytestr(inst.message)
492 492 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
493 493 commands.help_(ui, inst.command, full=False, command=True)
494 494 else:
495 495 ui.warn(_(b"hg: %s\n") % inst.message)
496 496 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
497 497 except error.UnknownCommand as inst:
498 498 detailed_exit_code = 10
499 499 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
500 500 try:
501 501 # check if the command is in a disabled extension
502 502 # (but don't check for extensions themselves)
503 503 formatted = help.formattedhelp(
504 504 ui, commands, inst.command, unknowncmd=True
505 505 )
506 506 ui.warn(nocmdmsg)
507 507 ui.write(formatted)
508 508 except (error.UnknownCommand, error.Abort):
509 509 suggested = False
510 510 if inst.all_commands:
511 511 sim = error.getsimilar(inst.all_commands, inst.command)
512 512 if sim:
513 513 ui.warn(nocmdmsg)
514 514 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
515 515 suggested = True
516 516 if not suggested:
517 517 ui.warn(nocmdmsg)
518 518 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
519 519 except IOError:
520 520 raise
521 521 except KeyboardInterrupt:
522 522 raise
523 523 except: # probably re-raises
524 524 if not handlecommandexception(ui):
525 525 raise
526 526
527 527 if ui.configbool(b'ui', b'detailed-exit-code'):
528 528 return detailed_exit_code
529 529 else:
530 530 return -1
531 531
532 532
533 533 def aliasargs(fn, givenargs):
534 534 args = []
535 535 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
536 536 if not util.safehasattr(fn, b'_origfunc'):
537 537 args = getattr(fn, 'args', args)
538 538 if args:
539 539 cmd = b' '.join(map(procutil.shellquote, args))
540 540
541 541 nums = []
542 542
543 543 def replacer(m):
544 544 num = int(m.group(1)) - 1
545 545 nums.append(num)
546 546 if num < len(givenargs):
547 547 return givenargs[num]
548 548 raise error.InputError(_(b'too few arguments for command alias'))
549 549
550 550 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
551 551 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
552 552 args = pycompat.shlexsplit(cmd)
553 553 return args + givenargs
554 554
555 555
556 556 def aliasinterpolate(name, args, cmd):
557 557 """interpolate args into cmd for shell aliases
558 558
559 559 This also handles $0, $@ and "$@".
560 560 """
561 561 # util.interpolate can't deal with "$@" (with quotes) because it's only
562 562 # built to match prefix + patterns.
563 563 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
564 564 replacemap[b'$0'] = name
565 565 replacemap[b'$$'] = b'$'
566 566 replacemap[b'$@'] = b' '.join(args)
567 567 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
568 568 # parameters, separated out into words. Emulate the same behavior here by
569 569 # quoting the arguments individually. POSIX shells will then typically
570 570 # tokenize each argument into exactly one word.
571 571 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
572 572 # escape '\$' for regex
573 573 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
574 574 r = re.compile(regex)
575 575 return r.sub(lambda x: replacemap[x.group()], cmd)
576 576
577 577
578 578 class cmdalias(object):
579 579 def __init__(self, ui, name, definition, cmdtable, source):
580 580 self.name = self.cmd = name
581 581 self.cmdname = b''
582 582 self.definition = definition
583 583 self.fn = None
584 584 self.givenargs = []
585 585 self.opts = []
586 586 self.help = b''
587 587 self.badalias = None
588 588 self.unknowncmd = False
589 589 self.source = source
590 590
591 591 try:
592 592 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
593 593 for alias, e in pycompat.iteritems(cmdtable):
594 594 if e is entry:
595 595 self.cmd = alias
596 596 break
597 597 self.shadows = True
598 598 except error.UnknownCommand:
599 599 self.shadows = False
600 600
601 601 if not self.definition:
602 602 self.badalias = _(b"no definition for alias '%s'") % self.name
603 603 return
604 604
605 605 if self.definition.startswith(b'!'):
606 606 shdef = self.definition[1:]
607 607 self.shell = True
608 608
609 609 def fn(ui, *args):
610 610 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
611 611
612 612 def _checkvar(m):
613 613 if m.groups()[0] == b'$':
614 614 return m.group()
615 615 elif int(m.groups()[0]) <= len(args):
616 616 return m.group()
617 617 else:
618 618 ui.debug(
619 619 b"No argument found for substitution "
620 620 b"of %i variable in alias '%s' definition.\n"
621 621 % (int(m.groups()[0]), self.name)
622 622 )
623 623 return b''
624 624
625 625 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
626 626 cmd = aliasinterpolate(self.name, args, cmd)
627 627 return ui.system(
628 628 cmd, environ=env, blockedtag=b'alias_%s' % self.name
629 629 )
630 630
631 631 self.fn = fn
632 632 self.alias = True
633 633 self._populatehelp(ui, name, shdef, self.fn)
634 634 return
635 635
636 636 try:
637 637 args = pycompat.shlexsplit(self.definition)
638 638 except ValueError as inst:
639 639 self.badalias = _(b"error in definition for alias '%s': %s") % (
640 640 self.name,
641 641 stringutil.forcebytestr(inst),
642 642 )
643 643 return
644 644 earlyopts, args = _earlysplitopts(args)
645 645 if earlyopts:
646 646 self.badalias = _(
647 647 b"error in definition for alias '%s': %s may "
648 648 b"only be given on the command line"
649 649 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
650 650 return
651 651 self.cmdname = cmd = args.pop(0)
652 652 self.givenargs = args
653 653
654 654 try:
655 655 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
656 656 if len(tableentry) > 2:
657 657 self.fn, self.opts, cmdhelp = tableentry
658 658 else:
659 659 self.fn, self.opts = tableentry
660 660 cmdhelp = None
661 661
662 662 self.alias = True
663 663 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
664 664
665 665 except error.UnknownCommand:
666 666 self.badalias = _(
667 667 b"alias '%s' resolves to unknown command '%s'"
668 668 ) % (
669 669 self.name,
670 670 cmd,
671 671 )
672 672 self.unknowncmd = True
673 673 except error.AmbiguousCommand:
674 674 self.badalias = _(
675 675 b"alias '%s' resolves to ambiguous command '%s'"
676 676 ) % (
677 677 self.name,
678 678 cmd,
679 679 )
680 680
681 681 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
682 682 # confine strings to be passed to i18n.gettext()
683 683 cfg = {}
684 684 for k in (b'doc', b'help', b'category'):
685 685 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
686 686 if v is None:
687 687 continue
688 688 if not encoding.isasciistr(v):
689 689 self.badalias = _(
690 690 b"non-ASCII character in alias definition '%s:%s'"
691 691 ) % (name, k)
692 692 return
693 693 cfg[k] = v
694 694
695 695 self.help = cfg.get(b'help', defaulthelp or b'')
696 696 if self.help and self.help.startswith(b"hg " + cmd):
697 697 # drop prefix in old-style help lines so hg shows the alias
698 698 self.help = self.help[4 + len(cmd) :]
699 699
700 700 self.owndoc = b'doc' in cfg
701 701 doc = cfg.get(b'doc', pycompat.getdoc(fn))
702 702 if doc is not None:
703 703 doc = pycompat.sysstr(doc)
704 704 self.__doc__ = doc
705 705
706 706 self.helpcategory = cfg.get(
707 707 b'category', registrar.command.CATEGORY_NONE
708 708 )
709 709
710 710 @property
711 711 def args(self):
712 712 args = pycompat.maplist(util.expandpath, self.givenargs)
713 713 return aliasargs(self.fn, args)
714 714
715 715 def __getattr__(self, name):
716 716 adefaults = {
717 717 'norepo': True,
718 718 'intents': set(),
719 719 'optionalrepo': False,
720 720 'inferrepo': False,
721 721 }
722 722 if name not in adefaults:
723 723 raise AttributeError(name)
724 724 if self.badalias or util.safehasattr(self, b'shell'):
725 725 return adefaults[name]
726 726 return getattr(self.fn, name)
727 727
728 728 def __call__(self, ui, *args, **opts):
729 729 if self.badalias:
730 730 hint = None
731 731 if self.unknowncmd:
732 732 try:
733 733 # check if the command is in a disabled extension
734 734 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
735 735 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
736 736 except error.UnknownCommand:
737 737 pass
738 738 raise error.ConfigError(self.badalias, hint=hint)
739 739 if self.shadows:
740 740 ui.debug(
741 741 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
742 742 )
743 743
744 744 ui.log(
745 745 b'commandalias',
746 746 b"alias '%s' expands to '%s'\n",
747 747 self.name,
748 748 self.definition,
749 749 )
750 750 if util.safehasattr(self, b'shell'):
751 751 return self.fn(ui, *args, **opts)
752 752 else:
753 753 try:
754 754 return util.checksignature(self.fn)(ui, *args, **opts)
755 755 except error.SignatureError:
756 756 args = b' '.join([self.cmdname] + self.args)
757 757 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
758 758 raise
759 759
760 760
761 761 class lazyaliasentry(object):
762 762 """like a typical command entry (func, opts, help), but is lazy"""
763 763
764 764 def __init__(self, ui, name, definition, cmdtable, source):
765 765 self.ui = ui
766 766 self.name = name
767 767 self.definition = definition
768 768 self.cmdtable = cmdtable.copy()
769 769 self.source = source
770 770 self.alias = True
771 771
772 772 @util.propertycache
773 773 def _aliasdef(self):
774 774 return cmdalias(
775 775 self.ui, self.name, self.definition, self.cmdtable, self.source
776 776 )
777 777
778 778 def __getitem__(self, n):
779 779 aliasdef = self._aliasdef
780 780 if n == 0:
781 781 return aliasdef
782 782 elif n == 1:
783 783 return aliasdef.opts
784 784 elif n == 2:
785 785 return aliasdef.help
786 786 else:
787 787 raise IndexError
788 788
789 789 def __iter__(self):
790 790 for i in range(3):
791 791 yield self[i]
792 792
793 793 def __len__(self):
794 794 return 3
795 795
796 796
797 797 def addaliases(ui, cmdtable):
798 798 # aliases are processed after extensions have been loaded, so they
799 799 # may use extension commands. Aliases can also use other alias definitions,
800 800 # but only if they have been defined prior to the current definition.
801 801 for alias, definition in ui.configitems(b'alias', ignoresub=True):
802 802 try:
803 803 if cmdtable[alias].definition == definition:
804 804 continue
805 805 except (KeyError, AttributeError):
806 806 # definition might not exist or it might not be a cmdalias
807 807 pass
808 808
809 809 source = ui.configsource(b'alias', alias)
810 810 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
811 811 cmdtable[alias] = entry
812 812
813 813
814 814 def _parse(ui, args):
815 815 options = {}
816 816 cmdoptions = {}
817 817
818 818 try:
819 819 args = fancyopts.fancyopts(args, commands.globalopts, options)
820 820 except getopt.GetoptError as inst:
821 821 raise error.CommandError(None, stringutil.forcebytestr(inst))
822 822
823 823 if args:
824 824 cmd, args = args[0], args[1:]
825 825 aliases, entry = cmdutil.findcmd(
826 826 cmd, commands.table, ui.configbool(b"ui", b"strict")
827 827 )
828 828 cmd = aliases[0]
829 829 args = aliasargs(entry[0], args)
830 830 defaults = ui.config(b"defaults", cmd)
831 831 if defaults:
832 832 args = (
833 833 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
834 834 + args
835 835 )
836 836 c = list(entry[1])
837 837 else:
838 838 cmd = None
839 839 c = []
840 840
841 841 # combine global options into local
842 842 for o in commands.globalopts:
843 843 c.append((o[0], o[1], options[o[1]], o[3]))
844 844
845 845 try:
846 846 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
847 847 except getopt.GetoptError as inst:
848 848 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
849 849
850 850 # separate global options back out
851 851 for o in commands.globalopts:
852 852 n = o[1]
853 853 options[n] = cmdoptions[n]
854 854 del cmdoptions[n]
855 855
856 856 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
857 857
858 858
859 859 def _parseconfig(ui, config):
860 860 """parse the --config options from the command line"""
861 861 configs = []
862 862
863 863 for cfg in config:
864 864 try:
865 865 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
866 866 section, name = name.split(b'.', 1)
867 867 if not section or not name:
868 868 raise IndexError
869 869 ui.setconfig(section, name, value, b'--config')
870 870 configs.append((section, name, value))
871 871 except (IndexError, ValueError):
872 872 raise error.InputError(
873 873 _(
874 874 b'malformed --config option: %r '
875 875 b'(use --config section.name=value)'
876 876 )
877 877 % pycompat.bytestr(cfg)
878 878 )
879 879
880 880 return configs
881 881
882 882
883 883 def _earlyparseopts(ui, args):
884 884 options = {}
885 885 fancyopts.fancyopts(
886 886 args,
887 887 commands.globalopts,
888 888 options,
889 889 gnu=not ui.plain(b'strictflags'),
890 890 early=True,
891 891 optaliases={b'repository': [b'repo']},
892 892 )
893 893 return options
894 894
895 895
896 896 def _earlysplitopts(args):
897 897 """Split args into a list of possible early options and remainder args"""
898 898 shortoptions = b'R:'
899 899 # TODO: perhaps 'debugger' should be included
900 900 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
901 901 return fancyopts.earlygetopt(
902 902 args, shortoptions, longoptions, gnu=True, keepsep=True
903 903 )
904 904
905 905
906 906 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
907 907 # run pre-hook, and abort if it fails
908 908 hook.hook(
909 909 lui,
910 910 repo,
911 911 b"pre-%s" % cmd,
912 912 True,
913 913 args=b" ".join(fullargs),
914 914 pats=cmdpats,
915 915 opts=cmdoptions,
916 916 )
917 917 try:
918 918 ret = _runcommand(ui, options, cmd, d)
919 919 # run post-hook, passing command result
920 920 hook.hook(
921 921 lui,
922 922 repo,
923 923 b"post-%s" % cmd,
924 924 False,
925 925 args=b" ".join(fullargs),
926 926 result=ret,
927 927 pats=cmdpats,
928 928 opts=cmdoptions,
929 929 )
930 930 except Exception:
931 931 # run failure hook and re-raise
932 932 hook.hook(
933 933 lui,
934 934 repo,
935 935 b"fail-%s" % cmd,
936 936 False,
937 937 args=b" ".join(fullargs),
938 938 pats=cmdpats,
939 939 opts=cmdoptions,
940 940 )
941 941 raise
942 942 return ret
943 943
944 944
945 945 def _readsharedsourceconfig(ui, path):
946 946 """if the current repository is shared one, this tries to read
947 947 .hg/hgrc of shared source if we are in share-safe mode
948 948
949 949 Config read is loaded into the ui object passed
950 950
951 951 This should be called before reading .hg/hgrc or the main repo
952 952 as that overrides config set in shared source"""
953 953 try:
954 954 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
955 955 requirements = set(fp.read().splitlines())
956 956 if not (
957 957 requirementsmod.SHARESAFE_REQUIREMENT in requirements
958 958 and requirementsmod.SHARED_REQUIREMENT in requirements
959 959 ):
960 960 return
961 961 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
962 962 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
963 963 root = sharedvfs.base
964 964 ui.readconfig(sharedvfs.join(b"hgrc"), root)
965 965 except IOError:
966 966 pass
967 967
968 968
969 969 def _getlocal(ui, rpath, wd=None):
970 970 """Return (path, local ui object) for the given target path.
971 971
972 972 Takes paths in [cwd]/.hg/hgrc into account."
973 973 """
974 974 if wd is None:
975 975 try:
976 976 wd = encoding.getcwd()
977 977 except OSError as e:
978 978 raise error.Abort(
979 979 _(b"error getting current working directory: %s")
980 980 % encoding.strtolocal(e.strerror)
981 981 )
982 982
983 983 path = cmdutil.findrepo(wd) or b""
984 984 if not path:
985 985 lui = ui
986 986 else:
987 987 lui = ui.copy()
988 988 if rcutil.use_repo_hgrc():
989 989 _readsharedsourceconfig(lui, path)
990 990 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
991 991 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
992 992
993 993 if rpath:
994 994 path = urlutil.get_clone_path(lui, rpath)[0]
995 995 lui = ui.copy()
996 996 if rcutil.use_repo_hgrc():
997 997 _readsharedsourceconfig(lui, path)
998 998 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
999 999 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
1000 1000
1001 1001 return path, lui
1002 1002
1003 1003
1004 1004 def _checkshellalias(lui, ui, args):
1005 1005 """Return the function to run the shell alias, if it is required"""
1006 1006 options = {}
1007 1007
1008 1008 try:
1009 1009 args = fancyopts.fancyopts(args, commands.globalopts, options)
1010 1010 except getopt.GetoptError:
1011 1011 return
1012 1012
1013 1013 if not args:
1014 1014 return
1015 1015
1016 1016 cmdtable = commands.table
1017 1017
1018 1018 cmd = args[0]
1019 1019 try:
1020 1020 strict = ui.configbool(b"ui", b"strict")
1021 1021 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1022 1022 except (error.AmbiguousCommand, error.UnknownCommand):
1023 1023 return
1024 1024
1025 1025 cmd = aliases[0]
1026 1026 fn = entry[0]
1027 1027
1028 1028 if cmd and util.safehasattr(fn, b'shell'):
1029 1029 # shell alias shouldn't receive early options which are consumed by hg
1030 1030 _earlyopts, args = _earlysplitopts(args)
1031 1031 d = lambda: fn(ui, *args[1:])
1032 1032 return lambda: runcommand(
1033 1033 lui, None, cmd, args[:1], ui, options, d, [], {}
1034 1034 )
1035 1035
1036 1036
1037 1037 def _dispatch(req):
1038 1038 args = req.args
1039 1039 ui = req.ui
1040 1040
1041 1041 # check for cwd
1042 1042 cwd = req.earlyoptions[b'cwd']
1043 1043 if cwd:
1044 1044 os.chdir(cwd)
1045 1045
1046 1046 rpath = req.earlyoptions[b'repository']
1047 1047 path, lui = _getlocal(ui, rpath)
1048 1048
1049 1049 uis = {ui, lui}
1050 1050
1051 1051 if req.repo:
1052 1052 uis.add(req.repo.ui)
1053 1053
1054 1054 if (
1055 1055 req.earlyoptions[b'verbose']
1056 1056 or req.earlyoptions[b'debug']
1057 1057 or req.earlyoptions[b'quiet']
1058 1058 ):
1059 1059 for opt in (b'verbose', b'debug', b'quiet'):
1060 1060 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1061 1061 for ui_ in uis:
1062 1062 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1063 1063
1064 1064 if req.earlyoptions[b'profile']:
1065 1065 for ui_ in uis:
1066 1066 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1067 elif req.earlyoptions[b'profile'] is False:
1068 # Check for it being set already, so that we don't pollute the config
1069 # with this when using chg in the very common case that it's not
1070 # enabled.
1071 if lui.configbool(b'profiling', b'enabled'):
1072 # Only do this on lui so that `chg foo` with a user config setting
1073 # profiling.enabled=1 still shows profiling information (chg will
1074 # specify `--no-profile` when `hg serve` is starting up, we don't
1075 # want that to propagate to every later invocation).
1076 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1067 1077
1068 1078 profile = lui.configbool(b'profiling', b'enabled')
1069 1079 with profiling.profile(lui, enabled=profile) as profiler:
1070 1080 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1071 1081 # reposetup
1072 1082 extensions.loadall(lui)
1073 1083 # Propagate any changes to lui.__class__ by extensions
1074 1084 ui.__class__ = lui.__class__
1075 1085
1076 1086 # (uisetup and extsetup are handled in extensions.loadall)
1077 1087
1078 1088 # (reposetup is handled in hg.repository)
1079 1089
1080 1090 addaliases(lui, commands.table)
1081 1091
1082 1092 # All aliases and commands are completely defined, now.
1083 1093 # Check abbreviation/ambiguity of shell alias.
1084 1094 shellaliasfn = _checkshellalias(lui, ui, args)
1085 1095 if shellaliasfn:
1086 1096 # no additional configs will be set, set up the ui instances
1087 1097 for ui_ in uis:
1088 1098 extensions.populateui(ui_)
1089 1099 return shellaliasfn()
1090 1100
1091 1101 # check for fallback encoding
1092 1102 fallback = lui.config(b'ui', b'fallbackencoding')
1093 1103 if fallback:
1094 1104 encoding.fallbackencoding = fallback
1095 1105
1096 1106 fullargs = args
1097 1107 cmd, func, args, options, cmdoptions = _parse(lui, args)
1098 1108
1099 1109 # store the canonical command name in request object for later access
1100 1110 req.canonical_command = cmd
1101 1111
1102 1112 if options[b"config"] != req.earlyoptions[b"config"]:
1103 1113 raise error.InputError(_(b"option --config may not be abbreviated"))
1104 1114 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1105 1115 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1106 1116 if options[b"repository"] != req.earlyoptions[b"repository"]:
1107 1117 raise error.InputError(
1108 1118 _(
1109 1119 b"option -R has to be separated from other options (e.g. not "
1110 1120 b"-qR) and --repository may only be abbreviated as --repo"
1111 1121 )
1112 1122 )
1113 1123 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1114 1124 raise error.InputError(
1115 1125 _(b"option --debugger may not be abbreviated")
1116 1126 )
1117 1127 # don't validate --profile/--traceback, which can be enabled from now
1118 1128
1119 1129 if options[b"encoding"]:
1120 1130 encoding.encoding = options[b"encoding"]
1121 1131 if options[b"encodingmode"]:
1122 1132 encoding.encodingmode = options[b"encodingmode"]
1123 1133 if options[b"time"]:
1124 1134
1125 1135 def get_times():
1126 1136 t = os.times()
1127 1137 if t[4] == 0.0:
1128 1138 # Windows leaves this as zero, so use time.perf_counter()
1129 1139 t = (t[0], t[1], t[2], t[3], util.timer())
1130 1140 return t
1131 1141
1132 1142 s = get_times()
1133 1143
1134 1144 def print_time():
1135 1145 t = get_times()
1136 1146 ui.warn(
1137 1147 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1138 1148 % (
1139 1149 t[4] - s[4],
1140 1150 t[0] - s[0],
1141 1151 t[2] - s[2],
1142 1152 t[1] - s[1],
1143 1153 t[3] - s[3],
1144 1154 )
1145 1155 )
1146 1156
1147 1157 ui.atexit(print_time)
1148 1158 if options[b"profile"]:
1149 1159 profiler.start()
1150 1160
1151 1161 # if abbreviated version of this were used, take them in account, now
1152 1162 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1153 1163 for opt in (b'verbose', b'debug', b'quiet'):
1154 1164 if options[opt] == req.earlyoptions[opt]:
1155 1165 continue
1156 1166 val = pycompat.bytestr(bool(options[opt]))
1157 1167 for ui_ in uis:
1158 1168 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1159 1169
1160 1170 if options[b'traceback']:
1161 1171 for ui_ in uis:
1162 1172 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1163 1173
1164 1174 if options[b'noninteractive']:
1165 1175 for ui_ in uis:
1166 1176 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1167 1177
1168 1178 if cmdoptions.get(b'insecure', False):
1169 1179 for ui_ in uis:
1170 1180 ui_.insecureconnections = True
1171 1181
1172 1182 # setup color handling before pager, because setting up pager
1173 1183 # might cause incorrect console information
1174 1184 coloropt = options[b'color']
1175 1185 for ui_ in uis:
1176 1186 if coloropt:
1177 1187 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1178 1188 color.setup(ui_)
1179 1189
1180 1190 if stringutil.parsebool(options[b'pager']):
1181 1191 # ui.pager() expects 'internal-always-' prefix in this case
1182 1192 ui.pager(b'internal-always-' + cmd)
1183 1193 elif options[b'pager'] != b'auto':
1184 1194 for ui_ in uis:
1185 1195 ui_.disablepager()
1186 1196
1187 1197 # configs are fully loaded, set up the ui instances
1188 1198 for ui_ in uis:
1189 1199 extensions.populateui(ui_)
1190 1200
1191 1201 if options[b'version']:
1192 1202 return commands.version_(ui)
1193 1203 if options[b'help']:
1194 1204 return commands.help_(ui, cmd, command=cmd is not None)
1195 1205 elif not cmd:
1196 1206 return commands.help_(ui, b'shortlist')
1197 1207
1198 1208 repo = None
1199 1209 cmdpats = args[:]
1200 1210 assert func is not None # help out pytype
1201 1211 if not func.norepo:
1202 1212 # use the repo from the request only if we don't have -R
1203 1213 if not rpath and not cwd:
1204 1214 repo = req.repo
1205 1215
1206 1216 if repo:
1207 1217 # set the descriptors of the repo ui to those of ui
1208 1218 repo.ui.fin = ui.fin
1209 1219 repo.ui.fout = ui.fout
1210 1220 repo.ui.ferr = ui.ferr
1211 1221 repo.ui.fmsg = ui.fmsg
1212 1222 else:
1213 1223 try:
1214 1224 repo = hg.repository(
1215 1225 ui,
1216 1226 path=path,
1217 1227 presetupfuncs=req.prereposetups,
1218 1228 intents=func.intents,
1219 1229 )
1220 1230 if not repo.local():
1221 1231 raise error.InputError(
1222 1232 _(b"repository '%s' is not local") % path
1223 1233 )
1224 1234 repo.ui.setconfig(
1225 1235 b"bundle", b"mainreporoot", repo.root, b'repo'
1226 1236 )
1227 1237 except error.RequirementError:
1228 1238 raise
1229 1239 except error.RepoError:
1230 1240 if rpath: # invalid -R path
1231 1241 raise
1232 1242 if not func.optionalrepo:
1233 1243 if func.inferrepo and args and not path:
1234 1244 # try to infer -R from command args
1235 1245 repos = pycompat.maplist(cmdutil.findrepo, args)
1236 1246 guess = repos[0]
1237 1247 if guess and repos.count(guess) == len(repos):
1238 1248 req.args = [b'--repository', guess] + fullargs
1239 1249 req.earlyoptions[b'repository'] = guess
1240 1250 return _dispatch(req)
1241 1251 if not path:
1242 1252 raise error.InputError(
1243 1253 _(
1244 1254 b"no repository found in"
1245 1255 b" '%s' (.hg not found)"
1246 1256 )
1247 1257 % encoding.getcwd()
1248 1258 )
1249 1259 raise
1250 1260 if repo:
1251 1261 ui = repo.ui
1252 1262 if options[b'hidden']:
1253 1263 repo = repo.unfiltered()
1254 1264 args.insert(0, repo)
1255 1265 elif rpath:
1256 1266 ui.warn(_(b"warning: --repository ignored\n"))
1257 1267
1258 1268 msg = _formatargs(fullargs)
1259 1269 ui.log(b"command", b'%s\n', msg)
1260 1270 strcmdopt = pycompat.strkwargs(cmdoptions)
1261 1271 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1262 1272 try:
1263 1273 return runcommand(
1264 1274 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1265 1275 )
1266 1276 finally:
1267 1277 if repo and repo != req.repo:
1268 1278 repo.close()
1269 1279
1270 1280
1271 1281 def _runcommand(ui, options, cmd, cmdfunc):
1272 1282 """Run a command function, possibly with profiling enabled."""
1273 1283 try:
1274 1284 with tracing.log("Running %s command" % cmd):
1275 1285 return cmdfunc()
1276 1286 except error.SignatureError:
1277 1287 raise error.CommandError(cmd, _(b'invalid arguments'))
1278 1288
1279 1289
1280 1290 def _exceptionwarning(ui):
1281 1291 """Produce a warning message for the current active exception"""
1282 1292
1283 1293 # For compatibility checking, we discard the portion of the hg
1284 1294 # version after the + on the assumption that if a "normal
1285 1295 # user" is running a build with a + in it the packager
1286 1296 # probably built from fairly close to a tag and anyone with a
1287 1297 # 'make local' copy of hg (where the version number can be out
1288 1298 # of date) will be clueful enough to notice the implausible
1289 1299 # version number and try updating.
1290 1300 ct = util.versiontuple(n=2)
1291 1301 worst = None, ct, b'', b''
1292 1302 if ui.config(b'ui', b'supportcontact') is None:
1293 1303 for name, mod in extensions.extensions():
1294 1304 # 'testedwith' should be bytes, but not all extensions are ported
1295 1305 # to py3 and we don't want UnicodeException because of that.
1296 1306 testedwith = stringutil.forcebytestr(
1297 1307 getattr(mod, 'testedwith', b'')
1298 1308 )
1299 1309 version = extensions.moduleversion(mod)
1300 1310 report = getattr(mod, 'buglink', _(b'the extension author.'))
1301 1311 if not testedwith.strip():
1302 1312 # We found an untested extension. It's likely the culprit.
1303 1313 worst = name, b'unknown', report, version
1304 1314 break
1305 1315
1306 1316 # Never blame on extensions bundled with Mercurial.
1307 1317 if extensions.ismoduleinternal(mod):
1308 1318 continue
1309 1319
1310 1320 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1311 1321 if ct in tested:
1312 1322 continue
1313 1323
1314 1324 lower = [t for t in tested if t < ct]
1315 1325 nearest = max(lower or tested)
1316 1326 if worst[0] is None or nearest < worst[1]:
1317 1327 worst = name, nearest, report, version
1318 1328 if worst[0] is not None:
1319 1329 name, testedwith, report, version = worst
1320 1330 if not isinstance(testedwith, (bytes, str)):
1321 1331 testedwith = b'.'.join(
1322 1332 [stringutil.forcebytestr(c) for c in testedwith]
1323 1333 )
1324 1334 extver = version or _(b"(version N/A)")
1325 1335 warning = _(
1326 1336 b'** Unknown exception encountered with '
1327 1337 b'possibly-broken third-party extension "%s" %s\n'
1328 1338 b'** which supports versions %s of Mercurial.\n'
1329 1339 b'** Please disable "%s" and try your action again.\n'
1330 1340 b'** If that fixes the bug please report it to %s\n'
1331 1341 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1332 1342 else:
1333 1343 bugtracker = ui.config(b'ui', b'supportcontact')
1334 1344 if bugtracker is None:
1335 1345 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1336 1346 warning = (
1337 1347 _(
1338 1348 b"** unknown exception encountered, "
1339 1349 b"please report by visiting\n** "
1340 1350 )
1341 1351 + bugtracker
1342 1352 + b'\n'
1343 1353 )
1344 1354 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1345 1355
1346 1356 def ext_with_ver(x):
1347 1357 ext = x[0]
1348 1358 ver = extensions.moduleversion(x[1])
1349 1359 if ver:
1350 1360 ext += b' ' + ver
1351 1361 return ext
1352 1362
1353 1363 warning += (
1354 1364 (_(b"** Python %s\n") % sysversion)
1355 1365 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1356 1366 + (
1357 1367 _(b"** Extensions loaded: %s\n")
1358 1368 % b", ".join(
1359 1369 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1360 1370 )
1361 1371 )
1362 1372 )
1363 1373 return warning
1364 1374
1365 1375
1366 1376 def handlecommandexception(ui):
1367 1377 """Produce a warning message for broken commands
1368 1378
1369 1379 Called when handling an exception; the exception is reraised if
1370 1380 this function returns False, ignored otherwise.
1371 1381 """
1372 1382 warning = _exceptionwarning(ui)
1373 1383 ui.log(
1374 1384 b"commandexception",
1375 1385 b"%s\n%s\n",
1376 1386 warning,
1377 1387 pycompat.sysbytes(traceback.format_exc()),
1378 1388 )
1379 1389 ui.warn(warning)
1380 1390 return False # re-raise the exception
@@ -1,470 +1,539 b''
1 1 #require chg
2 2
3 3 $ mkdir log
4 4 $ cp $HGRCPATH $HGRCPATH.unconfigured
5 5 $ cat <<'EOF' >> $HGRCPATH
6 6 > [cmdserver]
7 7 > log = $TESTTMP/log/server.log
8 8 > max-log-files = 1
9 9 > max-log-size = 10 kB
10 10 > EOF
11 11 $ cp $HGRCPATH $HGRCPATH.orig
12 12
13 13 $ filterlog () {
14 14 > sed -e 's!^[0-9/]* [0-9:]* ([0-9]*)>!YYYY/MM/DD HH:MM:SS (PID)>!' \
15 15 > -e 's!\(setprocname\|received fds\|setenv\): .*!\1: ...!' \
16 16 > -e 's!\(confighash\|mtimehash\) = [0-9a-f]*!\1 = ...!g' \
17 17 > -e 's!\(in \)[0-9.]*s\b!\1 ...s!g' \
18 18 > -e 's!\(pid\)=[0-9]*!\1=...!g' \
19 19 > -e 's!\(/server-\)[0-9a-f]*!\1...!g'
20 20 > }
21 21
22 22 init repo
23 23
24 24 $ chg init foo
25 25 $ cd foo
26 26
27 27 ill-formed config
28 28
29 29 $ chg status
30 30 $ echo '=brokenconfig' >> $HGRCPATH
31 31 $ chg status
32 32 config error at * =brokenconfig (glob)
33 33 [30]
34 34
35 35 $ cp $HGRCPATH.orig $HGRCPATH
36 36
37 37 long socket path
38 38
39 39 $ sockpath=$TESTTMP/this/path/should/be/longer/than/one-hundred-and-seven/characters/where/107/is/the/typical/size/limit/of/unix-domain-socket
40 40 $ mkdir -p $sockpath
41 41 $ bakchgsockname=$CHGSOCKNAME
42 42 $ CHGSOCKNAME=$sockpath/server
43 43 $ export CHGSOCKNAME
44 44 $ chg root
45 45 $TESTTMP/foo
46 46 $ rm -rf $sockpath
47 47 $ CHGSOCKNAME=$bakchgsockname
48 48 $ export CHGSOCKNAME
49 49
50 50 $ cd ..
51 51
52 52 editor
53 53 ------
54 54
55 55 $ cat >> pushbuffer.py <<EOF
56 56 > def reposetup(ui, repo):
57 57 > repo.ui.pushbuffer(subproc=True)
58 58 > EOF
59 59
60 60 $ chg init editor
61 61 $ cd editor
62 62
63 63 by default, system() should be redirected to the client:
64 64
65 65 $ touch foo
66 66 $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
67 67 > | egrep "HG:|run 'cat"
68 68 chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
69 69 HG: Enter commit message. Lines beginning with 'HG:' are removed.
70 70 HG: Leave message empty to abort commit.
71 71 HG: --
72 72 HG: user: test
73 73 HG: branch 'default'
74 74 HG: added foo
75 75
76 76 but no redirection should be made if output is captured:
77 77
78 78 $ touch bar
79 79 $ CHGDEBUG= HGEDITOR=cat chg ci -Am bufferred --edit \
80 80 > --config extensions.pushbuffer="$TESTTMP/pushbuffer.py" 2>&1 \
81 81 > | egrep "HG:|run 'cat"
82 82 [1]
83 83
84 84 check that commit commands succeeded:
85 85
86 86 $ hg log -T '{rev}:{desc}\n'
87 87 1:bufferred
88 88 0:channeled
89 89
90 90 $ cd ..
91 91
92 92 pager
93 93 -----
94 94
95 95 $ cat >> fakepager.py <<EOF
96 96 > import sys
97 97 > for line in sys.stdin:
98 98 > sys.stdout.write('paged! %r\n' % line)
99 99 > EOF
100 100
101 101 enable pager extension globally, but spawns the master server with no tty:
102 102
103 103 $ chg init pager
104 104 $ cd pager
105 105 $ cat >> $HGRCPATH <<EOF
106 106 > [extensions]
107 107 > pager =
108 108 > [pager]
109 109 > pager = "$PYTHON" $TESTTMP/fakepager.py
110 110 > EOF
111 111 $ chg version > /dev/null
112 112 $ touch foo
113 113 $ chg ci -qAm foo
114 114
115 115 pager should be enabled if the attached client has a tty:
116 116
117 117 $ chg log -l1 -q --config ui.formatted=True
118 118 paged! '0:1f7b0de80e11\n'
119 119 $ chg log -l1 -q --config ui.formatted=False
120 120 0:1f7b0de80e11
121 121
122 122 chg waits for pager if runcommand raises
123 123
124 124 $ cat > $TESTTMP/crash.py <<EOF
125 125 > from mercurial import registrar
126 126 > cmdtable = {}
127 127 > command = registrar.command(cmdtable)
128 128 > @command(b'crash')
129 129 > def pagercrash(ui, repo, *pats, **opts):
130 130 > ui.write(b'going to crash\n')
131 131 > raise Exception('.')
132 132 > EOF
133 133
134 134 $ cat > $TESTTMP/fakepager.py <<EOF
135 135 > from __future__ import absolute_import
136 136 > import sys
137 137 > import time
138 138 > for line in iter(sys.stdin.readline, ''):
139 139 > if 'crash' in line: # only interested in lines containing 'crash'
140 140 > # if chg exits when pager is sleeping (incorrectly), the output
141 141 > # will be captured by the next test case
142 142 > time.sleep(1)
143 143 > sys.stdout.write('crash-pager: %s' % line)
144 144 > EOF
145 145
146 146 $ cat >> .hg/hgrc <<EOF
147 147 > [extensions]
148 148 > crash = $TESTTMP/crash.py
149 149 > EOF
150 150
151 151 $ chg crash --pager=on --config ui.formatted=True 2>/dev/null
152 152 crash-pager: going to crash
153 153 [255]
154 154
155 155 no stdout data should be printed after pager quits, and the buffered data
156 156 should never persist (issue6207)
157 157
158 158 "killed!" may be printed if terminated by SIGPIPE, which isn't important
159 159 in this test.
160 160
161 161 $ cat > $TESTTMP/bulkwrite.py <<'EOF'
162 162 > import time
163 163 > from mercurial import error, registrar
164 164 > cmdtable = {}
165 165 > command = registrar.command(cmdtable)
166 166 > @command(b'bulkwrite')
167 167 > def bulkwrite(ui, repo, *pats, **opts):
168 168 > ui.write(b'going to write massive data\n')
169 169 > ui.flush()
170 170 > t = time.time()
171 171 > while time.time() - t < 2:
172 172 > ui.write(b'x' * 1023 + b'\n') # will be interrupted by SIGPIPE
173 173 > raise error.Abort(b"write() doesn't block")
174 174 > EOF
175 175
176 176 $ cat > $TESTTMP/fakepager.py <<'EOF'
177 177 > import sys
178 178 > import time
179 179 > sys.stdout.write('paged! %r\n' % sys.stdin.readline())
180 180 > time.sleep(1) # new data will be written
181 181 > EOF
182 182
183 183 $ cat >> .hg/hgrc <<EOF
184 184 > [extensions]
185 185 > bulkwrite = $TESTTMP/bulkwrite.py
186 186 > EOF
187 187
188 188 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
189 189 paged! 'going to write massive data\n'
190 190 killed! (?)
191 191 [255]
192 192
193 193 $ chg bulkwrite --pager=on --color no --config ui.formatted=True
194 194 paged! 'going to write massive data\n'
195 195 killed! (?)
196 196 [255]
197 197
198 198 $ cd ..
199 199
200 200 missing stdio
201 201 -------------
202 202
203 203 $ CHGDEBUG=1 chg version -q 0<&-
204 204 chg: debug: * stdio fds are missing (glob)
205 205 chg: debug: * execute original hg (glob)
206 206 Mercurial Distributed SCM * (glob)
207 207
208 208 server lifecycle
209 209 ----------------
210 210
211 211 chg server should be restarted on code change, and old server will shut down
212 212 automatically. In this test, we use the following time parameters:
213 213
214 214 - "sleep 1" to make mtime different
215 215 - "sleep 2" to notice mtime change (polling interval is 1 sec)
216 216
217 217 set up repository with an extension:
218 218
219 219 $ chg init extreload
220 220 $ cd extreload
221 221 $ touch dummyext.py
222 222 $ cat <<EOF >> .hg/hgrc
223 223 > [extensions]
224 224 > dummyext = dummyext.py
225 225 > EOF
226 226
227 227 isolate socket directory for stable result:
228 228
229 229 $ OLDCHGSOCKNAME=$CHGSOCKNAME
230 230 $ mkdir chgsock
231 231 $ CHGSOCKNAME=`pwd`/chgsock/server
232 232
233 233 warm up server:
234 234
235 235 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
236 236 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
237 237
238 238 new server should be started if extension modified:
239 239
240 240 $ sleep 1
241 241 $ touch dummyext.py
242 242 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
243 243 chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
244 244 chg: debug: * instruction: reconnect (glob)
245 245 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
246 246
247 247 old server will shut down, while new server should still be reachable:
248 248
249 249 $ sleep 2
250 250 $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
251 251
252 252 socket file should never be unlinked by old server:
253 253 (simulates unowned socket by updating mtime, which makes sure server exits
254 254 at polling cycle)
255 255
256 256 $ ls chgsock/server-*
257 257 chgsock/server-* (glob)
258 258 $ touch chgsock/server-*
259 259 $ sleep 2
260 260 $ ls chgsock/server-*
261 261 chgsock/server-* (glob)
262 262
263 263 since no server is reachable from socket file, new server should be started:
264 264 (this test makes sure that old server shut down automatically)
265 265
266 266 $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
267 267 chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
268 268
269 269 shut down servers and restore environment:
270 270
271 271 $ rm -R chgsock
272 272 $ sleep 2
273 273 $ CHGSOCKNAME=$OLDCHGSOCKNAME
274 274 $ cd ..
275 275
276 276 check that server events are recorded:
277 277
278 278 $ ls log
279 279 server.log
280 280 server.log.1
281 281
282 282 print only the last 10 lines, since we aren't sure how many records are
283 283 preserved (since setprocname isn't available on py3 and pure version,
284 284 the 10th-most-recent line is different when using py3):
285 285
286 286 $ cat log/server.log.1 log/server.log | tail -10 | filterlog
287 287 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ... (no-setprocname !)
288 288 YYYY/MM/DD HH:MM:SS (PID)> forked worker process (pid=...)
289 289 YYYY/MM/DD HH:MM:SS (PID)> setprocname: ... (setprocname !)
290 290 YYYY/MM/DD HH:MM:SS (PID)> received fds: ...
291 291 YYYY/MM/DD HH:MM:SS (PID)> chdir to '$TESTTMP/extreload'
292 292 YYYY/MM/DD HH:MM:SS (PID)> setumask 18
293 293 YYYY/MM/DD HH:MM:SS (PID)> setenv: ...
294 294 YYYY/MM/DD HH:MM:SS (PID)> confighash = ... mtimehash = ...
295 295 YYYY/MM/DD HH:MM:SS (PID)> validate: []
296 296 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
297 297 YYYY/MM/DD HH:MM:SS (PID)> $TESTTMP/extreload/chgsock/server-... is not owned, exiting.
298 298
299 299 global data mutated by schems
300 300 -----------------------------
301 301
302 302 $ hg init schemes
303 303 $ cd schemes
304 304
305 305 initial state
306 306
307 307 $ cat > .hg/hgrc <<'EOF'
308 308 > [extensions]
309 309 > schemes =
310 310 > [schemes]
311 311 > foo = https://foo.example.org/
312 312 > EOF
313 313 $ hg debugexpandscheme foo://expanded
314 314 https://foo.example.org/expanded
315 315 $ hg debugexpandscheme bar://unexpanded
316 316 bar://unexpanded
317 317
318 318 add bar
319 319
320 320 $ cat > .hg/hgrc <<'EOF'
321 321 > [extensions]
322 322 > schemes =
323 323 > [schemes]
324 324 > foo = https://foo.example.org/
325 325 > bar = https://bar.example.org/
326 326 > EOF
327 327 $ hg debugexpandscheme foo://expanded
328 328 https://foo.example.org/expanded
329 329 $ hg debugexpandscheme bar://expanded
330 330 https://bar.example.org/expanded
331 331
332 332 remove foo
333 333
334 334 $ cat > .hg/hgrc <<'EOF'
335 335 > [extensions]
336 336 > schemes =
337 337 > [schemes]
338 338 > bar = https://bar.example.org/
339 339 > EOF
340 340 $ hg debugexpandscheme foo://unexpanded
341 341 foo://unexpanded
342 342 $ hg debugexpandscheme bar://expanded
343 343 https://bar.example.org/expanded
344 344
345 345 $ cd ..
346 346
347 347 repository cache
348 348 ----------------
349 349
350 350 $ rm log/server.log*
351 351 $ cp $HGRCPATH.unconfigured $HGRCPATH
352 352 $ cat <<'EOF' >> $HGRCPATH
353 353 > [cmdserver]
354 354 > log = $TESTTMP/log/server.log
355 355 > max-repo-cache = 1
356 356 > track-log = command, repocache
357 357 > EOF
358 358
359 359 isolate socket directory for stable result:
360 360
361 361 $ OLDCHGSOCKNAME=$CHGSOCKNAME
362 362 $ mkdir chgsock
363 363 $ CHGSOCKNAME=`pwd`/chgsock/server
364 364
365 365 create empty repo and cache it:
366 366
367 367 $ hg init cached
368 368 $ hg id -R cached
369 369 000000000000 tip
370 370 $ sleep 1
371 371
372 372 modify repo (and cache will be invalidated):
373 373
374 374 $ touch cached/a
375 375 $ hg ci -R cached -Am 'add a'
376 376 adding a
377 377 $ sleep 1
378 378
379 379 read cached repo:
380 380
381 381 $ hg log -R cached
382 382 changeset: 0:ac82d8b1f7c4
383 383 tag: tip
384 384 user: test
385 385 date: Thu Jan 01 00:00:00 1970 +0000
386 386 summary: add a
387 387
388 388 $ sleep 1
389 389
390 390 discard cached from LRU cache:
391 391
392 392 $ hg clone cached cached2
393 393 updating to branch default
394 394 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
395 395 $ hg id -R cached2
396 396 ac82d8b1f7c4 tip
397 397 $ sleep 1
398 398
399 399 read uncached repo:
400 400
401 401 $ hg log -R cached
402 402 changeset: 0:ac82d8b1f7c4
403 403 tag: tip
404 404 user: test
405 405 date: Thu Jan 01 00:00:00 1970 +0000
406 406 summary: add a
407 407
408 408 $ sleep 1
409 409
410 410 shut down servers and restore environment:
411 411
412 412 $ rm -R chgsock
413 413 $ sleep 2
414 414 $ CHGSOCKNAME=$OLDCHGSOCKNAME
415 415
416 416 check server log:
417 417
418 418 $ cat log/server.log | filterlog
419 419 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...)
420 420 YYYY/MM/DD HH:MM:SS (PID)> worker process exited (pid=...) (?)
421 421 YYYY/MM/DD HH:MM:SS (PID)> init cached
422 422 YYYY/MM/DD HH:MM:SS (PID)> id -R cached
423 423 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
424 424 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
425 425 YYYY/MM/DD HH:MM:SS (PID)> ci -R cached -Am 'add a'
426 426 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
427 427 YYYY/MM/DD HH:MM:SS (PID)> repo from cache: $TESTTMP/cached
428 428 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
429 429 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
430 430 YYYY/MM/DD HH:MM:SS (PID)> clone cached cached2
431 431 YYYY/MM/DD HH:MM:SS (PID)> id -R cached2
432 432 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached2 (in ...s)
433 433 YYYY/MM/DD HH:MM:SS (PID)> log -R cached
434 434 YYYY/MM/DD HH:MM:SS (PID)> loaded repo into cache: $TESTTMP/cached (in ...s)
435 435
436 436 Test that chg works (sets to the user's actual LC_CTYPE) even when python
437 437 "coerces" the locale (py3.7+)
438 438
439 439 $ cat > $TESTTMP/debugenv.py <<EOF
440 440 > from mercurial import encoding
441 441 > from mercurial import registrar
442 442 > cmdtable = {}
443 443 > command = registrar.command(cmdtable)
444 444 > @command(b'debugenv', [], b'', norepo=True)
445 445 > def debugenv(ui):
446 446 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
447 447 > v = encoding.environ.get(k)
448 448 > if v is not None:
449 449 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
450 450 > EOF
451 451 (hg keeps python's modified LC_CTYPE, chg doesn't)
452 452 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
453 453 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
454 454 LC_CTYPE=C.UTF-8 (py37 !)
455 455 LC_CTYPE= (no-py37 !)
456 456 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
457 457 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
458 458 LC_CTYPE=
459 459 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
460 460 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
461 461 *cannot change locale* (glob) (?)
462 462 LC_CTYPE=unsupported_value
463 463 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
464 464 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
465 465 LC_CTYPE=
466 466 $ LANG= LC_ALL= LC_CTYPE= chg \
467 467 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
468 468 LC_ALL=
469 469 LC_CTYPE=
470 470 LANG=
471
472 Profiling isn't permanently enabled or carried over between chg invocations that
473 share the same server
474 $ cp $HGRCPATH.orig $HGRCPATH
475 $ hg init $TESTTMP/profiling
476 $ cd $TESTTMP/profiling
477 $ filteredchg() {
478 > CHGDEBUG=1 chg "$@" 2>&1 | egrep 'Sample count|start cmdserver' || true
479 > }
480 $ newchg() {
481 > chg --kill-chg-daemon
482 > filteredchg "$@" | egrep -v 'start cmdserver' || true
483 > }
484 (--profile isn't permanently on just because it was specified when chg was
485 started)
486 $ newchg log -r . --profile
487 Sample count: * (glob)
488 $ filteredchg log -r .
489 (enabling profiling via config works, even on the first chg command that starts
490 a cmdserver)
491 $ cat >> $HGRCPATH <<EOF
492 > [profiling]
493 > type=stat
494 > enabled=1
495 > EOF
496 $ newchg log -r .
497 Sample count: * (glob)
498 $ filteredchg log -r .
499 Sample count: * (glob)
500 (test that we aren't accumulating more and more samples each run)
501 $ cat > $TESTTMP/debugsleep.py <<EOF
502 > import time
503 > from mercurial import registrar
504 > cmdtable = {}
505 > command = registrar.command(cmdtable)
506 > @command(b'debugsleep', [], b'', norepo=True)
507 > def debugsleep(ui):
508 > start = time.time()
509 > x = 0
510 > while time.time() < start + 0.5:
511 > time.sleep(.1)
512 > x += 1
513 > ui.status(b'%d debugsleep iterations in %.03fs\n' % (x, time.time() - start))
514 > EOF
515 $ cat >> $HGRCPATH <<EOF
516 > [extensions]
517 > debugsleep = $TESTTMP/debugsleep.py
518 > EOF
519 $ newchg debugsleep > run_1
520 $ filteredchg debugsleep > run_2
521 $ filteredchg debugsleep > run_3
522 $ filteredchg debugsleep > run_4
523 FIXME: Run 4 should not be >3x Run 1's number of samples.
524 $ "$PYTHON" <<EOF
525 > r1 = int(open("run_1", "r").read().split()[-1])
526 > r4 = int(open("run_4", "r").read().split()[-1])
527 > print("Run 1: %d samples\nRun 4: %d samples\nRun 4 > 3 * Run 1: %s" %
528 > (r1, r4, r4 > (r1 * 3)))
529 > EOF
530 Run 1: * samples (glob)
531 Run 4: * samples (glob)
532 Run 4 > 3 * Run 1: True
533 (Disabling with --no-profile on the commandline still works, but isn't permanent)
534 $ newchg log -r . --no-profile
535 $ filteredchg log -r .
536 Sample count: * (glob)
537 $ filteredchg log -r . --no-profile
538 $ filteredchg log -r .
539 Sample count: * (glob)
General Comments 0
You need to be logged in to leave comments. Login now