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