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