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