##// END OF EJS Templates
config: extract the core config logic into a private method...
marmoute -
r33058:1aa05203 default
parent child Browse files
Show More
@@ -1,1760 +1,1766 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 """return the plain string version of a config"""
443 value = self._config(section, name, default=default,
444 untrusted=untrusted)
445 if value is _unset:
446 return None
447 return value
448
449 def _config(self, section, name, default=_unset, untrusted=False):
450 value = default
442 451 if isinstance(name, list):
443 452 alternates = name
444 # let us ignore the config items in the alternates case for now
445 if default is _unset:
446 default = None
447 453 else:
448 454 item = self._knownconfig.get(section, {}).get(name)
449 455 if default is _unset:
450 default = None
451 if item is not None:
452 default = item.default
456 if item is None:
457 value = default
458 else:
459 value = item.default
453 460 elif item is not None:
454 461 msg = ("specifying a default value for a registered "
455 462 "config item: '%s.%s' '%s'")
456 463 msg %= (section, name, default)
457 self.develwarn(msg, 1, 'warn-config-default')
464 self.develwarn(msg, 2, 'warn-config-default')
458 465
459 466 alternates = [name]
460 467
461 468 for n in alternates:
462 value = self._data(untrusted).get(section, n, None)
463 if value is not None:
469 candidate = self._data(untrusted).get(section, n, None)
470 if candidate is not None:
471 value = candidate
464 472 name = n
465 473 break
466 else:
467 value = default
468 474
469 475 if self.debugflag and not untrusted and self._reportuntrusted:
470 476 for n in alternates:
471 477 uvalue = self._ucfg.get(section, n)
472 478 if uvalue is not None and uvalue != value:
473 479 self.debug("ignoring untrusted configuration option "
474 480 "%s.%s = %s\n" % (section, n, uvalue))
475 481 return value
476 482
477 483 def configsuboptions(self, section, name, default=_unset, untrusted=False):
478 484 """Get a config option and all sub-options.
479 485
480 486 Some config options have sub-options that are declared with the
481 487 format "key:opt = value". This method is used to return the main
482 488 option and all its declared sub-options.
483 489
484 490 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
485 491 is a dict of defined sub-options where keys and values are strings.
486 492 """
487 493 main = self.config(section, name, default, untrusted=untrusted)
488 494 data = self._data(untrusted)
489 495 sub = {}
490 496 prefix = '%s:' % name
491 497 for k, v in data.items(section):
492 498 if k.startswith(prefix):
493 499 sub[k[len(prefix):]] = v
494 500
495 501 if self.debugflag and not untrusted and self._reportuntrusted:
496 502 for k, v in sub.items():
497 503 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
498 504 if uvalue is not None and uvalue != v:
499 505 self.debug('ignoring untrusted configuration option '
500 506 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
501 507
502 508 return main, sub
503 509
504 510 def configpath(self, section, name, default=_unset, untrusted=False):
505 511 'get a path config item, expanded relative to repo root or config file'
506 512 v = self.config(section, name, default, untrusted)
507 513 if v is None:
508 514 return None
509 515 if not os.path.isabs(v) or "://" not in v:
510 516 src = self.configsource(section, name, untrusted)
511 517 if ':' in src:
512 518 base = os.path.dirname(src.rsplit(':')[0])
513 519 v = os.path.join(base, os.path.expanduser(v))
514 520 return v
515 521
516 522 def configbool(self, section, name, default=_unset, untrusted=False):
517 523 """parse a configuration element as a boolean
518 524
519 525 >>> u = ui(); s = 'foo'
520 526 >>> u.setconfig(s, 'true', 'yes')
521 527 >>> u.configbool(s, 'true')
522 528 True
523 529 >>> u.setconfig(s, 'false', 'no')
524 530 >>> u.configbool(s, 'false')
525 531 False
526 532 >>> u.configbool(s, 'unknown')
527 533 False
528 534 >>> u.configbool(s, 'unknown', True)
529 535 True
530 536 >>> u.setconfig(s, 'invalid', 'somevalue')
531 537 >>> u.configbool(s, 'invalid')
532 538 Traceback (most recent call last):
533 539 ...
534 540 ConfigError: foo.invalid is not a boolean ('somevalue')
535 541 """
536 542
537 543 v = self.config(section, name, default, untrusted=untrusted)
538 544 if v is None:
539 545 if default is _unset:
540 546 return False
541 547 return default
542 548 if isinstance(v, bool):
543 549 return v
544 550 b = util.parsebool(v)
545 551 if b is None:
546 552 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
547 553 % (section, name, v))
548 554 return b
549 555
550 556 def configwith(self, convert, section, name, default=_unset,
551 557 desc=None, untrusted=False):
552 558 """parse a configuration element with a conversion function
553 559
554 560 >>> u = ui(); s = 'foo'
555 561 >>> u.setconfig(s, 'float1', '42')
556 562 >>> u.configwith(float, s, 'float1')
557 563 42.0
558 564 >>> u.setconfig(s, 'float2', '-4.25')
559 565 >>> u.configwith(float, s, 'float2')
560 566 -4.25
561 567 >>> u.configwith(float, s, 'unknown', 7)
562 568 7.0
563 569 >>> u.setconfig(s, 'invalid', 'somevalue')
564 570 >>> u.configwith(float, s, 'invalid')
565 571 Traceback (most recent call last):
566 572 ...
567 573 ConfigError: foo.invalid is not a valid float ('somevalue')
568 574 >>> u.configwith(float, s, 'invalid', desc='womble')
569 575 Traceback (most recent call last):
570 576 ...
571 577 ConfigError: foo.invalid is not a valid womble ('somevalue')
572 578 """
573 579
574 580 v = self.config(section, name, default, untrusted)
575 581 if v is None:
576 582 return v # do not attempt to convert None
577 583 try:
578 584 return convert(v)
579 585 except (ValueError, error.ParseError):
580 586 if desc is None:
581 587 desc = convert.__name__
582 588 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
583 589 % (section, name, desc, v))
584 590
585 591 def configint(self, section, name, default=_unset, untrusted=False):
586 592 """parse a configuration element as an integer
587 593
588 594 >>> u = ui(); s = 'foo'
589 595 >>> u.setconfig(s, 'int1', '42')
590 596 >>> u.configint(s, 'int1')
591 597 42
592 598 >>> u.setconfig(s, 'int2', '-42')
593 599 >>> u.configint(s, 'int2')
594 600 -42
595 601 >>> u.configint(s, 'unknown', 7)
596 602 7
597 603 >>> u.setconfig(s, 'invalid', 'somevalue')
598 604 >>> u.configint(s, 'invalid')
599 605 Traceback (most recent call last):
600 606 ...
601 607 ConfigError: foo.invalid is not a valid integer ('somevalue')
602 608 """
603 609
604 610 return self.configwith(int, section, name, default, 'integer',
605 611 untrusted)
606 612
607 613 def configbytes(self, section, name, default=_unset, untrusted=False):
608 614 """parse a configuration element as a quantity in bytes
609 615
610 616 Units can be specified as b (bytes), k or kb (kilobytes), m or
611 617 mb (megabytes), g or gb (gigabytes).
612 618
613 619 >>> u = ui(); s = 'foo'
614 620 >>> u.setconfig(s, 'val1', '42')
615 621 >>> u.configbytes(s, 'val1')
616 622 42
617 623 >>> u.setconfig(s, 'val2', '42.5 kb')
618 624 >>> u.configbytes(s, 'val2')
619 625 43520
620 626 >>> u.configbytes(s, 'unknown', '7 MB')
621 627 7340032
622 628 >>> u.setconfig(s, 'invalid', 'somevalue')
623 629 >>> u.configbytes(s, 'invalid')
624 630 Traceback (most recent call last):
625 631 ...
626 632 ConfigError: foo.invalid is not a byte quantity ('somevalue')
627 633 """
628 634
629 635 value = self.config(section, name, default, untrusted)
630 636 if value is None:
631 637 if default is _unset:
632 638 default = 0
633 639 value = default
634 640 if not isinstance(value, str):
635 641 return value
636 642 try:
637 643 return util.sizetoint(value)
638 644 except error.ParseError:
639 645 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
640 646 % (section, name, value))
641 647
642 648 def configlist(self, section, name, default=_unset, untrusted=False):
643 649 """parse a configuration element as a list of comma/space separated
644 650 strings
645 651
646 652 >>> u = ui(); s = 'foo'
647 653 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
648 654 >>> u.configlist(s, 'list1')
649 655 ['this', 'is', 'a small', 'test']
650 656 """
651 657 # default is not always a list
652 658 v = self.configwith(config.parselist, section, name, default,
653 659 'list', untrusted)
654 660 if isinstance(v, bytes):
655 661 return config.parselist(v)
656 662 elif v is None:
657 663 return []
658 664 return v
659 665
660 666 def configdate(self, section, name, default=_unset, untrusted=False):
661 667 """parse a configuration element as a tuple of ints
662 668
663 669 >>> u = ui(); s = 'foo'
664 670 >>> u.setconfig(s, 'date', '0 0')
665 671 >>> u.configdate(s, 'date')
666 672 (0, 0)
667 673 """
668 674 if self.config(section, name, default, untrusted):
669 675 return self.configwith(util.parsedate, section, name, default,
670 676 'date', untrusted)
671 677 if default is _unset:
672 678 return None
673 679 return default
674 680
675 681 def hasconfig(self, section, name, untrusted=False):
676 682 return self._data(untrusted).hasitem(section, name)
677 683
678 684 def has_section(self, section, untrusted=False):
679 685 '''tell whether section exists in config.'''
680 686 return section in self._data(untrusted)
681 687
682 688 def configitems(self, section, untrusted=False, ignoresub=False):
683 689 items = self._data(untrusted).items(section)
684 690 if ignoresub:
685 691 newitems = {}
686 692 for k, v in items:
687 693 if ':' not in k:
688 694 newitems[k] = v
689 695 items = newitems.items()
690 696 if self.debugflag and not untrusted and self._reportuntrusted:
691 697 for k, v in self._ucfg.items(section):
692 698 if self._tcfg.get(section, k) != v:
693 699 self.debug("ignoring untrusted configuration option "
694 700 "%s.%s = %s\n" % (section, k, v))
695 701 return items
696 702
697 703 def walkconfig(self, untrusted=False):
698 704 cfg = self._data(untrusted)
699 705 for section in cfg.sections():
700 706 for name, value in self.configitems(section, untrusted):
701 707 yield section, name, value
702 708
703 709 def plain(self, feature=None):
704 710 '''is plain mode active?
705 711
706 712 Plain mode means that all configuration variables which affect
707 713 the behavior and output of Mercurial should be
708 714 ignored. Additionally, the output should be stable,
709 715 reproducible and suitable for use in scripts or applications.
710 716
711 717 The only way to trigger plain mode is by setting either the
712 718 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
713 719
714 720 The return value can either be
715 721 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
716 722 - True otherwise
717 723 '''
718 724 if ('HGPLAIN' not in encoding.environ and
719 725 'HGPLAINEXCEPT' not in encoding.environ):
720 726 return False
721 727 exceptions = encoding.environ.get('HGPLAINEXCEPT',
722 728 '').strip().split(',')
723 729 if feature and exceptions:
724 730 return feature not in exceptions
725 731 return True
726 732
727 733 def username(self):
728 734 """Return default username to be used in commits.
729 735
730 736 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
731 737 and stop searching if one of these is set.
732 738 If not found and ui.askusername is True, ask the user, else use
733 739 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
734 740 """
735 741 user = encoding.environ.get("HGUSER")
736 742 if user is None:
737 743 user = self.config("ui", ["username", "user"])
738 744 if user is not None:
739 745 user = os.path.expandvars(user)
740 746 if user is None:
741 747 user = encoding.environ.get("EMAIL")
742 748 if user is None and self.configbool("ui", "askusername"):
743 749 user = self.prompt(_("enter a commit username:"), default=None)
744 750 if user is None and not self.interactive():
745 751 try:
746 752 user = '%s@%s' % (util.getuser(), socket.getfqdn())
747 753 self.warn(_("no username found, using '%s' instead\n") % user)
748 754 except KeyError:
749 755 pass
750 756 if not user:
751 757 raise error.Abort(_('no username supplied'),
752 758 hint=_("use 'hg config --edit' "
753 759 'to set your username'))
754 760 if "\n" in user:
755 761 raise error.Abort(_("username %s contains a newline\n")
756 762 % repr(user))
757 763 return user
758 764
759 765 def shortuser(self, user):
760 766 """Return a short representation of a user name or email address."""
761 767 if not self.verbose:
762 768 user = util.shortuser(user)
763 769 return user
764 770
765 771 def expandpath(self, loc, default=None):
766 772 """Return repository location relative to cwd or from [paths]"""
767 773 try:
768 774 p = self.paths.getpath(loc)
769 775 if p:
770 776 return p.rawloc
771 777 except error.RepoError:
772 778 pass
773 779
774 780 if default:
775 781 try:
776 782 p = self.paths.getpath(default)
777 783 if p:
778 784 return p.rawloc
779 785 except error.RepoError:
780 786 pass
781 787
782 788 return loc
783 789
784 790 @util.propertycache
785 791 def paths(self):
786 792 return paths(self)
787 793
788 794 def pushbuffer(self, error=False, subproc=False, labeled=False):
789 795 """install a buffer to capture standard output of the ui object
790 796
791 797 If error is True, the error output will be captured too.
792 798
793 799 If subproc is True, output from subprocesses (typically hooks) will be
794 800 captured too.
795 801
796 802 If labeled is True, any labels associated with buffered
797 803 output will be handled. By default, this has no effect
798 804 on the output returned, but extensions and GUI tools may
799 805 handle this argument and returned styled output. If output
800 806 is being buffered so it can be captured and parsed or
801 807 processed, labeled should not be set to True.
802 808 """
803 809 self._buffers.append([])
804 810 self._bufferstates.append((error, subproc, labeled))
805 811 self._bufferapplylabels = labeled
806 812
807 813 def popbuffer(self):
808 814 '''pop the last buffer and return the buffered output'''
809 815 self._bufferstates.pop()
810 816 if self._bufferstates:
811 817 self._bufferapplylabels = self._bufferstates[-1][2]
812 818 else:
813 819 self._bufferapplylabels = None
814 820
815 821 return "".join(self._buffers.pop())
816 822
817 823 def write(self, *args, **opts):
818 824 '''write args to output
819 825
820 826 By default, this method simply writes to the buffer or stdout.
821 827 Color mode can be set on the UI class to have the output decorated
822 828 with color modifier before being written to stdout.
823 829
824 830 The color used is controlled by an optional keyword argument, "label".
825 831 This should be a string containing label names separated by space.
826 832 Label names take the form of "topic.type". For example, ui.debug()
827 833 issues a label of "ui.debug".
828 834
829 835 When labeling output for a specific command, a label of
830 836 "cmdname.type" is recommended. For example, status issues
831 837 a label of "status.modified" for modified files.
832 838 '''
833 839 if self._buffers and not opts.get('prompt', False):
834 840 if self._bufferapplylabels:
835 841 label = opts.get('label', '')
836 842 self._buffers[-1].extend(self.label(a, label) for a in args)
837 843 else:
838 844 self._buffers[-1].extend(args)
839 845 elif self._colormode == 'win32':
840 846 # windows color printing is its own can of crab, defer to
841 847 # the color module and that is it.
842 848 color.win32print(self, self._write, *args, **opts)
843 849 else:
844 850 msgs = args
845 851 if self._colormode is not None:
846 852 label = opts.get('label', '')
847 853 msgs = [self.label(a, label) for a in args]
848 854 self._write(*msgs, **opts)
849 855
850 856 def _write(self, *msgs, **opts):
851 857 self._progclear()
852 858 # opencode timeblockedsection because this is a critical path
853 859 starttime = util.timer()
854 860 try:
855 861 for a in msgs:
856 862 self.fout.write(a)
857 863 except IOError as err:
858 864 raise error.StdioError(err)
859 865 finally:
860 866 self._blockedtimes['stdio_blocked'] += \
861 867 (util.timer() - starttime) * 1000
862 868
863 869 def write_err(self, *args, **opts):
864 870 self._progclear()
865 871 if self._bufferstates and self._bufferstates[-1][0]:
866 872 self.write(*args, **opts)
867 873 elif self._colormode == 'win32':
868 874 # windows color printing is its own can of crab, defer to
869 875 # the color module and that is it.
870 876 color.win32print(self, self._write_err, *args, **opts)
871 877 else:
872 878 msgs = args
873 879 if self._colormode is not None:
874 880 label = opts.get('label', '')
875 881 msgs = [self.label(a, label) for a in args]
876 882 self._write_err(*msgs, **opts)
877 883
878 884 def _write_err(self, *msgs, **opts):
879 885 try:
880 886 with self.timeblockedsection('stdio'):
881 887 if not getattr(self.fout, 'closed', False):
882 888 self.fout.flush()
883 889 for a in msgs:
884 890 self.ferr.write(a)
885 891 # stderr may be buffered under win32 when redirected to files,
886 892 # including stdout.
887 893 if not getattr(self.ferr, 'closed', False):
888 894 self.ferr.flush()
889 895 except IOError as inst:
890 896 raise error.StdioError(inst)
891 897
892 898 def flush(self):
893 899 # opencode timeblockedsection because this is a critical path
894 900 starttime = util.timer()
895 901 try:
896 902 try:
897 903 self.fout.flush()
898 904 except IOError as err:
899 905 raise error.StdioError(err)
900 906 finally:
901 907 try:
902 908 self.ferr.flush()
903 909 except IOError as err:
904 910 raise error.StdioError(err)
905 911 finally:
906 912 self._blockedtimes['stdio_blocked'] += \
907 913 (util.timer() - starttime) * 1000
908 914
909 915 def _isatty(self, fh):
910 916 if self.configbool('ui', 'nontty', False):
911 917 return False
912 918 return util.isatty(fh)
913 919
914 920 def disablepager(self):
915 921 self._disablepager = True
916 922
917 923 def pager(self, command):
918 924 """Start a pager for subsequent command output.
919 925
920 926 Commands which produce a long stream of output should call
921 927 this function to activate the user's preferred pagination
922 928 mechanism (which may be no pager). Calling this function
923 929 precludes any future use of interactive functionality, such as
924 930 prompting the user or activating curses.
925 931
926 932 Args:
927 933 command: The full, non-aliased name of the command. That is, "log"
928 934 not "history, "summary" not "summ", etc.
929 935 """
930 936 if (self._disablepager
931 937 or self.pageractive
932 938 or command in self.configlist('pager', 'ignore')
933 939 or not self.configbool('ui', 'paginate', True)
934 940 or not self.configbool('pager', 'attend-' + command, True)
935 941 # TODO: if we want to allow HGPLAINEXCEPT=pager,
936 942 # formatted() will need some adjustment.
937 943 or not self.formatted()
938 944 or self.plain()
939 945 # TODO: expose debugger-enabled on the UI object
940 946 or '--debugger' in pycompat.sysargv):
941 947 # We only want to paginate if the ui appears to be
942 948 # interactive, the user didn't say HGPLAIN or
943 949 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
944 950 return
945 951
946 952 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
947 953 if not pagercmd:
948 954 return
949 955
950 956 pagerenv = {}
951 957 for name, value in rcutil.defaultpagerenv().items():
952 958 if name not in encoding.environ:
953 959 pagerenv[name] = value
954 960
955 961 self.debug('starting pager for command %r\n' % command)
956 962 self.flush()
957 963
958 964 wasformatted = self.formatted()
959 965 if util.safehasattr(signal, "SIGPIPE"):
960 966 signal.signal(signal.SIGPIPE, _catchterm)
961 967 if self._runpager(pagercmd, pagerenv):
962 968 self.pageractive = True
963 969 # Preserve the formatted-ness of the UI. This is important
964 970 # because we mess with stdout, which might confuse
965 971 # auto-detection of things being formatted.
966 972 self.setconfig('ui', 'formatted', wasformatted, 'pager')
967 973 self.setconfig('ui', 'interactive', False, 'pager')
968 974
969 975 # If pagermode differs from color.mode, reconfigure color now that
970 976 # pageractive is set.
971 977 cm = self._colormode
972 978 if cm != self.config('color', 'pagermode', cm):
973 979 color.setup(self)
974 980 else:
975 981 # If the pager can't be spawned in dispatch when --pager=on is
976 982 # given, don't try again when the command runs, to avoid a duplicate
977 983 # warning about a missing pager command.
978 984 self.disablepager()
979 985
980 986 def _runpager(self, command, env=None):
981 987 """Actually start the pager and set up file descriptors.
982 988
983 989 This is separate in part so that extensions (like chg) can
984 990 override how a pager is invoked.
985 991 """
986 992 if command == 'cat':
987 993 # Save ourselves some work.
988 994 return False
989 995 # If the command doesn't contain any of these characters, we
990 996 # assume it's a binary and exec it directly. This means for
991 997 # simple pager command configurations, we can degrade
992 998 # gracefully and tell the user about their broken pager.
993 999 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
994 1000
995 1001 if pycompat.osname == 'nt' and not shell:
996 1002 # Window's built-in `more` cannot be invoked with shell=False, but
997 1003 # its `more.com` can. Hide this implementation detail from the
998 1004 # user so we can also get sane bad PAGER behavior. MSYS has
999 1005 # `more.exe`, so do a cmd.exe style resolution of the executable to
1000 1006 # determine which one to use.
1001 1007 fullcmd = util.findexe(command)
1002 1008 if not fullcmd:
1003 1009 self.warn(_("missing pager command '%s', skipping pager\n")
1004 1010 % command)
1005 1011 return False
1006 1012
1007 1013 command = fullcmd
1008 1014
1009 1015 try:
1010 1016 pager = subprocess.Popen(
1011 1017 command, shell=shell, bufsize=-1,
1012 1018 close_fds=util.closefds, stdin=subprocess.PIPE,
1013 1019 stdout=util.stdout, stderr=util.stderr,
1014 1020 env=util.shellenviron(env))
1015 1021 except OSError as e:
1016 1022 if e.errno == errno.ENOENT and not shell:
1017 1023 self.warn(_("missing pager command '%s', skipping pager\n")
1018 1024 % command)
1019 1025 return False
1020 1026 raise
1021 1027
1022 1028 # back up original file descriptors
1023 1029 stdoutfd = os.dup(util.stdout.fileno())
1024 1030 stderrfd = os.dup(util.stderr.fileno())
1025 1031
1026 1032 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1027 1033 if self._isatty(util.stderr):
1028 1034 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1029 1035
1030 1036 @self.atexit
1031 1037 def killpager():
1032 1038 if util.safehasattr(signal, "SIGINT"):
1033 1039 signal.signal(signal.SIGINT, signal.SIG_IGN)
1034 1040 # restore original fds, closing pager.stdin copies in the process
1035 1041 os.dup2(stdoutfd, util.stdout.fileno())
1036 1042 os.dup2(stderrfd, util.stderr.fileno())
1037 1043 pager.stdin.close()
1038 1044 pager.wait()
1039 1045
1040 1046 return True
1041 1047
1042 1048 def atexit(self, func, *args, **kwargs):
1043 1049 '''register a function to run after dispatching a request
1044 1050
1045 1051 Handlers do not stay registered across request boundaries.'''
1046 1052 self._exithandlers.append((func, args, kwargs))
1047 1053 return func
1048 1054
1049 1055 def interface(self, feature):
1050 1056 """what interface to use for interactive console features?
1051 1057
1052 1058 The interface is controlled by the value of `ui.interface` but also by
1053 1059 the value of feature-specific configuration. For example:
1054 1060
1055 1061 ui.interface.histedit = text
1056 1062 ui.interface.chunkselector = curses
1057 1063
1058 1064 Here the features are "histedit" and "chunkselector".
1059 1065
1060 1066 The configuration above means that the default interfaces for commands
1061 1067 is curses, the interface for histedit is text and the interface for
1062 1068 selecting chunk is crecord (the best curses interface available).
1063 1069
1064 1070 Consider the following example:
1065 1071 ui.interface = curses
1066 1072 ui.interface.histedit = text
1067 1073
1068 1074 Then histedit will use the text interface and chunkselector will use
1069 1075 the default curses interface (crecord at the moment).
1070 1076 """
1071 1077 alldefaults = frozenset(["text", "curses"])
1072 1078
1073 1079 featureinterfaces = {
1074 1080 "chunkselector": [
1075 1081 "text",
1076 1082 "curses",
1077 1083 ]
1078 1084 }
1079 1085
1080 1086 # Feature-specific interface
1081 1087 if feature not in featureinterfaces.keys():
1082 1088 # Programming error, not user error
1083 1089 raise ValueError("Unknown feature requested %s" % feature)
1084 1090
1085 1091 availableinterfaces = frozenset(featureinterfaces[feature])
1086 1092 if alldefaults > availableinterfaces:
1087 1093 # Programming error, not user error. We need a use case to
1088 1094 # define the right thing to do here.
1089 1095 raise ValueError(
1090 1096 "Feature %s does not handle all default interfaces" %
1091 1097 feature)
1092 1098
1093 1099 if self.plain():
1094 1100 return "text"
1095 1101
1096 1102 # Default interface for all the features
1097 1103 defaultinterface = "text"
1098 1104 i = self.config("ui", "interface", None)
1099 1105 if i in alldefaults:
1100 1106 defaultinterface = i
1101 1107
1102 1108 choseninterface = defaultinterface
1103 1109 f = self.config("ui", "interface.%s" % feature, None)
1104 1110 if f in availableinterfaces:
1105 1111 choseninterface = f
1106 1112
1107 1113 if i is not None and defaultinterface != i:
1108 1114 if f is not None:
1109 1115 self.warn(_("invalid value for ui.interface: %s\n") %
1110 1116 (i,))
1111 1117 else:
1112 1118 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1113 1119 (i, choseninterface))
1114 1120 if f is not None and choseninterface != f:
1115 1121 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1116 1122 (feature, f, choseninterface))
1117 1123
1118 1124 return choseninterface
1119 1125
1120 1126 def interactive(self):
1121 1127 '''is interactive input allowed?
1122 1128
1123 1129 An interactive session is a session where input can be reasonably read
1124 1130 from `sys.stdin'. If this function returns false, any attempt to read
1125 1131 from stdin should fail with an error, unless a sensible default has been
1126 1132 specified.
1127 1133
1128 1134 Interactiveness is triggered by the value of the `ui.interactive'
1129 1135 configuration variable or - if it is unset - when `sys.stdin' points
1130 1136 to a terminal device.
1131 1137
1132 1138 This function refers to input only; for output, see `ui.formatted()'.
1133 1139 '''
1134 1140 i = self.configbool("ui", "interactive", None)
1135 1141 if i is None:
1136 1142 # some environments replace stdin without implementing isatty
1137 1143 # usually those are non-interactive
1138 1144 return self._isatty(self.fin)
1139 1145
1140 1146 return i
1141 1147
1142 1148 def termwidth(self):
1143 1149 '''how wide is the terminal in columns?
1144 1150 '''
1145 1151 if 'COLUMNS' in encoding.environ:
1146 1152 try:
1147 1153 return int(encoding.environ['COLUMNS'])
1148 1154 except ValueError:
1149 1155 pass
1150 1156 return scmutil.termsize(self)[0]
1151 1157
1152 1158 def formatted(self):
1153 1159 '''should formatted output be used?
1154 1160
1155 1161 It is often desirable to format the output to suite the output medium.
1156 1162 Examples of this are truncating long lines or colorizing messages.
1157 1163 However, this is not often not desirable when piping output into other
1158 1164 utilities, e.g. `grep'.
1159 1165
1160 1166 Formatted output is triggered by the value of the `ui.formatted'
1161 1167 configuration variable or - if it is unset - when `sys.stdout' points
1162 1168 to a terminal device. Please note that `ui.formatted' should be
1163 1169 considered an implementation detail; it is not intended for use outside
1164 1170 Mercurial or its extensions.
1165 1171
1166 1172 This function refers to output only; for input, see `ui.interactive()'.
1167 1173 This function always returns false when in plain mode, see `ui.plain()'.
1168 1174 '''
1169 1175 if self.plain():
1170 1176 return False
1171 1177
1172 1178 i = self.configbool("ui", "formatted", None)
1173 1179 if i is None:
1174 1180 # some environments replace stdout without implementing isatty
1175 1181 # usually those are non-interactive
1176 1182 return self._isatty(self.fout)
1177 1183
1178 1184 return i
1179 1185
1180 1186 def _readline(self, prompt=''):
1181 1187 if self._isatty(self.fin):
1182 1188 try:
1183 1189 # magically add command line editing support, where
1184 1190 # available
1185 1191 import readline
1186 1192 # force demandimport to really load the module
1187 1193 readline.read_history_file
1188 1194 # windows sometimes raises something other than ImportError
1189 1195 except Exception:
1190 1196 pass
1191 1197
1192 1198 # call write() so output goes through subclassed implementation
1193 1199 # e.g. color extension on Windows
1194 1200 self.write(prompt, prompt=True)
1195 1201
1196 1202 # instead of trying to emulate raw_input, swap (self.fin,
1197 1203 # self.fout) with (sys.stdin, sys.stdout)
1198 1204 oldin = sys.stdin
1199 1205 oldout = sys.stdout
1200 1206 sys.stdin = self.fin
1201 1207 sys.stdout = self.fout
1202 1208 # prompt ' ' must exist; otherwise readline may delete entire line
1203 1209 # - http://bugs.python.org/issue12833
1204 1210 with self.timeblockedsection('stdio'):
1205 1211 line = raw_input(' ')
1206 1212 sys.stdin = oldin
1207 1213 sys.stdout = oldout
1208 1214
1209 1215 # When stdin is in binary mode on Windows, it can cause
1210 1216 # raw_input() to emit an extra trailing carriage return
1211 1217 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1212 1218 line = line[:-1]
1213 1219 return line
1214 1220
1215 1221 def prompt(self, msg, default="y"):
1216 1222 """Prompt user with msg, read response.
1217 1223 If ui is not interactive, the default is returned.
1218 1224 """
1219 1225 if not self.interactive():
1220 1226 self.write(msg, ' ', default or '', "\n")
1221 1227 return default
1222 1228 try:
1223 1229 r = self._readline(self.label(msg, 'ui.prompt'))
1224 1230 if not r:
1225 1231 r = default
1226 1232 if self.configbool('ui', 'promptecho'):
1227 1233 self.write(r, "\n")
1228 1234 return r
1229 1235 except EOFError:
1230 1236 raise error.ResponseExpected()
1231 1237
1232 1238 @staticmethod
1233 1239 def extractchoices(prompt):
1234 1240 """Extract prompt message and list of choices from specified prompt.
1235 1241
1236 1242 This returns tuple "(message, choices)", and "choices" is the
1237 1243 list of tuple "(response character, text without &)".
1238 1244
1239 1245 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1240 1246 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1241 1247 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1242 1248 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1243 1249 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1244 1250 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1245 1251 """
1246 1252
1247 1253 # Sadly, the prompt string may have been built with a filename
1248 1254 # containing "$$" so let's try to find the first valid-looking
1249 1255 # prompt to start parsing. Sadly, we also can't rely on
1250 1256 # choices containing spaces, ASCII, or basically anything
1251 1257 # except an ampersand followed by a character.
1252 1258 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1253 1259 msg = m.group(1)
1254 1260 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1255 1261 return (msg,
1256 1262 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1257 1263 for s in choices])
1258 1264
1259 1265 def promptchoice(self, prompt, default=0):
1260 1266 """Prompt user with a message, read response, and ensure it matches
1261 1267 one of the provided choices. The prompt is formatted as follows:
1262 1268
1263 1269 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1264 1270
1265 1271 The index of the choice is returned. Responses are case
1266 1272 insensitive. If ui is not interactive, the default is
1267 1273 returned.
1268 1274 """
1269 1275
1270 1276 msg, choices = self.extractchoices(prompt)
1271 1277 resps = [r for r, t in choices]
1272 1278 while True:
1273 1279 r = self.prompt(msg, resps[default])
1274 1280 if r.lower() in resps:
1275 1281 return resps.index(r.lower())
1276 1282 self.write(_("unrecognized response\n"))
1277 1283
1278 1284 def getpass(self, prompt=None, default=None):
1279 1285 if not self.interactive():
1280 1286 return default
1281 1287 try:
1282 1288 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1283 1289 # disable getpass() only if explicitly specified. it's still valid
1284 1290 # to interact with tty even if fin is not a tty.
1285 1291 with self.timeblockedsection('stdio'):
1286 1292 if self.configbool('ui', 'nontty'):
1287 1293 l = self.fin.readline()
1288 1294 if not l:
1289 1295 raise EOFError
1290 1296 return l.rstrip('\n')
1291 1297 else:
1292 1298 return getpass.getpass('')
1293 1299 except EOFError:
1294 1300 raise error.ResponseExpected()
1295 1301 def status(self, *msg, **opts):
1296 1302 '''write status message to output (if ui.quiet is False)
1297 1303
1298 1304 This adds an output label of "ui.status".
1299 1305 '''
1300 1306 if not self.quiet:
1301 1307 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1302 1308 self.write(*msg, **opts)
1303 1309 def warn(self, *msg, **opts):
1304 1310 '''write warning message to output (stderr)
1305 1311
1306 1312 This adds an output label of "ui.warning".
1307 1313 '''
1308 1314 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1309 1315 self.write_err(*msg, **opts)
1310 1316 def note(self, *msg, **opts):
1311 1317 '''write note to output (if ui.verbose is True)
1312 1318
1313 1319 This adds an output label of "ui.note".
1314 1320 '''
1315 1321 if self.verbose:
1316 1322 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1317 1323 self.write(*msg, **opts)
1318 1324 def debug(self, *msg, **opts):
1319 1325 '''write debug message to output (if ui.debugflag is True)
1320 1326
1321 1327 This adds an output label of "ui.debug".
1322 1328 '''
1323 1329 if self.debugflag:
1324 1330 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1325 1331 self.write(*msg, **opts)
1326 1332
1327 1333 def edit(self, text, user, extra=None, editform=None, pending=None,
1328 1334 repopath=None):
1329 1335 extra_defaults = {
1330 1336 'prefix': 'editor',
1331 1337 'suffix': '.txt',
1332 1338 }
1333 1339 if extra is not None:
1334 1340 extra_defaults.update(extra)
1335 1341 extra = extra_defaults
1336 1342
1337 1343 rdir = None
1338 1344 if self.configbool('experimental', 'editortmpinhg'):
1339 1345 rdir = repopath
1340 1346 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1341 1347 suffix=extra['suffix'],
1342 1348 dir=rdir)
1343 1349 try:
1344 1350 f = os.fdopen(fd, r'wb')
1345 1351 f.write(util.tonativeeol(text))
1346 1352 f.close()
1347 1353
1348 1354 environ = {'HGUSER': user}
1349 1355 if 'transplant_source' in extra:
1350 1356 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1351 1357 for label in ('intermediate-source', 'source', 'rebase_source'):
1352 1358 if label in extra:
1353 1359 environ.update({'HGREVISION': extra[label]})
1354 1360 break
1355 1361 if editform:
1356 1362 environ.update({'HGEDITFORM': editform})
1357 1363 if pending:
1358 1364 environ.update({'HG_PENDING': pending})
1359 1365
1360 1366 editor = self.geteditor()
1361 1367
1362 1368 self.system("%s \"%s\"" % (editor, name),
1363 1369 environ=environ,
1364 1370 onerr=error.Abort, errprefix=_("edit failed"),
1365 1371 blockedtag='editor')
1366 1372
1367 1373 f = open(name, r'rb')
1368 1374 t = util.fromnativeeol(f.read())
1369 1375 f.close()
1370 1376 finally:
1371 1377 os.unlink(name)
1372 1378
1373 1379 return t
1374 1380
1375 1381 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1376 1382 blockedtag=None):
1377 1383 '''execute shell command with appropriate output stream. command
1378 1384 output will be redirected if fout is not stdout.
1379 1385
1380 1386 if command fails and onerr is None, return status, else raise onerr
1381 1387 object as exception.
1382 1388 '''
1383 1389 if blockedtag is None:
1384 1390 # Long cmds tend to be because of an absolute path on cmd. Keep
1385 1391 # the tail end instead
1386 1392 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1387 1393 blockedtag = 'unknown_system_' + cmdsuffix
1388 1394 out = self.fout
1389 1395 if any(s[1] for s in self._bufferstates):
1390 1396 out = self
1391 1397 with self.timeblockedsection(blockedtag):
1392 1398 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1393 1399 if rc and onerr:
1394 1400 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1395 1401 util.explainexit(rc)[0])
1396 1402 if errprefix:
1397 1403 errmsg = '%s: %s' % (errprefix, errmsg)
1398 1404 raise onerr(errmsg)
1399 1405 return rc
1400 1406
1401 1407 def _runsystem(self, cmd, environ, cwd, out):
1402 1408 """actually execute the given shell command (can be overridden by
1403 1409 extensions like chg)"""
1404 1410 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1405 1411
1406 1412 def traceback(self, exc=None, force=False):
1407 1413 '''print exception traceback if traceback printing enabled or forced.
1408 1414 only to call in exception handler. returns true if traceback
1409 1415 printed.'''
1410 1416 if self.tracebackflag or force:
1411 1417 if exc is None:
1412 1418 exc = sys.exc_info()
1413 1419 cause = getattr(exc[1], 'cause', None)
1414 1420
1415 1421 if cause is not None:
1416 1422 causetb = traceback.format_tb(cause[2])
1417 1423 exctb = traceback.format_tb(exc[2])
1418 1424 exconly = traceback.format_exception_only(cause[0], cause[1])
1419 1425
1420 1426 # exclude frame where 'exc' was chained and rethrown from exctb
1421 1427 self.write_err('Traceback (most recent call last):\n',
1422 1428 ''.join(exctb[:-1]),
1423 1429 ''.join(causetb),
1424 1430 ''.join(exconly))
1425 1431 else:
1426 1432 output = traceback.format_exception(exc[0], exc[1], exc[2])
1427 1433 data = r''.join(output)
1428 1434 if pycompat.ispy3:
1429 1435 enc = pycompat.sysstr(encoding.encoding)
1430 1436 data = data.encode(enc, errors=r'replace')
1431 1437 self.write_err(data)
1432 1438 return self.tracebackflag or force
1433 1439
1434 1440 def geteditor(self):
1435 1441 '''return editor to use'''
1436 1442 if pycompat.sysplatform == 'plan9':
1437 1443 # vi is the MIPS instruction simulator on Plan 9. We
1438 1444 # instead default to E to plumb commit messages to
1439 1445 # avoid confusion.
1440 1446 editor = 'E'
1441 1447 else:
1442 1448 editor = 'vi'
1443 1449 return (encoding.environ.get("HGEDITOR") or
1444 1450 self.config("ui", "editor", editor))
1445 1451
1446 1452 @util.propertycache
1447 1453 def _progbar(self):
1448 1454 """setup the progbar singleton to the ui object"""
1449 1455 if (self.quiet or self.debugflag
1450 1456 or self.configbool('progress', 'disable', False)
1451 1457 or not progress.shouldprint(self)):
1452 1458 return None
1453 1459 return getprogbar(self)
1454 1460
1455 1461 def _progclear(self):
1456 1462 """clear progress bar output if any. use it before any output"""
1457 1463 if '_progbar' not in vars(self): # nothing loaded yet
1458 1464 return
1459 1465 if self._progbar is not None and self._progbar.printed:
1460 1466 self._progbar.clear()
1461 1467
1462 1468 def progress(self, topic, pos, item="", unit="", total=None):
1463 1469 '''show a progress message
1464 1470
1465 1471 By default a textual progress bar will be displayed if an operation
1466 1472 takes too long. 'topic' is the current operation, 'item' is a
1467 1473 non-numeric marker of the current position (i.e. the currently
1468 1474 in-process file), 'pos' is the current numeric position (i.e.
1469 1475 revision, bytes, etc.), unit is a corresponding unit label,
1470 1476 and total is the highest expected pos.
1471 1477
1472 1478 Multiple nested topics may be active at a time.
1473 1479
1474 1480 All topics should be marked closed by setting pos to None at
1475 1481 termination.
1476 1482 '''
1477 1483 if self._progbar is not None:
1478 1484 self._progbar.progress(topic, pos, item=item, unit=unit,
1479 1485 total=total)
1480 1486 if pos is None or not self.configbool('progress', 'debug'):
1481 1487 return
1482 1488
1483 1489 if unit:
1484 1490 unit = ' ' + unit
1485 1491 if item:
1486 1492 item = ' ' + item
1487 1493
1488 1494 if total:
1489 1495 pct = 100.0 * pos / total
1490 1496 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1491 1497 % (topic, item, pos, total, unit, pct))
1492 1498 else:
1493 1499 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1494 1500
1495 1501 def log(self, service, *msg, **opts):
1496 1502 '''hook for logging facility extensions
1497 1503
1498 1504 service should be a readily-identifiable subsystem, which will
1499 1505 allow filtering.
1500 1506
1501 1507 *msg should be a newline-terminated format string to log, and
1502 1508 then any values to %-format into that format string.
1503 1509
1504 1510 **opts currently has no defined meanings.
1505 1511 '''
1506 1512
1507 1513 def label(self, msg, label):
1508 1514 '''style msg based on supplied label
1509 1515
1510 1516 If some color mode is enabled, this will add the necessary control
1511 1517 characters to apply such color. In addition, 'debug' color mode adds
1512 1518 markup showing which label affects a piece of text.
1513 1519
1514 1520 ui.write(s, 'label') is equivalent to
1515 1521 ui.write(ui.label(s, 'label')).
1516 1522 '''
1517 1523 if self._colormode is not None:
1518 1524 return color.colorlabel(self, msg, label)
1519 1525 return msg
1520 1526
1521 1527 def develwarn(self, msg, stacklevel=1, config=None):
1522 1528 """issue a developer warning message
1523 1529
1524 1530 Use 'stacklevel' to report the offender some layers further up in the
1525 1531 stack.
1526 1532 """
1527 1533 if not self.configbool('devel', 'all-warnings'):
1528 1534 if config is not None and not self.configbool('devel', config):
1529 1535 return
1530 1536 msg = 'devel-warn: ' + msg
1531 1537 stacklevel += 1 # get in develwarn
1532 1538 if self.tracebackflag:
1533 1539 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1534 1540 self.log('develwarn', '%s at:\n%s' %
1535 1541 (msg, ''.join(util.getstackframes(stacklevel))))
1536 1542 else:
1537 1543 curframe = inspect.currentframe()
1538 1544 calframe = inspect.getouterframes(curframe, 2)
1539 1545 self.write_err('%s at: %s:%s (%s)\n'
1540 1546 % ((msg,) + calframe[stacklevel][1:4]))
1541 1547 self.log('develwarn', '%s at: %s:%s (%s)\n',
1542 1548 msg, *calframe[stacklevel][1:4])
1543 1549 curframe = calframe = None # avoid cycles
1544 1550
1545 1551 def deprecwarn(self, msg, version):
1546 1552 """issue a deprecation warning
1547 1553
1548 1554 - msg: message explaining what is deprecated and how to upgrade,
1549 1555 - version: last version where the API will be supported,
1550 1556 """
1551 1557 if not (self.configbool('devel', 'all-warnings')
1552 1558 or self.configbool('devel', 'deprec-warn')):
1553 1559 return
1554 1560 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1555 1561 " update your code.)") % version
1556 1562 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1557 1563
1558 1564 def exportableenviron(self):
1559 1565 """The environment variables that are safe to export, e.g. through
1560 1566 hgweb.
1561 1567 """
1562 1568 return self._exportableenviron
1563 1569
1564 1570 @contextlib.contextmanager
1565 1571 def configoverride(self, overrides, source=""):
1566 1572 """Context manager for temporary config overrides
1567 1573 `overrides` must be a dict of the following structure:
1568 1574 {(section, name) : value}"""
1569 1575 backups = {}
1570 1576 try:
1571 1577 for (section, name), value in overrides.items():
1572 1578 backups[(section, name)] = self.backupconfig(section, name)
1573 1579 self.setconfig(section, name, value, source)
1574 1580 yield
1575 1581 finally:
1576 1582 for __, backup in backups.items():
1577 1583 self.restoreconfig(backup)
1578 1584 # just restoring ui.quiet config to the previous value is not enough
1579 1585 # as it does not update ui.quiet class member
1580 1586 if ('ui', 'quiet') in overrides:
1581 1587 self.fixconfig(section='ui')
1582 1588
1583 1589 class paths(dict):
1584 1590 """Represents a collection of paths and their configs.
1585 1591
1586 1592 Data is initially derived from ui instances and the config files they have
1587 1593 loaded.
1588 1594 """
1589 1595 def __init__(self, ui):
1590 1596 dict.__init__(self)
1591 1597
1592 1598 for name, loc in ui.configitems('paths', ignoresub=True):
1593 1599 # No location is the same as not existing.
1594 1600 if not loc:
1595 1601 continue
1596 1602 loc, sub = ui.configsuboptions('paths', name)
1597 1603 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1598 1604
1599 1605 def getpath(self, name, default=None):
1600 1606 """Return a ``path`` from a string, falling back to default.
1601 1607
1602 1608 ``name`` can be a named path or locations. Locations are filesystem
1603 1609 paths or URIs.
1604 1610
1605 1611 Returns None if ``name`` is not a registered path, a URI, or a local
1606 1612 path to a repo.
1607 1613 """
1608 1614 # Only fall back to default if no path was requested.
1609 1615 if name is None:
1610 1616 if not default:
1611 1617 default = ()
1612 1618 elif not isinstance(default, (tuple, list)):
1613 1619 default = (default,)
1614 1620 for k in default:
1615 1621 try:
1616 1622 return self[k]
1617 1623 except KeyError:
1618 1624 continue
1619 1625 return None
1620 1626
1621 1627 # Most likely empty string.
1622 1628 # This may need to raise in the future.
1623 1629 if not name:
1624 1630 return None
1625 1631
1626 1632 try:
1627 1633 return self[name]
1628 1634 except KeyError:
1629 1635 # Try to resolve as a local path or URI.
1630 1636 try:
1631 1637 # We don't pass sub-options in, so no need to pass ui instance.
1632 1638 return path(None, None, rawloc=name)
1633 1639 except ValueError:
1634 1640 raise error.RepoError(_('repository %s does not exist') %
1635 1641 name)
1636 1642
1637 1643 _pathsuboptions = {}
1638 1644
1639 1645 def pathsuboption(option, attr):
1640 1646 """Decorator used to declare a path sub-option.
1641 1647
1642 1648 Arguments are the sub-option name and the attribute it should set on
1643 1649 ``path`` instances.
1644 1650
1645 1651 The decorated function will receive as arguments a ``ui`` instance,
1646 1652 ``path`` instance, and the string value of this option from the config.
1647 1653 The function should return the value that will be set on the ``path``
1648 1654 instance.
1649 1655
1650 1656 This decorator can be used to perform additional verification of
1651 1657 sub-options and to change the type of sub-options.
1652 1658 """
1653 1659 def register(func):
1654 1660 _pathsuboptions[option] = (attr, func)
1655 1661 return func
1656 1662 return register
1657 1663
1658 1664 @pathsuboption('pushurl', 'pushloc')
1659 1665 def pushurlpathoption(ui, path, value):
1660 1666 u = util.url(value)
1661 1667 # Actually require a URL.
1662 1668 if not u.scheme:
1663 1669 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1664 1670 return None
1665 1671
1666 1672 # Don't support the #foo syntax in the push URL to declare branch to
1667 1673 # push.
1668 1674 if u.fragment:
1669 1675 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1670 1676 'ignoring)\n') % path.name)
1671 1677 u.fragment = None
1672 1678
1673 1679 return str(u)
1674 1680
1675 1681 @pathsuboption('pushrev', 'pushrev')
1676 1682 def pushrevpathoption(ui, path, value):
1677 1683 return value
1678 1684
1679 1685 class path(object):
1680 1686 """Represents an individual path and its configuration."""
1681 1687
1682 1688 def __init__(self, ui, name, rawloc=None, suboptions=None):
1683 1689 """Construct a path from its config options.
1684 1690
1685 1691 ``ui`` is the ``ui`` instance the path is coming from.
1686 1692 ``name`` is the symbolic name of the path.
1687 1693 ``rawloc`` is the raw location, as defined in the config.
1688 1694 ``pushloc`` is the raw locations pushes should be made to.
1689 1695
1690 1696 If ``name`` is not defined, we require that the location be a) a local
1691 1697 filesystem path with a .hg directory or b) a URL. If not,
1692 1698 ``ValueError`` is raised.
1693 1699 """
1694 1700 if not rawloc:
1695 1701 raise ValueError('rawloc must be defined')
1696 1702
1697 1703 # Locations may define branches via syntax <base>#<branch>.
1698 1704 u = util.url(rawloc)
1699 1705 branch = None
1700 1706 if u.fragment:
1701 1707 branch = u.fragment
1702 1708 u.fragment = None
1703 1709
1704 1710 self.url = u
1705 1711 self.branch = branch
1706 1712
1707 1713 self.name = name
1708 1714 self.rawloc = rawloc
1709 1715 self.loc = '%s' % u
1710 1716
1711 1717 # When given a raw location but not a symbolic name, validate the
1712 1718 # location is valid.
1713 1719 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1714 1720 raise ValueError('location is not a URL or path to a local '
1715 1721 'repo: %s' % rawloc)
1716 1722
1717 1723 suboptions = suboptions or {}
1718 1724
1719 1725 # Now process the sub-options. If a sub-option is registered, its
1720 1726 # attribute will always be present. The value will be None if there
1721 1727 # was no valid sub-option.
1722 1728 for suboption, (attr, func) in _pathsuboptions.iteritems():
1723 1729 if suboption not in suboptions:
1724 1730 setattr(self, attr, None)
1725 1731 continue
1726 1732
1727 1733 value = func(ui, self, suboptions[suboption])
1728 1734 setattr(self, attr, value)
1729 1735
1730 1736 def _isvalidlocalpath(self, path):
1731 1737 """Returns True if the given path is a potentially valid repository.
1732 1738 This is its own function so that extensions can change the definition of
1733 1739 'valid' in this case (like when pulling from a git repo into a hg
1734 1740 one)."""
1735 1741 return os.path.isdir(os.path.join(path, '.hg'))
1736 1742
1737 1743 @property
1738 1744 def suboptions(self):
1739 1745 """Return sub-options and their values for this path.
1740 1746
1741 1747 This is intended to be used for presentation purposes.
1742 1748 """
1743 1749 d = {}
1744 1750 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1745 1751 value = getattr(self, attr)
1746 1752 if value is not None:
1747 1753 d[subopt] = value
1748 1754 return d
1749 1755
1750 1756 # we instantiate one globally shared progress bar to avoid
1751 1757 # competing progress bars when multiple UI objects get created
1752 1758 _progresssingleton = None
1753 1759
1754 1760 def getprogbar(ui):
1755 1761 global _progresssingleton
1756 1762 if _progresssingleton is None:
1757 1763 # passing 'ui' object to the singleton is fishy,
1758 1764 # this is how the extension used to work but feel free to rework it.
1759 1765 _progresssingleton = progress.progbar(ui)
1760 1766 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now