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