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