##// END OF EJS Templates
dispatch: always load extensions before running shell aliases (issue5230)...
Jun Wu -
r29132:12769703 default
parent child Browse files
Show More
@@ -1,1094 +1,1077 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import atexit
11 11 import difflib
12 12 import errno
13 13 import os
14 14 import pdb
15 15 import re
16 16 import shlex
17 17 import signal
18 18 import socket
19 19 import sys
20 20 import time
21 21 import traceback
22 22
23 23
24 24 from .i18n import _
25 25
26 26 from . import (
27 27 cmdutil,
28 28 commands,
29 29 demandimport,
30 30 encoding,
31 31 error,
32 32 extensions,
33 33 fancyopts,
34 34 fileset,
35 35 hg,
36 36 hook,
37 37 revset,
38 38 templatefilters,
39 39 templatekw,
40 40 templater,
41 41 ui as uimod,
42 42 util,
43 43 )
44 44
45 45 class request(object):
46 46 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
47 47 ferr=None):
48 48 self.args = args
49 49 self.ui = ui
50 50 self.repo = repo
51 51
52 52 # input/output/error streams
53 53 self.fin = fin
54 54 self.fout = fout
55 55 self.ferr = ferr
56 56
57 57 def run():
58 58 "run the command in sys.argv"
59 59 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
60 60
61 61 def _getsimilar(symbols, value):
62 62 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
63 63 # The cutoff for similarity here is pretty arbitrary. It should
64 64 # probably be investigated and tweaked.
65 65 return [s for s in symbols if sim(s) > 0.6]
66 66
67 67 def _reportsimilar(write, similar):
68 68 if len(similar) == 1:
69 69 write(_("(did you mean %s?)\n") % similar[0])
70 70 elif similar:
71 71 ss = ", ".join(sorted(similar))
72 72 write(_("(did you mean one of %s?)\n") % ss)
73 73
74 74 def _formatparse(write, inst):
75 75 similar = []
76 76 if isinstance(inst, error.UnknownIdentifier):
77 77 # make sure to check fileset first, as revset can invoke fileset
78 78 similar = _getsimilar(inst.symbols, inst.function)
79 79 if len(inst.args) > 1:
80 80 write(_("hg: parse error at %s: %s\n") %
81 81 (inst.args[1], inst.args[0]))
82 82 if (inst.args[0][0] == ' '):
83 83 write(_("unexpected leading whitespace\n"))
84 84 else:
85 85 write(_("hg: parse error: %s\n") % inst.args[0])
86 86 _reportsimilar(write, similar)
87 87 if inst.hint:
88 88 write(_("(%s)\n") % inst.hint)
89 89
90 90 def dispatch(req):
91 91 "run the command specified in req.args"
92 92 if req.ferr:
93 93 ferr = req.ferr
94 94 elif req.ui:
95 95 ferr = req.ui.ferr
96 96 else:
97 97 ferr = sys.stderr
98 98
99 99 try:
100 100 if not req.ui:
101 101 req.ui = uimod.ui()
102 102 if '--traceback' in req.args:
103 103 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
104 104
105 105 # set ui streams from the request
106 106 if req.fin:
107 107 req.ui.fin = req.fin
108 108 if req.fout:
109 109 req.ui.fout = req.fout
110 110 if req.ferr:
111 111 req.ui.ferr = req.ferr
112 112 except error.Abort as inst:
113 113 ferr.write(_("abort: %s\n") % inst)
114 114 if inst.hint:
115 115 ferr.write(_("(%s)\n") % inst.hint)
116 116 return -1
117 117 except error.ParseError as inst:
118 118 _formatparse(ferr.write, inst)
119 119 return -1
120 120
121 121 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
122 122 starttime = time.time()
123 123 ret = None
124 124 try:
125 125 ret = _runcatch(req)
126 126 except KeyboardInterrupt:
127 127 try:
128 128 req.ui.warn(_("interrupted!\n"))
129 129 except IOError as inst:
130 130 if inst.errno != errno.EPIPE:
131 131 raise
132 132 ret = -1
133 133 finally:
134 134 duration = time.time() - starttime
135 135 req.ui.flush()
136 136 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
137 137 msg, ret or 0, duration)
138 138 return ret
139 139
140 140 def _runcatch(req):
141 141 def catchterm(*args):
142 142 raise error.SignalInterrupt
143 143
144 144 ui = req.ui
145 145 try:
146 146 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
147 147 num = getattr(signal, name, None)
148 148 if num:
149 149 signal.signal(num, catchterm)
150 150 except ValueError:
151 151 pass # happens if called in a thread
152 152
153 153 try:
154 154 try:
155 155 debugger = 'pdb'
156 156 debugtrace = {
157 157 'pdb' : pdb.set_trace
158 158 }
159 159 debugmortem = {
160 160 'pdb' : pdb.post_mortem
161 161 }
162 162
163 163 # read --config before doing anything else
164 164 # (e.g. to change trust settings for reading .hg/hgrc)
165 165 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
166 166
167 167 if req.repo:
168 168 # copy configs that were passed on the cmdline (--config) to
169 169 # the repo ui
170 170 for sec, name, val in cfgs:
171 171 req.repo.ui.setconfig(sec, name, val, source='--config')
172 172
173 173 # developer config: ui.debugger
174 174 debugger = ui.config("ui", "debugger")
175 175 debugmod = pdb
176 176 if not debugger or ui.plain():
177 177 # if we are in HGPLAIN mode, then disable custom debugging
178 178 debugger = 'pdb'
179 179 elif '--debugger' in req.args:
180 180 # This import can be slow for fancy debuggers, so only
181 181 # do it when absolutely necessary, i.e. when actual
182 182 # debugging has been requested
183 183 with demandimport.deactivated():
184 184 try:
185 185 debugmod = __import__(debugger)
186 186 except ImportError:
187 187 pass # Leave debugmod = pdb
188 188
189 189 debugtrace[debugger] = debugmod.set_trace
190 190 debugmortem[debugger] = debugmod.post_mortem
191 191
192 192 # enter the debugger before command execution
193 193 if '--debugger' in req.args:
194 194 ui.warn(_("entering debugger - "
195 195 "type c to continue starting hg or h for help\n"))
196 196
197 197 if (debugger != 'pdb' and
198 198 debugtrace[debugger] == debugtrace['pdb']):
199 199 ui.warn(_("%s debugger specified "
200 200 "but its module was not found\n") % debugger)
201 201 with demandimport.deactivated():
202 202 debugtrace[debugger]()
203 203 try:
204 204 return _dispatch(req)
205 205 finally:
206 206 ui.flush()
207 207 except: # re-raises
208 208 # enter the debugger when we hit an exception
209 209 if '--debugger' in req.args:
210 210 traceback.print_exc()
211 211 debugmortem[debugger](sys.exc_info()[2])
212 212 ui.traceback()
213 213 raise
214 214
215 215 # Global exception handling, alphabetically
216 216 # Mercurial-specific first, followed by built-in and library exceptions
217 217 except error.AmbiguousCommand as inst:
218 218 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
219 219 (inst.args[0], " ".join(inst.args[1])))
220 220 except error.ParseError as inst:
221 221 _formatparse(ui.warn, inst)
222 222 return -1
223 223 except error.LockHeld as inst:
224 224 if inst.errno == errno.ETIMEDOUT:
225 225 reason = _('timed out waiting for lock held by %s') % inst.locker
226 226 else:
227 227 reason = _('lock held by %s') % inst.locker
228 228 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
229 229 except error.LockUnavailable as inst:
230 230 ui.warn(_("abort: could not lock %s: %s\n") %
231 231 (inst.desc or inst.filename, inst.strerror))
232 232 except error.CommandError as inst:
233 233 if inst.args[0]:
234 234 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
235 235 commands.help_(ui, inst.args[0], full=False, command=True)
236 236 else:
237 237 ui.warn(_("hg: %s\n") % inst.args[1])
238 238 commands.help_(ui, 'shortlist')
239 239 except error.OutOfBandError as inst:
240 240 if inst.args:
241 241 msg = _("abort: remote error:\n")
242 242 else:
243 243 msg = _("abort: remote error\n")
244 244 ui.warn(msg)
245 245 if inst.args:
246 246 ui.warn(''.join(inst.args))
247 247 if inst.hint:
248 248 ui.warn('(%s)\n' % inst.hint)
249 249 except error.RepoError as inst:
250 250 ui.warn(_("abort: %s!\n") % inst)
251 251 if inst.hint:
252 252 ui.warn(_("(%s)\n") % inst.hint)
253 253 except error.ResponseError as inst:
254 254 ui.warn(_("abort: %s") % inst.args[0])
255 255 if not isinstance(inst.args[1], basestring):
256 256 ui.warn(" %r\n" % (inst.args[1],))
257 257 elif not inst.args[1]:
258 258 ui.warn(_(" empty string\n"))
259 259 else:
260 260 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
261 261 except error.CensoredNodeError as inst:
262 262 ui.warn(_("abort: file censored %s!\n") % inst)
263 263 except error.RevlogError as inst:
264 264 ui.warn(_("abort: %s!\n") % inst)
265 265 except error.SignalInterrupt:
266 266 ui.warn(_("killed!\n"))
267 267 except error.UnknownCommand as inst:
268 268 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
269 269 try:
270 270 # check if the command is in a disabled extension
271 271 # (but don't check for extensions themselves)
272 272 commands.help_(ui, inst.args[0], unknowncmd=True)
273 273 except (error.UnknownCommand, error.Abort):
274 274 suggested = False
275 275 if len(inst.args) == 2:
276 276 sim = _getsimilar(inst.args[1], inst.args[0])
277 277 if sim:
278 278 _reportsimilar(ui.warn, sim)
279 279 suggested = True
280 280 if not suggested:
281 281 commands.help_(ui, 'shortlist')
282 282 except error.InterventionRequired as inst:
283 283 ui.warn("%s\n" % inst)
284 284 if inst.hint:
285 285 ui.warn(_("(%s)\n") % inst.hint)
286 286 return 1
287 287 except error.Abort as inst:
288 288 ui.warn(_("abort: %s\n") % inst)
289 289 if inst.hint:
290 290 ui.warn(_("(%s)\n") % inst.hint)
291 291 except ImportError as inst:
292 292 ui.warn(_("abort: %s!\n") % inst)
293 293 m = str(inst).split()[-1]
294 294 if m in "mpatch bdiff".split():
295 295 ui.warn(_("(did you forget to compile extensions?)\n"))
296 296 elif m in "zlib".split():
297 297 ui.warn(_("(is your Python install correct?)\n"))
298 298 except IOError as inst:
299 299 if util.safehasattr(inst, "code"):
300 300 ui.warn(_("abort: %s\n") % inst)
301 301 elif util.safehasattr(inst, "reason"):
302 302 try: # usually it is in the form (errno, strerror)
303 303 reason = inst.reason.args[1]
304 304 except (AttributeError, IndexError):
305 305 # it might be anything, for example a string
306 306 reason = inst.reason
307 307 if isinstance(reason, unicode):
308 308 # SSLError of Python 2.7.9 contains a unicode
309 309 reason = reason.encode(encoding.encoding, 'replace')
310 310 ui.warn(_("abort: error: %s\n") % reason)
311 311 elif (util.safehasattr(inst, "args")
312 312 and inst.args and inst.args[0] == errno.EPIPE):
313 313 pass
314 314 elif getattr(inst, "strerror", None):
315 315 if getattr(inst, "filename", None):
316 316 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
317 317 else:
318 318 ui.warn(_("abort: %s\n") % inst.strerror)
319 319 else:
320 320 raise
321 321 except OSError as inst:
322 322 if getattr(inst, "filename", None) is not None:
323 323 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
324 324 else:
325 325 ui.warn(_("abort: %s\n") % inst.strerror)
326 326 except KeyboardInterrupt:
327 327 raise
328 328 except MemoryError:
329 329 ui.warn(_("abort: out of memory\n"))
330 330 except SystemExit as inst:
331 331 # Commands shouldn't sys.exit directly, but give a return code.
332 332 # Just in case catch this and and pass exit code to caller.
333 333 return inst.code
334 334 except socket.error as inst:
335 335 ui.warn(_("abort: %s\n") % inst.args[-1])
336 336 except: # perhaps re-raises
337 337 if not handlecommandexception(ui):
338 338 raise
339 339
340 340 return -1
341 341
342 342 def aliasargs(fn, givenargs):
343 343 args = getattr(fn, 'args', [])
344 344 if args:
345 345 cmd = ' '.join(map(util.shellquote, args))
346 346
347 347 nums = []
348 348 def replacer(m):
349 349 num = int(m.group(1)) - 1
350 350 nums.append(num)
351 351 if num < len(givenargs):
352 352 return givenargs[num]
353 353 raise error.Abort(_('too few arguments for command alias'))
354 354 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
355 355 givenargs = [x for i, x in enumerate(givenargs)
356 356 if i not in nums]
357 357 args = shlex.split(cmd)
358 358 return args + givenargs
359 359
360 360 def aliasinterpolate(name, args, cmd):
361 361 '''interpolate args into cmd for shell aliases
362 362
363 363 This also handles $0, $@ and "$@".
364 364 '''
365 365 # util.interpolate can't deal with "$@" (with quotes) because it's only
366 366 # built to match prefix + patterns.
367 367 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
368 368 replacemap['$0'] = name
369 369 replacemap['$$'] = '$'
370 370 replacemap['$@'] = ' '.join(args)
371 371 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
372 372 # parameters, separated out into words. Emulate the same behavior here by
373 373 # quoting the arguments individually. POSIX shells will then typically
374 374 # tokenize each argument into exactly one word.
375 375 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
376 376 # escape '\$' for regex
377 377 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
378 378 r = re.compile(regex)
379 379 return r.sub(lambda x: replacemap[x.group()], cmd)
380 380
381 381 class cmdalias(object):
382 382 def __init__(self, name, definition, cmdtable, source):
383 383 self.name = self.cmd = name
384 384 self.cmdname = ''
385 385 self.definition = definition
386 386 self.fn = None
387 387 self.givenargs = []
388 388 self.opts = []
389 389 self.help = ''
390 390 self.badalias = None
391 391 self.unknowncmd = False
392 392 self.source = source
393 393
394 394 try:
395 395 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
396 396 for alias, e in cmdtable.iteritems():
397 397 if e is entry:
398 398 self.cmd = alias
399 399 break
400 400 self.shadows = True
401 401 except error.UnknownCommand:
402 402 self.shadows = False
403 403
404 404 if not self.definition:
405 405 self.badalias = _("no definition for alias '%s'") % self.name
406 406 return
407 407
408 408 if self.definition.startswith('!'):
409 409 self.shell = True
410 410 def fn(ui, *args):
411 411 env = {'HG_ARGS': ' '.join((self.name,) + args)}
412 412 def _checkvar(m):
413 413 if m.groups()[0] == '$':
414 414 return m.group()
415 415 elif int(m.groups()[0]) <= len(args):
416 416 return m.group()
417 417 else:
418 418 ui.debug("No argument found for substitution "
419 419 "of %i variable in alias '%s' definition."
420 420 % (int(m.groups()[0]), self.name))
421 421 return ''
422 422 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
423 423 cmd = aliasinterpolate(self.name, args, cmd)
424 424 return ui.system(cmd, environ=env)
425 425 self.fn = fn
426 426 return
427 427
428 428 try:
429 429 args = shlex.split(self.definition)
430 430 except ValueError as inst:
431 431 self.badalias = (_("error in definition for alias '%s': %s")
432 432 % (self.name, inst))
433 433 return
434 434 self.cmdname = cmd = args.pop(0)
435 435 self.givenargs = args
436 436
437 437 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
438 438 if _earlygetopt([invalidarg], args):
439 439 self.badalias = (_("error in definition for alias '%s': %s may "
440 440 "only be given on the command line")
441 441 % (self.name, invalidarg))
442 442 return
443 443
444 444 try:
445 445 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
446 446 if len(tableentry) > 2:
447 447 self.fn, self.opts, self.help = tableentry
448 448 else:
449 449 self.fn, self.opts = tableentry
450 450
451 451 if self.help.startswith("hg " + cmd):
452 452 # drop prefix in old-style help lines so hg shows the alias
453 453 self.help = self.help[4 + len(cmd):]
454 454 self.__doc__ = self.fn.__doc__
455 455
456 456 except error.UnknownCommand:
457 457 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
458 458 % (self.name, cmd))
459 459 self.unknowncmd = True
460 460 except error.AmbiguousCommand:
461 461 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
462 462 % (self.name, cmd))
463 463
464 464 @property
465 465 def args(self):
466 466 args = map(util.expandpath, self.givenargs)
467 467 return aliasargs(self.fn, args)
468 468
469 469 def __getattr__(self, name):
470 470 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
471 471 if name not in adefaults:
472 472 raise AttributeError(name)
473 473 if self.badalias or util.safehasattr(self, 'shell'):
474 474 return adefaults[name]
475 475 return getattr(self.fn, name)
476 476
477 477 def __call__(self, ui, *args, **opts):
478 478 if self.badalias:
479 479 hint = None
480 480 if self.unknowncmd:
481 481 try:
482 482 # check if the command is in a disabled extension
483 483 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
484 484 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
485 485 except error.UnknownCommand:
486 486 pass
487 487 raise error.Abort(self.badalias, hint=hint)
488 488 if self.shadows:
489 489 ui.debug("alias '%s' shadows command '%s'\n" %
490 490 (self.name, self.cmdname))
491 491
492 492 if util.safehasattr(self, 'shell'):
493 493 return self.fn(ui, *args, **opts)
494 494 else:
495 495 try:
496 496 return util.checksignature(self.fn)(ui, *args, **opts)
497 497 except error.SignatureError:
498 498 args = ' '.join([self.cmdname] + self.args)
499 499 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
500 500 raise
501 501
502 502 def addaliases(ui, cmdtable):
503 503 # aliases are processed after extensions have been loaded, so they
504 504 # may use extension commands. Aliases can also use other alias definitions,
505 505 # but only if they have been defined prior to the current definition.
506 506 for alias, definition in ui.configitems('alias'):
507 507 source = ui.configsource('alias', alias)
508 508 aliasdef = cmdalias(alias, definition, cmdtable, source)
509 509
510 510 try:
511 511 olddef = cmdtable[aliasdef.cmd][0]
512 512 if olddef.definition == aliasdef.definition:
513 513 continue
514 514 except (KeyError, AttributeError):
515 515 # definition might not exist or it might not be a cmdalias
516 516 pass
517 517
518 518 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
519 519
520 520 def _parse(ui, args):
521 521 options = {}
522 522 cmdoptions = {}
523 523
524 524 try:
525 525 args = fancyopts.fancyopts(args, commands.globalopts, options)
526 526 except fancyopts.getopt.GetoptError as inst:
527 527 raise error.CommandError(None, inst)
528 528
529 529 if args:
530 530 cmd, args = args[0], args[1:]
531 531 aliases, entry = cmdutil.findcmd(cmd, commands.table,
532 532 ui.configbool("ui", "strict"))
533 533 cmd = aliases[0]
534 534 args = aliasargs(entry[0], args)
535 535 defaults = ui.config("defaults", cmd)
536 536 if defaults:
537 537 args = map(util.expandpath, shlex.split(defaults)) + args
538 538 c = list(entry[1])
539 539 else:
540 540 cmd = None
541 541 c = []
542 542
543 543 # combine global options into local
544 544 for o in commands.globalopts:
545 545 c.append((o[0], o[1], options[o[1]], o[3]))
546 546
547 547 try:
548 548 args = fancyopts.fancyopts(args, c, cmdoptions, True)
549 549 except fancyopts.getopt.GetoptError as inst:
550 550 raise error.CommandError(cmd, inst)
551 551
552 552 # separate global options back out
553 553 for o in commands.globalopts:
554 554 n = o[1]
555 555 options[n] = cmdoptions[n]
556 556 del cmdoptions[n]
557 557
558 558 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
559 559
560 560 def _parseconfig(ui, config):
561 561 """parse the --config options from the command line"""
562 562 configs = []
563 563
564 564 for cfg in config:
565 565 try:
566 566 name, value = [cfgelem.strip()
567 567 for cfgelem in cfg.split('=', 1)]
568 568 section, name = name.split('.', 1)
569 569 if not section or not name:
570 570 raise IndexError
571 571 ui.setconfig(section, name, value, '--config')
572 572 configs.append((section, name, value))
573 573 except (IndexError, ValueError):
574 574 raise error.Abort(_('malformed --config option: %r '
575 575 '(use --config section.name=value)') % cfg)
576 576
577 577 return configs
578 578
579 579 def _earlygetopt(aliases, args):
580 580 """Return list of values for an option (or aliases).
581 581
582 582 The values are listed in the order they appear in args.
583 583 The options and values are removed from args.
584 584
585 585 >>> args = ['x', '--cwd', 'foo', 'y']
586 586 >>> _earlygetopt(['--cwd'], args), args
587 587 (['foo'], ['x', 'y'])
588 588
589 589 >>> args = ['x', '--cwd=bar', 'y']
590 590 >>> _earlygetopt(['--cwd'], args), args
591 591 (['bar'], ['x', 'y'])
592 592
593 593 >>> args = ['x', '-R', 'foo', 'y']
594 594 >>> _earlygetopt(['-R'], args), args
595 595 (['foo'], ['x', 'y'])
596 596
597 597 >>> args = ['x', '-Rbar', 'y']
598 598 >>> _earlygetopt(['-R'], args), args
599 599 (['bar'], ['x', 'y'])
600 600 """
601 601 try:
602 602 argcount = args.index("--")
603 603 except ValueError:
604 604 argcount = len(args)
605 605 shortopts = [opt for opt in aliases if len(opt) == 2]
606 606 values = []
607 607 pos = 0
608 608 while pos < argcount:
609 609 fullarg = arg = args[pos]
610 610 equals = arg.find('=')
611 611 if equals > -1:
612 612 arg = arg[:equals]
613 613 if arg in aliases:
614 614 del args[pos]
615 615 if equals > -1:
616 616 values.append(fullarg[equals + 1:])
617 617 argcount -= 1
618 618 else:
619 619 if pos + 1 >= argcount:
620 620 # ignore and let getopt report an error if there is no value
621 621 break
622 622 values.append(args.pop(pos))
623 623 argcount -= 2
624 624 elif arg[:2] in shortopts:
625 625 # short option can have no following space, e.g. hg log -Rfoo
626 626 values.append(args.pop(pos)[2:])
627 627 argcount -= 1
628 628 else:
629 629 pos += 1
630 630 return values
631 631
632 632 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
633 633 # run pre-hook, and abort if it fails
634 634 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
635 635 pats=cmdpats, opts=cmdoptions)
636 636 try:
637 637 ret = _runcommand(ui, options, cmd, d)
638 638 # run post-hook, passing command result
639 639 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
640 640 result=ret, pats=cmdpats, opts=cmdoptions)
641 641 except Exception:
642 642 # run failure hook and re-raise
643 643 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
644 644 pats=cmdpats, opts=cmdoptions)
645 645 raise
646 646 return ret
647 647
648 648 def _getlocal(ui, rpath, wd=None):
649 649 """Return (path, local ui object) for the given target path.
650 650
651 651 Takes paths in [cwd]/.hg/hgrc into account."
652 652 """
653 653 if wd is None:
654 654 try:
655 655 wd = os.getcwd()
656 656 except OSError as e:
657 657 raise error.Abort(_("error getting current working directory: %s") %
658 658 e.strerror)
659 659 path = cmdutil.findrepo(wd) or ""
660 660 if not path:
661 661 lui = ui
662 662 else:
663 663 lui = ui.copy()
664 664 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
665 665
666 666 if rpath and rpath[-1]:
667 667 path = lui.expandpath(rpath[-1])
668 668 lui = ui.copy()
669 669 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
670 670
671 671 return path, lui
672 672
673 def _checkshellalias(lui, ui, args, precheck=True):
674 """Return the function to run the shell alias, if it is required
675
676 'precheck' is whether this function is invoked before adding
677 aliases or not.
678 """
673 def _checkshellalias(lui, ui, args):
674 """Return the function to run the shell alias, if it is required"""
679 675 options = {}
680 676
681 677 try:
682 678 args = fancyopts.fancyopts(args, commands.globalopts, options)
683 679 except fancyopts.getopt.GetoptError:
684 680 return
685 681
686 682 if not args:
687 683 return
688 684
689 if precheck:
690 strict = True
691 cmdtable = commands.table.copy()
692 addaliases(lui, cmdtable)
693 else:
694 strict = False
695 685 cmdtable = commands.table
696 686
697 687 cmd = args[0]
698 688 try:
689 strict = ui.configbool("ui", "strict")
699 690 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
700 691 except (error.AmbiguousCommand, error.UnknownCommand):
701 692 return
702 693
703 694 cmd = aliases[0]
704 695 fn = entry[0]
705 696
706 697 if cmd and util.safehasattr(fn, 'shell'):
707 698 d = lambda: fn(ui, *args[1:])
708 699 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
709 700 [], {})
710 701
711 702 def _cmdattr(ui, cmd, func, attr):
712 703 try:
713 704 return getattr(func, attr)
714 705 except AttributeError:
715 706 ui.deprecwarn("missing attribute '%s', use @command decorator "
716 707 "to register '%s'" % (attr, cmd), '3.8')
717 708 return False
718 709
719 710 _loaded = set()
720 711
721 712 # list of (objname, loadermod, loadername) tuple:
722 713 # - objname is the name of an object in extension module, from which
723 714 # extra information is loaded
724 715 # - loadermod is the module where loader is placed
725 716 # - loadername is the name of the function, which takes (ui, extensionname,
726 717 # extraobj) arguments
727 718 extraloaders = [
728 719 ('cmdtable', commands, 'loadcmdtable'),
729 720 ('filesetpredicate', fileset, 'loadpredicate'),
730 721 ('revsetpredicate', revset, 'loadpredicate'),
731 722 ('templatefilter', templatefilters, 'loadfilter'),
732 723 ('templatefunc', templater, 'loadfunction'),
733 724 ('templatekeyword', templatekw, 'loadkeyword'),
734 725 ]
735 726
736 727 def _dispatch(req):
737 728 args = req.args
738 729 ui = req.ui
739 730
740 731 # check for cwd
741 732 cwd = _earlygetopt(['--cwd'], args)
742 733 if cwd:
743 734 os.chdir(cwd[-1])
744 735
745 736 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
746 737 path, lui = _getlocal(ui, rpath)
747 738
748 # Now that we're operating in the right directory/repository with
749 # the right config settings, check for shell aliases
750 shellaliasfn = _checkshellalias(lui, ui, args)
751 if shellaliasfn:
752 return shellaliasfn()
753
754 739 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
755 740 # reposetup. Programs like TortoiseHg will call _dispatch several
756 741 # times so we keep track of configured extensions in _loaded.
757 742 extensions.loadall(lui)
758 743 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
759 744 # Propagate any changes to lui.__class__ by extensions
760 745 ui.__class__ = lui.__class__
761 746
762 747 # (uisetup and extsetup are handled in extensions.loadall)
763 748
764 749 for name, module in exts:
765 750 for objname, loadermod, loadername in extraloaders:
766 751 extraobj = getattr(module, objname, None)
767 752 if extraobj is not None:
768 753 getattr(loadermod, loadername)(ui, name, extraobj)
769 754 _loaded.add(name)
770 755
771 756 # (reposetup is handled in hg.repository)
772 757
773 758 addaliases(lui, commands.table)
774 759
775 if not lui.configbool("ui", "strict"):
776 760 # All aliases and commands are completely defined, now.
777 # Check abbreviation/ambiguity of shell alias again, because shell
778 # alias may cause failure of "_parse" (see issue4355)
779 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
761 # Check abbreviation/ambiguity of shell alias.
762 shellaliasfn = _checkshellalias(lui, ui, args)
780 763 if shellaliasfn:
781 764 return shellaliasfn()
782 765
783 766 # check for fallback encoding
784 767 fallback = lui.config('ui', 'fallbackencoding')
785 768 if fallback:
786 769 encoding.fallbackencoding = fallback
787 770
788 771 fullargs = args
789 772 cmd, func, args, options, cmdoptions = _parse(lui, args)
790 773
791 774 if options["config"]:
792 775 raise error.Abort(_("option --config may not be abbreviated!"))
793 776 if options["cwd"]:
794 777 raise error.Abort(_("option --cwd may not be abbreviated!"))
795 778 if options["repository"]:
796 779 raise error.Abort(_(
797 780 "option -R has to be separated from other options (e.g. not -qR) "
798 781 "and --repository may only be abbreviated as --repo!"))
799 782
800 783 if options["encoding"]:
801 784 encoding.encoding = options["encoding"]
802 785 if options["encodingmode"]:
803 786 encoding.encodingmode = options["encodingmode"]
804 787 if options["time"]:
805 788 def get_times():
806 789 t = os.times()
807 790 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
808 791 t = (t[0], t[1], t[2], t[3], time.clock())
809 792 return t
810 793 s = get_times()
811 794 def print_time():
812 795 t = get_times()
813 796 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
814 797 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
815 798 atexit.register(print_time)
816 799
817 800 uis = set([ui, lui])
818 801
819 802 if req.repo:
820 803 uis.add(req.repo.ui)
821 804
822 805 if options['verbose'] or options['debug'] or options['quiet']:
823 806 for opt in ('verbose', 'debug', 'quiet'):
824 807 val = str(bool(options[opt]))
825 808 for ui_ in uis:
826 809 ui_.setconfig('ui', opt, val, '--' + opt)
827 810
828 811 if options['traceback']:
829 812 for ui_ in uis:
830 813 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
831 814
832 815 if options['noninteractive']:
833 816 for ui_ in uis:
834 817 ui_.setconfig('ui', 'interactive', 'off', '-y')
835 818
836 819 if cmdoptions.get('insecure', False):
837 820 for ui_ in uis:
838 821 ui_.insecureconnections = True
839 822
840 823 if options['version']:
841 824 return commands.version_(ui)
842 825 if options['help']:
843 826 return commands.help_(ui, cmd, command=cmd is not None)
844 827 elif not cmd:
845 828 return commands.help_(ui, 'shortlist')
846 829
847 830 repo = None
848 831 cmdpats = args[:]
849 832 if not _cmdattr(ui, cmd, func, 'norepo'):
850 833 # use the repo from the request only if we don't have -R
851 834 if not rpath and not cwd:
852 835 repo = req.repo
853 836
854 837 if repo:
855 838 # set the descriptors of the repo ui to those of ui
856 839 repo.ui.fin = ui.fin
857 840 repo.ui.fout = ui.fout
858 841 repo.ui.ferr = ui.ferr
859 842 else:
860 843 try:
861 844 repo = hg.repository(ui, path=path)
862 845 if not repo.local():
863 846 raise error.Abort(_("repository '%s' is not local") % path)
864 847 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
865 848 except error.RequirementError:
866 849 raise
867 850 except error.RepoError:
868 851 if rpath and rpath[-1]: # invalid -R path
869 852 raise
870 853 if not _cmdattr(ui, cmd, func, 'optionalrepo'):
871 854 if (_cmdattr(ui, cmd, func, 'inferrepo') and
872 855 args and not path):
873 856 # try to infer -R from command args
874 857 repos = map(cmdutil.findrepo, args)
875 858 guess = repos[0]
876 859 if guess and repos.count(guess) == len(repos):
877 860 req.args = ['--repository', guess] + fullargs
878 861 return _dispatch(req)
879 862 if not path:
880 863 raise error.RepoError(_("no repository found in '%s'"
881 864 " (.hg not found)")
882 865 % os.getcwd())
883 866 raise
884 867 if repo:
885 868 ui = repo.ui
886 869 if options['hidden']:
887 870 repo = repo.unfiltered()
888 871 args.insert(0, repo)
889 872 elif rpath:
890 873 ui.warn(_("warning: --repository ignored\n"))
891 874
892 875 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
893 876 ui.log("command", '%s\n', msg)
894 877 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
895 878 try:
896 879 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
897 880 cmdpats, cmdoptions)
898 881 finally:
899 882 if repo and repo != req.repo:
900 883 repo.close()
901 884
902 885 def lsprofile(ui, func, fp):
903 886 format = ui.config('profiling', 'format', default='text')
904 887 field = ui.config('profiling', 'sort', default='inlinetime')
905 888 limit = ui.configint('profiling', 'limit', default=30)
906 889 climit = ui.configint('profiling', 'nested', default=0)
907 890
908 891 if format not in ['text', 'kcachegrind']:
909 892 ui.warn(_("unrecognized profiling format '%s'"
910 893 " - Ignored\n") % format)
911 894 format = 'text'
912 895
913 896 try:
914 897 from . import lsprof
915 898 except ImportError:
916 899 raise error.Abort(_(
917 900 'lsprof not available - install from '
918 901 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
919 902 p = lsprof.Profiler()
920 903 p.enable(subcalls=True)
921 904 try:
922 905 return func()
923 906 finally:
924 907 p.disable()
925 908
926 909 if format == 'kcachegrind':
927 910 from . import lsprofcalltree
928 911 calltree = lsprofcalltree.KCacheGrind(p)
929 912 calltree.output(fp)
930 913 else:
931 914 # format == 'text'
932 915 stats = lsprof.Stats(p.getstats())
933 916 stats.sort(field)
934 917 stats.pprint(limit=limit, file=fp, climit=climit)
935 918
936 919 def flameprofile(ui, func, fp):
937 920 try:
938 921 from flamegraph import flamegraph
939 922 except ImportError:
940 923 raise error.Abort(_(
941 924 'flamegraph not available - install from '
942 925 'https://github.com/evanhempel/python-flamegraph'))
943 926 # developer config: profiling.freq
944 927 freq = ui.configint('profiling', 'freq', default=1000)
945 928 filter_ = None
946 929 collapse_recursion = True
947 930 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
948 931 filter_, collapse_recursion)
949 932 start_time = time.clock()
950 933 try:
951 934 thread.start()
952 935 func()
953 936 finally:
954 937 thread.stop()
955 938 thread.join()
956 939 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
957 940 time.clock() - start_time, thread.num_frames(),
958 941 thread.num_frames(unique=True)))
959 942
960 943
961 944 def statprofile(ui, func, fp):
962 945 try:
963 946 import statprof
964 947 except ImportError:
965 948 raise error.Abort(_(
966 949 'statprof not available - install using "easy_install statprof"'))
967 950
968 951 freq = ui.configint('profiling', 'freq', default=1000)
969 952 if freq > 0:
970 953 statprof.reset(freq)
971 954 else:
972 955 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
973 956
974 957 statprof.start()
975 958 try:
976 959 return func()
977 960 finally:
978 961 statprof.stop()
979 962 statprof.display(fp)
980 963
981 964 def _runcommand(ui, options, cmd, cmdfunc):
982 965 """Enables the profiler if applicable.
983 966
984 967 ``profiling.enabled`` - boolean config that enables or disables profiling
985 968 """
986 969 def checkargs():
987 970 try:
988 971 return cmdfunc()
989 972 except error.SignatureError:
990 973 raise error.CommandError(cmd, _("invalid arguments"))
991 974
992 975 if options['profile'] or ui.configbool('profiling', 'enabled'):
993 976 profiler = os.getenv('HGPROF')
994 977 if profiler is None:
995 978 profiler = ui.config('profiling', 'type', default='ls')
996 979 if profiler not in ('ls', 'stat', 'flame'):
997 980 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
998 981 profiler = 'ls'
999 982
1000 983 output = ui.config('profiling', 'output')
1001 984
1002 985 if output == 'blackbox':
1003 986 fp = util.stringio()
1004 987 elif output:
1005 988 path = ui.expandpath(output)
1006 989 fp = open(path, 'wb')
1007 990 else:
1008 991 fp = sys.stderr
1009 992
1010 993 try:
1011 994 if profiler == 'ls':
1012 995 return lsprofile(ui, checkargs, fp)
1013 996 elif profiler == 'flame':
1014 997 return flameprofile(ui, checkargs, fp)
1015 998 else:
1016 999 return statprofile(ui, checkargs, fp)
1017 1000 finally:
1018 1001 if output:
1019 1002 if output == 'blackbox':
1020 1003 val = "Profile:\n%s" % fp.getvalue()
1021 1004 # ui.log treats the input as a format string,
1022 1005 # so we need to escape any % signs.
1023 1006 val = val.replace('%', '%%')
1024 1007 ui.log('profile', val)
1025 1008 fp.close()
1026 1009 else:
1027 1010 return checkargs()
1028 1011
1029 1012 def _exceptionwarning(ui):
1030 1013 """Produce a warning message for the current active exception"""
1031 1014
1032 1015 # For compatibility checking, we discard the portion of the hg
1033 1016 # version after the + on the assumption that if a "normal
1034 1017 # user" is running a build with a + in it the packager
1035 1018 # probably built from fairly close to a tag and anyone with a
1036 1019 # 'make local' copy of hg (where the version number can be out
1037 1020 # of date) will be clueful enough to notice the implausible
1038 1021 # version number and try updating.
1039 1022 ct = util.versiontuple(n=2)
1040 1023 worst = None, ct, ''
1041 1024 if ui.config('ui', 'supportcontact', None) is None:
1042 1025 for name, mod in extensions.extensions():
1043 1026 testedwith = getattr(mod, 'testedwith', '')
1044 1027 report = getattr(mod, 'buglink', _('the extension author.'))
1045 1028 if not testedwith.strip():
1046 1029 # We found an untested extension. It's likely the culprit.
1047 1030 worst = name, 'unknown', report
1048 1031 break
1049 1032
1050 1033 # Never blame on extensions bundled with Mercurial.
1051 1034 if testedwith == 'internal':
1052 1035 continue
1053 1036
1054 1037 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1055 1038 if ct in tested:
1056 1039 continue
1057 1040
1058 1041 lower = [t for t in tested if t < ct]
1059 1042 nearest = max(lower or tested)
1060 1043 if worst[0] is None or nearest < worst[1]:
1061 1044 worst = name, nearest, report
1062 1045 if worst[0] is not None:
1063 1046 name, testedwith, report = worst
1064 1047 if not isinstance(testedwith, str):
1065 1048 testedwith = '.'.join([str(c) for c in testedwith])
1066 1049 warning = (_('** Unknown exception encountered with '
1067 1050 'possibly-broken third-party extension %s\n'
1068 1051 '** which supports versions %s of Mercurial.\n'
1069 1052 '** Please disable %s and try your action again.\n'
1070 1053 '** If that fixes the bug please report it to %s\n')
1071 1054 % (name, testedwith, name, report))
1072 1055 else:
1073 1056 bugtracker = ui.config('ui', 'supportcontact', None)
1074 1057 if bugtracker is None:
1075 1058 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1076 1059 warning = (_("** unknown exception encountered, "
1077 1060 "please report by visiting\n** ") + bugtracker + '\n')
1078 1061 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
1079 1062 (_("** Mercurial Distributed SCM (version %s)\n") %
1080 1063 util.version()) +
1081 1064 (_("** Extensions loaded: %s\n") %
1082 1065 ", ".join([x[0] for x in extensions.extensions()])))
1083 1066 return warning
1084 1067
1085 1068 def handlecommandexception(ui):
1086 1069 """Produce a warning message for broken commands
1087 1070
1088 1071 Called when handling an exception; the exception is reraised if
1089 1072 this function returns False, ignored otherwise.
1090 1073 """
1091 1074 warning = _exceptionwarning(ui)
1092 1075 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1093 1076 ui.warn(warning)
1094 1077 return False # re-raise the exception
@@ -1,179 +1,191 b''
1 1 $ cat >> fakepager.py <<EOF
2 2 > import sys
3 3 > for line in sys.stdin:
4 4 > sys.stdout.write('paged! %r\n' % line)
5 5 > EOF
6 6
7 7 Enable ui.formatted because pager won't fire without it, and set up
8 8 pager and tell it to use our fake pager that lets us see when the
9 9 pager was running.
10 10 $ cat >> $HGRCPATH <<EOF
11 11 > [ui]
12 12 > formatted = yes
13 13 > [extensions]
14 14 > pager=
15 15 > [pager]
16 16 > pager = python $TESTTMP/fakepager.py
17 17 > EOF
18 18
19 19 $ hg init repo
20 20 $ cd repo
21 21 $ echo a >> a
22 22 $ hg add a
23 23 $ hg ci -m 'add a'
24 24 $ for x in `python $TESTDIR/seq.py 1 10`; do
25 25 > echo a $x >> a
26 26 > hg ci -m "modify a $x"
27 27 > done
28 28
29 29 By default diff and log are paged, but summary is not:
30 30
31 31 $ hg diff -c 2 --pager=yes
32 32 paged! 'diff -r f4be7687d414 -r bce265549556 a\n'
33 33 paged! '--- a/a\tThu Jan 01 00:00:00 1970 +0000\n'
34 34 paged! '+++ b/a\tThu Jan 01 00:00:00 1970 +0000\n'
35 35 paged! '@@ -1,2 +1,3 @@\n'
36 36 paged! ' a\n'
37 37 paged! ' a 1\n'
38 38 paged! '+a 2\n'
39 39
40 40 $ hg log --limit 2
41 41 paged! 'changeset: 10:46106edeeb38\n'
42 42 paged! 'tag: tip\n'
43 43 paged! 'user: test\n'
44 44 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
45 45 paged! 'summary: modify a 10\n'
46 46 paged! '\n'
47 47 paged! 'changeset: 9:6dd8ea7dd621\n'
48 48 paged! 'user: test\n'
49 49 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
50 50 paged! 'summary: modify a 9\n'
51 51 paged! '\n'
52 52
53 53 $ hg summary
54 54 parent: 10:46106edeeb38 tip
55 55 modify a 10
56 56 branch: default
57 57 commit: (clean)
58 58 update: (current)
59 59 phases: 11 draft
60 60
61 61 We can enable the pager on summary:
62 62
63 63 $ hg --config pager.attend-summary=yes summary
64 64 paged! 'parent: 10:46106edeeb38 tip\n'
65 65 paged! ' modify a 10\n'
66 66 paged! 'branch: default\n'
67 67 paged! 'commit: (clean)\n'
68 68 paged! 'update: (current)\n'
69 69 paged! 'phases: 11 draft\n'
70 70
71 71 If we completely change the attend list that's respected:
72 72
73 73 $ hg --config pager.attend-diff=no diff -c 2
74 74 diff -r f4be7687d414 -r bce265549556 a
75 75 --- a/a Thu Jan 01 00:00:00 1970 +0000
76 76 +++ b/a Thu Jan 01 00:00:00 1970 +0000
77 77 @@ -1,2 +1,3 @@
78 78 a
79 79 a 1
80 80 +a 2
81 81
82 82 $ hg --config pager.attend=summary diff -c 2
83 83 diff -r f4be7687d414 -r bce265549556 a
84 84 --- a/a Thu Jan 01 00:00:00 1970 +0000
85 85 +++ b/a Thu Jan 01 00:00:00 1970 +0000
86 86 @@ -1,2 +1,3 @@
87 87 a
88 88 a 1
89 89 +a 2
90 90
91 91 If 'log' is in attend, then 'history' should also be paged:
92 92 $ hg history --limit 2 --config pager.attend=log
93 93 paged! 'changeset: 10:46106edeeb38\n'
94 94 paged! 'tag: tip\n'
95 95 paged! 'user: test\n'
96 96 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
97 97 paged! 'summary: modify a 10\n'
98 98 paged! '\n'
99 99 paged! 'changeset: 9:6dd8ea7dd621\n'
100 100 paged! 'user: test\n'
101 101 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
102 102 paged! 'summary: modify a 9\n'
103 103 paged! '\n'
104 104
105 105 Possible bug: history is explicitly ignored in pager config, but
106 106 because log is in the attend list it still gets pager treatment.
107 107
108 108 $ hg history --limit 2 --config pager.attend=log \
109 109 > --config pager.ignore=history
110 110 paged! 'changeset: 10:46106edeeb38\n'
111 111 paged! 'tag: tip\n'
112 112 paged! 'user: test\n'
113 113 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
114 114 paged! 'summary: modify a 10\n'
115 115 paged! '\n'
116 116 paged! 'changeset: 9:6dd8ea7dd621\n'
117 117 paged! 'user: test\n'
118 118 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
119 119 paged! 'summary: modify a 9\n'
120 120 paged! '\n'
121 121
122 122 Possible bug: history is explicitly marked as attend-history=no, but
123 123 it doesn't fail to get paged because log is still in the attend list.
124 124
125 125 $ hg history --limit 2 --config pager.attend-history=no
126 126 paged! 'changeset: 10:46106edeeb38\n'
127 127 paged! 'tag: tip\n'
128 128 paged! 'user: test\n'
129 129 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
130 130 paged! 'summary: modify a 10\n'
131 131 paged! '\n'
132 132 paged! 'changeset: 9:6dd8ea7dd621\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 9\n'
136 136 paged! '\n'
137 137
138 138 Possible bug: disabling pager for log but enabling it for history
139 139 doesn't result in history being paged.
140 140
141 141 $ hg history --limit 2 --config pager.attend-log=no \
142 142 > --config pager.attend-history=yes
143 143 changeset: 10:46106edeeb38
144 144 tag: tip
145 145 user: test
146 146 date: Thu Jan 01 00:00:00 1970 +0000
147 147 summary: modify a 10
148 148
149 149 changeset: 9:6dd8ea7dd621
150 150 user: test
151 151 date: Thu Jan 01 00:00:00 1970 +0000
152 152 summary: modify a 9
153 153
154 154
155 155 Pager with color enabled allows colors to come through by default,
156 156 even though stdout is no longer a tty.
157 157 $ cat >> $HGRCPATH <<EOF
158 158 > [extensions]
159 159 > color=
160 160 > [color]
161 161 > mode = ansi
162 162 > EOF
163 163 $ hg log --limit 3
164 164 paged! '\x1b[0;33mchangeset: 10:46106edeeb38\x1b[0m\n'
165 165 paged! 'tag: tip\n'
166 166 paged! 'user: test\n'
167 167 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
168 168 paged! 'summary: modify a 10\n'
169 169 paged! '\n'
170 170 paged! '\x1b[0;33mchangeset: 9:6dd8ea7dd621\x1b[0m\n'
171 171 paged! 'user: test\n'
172 172 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
173 173 paged! 'summary: modify a 9\n'
174 174 paged! '\n'
175 175 paged! '\x1b[0;33mchangeset: 8:cff05a6312fe\x1b[0m\n'
176 176 paged! 'user: test\n'
177 177 paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
178 178 paged! 'summary: modify a 8\n'
179 179 paged! '\n'
180
181 Pager works with shell aliases.
182
183 $ cat >> $HGRCPATH <<EOF
184 > [alias]
185 > echoa = !echo a
186 > EOF
187
188 $ hg echoa
189 a
190 $ hg --config pager.attend-echoa=yes echoa
191 paged! 'a\n'
General Comments 0
You need to be logged in to leave comments. Login now