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