##// END OF EJS Templates
chg: make `chg --cwd $anything -R $relativepath` behave better...
Valentin Gatien-Baron -
r50036:22fc6945 stable draft
parent child Browse files
Show More
@@ -1,1390 +1,1392 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 and not status:
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 formattedargs = _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 formattedargs,
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 if wd is not None:
995 rpath = os.path.join(wd, rpath)
994 996 path = urlutil.get_clone_path(lui, rpath)[0]
995 997 lui = ui.copy()
996 998 if rcutil.use_repo_hgrc():
997 999 _readsharedsourceconfig(lui, path)
998 1000 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
999 1001 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
1000 1002
1001 1003 return path, lui
1002 1004
1003 1005
1004 1006 def _checkshellalias(lui, ui, args):
1005 1007 """Return the function to run the shell alias, if it is required"""
1006 1008 options = {}
1007 1009
1008 1010 try:
1009 1011 args = fancyopts.fancyopts(args, commands.globalopts, options)
1010 1012 except getopt.GetoptError:
1011 1013 return
1012 1014
1013 1015 if not args:
1014 1016 return
1015 1017
1016 1018 cmdtable = commands.table
1017 1019
1018 1020 cmd = args[0]
1019 1021 try:
1020 1022 strict = ui.configbool(b"ui", b"strict")
1021 1023 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1022 1024 except (error.AmbiguousCommand, error.UnknownCommand):
1023 1025 return
1024 1026
1025 1027 cmd = aliases[0]
1026 1028 fn = entry[0]
1027 1029
1028 1030 if cmd and util.safehasattr(fn, b'shell'):
1029 1031 # shell alias shouldn't receive early options which are consumed by hg
1030 1032 _earlyopts, args = _earlysplitopts(args)
1031 1033 d = lambda: fn(ui, *args[1:])
1032 1034 return lambda: runcommand(
1033 1035 lui, None, cmd, args[:1], ui, options, d, [], {}
1034 1036 )
1035 1037
1036 1038
1037 1039 def _dispatch(req):
1038 1040 args = req.args
1039 1041 ui = req.ui
1040 1042
1041 1043 # check for cwd
1042 1044 cwd = req.earlyoptions[b'cwd']
1043 1045 if cwd:
1044 1046 os.chdir(cwd)
1045 1047
1046 1048 rpath = req.earlyoptions[b'repository']
1047 1049 path, lui = _getlocal(ui, rpath)
1048 1050
1049 1051 uis = {ui, lui}
1050 1052
1051 1053 if req.repo:
1052 1054 uis.add(req.repo.ui)
1053 1055
1054 1056 if (
1055 1057 req.earlyoptions[b'verbose']
1056 1058 or req.earlyoptions[b'debug']
1057 1059 or req.earlyoptions[b'quiet']
1058 1060 ):
1059 1061 for opt in (b'verbose', b'debug', b'quiet'):
1060 1062 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1061 1063 for ui_ in uis:
1062 1064 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1063 1065
1064 1066 if req.earlyoptions[b'profile']:
1065 1067 for ui_ in uis:
1066 1068 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1067 1069 elif req.earlyoptions[b'profile'] is False:
1068 1070 # Check for it being set already, so that we don't pollute the config
1069 1071 # with this when using chg in the very common case that it's not
1070 1072 # enabled.
1071 1073 if lui.configbool(b'profiling', b'enabled'):
1072 1074 # Only do this on lui so that `chg foo` with a user config setting
1073 1075 # profiling.enabled=1 still shows profiling information (chg will
1074 1076 # specify `--no-profile` when `hg serve` is starting up, we don't
1075 1077 # want that to propagate to every later invocation).
1076 1078 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1077 1079
1078 1080 profile = lui.configbool(b'profiling', b'enabled')
1079 1081 with profiling.profile(lui, enabled=profile) as profiler:
1080 1082 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1081 1083 # reposetup
1082 1084 extensions.loadall(lui)
1083 1085 # Propagate any changes to lui.__class__ by extensions
1084 1086 ui.__class__ = lui.__class__
1085 1087
1086 1088 # (uisetup and extsetup are handled in extensions.loadall)
1087 1089
1088 1090 # (reposetup is handled in hg.repository)
1089 1091
1090 1092 addaliases(lui, commands.table)
1091 1093
1092 1094 # All aliases and commands are completely defined, now.
1093 1095 # Check abbreviation/ambiguity of shell alias.
1094 1096 shellaliasfn = _checkshellalias(lui, ui, args)
1095 1097 if shellaliasfn:
1096 1098 # no additional configs will be set, set up the ui instances
1097 1099 for ui_ in uis:
1098 1100 extensions.populateui(ui_)
1099 1101 return shellaliasfn()
1100 1102
1101 1103 # check for fallback encoding
1102 1104 fallback = lui.config(b'ui', b'fallbackencoding')
1103 1105 if fallback:
1104 1106 encoding.fallbackencoding = fallback
1105 1107
1106 1108 fullargs = args
1107 1109 cmd, func, args, options, cmdoptions = _parse(lui, args)
1108 1110
1109 1111 # store the canonical command name in request object for later access
1110 1112 req.canonical_command = cmd
1111 1113
1112 1114 if options[b"config"] != req.earlyoptions[b"config"]:
1113 1115 raise error.InputError(_(b"option --config may not be abbreviated"))
1114 1116 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1115 1117 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1116 1118 if options[b"repository"] != req.earlyoptions[b"repository"]:
1117 1119 raise error.InputError(
1118 1120 _(
1119 1121 b"option -R has to be separated from other options (e.g. not "
1120 1122 b"-qR) and --repository may only be abbreviated as --repo"
1121 1123 )
1122 1124 )
1123 1125 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1124 1126 raise error.InputError(
1125 1127 _(b"option --debugger may not be abbreviated")
1126 1128 )
1127 1129 # don't validate --profile/--traceback, which can be enabled from now
1128 1130
1129 1131 if options[b"encoding"]:
1130 1132 encoding.encoding = options[b"encoding"]
1131 1133 if options[b"encodingmode"]:
1132 1134 encoding.encodingmode = options[b"encodingmode"]
1133 1135 if options[b"time"]:
1134 1136
1135 1137 def get_times():
1136 1138 t = os.times()
1137 1139 if t[4] == 0.0:
1138 1140 # Windows leaves this as zero, so use time.perf_counter()
1139 1141 t = (t[0], t[1], t[2], t[3], util.timer())
1140 1142 return t
1141 1143
1142 1144 s = get_times()
1143 1145
1144 1146 def print_time():
1145 1147 t = get_times()
1146 1148 ui.warn(
1147 1149 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1148 1150 % (
1149 1151 t[4] - s[4],
1150 1152 t[0] - s[0],
1151 1153 t[2] - s[2],
1152 1154 t[1] - s[1],
1153 1155 t[3] - s[3],
1154 1156 )
1155 1157 )
1156 1158
1157 1159 ui.atexit(print_time)
1158 1160 if options[b"profile"]:
1159 1161 profiler.start()
1160 1162
1161 1163 # if abbreviated version of this were used, take them in account, now
1162 1164 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1163 1165 for opt in (b'verbose', b'debug', b'quiet'):
1164 1166 if options[opt] == req.earlyoptions[opt]:
1165 1167 continue
1166 1168 val = pycompat.bytestr(bool(options[opt]))
1167 1169 for ui_ in uis:
1168 1170 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1169 1171
1170 1172 if options[b'traceback']:
1171 1173 for ui_ in uis:
1172 1174 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1173 1175
1174 1176 if options[b'noninteractive']:
1175 1177 for ui_ in uis:
1176 1178 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1177 1179
1178 1180 if cmdoptions.get(b'insecure', False):
1179 1181 for ui_ in uis:
1180 1182 ui_.insecureconnections = True
1181 1183
1182 1184 # setup color handling before pager, because setting up pager
1183 1185 # might cause incorrect console information
1184 1186 coloropt = options[b'color']
1185 1187 for ui_ in uis:
1186 1188 if coloropt:
1187 1189 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1188 1190 color.setup(ui_)
1189 1191
1190 1192 if stringutil.parsebool(options[b'pager']):
1191 1193 # ui.pager() expects 'internal-always-' prefix in this case
1192 1194 ui.pager(b'internal-always-' + cmd)
1193 1195 elif options[b'pager'] != b'auto':
1194 1196 for ui_ in uis:
1195 1197 ui_.disablepager()
1196 1198
1197 1199 # configs are fully loaded, set up the ui instances
1198 1200 for ui_ in uis:
1199 1201 extensions.populateui(ui_)
1200 1202
1201 1203 if options[b'version']:
1202 1204 return commands.version_(ui)
1203 1205 if options[b'help']:
1204 1206 return commands.help_(ui, cmd, command=cmd is not None)
1205 1207 elif not cmd:
1206 1208 return commands.help_(ui, b'shortlist')
1207 1209
1208 1210 repo = None
1209 1211 cmdpats = args[:]
1210 1212 assert func is not None # help out pytype
1211 1213 if not func.norepo:
1212 1214 # use the repo from the request only if we don't have -R
1213 1215 if not rpath and not cwd:
1214 1216 repo = req.repo
1215 1217
1216 1218 if repo:
1217 1219 # set the descriptors of the repo ui to those of ui
1218 1220 repo.ui.fin = ui.fin
1219 1221 repo.ui.fout = ui.fout
1220 1222 repo.ui.ferr = ui.ferr
1221 1223 repo.ui.fmsg = ui.fmsg
1222 1224 else:
1223 1225 try:
1224 1226 repo = hg.repository(
1225 1227 ui,
1226 1228 path=path,
1227 1229 presetupfuncs=req.prereposetups,
1228 1230 intents=func.intents,
1229 1231 )
1230 1232 if not repo.local():
1231 1233 raise error.InputError(
1232 1234 _(b"repository '%s' is not local") % path
1233 1235 )
1234 1236 repo.ui.setconfig(
1235 1237 b"bundle", b"mainreporoot", repo.root, b'repo'
1236 1238 )
1237 1239 except error.RequirementError:
1238 1240 raise
1239 1241 except error.RepoError:
1240 1242 if rpath: # invalid -R path
1241 1243 raise
1242 1244 if not func.optionalrepo:
1243 1245 if func.inferrepo and args and not path:
1244 1246 # try to infer -R from command args
1245 1247 repos = pycompat.maplist(cmdutil.findrepo, args)
1246 1248 guess = repos[0]
1247 1249 if guess and repos.count(guess) == len(repos):
1248 1250 req.args = [b'--repository', guess] + fullargs
1249 1251 req.earlyoptions[b'repository'] = guess
1250 1252 return _dispatch(req)
1251 1253 if not path:
1252 1254 raise error.InputError(
1253 1255 _(
1254 1256 b"no repository found in"
1255 1257 b" '%s' (.hg not found)"
1256 1258 )
1257 1259 % encoding.getcwd()
1258 1260 )
1259 1261 raise
1260 1262 if repo:
1261 1263 ui = repo.ui
1262 1264 if options[b'hidden']:
1263 1265 repo = repo.unfiltered()
1264 1266 args.insert(0, repo)
1265 1267 elif rpath:
1266 1268 ui.warn(_(b"warning: --repository ignored\n"))
1267 1269
1268 1270 msg = _formatargs(fullargs)
1269 1271 ui.log(b"command", b'%s\n', msg)
1270 1272 strcmdopt = pycompat.strkwargs(cmdoptions)
1271 1273 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1272 1274 try:
1273 1275 return runcommand(
1274 1276 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1275 1277 )
1276 1278 finally:
1277 1279 if repo and repo != req.repo:
1278 1280 repo.close()
1279 1281
1280 1282
1281 1283 def _runcommand(ui, options, cmd, cmdfunc):
1282 1284 """Run a command function, possibly with profiling enabled."""
1283 1285 try:
1284 1286 with tracing.log("Running %s command" % cmd):
1285 1287 return cmdfunc()
1286 1288 except error.SignatureError:
1287 1289 raise error.CommandError(cmd, _(b'invalid arguments'))
1288 1290
1289 1291
1290 1292 def _exceptionwarning(ui):
1291 1293 """Produce a warning message for the current active exception"""
1292 1294
1293 1295 # For compatibility checking, we discard the portion of the hg
1294 1296 # version after the + on the assumption that if a "normal
1295 1297 # user" is running a build with a + in it the packager
1296 1298 # probably built from fairly close to a tag and anyone with a
1297 1299 # 'make local' copy of hg (where the version number can be out
1298 1300 # of date) will be clueful enough to notice the implausible
1299 1301 # version number and try updating.
1300 1302 ct = util.versiontuple(n=2)
1301 1303 worst = None, ct, b'', b''
1302 1304 if ui.config(b'ui', b'supportcontact') is None:
1303 1305 for name, mod in extensions.extensions():
1304 1306 # 'testedwith' should be bytes, but not all extensions are ported
1305 1307 # to py3 and we don't want UnicodeException because of that.
1306 1308 testedwith = stringutil.forcebytestr(
1307 1309 getattr(mod, 'testedwith', b'')
1308 1310 )
1309 1311 version = extensions.moduleversion(mod)
1310 1312 report = getattr(mod, 'buglink', _(b'the extension author.'))
1311 1313 if not testedwith.strip():
1312 1314 # We found an untested extension. It's likely the culprit.
1313 1315 worst = name, b'unknown', report, version
1314 1316 break
1315 1317
1316 1318 # Never blame on extensions bundled with Mercurial.
1317 1319 if extensions.ismoduleinternal(mod):
1318 1320 continue
1319 1321
1320 1322 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1321 1323 if ct in tested:
1322 1324 continue
1323 1325
1324 1326 lower = [t for t in tested if t < ct]
1325 1327 nearest = max(lower or tested)
1326 1328 if worst[0] is None or nearest < worst[1]:
1327 1329 worst = name, nearest, report, version
1328 1330 if worst[0] is not None:
1329 1331 name, testedwith, report, version = worst
1330 1332 if not isinstance(testedwith, (bytes, str)):
1331 1333 testedwith = b'.'.join(
1332 1334 [stringutil.forcebytestr(c) for c in testedwith]
1333 1335 )
1334 1336 extver = version or _(b"(version N/A)")
1335 1337 warning = _(
1336 1338 b'** Unknown exception encountered with '
1337 1339 b'possibly-broken third-party extension "%s" %s\n'
1338 1340 b'** which supports versions %s of Mercurial.\n'
1339 1341 b'** Please disable "%s" and try your action again.\n'
1340 1342 b'** If that fixes the bug please report it to %s\n'
1341 1343 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1342 1344 else:
1343 1345 bugtracker = ui.config(b'ui', b'supportcontact')
1344 1346 if bugtracker is None:
1345 1347 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1346 1348 warning = (
1347 1349 _(
1348 1350 b"** unknown exception encountered, "
1349 1351 b"please report by visiting\n** "
1350 1352 )
1351 1353 + bugtracker
1352 1354 + b'\n'
1353 1355 )
1354 1356 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1355 1357
1356 1358 def ext_with_ver(x):
1357 1359 ext = x[0]
1358 1360 ver = extensions.moduleversion(x[1])
1359 1361 if ver:
1360 1362 ext += b' ' + ver
1361 1363 return ext
1362 1364
1363 1365 warning += (
1364 1366 (_(b"** Python %s\n") % sysversion)
1365 1367 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1366 1368 + (
1367 1369 _(b"** Extensions loaded: %s\n")
1368 1370 % b", ".join(
1369 1371 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1370 1372 )
1371 1373 )
1372 1374 )
1373 1375 return warning
1374 1376
1375 1377
1376 1378 def handlecommandexception(ui):
1377 1379 """Produce a warning message for broken commands
1378 1380
1379 1381 Called when handling an exception; the exception is reraised if
1380 1382 this function returns False, ignored otherwise.
1381 1383 """
1382 1384 warning = _exceptionwarning(ui)
1383 1385 ui.log(
1384 1386 b"commandexception",
1385 1387 b"%s\n%s\n",
1386 1388 warning,
1387 1389 pycompat.sysbytes(traceback.format_exc()),
1388 1390 )
1389 1391 ui.warn(warning)
1390 1392 return False # re-raise the exception
@@ -1,539 +1,549 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 -R is interpreted relative to --cwd
437
438 $ hg init repo1
439 $ hg init repo2
440 $ printf "[alias]\nx=y\n" >> repo1/.hg/hgrc
441 $ printf "[alias]\nx=z\n" >> repo2/.hg/hgrc
442 $ (cd repo1; chg --cwd ../repo2 -R . show alias.x)
443 z
444
436 445 Test that chg works (sets to the user's actual LC_CTYPE) even when python
437 446 "coerces" the locale (py3.7+)
438 447
439 448 $ cat > $TESTTMP/debugenv.py <<EOF
440 449 > from mercurial import encoding
441 450 > from mercurial import registrar
442 451 > cmdtable = {}
443 452 > command = registrar.command(cmdtable)
444 453 > @command(b'debugenv', [], b'', norepo=True)
445 454 > def debugenv(ui):
446 455 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
447 456 > v = encoding.environ.get(k)
448 457 > if v is not None:
449 458 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
450 459 > EOF
451 460 (hg keeps python's modified LC_CTYPE, chg doesn't)
452 461 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
453 462 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
454 463 LC_CTYPE=C.UTF-8 (py37 !)
455 464 LC_CTYPE= (no-py37 !)
456 465 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
457 466 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
458 467 LC_CTYPE=
459 468 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
460 469 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
461 470 *cannot change locale* (glob) (?)
462 471 LC_CTYPE=unsupported_value
463 472 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
464 473 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
465 474 LC_CTYPE=
466 475 $ LANG= LC_ALL= LC_CTYPE= chg \
467 476 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
468 477 LC_ALL=
469 478 LC_CTYPE=
470 479 LANG=
471 480
472 481 Profiling isn't permanently enabled or carried over between chg invocations that
473 482 share the same server
474 483 $ cp $HGRCPATH.orig $HGRCPATH
475 484 $ hg init $TESTTMP/profiling
476 485 $ cd $TESTTMP/profiling
477 486 $ filteredchg() {
478 487 > CHGDEBUG=1 chg "$@" 2>&1 | sed -rn 's_^No samples recorded.*$_Sample count: 0_; /Sample count/p; /start cmdserver/p'
479 488 > }
480 489 $ newchg() {
481 490 > chg --kill-chg-daemon
482 491 > filteredchg "$@" | egrep -v 'start cmdserver' || true
483 492 > }
484 493 (--profile isn't permanently on just because it was specified when chg was
485 494 started)
486 495 $ newchg log -r . --profile
487 496 Sample count: * (glob)
488 497 $ filteredchg log -r .
489 498 (enabling profiling via config works, even on the first chg command that starts
490 499 a cmdserver)
491 500 $ cat >> $HGRCPATH <<EOF
492 501 > [profiling]
493 502 > type=stat
494 503 > enabled=1
495 504 > EOF
496 505 $ newchg log -r .
497 506 Sample count: * (glob)
498 507 $ filteredchg log -r .
499 508 Sample count: * (glob)
500 509 (test that we aren't accumulating more and more samples each run)
501 510 $ cat > $TESTTMP/debugsleep.py <<EOF
502 511 > import time
503 512 > from mercurial import registrar
504 513 > cmdtable = {}
505 514 > command = registrar.command(cmdtable)
506 515 > @command(b'debugsleep', [], b'', norepo=True)
507 516 > def debugsleep(ui):
508 517 > start = time.time()
509 518 > x = 0
510 519 > while time.time() < start + 0.5:
511 520 > time.sleep(.1)
512 521 > x += 1
513 522 > ui.status(b'%d debugsleep iterations in %.03fs\n' % (x, time.time() - start))
514 523 > EOF
515 524 $ cat >> $HGRCPATH <<EOF
516 525 > [extensions]
517 526 > debugsleep = $TESTTMP/debugsleep.py
518 527 > EOF
519 528 $ newchg debugsleep > run_1
520 529 $ filteredchg debugsleep > run_2
521 530 $ filteredchg debugsleep > run_3
522 531 $ filteredchg debugsleep > run_4
523 532 FIXME: Run 4 should not be >3x Run 1's number of samples.
524 533 $ "$PYTHON" <<EOF
525 534 > r1 = int(open("run_1", "r").read().split()[-1])
526 535 > r4 = int(open("run_4", "r").read().split()[-1])
527 536 > print("Run 1: %d samples\nRun 4: %d samples\nRun 4 > 3 * Run 1: %s" %
528 537 > (r1, r4, r4 > (r1 * 3)))
529 538 > EOF
530 539 Run 1: * samples (glob)
531 540 Run 4: * samples (glob)
532 541 Run 4 > 3 * Run 1: False
533 542 (Disabling with --no-profile on the commandline still works, but isn't permanent)
534 543 $ newchg log -r . --no-profile
535 544 $ filteredchg log -r .
536 545 Sample count: * (glob)
537 546 $ filteredchg log -r . --no-profile
538 547 $ filteredchg log -r .
539 548 Sample count: * (glob)
549 $ cd ../
General Comments 0
You need to be logged in to leave comments. Login now