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