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