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