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