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