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