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