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