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