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