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