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