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