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