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