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