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