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