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