##// END OF EJS Templates
tweakdefaults: set ui.relative-paths instead of command.status.relative...
Martin von Zweigbergk -
r41634:97ab4cbb default
parent child Browse files
Show More
@@ -1,2112 +1,2112 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 # Make compatible commands emit cwd-relative paths by default.
62 relative-paths = yes
61 63
62 64 [commands]
63 65 # Grep working directory by default.
64 66 grep.all-files = True
65 # Make `hg status` emit cwd-relative paths by default.
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 break
570 570
571 571 if self.debugflag and not untrusted and self._reportuntrusted:
572 572 for s, n in alternates:
573 573 uvalue = self._ucfg.get(s, n)
574 574 if uvalue is not None and uvalue != value:
575 575 self.debug("ignoring untrusted configuration option "
576 576 "%s.%s = %s\n" % (s, n, uvalue))
577 577 return value
578 578
579 579 def configsuboptions(self, section, name, default=_unset, untrusted=False):
580 580 """Get a config option and all sub-options.
581 581
582 582 Some config options have sub-options that are declared with the
583 583 format "key:opt = value". This method is used to return the main
584 584 option and all its declared sub-options.
585 585
586 586 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
587 587 is a dict of defined sub-options where keys and values are strings.
588 588 """
589 589 main = self.config(section, name, default, untrusted=untrusted)
590 590 data = self._data(untrusted)
591 591 sub = {}
592 592 prefix = '%s:' % name
593 593 for k, v in data.items(section):
594 594 if k.startswith(prefix):
595 595 sub[k[len(prefix):]] = v
596 596
597 597 if self.debugflag and not untrusted and self._reportuntrusted:
598 598 for k, v in sub.items():
599 599 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
600 600 if uvalue is not None and uvalue != v:
601 601 self.debug('ignoring untrusted configuration option '
602 602 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
603 603
604 604 return main, sub
605 605
606 606 def configpath(self, section, name, default=_unset, untrusted=False):
607 607 'get a path config item, expanded relative to repo root or config file'
608 608 v = self.config(section, name, default, untrusted)
609 609 if v is None:
610 610 return None
611 611 if not os.path.isabs(v) or "://" not in v:
612 612 src = self.configsource(section, name, untrusted)
613 613 if ':' in src:
614 614 base = os.path.dirname(src.rsplit(':')[0])
615 615 v = os.path.join(base, os.path.expanduser(v))
616 616 return v
617 617
618 618 def configbool(self, section, name, default=_unset, untrusted=False):
619 619 """parse a configuration element as a boolean
620 620
621 621 >>> u = ui(); s = b'foo'
622 622 >>> u.setconfig(s, b'true', b'yes')
623 623 >>> u.configbool(s, b'true')
624 624 True
625 625 >>> u.setconfig(s, b'false', b'no')
626 626 >>> u.configbool(s, b'false')
627 627 False
628 628 >>> u.configbool(s, b'unknown')
629 629 False
630 630 >>> u.configbool(s, b'unknown', True)
631 631 True
632 632 >>> u.setconfig(s, b'invalid', b'somevalue')
633 633 >>> u.configbool(s, b'invalid')
634 634 Traceback (most recent call last):
635 635 ...
636 636 ConfigError: foo.invalid is not a boolean ('somevalue')
637 637 """
638 638
639 639 v = self._config(section, name, default, untrusted=untrusted)
640 640 if v is None:
641 641 return v
642 642 if v is _unset:
643 643 if default is _unset:
644 644 return False
645 645 return default
646 646 if isinstance(v, bool):
647 647 return v
648 648 b = stringutil.parsebool(v)
649 649 if b is None:
650 650 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
651 651 % (section, name, v))
652 652 return b
653 653
654 654 def configwith(self, convert, section, name, default=_unset,
655 655 desc=None, untrusted=False):
656 656 """parse a configuration element with a conversion function
657 657
658 658 >>> u = ui(); s = b'foo'
659 659 >>> u.setconfig(s, b'float1', b'42')
660 660 >>> u.configwith(float, s, b'float1')
661 661 42.0
662 662 >>> u.setconfig(s, b'float2', b'-4.25')
663 663 >>> u.configwith(float, s, b'float2')
664 664 -4.25
665 665 >>> u.configwith(float, s, b'unknown', 7)
666 666 7.0
667 667 >>> u.setconfig(s, b'invalid', b'somevalue')
668 668 >>> u.configwith(float, s, b'invalid')
669 669 Traceback (most recent call last):
670 670 ...
671 671 ConfigError: foo.invalid is not a valid float ('somevalue')
672 672 >>> u.configwith(float, s, b'invalid', desc=b'womble')
673 673 Traceback (most recent call last):
674 674 ...
675 675 ConfigError: foo.invalid is not a valid womble ('somevalue')
676 676 """
677 677
678 678 v = self.config(section, name, default, untrusted)
679 679 if v is None:
680 680 return v # do not attempt to convert None
681 681 try:
682 682 return convert(v)
683 683 except (ValueError, error.ParseError):
684 684 if desc is None:
685 685 desc = pycompat.sysbytes(convert.__name__)
686 686 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
687 687 % (section, name, desc, v))
688 688
689 689 def configint(self, section, name, default=_unset, untrusted=False):
690 690 """parse a configuration element as an integer
691 691
692 692 >>> u = ui(); s = b'foo'
693 693 >>> u.setconfig(s, b'int1', b'42')
694 694 >>> u.configint(s, b'int1')
695 695 42
696 696 >>> u.setconfig(s, b'int2', b'-42')
697 697 >>> u.configint(s, b'int2')
698 698 -42
699 699 >>> u.configint(s, b'unknown', 7)
700 700 7
701 701 >>> u.setconfig(s, b'invalid', b'somevalue')
702 702 >>> u.configint(s, b'invalid')
703 703 Traceback (most recent call last):
704 704 ...
705 705 ConfigError: foo.invalid is not a valid integer ('somevalue')
706 706 """
707 707
708 708 return self.configwith(int, section, name, default, 'integer',
709 709 untrusted)
710 710
711 711 def configbytes(self, section, name, default=_unset, untrusted=False):
712 712 """parse a configuration element as a quantity in bytes
713 713
714 714 Units can be specified as b (bytes), k or kb (kilobytes), m or
715 715 mb (megabytes), g or gb (gigabytes).
716 716
717 717 >>> u = ui(); s = b'foo'
718 718 >>> u.setconfig(s, b'val1', b'42')
719 719 >>> u.configbytes(s, b'val1')
720 720 42
721 721 >>> u.setconfig(s, b'val2', b'42.5 kb')
722 722 >>> u.configbytes(s, b'val2')
723 723 43520
724 724 >>> u.configbytes(s, b'unknown', b'7 MB')
725 725 7340032
726 726 >>> u.setconfig(s, b'invalid', b'somevalue')
727 727 >>> u.configbytes(s, b'invalid')
728 728 Traceback (most recent call last):
729 729 ...
730 730 ConfigError: foo.invalid is not a byte quantity ('somevalue')
731 731 """
732 732
733 733 value = self._config(section, name, default, untrusted)
734 734 if value is _unset:
735 735 if default is _unset:
736 736 default = 0
737 737 value = default
738 738 if not isinstance(value, bytes):
739 739 return value
740 740 try:
741 741 return util.sizetoint(value)
742 742 except error.ParseError:
743 743 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
744 744 % (section, name, value))
745 745
746 746 def configlist(self, section, name, default=_unset, untrusted=False):
747 747 """parse a configuration element as a list of comma/space separated
748 748 strings
749 749
750 750 >>> u = ui(); s = b'foo'
751 751 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
752 752 >>> u.configlist(s, b'list1')
753 753 ['this', 'is', 'a small', 'test']
754 754 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
755 755 >>> u.configlist(s, b'list2')
756 756 ['this', 'is', 'a small', 'test']
757 757 """
758 758 # default is not always a list
759 759 v = self.configwith(config.parselist, section, name, default,
760 760 'list', untrusted)
761 761 if isinstance(v, bytes):
762 762 return config.parselist(v)
763 763 elif v is None:
764 764 return []
765 765 return v
766 766
767 767 def configdate(self, section, name, default=_unset, untrusted=False):
768 768 """parse a configuration element as a tuple of ints
769 769
770 770 >>> u = ui(); s = b'foo'
771 771 >>> u.setconfig(s, b'date', b'0 0')
772 772 >>> u.configdate(s, b'date')
773 773 (0, 0)
774 774 """
775 775 if self.config(section, name, default, untrusted):
776 776 return self.configwith(dateutil.parsedate, section, name, default,
777 777 'date', untrusted)
778 778 if default is _unset:
779 779 return None
780 780 return default
781 781
782 782 def hasconfig(self, section, name, untrusted=False):
783 783 return self._data(untrusted).hasitem(section, name)
784 784
785 785 def has_section(self, section, untrusted=False):
786 786 '''tell whether section exists in config.'''
787 787 return section in self._data(untrusted)
788 788
789 789 def configitems(self, section, untrusted=False, ignoresub=False):
790 790 items = self._data(untrusted).items(section)
791 791 if ignoresub:
792 792 items = [i for i in items if ':' not in i[0]]
793 793 if self.debugflag and not untrusted and self._reportuntrusted:
794 794 for k, v in self._ucfg.items(section):
795 795 if self._tcfg.get(section, k) != v:
796 796 self.debug("ignoring untrusted configuration option "
797 797 "%s.%s = %s\n" % (section, k, v))
798 798 return items
799 799
800 800 def walkconfig(self, untrusted=False):
801 801 cfg = self._data(untrusted)
802 802 for section in cfg.sections():
803 803 for name, value in self.configitems(section, untrusted):
804 804 yield section, name, value
805 805
806 806 def plain(self, feature=None):
807 807 '''is plain mode active?
808 808
809 809 Plain mode means that all configuration variables which affect
810 810 the behavior and output of Mercurial should be
811 811 ignored. Additionally, the output should be stable,
812 812 reproducible and suitable for use in scripts or applications.
813 813
814 814 The only way to trigger plain mode is by setting either the
815 815 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
816 816
817 817 The return value can either be
818 818 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
819 819 - False if feature is disabled by default and not included in HGPLAIN
820 820 - True otherwise
821 821 '''
822 822 if ('HGPLAIN' not in encoding.environ and
823 823 'HGPLAINEXCEPT' not in encoding.environ):
824 824 return False
825 825 exceptions = encoding.environ.get('HGPLAINEXCEPT',
826 826 '').strip().split(',')
827 827 # TODO: add support for HGPLAIN=+feature,-feature syntax
828 828 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
829 829 exceptions.append('strictflags')
830 830 if feature and exceptions:
831 831 return feature not in exceptions
832 832 return True
833 833
834 834 def username(self, acceptempty=False):
835 835 """Return default username to be used in commits.
836 836
837 837 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
838 838 and stop searching if one of these is set.
839 839 If not found and acceptempty is True, returns None.
840 840 If not found and ui.askusername is True, ask the user, else use
841 841 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
842 842 If no username could be found, raise an Abort error.
843 843 """
844 844 user = encoding.environ.get("HGUSER")
845 845 if user is None:
846 846 user = self.config("ui", "username")
847 847 if user is not None:
848 848 user = os.path.expandvars(user)
849 849 if user is None:
850 850 user = encoding.environ.get("EMAIL")
851 851 if user is None and acceptempty:
852 852 return user
853 853 if user is None and self.configbool("ui", "askusername"):
854 854 user = self.prompt(_("enter a commit username:"), default=None)
855 855 if user is None and not self.interactive():
856 856 try:
857 857 user = '%s@%s' % (procutil.getuser(),
858 858 encoding.strtolocal(socket.getfqdn()))
859 859 self.warn(_("no username found, using '%s' instead\n") % user)
860 860 except KeyError:
861 861 pass
862 862 if not user:
863 863 raise error.Abort(_('no username supplied'),
864 864 hint=_("use 'hg config --edit' "
865 865 'to set your username'))
866 866 if "\n" in user:
867 867 raise error.Abort(_("username %r contains a newline\n")
868 868 % pycompat.bytestr(user))
869 869 return user
870 870
871 871 def shortuser(self, user):
872 872 """Return a short representation of a user name or email address."""
873 873 if not self.verbose:
874 874 user = stringutil.shortuser(user)
875 875 return user
876 876
877 877 def expandpath(self, loc, default=None):
878 878 """Return repository location relative to cwd or from [paths]"""
879 879 try:
880 880 p = self.paths.getpath(loc)
881 881 if p:
882 882 return p.rawloc
883 883 except error.RepoError:
884 884 pass
885 885
886 886 if default:
887 887 try:
888 888 p = self.paths.getpath(default)
889 889 if p:
890 890 return p.rawloc
891 891 except error.RepoError:
892 892 pass
893 893
894 894 return loc
895 895
896 896 @util.propertycache
897 897 def paths(self):
898 898 return paths(self)
899 899
900 900 @property
901 901 def fout(self):
902 902 return self._fout
903 903
904 904 @fout.setter
905 905 def fout(self, f):
906 906 self._fout = f
907 907 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
908 908
909 909 @property
910 910 def ferr(self):
911 911 return self._ferr
912 912
913 913 @ferr.setter
914 914 def ferr(self, f):
915 915 self._ferr = f
916 916 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
917 917
918 918 @property
919 919 def fin(self):
920 920 return self._fin
921 921
922 922 @fin.setter
923 923 def fin(self, f):
924 924 self._fin = f
925 925
926 926 @property
927 927 def fmsg(self):
928 928 """Stream dedicated for status/error messages; may be None if
929 929 fout/ferr are used"""
930 930 return self._fmsg
931 931
932 932 @fmsg.setter
933 933 def fmsg(self, f):
934 934 self._fmsg = f
935 935 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
936 936
937 937 def pushbuffer(self, error=False, subproc=False, labeled=False):
938 938 """install a buffer to capture standard output of the ui object
939 939
940 940 If error is True, the error output will be captured too.
941 941
942 942 If subproc is True, output from subprocesses (typically hooks) will be
943 943 captured too.
944 944
945 945 If labeled is True, any labels associated with buffered
946 946 output will be handled. By default, this has no effect
947 947 on the output returned, but extensions and GUI tools may
948 948 handle this argument and returned styled output. If output
949 949 is being buffered so it can be captured and parsed or
950 950 processed, labeled should not be set to True.
951 951 """
952 952 self._buffers.append([])
953 953 self._bufferstates.append((error, subproc, labeled))
954 954 self._bufferapplylabels = labeled
955 955
956 956 def popbuffer(self):
957 957 '''pop the last buffer and return the buffered output'''
958 958 self._bufferstates.pop()
959 959 if self._bufferstates:
960 960 self._bufferapplylabels = self._bufferstates[-1][2]
961 961 else:
962 962 self._bufferapplylabels = None
963 963
964 964 return "".join(self._buffers.pop())
965 965
966 966 def _isbuffered(self, dest):
967 967 if dest is self._fout:
968 968 return bool(self._buffers)
969 969 if dest is self._ferr:
970 970 return bool(self._bufferstates and self._bufferstates[-1][0])
971 971 return False
972 972
973 973 def canwritewithoutlabels(self):
974 974 '''check if write skips the label'''
975 975 if self._buffers and not self._bufferapplylabels:
976 976 return True
977 977 return self._colormode is None
978 978
979 979 def canbatchlabeledwrites(self):
980 980 '''check if write calls with labels are batchable'''
981 981 # Windows color printing is special, see ``write``.
982 982 return self._colormode != 'win32'
983 983
984 984 def write(self, *args, **opts):
985 985 '''write args to output
986 986
987 987 By default, this method simply writes to the buffer or stdout.
988 988 Color mode can be set on the UI class to have the output decorated
989 989 with color modifier before being written to stdout.
990 990
991 991 The color used is controlled by an optional keyword argument, "label".
992 992 This should be a string containing label names separated by space.
993 993 Label names take the form of "topic.type". For example, ui.debug()
994 994 issues a label of "ui.debug".
995 995
996 996 When labeling output for a specific command, a label of
997 997 "cmdname.type" is recommended. For example, status issues
998 998 a label of "status.modified" for modified files.
999 999 '''
1000 1000 dest = self._fout
1001 1001
1002 1002 # inlined _write() for speed
1003 1003 if self._buffers:
1004 1004 label = opts.get(r'label', '')
1005 1005 if label and self._bufferapplylabels:
1006 1006 self._buffers[-1].extend(self.label(a, label) for a in args)
1007 1007 else:
1008 1008 self._buffers[-1].extend(args)
1009 1009 return
1010 1010
1011 1011 # inliend _writenobuf() for speed
1012 1012 self._progclear()
1013 1013 msg = b''.join(args)
1014 1014
1015 1015 # opencode timeblockedsection because this is a critical path
1016 1016 starttime = util.timer()
1017 1017 try:
1018 1018 if self._colormode == 'win32':
1019 1019 # windows color printing is its own can of crab, defer to
1020 1020 # the color module and that is it.
1021 1021 color.win32print(self, dest.write, msg, **opts)
1022 1022 else:
1023 1023 if self._colormode is not None:
1024 1024 label = opts.get(r'label', '')
1025 1025 msg = self.label(msg, label)
1026 1026 dest.write(msg)
1027 1027 except IOError as err:
1028 1028 raise error.StdioError(err)
1029 1029 finally:
1030 1030 self._blockedtimes['stdio_blocked'] += \
1031 1031 (util.timer() - starttime) * 1000
1032 1032
1033 1033 def write_err(self, *args, **opts):
1034 1034 self._write(self._ferr, *args, **opts)
1035 1035
1036 1036 def _write(self, dest, *args, **opts):
1037 1037 # update write() as well if you touch this code
1038 1038 if self._isbuffered(dest):
1039 1039 label = opts.get(r'label', '')
1040 1040 if label and self._bufferapplylabels:
1041 1041 self._buffers[-1].extend(self.label(a, label) for a in args)
1042 1042 else:
1043 1043 self._buffers[-1].extend(args)
1044 1044 else:
1045 1045 self._writenobuf(dest, *args, **opts)
1046 1046
1047 1047 def _writenobuf(self, dest, *args, **opts):
1048 1048 # update write() as well if you touch this code
1049 1049 self._progclear()
1050 1050 msg = b''.join(args)
1051 1051
1052 1052 # opencode timeblockedsection because this is a critical path
1053 1053 starttime = util.timer()
1054 1054 try:
1055 1055 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1056 1056 self._fout.flush()
1057 1057 if getattr(dest, 'structured', False):
1058 1058 # channel for machine-readable output with metadata, where
1059 1059 # no extra colorization is necessary.
1060 1060 dest.write(msg, **opts)
1061 1061 elif self._colormode == 'win32':
1062 1062 # windows color printing is its own can of crab, defer to
1063 1063 # the color module and that is it.
1064 1064 color.win32print(self, dest.write, msg, **opts)
1065 1065 else:
1066 1066 if self._colormode is not None:
1067 1067 label = opts.get(r'label', '')
1068 1068 msg = self.label(msg, label)
1069 1069 dest.write(msg)
1070 1070 # stderr may be buffered under win32 when redirected to files,
1071 1071 # including stdout.
1072 1072 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1073 1073 dest.flush()
1074 1074 except IOError as err:
1075 1075 if (dest is self._ferr
1076 1076 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1077 1077 # no way to report the error, so ignore it
1078 1078 return
1079 1079 raise error.StdioError(err)
1080 1080 finally:
1081 1081 self._blockedtimes['stdio_blocked'] += \
1082 1082 (util.timer() - starttime) * 1000
1083 1083
1084 1084 def _writemsg(self, dest, *args, **opts):
1085 1085 _writemsgwith(self._write, dest, *args, **opts)
1086 1086
1087 1087 def _writemsgnobuf(self, dest, *args, **opts):
1088 1088 _writemsgwith(self._writenobuf, dest, *args, **opts)
1089 1089
1090 1090 def flush(self):
1091 1091 # opencode timeblockedsection because this is a critical path
1092 1092 starttime = util.timer()
1093 1093 try:
1094 1094 try:
1095 1095 self._fout.flush()
1096 1096 except IOError as err:
1097 1097 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1098 1098 raise error.StdioError(err)
1099 1099 finally:
1100 1100 try:
1101 1101 self._ferr.flush()
1102 1102 except IOError as err:
1103 1103 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1104 1104 raise error.StdioError(err)
1105 1105 finally:
1106 1106 self._blockedtimes['stdio_blocked'] += \
1107 1107 (util.timer() - starttime) * 1000
1108 1108
1109 1109 def _isatty(self, fh):
1110 1110 if self.configbool('ui', 'nontty'):
1111 1111 return False
1112 1112 return procutil.isatty(fh)
1113 1113
1114 1114 def protectfinout(self):
1115 1115 """Duplicate ui streams and redirect original if they are stdio
1116 1116
1117 1117 Returns (fin, fout) which point to the original ui fds, but may be
1118 1118 copy of them. The returned streams can be considered "owned" in that
1119 1119 print(), exec(), etc. never reach to them.
1120 1120 """
1121 1121 if self._finoutredirected:
1122 1122 # if already redirected, protectstdio() would just create another
1123 1123 # nullfd pair, which is equivalent to returning self._fin/_fout.
1124 1124 return self._fin, self._fout
1125 1125 fin, fout = procutil.protectstdio(self._fin, self._fout)
1126 1126 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1127 1127 return fin, fout
1128 1128
1129 1129 def restorefinout(self, fin, fout):
1130 1130 """Restore ui streams from possibly duplicated (fin, fout)"""
1131 1131 if (fin, fout) == (self._fin, self._fout):
1132 1132 return
1133 1133 procutil.restorestdio(self._fin, self._fout, fin, fout)
1134 1134 # protectfinout() won't create more than one duplicated streams,
1135 1135 # so we can just turn the redirection flag off.
1136 1136 self._finoutredirected = False
1137 1137
1138 1138 @contextlib.contextmanager
1139 1139 def protectedfinout(self):
1140 1140 """Run code block with protected standard streams"""
1141 1141 fin, fout = self.protectfinout()
1142 1142 try:
1143 1143 yield fin, fout
1144 1144 finally:
1145 1145 self.restorefinout(fin, fout)
1146 1146
1147 1147 def disablepager(self):
1148 1148 self._disablepager = True
1149 1149
1150 1150 def pager(self, command):
1151 1151 """Start a pager for subsequent command output.
1152 1152
1153 1153 Commands which produce a long stream of output should call
1154 1154 this function to activate the user's preferred pagination
1155 1155 mechanism (which may be no pager). Calling this function
1156 1156 precludes any future use of interactive functionality, such as
1157 1157 prompting the user or activating curses.
1158 1158
1159 1159 Args:
1160 1160 command: The full, non-aliased name of the command. That is, "log"
1161 1161 not "history, "summary" not "summ", etc.
1162 1162 """
1163 1163 if (self._disablepager
1164 1164 or self.pageractive):
1165 1165 # how pager should do is already determined
1166 1166 return
1167 1167
1168 1168 if not command.startswith('internal-always-') and (
1169 1169 # explicit --pager=on (= 'internal-always-' prefix) should
1170 1170 # take precedence over disabling factors below
1171 1171 command in self.configlist('pager', 'ignore')
1172 1172 or not self.configbool('ui', 'paginate')
1173 1173 or not self.configbool('pager', 'attend-' + command, True)
1174 1174 or encoding.environ.get('TERM') == 'dumb'
1175 1175 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1176 1176 # formatted() will need some adjustment.
1177 1177 or not self.formatted()
1178 1178 or self.plain()
1179 1179 or self._buffers
1180 1180 # TODO: expose debugger-enabled on the UI object
1181 1181 or '--debugger' in pycompat.sysargv):
1182 1182 # We only want to paginate if the ui appears to be
1183 1183 # interactive, the user didn't say HGPLAIN or
1184 1184 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1185 1185 return
1186 1186
1187 1187 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1188 1188 if not pagercmd:
1189 1189 return
1190 1190
1191 1191 pagerenv = {}
1192 1192 for name, value in rcutil.defaultpagerenv().items():
1193 1193 if name not in encoding.environ:
1194 1194 pagerenv[name] = value
1195 1195
1196 1196 self.debug('starting pager for command %s\n' %
1197 1197 stringutil.pprint(command))
1198 1198 self.flush()
1199 1199
1200 1200 wasformatted = self.formatted()
1201 1201 if util.safehasattr(signal, "SIGPIPE"):
1202 1202 signal.signal(signal.SIGPIPE, _catchterm)
1203 1203 if self._runpager(pagercmd, pagerenv):
1204 1204 self.pageractive = True
1205 1205 # Preserve the formatted-ness of the UI. This is important
1206 1206 # because we mess with stdout, which might confuse
1207 1207 # auto-detection of things being formatted.
1208 1208 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1209 1209 self.setconfig('ui', 'interactive', False, 'pager')
1210 1210
1211 1211 # If pagermode differs from color.mode, reconfigure color now that
1212 1212 # pageractive is set.
1213 1213 cm = self._colormode
1214 1214 if cm != self.config('color', 'pagermode', cm):
1215 1215 color.setup(self)
1216 1216 else:
1217 1217 # If the pager can't be spawned in dispatch when --pager=on is
1218 1218 # given, don't try again when the command runs, to avoid a duplicate
1219 1219 # warning about a missing pager command.
1220 1220 self.disablepager()
1221 1221
1222 1222 def _runpager(self, command, env=None):
1223 1223 """Actually start the pager and set up file descriptors.
1224 1224
1225 1225 This is separate in part so that extensions (like chg) can
1226 1226 override how a pager is invoked.
1227 1227 """
1228 1228 if command == 'cat':
1229 1229 # Save ourselves some work.
1230 1230 return False
1231 1231 # If the command doesn't contain any of these characters, we
1232 1232 # assume it's a binary and exec it directly. This means for
1233 1233 # simple pager command configurations, we can degrade
1234 1234 # gracefully and tell the user about their broken pager.
1235 1235 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1236 1236
1237 1237 if pycompat.iswindows and not shell:
1238 1238 # Window's built-in `more` cannot be invoked with shell=False, but
1239 1239 # its `more.com` can. Hide this implementation detail from the
1240 1240 # user so we can also get sane bad PAGER behavior. MSYS has
1241 1241 # `more.exe`, so do a cmd.exe style resolution of the executable to
1242 1242 # determine which one to use.
1243 1243 fullcmd = procutil.findexe(command)
1244 1244 if not fullcmd:
1245 1245 self.warn(_("missing pager command '%s', skipping pager\n")
1246 1246 % command)
1247 1247 return False
1248 1248
1249 1249 command = fullcmd
1250 1250
1251 1251 try:
1252 1252 pager = subprocess.Popen(
1253 1253 procutil.tonativestr(command), shell=shell, bufsize=-1,
1254 1254 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1255 1255 stdout=procutil.stdout, stderr=procutil.stderr,
1256 1256 env=procutil.tonativeenv(procutil.shellenviron(env)))
1257 1257 except OSError as e:
1258 1258 if e.errno == errno.ENOENT and not shell:
1259 1259 self.warn(_("missing pager command '%s', skipping pager\n")
1260 1260 % command)
1261 1261 return False
1262 1262 raise
1263 1263
1264 1264 # back up original file descriptors
1265 1265 stdoutfd = os.dup(procutil.stdout.fileno())
1266 1266 stderrfd = os.dup(procutil.stderr.fileno())
1267 1267
1268 1268 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1269 1269 if self._isatty(procutil.stderr):
1270 1270 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1271 1271
1272 1272 @self.atexit
1273 1273 def killpager():
1274 1274 if util.safehasattr(signal, "SIGINT"):
1275 1275 signal.signal(signal.SIGINT, signal.SIG_IGN)
1276 1276 # restore original fds, closing pager.stdin copies in the process
1277 1277 os.dup2(stdoutfd, procutil.stdout.fileno())
1278 1278 os.dup2(stderrfd, procutil.stderr.fileno())
1279 1279 pager.stdin.close()
1280 1280 pager.wait()
1281 1281
1282 1282 return True
1283 1283
1284 1284 @property
1285 1285 def _exithandlers(self):
1286 1286 return _reqexithandlers
1287 1287
1288 1288 def atexit(self, func, *args, **kwargs):
1289 1289 '''register a function to run after dispatching a request
1290 1290
1291 1291 Handlers do not stay registered across request boundaries.'''
1292 1292 self._exithandlers.append((func, args, kwargs))
1293 1293 return func
1294 1294
1295 1295 def interface(self, feature):
1296 1296 """what interface to use for interactive console features?
1297 1297
1298 1298 The interface is controlled by the value of `ui.interface` but also by
1299 1299 the value of feature-specific configuration. For example:
1300 1300
1301 1301 ui.interface.histedit = text
1302 1302 ui.interface.chunkselector = curses
1303 1303
1304 1304 Here the features are "histedit" and "chunkselector".
1305 1305
1306 1306 The configuration above means that the default interfaces for commands
1307 1307 is curses, the interface for histedit is text and the interface for
1308 1308 selecting chunk is crecord (the best curses interface available).
1309 1309
1310 1310 Consider the following example:
1311 1311 ui.interface = curses
1312 1312 ui.interface.histedit = text
1313 1313
1314 1314 Then histedit will use the text interface and chunkselector will use
1315 1315 the default curses interface (crecord at the moment).
1316 1316 """
1317 1317 alldefaults = frozenset(["text", "curses"])
1318 1318
1319 1319 featureinterfaces = {
1320 1320 "chunkselector": [
1321 1321 "text",
1322 1322 "curses",
1323 1323 ],
1324 1324 "histedit": [
1325 1325 "text",
1326 1326 "curses",
1327 1327 ],
1328 1328 }
1329 1329
1330 1330 # Feature-specific interface
1331 1331 if feature not in featureinterfaces.keys():
1332 1332 # Programming error, not user error
1333 1333 raise ValueError("Unknown feature requested %s" % feature)
1334 1334
1335 1335 availableinterfaces = frozenset(featureinterfaces[feature])
1336 1336 if alldefaults > availableinterfaces:
1337 1337 # Programming error, not user error. We need a use case to
1338 1338 # define the right thing to do here.
1339 1339 raise ValueError(
1340 1340 "Feature %s does not handle all default interfaces" %
1341 1341 feature)
1342 1342
1343 1343 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1344 1344 return "text"
1345 1345
1346 1346 # Default interface for all the features
1347 1347 defaultinterface = "text"
1348 1348 i = self.config("ui", "interface")
1349 1349 if i in alldefaults:
1350 1350 defaultinterface = i
1351 1351
1352 1352 choseninterface = defaultinterface
1353 1353 f = self.config("ui", "interface.%s" % feature)
1354 1354 if f in availableinterfaces:
1355 1355 choseninterface = f
1356 1356
1357 1357 if i is not None and defaultinterface != i:
1358 1358 if f is not None:
1359 1359 self.warn(_("invalid value for ui.interface: %s\n") %
1360 1360 (i,))
1361 1361 else:
1362 1362 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1363 1363 (i, choseninterface))
1364 1364 if f is not None and choseninterface != f:
1365 1365 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1366 1366 (feature, f, choseninterface))
1367 1367
1368 1368 return choseninterface
1369 1369
1370 1370 def interactive(self):
1371 1371 '''is interactive input allowed?
1372 1372
1373 1373 An interactive session is a session where input can be reasonably read
1374 1374 from `sys.stdin'. If this function returns false, any attempt to read
1375 1375 from stdin should fail with an error, unless a sensible default has been
1376 1376 specified.
1377 1377
1378 1378 Interactiveness is triggered by the value of the `ui.interactive'
1379 1379 configuration variable or - if it is unset - when `sys.stdin' points
1380 1380 to a terminal device.
1381 1381
1382 1382 This function refers to input only; for output, see `ui.formatted()'.
1383 1383 '''
1384 1384 i = self.configbool("ui", "interactive")
1385 1385 if i is None:
1386 1386 # some environments replace stdin without implementing isatty
1387 1387 # usually those are non-interactive
1388 1388 return self._isatty(self._fin)
1389 1389
1390 1390 return i
1391 1391
1392 1392 def termwidth(self):
1393 1393 '''how wide is the terminal in columns?
1394 1394 '''
1395 1395 if 'COLUMNS' in encoding.environ:
1396 1396 try:
1397 1397 return int(encoding.environ['COLUMNS'])
1398 1398 except ValueError:
1399 1399 pass
1400 1400 return scmutil.termsize(self)[0]
1401 1401
1402 1402 def formatted(self):
1403 1403 '''should formatted output be used?
1404 1404
1405 1405 It is often desirable to format the output to suite the output medium.
1406 1406 Examples of this are truncating long lines or colorizing messages.
1407 1407 However, this is not often not desirable when piping output into other
1408 1408 utilities, e.g. `grep'.
1409 1409
1410 1410 Formatted output is triggered by the value of the `ui.formatted'
1411 1411 configuration variable or - if it is unset - when `sys.stdout' points
1412 1412 to a terminal device. Please note that `ui.formatted' should be
1413 1413 considered an implementation detail; it is not intended for use outside
1414 1414 Mercurial or its extensions.
1415 1415
1416 1416 This function refers to output only; for input, see `ui.interactive()'.
1417 1417 This function always returns false when in plain mode, see `ui.plain()'.
1418 1418 '''
1419 1419 if self.plain():
1420 1420 return False
1421 1421
1422 1422 i = self.configbool("ui", "formatted")
1423 1423 if i is None:
1424 1424 # some environments replace stdout without implementing isatty
1425 1425 # usually those are non-interactive
1426 1426 return self._isatty(self._fout)
1427 1427
1428 1428 return i
1429 1429
1430 1430 def _readline(self):
1431 1431 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1432 1432 # because they have to be text streams with *no buffering*. Instead,
1433 1433 # we use rawinput() only if call_readline() will be invoked by
1434 1434 # PyOS_Readline(), so no I/O will be made at Python layer.
1435 1435 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1436 1436 and procutil.isstdin(self._fin)
1437 1437 and procutil.isstdout(self._fout))
1438 1438 if usereadline:
1439 1439 try:
1440 1440 # magically add command line editing support, where
1441 1441 # available
1442 1442 import readline
1443 1443 # force demandimport to really load the module
1444 1444 readline.read_history_file
1445 1445 # windows sometimes raises something other than ImportError
1446 1446 except Exception:
1447 1447 usereadline = False
1448 1448
1449 1449 # prompt ' ' must exist; otherwise readline may delete entire line
1450 1450 # - http://bugs.python.org/issue12833
1451 1451 with self.timeblockedsection('stdio'):
1452 1452 if usereadline:
1453 1453 line = encoding.strtolocal(pycompat.rawinput(r' '))
1454 1454 # When stdin is in binary mode on Windows, it can cause
1455 1455 # raw_input() to emit an extra trailing carriage return
1456 1456 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1457 1457 line = line[:-1]
1458 1458 else:
1459 1459 self._fout.write(b' ')
1460 1460 self._fout.flush()
1461 1461 line = self._fin.readline()
1462 1462 if not line:
1463 1463 raise EOFError
1464 1464 line = line.rstrip(pycompat.oslinesep)
1465 1465
1466 1466 return line
1467 1467
1468 1468 def prompt(self, msg, default="y"):
1469 1469 """Prompt user with msg, read response.
1470 1470 If ui is not interactive, the default is returned.
1471 1471 """
1472 1472 return self._prompt(msg, default=default)
1473 1473
1474 1474 def _prompt(self, msg, **opts):
1475 1475 default = opts[r'default']
1476 1476 if not self.interactive():
1477 1477 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1478 1478 self._writemsg(self._fmsgout, default or '', "\n",
1479 1479 type='promptecho')
1480 1480 return default
1481 1481 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1482 1482 self.flush()
1483 1483 try:
1484 1484 r = self._readline()
1485 1485 if not r:
1486 1486 r = default
1487 1487 if self.configbool('ui', 'promptecho'):
1488 1488 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1489 1489 return r
1490 1490 except EOFError:
1491 1491 raise error.ResponseExpected()
1492 1492
1493 1493 @staticmethod
1494 1494 def extractchoices(prompt):
1495 1495 """Extract prompt message and list of choices from specified prompt.
1496 1496
1497 1497 This returns tuple "(message, choices)", and "choices" is the
1498 1498 list of tuple "(response character, text without &)".
1499 1499
1500 1500 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1501 1501 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1502 1502 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1503 1503 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1504 1504 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1505 1505 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1506 1506 """
1507 1507
1508 1508 # Sadly, the prompt string may have been built with a filename
1509 1509 # containing "$$" so let's try to find the first valid-looking
1510 1510 # prompt to start parsing. Sadly, we also can't rely on
1511 1511 # choices containing spaces, ASCII, or basically anything
1512 1512 # except an ampersand followed by a character.
1513 1513 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1514 1514 msg = m.group(1)
1515 1515 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1516 1516 def choicetuple(s):
1517 1517 ampidx = s.index('&')
1518 1518 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1519 1519 return (msg, [choicetuple(s) for s in choices])
1520 1520
1521 1521 def promptchoice(self, prompt, default=0):
1522 1522 """Prompt user with a message, read response, and ensure it matches
1523 1523 one of the provided choices. The prompt is formatted as follows:
1524 1524
1525 1525 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1526 1526
1527 1527 The index of the choice is returned. Responses are case
1528 1528 insensitive. If ui is not interactive, the default is
1529 1529 returned.
1530 1530 """
1531 1531
1532 1532 msg, choices = self.extractchoices(prompt)
1533 1533 resps = [r for r, t in choices]
1534 1534 while True:
1535 1535 r = self._prompt(msg, default=resps[default], choices=choices)
1536 1536 if r.lower() in resps:
1537 1537 return resps.index(r.lower())
1538 1538 # TODO: shouldn't it be a warning?
1539 1539 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1540 1540
1541 1541 def getpass(self, prompt=None, default=None):
1542 1542 if not self.interactive():
1543 1543 return default
1544 1544 try:
1545 1545 self._writemsg(self._fmsgerr, prompt or _('password: '),
1546 1546 type='prompt', password=True)
1547 1547 # disable getpass() only if explicitly specified. it's still valid
1548 1548 # to interact with tty even if fin is not a tty.
1549 1549 with self.timeblockedsection('stdio'):
1550 1550 if self.configbool('ui', 'nontty'):
1551 1551 l = self._fin.readline()
1552 1552 if not l:
1553 1553 raise EOFError
1554 1554 return l.rstrip('\n')
1555 1555 else:
1556 1556 return getpass.getpass('')
1557 1557 except EOFError:
1558 1558 raise error.ResponseExpected()
1559 1559
1560 1560 def status(self, *msg, **opts):
1561 1561 '''write status message to output (if ui.quiet is False)
1562 1562
1563 1563 This adds an output label of "ui.status".
1564 1564 '''
1565 1565 if not self.quiet:
1566 1566 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1567 1567
1568 1568 def warn(self, *msg, **opts):
1569 1569 '''write warning message to output (stderr)
1570 1570
1571 1571 This adds an output label of "ui.warning".
1572 1572 '''
1573 1573 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1574 1574
1575 1575 def error(self, *msg, **opts):
1576 1576 '''write error message to output (stderr)
1577 1577
1578 1578 This adds an output label of "ui.error".
1579 1579 '''
1580 1580 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1581 1581
1582 1582 def note(self, *msg, **opts):
1583 1583 '''write note to output (if ui.verbose is True)
1584 1584
1585 1585 This adds an output label of "ui.note".
1586 1586 '''
1587 1587 if self.verbose:
1588 1588 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1589 1589
1590 1590 def debug(self, *msg, **opts):
1591 1591 '''write debug message to output (if ui.debugflag is True)
1592 1592
1593 1593 This adds an output label of "ui.debug".
1594 1594 '''
1595 1595 if self.debugflag:
1596 1596 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1597 1597 self.log(b'debug', b'%s', b''.join(msg))
1598 1598
1599 1599 def edit(self, text, user, extra=None, editform=None, pending=None,
1600 1600 repopath=None, action=None):
1601 1601 if action is None:
1602 1602 self.develwarn('action is None but will soon be a required '
1603 1603 'parameter to ui.edit()')
1604 1604 extra_defaults = {
1605 1605 'prefix': 'editor',
1606 1606 'suffix': '.txt',
1607 1607 }
1608 1608 if extra is not None:
1609 1609 if extra.get('suffix') is not None:
1610 1610 self.develwarn('extra.suffix is not None but will soon be '
1611 1611 'ignored by ui.edit()')
1612 1612 extra_defaults.update(extra)
1613 1613 extra = extra_defaults
1614 1614
1615 1615 if action == 'diff':
1616 1616 suffix = '.diff'
1617 1617 elif action:
1618 1618 suffix = '.%s.hg.txt' % action
1619 1619 else:
1620 1620 suffix = extra['suffix']
1621 1621
1622 1622 rdir = None
1623 1623 if self.configbool('experimental', 'editortmpinhg'):
1624 1624 rdir = repopath
1625 1625 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1626 1626 suffix=suffix,
1627 1627 dir=rdir)
1628 1628 try:
1629 1629 f = os.fdopen(fd, r'wb')
1630 1630 f.write(util.tonativeeol(text))
1631 1631 f.close()
1632 1632
1633 1633 environ = {'HGUSER': user}
1634 1634 if 'transplant_source' in extra:
1635 1635 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1636 1636 for label in ('intermediate-source', 'source', 'rebase_source'):
1637 1637 if label in extra:
1638 1638 environ.update({'HGREVISION': extra[label]})
1639 1639 break
1640 1640 if editform:
1641 1641 environ.update({'HGEDITFORM': editform})
1642 1642 if pending:
1643 1643 environ.update({'HG_PENDING': pending})
1644 1644
1645 1645 editor = self.geteditor()
1646 1646
1647 1647 self.system("%s \"%s\"" % (editor, name),
1648 1648 environ=environ,
1649 1649 onerr=error.Abort, errprefix=_("edit failed"),
1650 1650 blockedtag='editor')
1651 1651
1652 1652 f = open(name, r'rb')
1653 1653 t = util.fromnativeeol(f.read())
1654 1654 f.close()
1655 1655 finally:
1656 1656 os.unlink(name)
1657 1657
1658 1658 return t
1659 1659
1660 1660 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1661 1661 blockedtag=None):
1662 1662 '''execute shell command with appropriate output stream. command
1663 1663 output will be redirected if fout is not stdout.
1664 1664
1665 1665 if command fails and onerr is None, return status, else raise onerr
1666 1666 object as exception.
1667 1667 '''
1668 1668 if blockedtag is None:
1669 1669 # Long cmds tend to be because of an absolute path on cmd. Keep
1670 1670 # the tail end instead
1671 1671 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1672 1672 blockedtag = 'unknown_system_' + cmdsuffix
1673 1673 out = self._fout
1674 1674 if any(s[1] for s in self._bufferstates):
1675 1675 out = self
1676 1676 with self.timeblockedsection(blockedtag):
1677 1677 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1678 1678 if rc and onerr:
1679 1679 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1680 1680 procutil.explainexit(rc))
1681 1681 if errprefix:
1682 1682 errmsg = '%s: %s' % (errprefix, errmsg)
1683 1683 raise onerr(errmsg)
1684 1684 return rc
1685 1685
1686 1686 def _runsystem(self, cmd, environ, cwd, out):
1687 1687 """actually execute the given shell command (can be overridden by
1688 1688 extensions like chg)"""
1689 1689 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1690 1690
1691 1691 def traceback(self, exc=None, force=False):
1692 1692 '''print exception traceback if traceback printing enabled or forced.
1693 1693 only to call in exception handler. returns true if traceback
1694 1694 printed.'''
1695 1695 if self.tracebackflag or force:
1696 1696 if exc is None:
1697 1697 exc = sys.exc_info()
1698 1698 cause = getattr(exc[1], 'cause', None)
1699 1699
1700 1700 if cause is not None:
1701 1701 causetb = traceback.format_tb(cause[2])
1702 1702 exctb = traceback.format_tb(exc[2])
1703 1703 exconly = traceback.format_exception_only(cause[0], cause[1])
1704 1704
1705 1705 # exclude frame where 'exc' was chained and rethrown from exctb
1706 1706 self.write_err('Traceback (most recent call last):\n',
1707 1707 ''.join(exctb[:-1]),
1708 1708 ''.join(causetb),
1709 1709 ''.join(exconly))
1710 1710 else:
1711 1711 output = traceback.format_exception(exc[0], exc[1], exc[2])
1712 1712 self.write_err(encoding.strtolocal(r''.join(output)))
1713 1713 return self.tracebackflag or force
1714 1714
1715 1715 def geteditor(self):
1716 1716 '''return editor to use'''
1717 1717 if pycompat.sysplatform == 'plan9':
1718 1718 # vi is the MIPS instruction simulator on Plan 9. We
1719 1719 # instead default to E to plumb commit messages to
1720 1720 # avoid confusion.
1721 1721 editor = 'E'
1722 1722 else:
1723 1723 editor = 'vi'
1724 1724 return (encoding.environ.get("HGEDITOR") or
1725 1725 self.config("ui", "editor", editor))
1726 1726
1727 1727 @util.propertycache
1728 1728 def _progbar(self):
1729 1729 """setup the progbar singleton to the ui object"""
1730 1730 if (self.quiet or self.debugflag
1731 1731 or self.configbool('progress', 'disable')
1732 1732 or not progress.shouldprint(self)):
1733 1733 return None
1734 1734 return getprogbar(self)
1735 1735
1736 1736 def _progclear(self):
1737 1737 """clear progress bar output if any. use it before any output"""
1738 1738 if not haveprogbar(): # nothing loaded yet
1739 1739 return
1740 1740 if self._progbar is not None and self._progbar.printed:
1741 1741 self._progbar.clear()
1742 1742
1743 1743 def progress(self, topic, pos, item="", unit="", total=None):
1744 1744 '''show a progress message
1745 1745
1746 1746 By default a textual progress bar will be displayed if an operation
1747 1747 takes too long. 'topic' is the current operation, 'item' is a
1748 1748 non-numeric marker of the current position (i.e. the currently
1749 1749 in-process file), 'pos' is the current numeric position (i.e.
1750 1750 revision, bytes, etc.), unit is a corresponding unit label,
1751 1751 and total is the highest expected pos.
1752 1752
1753 1753 Multiple nested topics may be active at a time.
1754 1754
1755 1755 All topics should be marked closed by setting pos to None at
1756 1756 termination.
1757 1757 '''
1758 1758 self.deprecwarn("use ui.makeprogress() instead of ui.progress()",
1759 1759 "5.1")
1760 1760 progress = self.makeprogress(topic, unit, total)
1761 1761 if pos is not None:
1762 1762 progress.update(pos, item=item)
1763 1763 else:
1764 1764 progress.complete()
1765 1765
1766 1766 def makeprogress(self, topic, unit="", total=None):
1767 1767 """Create a progress helper for the specified topic"""
1768 1768 if getattr(self._fmsgerr, 'structured', False):
1769 1769 # channel for machine-readable output with metadata, just send
1770 1770 # raw information
1771 1771 # TODO: consider porting some useful information (e.g. estimated
1772 1772 # time) from progbar. we might want to support update delay to
1773 1773 # reduce the cost of transferring progress messages.
1774 1774 def updatebar(topic, pos, item, unit, total):
1775 1775 self._fmsgerr.write(None, type=b'progress', topic=topic,
1776 1776 pos=pos, item=item, unit=unit, total=total)
1777 1777 elif self._progbar is not None:
1778 1778 updatebar = self._progbar.progress
1779 1779 else:
1780 1780 def updatebar(topic, pos, item, unit, total):
1781 1781 pass
1782 1782 return scmutil.progress(self, updatebar, topic, unit, total)
1783 1783
1784 1784 def getlogger(self, name):
1785 1785 """Returns a logger of the given name; or None if not registered"""
1786 1786 return self._loggers.get(name)
1787 1787
1788 1788 def setlogger(self, name, logger):
1789 1789 """Install logger which can be identified later by the given name
1790 1790
1791 1791 More than one loggers can be registered. Use extension or module
1792 1792 name to uniquely identify the logger instance.
1793 1793 """
1794 1794 self._loggers[name] = logger
1795 1795
1796 1796 def log(self, event, msgfmt, *msgargs, **opts):
1797 1797 '''hook for logging facility extensions
1798 1798
1799 1799 event should be a readily-identifiable subsystem, which will
1800 1800 allow filtering.
1801 1801
1802 1802 msgfmt should be a newline-terminated format string to log, and
1803 1803 *msgargs are %-formatted into it.
1804 1804
1805 1805 **opts currently has no defined meanings.
1806 1806 '''
1807 1807 if not self._loggers:
1808 1808 return
1809 1809 activeloggers = [l for l in self._loggers.itervalues()
1810 1810 if l.tracked(event)]
1811 1811 if not activeloggers:
1812 1812 return
1813 1813 msg = msgfmt % msgargs
1814 1814 opts = pycompat.byteskwargs(opts)
1815 1815 # guard against recursion from e.g. ui.debug()
1816 1816 registeredloggers = self._loggers
1817 1817 self._loggers = {}
1818 1818 try:
1819 1819 for logger in activeloggers:
1820 1820 logger.log(self, event, msg, opts)
1821 1821 finally:
1822 1822 self._loggers = registeredloggers
1823 1823
1824 1824 def label(self, msg, label):
1825 1825 '''style msg based on supplied label
1826 1826
1827 1827 If some color mode is enabled, this will add the necessary control
1828 1828 characters to apply such color. In addition, 'debug' color mode adds
1829 1829 markup showing which label affects a piece of text.
1830 1830
1831 1831 ui.write(s, 'label') is equivalent to
1832 1832 ui.write(ui.label(s, 'label')).
1833 1833 '''
1834 1834 if self._colormode is not None:
1835 1835 return color.colorlabel(self, msg, label)
1836 1836 return msg
1837 1837
1838 1838 def develwarn(self, msg, stacklevel=1, config=None):
1839 1839 """issue a developer warning message
1840 1840
1841 1841 Use 'stacklevel' to report the offender some layers further up in the
1842 1842 stack.
1843 1843 """
1844 1844 if not self.configbool('devel', 'all-warnings'):
1845 1845 if config is None or not self.configbool('devel', config):
1846 1846 return
1847 1847 msg = 'devel-warn: ' + msg
1848 1848 stacklevel += 1 # get in develwarn
1849 1849 if self.tracebackflag:
1850 1850 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1851 1851 self.log('develwarn', '%s at:\n%s' %
1852 1852 (msg, ''.join(util.getstackframes(stacklevel))))
1853 1853 else:
1854 1854 curframe = inspect.currentframe()
1855 1855 calframe = inspect.getouterframes(curframe, 2)
1856 1856 fname, lineno, fmsg = calframe[stacklevel][1:4]
1857 1857 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1858 1858 self.write_err('%s at: %s:%d (%s)\n'
1859 1859 % (msg, fname, lineno, fmsg))
1860 1860 self.log('develwarn', '%s at: %s:%d (%s)\n',
1861 1861 msg, fname, lineno, fmsg)
1862 1862 curframe = calframe = None # avoid cycles
1863 1863
1864 1864 def deprecwarn(self, msg, version, stacklevel=2):
1865 1865 """issue a deprecation warning
1866 1866
1867 1867 - msg: message explaining what is deprecated and how to upgrade,
1868 1868 - version: last version where the API will be supported,
1869 1869 """
1870 1870 if not (self.configbool('devel', 'all-warnings')
1871 1871 or self.configbool('devel', 'deprec-warn')):
1872 1872 return
1873 1873 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1874 1874 " update your code.)") % version
1875 1875 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1876 1876
1877 1877 def exportableenviron(self):
1878 1878 """The environment variables that are safe to export, e.g. through
1879 1879 hgweb.
1880 1880 """
1881 1881 return self._exportableenviron
1882 1882
1883 1883 @contextlib.contextmanager
1884 1884 def configoverride(self, overrides, source=""):
1885 1885 """Context manager for temporary config overrides
1886 1886 `overrides` must be a dict of the following structure:
1887 1887 {(section, name) : value}"""
1888 1888 backups = {}
1889 1889 try:
1890 1890 for (section, name), value in overrides.items():
1891 1891 backups[(section, name)] = self.backupconfig(section, name)
1892 1892 self.setconfig(section, name, value, source)
1893 1893 yield
1894 1894 finally:
1895 1895 for __, backup in backups.items():
1896 1896 self.restoreconfig(backup)
1897 1897 # just restoring ui.quiet config to the previous value is not enough
1898 1898 # as it does not update ui.quiet class member
1899 1899 if ('ui', 'quiet') in overrides:
1900 1900 self.fixconfig(section='ui')
1901 1901
1902 1902 class paths(dict):
1903 1903 """Represents a collection of paths and their configs.
1904 1904
1905 1905 Data is initially derived from ui instances and the config files they have
1906 1906 loaded.
1907 1907 """
1908 1908 def __init__(self, ui):
1909 1909 dict.__init__(self)
1910 1910
1911 1911 for name, loc in ui.configitems('paths', ignoresub=True):
1912 1912 # No location is the same as not existing.
1913 1913 if not loc:
1914 1914 continue
1915 1915 loc, sub = ui.configsuboptions('paths', name)
1916 1916 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1917 1917
1918 1918 def getpath(self, name, default=None):
1919 1919 """Return a ``path`` from a string, falling back to default.
1920 1920
1921 1921 ``name`` can be a named path or locations. Locations are filesystem
1922 1922 paths or URIs.
1923 1923
1924 1924 Returns None if ``name`` is not a registered path, a URI, or a local
1925 1925 path to a repo.
1926 1926 """
1927 1927 # Only fall back to default if no path was requested.
1928 1928 if name is None:
1929 1929 if not default:
1930 1930 default = ()
1931 1931 elif not isinstance(default, (tuple, list)):
1932 1932 default = (default,)
1933 1933 for k in default:
1934 1934 try:
1935 1935 return self[k]
1936 1936 except KeyError:
1937 1937 continue
1938 1938 return None
1939 1939
1940 1940 # Most likely empty string.
1941 1941 # This may need to raise in the future.
1942 1942 if not name:
1943 1943 return None
1944 1944
1945 1945 try:
1946 1946 return self[name]
1947 1947 except KeyError:
1948 1948 # Try to resolve as a local path or URI.
1949 1949 try:
1950 1950 # We don't pass sub-options in, so no need to pass ui instance.
1951 1951 return path(None, None, rawloc=name)
1952 1952 except ValueError:
1953 1953 raise error.RepoError(_('repository %s does not exist') %
1954 1954 name)
1955 1955
1956 1956 _pathsuboptions = {}
1957 1957
1958 1958 def pathsuboption(option, attr):
1959 1959 """Decorator used to declare a path sub-option.
1960 1960
1961 1961 Arguments are the sub-option name and the attribute it should set on
1962 1962 ``path`` instances.
1963 1963
1964 1964 The decorated function will receive as arguments a ``ui`` instance,
1965 1965 ``path`` instance, and the string value of this option from the config.
1966 1966 The function should return the value that will be set on the ``path``
1967 1967 instance.
1968 1968
1969 1969 This decorator can be used to perform additional verification of
1970 1970 sub-options and to change the type of sub-options.
1971 1971 """
1972 1972 def register(func):
1973 1973 _pathsuboptions[option] = (attr, func)
1974 1974 return func
1975 1975 return register
1976 1976
1977 1977 @pathsuboption('pushurl', 'pushloc')
1978 1978 def pushurlpathoption(ui, path, value):
1979 1979 u = util.url(value)
1980 1980 # Actually require a URL.
1981 1981 if not u.scheme:
1982 1982 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1983 1983 return None
1984 1984
1985 1985 # Don't support the #foo syntax in the push URL to declare branch to
1986 1986 # push.
1987 1987 if u.fragment:
1988 1988 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1989 1989 'ignoring)\n') % path.name)
1990 1990 u.fragment = None
1991 1991
1992 1992 return bytes(u)
1993 1993
1994 1994 @pathsuboption('pushrev', 'pushrev')
1995 1995 def pushrevpathoption(ui, path, value):
1996 1996 return value
1997 1997
1998 1998 class path(object):
1999 1999 """Represents an individual path and its configuration."""
2000 2000
2001 2001 def __init__(self, ui, name, rawloc=None, suboptions=None):
2002 2002 """Construct a path from its config options.
2003 2003
2004 2004 ``ui`` is the ``ui`` instance the path is coming from.
2005 2005 ``name`` is the symbolic name of the path.
2006 2006 ``rawloc`` is the raw location, as defined in the config.
2007 2007 ``pushloc`` is the raw locations pushes should be made to.
2008 2008
2009 2009 If ``name`` is not defined, we require that the location be a) a local
2010 2010 filesystem path with a .hg directory or b) a URL. If not,
2011 2011 ``ValueError`` is raised.
2012 2012 """
2013 2013 if not rawloc:
2014 2014 raise ValueError('rawloc must be defined')
2015 2015
2016 2016 # Locations may define branches via syntax <base>#<branch>.
2017 2017 u = util.url(rawloc)
2018 2018 branch = None
2019 2019 if u.fragment:
2020 2020 branch = u.fragment
2021 2021 u.fragment = None
2022 2022
2023 2023 self.url = u
2024 2024 self.branch = branch
2025 2025
2026 2026 self.name = name
2027 2027 self.rawloc = rawloc
2028 2028 self.loc = '%s' % u
2029 2029
2030 2030 # When given a raw location but not a symbolic name, validate the
2031 2031 # location is valid.
2032 2032 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2033 2033 raise ValueError('location is not a URL or path to a local '
2034 2034 'repo: %s' % rawloc)
2035 2035
2036 2036 suboptions = suboptions or {}
2037 2037
2038 2038 # Now process the sub-options. If a sub-option is registered, its
2039 2039 # attribute will always be present. The value will be None if there
2040 2040 # was no valid sub-option.
2041 2041 for suboption, (attr, func) in _pathsuboptions.iteritems():
2042 2042 if suboption not in suboptions:
2043 2043 setattr(self, attr, None)
2044 2044 continue
2045 2045
2046 2046 value = func(ui, self, suboptions[suboption])
2047 2047 setattr(self, attr, value)
2048 2048
2049 2049 def _isvalidlocalpath(self, path):
2050 2050 """Returns True if the given path is a potentially valid repository.
2051 2051 This is its own function so that extensions can change the definition of
2052 2052 'valid' in this case (like when pulling from a git repo into a hg
2053 2053 one)."""
2054 2054 try:
2055 2055 return os.path.isdir(os.path.join(path, '.hg'))
2056 2056 # Python 2 may return TypeError. Python 3, ValueError.
2057 2057 except (TypeError, ValueError):
2058 2058 return False
2059 2059
2060 2060 @property
2061 2061 def suboptions(self):
2062 2062 """Return sub-options and their values for this path.
2063 2063
2064 2064 This is intended to be used for presentation purposes.
2065 2065 """
2066 2066 d = {}
2067 2067 for subopt, (attr, _func) in _pathsuboptions.iteritems():
2068 2068 value = getattr(self, attr)
2069 2069 if value is not None:
2070 2070 d[subopt] = value
2071 2071 return d
2072 2072
2073 2073 # we instantiate one globally shared progress bar to avoid
2074 2074 # competing progress bars when multiple UI objects get created
2075 2075 _progresssingleton = None
2076 2076
2077 2077 def getprogbar(ui):
2078 2078 global _progresssingleton
2079 2079 if _progresssingleton is None:
2080 2080 # passing 'ui' object to the singleton is fishy,
2081 2081 # this is how the extension used to work but feel free to rework it.
2082 2082 _progresssingleton = progress.progbar(ui)
2083 2083 return _progresssingleton
2084 2084
2085 2085 def haveprogbar():
2086 2086 return _progresssingleton is not None
2087 2087
2088 2088 def _selectmsgdests(ui):
2089 2089 name = ui.config(b'ui', b'message-output')
2090 2090 if name == b'channel':
2091 2091 if ui.fmsg:
2092 2092 return ui.fmsg, ui.fmsg
2093 2093 else:
2094 2094 # fall back to ferr if channel isn't ready so that status/error
2095 2095 # messages can be printed
2096 2096 return ui.ferr, ui.ferr
2097 2097 if name == b'stdio':
2098 2098 return ui.fout, ui.ferr
2099 2099 if name == b'stderr':
2100 2100 return ui.ferr, ui.ferr
2101 2101 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2102 2102
2103 2103 def _writemsgwith(write, dest, *args, **opts):
2104 2104 """Write ui message with the given ui._write*() function
2105 2105
2106 2106 The specified message type is translated to 'ui.<type>' label if the dest
2107 2107 isn't a structured channel, so that the message will be colorized.
2108 2108 """
2109 2109 # TODO: maybe change 'type' to a mandatory option
2110 2110 if r'type' in opts and not getattr(dest, 'structured', False):
2111 2111 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2112 2112 write(dest, *args, **opts)
General Comments 0
You need to be logged in to leave comments. Login now