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