##// END OF EJS Templates
ui: introduce neverpager() call...
Augie Fackler -
r30994:3ed6e439 default
parent child Browse files
Show More
@@ -1,894 +1,897 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 getopt
14 14 import os
15 15 import pdb
16 16 import re
17 17 import signal
18 18 import sys
19 19 import time
20 20 import traceback
21 21
22 22
23 23 from .i18n import _
24 24
25 25 from . import (
26 26 cmdutil,
27 27 color,
28 28 commands,
29 29 debugcommands,
30 30 demandimport,
31 31 encoding,
32 32 error,
33 33 extensions,
34 34 fancyopts,
35 35 fileset,
36 36 hg,
37 37 hook,
38 38 profiling,
39 39 pycompat,
40 40 revset,
41 41 scmutil,
42 42 templatefilters,
43 43 templatekw,
44 44 templater,
45 45 ui as uimod,
46 46 util,
47 47 )
48 48
49 49 class request(object):
50 50 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
51 51 ferr=None):
52 52 self.args = args
53 53 self.ui = ui
54 54 self.repo = repo
55 55
56 56 # input/output/error streams
57 57 self.fin = fin
58 58 self.fout = fout
59 59 self.ferr = ferr
60 60
61 61 def run():
62 62 "run the command in sys.argv"
63 63 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
64 64
65 65 def _getsimilar(symbols, value):
66 66 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
67 67 # The cutoff for similarity here is pretty arbitrary. It should
68 68 # probably be investigated and tweaked.
69 69 return [s for s in symbols if sim(s) > 0.6]
70 70
71 71 def _reportsimilar(write, similar):
72 72 if len(similar) == 1:
73 73 write(_("(did you mean %s?)\n") % similar[0])
74 74 elif similar:
75 75 ss = ", ".join(sorted(similar))
76 76 write(_("(did you mean one of %s?)\n") % ss)
77 77
78 78 def _formatparse(write, inst):
79 79 similar = []
80 80 if isinstance(inst, error.UnknownIdentifier):
81 81 # make sure to check fileset first, as revset can invoke fileset
82 82 similar = _getsimilar(inst.symbols, inst.function)
83 83 if len(inst.args) > 1:
84 84 write(_("hg: parse error at %s: %s\n") %
85 85 (inst.args[1], inst.args[0]))
86 86 if (inst.args[0][0] == ' '):
87 87 write(_("unexpected leading whitespace\n"))
88 88 else:
89 89 write(_("hg: parse error: %s\n") % inst.args[0])
90 90 _reportsimilar(write, similar)
91 91 if inst.hint:
92 92 write(_("(%s)\n") % inst.hint)
93 93
94 94 def dispatch(req):
95 95 "run the command specified in req.args"
96 96 if req.ferr:
97 97 ferr = req.ferr
98 98 elif req.ui:
99 99 ferr = req.ui.ferr
100 100 else:
101 101 ferr = util.stderr
102 102
103 103 try:
104 104 if not req.ui:
105 105 req.ui = uimod.ui.load()
106 106 if '--traceback' in req.args:
107 107 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
108 108
109 109 # set ui streams from the request
110 110 if req.fin:
111 111 req.ui.fin = req.fin
112 112 if req.fout:
113 113 req.ui.fout = req.fout
114 114 if req.ferr:
115 115 req.ui.ferr = req.ferr
116 116 except error.Abort as inst:
117 117 ferr.write(_("abort: %s\n") % inst)
118 118 if inst.hint:
119 119 ferr.write(_("(%s)\n") % inst.hint)
120 120 return -1
121 121 except error.ParseError as inst:
122 122 _formatparse(ferr.write, inst)
123 123 return -1
124 124
125 125 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
126 126 starttime = util.timer()
127 127 ret = None
128 128 try:
129 129 ret = _runcatch(req)
130 130 except KeyboardInterrupt:
131 131 try:
132 132 req.ui.warn(_("interrupted!\n"))
133 133 except IOError as inst:
134 134 if inst.errno != errno.EPIPE:
135 135 raise
136 136 ret = -1
137 137 finally:
138 138 duration = util.timer() - starttime
139 139 req.ui.flush()
140 140 if req.ui.logblockedtimes:
141 141 req.ui._blockedtimes['command_duration'] = duration * 1000
142 142 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
143 143 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
144 144 msg, ret or 0, duration)
145 145 return ret
146 146
147 147 def _runcatch(req):
148 148 def catchterm(*args):
149 149 raise error.SignalInterrupt
150 150
151 151 ui = req.ui
152 152 try:
153 153 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
154 154 num = getattr(signal, name, None)
155 155 if num:
156 156 signal.signal(num, catchterm)
157 157 except ValueError:
158 158 pass # happens if called in a thread
159 159
160 160 def _runcatchfunc():
161 161 try:
162 162 debugger = 'pdb'
163 163 debugtrace = {
164 164 'pdb' : pdb.set_trace
165 165 }
166 166 debugmortem = {
167 167 'pdb' : pdb.post_mortem
168 168 }
169 169
170 170 # read --config before doing anything else
171 171 # (e.g. to change trust settings for reading .hg/hgrc)
172 172 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
173 173
174 174 if req.repo:
175 175 # copy configs that were passed on the cmdline (--config) to
176 176 # the repo ui
177 177 for sec, name, val in cfgs:
178 178 req.repo.ui.setconfig(sec, name, val, source='--config')
179 179
180 180 # developer config: ui.debugger
181 181 debugger = ui.config("ui", "debugger")
182 182 debugmod = pdb
183 183 if not debugger or ui.plain():
184 184 # if we are in HGPLAIN mode, then disable custom debugging
185 185 debugger = 'pdb'
186 186 elif '--debugger' in req.args:
187 187 # This import can be slow for fancy debuggers, so only
188 188 # do it when absolutely necessary, i.e. when actual
189 189 # debugging has been requested
190 190 with demandimport.deactivated():
191 191 try:
192 192 debugmod = __import__(debugger)
193 193 except ImportError:
194 194 pass # Leave debugmod = pdb
195 195
196 196 debugtrace[debugger] = debugmod.set_trace
197 197 debugmortem[debugger] = debugmod.post_mortem
198 198
199 199 # enter the debugger before command execution
200 200 if '--debugger' in req.args:
201 201 ui.warn(_("entering debugger - "
202 202 "type c to continue starting hg or h for help\n"))
203 203
204 204 if (debugger != 'pdb' and
205 205 debugtrace[debugger] == debugtrace['pdb']):
206 206 ui.warn(_("%s debugger specified "
207 207 "but its module was not found\n") % debugger)
208 208 with demandimport.deactivated():
209 209 debugtrace[debugger]()
210 210 try:
211 211 return _dispatch(req)
212 212 finally:
213 213 ui.flush()
214 214 except: # re-raises
215 215 # enter the debugger when we hit an exception
216 216 if '--debugger' in req.args:
217 217 traceback.print_exc()
218 218 debugmortem[debugger](sys.exc_info()[2])
219 219 ui.traceback()
220 220 raise
221 221
222 222 return callcatch(ui, _runcatchfunc)
223 223
224 224 def callcatch(ui, func):
225 225 """like scmutil.callcatch but handles more high-level exceptions about
226 226 config parsing and commands. besides, use handlecommandexception to handle
227 227 uncaught exceptions.
228 228 """
229 229 try:
230 230 return scmutil.callcatch(ui, func)
231 231 except error.AmbiguousCommand as inst:
232 232 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
233 233 (inst.args[0], " ".join(inst.args[1])))
234 234 except error.CommandError as inst:
235 235 if inst.args[0]:
236 236 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
237 237 commands.help_(ui, inst.args[0], full=False, command=True)
238 238 else:
239 239 ui.warn(_("hg: %s\n") % inst.args[1])
240 240 commands.help_(ui, 'shortlist')
241 241 except error.ParseError as inst:
242 242 _formatparse(ui.warn, inst)
243 243 return -1
244 244 except error.UnknownCommand as inst:
245 245 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
246 246 try:
247 247 # check if the command is in a disabled extension
248 248 # (but don't check for extensions themselves)
249 249 commands.help_(ui, inst.args[0], unknowncmd=True)
250 250 except (error.UnknownCommand, error.Abort):
251 251 suggested = False
252 252 if len(inst.args) == 2:
253 253 sim = _getsimilar(inst.args[1], inst.args[0])
254 254 if sim:
255 255 _reportsimilar(ui.warn, sim)
256 256 suggested = True
257 257 if not suggested:
258 258 commands.help_(ui, 'shortlist')
259 259 except IOError:
260 260 raise
261 261 except KeyboardInterrupt:
262 262 raise
263 263 except: # probably re-raises
264 264 if not handlecommandexception(ui):
265 265 raise
266 266
267 267 return -1
268 268
269 269 def aliasargs(fn, givenargs):
270 270 args = getattr(fn, 'args', [])
271 271 if args:
272 272 cmd = ' '.join(map(util.shellquote, args))
273 273
274 274 nums = []
275 275 def replacer(m):
276 276 num = int(m.group(1)) - 1
277 277 nums.append(num)
278 278 if num < len(givenargs):
279 279 return givenargs[num]
280 280 raise error.Abort(_('too few arguments for command alias'))
281 281 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
282 282 givenargs = [x for i, x in enumerate(givenargs)
283 283 if i not in nums]
284 284 args = pycompat.shlexsplit(cmd)
285 285 return args + givenargs
286 286
287 287 def aliasinterpolate(name, args, cmd):
288 288 '''interpolate args into cmd for shell aliases
289 289
290 290 This also handles $0, $@ and "$@".
291 291 '''
292 292 # util.interpolate can't deal with "$@" (with quotes) because it's only
293 293 # built to match prefix + patterns.
294 294 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
295 295 replacemap['$0'] = name
296 296 replacemap['$$'] = '$'
297 297 replacemap['$@'] = ' '.join(args)
298 298 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
299 299 # parameters, separated out into words. Emulate the same behavior here by
300 300 # quoting the arguments individually. POSIX shells will then typically
301 301 # tokenize each argument into exactly one word.
302 302 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
303 303 # escape '\$' for regex
304 304 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
305 305 r = re.compile(regex)
306 306 return r.sub(lambda x: replacemap[x.group()], cmd)
307 307
308 308 class cmdalias(object):
309 309 def __init__(self, name, definition, cmdtable, source):
310 310 self.name = self.cmd = name
311 311 self.cmdname = ''
312 312 self.definition = definition
313 313 self.fn = None
314 314 self.givenargs = []
315 315 self.opts = []
316 316 self.help = ''
317 317 self.badalias = None
318 318 self.unknowncmd = False
319 319 self.source = source
320 320
321 321 try:
322 322 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
323 323 for alias, e in cmdtable.iteritems():
324 324 if e is entry:
325 325 self.cmd = alias
326 326 break
327 327 self.shadows = True
328 328 except error.UnknownCommand:
329 329 self.shadows = False
330 330
331 331 if not self.definition:
332 332 self.badalias = _("no definition for alias '%s'") % self.name
333 333 return
334 334
335 335 if self.definition.startswith('!'):
336 336 self.shell = True
337 337 def fn(ui, *args):
338 338 env = {'HG_ARGS': ' '.join((self.name,) + args)}
339 339 def _checkvar(m):
340 340 if m.groups()[0] == '$':
341 341 return m.group()
342 342 elif int(m.groups()[0]) <= len(args):
343 343 return m.group()
344 344 else:
345 345 ui.debug("No argument found for substitution "
346 346 "of %i variable in alias '%s' definition."
347 347 % (int(m.groups()[0]), self.name))
348 348 return ''
349 349 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
350 350 cmd = aliasinterpolate(self.name, args, cmd)
351 351 return ui.system(cmd, environ=env)
352 352 self.fn = fn
353 353 return
354 354
355 355 try:
356 356 args = pycompat.shlexsplit(self.definition)
357 357 except ValueError as inst:
358 358 self.badalias = (_("error in definition for alias '%s': %s")
359 359 % (self.name, inst))
360 360 return
361 361 self.cmdname = cmd = args.pop(0)
362 362 self.givenargs = args
363 363
364 364 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
365 365 if _earlygetopt([invalidarg], args):
366 366 self.badalias = (_("error in definition for alias '%s': %s may "
367 367 "only be given on the command line")
368 368 % (self.name, invalidarg))
369 369 return
370 370
371 371 try:
372 372 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
373 373 if len(tableentry) > 2:
374 374 self.fn, self.opts, self.help = tableentry
375 375 else:
376 376 self.fn, self.opts = tableentry
377 377
378 378 if self.help.startswith("hg " + cmd):
379 379 # drop prefix in old-style help lines so hg shows the alias
380 380 self.help = self.help[4 + len(cmd):]
381 381 self.__doc__ = self.fn.__doc__
382 382
383 383 except error.UnknownCommand:
384 384 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
385 385 % (self.name, cmd))
386 386 self.unknowncmd = True
387 387 except error.AmbiguousCommand:
388 388 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
389 389 % (self.name, cmd))
390 390
391 391 @property
392 392 def args(self):
393 393 args = map(util.expandpath, self.givenargs)
394 394 return aliasargs(self.fn, args)
395 395
396 396 def __getattr__(self, name):
397 397 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
398 398 if name not in adefaults:
399 399 raise AttributeError(name)
400 400 if self.badalias or util.safehasattr(self, 'shell'):
401 401 return adefaults[name]
402 402 return getattr(self.fn, name)
403 403
404 404 def __call__(self, ui, *args, **opts):
405 405 if self.badalias:
406 406 hint = None
407 407 if self.unknowncmd:
408 408 try:
409 409 # check if the command is in a disabled extension
410 410 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
411 411 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
412 412 except error.UnknownCommand:
413 413 pass
414 414 raise error.Abort(self.badalias, hint=hint)
415 415 if self.shadows:
416 416 ui.debug("alias '%s' shadows command '%s'\n" %
417 417 (self.name, self.cmdname))
418 418
419 419 ui.log('commandalias', "alias '%s' expands to '%s'\n",
420 420 self.name, self.definition)
421 421 if util.safehasattr(self, 'shell'):
422 422 return self.fn(ui, *args, **opts)
423 423 else:
424 424 try:
425 425 return util.checksignature(self.fn)(ui, *args, **opts)
426 426 except error.SignatureError:
427 427 args = ' '.join([self.cmdname] + self.args)
428 428 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
429 429 raise
430 430
431 431 def addaliases(ui, cmdtable):
432 432 # aliases are processed after extensions have been loaded, so they
433 433 # may use extension commands. Aliases can also use other alias definitions,
434 434 # but only if they have been defined prior to the current definition.
435 435 for alias, definition in ui.configitems('alias'):
436 436 source = ui.configsource('alias', alias)
437 437 aliasdef = cmdalias(alias, definition, cmdtable, source)
438 438
439 439 try:
440 440 olddef = cmdtable[aliasdef.cmd][0]
441 441 if olddef.definition == aliasdef.definition:
442 442 continue
443 443 except (KeyError, AttributeError):
444 444 # definition might not exist or it might not be a cmdalias
445 445 pass
446 446
447 447 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
448 448
449 449 def _parse(ui, args):
450 450 options = {}
451 451 cmdoptions = {}
452 452
453 453 try:
454 454 args = fancyopts.fancyopts(args, commands.globalopts, options)
455 455 except getopt.GetoptError as inst:
456 456 raise error.CommandError(None, inst)
457 457
458 458 if args:
459 459 cmd, args = args[0], args[1:]
460 460 aliases, entry = cmdutil.findcmd(cmd, commands.table,
461 461 ui.configbool("ui", "strict"))
462 462 cmd = aliases[0]
463 463 args = aliasargs(entry[0], args)
464 464 defaults = ui.config("defaults", cmd)
465 465 if defaults:
466 466 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
467 467 c = list(entry[1])
468 468 else:
469 469 cmd = None
470 470 c = []
471 471
472 472 # combine global options into local
473 473 for o in commands.globalopts:
474 474 c.append((o[0], o[1], options[o[1]], o[3]))
475 475
476 476 try:
477 477 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
478 478 except getopt.GetoptError as inst:
479 479 raise error.CommandError(cmd, inst)
480 480
481 481 # separate global options back out
482 482 for o in commands.globalopts:
483 483 n = o[1]
484 484 options[n] = cmdoptions[n]
485 485 del cmdoptions[n]
486 486
487 487 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
488 488
489 489 def _parseconfig(ui, config):
490 490 """parse the --config options from the command line"""
491 491 configs = []
492 492
493 493 for cfg in config:
494 494 try:
495 495 name, value = [cfgelem.strip()
496 496 for cfgelem in cfg.split('=', 1)]
497 497 section, name = name.split('.', 1)
498 498 if not section or not name:
499 499 raise IndexError
500 500 ui.setconfig(section, name, value, '--config')
501 501 configs.append((section, name, value))
502 502 except (IndexError, ValueError):
503 503 raise error.Abort(_('malformed --config option: %r '
504 504 '(use --config section.name=value)') % cfg)
505 505
506 506 return configs
507 507
508 508 def _earlygetopt(aliases, args):
509 509 """Return list of values for an option (or aliases).
510 510
511 511 The values are listed in the order they appear in args.
512 512 The options and values are removed from args.
513 513
514 514 >>> args = ['x', '--cwd', 'foo', 'y']
515 515 >>> _earlygetopt(['--cwd'], args), args
516 516 (['foo'], ['x', 'y'])
517 517
518 518 >>> args = ['x', '--cwd=bar', 'y']
519 519 >>> _earlygetopt(['--cwd'], args), args
520 520 (['bar'], ['x', 'y'])
521 521
522 522 >>> args = ['x', '-R', 'foo', 'y']
523 523 >>> _earlygetopt(['-R'], args), args
524 524 (['foo'], ['x', 'y'])
525 525
526 526 >>> args = ['x', '-Rbar', 'y']
527 527 >>> _earlygetopt(['-R'], args), args
528 528 (['bar'], ['x', 'y'])
529 529 """
530 530 try:
531 531 argcount = args.index("--")
532 532 except ValueError:
533 533 argcount = len(args)
534 534 shortopts = [opt for opt in aliases if len(opt) == 2]
535 535 values = []
536 536 pos = 0
537 537 while pos < argcount:
538 538 fullarg = arg = args[pos]
539 539 equals = arg.find('=')
540 540 if equals > -1:
541 541 arg = arg[:equals]
542 542 if arg in aliases:
543 543 del args[pos]
544 544 if equals > -1:
545 545 values.append(fullarg[equals + 1:])
546 546 argcount -= 1
547 547 else:
548 548 if pos + 1 >= argcount:
549 549 # ignore and let getopt report an error if there is no value
550 550 break
551 551 values.append(args.pop(pos))
552 552 argcount -= 2
553 553 elif arg[:2] in shortopts:
554 554 # short option can have no following space, e.g. hg log -Rfoo
555 555 values.append(args.pop(pos)[2:])
556 556 argcount -= 1
557 557 else:
558 558 pos += 1
559 559 return values
560 560
561 561 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
562 562 # run pre-hook, and abort if it fails
563 563 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
564 564 pats=cmdpats, opts=cmdoptions)
565 565 try:
566 566 ret = _runcommand(ui, options, cmd, d)
567 567 # run post-hook, passing command result
568 568 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
569 569 result=ret, pats=cmdpats, opts=cmdoptions)
570 570 except Exception:
571 571 # run failure hook and re-raise
572 572 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
573 573 pats=cmdpats, opts=cmdoptions)
574 574 raise
575 575 return ret
576 576
577 577 def _getlocal(ui, rpath, wd=None):
578 578 """Return (path, local ui object) for the given target path.
579 579
580 580 Takes paths in [cwd]/.hg/hgrc into account."
581 581 """
582 582 if wd is None:
583 583 try:
584 584 wd = pycompat.getcwd()
585 585 except OSError as e:
586 586 raise error.Abort(_("error getting current working directory: %s") %
587 587 e.strerror)
588 588 path = cmdutil.findrepo(wd) or ""
589 589 if not path:
590 590 lui = ui
591 591 else:
592 592 lui = ui.copy()
593 593 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
594 594
595 595 if rpath and rpath[-1]:
596 596 path = lui.expandpath(rpath[-1])
597 597 lui = ui.copy()
598 598 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
599 599
600 600 return path, lui
601 601
602 602 def _checkshellalias(lui, ui, args):
603 603 """Return the function to run the shell alias, if it is required"""
604 604 options = {}
605 605
606 606 try:
607 607 args = fancyopts.fancyopts(args, commands.globalopts, options)
608 608 except getopt.GetoptError:
609 609 return
610 610
611 611 if not args:
612 612 return
613 613
614 614 cmdtable = commands.table
615 615
616 616 cmd = args[0]
617 617 try:
618 618 strict = ui.configbool("ui", "strict")
619 619 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
620 620 except (error.AmbiguousCommand, error.UnknownCommand):
621 621 return
622 622
623 623 cmd = aliases[0]
624 624 fn = entry[0]
625 625
626 626 if cmd and util.safehasattr(fn, 'shell'):
627 627 d = lambda: fn(ui, *args[1:])
628 628 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
629 629 [], {})
630 630
631 631 _loaded = set()
632 632
633 633 # list of (objname, loadermod, loadername) tuple:
634 634 # - objname is the name of an object in extension module, from which
635 635 # extra information is loaded
636 636 # - loadermod is the module where loader is placed
637 637 # - loadername is the name of the function, which takes (ui, extensionname,
638 638 # extraobj) arguments
639 639 extraloaders = [
640 640 ('cmdtable', commands, 'loadcmdtable'),
641 641 ('colortable', color, 'loadcolortable'),
642 642 ('filesetpredicate', fileset, 'loadpredicate'),
643 643 ('revsetpredicate', revset, 'loadpredicate'),
644 644 ('templatefilter', templatefilters, 'loadfilter'),
645 645 ('templatefunc', templater, 'loadfunction'),
646 646 ('templatekeyword', templatekw, 'loadkeyword'),
647 647 ]
648 648
649 649 def _dispatch(req):
650 650 args = req.args
651 651 ui = req.ui
652 652
653 653 # check for cwd
654 654 cwd = _earlygetopt(['--cwd'], args)
655 655 if cwd:
656 656 os.chdir(cwd[-1])
657 657
658 658 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
659 659 path, lui = _getlocal(ui, rpath)
660 660
661 661 # Side-effect of accessing is debugcommands module is guaranteed to be
662 662 # imported and commands.table is populated.
663 663 debugcommands.command
664 664
665 665 uis = set([ui, lui])
666 666
667 667 if req.repo:
668 668 uis.add(req.repo.ui)
669 669
670 670 if '--profile' in args:
671 671 for ui_ in uis:
672 672 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
673 673
674 674 with profiling.maybeprofile(lui):
675 675 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
676 676 # reposetup. Programs like TortoiseHg will call _dispatch several
677 677 # times so we keep track of configured extensions in _loaded.
678 678 extensions.loadall(lui)
679 679 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
680 680 # Propagate any changes to lui.__class__ by extensions
681 681 ui.__class__ = lui.__class__
682 682
683 683 # (uisetup and extsetup are handled in extensions.loadall)
684 684
685 685 for name, module in exts:
686 686 for objname, loadermod, loadername in extraloaders:
687 687 extraobj = getattr(module, objname, None)
688 688 if extraobj is not None:
689 689 getattr(loadermod, loadername)(ui, name, extraobj)
690 690 _loaded.add(name)
691 691
692 692 # (reposetup is handled in hg.repository)
693 693
694 694 addaliases(lui, commands.table)
695 695
696 696 # All aliases and commands are completely defined, now.
697 697 # Check abbreviation/ambiguity of shell alias.
698 698 shellaliasfn = _checkshellalias(lui, ui, args)
699 699 if shellaliasfn:
700 700 return shellaliasfn()
701 701
702 702 # check for fallback encoding
703 703 fallback = lui.config('ui', 'fallbackencoding')
704 704 if fallback:
705 705 encoding.fallbackencoding = fallback
706 706
707 707 fullargs = args
708 708 cmd, func, args, options, cmdoptions = _parse(lui, args)
709 709
710 710 if options["config"]:
711 711 raise error.Abort(_("option --config may not be abbreviated!"))
712 712 if options["cwd"]:
713 713 raise error.Abort(_("option --cwd may not be abbreviated!"))
714 714 if options["repository"]:
715 715 raise error.Abort(_(
716 716 "option -R has to be separated from other options (e.g. not "
717 717 "-qR) and --repository may only be abbreviated as --repo!"))
718 718
719 719 if options["encoding"]:
720 720 encoding.encoding = options["encoding"]
721 721 if options["encodingmode"]:
722 722 encoding.encodingmode = options["encodingmode"]
723 723 if options["time"]:
724 724 def get_times():
725 725 t = os.times()
726 726 if t[4] == 0.0:
727 727 # Windows leaves this as zero, so use time.clock()
728 728 t = (t[0], t[1], t[2], t[3], time.clock())
729 729 return t
730 730 s = get_times()
731 731 def print_time():
732 732 t = get_times()
733 733 ui.warn(
734 734 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
735 735 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
736 736 atexit.register(print_time)
737 737
738 738 if options['verbose'] or options['debug'] or options['quiet']:
739 739 for opt in ('verbose', 'debug', 'quiet'):
740 740 val = str(bool(options[opt]))
741 741 for ui_ in uis:
742 742 ui_.setconfig('ui', opt, val, '--' + opt)
743 743
744 744 if options['traceback']:
745 745 for ui_ in uis:
746 746 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
747 747
748 748 if options['noninteractive']:
749 749 for ui_ in uis:
750 750 ui_.setconfig('ui', 'interactive', 'off', '-y')
751 751
752 if options['pager'] != 'auto' and not util.parsebool(options['pager']):
753 ui.neverpager()
754
752 755 if cmdoptions.get('insecure', False):
753 756 for ui_ in uis:
754 757 ui_.insecureconnections = True
755 758
756 759 if options['version']:
757 760 return commands.version_(ui)
758 761 if options['help']:
759 762 return commands.help_(ui, cmd, command=cmd is not None)
760 763 elif not cmd:
761 764 return commands.help_(ui, 'shortlist')
762 765
763 766 repo = None
764 767 cmdpats = args[:]
765 768 if not func.norepo:
766 769 # use the repo from the request only if we don't have -R
767 770 if not rpath and not cwd:
768 771 repo = req.repo
769 772
770 773 if repo:
771 774 # set the descriptors of the repo ui to those of ui
772 775 repo.ui.fin = ui.fin
773 776 repo.ui.fout = ui.fout
774 777 repo.ui.ferr = ui.ferr
775 778 else:
776 779 try:
777 780 repo = hg.repository(ui, path=path)
778 781 if not repo.local():
779 782 raise error.Abort(_("repository '%s' is not local")
780 783 % path)
781 784 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
782 785 'repo')
783 786 except error.RequirementError:
784 787 raise
785 788 except error.RepoError:
786 789 if rpath and rpath[-1]: # invalid -R path
787 790 raise
788 791 if not func.optionalrepo:
789 792 if func.inferrepo and args and not path:
790 793 # try to infer -R from command args
791 794 repos = map(cmdutil.findrepo, args)
792 795 guess = repos[0]
793 796 if guess and repos.count(guess) == len(repos):
794 797 req.args = ['--repository', guess] + fullargs
795 798 return _dispatch(req)
796 799 if not path:
797 800 raise error.RepoError(_("no repository found in"
798 801 " '%s' (.hg not found)")
799 802 % pycompat.getcwd())
800 803 raise
801 804 if repo:
802 805 ui = repo.ui
803 806 if options['hidden']:
804 807 repo = repo.unfiltered()
805 808 args.insert(0, repo)
806 809 elif rpath:
807 810 ui.warn(_("warning: --repository ignored\n"))
808 811
809 812 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
810 813 ui.log("command", '%s\n', msg)
811 814 strcmdopt = pycompat.strkwargs(cmdoptions)
812 815 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
813 816 try:
814 817 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
815 818 cmdpats, cmdoptions)
816 819 finally:
817 820 if repo and repo != req.repo:
818 821 repo.close()
819 822
820 823 def _runcommand(ui, options, cmd, cmdfunc):
821 824 """Run a command function, possibly with profiling enabled."""
822 825 if util.parsebool(options['pager']):
823 826 ui.pager('internal-always-' + cmd)
824 827 try:
825 828 return cmdfunc()
826 829 except error.SignatureError:
827 830 raise error.CommandError(cmd, _('invalid arguments'))
828 831
829 832 def _exceptionwarning(ui):
830 833 """Produce a warning message for the current active exception"""
831 834
832 835 # For compatibility checking, we discard the portion of the hg
833 836 # version after the + on the assumption that if a "normal
834 837 # user" is running a build with a + in it the packager
835 838 # probably built from fairly close to a tag and anyone with a
836 839 # 'make local' copy of hg (where the version number can be out
837 840 # of date) will be clueful enough to notice the implausible
838 841 # version number and try updating.
839 842 ct = util.versiontuple(n=2)
840 843 worst = None, ct, ''
841 844 if ui.config('ui', 'supportcontact', None) is None:
842 845 for name, mod in extensions.extensions():
843 846 testedwith = getattr(mod, 'testedwith', '')
844 847 report = getattr(mod, 'buglink', _('the extension author.'))
845 848 if not testedwith.strip():
846 849 # We found an untested extension. It's likely the culprit.
847 850 worst = name, 'unknown', report
848 851 break
849 852
850 853 # Never blame on extensions bundled with Mercurial.
851 854 if extensions.ismoduleinternal(mod):
852 855 continue
853 856
854 857 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
855 858 if ct in tested:
856 859 continue
857 860
858 861 lower = [t for t in tested if t < ct]
859 862 nearest = max(lower or tested)
860 863 if worst[0] is None or nearest < worst[1]:
861 864 worst = name, nearest, report
862 865 if worst[0] is not None:
863 866 name, testedwith, report = worst
864 867 if not isinstance(testedwith, str):
865 868 testedwith = '.'.join([str(c) for c in testedwith])
866 869 warning = (_('** Unknown exception encountered with '
867 870 'possibly-broken third-party extension %s\n'
868 871 '** which supports versions %s of Mercurial.\n'
869 872 '** Please disable %s and try your action again.\n'
870 873 '** If that fixes the bug please report it to %s\n')
871 874 % (name, testedwith, name, report))
872 875 else:
873 876 bugtracker = ui.config('ui', 'supportcontact', None)
874 877 if bugtracker is None:
875 878 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
876 879 warning = (_("** unknown exception encountered, "
877 880 "please report by visiting\n** ") + bugtracker + '\n')
878 881 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
879 882 (_("** Mercurial Distributed SCM (version %s)\n") %
880 883 util.version()) +
881 884 (_("** Extensions loaded: %s\n") %
882 885 ", ".join([x[0] for x in extensions.extensions()])))
883 886 return warning
884 887
885 888 def handlecommandexception(ui):
886 889 """Produce a warning message for broken commands
887 890
888 891 Called when handling an exception; the exception is reraised if
889 892 this function returns False, ignored otherwise.
890 893 """
891 894 warning = _exceptionwarning(ui)
892 895 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
893 896 ui.warn(warning)
894 897 return False # re-raise the exception
@@ -1,1595 +1,1601 b''
1 1 # ui.py - user interface bits 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
9 9
10 10 import atexit
11 11 import collections
12 12 import contextlib
13 13 import errno
14 14 import getpass
15 15 import inspect
16 16 import os
17 17 import re
18 18 import signal
19 19 import socket
20 20 import subprocess
21 21 import sys
22 22 import tempfile
23 23 import traceback
24 24
25 25 from .i18n import _
26 26 from .node import hex
27 27
28 28 from . import (
29 29 config,
30 30 encoding,
31 31 error,
32 32 formatter,
33 33 progress,
34 34 pycompat,
35 35 scmutil,
36 36 util,
37 37 )
38 38
39 39 urlreq = util.urlreq
40 40
41 41 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
42 42 _keepalnum = ''.join(c for c in map(chr, range(256)) if not c.isalnum())
43 43
44 44 samplehgrcs = {
45 45 'user':
46 46 """# example user config (see 'hg help config' for more info)
47 47 [ui]
48 48 # name and email, e.g.
49 49 # username = Jane Doe <jdoe@example.com>
50 50 username =
51 51
52 52 [extensions]
53 53 # uncomment these lines to enable some popular extensions
54 54 # (see 'hg help extensions' for more info)
55 55 #
56 56 # pager =
57 57 # color =""",
58 58
59 59 'cloned':
60 60 """# example repository config (see 'hg help config' for more info)
61 61 [paths]
62 62 default = %s
63 63
64 64 # path aliases to other clones of this repo in URLs or filesystem paths
65 65 # (see 'hg help config.paths' for more info)
66 66 #
67 67 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
68 68 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
69 69 # my-clone = /home/jdoe/jdoes-clone
70 70
71 71 [ui]
72 72 # name and email (local to this repository, optional), e.g.
73 73 # username = Jane Doe <jdoe@example.com>
74 74 """,
75 75
76 76 'local':
77 77 """# example repository config (see 'hg help config' for more info)
78 78 [paths]
79 79 # path aliases to other clones of this repo in URLs or filesystem paths
80 80 # (see 'hg help config.paths' for more info)
81 81 #
82 82 # default = http://example.com/hg/example-repo
83 83 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
84 84 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
85 85 # my-clone = /home/jdoe/jdoes-clone
86 86
87 87 [ui]
88 88 # name and email (local to this repository, optional), e.g.
89 89 # username = Jane Doe <jdoe@example.com>
90 90 """,
91 91
92 92 'global':
93 93 """# example system-wide hg config (see 'hg help config' for more info)
94 94
95 95 [extensions]
96 96 # uncomment these lines to enable some popular extensions
97 97 # (see 'hg help extensions' for more info)
98 98 #
99 99 # blackbox =
100 100 # color =
101 101 # pager =""",
102 102 }
103 103
104 104
105 105 class httppasswordmgrdbproxy(object):
106 106 """Delays loading urllib2 until it's needed."""
107 107 def __init__(self):
108 108 self._mgr = None
109 109
110 110 def _get_mgr(self):
111 111 if self._mgr is None:
112 112 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
113 113 return self._mgr
114 114
115 115 def add_password(self, *args, **kwargs):
116 116 return self._get_mgr().add_password(*args, **kwargs)
117 117
118 118 def find_user_password(self, *args, **kwargs):
119 119 return self._get_mgr().find_user_password(*args, **kwargs)
120 120
121 121 def _catchterm(*args):
122 122 raise error.SignalInterrupt
123 123
124 124 class ui(object):
125 125 def __init__(self, src=None):
126 126 """Create a fresh new ui object if no src given
127 127
128 128 Use uimod.ui.load() to create a ui which knows global and user configs.
129 129 In most cases, you should use ui.copy() to create a copy of an existing
130 130 ui object.
131 131 """
132 132 # _buffers: used for temporary capture of output
133 133 self._buffers = []
134 134 # 3-tuple describing how each buffer in the stack behaves.
135 135 # Values are (capture stderr, capture subprocesses, apply labels).
136 136 self._bufferstates = []
137 137 # When a buffer is active, defines whether we are expanding labels.
138 138 # This exists to prevent an extra list lookup.
139 139 self._bufferapplylabels = None
140 140 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
141 141 self._reportuntrusted = True
142 142 self._ocfg = config.config() # overlay
143 143 self._tcfg = config.config() # trusted
144 144 self._ucfg = config.config() # untrusted
145 145 self._trustusers = set()
146 146 self._trustgroups = set()
147 147 self.callhooks = True
148 148 # Insecure server connections requested.
149 149 self.insecureconnections = False
150 150 # Blocked time
151 151 self.logblockedtimes = False
152 152
153 153 if src:
154 154 self.fout = src.fout
155 155 self.ferr = src.ferr
156 156 self.fin = src.fin
157 157 self.pageractive = src.pageractive
158 self._neverpager = src._neverpager
158 159
159 160 self._tcfg = src._tcfg.copy()
160 161 self._ucfg = src._ucfg.copy()
161 162 self._ocfg = src._ocfg.copy()
162 163 self._trustusers = src._trustusers.copy()
163 164 self._trustgroups = src._trustgroups.copy()
164 165 self.environ = src.environ
165 166 self.callhooks = src.callhooks
166 167 self.insecureconnections = src.insecureconnections
167 168 self.fixconfig()
168 169
169 170 self.httppasswordmgrdb = src.httppasswordmgrdb
170 171 self._blockedtimes = src._blockedtimes
171 172 else:
172 173 self.fout = util.stdout
173 174 self.ferr = util.stderr
174 175 self.fin = util.stdin
175 176 self.pageractive = False
177 self._neverpager = False
176 178
177 179 # shared read-only environment
178 180 self.environ = encoding.environ
179 181
180 182 self.httppasswordmgrdb = httppasswordmgrdbproxy()
181 183 self._blockedtimes = collections.defaultdict(int)
182 184
183 185 allowed = self.configlist('experimental', 'exportableenviron')
184 186 if '*' in allowed:
185 187 self._exportableenviron = self.environ
186 188 else:
187 189 self._exportableenviron = {}
188 190 for k in allowed:
189 191 if k in self.environ:
190 192 self._exportableenviron[k] = self.environ[k]
191 193
192 194 @classmethod
193 195 def load(cls):
194 196 """Create a ui and load global and user configs"""
195 197 u = cls()
196 198 # we always trust global config files
197 199 for f in scmutil.rcpath():
198 200 u.readconfig(f, trust=True)
199 201 return u
200 202
201 203 def copy(self):
202 204 return self.__class__(self)
203 205
204 206 def resetstate(self):
205 207 """Clear internal state that shouldn't persist across commands"""
206 208 if self._progbar:
207 209 self._progbar.resetstate() # reset last-print time of progress bar
208 210 self.httppasswordmgrdb = httppasswordmgrdbproxy()
209 211
210 212 @contextlib.contextmanager
211 213 def timeblockedsection(self, key):
212 214 # this is open-coded below - search for timeblockedsection to find them
213 215 starttime = util.timer()
214 216 try:
215 217 yield
216 218 finally:
217 219 self._blockedtimes[key + '_blocked'] += \
218 220 (util.timer() - starttime) * 1000
219 221
220 222 def formatter(self, topic, opts):
221 223 return formatter.formatter(self, topic, opts)
222 224
223 225 def _trusted(self, fp, f):
224 226 st = util.fstat(fp)
225 227 if util.isowner(st):
226 228 return True
227 229
228 230 tusers, tgroups = self._trustusers, self._trustgroups
229 231 if '*' in tusers or '*' in tgroups:
230 232 return True
231 233
232 234 user = util.username(st.st_uid)
233 235 group = util.groupname(st.st_gid)
234 236 if user in tusers or group in tgroups or user == util.username():
235 237 return True
236 238
237 239 if self._reportuntrusted:
238 240 self.warn(_('not trusting file %s from untrusted '
239 241 'user %s, group %s\n') % (f, user, group))
240 242 return False
241 243
242 244 def readconfig(self, filename, root=None, trust=False,
243 245 sections=None, remap=None):
244 246 try:
245 247 fp = open(filename, u'rb')
246 248 except IOError:
247 249 if not sections: # ignore unless we were looking for something
248 250 return
249 251 raise
250 252
251 253 cfg = config.config()
252 254 trusted = sections or trust or self._trusted(fp, filename)
253 255
254 256 try:
255 257 cfg.read(filename, fp, sections=sections, remap=remap)
256 258 fp.close()
257 259 except error.ConfigError as inst:
258 260 if trusted:
259 261 raise
260 262 self.warn(_("ignored: %s\n") % str(inst))
261 263
262 264 if self.plain():
263 265 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
264 266 'logtemplate', 'statuscopies', 'style',
265 267 'traceback', 'verbose'):
266 268 if k in cfg['ui']:
267 269 del cfg['ui'][k]
268 270 for k, v in cfg.items('defaults'):
269 271 del cfg['defaults'][k]
270 272 # Don't remove aliases from the configuration if in the exceptionlist
271 273 if self.plain('alias'):
272 274 for k, v in cfg.items('alias'):
273 275 del cfg['alias'][k]
274 276 if self.plain('revsetalias'):
275 277 for k, v in cfg.items('revsetalias'):
276 278 del cfg['revsetalias'][k]
277 279 if self.plain('templatealias'):
278 280 for k, v in cfg.items('templatealias'):
279 281 del cfg['templatealias'][k]
280 282
281 283 if trusted:
282 284 self._tcfg.update(cfg)
283 285 self._tcfg.update(self._ocfg)
284 286 self._ucfg.update(cfg)
285 287 self._ucfg.update(self._ocfg)
286 288
287 289 if root is None:
288 290 root = os.path.expanduser('~')
289 291 self.fixconfig(root=root)
290 292
291 293 def fixconfig(self, root=None, section=None):
292 294 if section in (None, 'paths'):
293 295 # expand vars and ~
294 296 # translate paths relative to root (or home) into absolute paths
295 297 root = root or pycompat.getcwd()
296 298 for c in self._tcfg, self._ucfg, self._ocfg:
297 299 for n, p in c.items('paths'):
298 300 # Ignore sub-options.
299 301 if ':' in n:
300 302 continue
301 303 if not p:
302 304 continue
303 305 if '%%' in p:
304 306 s = self.configsource('paths', n) or 'none'
305 307 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
306 308 % (n, p, s))
307 309 p = p.replace('%%', '%')
308 310 p = util.expandpath(p)
309 311 if not util.hasscheme(p) and not os.path.isabs(p):
310 312 p = os.path.normpath(os.path.join(root, p))
311 313 c.set("paths", n, p)
312 314
313 315 if section in (None, 'ui'):
314 316 # update ui options
315 317 self.debugflag = self.configbool('ui', 'debug')
316 318 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
317 319 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
318 320 if self.verbose and self.quiet:
319 321 self.quiet = self.verbose = False
320 322 self._reportuntrusted = self.debugflag or self.configbool("ui",
321 323 "report_untrusted", True)
322 324 self.tracebackflag = self.configbool('ui', 'traceback', False)
323 325 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
324 326
325 327 if section in (None, 'trusted'):
326 328 # update trust information
327 329 self._trustusers.update(self.configlist('trusted', 'users'))
328 330 self._trustgroups.update(self.configlist('trusted', 'groups'))
329 331
330 332 def backupconfig(self, section, item):
331 333 return (self._ocfg.backup(section, item),
332 334 self._tcfg.backup(section, item),
333 335 self._ucfg.backup(section, item),)
334 336 def restoreconfig(self, data):
335 337 self._ocfg.restore(data[0])
336 338 self._tcfg.restore(data[1])
337 339 self._ucfg.restore(data[2])
338 340
339 341 def setconfig(self, section, name, value, source=''):
340 342 for cfg in (self._ocfg, self._tcfg, self._ucfg):
341 343 cfg.set(section, name, value, source)
342 344 self.fixconfig(section=section)
343 345
344 346 def _data(self, untrusted):
345 347 return untrusted and self._ucfg or self._tcfg
346 348
347 349 def configsource(self, section, name, untrusted=False):
348 350 return self._data(untrusted).source(section, name)
349 351
350 352 def config(self, section, name, default=None, untrusted=False):
351 353 if isinstance(name, list):
352 354 alternates = name
353 355 else:
354 356 alternates = [name]
355 357
356 358 for n in alternates:
357 359 value = self._data(untrusted).get(section, n, None)
358 360 if value is not None:
359 361 name = n
360 362 break
361 363 else:
362 364 value = default
363 365
364 366 if self.debugflag and not untrusted and self._reportuntrusted:
365 367 for n in alternates:
366 368 uvalue = self._ucfg.get(section, n)
367 369 if uvalue is not None and uvalue != value:
368 370 self.debug("ignoring untrusted configuration option "
369 371 "%s.%s = %s\n" % (section, n, uvalue))
370 372 return value
371 373
372 374 def configsuboptions(self, section, name, default=None, untrusted=False):
373 375 """Get a config option and all sub-options.
374 376
375 377 Some config options have sub-options that are declared with the
376 378 format "key:opt = value". This method is used to return the main
377 379 option and all its declared sub-options.
378 380
379 381 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
380 382 is a dict of defined sub-options where keys and values are strings.
381 383 """
382 384 data = self._data(untrusted)
383 385 main = data.get(section, name, default)
384 386 if self.debugflag and not untrusted and self._reportuntrusted:
385 387 uvalue = self._ucfg.get(section, name)
386 388 if uvalue is not None and uvalue != main:
387 389 self.debug('ignoring untrusted configuration option '
388 390 '%s.%s = %s\n' % (section, name, uvalue))
389 391
390 392 sub = {}
391 393 prefix = '%s:' % name
392 394 for k, v in data.items(section):
393 395 if k.startswith(prefix):
394 396 sub[k[len(prefix):]] = v
395 397
396 398 if self.debugflag and not untrusted and self._reportuntrusted:
397 399 for k, v in sub.items():
398 400 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
399 401 if uvalue is not None and uvalue != v:
400 402 self.debug('ignoring untrusted configuration option '
401 403 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
402 404
403 405 return main, sub
404 406
405 407 def configpath(self, section, name, default=None, untrusted=False):
406 408 'get a path config item, expanded relative to repo root or config file'
407 409 v = self.config(section, name, default, untrusted)
408 410 if v is None:
409 411 return None
410 412 if not os.path.isabs(v) or "://" not in v:
411 413 src = self.configsource(section, name, untrusted)
412 414 if ':' in src:
413 415 base = os.path.dirname(src.rsplit(':')[0])
414 416 v = os.path.join(base, os.path.expanduser(v))
415 417 return v
416 418
417 419 def configbool(self, section, name, default=False, untrusted=False):
418 420 """parse a configuration element as a boolean
419 421
420 422 >>> u = ui(); s = 'foo'
421 423 >>> u.setconfig(s, 'true', 'yes')
422 424 >>> u.configbool(s, 'true')
423 425 True
424 426 >>> u.setconfig(s, 'false', 'no')
425 427 >>> u.configbool(s, 'false')
426 428 False
427 429 >>> u.configbool(s, 'unknown')
428 430 False
429 431 >>> u.configbool(s, 'unknown', True)
430 432 True
431 433 >>> u.setconfig(s, 'invalid', 'somevalue')
432 434 >>> u.configbool(s, 'invalid')
433 435 Traceback (most recent call last):
434 436 ...
435 437 ConfigError: foo.invalid is not a boolean ('somevalue')
436 438 """
437 439
438 440 v = self.config(section, name, None, untrusted)
439 441 if v is None:
440 442 return default
441 443 if isinstance(v, bool):
442 444 return v
443 445 b = util.parsebool(v)
444 446 if b is None:
445 447 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
446 448 % (section, name, v))
447 449 return b
448 450
449 451 def configwith(self, convert, section, name, default=None,
450 452 desc=None, untrusted=False):
451 453 """parse a configuration element with a conversion function
452 454
453 455 >>> u = ui(); s = 'foo'
454 456 >>> u.setconfig(s, 'float1', '42')
455 457 >>> u.configwith(float, s, 'float1')
456 458 42.0
457 459 >>> u.setconfig(s, 'float2', '-4.25')
458 460 >>> u.configwith(float, s, 'float2')
459 461 -4.25
460 462 >>> u.configwith(float, s, 'unknown', 7)
461 463 7
462 464 >>> u.setconfig(s, 'invalid', 'somevalue')
463 465 >>> u.configwith(float, s, 'invalid')
464 466 Traceback (most recent call last):
465 467 ...
466 468 ConfigError: foo.invalid is not a valid float ('somevalue')
467 469 >>> u.configwith(float, s, 'invalid', desc='womble')
468 470 Traceback (most recent call last):
469 471 ...
470 472 ConfigError: foo.invalid is not a valid womble ('somevalue')
471 473 """
472 474
473 475 v = self.config(section, name, None, untrusted)
474 476 if v is None:
475 477 return default
476 478 try:
477 479 return convert(v)
478 480 except ValueError:
479 481 if desc is None:
480 482 desc = convert.__name__
481 483 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
482 484 % (section, name, desc, v))
483 485
484 486 def configint(self, section, name, default=None, untrusted=False):
485 487 """parse a configuration element as an integer
486 488
487 489 >>> u = ui(); s = 'foo'
488 490 >>> u.setconfig(s, 'int1', '42')
489 491 >>> u.configint(s, 'int1')
490 492 42
491 493 >>> u.setconfig(s, 'int2', '-42')
492 494 >>> u.configint(s, 'int2')
493 495 -42
494 496 >>> u.configint(s, 'unknown', 7)
495 497 7
496 498 >>> u.setconfig(s, 'invalid', 'somevalue')
497 499 >>> u.configint(s, 'invalid')
498 500 Traceback (most recent call last):
499 501 ...
500 502 ConfigError: foo.invalid is not a valid integer ('somevalue')
501 503 """
502 504
503 505 return self.configwith(int, section, name, default, 'integer',
504 506 untrusted)
505 507
506 508 def configbytes(self, section, name, default=0, untrusted=False):
507 509 """parse a configuration element as a quantity in bytes
508 510
509 511 Units can be specified as b (bytes), k or kb (kilobytes), m or
510 512 mb (megabytes), g or gb (gigabytes).
511 513
512 514 >>> u = ui(); s = 'foo'
513 515 >>> u.setconfig(s, 'val1', '42')
514 516 >>> u.configbytes(s, 'val1')
515 517 42
516 518 >>> u.setconfig(s, 'val2', '42.5 kb')
517 519 >>> u.configbytes(s, 'val2')
518 520 43520
519 521 >>> u.configbytes(s, 'unknown', '7 MB')
520 522 7340032
521 523 >>> u.setconfig(s, 'invalid', 'somevalue')
522 524 >>> u.configbytes(s, 'invalid')
523 525 Traceback (most recent call last):
524 526 ...
525 527 ConfigError: foo.invalid is not a byte quantity ('somevalue')
526 528 """
527 529
528 530 value = self.config(section, name)
529 531 if value is None:
530 532 if not isinstance(default, str):
531 533 return default
532 534 value = default
533 535 try:
534 536 return util.sizetoint(value)
535 537 except error.ParseError:
536 538 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
537 539 % (section, name, value))
538 540
539 541 def configlist(self, section, name, default=None, untrusted=False):
540 542 """parse a configuration element as a list of comma/space separated
541 543 strings
542 544
543 545 >>> u = ui(); s = 'foo'
544 546 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
545 547 >>> u.configlist(s, 'list1')
546 548 ['this', 'is', 'a small', 'test']
547 549 """
548 550
549 551 def _parse_plain(parts, s, offset):
550 552 whitespace = False
551 553 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
552 554 whitespace = True
553 555 offset += 1
554 556 if offset >= len(s):
555 557 return None, parts, offset
556 558 if whitespace:
557 559 parts.append('')
558 560 if s[offset] == '"' and not parts[-1]:
559 561 return _parse_quote, parts, offset + 1
560 562 elif s[offset] == '"' and parts[-1][-1] == '\\':
561 563 parts[-1] = parts[-1][:-1] + s[offset]
562 564 return _parse_plain, parts, offset + 1
563 565 parts[-1] += s[offset]
564 566 return _parse_plain, parts, offset + 1
565 567
566 568 def _parse_quote(parts, s, offset):
567 569 if offset < len(s) and s[offset] == '"': # ""
568 570 parts.append('')
569 571 offset += 1
570 572 while offset < len(s) and (s[offset].isspace() or
571 573 s[offset] == ','):
572 574 offset += 1
573 575 return _parse_plain, parts, offset
574 576
575 577 while offset < len(s) and s[offset] != '"':
576 578 if (s[offset] == '\\' and offset + 1 < len(s)
577 579 and s[offset + 1] == '"'):
578 580 offset += 1
579 581 parts[-1] += '"'
580 582 else:
581 583 parts[-1] += s[offset]
582 584 offset += 1
583 585
584 586 if offset >= len(s):
585 587 real_parts = _configlist(parts[-1])
586 588 if not real_parts:
587 589 parts[-1] = '"'
588 590 else:
589 591 real_parts[0] = '"' + real_parts[0]
590 592 parts = parts[:-1]
591 593 parts.extend(real_parts)
592 594 return None, parts, offset
593 595
594 596 offset += 1
595 597 while offset < len(s) and s[offset] in [' ', ',']:
596 598 offset += 1
597 599
598 600 if offset < len(s):
599 601 if offset + 1 == len(s) and s[offset] == '"':
600 602 parts[-1] += '"'
601 603 offset += 1
602 604 else:
603 605 parts.append('')
604 606 else:
605 607 return None, parts, offset
606 608
607 609 return _parse_plain, parts, offset
608 610
609 611 def _configlist(s):
610 612 s = s.rstrip(' ,')
611 613 if not s:
612 614 return []
613 615 parser, parts, offset = _parse_plain, [''], 0
614 616 while parser:
615 617 parser, parts, offset = parser(parts, s, offset)
616 618 return parts
617 619
618 620 result = self.config(section, name, untrusted=untrusted)
619 621 if result is None:
620 622 result = default or []
621 623 if isinstance(result, bytes):
622 624 result = _configlist(result.lstrip(' ,\n'))
623 625 if result is None:
624 626 result = default or []
625 627 return result
626 628
627 629 def hasconfig(self, section, name, untrusted=False):
628 630 return self._data(untrusted).hasitem(section, name)
629 631
630 632 def has_section(self, section, untrusted=False):
631 633 '''tell whether section exists in config.'''
632 634 return section in self._data(untrusted)
633 635
634 636 def configitems(self, section, untrusted=False, ignoresub=False):
635 637 items = self._data(untrusted).items(section)
636 638 if ignoresub:
637 639 newitems = {}
638 640 for k, v in items:
639 641 if ':' not in k:
640 642 newitems[k] = v
641 643 items = newitems.items()
642 644 if self.debugflag and not untrusted and self._reportuntrusted:
643 645 for k, v in self._ucfg.items(section):
644 646 if self._tcfg.get(section, k) != v:
645 647 self.debug("ignoring untrusted configuration option "
646 648 "%s.%s = %s\n" % (section, k, v))
647 649 return items
648 650
649 651 def walkconfig(self, untrusted=False):
650 652 cfg = self._data(untrusted)
651 653 for section in cfg.sections():
652 654 for name, value in self.configitems(section, untrusted):
653 655 yield section, name, value
654 656
655 657 def plain(self, feature=None):
656 658 '''is plain mode active?
657 659
658 660 Plain mode means that all configuration variables which affect
659 661 the behavior and output of Mercurial should be
660 662 ignored. Additionally, the output should be stable,
661 663 reproducible and suitable for use in scripts or applications.
662 664
663 665 The only way to trigger plain mode is by setting either the
664 666 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
665 667
666 668 The return value can either be
667 669 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
668 670 - True otherwise
669 671 '''
670 672 if ('HGPLAIN' not in encoding.environ and
671 673 'HGPLAINEXCEPT' not in encoding.environ):
672 674 return False
673 675 exceptions = encoding.environ.get('HGPLAINEXCEPT',
674 676 '').strip().split(',')
675 677 if feature and exceptions:
676 678 return feature not in exceptions
677 679 return True
678 680
679 681 def username(self):
680 682 """Return default username to be used in commits.
681 683
682 684 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
683 685 and stop searching if one of these is set.
684 686 If not found and ui.askusername is True, ask the user, else use
685 687 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
686 688 """
687 689 user = encoding.environ.get("HGUSER")
688 690 if user is None:
689 691 user = self.config("ui", ["username", "user"])
690 692 if user is not None:
691 693 user = os.path.expandvars(user)
692 694 if user is None:
693 695 user = encoding.environ.get("EMAIL")
694 696 if user is None and self.configbool("ui", "askusername"):
695 697 user = self.prompt(_("enter a commit username:"), default=None)
696 698 if user is None and not self.interactive():
697 699 try:
698 700 user = '%s@%s' % (util.getuser(), socket.getfqdn())
699 701 self.warn(_("no username found, using '%s' instead\n") % user)
700 702 except KeyError:
701 703 pass
702 704 if not user:
703 705 raise error.Abort(_('no username supplied'),
704 706 hint=_("use 'hg config --edit' "
705 707 'to set your username'))
706 708 if "\n" in user:
707 709 raise error.Abort(_("username %s contains a newline\n")
708 710 % repr(user))
709 711 return user
710 712
711 713 def shortuser(self, user):
712 714 """Return a short representation of a user name or email address."""
713 715 if not self.verbose:
714 716 user = util.shortuser(user)
715 717 return user
716 718
717 719 def expandpath(self, loc, default=None):
718 720 """Return repository location relative to cwd or from [paths]"""
719 721 try:
720 722 p = self.paths.getpath(loc)
721 723 if p:
722 724 return p.rawloc
723 725 except error.RepoError:
724 726 pass
725 727
726 728 if default:
727 729 try:
728 730 p = self.paths.getpath(default)
729 731 if p:
730 732 return p.rawloc
731 733 except error.RepoError:
732 734 pass
733 735
734 736 return loc
735 737
736 738 @util.propertycache
737 739 def paths(self):
738 740 return paths(self)
739 741
740 742 def pushbuffer(self, error=False, subproc=False, labeled=False):
741 743 """install a buffer to capture standard output of the ui object
742 744
743 745 If error is True, the error output will be captured too.
744 746
745 747 If subproc is True, output from subprocesses (typically hooks) will be
746 748 captured too.
747 749
748 750 If labeled is True, any labels associated with buffered
749 751 output will be handled. By default, this has no effect
750 752 on the output returned, but extensions and GUI tools may
751 753 handle this argument and returned styled output. If output
752 754 is being buffered so it can be captured and parsed or
753 755 processed, labeled should not be set to True.
754 756 """
755 757 self._buffers.append([])
756 758 self._bufferstates.append((error, subproc, labeled))
757 759 self._bufferapplylabels = labeled
758 760
759 761 def popbuffer(self):
760 762 '''pop the last buffer and return the buffered output'''
761 763 self._bufferstates.pop()
762 764 if self._bufferstates:
763 765 self._bufferapplylabels = self._bufferstates[-1][2]
764 766 else:
765 767 self._bufferapplylabels = None
766 768
767 769 return "".join(self._buffers.pop())
768 770
769 771 def write(self, *args, **opts):
770 772 '''write args to output
771 773
772 774 By default, this method simply writes to the buffer or stdout,
773 775 but extensions or GUI tools may override this method,
774 776 write_err(), popbuffer(), and label() to style output from
775 777 various parts of hg.
776 778
777 779 An optional keyword argument, "label", can be passed in.
778 780 This should be a string containing label names separated by
779 781 space. Label names take the form of "topic.type". For example,
780 782 ui.debug() issues a label of "ui.debug".
781 783
782 784 When labeling output for a specific command, a label of
783 785 "cmdname.type" is recommended. For example, status issues
784 786 a label of "status.modified" for modified files.
785 787 '''
786 788 if self._buffers and not opts.get('prompt', False):
787 789 self._buffers[-1].extend(a for a in args)
788 790 else:
789 791 self._progclear()
790 792 # opencode timeblockedsection because this is a critical path
791 793 starttime = util.timer()
792 794 try:
793 795 for a in args:
794 796 self.fout.write(a)
795 797 finally:
796 798 self._blockedtimes['stdio_blocked'] += \
797 799 (util.timer() - starttime) * 1000
798 800
799 801 def write_err(self, *args, **opts):
800 802 self._progclear()
801 803 try:
802 804 if self._bufferstates and self._bufferstates[-1][0]:
803 805 return self.write(*args, **opts)
804 806 with self.timeblockedsection('stdio'):
805 807 if not getattr(self.fout, 'closed', False):
806 808 self.fout.flush()
807 809 for a in args:
808 810 self.ferr.write(a)
809 811 # stderr may be buffered under win32 when redirected to files,
810 812 # including stdout.
811 813 if not getattr(self.ferr, 'closed', False):
812 814 self.ferr.flush()
813 815 except IOError as inst:
814 816 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
815 817 raise
816 818
817 819 def flush(self):
818 820 # opencode timeblockedsection because this is a critical path
819 821 starttime = util.timer()
820 822 try:
821 823 try: self.fout.flush()
822 824 except (IOError, ValueError): pass
823 825 try: self.ferr.flush()
824 826 except (IOError, ValueError): pass
825 827 finally:
826 828 self._blockedtimes['stdio_blocked'] += \
827 829 (util.timer() - starttime) * 1000
828 830
829 831 def _isatty(self, fh):
830 832 if self.configbool('ui', 'nontty', False):
831 833 return False
832 834 return util.isatty(fh)
833 835
836 def neverpager(self):
837 self._neverpager = True
838
834 839 def pager(self, command):
835 840 """Start a pager for subsequent command output.
836 841
837 842 Commands which produce a long stream of output should call
838 843 this function to activate the user's preferred pagination
839 844 mechanism (which may be no pager). Calling this function
840 845 precludes any future use of interactive functionality, such as
841 846 prompting the user or activating curses.
842 847
843 848 Args:
844 849 command: The full, non-aliased name of the command. That is, "log"
845 850 not "history, "summary" not "summ", etc.
846 851 """
847 if (self.pageractive
852 if (self._neverpager
853 or self.pageractive
848 854 # TODO: if we want to allow HGPLAINEXCEPT=pager,
849 855 # formatted() will need some adjustment.
850 856 or not self.formatted()
851 857 or self.plain()
852 858 # TODO: expose debugger-enabled on the UI object
853 859 or '--debugger' in sys.argv):
854 860 # We only want to paginate if the ui appears to be
855 861 # interactive, the user didn't say HGPLAIN or
856 862 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
857 863 return
858 864
859 865 # TODO: add a "system defaults" config section so this default
860 866 # of more(1) can be easily replaced with a global
861 867 # configuration file. For example, on OS X the sane default is
862 868 # less(1), not more(1), and on debian it's
863 869 # sensible-pager(1). We should probably also give the system
864 870 # default editor command similar treatment.
865 871 envpager = encoding.environ.get('PAGER', 'more')
866 872 pagercmd = self.config('pager', 'pager', envpager)
867 873 self.pageractive = True
868 874 # Preserve the formatted-ness of the UI. This is important
869 875 # because we mess with stdout, which might confuse
870 876 # auto-detection of things being formatted.
871 877 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
872 878 self.setconfig('ui', 'interactive', False, 'pager')
873 879 if util.safehasattr(signal, "SIGPIPE"):
874 880 signal.signal(signal.SIGPIPE, _catchterm)
875 881 self._runpager(pagercmd)
876 882
877 883 def _runpager(self, command):
878 884 """Actually start the pager and set up file descriptors.
879 885
880 886 This is separate in part so that extensions (like chg) can
881 887 override how a pager is invoked.
882 888 """
883 889 pager = subprocess.Popen(command, shell=True, bufsize=-1,
884 890 close_fds=util.closefds, stdin=subprocess.PIPE,
885 891 stdout=util.stdout, stderr=util.stderr)
886 892
887 893 # back up original file descriptors
888 894 stdoutfd = os.dup(util.stdout.fileno())
889 895 stderrfd = os.dup(util.stderr.fileno())
890 896
891 897 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
892 898 if self._isatty(util.stderr):
893 899 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
894 900
895 901 @atexit.register
896 902 def killpager():
897 903 if util.safehasattr(signal, "SIGINT"):
898 904 signal.signal(signal.SIGINT, signal.SIG_IGN)
899 905 # restore original fds, closing pager.stdin copies in the process
900 906 os.dup2(stdoutfd, util.stdout.fileno())
901 907 os.dup2(stderrfd, util.stderr.fileno())
902 908 pager.stdin.close()
903 909 pager.wait()
904 910
905 911 def interface(self, feature):
906 912 """what interface to use for interactive console features?
907 913
908 914 The interface is controlled by the value of `ui.interface` but also by
909 915 the value of feature-specific configuration. For example:
910 916
911 917 ui.interface.histedit = text
912 918 ui.interface.chunkselector = curses
913 919
914 920 Here the features are "histedit" and "chunkselector".
915 921
916 922 The configuration above means that the default interfaces for commands
917 923 is curses, the interface for histedit is text and the interface for
918 924 selecting chunk is crecord (the best curses interface available).
919 925
920 926 Consider the following example:
921 927 ui.interface = curses
922 928 ui.interface.histedit = text
923 929
924 930 Then histedit will use the text interface and chunkselector will use
925 931 the default curses interface (crecord at the moment).
926 932 """
927 933 alldefaults = frozenset(["text", "curses"])
928 934
929 935 featureinterfaces = {
930 936 "chunkselector": [
931 937 "text",
932 938 "curses",
933 939 ]
934 940 }
935 941
936 942 # Feature-specific interface
937 943 if feature not in featureinterfaces.keys():
938 944 # Programming error, not user error
939 945 raise ValueError("Unknown feature requested %s" % feature)
940 946
941 947 availableinterfaces = frozenset(featureinterfaces[feature])
942 948 if alldefaults > availableinterfaces:
943 949 # Programming error, not user error. We need a use case to
944 950 # define the right thing to do here.
945 951 raise ValueError(
946 952 "Feature %s does not handle all default interfaces" %
947 953 feature)
948 954
949 955 if self.plain():
950 956 return "text"
951 957
952 958 # Default interface for all the features
953 959 defaultinterface = "text"
954 960 i = self.config("ui", "interface", None)
955 961 if i in alldefaults:
956 962 defaultinterface = i
957 963
958 964 choseninterface = defaultinterface
959 965 f = self.config("ui", "interface.%s" % feature, None)
960 966 if f in availableinterfaces:
961 967 choseninterface = f
962 968
963 969 if i is not None and defaultinterface != i:
964 970 if f is not None:
965 971 self.warn(_("invalid value for ui.interface: %s\n") %
966 972 (i,))
967 973 else:
968 974 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
969 975 (i, choseninterface))
970 976 if f is not None and choseninterface != f:
971 977 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
972 978 (feature, f, choseninterface))
973 979
974 980 return choseninterface
975 981
976 982 def interactive(self):
977 983 '''is interactive input allowed?
978 984
979 985 An interactive session is a session where input can be reasonably read
980 986 from `sys.stdin'. If this function returns false, any attempt to read
981 987 from stdin should fail with an error, unless a sensible default has been
982 988 specified.
983 989
984 990 Interactiveness is triggered by the value of the `ui.interactive'
985 991 configuration variable or - if it is unset - when `sys.stdin' points
986 992 to a terminal device.
987 993
988 994 This function refers to input only; for output, see `ui.formatted()'.
989 995 '''
990 996 i = self.configbool("ui", "interactive", None)
991 997 if i is None:
992 998 # some environments replace stdin without implementing isatty
993 999 # usually those are non-interactive
994 1000 return self._isatty(self.fin)
995 1001
996 1002 return i
997 1003
998 1004 def termwidth(self):
999 1005 '''how wide is the terminal in columns?
1000 1006 '''
1001 1007 if 'COLUMNS' in encoding.environ:
1002 1008 try:
1003 1009 return int(encoding.environ['COLUMNS'])
1004 1010 except ValueError:
1005 1011 pass
1006 1012 return scmutil.termsize(self)[0]
1007 1013
1008 1014 def formatted(self):
1009 1015 '''should formatted output be used?
1010 1016
1011 1017 It is often desirable to format the output to suite the output medium.
1012 1018 Examples of this are truncating long lines or colorizing messages.
1013 1019 However, this is not often not desirable when piping output into other
1014 1020 utilities, e.g. `grep'.
1015 1021
1016 1022 Formatted output is triggered by the value of the `ui.formatted'
1017 1023 configuration variable or - if it is unset - when `sys.stdout' points
1018 1024 to a terminal device. Please note that `ui.formatted' should be
1019 1025 considered an implementation detail; it is not intended for use outside
1020 1026 Mercurial or its extensions.
1021 1027
1022 1028 This function refers to output only; for input, see `ui.interactive()'.
1023 1029 This function always returns false when in plain mode, see `ui.plain()'.
1024 1030 '''
1025 1031 if self.plain():
1026 1032 return False
1027 1033
1028 1034 i = self.configbool("ui", "formatted", None)
1029 1035 if i is None:
1030 1036 # some environments replace stdout without implementing isatty
1031 1037 # usually those are non-interactive
1032 1038 return self._isatty(self.fout)
1033 1039
1034 1040 return i
1035 1041
1036 1042 def _readline(self, prompt=''):
1037 1043 if self._isatty(self.fin):
1038 1044 try:
1039 1045 # magically add command line editing support, where
1040 1046 # available
1041 1047 import readline
1042 1048 # force demandimport to really load the module
1043 1049 readline.read_history_file
1044 1050 # windows sometimes raises something other than ImportError
1045 1051 except Exception:
1046 1052 pass
1047 1053
1048 1054 # call write() so output goes through subclassed implementation
1049 1055 # e.g. color extension on Windows
1050 1056 self.write(prompt, prompt=True)
1051 1057
1052 1058 # instead of trying to emulate raw_input, swap (self.fin,
1053 1059 # self.fout) with (sys.stdin, sys.stdout)
1054 1060 oldin = sys.stdin
1055 1061 oldout = sys.stdout
1056 1062 sys.stdin = self.fin
1057 1063 sys.stdout = self.fout
1058 1064 # prompt ' ' must exist; otherwise readline may delete entire line
1059 1065 # - http://bugs.python.org/issue12833
1060 1066 with self.timeblockedsection('stdio'):
1061 1067 line = raw_input(' ')
1062 1068 sys.stdin = oldin
1063 1069 sys.stdout = oldout
1064 1070
1065 1071 # When stdin is in binary mode on Windows, it can cause
1066 1072 # raw_input() to emit an extra trailing carriage return
1067 1073 if os.linesep == '\r\n' and line and line[-1] == '\r':
1068 1074 line = line[:-1]
1069 1075 return line
1070 1076
1071 1077 def prompt(self, msg, default="y"):
1072 1078 """Prompt user with msg, read response.
1073 1079 If ui is not interactive, the default is returned.
1074 1080 """
1075 1081 if not self.interactive():
1076 1082 self.write(msg, ' ', default or '', "\n")
1077 1083 return default
1078 1084 try:
1079 1085 r = self._readline(self.label(msg, 'ui.prompt'))
1080 1086 if not r:
1081 1087 r = default
1082 1088 if self.configbool('ui', 'promptecho'):
1083 1089 self.write(r, "\n")
1084 1090 return r
1085 1091 except EOFError:
1086 1092 raise error.ResponseExpected()
1087 1093
1088 1094 @staticmethod
1089 1095 def extractchoices(prompt):
1090 1096 """Extract prompt message and list of choices from specified prompt.
1091 1097
1092 1098 This returns tuple "(message, choices)", and "choices" is the
1093 1099 list of tuple "(response character, text without &)".
1094 1100
1095 1101 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1096 1102 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1097 1103 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1098 1104 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1099 1105 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1100 1106 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1101 1107 """
1102 1108
1103 1109 # Sadly, the prompt string may have been built with a filename
1104 1110 # containing "$$" so let's try to find the first valid-looking
1105 1111 # prompt to start parsing. Sadly, we also can't rely on
1106 1112 # choices containing spaces, ASCII, or basically anything
1107 1113 # except an ampersand followed by a character.
1108 1114 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1109 1115 msg = m.group(1)
1110 1116 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1111 1117 return (msg,
1112 1118 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1113 1119 for s in choices])
1114 1120
1115 1121 def promptchoice(self, prompt, default=0):
1116 1122 """Prompt user with a message, read response, and ensure it matches
1117 1123 one of the provided choices. The prompt is formatted as follows:
1118 1124
1119 1125 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1120 1126
1121 1127 The index of the choice is returned. Responses are case
1122 1128 insensitive. If ui is not interactive, the default is
1123 1129 returned.
1124 1130 """
1125 1131
1126 1132 msg, choices = self.extractchoices(prompt)
1127 1133 resps = [r for r, t in choices]
1128 1134 while True:
1129 1135 r = self.prompt(msg, resps[default])
1130 1136 if r.lower() in resps:
1131 1137 return resps.index(r.lower())
1132 1138 self.write(_("unrecognized response\n"))
1133 1139
1134 1140 def getpass(self, prompt=None, default=None):
1135 1141 if not self.interactive():
1136 1142 return default
1137 1143 try:
1138 1144 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1139 1145 # disable getpass() only if explicitly specified. it's still valid
1140 1146 # to interact with tty even if fin is not a tty.
1141 1147 with self.timeblockedsection('stdio'):
1142 1148 if self.configbool('ui', 'nontty'):
1143 1149 l = self.fin.readline()
1144 1150 if not l:
1145 1151 raise EOFError
1146 1152 return l.rstrip('\n')
1147 1153 else:
1148 1154 return getpass.getpass('')
1149 1155 except EOFError:
1150 1156 raise error.ResponseExpected()
1151 1157 def status(self, *msg, **opts):
1152 1158 '''write status message to output (if ui.quiet is False)
1153 1159
1154 1160 This adds an output label of "ui.status".
1155 1161 '''
1156 1162 if not self.quiet:
1157 1163 opts['label'] = opts.get('label', '') + ' ui.status'
1158 1164 self.write(*msg, **opts)
1159 1165 def warn(self, *msg, **opts):
1160 1166 '''write warning message to output (stderr)
1161 1167
1162 1168 This adds an output label of "ui.warning".
1163 1169 '''
1164 1170 opts['label'] = opts.get('label', '') + ' ui.warning'
1165 1171 self.write_err(*msg, **opts)
1166 1172 def note(self, *msg, **opts):
1167 1173 '''write note to output (if ui.verbose is True)
1168 1174
1169 1175 This adds an output label of "ui.note".
1170 1176 '''
1171 1177 if self.verbose:
1172 1178 opts['label'] = opts.get('label', '') + ' ui.note'
1173 1179 self.write(*msg, **opts)
1174 1180 def debug(self, *msg, **opts):
1175 1181 '''write debug message to output (if ui.debugflag is True)
1176 1182
1177 1183 This adds an output label of "ui.debug".
1178 1184 '''
1179 1185 if self.debugflag:
1180 1186 opts['label'] = opts.get('label', '') + ' ui.debug'
1181 1187 self.write(*msg, **opts)
1182 1188
1183 1189 def edit(self, text, user, extra=None, editform=None, pending=None,
1184 1190 repopath=None):
1185 1191 extra_defaults = {
1186 1192 'prefix': 'editor',
1187 1193 'suffix': '.txt',
1188 1194 }
1189 1195 if extra is not None:
1190 1196 extra_defaults.update(extra)
1191 1197 extra = extra_defaults
1192 1198
1193 1199 rdir = None
1194 1200 if self.configbool('experimental', 'editortmpinhg'):
1195 1201 rdir = repopath
1196 1202 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1197 1203 suffix=extra['suffix'], text=True,
1198 1204 dir=rdir)
1199 1205 try:
1200 1206 f = os.fdopen(fd, pycompat.sysstr("w"))
1201 1207 f.write(text)
1202 1208 f.close()
1203 1209
1204 1210 environ = {'HGUSER': user}
1205 1211 if 'transplant_source' in extra:
1206 1212 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1207 1213 for label in ('intermediate-source', 'source', 'rebase_source'):
1208 1214 if label in extra:
1209 1215 environ.update({'HGREVISION': extra[label]})
1210 1216 break
1211 1217 if editform:
1212 1218 environ.update({'HGEDITFORM': editform})
1213 1219 if pending:
1214 1220 environ.update({'HG_PENDING': pending})
1215 1221
1216 1222 editor = self.geteditor()
1217 1223
1218 1224 self.system("%s \"%s\"" % (editor, name),
1219 1225 environ=environ,
1220 1226 onerr=error.Abort, errprefix=_("edit failed"),
1221 1227 blockedtag='editor')
1222 1228
1223 1229 f = open(name)
1224 1230 t = f.read()
1225 1231 f.close()
1226 1232 finally:
1227 1233 os.unlink(name)
1228 1234
1229 1235 return t
1230 1236
1231 1237 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1232 1238 blockedtag=None):
1233 1239 '''execute shell command with appropriate output stream. command
1234 1240 output will be redirected if fout is not stdout.
1235 1241 '''
1236 1242 if blockedtag is None:
1237 1243 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1238 1244 out = self.fout
1239 1245 if any(s[1] for s in self._bufferstates):
1240 1246 out = self
1241 1247 with self.timeblockedsection(blockedtag):
1242 1248 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1243 1249 errprefix=errprefix, out=out)
1244 1250
1245 1251 def traceback(self, exc=None, force=False):
1246 1252 '''print exception traceback if traceback printing enabled or forced.
1247 1253 only to call in exception handler. returns true if traceback
1248 1254 printed.'''
1249 1255 if self.tracebackflag or force:
1250 1256 if exc is None:
1251 1257 exc = sys.exc_info()
1252 1258 cause = getattr(exc[1], 'cause', None)
1253 1259
1254 1260 if cause is not None:
1255 1261 causetb = traceback.format_tb(cause[2])
1256 1262 exctb = traceback.format_tb(exc[2])
1257 1263 exconly = traceback.format_exception_only(cause[0], cause[1])
1258 1264
1259 1265 # exclude frame where 'exc' was chained and rethrown from exctb
1260 1266 self.write_err('Traceback (most recent call last):\n',
1261 1267 ''.join(exctb[:-1]),
1262 1268 ''.join(causetb),
1263 1269 ''.join(exconly))
1264 1270 else:
1265 1271 output = traceback.format_exception(exc[0], exc[1], exc[2])
1266 1272 self.write_err(''.join(output))
1267 1273 return self.tracebackflag or force
1268 1274
1269 1275 def geteditor(self):
1270 1276 '''return editor to use'''
1271 1277 if pycompat.sysplatform == 'plan9':
1272 1278 # vi is the MIPS instruction simulator on Plan 9. We
1273 1279 # instead default to E to plumb commit messages to
1274 1280 # avoid confusion.
1275 1281 editor = 'E'
1276 1282 else:
1277 1283 editor = 'vi'
1278 1284 return (encoding.environ.get("HGEDITOR") or
1279 1285 self.config("ui", "editor") or
1280 1286 encoding.environ.get("VISUAL") or
1281 1287 encoding.environ.get("EDITOR", editor))
1282 1288
1283 1289 @util.propertycache
1284 1290 def _progbar(self):
1285 1291 """setup the progbar singleton to the ui object"""
1286 1292 if (self.quiet or self.debugflag
1287 1293 or self.configbool('progress', 'disable', False)
1288 1294 or not progress.shouldprint(self)):
1289 1295 return None
1290 1296 return getprogbar(self)
1291 1297
1292 1298 def _progclear(self):
1293 1299 """clear progress bar output if any. use it before any output"""
1294 1300 if '_progbar' not in vars(self): # nothing loaded yet
1295 1301 return
1296 1302 if self._progbar is not None and self._progbar.printed:
1297 1303 self._progbar.clear()
1298 1304
1299 1305 def progress(self, topic, pos, item="", unit="", total=None):
1300 1306 '''show a progress message
1301 1307
1302 1308 By default a textual progress bar will be displayed if an operation
1303 1309 takes too long. 'topic' is the current operation, 'item' is a
1304 1310 non-numeric marker of the current position (i.e. the currently
1305 1311 in-process file), 'pos' is the current numeric position (i.e.
1306 1312 revision, bytes, etc.), unit is a corresponding unit label,
1307 1313 and total is the highest expected pos.
1308 1314
1309 1315 Multiple nested topics may be active at a time.
1310 1316
1311 1317 All topics should be marked closed by setting pos to None at
1312 1318 termination.
1313 1319 '''
1314 1320 if self._progbar is not None:
1315 1321 self._progbar.progress(topic, pos, item=item, unit=unit,
1316 1322 total=total)
1317 1323 if pos is None or not self.configbool('progress', 'debug'):
1318 1324 return
1319 1325
1320 1326 if unit:
1321 1327 unit = ' ' + unit
1322 1328 if item:
1323 1329 item = ' ' + item
1324 1330
1325 1331 if total:
1326 1332 pct = 100.0 * pos / total
1327 1333 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1328 1334 % (topic, item, pos, total, unit, pct))
1329 1335 else:
1330 1336 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1331 1337
1332 1338 def log(self, service, *msg, **opts):
1333 1339 '''hook for logging facility extensions
1334 1340
1335 1341 service should be a readily-identifiable subsystem, which will
1336 1342 allow filtering.
1337 1343
1338 1344 *msg should be a newline-terminated format string to log, and
1339 1345 then any values to %-format into that format string.
1340 1346
1341 1347 **opts currently has no defined meanings.
1342 1348 '''
1343 1349
1344 1350 def label(self, msg, label):
1345 1351 '''style msg based on supplied label
1346 1352
1347 1353 Like ui.write(), this just returns msg unchanged, but extensions
1348 1354 and GUI tools can override it to allow styling output without
1349 1355 writing it.
1350 1356
1351 1357 ui.write(s, 'label') is equivalent to
1352 1358 ui.write(ui.label(s, 'label')).
1353 1359 '''
1354 1360 return msg
1355 1361
1356 1362 def develwarn(self, msg, stacklevel=1, config=None):
1357 1363 """issue a developer warning message
1358 1364
1359 1365 Use 'stacklevel' to report the offender some layers further up in the
1360 1366 stack.
1361 1367 """
1362 1368 if not self.configbool('devel', 'all-warnings'):
1363 1369 if config is not None and not self.configbool('devel', config):
1364 1370 return
1365 1371 msg = 'devel-warn: ' + msg
1366 1372 stacklevel += 1 # get in develwarn
1367 1373 if self.tracebackflag:
1368 1374 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1369 1375 self.log('develwarn', '%s at:\n%s' %
1370 1376 (msg, ''.join(util.getstackframes(stacklevel))))
1371 1377 else:
1372 1378 curframe = inspect.currentframe()
1373 1379 calframe = inspect.getouterframes(curframe, 2)
1374 1380 self.write_err('%s at: %s:%s (%s)\n'
1375 1381 % ((msg,) + calframe[stacklevel][1:4]))
1376 1382 self.log('develwarn', '%s at: %s:%s (%s)\n',
1377 1383 msg, *calframe[stacklevel][1:4])
1378 1384 curframe = calframe = None # avoid cycles
1379 1385
1380 1386 def deprecwarn(self, msg, version):
1381 1387 """issue a deprecation warning
1382 1388
1383 1389 - msg: message explaining what is deprecated and how to upgrade,
1384 1390 - version: last version where the API will be supported,
1385 1391 """
1386 1392 if not (self.configbool('devel', 'all-warnings')
1387 1393 or self.configbool('devel', 'deprec-warn')):
1388 1394 return
1389 1395 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1390 1396 " update your code.)") % version
1391 1397 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1392 1398
1393 1399 def exportableenviron(self):
1394 1400 """The environment variables that are safe to export, e.g. through
1395 1401 hgweb.
1396 1402 """
1397 1403 return self._exportableenviron
1398 1404
1399 1405 @contextlib.contextmanager
1400 1406 def configoverride(self, overrides, source=""):
1401 1407 """Context manager for temporary config overrides
1402 1408 `overrides` must be a dict of the following structure:
1403 1409 {(section, name) : value}"""
1404 1410 backups = {}
1405 1411 try:
1406 1412 for (section, name), value in overrides.items():
1407 1413 backups[(section, name)] = self.backupconfig(section, name)
1408 1414 self.setconfig(section, name, value, source)
1409 1415 yield
1410 1416 finally:
1411 1417 for __, backup in backups.items():
1412 1418 self.restoreconfig(backup)
1413 1419 # just restoring ui.quiet config to the previous value is not enough
1414 1420 # as it does not update ui.quiet class member
1415 1421 if ('ui', 'quiet') in overrides:
1416 1422 self.fixconfig(section='ui')
1417 1423
1418 1424 class paths(dict):
1419 1425 """Represents a collection of paths and their configs.
1420 1426
1421 1427 Data is initially derived from ui instances and the config files they have
1422 1428 loaded.
1423 1429 """
1424 1430 def __init__(self, ui):
1425 1431 dict.__init__(self)
1426 1432
1427 1433 for name, loc in ui.configitems('paths', ignoresub=True):
1428 1434 # No location is the same as not existing.
1429 1435 if not loc:
1430 1436 continue
1431 1437 loc, sub = ui.configsuboptions('paths', name)
1432 1438 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1433 1439
1434 1440 def getpath(self, name, default=None):
1435 1441 """Return a ``path`` from a string, falling back to default.
1436 1442
1437 1443 ``name`` can be a named path or locations. Locations are filesystem
1438 1444 paths or URIs.
1439 1445
1440 1446 Returns None if ``name`` is not a registered path, a URI, or a local
1441 1447 path to a repo.
1442 1448 """
1443 1449 # Only fall back to default if no path was requested.
1444 1450 if name is None:
1445 1451 if not default:
1446 1452 default = ()
1447 1453 elif not isinstance(default, (tuple, list)):
1448 1454 default = (default,)
1449 1455 for k in default:
1450 1456 try:
1451 1457 return self[k]
1452 1458 except KeyError:
1453 1459 continue
1454 1460 return None
1455 1461
1456 1462 # Most likely empty string.
1457 1463 # This may need to raise in the future.
1458 1464 if not name:
1459 1465 return None
1460 1466
1461 1467 try:
1462 1468 return self[name]
1463 1469 except KeyError:
1464 1470 # Try to resolve as a local path or URI.
1465 1471 try:
1466 1472 # We don't pass sub-options in, so no need to pass ui instance.
1467 1473 return path(None, None, rawloc=name)
1468 1474 except ValueError:
1469 1475 raise error.RepoError(_('repository %s does not exist') %
1470 1476 name)
1471 1477
1472 1478 _pathsuboptions = {}
1473 1479
1474 1480 def pathsuboption(option, attr):
1475 1481 """Decorator used to declare a path sub-option.
1476 1482
1477 1483 Arguments are the sub-option name and the attribute it should set on
1478 1484 ``path`` instances.
1479 1485
1480 1486 The decorated function will receive as arguments a ``ui`` instance,
1481 1487 ``path`` instance, and the string value of this option from the config.
1482 1488 The function should return the value that will be set on the ``path``
1483 1489 instance.
1484 1490
1485 1491 This decorator can be used to perform additional verification of
1486 1492 sub-options and to change the type of sub-options.
1487 1493 """
1488 1494 def register(func):
1489 1495 _pathsuboptions[option] = (attr, func)
1490 1496 return func
1491 1497 return register
1492 1498
1493 1499 @pathsuboption('pushurl', 'pushloc')
1494 1500 def pushurlpathoption(ui, path, value):
1495 1501 u = util.url(value)
1496 1502 # Actually require a URL.
1497 1503 if not u.scheme:
1498 1504 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1499 1505 return None
1500 1506
1501 1507 # Don't support the #foo syntax in the push URL to declare branch to
1502 1508 # push.
1503 1509 if u.fragment:
1504 1510 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1505 1511 'ignoring)\n') % path.name)
1506 1512 u.fragment = None
1507 1513
1508 1514 return str(u)
1509 1515
1510 1516 @pathsuboption('pushrev', 'pushrev')
1511 1517 def pushrevpathoption(ui, path, value):
1512 1518 return value
1513 1519
1514 1520 class path(object):
1515 1521 """Represents an individual path and its configuration."""
1516 1522
1517 1523 def __init__(self, ui, name, rawloc=None, suboptions=None):
1518 1524 """Construct a path from its config options.
1519 1525
1520 1526 ``ui`` is the ``ui`` instance the path is coming from.
1521 1527 ``name`` is the symbolic name of the path.
1522 1528 ``rawloc`` is the raw location, as defined in the config.
1523 1529 ``pushloc`` is the raw locations pushes should be made to.
1524 1530
1525 1531 If ``name`` is not defined, we require that the location be a) a local
1526 1532 filesystem path with a .hg directory or b) a URL. If not,
1527 1533 ``ValueError`` is raised.
1528 1534 """
1529 1535 if not rawloc:
1530 1536 raise ValueError('rawloc must be defined')
1531 1537
1532 1538 # Locations may define branches via syntax <base>#<branch>.
1533 1539 u = util.url(rawloc)
1534 1540 branch = None
1535 1541 if u.fragment:
1536 1542 branch = u.fragment
1537 1543 u.fragment = None
1538 1544
1539 1545 self.url = u
1540 1546 self.branch = branch
1541 1547
1542 1548 self.name = name
1543 1549 self.rawloc = rawloc
1544 1550 self.loc = str(u)
1545 1551
1546 1552 # When given a raw location but not a symbolic name, validate the
1547 1553 # location is valid.
1548 1554 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1549 1555 raise ValueError('location is not a URL or path to a local '
1550 1556 'repo: %s' % rawloc)
1551 1557
1552 1558 suboptions = suboptions or {}
1553 1559
1554 1560 # Now process the sub-options. If a sub-option is registered, its
1555 1561 # attribute will always be present. The value will be None if there
1556 1562 # was no valid sub-option.
1557 1563 for suboption, (attr, func) in _pathsuboptions.iteritems():
1558 1564 if suboption not in suboptions:
1559 1565 setattr(self, attr, None)
1560 1566 continue
1561 1567
1562 1568 value = func(ui, self, suboptions[suboption])
1563 1569 setattr(self, attr, value)
1564 1570
1565 1571 def _isvalidlocalpath(self, path):
1566 1572 """Returns True if the given path is a potentially valid repository.
1567 1573 This is its own function so that extensions can change the definition of
1568 1574 'valid' in this case (like when pulling from a git repo into a hg
1569 1575 one)."""
1570 1576 return os.path.isdir(os.path.join(path, '.hg'))
1571 1577
1572 1578 @property
1573 1579 def suboptions(self):
1574 1580 """Return sub-options and their values for this path.
1575 1581
1576 1582 This is intended to be used for presentation purposes.
1577 1583 """
1578 1584 d = {}
1579 1585 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1580 1586 value = getattr(self, attr)
1581 1587 if value is not None:
1582 1588 d[subopt] = value
1583 1589 return d
1584 1590
1585 1591 # we instantiate one globally shared progress bar to avoid
1586 1592 # competing progress bars when multiple UI objects get created
1587 1593 _progresssingleton = None
1588 1594
1589 1595 def getprogbar(ui):
1590 1596 global _progresssingleton
1591 1597 if _progresssingleton is None:
1592 1598 # passing 'ui' object to the singleton is fishy,
1593 1599 # this is how the extension used to work but feel free to rework it.
1594 1600 _progresssingleton = progress.progbar(ui)
1595 1601 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now