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