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