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