##// END OF EJS Templates
ui: inline _write() into write() due to performance issue...
Yuya Nishihara -
r41376:b4a3abdc stable
parent child Browse files
Show More
@@ -1,2077 +1,2088 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 self._write(self._fout, *args, **opts)
1002 dest = self._fout
1003
1004 # inlined _write() for speed
1005 if self._isbuffered(dest):
1006 label = opts.get(r'label', '')
1007 if label and self._bufferapplylabels:
1008 self._buffers[-1].extend(self.label(a, label) for a in args)
1009 else:
1010 self._buffers[-1].extend(args)
1011 else:
1012 self._writenobuf(dest, *args, **opts)
1003 1013
1004 1014 def write_err(self, *args, **opts):
1005 1015 self._write(self._ferr, *args, **opts)
1006 1016
1007 1017 def _write(self, dest, *args, **opts):
1018 # update write() as well if you touch this code
1008 1019 if self._isbuffered(dest):
1009 1020 label = opts.get(r'label', '')
1010 1021 if label and self._bufferapplylabels:
1011 1022 self._buffers[-1].extend(self.label(a, label) for a in args)
1012 1023 else:
1013 1024 self._buffers[-1].extend(args)
1014 1025 else:
1015 1026 self._writenobuf(dest, *args, **opts)
1016 1027
1017 1028 def _writenobuf(self, dest, *args, **opts):
1018 1029 self._progclear()
1019 1030 msg = b''.join(args)
1020 1031
1021 1032 # opencode timeblockedsection because this is a critical path
1022 1033 starttime = util.timer()
1023 1034 try:
1024 1035 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1025 1036 self._fout.flush()
1026 1037 if getattr(dest, 'structured', False):
1027 1038 # channel for machine-readable output with metadata, where
1028 1039 # no extra colorization is necessary.
1029 1040 dest.write(msg, **opts)
1030 1041 elif self._colormode == 'win32':
1031 1042 # windows color printing is its own can of crab, defer to
1032 1043 # the color module and that is it.
1033 1044 color.win32print(self, dest.write, msg, **opts)
1034 1045 else:
1035 1046 if self._colormode is not None:
1036 1047 label = opts.get(r'label', '')
1037 1048 msg = self.label(msg, label)
1038 1049 dest.write(msg)
1039 1050 # stderr may be buffered under win32 when redirected to files,
1040 1051 # including stdout.
1041 1052 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1042 1053 dest.flush()
1043 1054 except IOError as err:
1044 1055 if (dest is self._ferr
1045 1056 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1046 1057 # no way to report the error, so ignore it
1047 1058 return
1048 1059 raise error.StdioError(err)
1049 1060 finally:
1050 1061 self._blockedtimes['stdio_blocked'] += \
1051 1062 (util.timer() - starttime) * 1000
1052 1063
1053 1064 def _writemsg(self, dest, *args, **opts):
1054 1065 _writemsgwith(self._write, dest, *args, **opts)
1055 1066
1056 1067 def _writemsgnobuf(self, dest, *args, **opts):
1057 1068 _writemsgwith(self._writenobuf, dest, *args, **opts)
1058 1069
1059 1070 def flush(self):
1060 1071 # opencode timeblockedsection because this is a critical path
1061 1072 starttime = util.timer()
1062 1073 try:
1063 1074 try:
1064 1075 self._fout.flush()
1065 1076 except IOError as err:
1066 1077 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1067 1078 raise error.StdioError(err)
1068 1079 finally:
1069 1080 try:
1070 1081 self._ferr.flush()
1071 1082 except IOError as err:
1072 1083 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1073 1084 raise error.StdioError(err)
1074 1085 finally:
1075 1086 self._blockedtimes['stdio_blocked'] += \
1076 1087 (util.timer() - starttime) * 1000
1077 1088
1078 1089 def _isatty(self, fh):
1079 1090 if self.configbool('ui', 'nontty'):
1080 1091 return False
1081 1092 return procutil.isatty(fh)
1082 1093
1083 1094 def protectfinout(self):
1084 1095 """Duplicate ui streams and redirect original if they are stdio
1085 1096
1086 1097 Returns (fin, fout) which point to the original ui fds, but may be
1087 1098 copy of them. The returned streams can be considered "owned" in that
1088 1099 print(), exec(), etc. never reach to them.
1089 1100 """
1090 1101 if self._finoutredirected:
1091 1102 # if already redirected, protectstdio() would just create another
1092 1103 # nullfd pair, which is equivalent to returning self._fin/_fout.
1093 1104 return self._fin, self._fout
1094 1105 fin, fout = procutil.protectstdio(self._fin, self._fout)
1095 1106 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1096 1107 return fin, fout
1097 1108
1098 1109 def restorefinout(self, fin, fout):
1099 1110 """Restore ui streams from possibly duplicated (fin, fout)"""
1100 1111 if (fin, fout) == (self._fin, self._fout):
1101 1112 return
1102 1113 procutil.restorestdio(self._fin, self._fout, fin, fout)
1103 1114 # protectfinout() won't create more than one duplicated streams,
1104 1115 # so we can just turn the redirection flag off.
1105 1116 self._finoutredirected = False
1106 1117
1107 1118 @contextlib.contextmanager
1108 1119 def protectedfinout(self):
1109 1120 """Run code block with protected standard streams"""
1110 1121 fin, fout = self.protectfinout()
1111 1122 try:
1112 1123 yield fin, fout
1113 1124 finally:
1114 1125 self.restorefinout(fin, fout)
1115 1126
1116 1127 def disablepager(self):
1117 1128 self._disablepager = True
1118 1129
1119 1130 def pager(self, command):
1120 1131 """Start a pager for subsequent command output.
1121 1132
1122 1133 Commands which produce a long stream of output should call
1123 1134 this function to activate the user's preferred pagination
1124 1135 mechanism (which may be no pager). Calling this function
1125 1136 precludes any future use of interactive functionality, such as
1126 1137 prompting the user or activating curses.
1127 1138
1128 1139 Args:
1129 1140 command: The full, non-aliased name of the command. That is, "log"
1130 1141 not "history, "summary" not "summ", etc.
1131 1142 """
1132 1143 if (self._disablepager
1133 1144 or self.pageractive):
1134 1145 # how pager should do is already determined
1135 1146 return
1136 1147
1137 1148 if not command.startswith('internal-always-') and (
1138 1149 # explicit --pager=on (= 'internal-always-' prefix) should
1139 1150 # take precedence over disabling factors below
1140 1151 command in self.configlist('pager', 'ignore')
1141 1152 or not self.configbool('ui', 'paginate')
1142 1153 or not self.configbool('pager', 'attend-' + command, True)
1143 1154 or encoding.environ.get('TERM') == 'dumb'
1144 1155 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1145 1156 # formatted() will need some adjustment.
1146 1157 or not self.formatted()
1147 1158 or self.plain()
1148 1159 or self._buffers
1149 1160 # TODO: expose debugger-enabled on the UI object
1150 1161 or '--debugger' in pycompat.sysargv):
1151 1162 # We only want to paginate if the ui appears to be
1152 1163 # interactive, the user didn't say HGPLAIN or
1153 1164 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1154 1165 return
1155 1166
1156 1167 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1157 1168 if not pagercmd:
1158 1169 return
1159 1170
1160 1171 pagerenv = {}
1161 1172 for name, value in rcutil.defaultpagerenv().items():
1162 1173 if name not in encoding.environ:
1163 1174 pagerenv[name] = value
1164 1175
1165 1176 self.debug('starting pager for command %s\n' %
1166 1177 stringutil.pprint(command))
1167 1178 self.flush()
1168 1179
1169 1180 wasformatted = self.formatted()
1170 1181 if util.safehasattr(signal, "SIGPIPE"):
1171 1182 signal.signal(signal.SIGPIPE, _catchterm)
1172 1183 if self._runpager(pagercmd, pagerenv):
1173 1184 self.pageractive = True
1174 1185 # Preserve the formatted-ness of the UI. This is important
1175 1186 # because we mess with stdout, which might confuse
1176 1187 # auto-detection of things being formatted.
1177 1188 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1178 1189 self.setconfig('ui', 'interactive', False, 'pager')
1179 1190
1180 1191 # If pagermode differs from color.mode, reconfigure color now that
1181 1192 # pageractive is set.
1182 1193 cm = self._colormode
1183 1194 if cm != self.config('color', 'pagermode', cm):
1184 1195 color.setup(self)
1185 1196 else:
1186 1197 # If the pager can't be spawned in dispatch when --pager=on is
1187 1198 # given, don't try again when the command runs, to avoid a duplicate
1188 1199 # warning about a missing pager command.
1189 1200 self.disablepager()
1190 1201
1191 1202 def _runpager(self, command, env=None):
1192 1203 """Actually start the pager and set up file descriptors.
1193 1204
1194 1205 This is separate in part so that extensions (like chg) can
1195 1206 override how a pager is invoked.
1196 1207 """
1197 1208 if command == 'cat':
1198 1209 # Save ourselves some work.
1199 1210 return False
1200 1211 # If the command doesn't contain any of these characters, we
1201 1212 # assume it's a binary and exec it directly. This means for
1202 1213 # simple pager command configurations, we can degrade
1203 1214 # gracefully and tell the user about their broken pager.
1204 1215 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1205 1216
1206 1217 if pycompat.iswindows and not shell:
1207 1218 # Window's built-in `more` cannot be invoked with shell=False, but
1208 1219 # its `more.com` can. Hide this implementation detail from the
1209 1220 # user so we can also get sane bad PAGER behavior. MSYS has
1210 1221 # `more.exe`, so do a cmd.exe style resolution of the executable to
1211 1222 # determine which one to use.
1212 1223 fullcmd = procutil.findexe(command)
1213 1224 if not fullcmd:
1214 1225 self.warn(_("missing pager command '%s', skipping pager\n")
1215 1226 % command)
1216 1227 return False
1217 1228
1218 1229 command = fullcmd
1219 1230
1220 1231 try:
1221 1232 pager = subprocess.Popen(
1222 1233 procutil.tonativestr(command), shell=shell, bufsize=-1,
1223 1234 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1224 1235 stdout=procutil.stdout, stderr=procutil.stderr,
1225 1236 env=procutil.tonativeenv(procutil.shellenviron(env)))
1226 1237 except OSError as e:
1227 1238 if e.errno == errno.ENOENT and not shell:
1228 1239 self.warn(_("missing pager command '%s', skipping pager\n")
1229 1240 % command)
1230 1241 return False
1231 1242 raise
1232 1243
1233 1244 # back up original file descriptors
1234 1245 stdoutfd = os.dup(procutil.stdout.fileno())
1235 1246 stderrfd = os.dup(procutil.stderr.fileno())
1236 1247
1237 1248 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1238 1249 if self._isatty(procutil.stderr):
1239 1250 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1240 1251
1241 1252 @self.atexit
1242 1253 def killpager():
1243 1254 if util.safehasattr(signal, "SIGINT"):
1244 1255 signal.signal(signal.SIGINT, signal.SIG_IGN)
1245 1256 # restore original fds, closing pager.stdin copies in the process
1246 1257 os.dup2(stdoutfd, procutil.stdout.fileno())
1247 1258 os.dup2(stderrfd, procutil.stderr.fileno())
1248 1259 pager.stdin.close()
1249 1260 pager.wait()
1250 1261
1251 1262 return True
1252 1263
1253 1264 @property
1254 1265 def _exithandlers(self):
1255 1266 return _reqexithandlers
1256 1267
1257 1268 def atexit(self, func, *args, **kwargs):
1258 1269 '''register a function to run after dispatching a request
1259 1270
1260 1271 Handlers do not stay registered across request boundaries.'''
1261 1272 self._exithandlers.append((func, args, kwargs))
1262 1273 return func
1263 1274
1264 1275 def interface(self, feature):
1265 1276 """what interface to use for interactive console features?
1266 1277
1267 1278 The interface is controlled by the value of `ui.interface` but also by
1268 1279 the value of feature-specific configuration. For example:
1269 1280
1270 1281 ui.interface.histedit = text
1271 1282 ui.interface.chunkselector = curses
1272 1283
1273 1284 Here the features are "histedit" and "chunkselector".
1274 1285
1275 1286 The configuration above means that the default interfaces for commands
1276 1287 is curses, the interface for histedit is text and the interface for
1277 1288 selecting chunk is crecord (the best curses interface available).
1278 1289
1279 1290 Consider the following example:
1280 1291 ui.interface = curses
1281 1292 ui.interface.histedit = text
1282 1293
1283 1294 Then histedit will use the text interface and chunkselector will use
1284 1295 the default curses interface (crecord at the moment).
1285 1296 """
1286 1297 alldefaults = frozenset(["text", "curses"])
1287 1298
1288 1299 featureinterfaces = {
1289 1300 "chunkselector": [
1290 1301 "text",
1291 1302 "curses",
1292 1303 ],
1293 1304 "histedit": [
1294 1305 "text",
1295 1306 "curses",
1296 1307 ],
1297 1308 }
1298 1309
1299 1310 # Feature-specific interface
1300 1311 if feature not in featureinterfaces.keys():
1301 1312 # Programming error, not user error
1302 1313 raise ValueError("Unknown feature requested %s" % feature)
1303 1314
1304 1315 availableinterfaces = frozenset(featureinterfaces[feature])
1305 1316 if alldefaults > availableinterfaces:
1306 1317 # Programming error, not user error. We need a use case to
1307 1318 # define the right thing to do here.
1308 1319 raise ValueError(
1309 1320 "Feature %s does not handle all default interfaces" %
1310 1321 feature)
1311 1322
1312 1323 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1313 1324 return "text"
1314 1325
1315 1326 # Default interface for all the features
1316 1327 defaultinterface = "text"
1317 1328 i = self.config("ui", "interface")
1318 1329 if i in alldefaults:
1319 1330 defaultinterface = i
1320 1331
1321 1332 choseninterface = defaultinterface
1322 1333 f = self.config("ui", "interface.%s" % feature)
1323 1334 if f in availableinterfaces:
1324 1335 choseninterface = f
1325 1336
1326 1337 if i is not None and defaultinterface != i:
1327 1338 if f is not None:
1328 1339 self.warn(_("invalid value for ui.interface: %s\n") %
1329 1340 (i,))
1330 1341 else:
1331 1342 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1332 1343 (i, choseninterface))
1333 1344 if f is not None and choseninterface != f:
1334 1345 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1335 1346 (feature, f, choseninterface))
1336 1347
1337 1348 return choseninterface
1338 1349
1339 1350 def interactive(self):
1340 1351 '''is interactive input allowed?
1341 1352
1342 1353 An interactive session is a session where input can be reasonably read
1343 1354 from `sys.stdin'. If this function returns false, any attempt to read
1344 1355 from stdin should fail with an error, unless a sensible default has been
1345 1356 specified.
1346 1357
1347 1358 Interactiveness is triggered by the value of the `ui.interactive'
1348 1359 configuration variable or - if it is unset - when `sys.stdin' points
1349 1360 to a terminal device.
1350 1361
1351 1362 This function refers to input only; for output, see `ui.formatted()'.
1352 1363 '''
1353 1364 i = self.configbool("ui", "interactive")
1354 1365 if i is None:
1355 1366 # some environments replace stdin without implementing isatty
1356 1367 # usually those are non-interactive
1357 1368 return self._isatty(self._fin)
1358 1369
1359 1370 return i
1360 1371
1361 1372 def termwidth(self):
1362 1373 '''how wide is the terminal in columns?
1363 1374 '''
1364 1375 if 'COLUMNS' in encoding.environ:
1365 1376 try:
1366 1377 return int(encoding.environ['COLUMNS'])
1367 1378 except ValueError:
1368 1379 pass
1369 1380 return scmutil.termsize(self)[0]
1370 1381
1371 1382 def formatted(self):
1372 1383 '''should formatted output be used?
1373 1384
1374 1385 It is often desirable to format the output to suite the output medium.
1375 1386 Examples of this are truncating long lines or colorizing messages.
1376 1387 However, this is not often not desirable when piping output into other
1377 1388 utilities, e.g. `grep'.
1378 1389
1379 1390 Formatted output is triggered by the value of the `ui.formatted'
1380 1391 configuration variable or - if it is unset - when `sys.stdout' points
1381 1392 to a terminal device. Please note that `ui.formatted' should be
1382 1393 considered an implementation detail; it is not intended for use outside
1383 1394 Mercurial or its extensions.
1384 1395
1385 1396 This function refers to output only; for input, see `ui.interactive()'.
1386 1397 This function always returns false when in plain mode, see `ui.plain()'.
1387 1398 '''
1388 1399 if self.plain():
1389 1400 return False
1390 1401
1391 1402 i = self.configbool("ui", "formatted")
1392 1403 if i is None:
1393 1404 # some environments replace stdout without implementing isatty
1394 1405 # usually those are non-interactive
1395 1406 return self._isatty(self._fout)
1396 1407
1397 1408 return i
1398 1409
1399 1410 def _readline(self):
1400 1411 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1401 1412 # because they have to be text streams with *no buffering*. Instead,
1402 1413 # we use rawinput() only if call_readline() will be invoked by
1403 1414 # PyOS_Readline(), so no I/O will be made at Python layer.
1404 1415 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1405 1416 and procutil.isstdin(self._fin)
1406 1417 and procutil.isstdout(self._fout))
1407 1418 if usereadline:
1408 1419 try:
1409 1420 # magically add command line editing support, where
1410 1421 # available
1411 1422 import readline
1412 1423 # force demandimport to really load the module
1413 1424 readline.read_history_file
1414 1425 # windows sometimes raises something other than ImportError
1415 1426 except Exception:
1416 1427 usereadline = False
1417 1428
1418 1429 # prompt ' ' must exist; otherwise readline may delete entire line
1419 1430 # - http://bugs.python.org/issue12833
1420 1431 with self.timeblockedsection('stdio'):
1421 1432 if usereadline:
1422 1433 line = encoding.strtolocal(pycompat.rawinput(r' '))
1423 1434 # When stdin is in binary mode on Windows, it can cause
1424 1435 # raw_input() to emit an extra trailing carriage return
1425 1436 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1426 1437 line = line[:-1]
1427 1438 else:
1428 1439 self._fout.write(b' ')
1429 1440 self._fout.flush()
1430 1441 line = self._fin.readline()
1431 1442 if not line:
1432 1443 raise EOFError
1433 1444 line = line.rstrip(pycompat.oslinesep)
1434 1445
1435 1446 return line
1436 1447
1437 1448 def prompt(self, msg, default="y"):
1438 1449 """Prompt user with msg, read response.
1439 1450 If ui is not interactive, the default is returned.
1440 1451 """
1441 1452 return self._prompt(msg, default=default)
1442 1453
1443 1454 def _prompt(self, msg, **opts):
1444 1455 default = opts[r'default']
1445 1456 if not self.interactive():
1446 1457 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1447 1458 self._writemsg(self._fmsgout, default or '', "\n",
1448 1459 type='promptecho')
1449 1460 return default
1450 1461 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1451 1462 self.flush()
1452 1463 try:
1453 1464 r = self._readline()
1454 1465 if not r:
1455 1466 r = default
1456 1467 if self.configbool('ui', 'promptecho'):
1457 1468 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1458 1469 return r
1459 1470 except EOFError:
1460 1471 raise error.ResponseExpected()
1461 1472
1462 1473 @staticmethod
1463 1474 def extractchoices(prompt):
1464 1475 """Extract prompt message and list of choices from specified prompt.
1465 1476
1466 1477 This returns tuple "(message, choices)", and "choices" is the
1467 1478 list of tuple "(response character, text without &)".
1468 1479
1469 1480 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1470 1481 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1471 1482 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1472 1483 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1473 1484 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1474 1485 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1475 1486 """
1476 1487
1477 1488 # Sadly, the prompt string may have been built with a filename
1478 1489 # containing "$$" so let's try to find the first valid-looking
1479 1490 # prompt to start parsing. Sadly, we also can't rely on
1480 1491 # choices containing spaces, ASCII, or basically anything
1481 1492 # except an ampersand followed by a character.
1482 1493 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1483 1494 msg = m.group(1)
1484 1495 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1485 1496 def choicetuple(s):
1486 1497 ampidx = s.index('&')
1487 1498 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1488 1499 return (msg, [choicetuple(s) for s in choices])
1489 1500
1490 1501 def promptchoice(self, prompt, default=0):
1491 1502 """Prompt user with a message, read response, and ensure it matches
1492 1503 one of the provided choices. The prompt is formatted as follows:
1493 1504
1494 1505 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1495 1506
1496 1507 The index of the choice is returned. Responses are case
1497 1508 insensitive. If ui is not interactive, the default is
1498 1509 returned.
1499 1510 """
1500 1511
1501 1512 msg, choices = self.extractchoices(prompt)
1502 1513 resps = [r for r, t in choices]
1503 1514 while True:
1504 1515 r = self._prompt(msg, default=resps[default], choices=choices)
1505 1516 if r.lower() in resps:
1506 1517 return resps.index(r.lower())
1507 1518 # TODO: shouldn't it be a warning?
1508 1519 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1509 1520
1510 1521 def getpass(self, prompt=None, default=None):
1511 1522 if not self.interactive():
1512 1523 return default
1513 1524 try:
1514 1525 self._writemsg(self._fmsgerr, prompt or _('password: '),
1515 1526 type='prompt', password=True)
1516 1527 # disable getpass() only if explicitly specified. it's still valid
1517 1528 # to interact with tty even if fin is not a tty.
1518 1529 with self.timeblockedsection('stdio'):
1519 1530 if self.configbool('ui', 'nontty'):
1520 1531 l = self._fin.readline()
1521 1532 if not l:
1522 1533 raise EOFError
1523 1534 return l.rstrip('\n')
1524 1535 else:
1525 1536 return getpass.getpass('')
1526 1537 except EOFError:
1527 1538 raise error.ResponseExpected()
1528 1539
1529 1540 def status(self, *msg, **opts):
1530 1541 '''write status message to output (if ui.quiet is False)
1531 1542
1532 1543 This adds an output label of "ui.status".
1533 1544 '''
1534 1545 if not self.quiet:
1535 1546 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1536 1547
1537 1548 def warn(self, *msg, **opts):
1538 1549 '''write warning message to output (stderr)
1539 1550
1540 1551 This adds an output label of "ui.warning".
1541 1552 '''
1542 1553 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1543 1554
1544 1555 def error(self, *msg, **opts):
1545 1556 '''write error message to output (stderr)
1546 1557
1547 1558 This adds an output label of "ui.error".
1548 1559 '''
1549 1560 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1550 1561
1551 1562 def note(self, *msg, **opts):
1552 1563 '''write note to output (if ui.verbose is True)
1553 1564
1554 1565 This adds an output label of "ui.note".
1555 1566 '''
1556 1567 if self.verbose:
1557 1568 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1558 1569
1559 1570 def debug(self, *msg, **opts):
1560 1571 '''write debug message to output (if ui.debugflag is True)
1561 1572
1562 1573 This adds an output label of "ui.debug".
1563 1574 '''
1564 1575 if self.debugflag:
1565 1576 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1566 1577 self.log(b'debug', b'%s', b''.join(msg))
1567 1578
1568 1579 def edit(self, text, user, extra=None, editform=None, pending=None,
1569 1580 repopath=None, action=None):
1570 1581 if action is None:
1571 1582 self.develwarn('action is None but will soon be a required '
1572 1583 'parameter to ui.edit()')
1573 1584 extra_defaults = {
1574 1585 'prefix': 'editor',
1575 1586 'suffix': '.txt',
1576 1587 }
1577 1588 if extra is not None:
1578 1589 if extra.get('suffix') is not None:
1579 1590 self.develwarn('extra.suffix is not None but will soon be '
1580 1591 'ignored by ui.edit()')
1581 1592 extra_defaults.update(extra)
1582 1593 extra = extra_defaults
1583 1594
1584 1595 if action == 'diff':
1585 1596 suffix = '.diff'
1586 1597 elif action:
1587 1598 suffix = '.%s.hg.txt' % action
1588 1599 else:
1589 1600 suffix = extra['suffix']
1590 1601
1591 1602 rdir = None
1592 1603 if self.configbool('experimental', 'editortmpinhg'):
1593 1604 rdir = repopath
1594 1605 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1595 1606 suffix=suffix,
1596 1607 dir=rdir)
1597 1608 try:
1598 1609 f = os.fdopen(fd, r'wb')
1599 1610 f.write(util.tonativeeol(text))
1600 1611 f.close()
1601 1612
1602 1613 environ = {'HGUSER': user}
1603 1614 if 'transplant_source' in extra:
1604 1615 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1605 1616 for label in ('intermediate-source', 'source', 'rebase_source'):
1606 1617 if label in extra:
1607 1618 environ.update({'HGREVISION': extra[label]})
1608 1619 break
1609 1620 if editform:
1610 1621 environ.update({'HGEDITFORM': editform})
1611 1622 if pending:
1612 1623 environ.update({'HG_PENDING': pending})
1613 1624
1614 1625 editor = self.geteditor()
1615 1626
1616 1627 self.system("%s \"%s\"" % (editor, name),
1617 1628 environ=environ,
1618 1629 onerr=error.Abort, errprefix=_("edit failed"),
1619 1630 blockedtag='editor')
1620 1631
1621 1632 f = open(name, r'rb')
1622 1633 t = util.fromnativeeol(f.read())
1623 1634 f.close()
1624 1635 finally:
1625 1636 os.unlink(name)
1626 1637
1627 1638 return t
1628 1639
1629 1640 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1630 1641 blockedtag=None):
1631 1642 '''execute shell command with appropriate output stream. command
1632 1643 output will be redirected if fout is not stdout.
1633 1644
1634 1645 if command fails and onerr is None, return status, else raise onerr
1635 1646 object as exception.
1636 1647 '''
1637 1648 if blockedtag is None:
1638 1649 # Long cmds tend to be because of an absolute path on cmd. Keep
1639 1650 # the tail end instead
1640 1651 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1641 1652 blockedtag = 'unknown_system_' + cmdsuffix
1642 1653 out = self._fout
1643 1654 if any(s[1] for s in self._bufferstates):
1644 1655 out = self
1645 1656 with self.timeblockedsection(blockedtag):
1646 1657 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1647 1658 if rc and onerr:
1648 1659 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1649 1660 procutil.explainexit(rc))
1650 1661 if errprefix:
1651 1662 errmsg = '%s: %s' % (errprefix, errmsg)
1652 1663 raise onerr(errmsg)
1653 1664 return rc
1654 1665
1655 1666 def _runsystem(self, cmd, environ, cwd, out):
1656 1667 """actually execute the given shell command (can be overridden by
1657 1668 extensions like chg)"""
1658 1669 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1659 1670
1660 1671 def traceback(self, exc=None, force=False):
1661 1672 '''print exception traceback if traceback printing enabled or forced.
1662 1673 only to call in exception handler. returns true if traceback
1663 1674 printed.'''
1664 1675 if self.tracebackflag or force:
1665 1676 if exc is None:
1666 1677 exc = sys.exc_info()
1667 1678 cause = getattr(exc[1], 'cause', None)
1668 1679
1669 1680 if cause is not None:
1670 1681 causetb = traceback.format_tb(cause[2])
1671 1682 exctb = traceback.format_tb(exc[2])
1672 1683 exconly = traceback.format_exception_only(cause[0], cause[1])
1673 1684
1674 1685 # exclude frame where 'exc' was chained and rethrown from exctb
1675 1686 self.write_err('Traceback (most recent call last):\n',
1676 1687 ''.join(exctb[:-1]),
1677 1688 ''.join(causetb),
1678 1689 ''.join(exconly))
1679 1690 else:
1680 1691 output = traceback.format_exception(exc[0], exc[1], exc[2])
1681 1692 self.write_err(encoding.strtolocal(r''.join(output)))
1682 1693 return self.tracebackflag or force
1683 1694
1684 1695 def geteditor(self):
1685 1696 '''return editor to use'''
1686 1697 if pycompat.sysplatform == 'plan9':
1687 1698 # vi is the MIPS instruction simulator on Plan 9. We
1688 1699 # instead default to E to plumb commit messages to
1689 1700 # avoid confusion.
1690 1701 editor = 'E'
1691 1702 else:
1692 1703 editor = 'vi'
1693 1704 return (encoding.environ.get("HGEDITOR") or
1694 1705 self.config("ui", "editor", editor))
1695 1706
1696 1707 @util.propertycache
1697 1708 def _progbar(self):
1698 1709 """setup the progbar singleton to the ui object"""
1699 1710 if (self.quiet or self.debugflag
1700 1711 or self.configbool('progress', 'disable')
1701 1712 or not progress.shouldprint(self)):
1702 1713 return None
1703 1714 return getprogbar(self)
1704 1715
1705 1716 def _progclear(self):
1706 1717 """clear progress bar output if any. use it before any output"""
1707 1718 if not haveprogbar(): # nothing loaded yet
1708 1719 return
1709 1720 if self._progbar is not None and self._progbar.printed:
1710 1721 self._progbar.clear()
1711 1722
1712 1723 def progress(self, topic, pos, item="", unit="", total=None):
1713 1724 '''show a progress message
1714 1725
1715 1726 By default a textual progress bar will be displayed if an operation
1716 1727 takes too long. 'topic' is the current operation, 'item' is a
1717 1728 non-numeric marker of the current position (i.e. the currently
1718 1729 in-process file), 'pos' is the current numeric position (i.e.
1719 1730 revision, bytes, etc.), unit is a corresponding unit label,
1720 1731 and total is the highest expected pos.
1721 1732
1722 1733 Multiple nested topics may be active at a time.
1723 1734
1724 1735 All topics should be marked closed by setting pos to None at
1725 1736 termination.
1726 1737 '''
1727 1738 self.deprecwarn("use ui.makeprogress() instead of ui.progress()",
1728 1739 "5.1")
1729 1740 progress = self.makeprogress(topic, unit, total)
1730 1741 if pos is not None:
1731 1742 progress.update(pos, item=item)
1732 1743 else:
1733 1744 progress.complete()
1734 1745
1735 1746 def makeprogress(self, topic, unit="", total=None):
1736 1747 """Create a progress helper for the specified topic"""
1737 1748 if getattr(self._fmsgerr, 'structured', False):
1738 1749 # channel for machine-readable output with metadata, just send
1739 1750 # raw information
1740 1751 # TODO: consider porting some useful information (e.g. estimated
1741 1752 # time) from progbar. we might want to support update delay to
1742 1753 # reduce the cost of transferring progress messages.
1743 1754 def updatebar(topic, pos, item, unit, total):
1744 1755 self._fmsgerr.write(None, type=b'progress', topic=topic,
1745 1756 pos=pos, item=item, unit=unit, total=total)
1746 1757 elif self._progbar is not None:
1747 1758 updatebar = self._progbar.progress
1748 1759 else:
1749 1760 def updatebar(topic, pos, item, unit, total):
1750 1761 pass
1751 1762 return scmutil.progress(self, updatebar, topic, unit, total)
1752 1763
1753 1764 def getlogger(self, name):
1754 1765 """Returns a logger of the given name; or None if not registered"""
1755 1766 return self._loggers.get(name)
1756 1767
1757 1768 def setlogger(self, name, logger):
1758 1769 """Install logger which can be identified later by the given name
1759 1770
1760 1771 More than one loggers can be registered. Use extension or module
1761 1772 name to uniquely identify the logger instance.
1762 1773 """
1763 1774 self._loggers[name] = logger
1764 1775
1765 1776 def log(self, event, msgfmt, *msgargs, **opts):
1766 1777 '''hook for logging facility extensions
1767 1778
1768 1779 event should be a readily-identifiable subsystem, which will
1769 1780 allow filtering.
1770 1781
1771 1782 msgfmt should be a newline-terminated format string to log, and
1772 1783 *msgargs are %-formatted into it.
1773 1784
1774 1785 **opts currently has no defined meanings.
1775 1786 '''
1776 1787 if not self._loggers:
1777 1788 return
1778 1789 activeloggers = [l for l in self._loggers.itervalues()
1779 1790 if l.tracked(event)]
1780 1791 if not activeloggers:
1781 1792 return
1782 1793 msg = msgfmt % msgargs
1783 1794 opts = pycompat.byteskwargs(opts)
1784 1795 # guard against recursion from e.g. ui.debug()
1785 1796 registeredloggers = self._loggers
1786 1797 self._loggers = {}
1787 1798 try:
1788 1799 for logger in activeloggers:
1789 1800 logger.log(self, event, msg, opts)
1790 1801 finally:
1791 1802 self._loggers = registeredloggers
1792 1803
1793 1804 def label(self, msg, label):
1794 1805 '''style msg based on supplied label
1795 1806
1796 1807 If some color mode is enabled, this will add the necessary control
1797 1808 characters to apply such color. In addition, 'debug' color mode adds
1798 1809 markup showing which label affects a piece of text.
1799 1810
1800 1811 ui.write(s, 'label') is equivalent to
1801 1812 ui.write(ui.label(s, 'label')).
1802 1813 '''
1803 1814 if self._colormode is not None:
1804 1815 return color.colorlabel(self, msg, label)
1805 1816 return msg
1806 1817
1807 1818 def develwarn(self, msg, stacklevel=1, config=None):
1808 1819 """issue a developer warning message
1809 1820
1810 1821 Use 'stacklevel' to report the offender some layers further up in the
1811 1822 stack.
1812 1823 """
1813 1824 if not self.configbool('devel', 'all-warnings'):
1814 1825 if config is None or not self.configbool('devel', config):
1815 1826 return
1816 1827 msg = 'devel-warn: ' + msg
1817 1828 stacklevel += 1 # get in develwarn
1818 1829 if self.tracebackflag:
1819 1830 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1820 1831 self.log('develwarn', '%s at:\n%s' %
1821 1832 (msg, ''.join(util.getstackframes(stacklevel))))
1822 1833 else:
1823 1834 curframe = inspect.currentframe()
1824 1835 calframe = inspect.getouterframes(curframe, 2)
1825 1836 fname, lineno, fmsg = calframe[stacklevel][1:4]
1826 1837 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1827 1838 self.write_err('%s at: %s:%d (%s)\n'
1828 1839 % (msg, fname, lineno, fmsg))
1829 1840 self.log('develwarn', '%s at: %s:%d (%s)\n',
1830 1841 msg, fname, lineno, fmsg)
1831 1842 curframe = calframe = None # avoid cycles
1832 1843
1833 1844 def deprecwarn(self, msg, version, stacklevel=2):
1834 1845 """issue a deprecation warning
1835 1846
1836 1847 - msg: message explaining what is deprecated and how to upgrade,
1837 1848 - version: last version where the API will be supported,
1838 1849 """
1839 1850 if not (self.configbool('devel', 'all-warnings')
1840 1851 or self.configbool('devel', 'deprec-warn')):
1841 1852 return
1842 1853 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1843 1854 " update your code.)") % version
1844 1855 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1845 1856
1846 1857 def exportableenviron(self):
1847 1858 """The environment variables that are safe to export, e.g. through
1848 1859 hgweb.
1849 1860 """
1850 1861 return self._exportableenviron
1851 1862
1852 1863 @contextlib.contextmanager
1853 1864 def configoverride(self, overrides, source=""):
1854 1865 """Context manager for temporary config overrides
1855 1866 `overrides` must be a dict of the following structure:
1856 1867 {(section, name) : value}"""
1857 1868 backups = {}
1858 1869 try:
1859 1870 for (section, name), value in overrides.items():
1860 1871 backups[(section, name)] = self.backupconfig(section, name)
1861 1872 self.setconfig(section, name, value, source)
1862 1873 yield
1863 1874 finally:
1864 1875 for __, backup in backups.items():
1865 1876 self.restoreconfig(backup)
1866 1877 # just restoring ui.quiet config to the previous value is not enough
1867 1878 # as it does not update ui.quiet class member
1868 1879 if ('ui', 'quiet') in overrides:
1869 1880 self.fixconfig(section='ui')
1870 1881
1871 1882 class paths(dict):
1872 1883 """Represents a collection of paths and their configs.
1873 1884
1874 1885 Data is initially derived from ui instances and the config files they have
1875 1886 loaded.
1876 1887 """
1877 1888 def __init__(self, ui):
1878 1889 dict.__init__(self)
1879 1890
1880 1891 for name, loc in ui.configitems('paths', ignoresub=True):
1881 1892 # No location is the same as not existing.
1882 1893 if not loc:
1883 1894 continue
1884 1895 loc, sub = ui.configsuboptions('paths', name)
1885 1896 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1886 1897
1887 1898 def getpath(self, name, default=None):
1888 1899 """Return a ``path`` from a string, falling back to default.
1889 1900
1890 1901 ``name`` can be a named path or locations. Locations are filesystem
1891 1902 paths or URIs.
1892 1903
1893 1904 Returns None if ``name`` is not a registered path, a URI, or a local
1894 1905 path to a repo.
1895 1906 """
1896 1907 # Only fall back to default if no path was requested.
1897 1908 if name is None:
1898 1909 if not default:
1899 1910 default = ()
1900 1911 elif not isinstance(default, (tuple, list)):
1901 1912 default = (default,)
1902 1913 for k in default:
1903 1914 try:
1904 1915 return self[k]
1905 1916 except KeyError:
1906 1917 continue
1907 1918 return None
1908 1919
1909 1920 # Most likely empty string.
1910 1921 # This may need to raise in the future.
1911 1922 if not name:
1912 1923 return None
1913 1924
1914 1925 try:
1915 1926 return self[name]
1916 1927 except KeyError:
1917 1928 # Try to resolve as a local path or URI.
1918 1929 try:
1919 1930 # We don't pass sub-options in, so no need to pass ui instance.
1920 1931 return path(None, None, rawloc=name)
1921 1932 except ValueError:
1922 1933 raise error.RepoError(_('repository %s does not exist') %
1923 1934 name)
1924 1935
1925 1936 _pathsuboptions = {}
1926 1937
1927 1938 def pathsuboption(option, attr):
1928 1939 """Decorator used to declare a path sub-option.
1929 1940
1930 1941 Arguments are the sub-option name and the attribute it should set on
1931 1942 ``path`` instances.
1932 1943
1933 1944 The decorated function will receive as arguments a ``ui`` instance,
1934 1945 ``path`` instance, and the string value of this option from the config.
1935 1946 The function should return the value that will be set on the ``path``
1936 1947 instance.
1937 1948
1938 1949 This decorator can be used to perform additional verification of
1939 1950 sub-options and to change the type of sub-options.
1940 1951 """
1941 1952 def register(func):
1942 1953 _pathsuboptions[option] = (attr, func)
1943 1954 return func
1944 1955 return register
1945 1956
1946 1957 @pathsuboption('pushurl', 'pushloc')
1947 1958 def pushurlpathoption(ui, path, value):
1948 1959 u = util.url(value)
1949 1960 # Actually require a URL.
1950 1961 if not u.scheme:
1951 1962 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1952 1963 return None
1953 1964
1954 1965 # Don't support the #foo syntax in the push URL to declare branch to
1955 1966 # push.
1956 1967 if u.fragment:
1957 1968 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1958 1969 'ignoring)\n') % path.name)
1959 1970 u.fragment = None
1960 1971
1961 1972 return bytes(u)
1962 1973
1963 1974 @pathsuboption('pushrev', 'pushrev')
1964 1975 def pushrevpathoption(ui, path, value):
1965 1976 return value
1966 1977
1967 1978 class path(object):
1968 1979 """Represents an individual path and its configuration."""
1969 1980
1970 1981 def __init__(self, ui, name, rawloc=None, suboptions=None):
1971 1982 """Construct a path from its config options.
1972 1983
1973 1984 ``ui`` is the ``ui`` instance the path is coming from.
1974 1985 ``name`` is the symbolic name of the path.
1975 1986 ``rawloc`` is the raw location, as defined in the config.
1976 1987 ``pushloc`` is the raw locations pushes should be made to.
1977 1988
1978 1989 If ``name`` is not defined, we require that the location be a) a local
1979 1990 filesystem path with a .hg directory or b) a URL. If not,
1980 1991 ``ValueError`` is raised.
1981 1992 """
1982 1993 if not rawloc:
1983 1994 raise ValueError('rawloc must be defined')
1984 1995
1985 1996 # Locations may define branches via syntax <base>#<branch>.
1986 1997 u = util.url(rawloc)
1987 1998 branch = None
1988 1999 if u.fragment:
1989 2000 branch = u.fragment
1990 2001 u.fragment = None
1991 2002
1992 2003 self.url = u
1993 2004 self.branch = branch
1994 2005
1995 2006 self.name = name
1996 2007 self.rawloc = rawloc
1997 2008 self.loc = '%s' % u
1998 2009
1999 2010 # When given a raw location but not a symbolic name, validate the
2000 2011 # location is valid.
2001 2012 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2002 2013 raise ValueError('location is not a URL or path to a local '
2003 2014 'repo: %s' % rawloc)
2004 2015
2005 2016 suboptions = suboptions or {}
2006 2017
2007 2018 # Now process the sub-options. If a sub-option is registered, its
2008 2019 # attribute will always be present. The value will be None if there
2009 2020 # was no valid sub-option.
2010 2021 for suboption, (attr, func) in _pathsuboptions.iteritems():
2011 2022 if suboption not in suboptions:
2012 2023 setattr(self, attr, None)
2013 2024 continue
2014 2025
2015 2026 value = func(ui, self, suboptions[suboption])
2016 2027 setattr(self, attr, value)
2017 2028
2018 2029 def _isvalidlocalpath(self, path):
2019 2030 """Returns True if the given path is a potentially valid repository.
2020 2031 This is its own function so that extensions can change the definition of
2021 2032 'valid' in this case (like when pulling from a git repo into a hg
2022 2033 one)."""
2023 2034 return os.path.isdir(os.path.join(path, '.hg'))
2024 2035
2025 2036 @property
2026 2037 def suboptions(self):
2027 2038 """Return sub-options and their values for this path.
2028 2039
2029 2040 This is intended to be used for presentation purposes.
2030 2041 """
2031 2042 d = {}
2032 2043 for subopt, (attr, _func) in _pathsuboptions.iteritems():
2033 2044 value = getattr(self, attr)
2034 2045 if value is not None:
2035 2046 d[subopt] = value
2036 2047 return d
2037 2048
2038 2049 # we instantiate one globally shared progress bar to avoid
2039 2050 # competing progress bars when multiple UI objects get created
2040 2051 _progresssingleton = None
2041 2052
2042 2053 def getprogbar(ui):
2043 2054 global _progresssingleton
2044 2055 if _progresssingleton is None:
2045 2056 # passing 'ui' object to the singleton is fishy,
2046 2057 # this is how the extension used to work but feel free to rework it.
2047 2058 _progresssingleton = progress.progbar(ui)
2048 2059 return _progresssingleton
2049 2060
2050 2061 def haveprogbar():
2051 2062 return _progresssingleton is not None
2052 2063
2053 2064 def _selectmsgdests(ui):
2054 2065 name = ui.config(b'ui', b'message-output')
2055 2066 if name == b'channel':
2056 2067 if ui.fmsg:
2057 2068 return ui.fmsg, ui.fmsg
2058 2069 else:
2059 2070 # fall back to ferr if channel isn't ready so that status/error
2060 2071 # messages can be printed
2061 2072 return ui.ferr, ui.ferr
2062 2073 if name == b'stdio':
2063 2074 return ui.fout, ui.ferr
2064 2075 if name == b'stderr':
2065 2076 return ui.ferr, ui.ferr
2066 2077 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2067 2078
2068 2079 def _writemsgwith(write, dest, *args, **opts):
2069 2080 """Write ui message with the given ui._write*() function
2070 2081
2071 2082 The specified message type is translated to 'ui.<type>' label if the dest
2072 2083 isn't a structured channel, so that the message will be colorized.
2073 2084 """
2074 2085 # TODO: maybe change 'type' to a mandatory option
2075 2086 if r'type' in opts and not getattr(dest, 'structured', False):
2076 2087 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2077 2088 write(dest, *args, **opts)
General Comments 0
You need to be logged in to leave comments. Login now