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