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