##// END OF EJS Templates
repo-config: clarify why we seems to parse the repository "twice"...
marmoute -
r53318:8ac33b3f default
parent child Browse files
Show More
@@ -1,1389 +1,1392
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 from __future__ import annotations
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
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 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 .configuration import rcutil
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) -> None:
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 hasattr(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 hasattr(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 hasattr(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 hasattr(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) -> int:
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__(pycompat.sysstr(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 hasattr(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 711 if self.badalias or hasattr(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 hasattr(self, '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 # the specified path, might be defined in the [paths] section of the
990 # local repository. So we had to read the local config first even if it
991 # get overriden here.
989 992 path_obj = urlutil.get_clone_path_obj(lui, rpath)
990 993 path = path_obj.rawloc
991 994 lui = ui.copy()
992 995 if rcutil.use_repo_hgrc():
993 996 _readsharedsourceconfig(lui, path)
994 997 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
995 998 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
996 999
997 1000 if oldcwd:
998 1001 os.chdir(oldcwd)
999 1002
1000 1003 return path, lui
1001 1004
1002 1005
1003 1006 def _checkshellalias(lui, ui, args):
1004 1007 """Return the function to run the shell alias, if it is required"""
1005 1008 options = {}
1006 1009
1007 1010 try:
1008 1011 args = fancyopts.fancyopts(args, commands.globalopts, options)
1009 1012 except getopt.GetoptError:
1010 1013 return
1011 1014
1012 1015 if not args:
1013 1016 return
1014 1017
1015 1018 cmdtable = commands.table
1016 1019
1017 1020 cmd = args[0]
1018 1021 try:
1019 1022 strict = ui.configbool(b"ui", b"strict")
1020 1023 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1021 1024 except (error.AmbiguousCommand, error.UnknownCommand):
1022 1025 return
1023 1026
1024 1027 cmd = aliases[0]
1025 1028 fn = entry[0]
1026 1029
1027 1030 if cmd and hasattr(fn, 'shell'):
1028 1031 # shell alias shouldn't receive early options which are consumed by hg
1029 1032 _earlyopts, args = _earlysplitopts(args)
1030 1033 d = lambda: fn(ui, *args[1:])
1031 1034 return lambda: runcommand(
1032 1035 lui, None, cmd, args[:1], ui, options, d, [], {}
1033 1036 )
1034 1037
1035 1038
1036 1039 def _dispatch(req):
1037 1040 args = req.args
1038 1041 ui = req.ui
1039 1042
1040 1043 # check for cwd
1041 1044 cwd = req.earlyoptions[b'cwd']
1042 1045 if cwd:
1043 1046 os.chdir(cwd)
1044 1047
1045 1048 rpath = req.earlyoptions[b'repository']
1046 1049 path, lui = _getlocal(ui, rpath)
1047 1050
1048 1051 uis = {ui, lui}
1049 1052
1050 1053 if req.repo:
1051 1054 uis.add(req.repo.ui)
1052 1055
1053 1056 if (
1054 1057 req.earlyoptions[b'verbose']
1055 1058 or req.earlyoptions[b'debug']
1056 1059 or req.earlyoptions[b'quiet']
1057 1060 ):
1058 1061 for opt in (b'verbose', b'debug', b'quiet'):
1059 1062 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1060 1063 for ui_ in uis:
1061 1064 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1062 1065
1063 1066 if req.earlyoptions[b'profile']:
1064 1067 for ui_ in uis:
1065 1068 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1066 1069 elif req.earlyoptions[b'profile'] is False:
1067 1070 # Check for it being set already, so that we don't pollute the config
1068 1071 # with this when using chg in the very common case that it's not
1069 1072 # enabled.
1070 1073 if lui.configbool(b'profiling', b'enabled'):
1071 1074 # Only do this on lui so that `chg foo` with a user config setting
1072 1075 # profiling.enabled=1 still shows profiling information (chg will
1073 1076 # specify `--no-profile` when `hg serve` is starting up, we don't
1074 1077 # want that to propagate to every later invocation).
1075 1078 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1076 1079
1077 1080 profile = lui.configbool(b'profiling', b'enabled')
1078 1081 with profiling.profile(lui, enabled=profile) as profiler:
1079 1082 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1080 1083 # reposetup
1081 1084 extensions.loadall(lui)
1082 1085 # Propagate any changes to lui.__class__ by extensions
1083 1086 ui.__class__ = lui.__class__
1084 1087
1085 1088 # (uisetup and extsetup are handled in extensions.loadall)
1086 1089
1087 1090 # (reposetup is handled in hg.repository)
1088 1091
1089 1092 addaliases(lui, commands.table)
1090 1093
1091 1094 # All aliases and commands are completely defined, now.
1092 1095 # Check abbreviation/ambiguity of shell alias.
1093 1096 shellaliasfn = _checkshellalias(lui, ui, args)
1094 1097 if shellaliasfn:
1095 1098 # no additional configs will be set, set up the ui instances
1096 1099 for ui_ in uis:
1097 1100 extensions.populateui(ui_)
1098 1101 return shellaliasfn()
1099 1102
1100 1103 # check for fallback encoding
1101 1104 fallback = lui.config(b'ui', b'fallbackencoding')
1102 1105 if fallback:
1103 1106 encoding.fallbackencoding = fallback
1104 1107
1105 1108 fullargs = args
1106 1109 cmd, func, args, options, cmdoptions = _parse(lui, args)
1107 1110
1108 1111 # store the canonical command name in request object for later access
1109 1112 req.canonical_command = cmd
1110 1113
1111 1114 if options[b"config"] != req.earlyoptions[b"config"]:
1112 1115 raise error.InputError(_(b"option --config may not be abbreviated"))
1113 1116 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1114 1117 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1115 1118 if options[b"repository"] != req.earlyoptions[b"repository"]:
1116 1119 raise error.InputError(
1117 1120 _(
1118 1121 b"option -R has to be separated from other options (e.g. not "
1119 1122 b"-qR) and --repository may only be abbreviated as --repo"
1120 1123 )
1121 1124 )
1122 1125 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1123 1126 raise error.InputError(
1124 1127 _(b"option --debugger may not be abbreviated")
1125 1128 )
1126 1129 # don't validate --profile/--traceback, which can be enabled from now
1127 1130
1128 1131 if options[b"encoding"]:
1129 1132 encoding.encoding = options[b"encoding"]
1130 1133 if options[b"encodingmode"]:
1131 1134 encoding.encodingmode = options[b"encodingmode"]
1132 1135 if options[b"time"]:
1133 1136
1134 1137 def get_times():
1135 1138 t = os.times()
1136 1139 if t[4] == 0.0:
1137 1140 # Windows leaves this as zero, so use time.perf_counter()
1138 1141 t = (t[0], t[1], t[2], t[3], util.timer())
1139 1142 return t
1140 1143
1141 1144 s = get_times()
1142 1145
1143 1146 def print_time():
1144 1147 t = get_times()
1145 1148 ui.warn(
1146 1149 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1147 1150 % (
1148 1151 t[4] - s[4],
1149 1152 t[0] - s[0],
1150 1153 t[2] - s[2],
1151 1154 t[1] - s[1],
1152 1155 t[3] - s[3],
1153 1156 )
1154 1157 )
1155 1158
1156 1159 ui.atexit(print_time)
1157 1160 if options[b"profile"]:
1158 1161 profiler.start()
1159 1162
1160 1163 # if abbreviated version of this were used, take them in account, now
1161 1164 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1162 1165 for opt in (b'verbose', b'debug', b'quiet'):
1163 1166 if options[opt] == req.earlyoptions[opt]:
1164 1167 continue
1165 1168 val = pycompat.bytestr(bool(options[opt]))
1166 1169 for ui_ in uis:
1167 1170 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1168 1171
1169 1172 if options[b'traceback']:
1170 1173 for ui_ in uis:
1171 1174 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1172 1175
1173 1176 if options[b'noninteractive']:
1174 1177 for ui_ in uis:
1175 1178 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1176 1179
1177 1180 if cmdoptions.get(b'insecure', False):
1178 1181 for ui_ in uis:
1179 1182 ui_.insecureconnections = True
1180 1183
1181 1184 # setup color handling before pager, because setting up pager
1182 1185 # might cause incorrect console information
1183 1186 coloropt = options[b'color']
1184 1187 for ui_ in uis:
1185 1188 if coloropt:
1186 1189 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1187 1190 color.setup(ui_)
1188 1191
1189 1192 if stringutil.parsebool(options[b'pager']):
1190 1193 # ui.pager() expects 'internal-always-' prefix in this case
1191 1194 ui.pager(b'internal-always-' + cmd)
1192 1195 elif options[b'pager'] != b'auto':
1193 1196 for ui_ in uis:
1194 1197 ui_.disablepager()
1195 1198
1196 1199 # configs are fully loaded, set up the ui instances
1197 1200 for ui_ in uis:
1198 1201 extensions.populateui(ui_)
1199 1202
1200 1203 if options[b'version']:
1201 1204 return commands.version_(ui)
1202 1205 if options[b'help']:
1203 1206 return commands.help_(ui, cmd, command=cmd is not None)
1204 1207 elif not cmd:
1205 1208 return commands.help_(ui, b'shortlist')
1206 1209
1207 1210 repo = None
1208 1211 cmdpats = args[:]
1209 1212 assert func is not None # help out pytype
1210 1213 if not func.norepo:
1211 1214 # use the repo from the request only if we don't have -R
1212 1215 if not rpath and not cwd:
1213 1216 repo = req.repo
1214 1217
1215 1218 if repo:
1216 1219 # set the descriptors of the repo ui to those of ui
1217 1220 repo.ui.fin = ui.fin
1218 1221 repo.ui.fout = ui.fout
1219 1222 repo.ui.ferr = ui.ferr
1220 1223 repo.ui.fmsg = ui.fmsg
1221 1224 else:
1222 1225 try:
1223 1226 repo = hg.repository(
1224 1227 ui,
1225 1228 path=path,
1226 1229 presetupfuncs=req.prereposetups,
1227 1230 intents=func.intents,
1228 1231 )
1229 1232 if not repo.local():
1230 1233 raise error.InputError(
1231 1234 _(b"repository '%s' is not local") % path
1232 1235 )
1233 1236 repo.ui.setconfig(
1234 1237 b"bundle", b"mainreporoot", repo.root, b'repo'
1235 1238 )
1236 1239 except error.RequirementError:
1237 1240 raise
1238 1241 except error.RepoError:
1239 1242 if rpath: # invalid -R path
1240 1243 raise
1241 1244 if not func.optionalrepo:
1242 1245 if func.inferrepo and args and not path:
1243 1246 # try to infer -R from command args
1244 1247 repos = pycompat.maplist(cmdutil.findrepo, args)
1245 1248 guess = repos[0]
1246 1249 if guess and repos.count(guess) == len(repos):
1247 1250 req.args = [b'--repository', guess] + fullargs
1248 1251 req.earlyoptions[b'repository'] = guess
1249 1252 return _dispatch(req)
1250 1253 if not path:
1251 1254 raise error.InputError(
1252 1255 _(
1253 1256 b"no repository found in"
1254 1257 b" '%s' (.hg not found)"
1255 1258 )
1256 1259 % encoding.getcwd()
1257 1260 )
1258 1261 raise
1259 1262 if repo:
1260 1263 ui = repo.ui
1261 1264 if options[b'hidden']:
1262 1265 repo = repo.unfiltered()
1263 1266 args.insert(0, repo)
1264 1267 elif rpath:
1265 1268 ui.warn(_(b"warning: --repository ignored\n"))
1266 1269
1267 1270 msg = _formatargs(fullargs)
1268 1271 ui.log(b"command", b'%s\n', msg)
1269 1272 strcmdopt = pycompat.strkwargs(cmdoptions)
1270 1273 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1271 1274 try:
1272 1275 return runcommand(
1273 1276 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1274 1277 )
1275 1278 finally:
1276 1279 if repo and repo != req.repo:
1277 1280 repo.close()
1278 1281
1279 1282
1280 1283 def _runcommand(ui, options, cmd, cmdfunc):
1281 1284 """Run a command function, possibly with profiling enabled."""
1282 1285 try:
1283 1286 with tracing.log("Running %s command" % cmd):
1284 1287 return cmdfunc()
1285 1288 except error.SignatureError:
1286 1289 raise error.CommandError(cmd, _(b'invalid arguments'))
1287 1290
1288 1291
1289 1292 def _exceptionwarning(ui):
1290 1293 """Produce a warning message for the current active exception"""
1291 1294
1292 1295 # For compatibility checking, we discard the portion of the hg
1293 1296 # version after the + on the assumption that if a "normal
1294 1297 # user" is running a build with a + in it the packager
1295 1298 # probably built from fairly close to a tag and anyone with a
1296 1299 # 'make local' copy of hg (where the version number can be out
1297 1300 # of date) will be clueful enough to notice the implausible
1298 1301 # version number and try updating.
1299 1302 ct = util.versiontuple(n=2)
1300 1303 worst = None, ct, b'', b''
1301 1304 if ui.config(b'ui', b'supportcontact') is None:
1302 1305 for name, mod in extensions.extensions():
1303 1306 # 'testedwith' should be bytes, but not all extensions are ported
1304 1307 # to py3 and we don't want UnicodeException because of that.
1305 1308 testedwith = stringutil.forcebytestr(
1306 1309 getattr(mod, 'testedwith', b'')
1307 1310 )
1308 1311 version = extensions.moduleversion(mod)
1309 1312 report = getattr(mod, 'buglink', _(b'the extension author.'))
1310 1313 if not testedwith.strip():
1311 1314 # We found an untested extension. It's likely the culprit.
1312 1315 worst = name, b'unknown', report, version
1313 1316 break
1314 1317
1315 1318 # Never blame on extensions bundled with Mercurial.
1316 1319 if extensions.ismoduleinternal(mod):
1317 1320 continue
1318 1321
1319 1322 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1320 1323 if ct in tested:
1321 1324 continue
1322 1325
1323 1326 lower = [t for t in tested if t < ct]
1324 1327 nearest = max(lower or tested)
1325 1328 if worst[0] is None or nearest < worst[1]:
1326 1329 worst = name, nearest, report, version
1327 1330 if worst[0] is not None:
1328 1331 name, testedwith, report, version = worst
1329 1332 if not isinstance(testedwith, (bytes, str)):
1330 1333 testedwith = b'.'.join(
1331 1334 [stringutil.forcebytestr(c) for c in testedwith]
1332 1335 )
1333 1336 extver = version or _(b"(version N/A)")
1334 1337 warning = _(
1335 1338 b'** Unknown exception encountered with '
1336 1339 b'possibly-broken third-party extension "%s" %s\n'
1337 1340 b'** which supports versions %s of Mercurial.\n'
1338 1341 b'** Please disable "%s" and try your action again.\n'
1339 1342 b'** If that fixes the bug please report it to %s\n'
1340 1343 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1341 1344 else:
1342 1345 bugtracker = ui.config(b'ui', b'supportcontact')
1343 1346 if bugtracker is None:
1344 1347 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1345 1348 warning = (
1346 1349 _(
1347 1350 b"** unknown exception encountered, "
1348 1351 b"please report by visiting\n** "
1349 1352 )
1350 1353 + bugtracker
1351 1354 + b'\n'
1352 1355 )
1353 1356 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1354 1357
1355 1358 def ext_with_ver(x):
1356 1359 ext = x[0]
1357 1360 ver = extensions.moduleversion(x[1])
1358 1361 if ver:
1359 1362 ext += b' ' + ver
1360 1363 return ext
1361 1364
1362 1365 warning += (
1363 1366 (_(b"** Python %s\n") % sysversion)
1364 1367 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1365 1368 + (
1366 1369 _(b"** Extensions loaded: %s\n")
1367 1370 % b", ".join(
1368 1371 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1369 1372 )
1370 1373 )
1371 1374 )
1372 1375 return warning
1373 1376
1374 1377
1375 1378 def handlecommandexception(ui):
1376 1379 """Produce a warning message for broken commands
1377 1380
1378 1381 Called when handling an exception; the exception is reraised if
1379 1382 this function returns False, ignored otherwise.
1380 1383 """
1381 1384 warning = _exceptionwarning(ui)
1382 1385 ui.log(
1383 1386 b"commandexception",
1384 1387 b"%s\n%s\n",
1385 1388 warning,
1386 1389 pycompat.sysbytes(traceback.format_exc()),
1387 1390 )
1388 1391 ui.warn(warning)
1389 1392 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now