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