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