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