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