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