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