##// END OF EJS Templates
ui: option to preserve the progress bar...
Joerg Sonnenberger -
r43559:82879e06 default
parent child Browse files
Show More
@@ -1,2300 +1,2309 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import contextlib
12 12 import errno
13 13 import getpass
14 14 import inspect
15 15 import os
16 16 import re
17 17 import signal
18 18 import socket
19 19 import subprocess
20 20 import sys
21 21 import traceback
22 22
23 23 from .i18n import _
24 24 from .node import hex
25 25 from .pycompat import (
26 26 getattr,
27 27 open,
28 28 setattr,
29 29 )
30 30
31 31 from . import (
32 32 color,
33 33 config,
34 34 configitems,
35 35 encoding,
36 36 error,
37 37 formatter,
38 38 loggingutil,
39 39 progress,
40 40 pycompat,
41 41 rcutil,
42 42 scmutil,
43 43 util,
44 44 )
45 45 from .utils import (
46 46 dateutil,
47 47 procutil,
48 48 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 Progress reports via stderr are normally cleared before writing as
1076 stdout and stderr go to the same terminal. This can be skipped with
1077 the optional keyword argument "keepprogressbar". The progress bar
1078 will continue to occupy a partial line on stderr in that case.
1079 This functionality is intended when Mercurial acts as data source
1080 in a pipe.
1081
1075 1082 When labeling output for a specific command, a label of
1076 1083 "cmdname.type" is recommended. For example, status issues
1077 1084 a label of "status.modified" for modified files.
1078 1085 '''
1079 1086 dest = self._fout
1080 1087
1081 1088 # inlined _write() for speed
1082 1089 if self._buffers:
1083 1090 label = opts.get(r'label', b'')
1084 1091 if label and self._bufferapplylabels:
1085 1092 self._buffers[-1].extend(self.label(a, label) for a in args)
1086 1093 else:
1087 1094 self._buffers[-1].extend(args)
1088 1095 return
1089 1096
1090 # inliend _writenobuf() for speed
1097 # inlined _writenobuf() for speed
1098 if not opts.get(r'keepprogressbar', False):
1091 1099 self._progclear()
1092 1100 msg = b''.join(args)
1093 1101
1094 1102 # opencode timeblockedsection because this is a critical path
1095 1103 starttime = util.timer()
1096 1104 try:
1097 1105 if self._colormode == b'win32':
1098 1106 # windows color printing is its own can of crab, defer to
1099 1107 # the color module and that is it.
1100 1108 color.win32print(self, dest.write, msg, **opts)
1101 1109 else:
1102 1110 if self._colormode is not None:
1103 1111 label = opts.get(r'label', b'')
1104 1112 msg = self.label(msg, label)
1105 1113 dest.write(msg)
1106 1114 except IOError as err:
1107 1115 raise error.StdioError(err)
1108 1116 finally:
1109 1117 self._blockedtimes[b'stdio_blocked'] += (
1110 1118 util.timer() - starttime
1111 1119 ) * 1000
1112 1120
1113 1121 def write_err(self, *args, **opts):
1114 1122 self._write(self._ferr, *args, **opts)
1115 1123
1116 1124 def _write(self, dest, *args, **opts):
1117 1125 # update write() as well if you touch this code
1118 1126 if self._isbuffered(dest):
1119 1127 label = opts.get(r'label', b'')
1120 1128 if label and self._bufferapplylabels:
1121 1129 self._buffers[-1].extend(self.label(a, label) for a in args)
1122 1130 else:
1123 1131 self._buffers[-1].extend(args)
1124 1132 else:
1125 1133 self._writenobuf(dest, *args, **opts)
1126 1134
1127 1135 def _writenobuf(self, dest, *args, **opts):
1128 1136 # update write() as well if you touch this code
1137 if not opts.get(r'keepprogressbar', False):
1129 1138 self._progclear()
1130 1139 msg = b''.join(args)
1131 1140
1132 1141 # opencode timeblockedsection because this is a critical path
1133 1142 starttime = util.timer()
1134 1143 try:
1135 1144 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1136 1145 self._fout.flush()
1137 1146 if getattr(dest, 'structured', False):
1138 1147 # channel for machine-readable output with metadata, where
1139 1148 # no extra colorization is necessary.
1140 1149 dest.write(msg, **opts)
1141 1150 elif self._colormode == b'win32':
1142 1151 # windows color printing is its own can of crab, defer to
1143 1152 # the color module and that is it.
1144 1153 color.win32print(self, dest.write, msg, **opts)
1145 1154 else:
1146 1155 if self._colormode is not None:
1147 1156 label = opts.get(r'label', b'')
1148 1157 msg = self.label(msg, label)
1149 1158 dest.write(msg)
1150 1159 # stderr may be buffered under win32 when redirected to files,
1151 1160 # including stdout.
1152 1161 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1153 1162 dest.flush()
1154 1163 except IOError as err:
1155 1164 if dest is self._ferr and err.errno in (
1156 1165 errno.EPIPE,
1157 1166 errno.EIO,
1158 1167 errno.EBADF,
1159 1168 ):
1160 1169 # no way to report the error, so ignore it
1161 1170 return
1162 1171 raise error.StdioError(err)
1163 1172 finally:
1164 1173 self._blockedtimes[b'stdio_blocked'] += (
1165 1174 util.timer() - starttime
1166 1175 ) * 1000
1167 1176
1168 1177 def _writemsg(self, dest, *args, **opts):
1169 1178 _writemsgwith(self._write, dest, *args, **opts)
1170 1179
1171 1180 def _writemsgnobuf(self, dest, *args, **opts):
1172 1181 _writemsgwith(self._writenobuf, dest, *args, **opts)
1173 1182
1174 1183 def flush(self):
1175 1184 # opencode timeblockedsection because this is a critical path
1176 1185 starttime = util.timer()
1177 1186 try:
1178 1187 try:
1179 1188 self._fout.flush()
1180 1189 except IOError as err:
1181 1190 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1182 1191 raise error.StdioError(err)
1183 1192 finally:
1184 1193 try:
1185 1194 self._ferr.flush()
1186 1195 except IOError as err:
1187 1196 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1188 1197 raise error.StdioError(err)
1189 1198 finally:
1190 1199 self._blockedtimes[b'stdio_blocked'] += (
1191 1200 util.timer() - starttime
1192 1201 ) * 1000
1193 1202
1194 1203 def _isatty(self, fh):
1195 1204 if self.configbool(b'ui', b'nontty'):
1196 1205 return False
1197 1206 return procutil.isatty(fh)
1198 1207
1199 1208 def protectfinout(self):
1200 1209 """Duplicate ui streams and redirect original if they are stdio
1201 1210
1202 1211 Returns (fin, fout) which point to the original ui fds, but may be
1203 1212 copy of them. The returned streams can be considered "owned" in that
1204 1213 print(), exec(), etc. never reach to them.
1205 1214 """
1206 1215 if self._finoutredirected:
1207 1216 # if already redirected, protectstdio() would just create another
1208 1217 # nullfd pair, which is equivalent to returning self._fin/_fout.
1209 1218 return self._fin, self._fout
1210 1219 fin, fout = procutil.protectstdio(self._fin, self._fout)
1211 1220 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1212 1221 return fin, fout
1213 1222
1214 1223 def restorefinout(self, fin, fout):
1215 1224 """Restore ui streams from possibly duplicated (fin, fout)"""
1216 1225 if (fin, fout) == (self._fin, self._fout):
1217 1226 return
1218 1227 procutil.restorestdio(self._fin, self._fout, fin, fout)
1219 1228 # protectfinout() won't create more than one duplicated streams,
1220 1229 # so we can just turn the redirection flag off.
1221 1230 self._finoutredirected = False
1222 1231
1223 1232 @contextlib.contextmanager
1224 1233 def protectedfinout(self):
1225 1234 """Run code block with protected standard streams"""
1226 1235 fin, fout = self.protectfinout()
1227 1236 try:
1228 1237 yield fin, fout
1229 1238 finally:
1230 1239 self.restorefinout(fin, fout)
1231 1240
1232 1241 def disablepager(self):
1233 1242 self._disablepager = True
1234 1243
1235 1244 def pager(self, command):
1236 1245 """Start a pager for subsequent command output.
1237 1246
1238 1247 Commands which produce a long stream of output should call
1239 1248 this function to activate the user's preferred pagination
1240 1249 mechanism (which may be no pager). Calling this function
1241 1250 precludes any future use of interactive functionality, such as
1242 1251 prompting the user or activating curses.
1243 1252
1244 1253 Args:
1245 1254 command: The full, non-aliased name of the command. That is, "log"
1246 1255 not "history, "summary" not "summ", etc.
1247 1256 """
1248 1257 if self._disablepager or self.pageractive:
1249 1258 # how pager should do is already determined
1250 1259 return
1251 1260
1252 1261 if not command.startswith(b'internal-always-') and (
1253 1262 # explicit --pager=on (= 'internal-always-' prefix) should
1254 1263 # take precedence over disabling factors below
1255 1264 command in self.configlist(b'pager', b'ignore')
1256 1265 or not self.configbool(b'ui', b'paginate')
1257 1266 or not self.configbool(b'pager', b'attend-' + command, True)
1258 1267 or encoding.environ.get(b'TERM') == b'dumb'
1259 1268 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1260 1269 # formatted() will need some adjustment.
1261 1270 or not self.formatted()
1262 1271 or self.plain()
1263 1272 or self._buffers
1264 1273 # TODO: expose debugger-enabled on the UI object
1265 1274 or b'--debugger' in pycompat.sysargv
1266 1275 ):
1267 1276 # We only want to paginate if the ui appears to be
1268 1277 # interactive, the user didn't say HGPLAIN or
1269 1278 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1270 1279 return
1271 1280
1272 1281 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1273 1282 if not pagercmd:
1274 1283 return
1275 1284
1276 1285 pagerenv = {}
1277 1286 for name, value in rcutil.defaultpagerenv().items():
1278 1287 if name not in encoding.environ:
1279 1288 pagerenv[name] = value
1280 1289
1281 1290 self.debug(
1282 1291 b'starting pager for command %s\n' % stringutil.pprint(command)
1283 1292 )
1284 1293 self.flush()
1285 1294
1286 1295 wasformatted = self.formatted()
1287 1296 if util.safehasattr(signal, b"SIGPIPE"):
1288 1297 signal.signal(signal.SIGPIPE, _catchterm)
1289 1298 if self._runpager(pagercmd, pagerenv):
1290 1299 self.pageractive = True
1291 1300 # Preserve the formatted-ness of the UI. This is important
1292 1301 # because we mess with stdout, which might confuse
1293 1302 # auto-detection of things being formatted.
1294 1303 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1295 1304 self.setconfig(b'ui', b'interactive', False, b'pager')
1296 1305
1297 1306 # If pagermode differs from color.mode, reconfigure color now that
1298 1307 # pageractive is set.
1299 1308 cm = self._colormode
1300 1309 if cm != self.config(b'color', b'pagermode', cm):
1301 1310 color.setup(self)
1302 1311 else:
1303 1312 # If the pager can't be spawned in dispatch when --pager=on is
1304 1313 # given, don't try again when the command runs, to avoid a duplicate
1305 1314 # warning about a missing pager command.
1306 1315 self.disablepager()
1307 1316
1308 1317 def _runpager(self, command, env=None):
1309 1318 """Actually start the pager and set up file descriptors.
1310 1319
1311 1320 This is separate in part so that extensions (like chg) can
1312 1321 override how a pager is invoked.
1313 1322 """
1314 1323 if command == b'cat':
1315 1324 # Save ourselves some work.
1316 1325 return False
1317 1326 # If the command doesn't contain any of these characters, we
1318 1327 # assume it's a binary and exec it directly. This means for
1319 1328 # simple pager command configurations, we can degrade
1320 1329 # gracefully and tell the user about their broken pager.
1321 1330 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1322 1331
1323 1332 if pycompat.iswindows and not shell:
1324 1333 # Window's built-in `more` cannot be invoked with shell=False, but
1325 1334 # its `more.com` can. Hide this implementation detail from the
1326 1335 # user so we can also get sane bad PAGER behavior. MSYS has
1327 1336 # `more.exe`, so do a cmd.exe style resolution of the executable to
1328 1337 # determine which one to use.
1329 1338 fullcmd = procutil.findexe(command)
1330 1339 if not fullcmd:
1331 1340 self.warn(
1332 1341 _(b"missing pager command '%s', skipping pager\n") % command
1333 1342 )
1334 1343 return False
1335 1344
1336 1345 command = fullcmd
1337 1346
1338 1347 try:
1339 1348 pager = subprocess.Popen(
1340 1349 procutil.tonativestr(command),
1341 1350 shell=shell,
1342 1351 bufsize=-1,
1343 1352 close_fds=procutil.closefds,
1344 1353 stdin=subprocess.PIPE,
1345 1354 stdout=procutil.stdout,
1346 1355 stderr=procutil.stderr,
1347 1356 env=procutil.tonativeenv(procutil.shellenviron(env)),
1348 1357 )
1349 1358 except OSError as e:
1350 1359 if e.errno == errno.ENOENT and not shell:
1351 1360 self.warn(
1352 1361 _(b"missing pager command '%s', skipping pager\n") % command
1353 1362 )
1354 1363 return False
1355 1364 raise
1356 1365
1357 1366 # back up original file descriptors
1358 1367 stdoutfd = os.dup(procutil.stdout.fileno())
1359 1368 stderrfd = os.dup(procutil.stderr.fileno())
1360 1369
1361 1370 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1362 1371 if self._isatty(procutil.stderr):
1363 1372 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1364 1373
1365 1374 @self.atexit
1366 1375 def killpager():
1367 1376 if util.safehasattr(signal, b"SIGINT"):
1368 1377 signal.signal(signal.SIGINT, signal.SIG_IGN)
1369 1378 # restore original fds, closing pager.stdin copies in the process
1370 1379 os.dup2(stdoutfd, procutil.stdout.fileno())
1371 1380 os.dup2(stderrfd, procutil.stderr.fileno())
1372 1381 pager.stdin.close()
1373 1382 pager.wait()
1374 1383
1375 1384 return True
1376 1385
1377 1386 @property
1378 1387 def _exithandlers(self):
1379 1388 return _reqexithandlers
1380 1389
1381 1390 def atexit(self, func, *args, **kwargs):
1382 1391 '''register a function to run after dispatching a request
1383 1392
1384 1393 Handlers do not stay registered across request boundaries.'''
1385 1394 self._exithandlers.append((func, args, kwargs))
1386 1395 return func
1387 1396
1388 1397 def interface(self, feature):
1389 1398 """what interface to use for interactive console features?
1390 1399
1391 1400 The interface is controlled by the value of `ui.interface` but also by
1392 1401 the value of feature-specific configuration. For example:
1393 1402
1394 1403 ui.interface.histedit = text
1395 1404 ui.interface.chunkselector = curses
1396 1405
1397 1406 Here the features are "histedit" and "chunkselector".
1398 1407
1399 1408 The configuration above means that the default interfaces for commands
1400 1409 is curses, the interface for histedit is text and the interface for
1401 1410 selecting chunk is crecord (the best curses interface available).
1402 1411
1403 1412 Consider the following example:
1404 1413 ui.interface = curses
1405 1414 ui.interface.histedit = text
1406 1415
1407 1416 Then histedit will use the text interface and chunkselector will use
1408 1417 the default curses interface (crecord at the moment).
1409 1418 """
1410 1419 alldefaults = frozenset([b"text", b"curses"])
1411 1420
1412 1421 featureinterfaces = {
1413 1422 b"chunkselector": [b"text", b"curses",],
1414 1423 b"histedit": [b"text", b"curses",],
1415 1424 }
1416 1425
1417 1426 # Feature-specific interface
1418 1427 if feature not in featureinterfaces.keys():
1419 1428 # Programming error, not user error
1420 1429 raise ValueError(b"Unknown feature requested %s" % feature)
1421 1430
1422 1431 availableinterfaces = frozenset(featureinterfaces[feature])
1423 1432 if alldefaults > availableinterfaces:
1424 1433 # Programming error, not user error. We need a use case to
1425 1434 # define the right thing to do here.
1426 1435 raise ValueError(
1427 1436 b"Feature %s does not handle all default interfaces" % feature
1428 1437 )
1429 1438
1430 1439 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1431 1440 return b"text"
1432 1441
1433 1442 # Default interface for all the features
1434 1443 defaultinterface = b"text"
1435 1444 i = self.config(b"ui", b"interface")
1436 1445 if i in alldefaults:
1437 1446 defaultinterface = i
1438 1447
1439 1448 choseninterface = defaultinterface
1440 1449 f = self.config(b"ui", b"interface.%s" % feature)
1441 1450 if f in availableinterfaces:
1442 1451 choseninterface = f
1443 1452
1444 1453 if i is not None and defaultinterface != i:
1445 1454 if f is not None:
1446 1455 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1447 1456 else:
1448 1457 self.warn(
1449 1458 _(b"invalid value for ui.interface: %s (using %s)\n")
1450 1459 % (i, choseninterface)
1451 1460 )
1452 1461 if f is not None and choseninterface != f:
1453 1462 self.warn(
1454 1463 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1455 1464 % (feature, f, choseninterface)
1456 1465 )
1457 1466
1458 1467 return choseninterface
1459 1468
1460 1469 def interactive(self):
1461 1470 '''is interactive input allowed?
1462 1471
1463 1472 An interactive session is a session where input can be reasonably read
1464 1473 from `sys.stdin'. If this function returns false, any attempt to read
1465 1474 from stdin should fail with an error, unless a sensible default has been
1466 1475 specified.
1467 1476
1468 1477 Interactiveness is triggered by the value of the `ui.interactive'
1469 1478 configuration variable or - if it is unset - when `sys.stdin' points
1470 1479 to a terminal device.
1471 1480
1472 1481 This function refers to input only; for output, see `ui.formatted()'.
1473 1482 '''
1474 1483 i = self.configbool(b"ui", b"interactive")
1475 1484 if i is None:
1476 1485 # some environments replace stdin without implementing isatty
1477 1486 # usually those are non-interactive
1478 1487 return self._isatty(self._fin)
1479 1488
1480 1489 return i
1481 1490
1482 1491 def termwidth(self):
1483 1492 '''how wide is the terminal in columns?
1484 1493 '''
1485 1494 if b'COLUMNS' in encoding.environ:
1486 1495 try:
1487 1496 return int(encoding.environ[b'COLUMNS'])
1488 1497 except ValueError:
1489 1498 pass
1490 1499 return scmutil.termsize(self)[0]
1491 1500
1492 1501 def formatted(self):
1493 1502 '''should formatted output be used?
1494 1503
1495 1504 It is often desirable to format the output to suite the output medium.
1496 1505 Examples of this are truncating long lines or colorizing messages.
1497 1506 However, this is not often not desirable when piping output into other
1498 1507 utilities, e.g. `grep'.
1499 1508
1500 1509 Formatted output is triggered by the value of the `ui.formatted'
1501 1510 configuration variable or - if it is unset - when `sys.stdout' points
1502 1511 to a terminal device. Please note that `ui.formatted' should be
1503 1512 considered an implementation detail; it is not intended for use outside
1504 1513 Mercurial or its extensions.
1505 1514
1506 1515 This function refers to output only; for input, see `ui.interactive()'.
1507 1516 This function always returns false when in plain mode, see `ui.plain()'.
1508 1517 '''
1509 1518 if self.plain():
1510 1519 return False
1511 1520
1512 1521 i = self.configbool(b"ui", b"formatted")
1513 1522 if i is None:
1514 1523 # some environments replace stdout without implementing isatty
1515 1524 # usually those are non-interactive
1516 1525 return self._isatty(self._fout)
1517 1526
1518 1527 return i
1519 1528
1520 1529 def _readline(self, prompt=b' ', promptopts=None):
1521 1530 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1522 1531 # because they have to be text streams with *no buffering*. Instead,
1523 1532 # we use rawinput() only if call_readline() will be invoked by
1524 1533 # PyOS_Readline(), so no I/O will be made at Python layer.
1525 1534 usereadline = (
1526 1535 self._isatty(self._fin)
1527 1536 and self._isatty(self._fout)
1528 1537 and procutil.isstdin(self._fin)
1529 1538 and procutil.isstdout(self._fout)
1530 1539 )
1531 1540 if usereadline:
1532 1541 try:
1533 1542 # magically add command line editing support, where
1534 1543 # available
1535 1544 import readline
1536 1545
1537 1546 # force demandimport to really load the module
1538 1547 readline.read_history_file
1539 1548 # windows sometimes raises something other than ImportError
1540 1549 except Exception:
1541 1550 usereadline = False
1542 1551
1543 1552 if self._colormode == b'win32' or not usereadline:
1544 1553 if not promptopts:
1545 1554 promptopts = {}
1546 1555 self._writemsgnobuf(
1547 1556 self._fmsgout, prompt, type=b'prompt', **promptopts
1548 1557 )
1549 1558 self.flush()
1550 1559 prompt = b' '
1551 1560 else:
1552 1561 prompt = self.label(prompt, b'ui.prompt') + b' '
1553 1562
1554 1563 # prompt ' ' must exist; otherwise readline may delete entire line
1555 1564 # - http://bugs.python.org/issue12833
1556 1565 with self.timeblockedsection(b'stdio'):
1557 1566 if usereadline:
1558 1567 prompt = encoding.strfromlocal(prompt)
1559 1568 line = encoding.strtolocal(pycompat.rawinput(prompt))
1560 1569 # When stdin is in binary mode on Windows, it can cause
1561 1570 # raw_input() to emit an extra trailing carriage return
1562 1571 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1563 1572 line = line[:-1]
1564 1573 else:
1565 1574 self._fout.write(pycompat.bytestr(prompt))
1566 1575 self._fout.flush()
1567 1576 line = self._fin.readline()
1568 1577 if not line:
1569 1578 raise EOFError
1570 1579 line = line.rstrip(pycompat.oslinesep)
1571 1580
1572 1581 return line
1573 1582
1574 1583 def prompt(self, msg, default=b"y"):
1575 1584 """Prompt user with msg, read response.
1576 1585 If ui is not interactive, the default is returned.
1577 1586 """
1578 1587 return self._prompt(msg, default=default)
1579 1588
1580 1589 def _prompt(self, msg, **opts):
1581 1590 default = opts[r'default']
1582 1591 if not self.interactive():
1583 1592 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1584 1593 self._writemsg(
1585 1594 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1586 1595 )
1587 1596 return default
1588 1597 try:
1589 1598 r = self._readline(prompt=msg, promptopts=opts)
1590 1599 if not r:
1591 1600 r = default
1592 1601 if self.configbool(b'ui', b'promptecho'):
1593 1602 self._writemsg(self._fmsgout, r, b"\n", type=b'promptecho')
1594 1603 return r
1595 1604 except EOFError:
1596 1605 raise error.ResponseExpected()
1597 1606
1598 1607 @staticmethod
1599 1608 def extractchoices(prompt):
1600 1609 """Extract prompt message and list of choices from specified prompt.
1601 1610
1602 1611 This returns tuple "(message, choices)", and "choices" is the
1603 1612 list of tuple "(response character, text without &)".
1604 1613
1605 1614 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1606 1615 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1607 1616 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1608 1617 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1609 1618 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1610 1619 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1611 1620 """
1612 1621
1613 1622 # Sadly, the prompt string may have been built with a filename
1614 1623 # containing "$$" so let's try to find the first valid-looking
1615 1624 # prompt to start parsing. Sadly, we also can't rely on
1616 1625 # choices containing spaces, ASCII, or basically anything
1617 1626 # except an ampersand followed by a character.
1618 1627 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1619 1628 msg = m.group(1)
1620 1629 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1621 1630
1622 1631 def choicetuple(s):
1623 1632 ampidx = s.index(b'&')
1624 1633 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1625 1634
1626 1635 return (msg, [choicetuple(s) for s in choices])
1627 1636
1628 1637 def promptchoice(self, prompt, default=0):
1629 1638 """Prompt user with a message, read response, and ensure it matches
1630 1639 one of the provided choices. The prompt is formatted as follows:
1631 1640
1632 1641 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1633 1642
1634 1643 The index of the choice is returned. Responses are case
1635 1644 insensitive. If ui is not interactive, the default is
1636 1645 returned.
1637 1646 """
1638 1647
1639 1648 msg, choices = self.extractchoices(prompt)
1640 1649 resps = [r for r, t in choices]
1641 1650 while True:
1642 1651 r = self._prompt(msg, default=resps[default], choices=choices)
1643 1652 if r.lower() in resps:
1644 1653 return resps.index(r.lower())
1645 1654 # TODO: shouldn't it be a warning?
1646 1655 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1647 1656
1648 1657 def getpass(self, prompt=None, default=None):
1649 1658 if not self.interactive():
1650 1659 return default
1651 1660 try:
1652 1661 self._writemsg(
1653 1662 self._fmsgerr,
1654 1663 prompt or _(b'password: '),
1655 1664 type=b'prompt',
1656 1665 password=True,
1657 1666 )
1658 1667 # disable getpass() only if explicitly specified. it's still valid
1659 1668 # to interact with tty even if fin is not a tty.
1660 1669 with self.timeblockedsection(b'stdio'):
1661 1670 if self.configbool(b'ui', b'nontty'):
1662 1671 l = self._fin.readline()
1663 1672 if not l:
1664 1673 raise EOFError
1665 1674 return l.rstrip(b'\n')
1666 1675 else:
1667 1676 return getpass.getpass(r'')
1668 1677 except EOFError:
1669 1678 raise error.ResponseExpected()
1670 1679
1671 1680 def status(self, *msg, **opts):
1672 1681 '''write status message to output (if ui.quiet is False)
1673 1682
1674 1683 This adds an output label of "ui.status".
1675 1684 '''
1676 1685 if not self.quiet:
1677 1686 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1678 1687
1679 1688 def warn(self, *msg, **opts):
1680 1689 '''write warning message to output (stderr)
1681 1690
1682 1691 This adds an output label of "ui.warning".
1683 1692 '''
1684 1693 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1685 1694
1686 1695 def error(self, *msg, **opts):
1687 1696 '''write error message to output (stderr)
1688 1697
1689 1698 This adds an output label of "ui.error".
1690 1699 '''
1691 1700 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1692 1701
1693 1702 def note(self, *msg, **opts):
1694 1703 '''write note to output (if ui.verbose is True)
1695 1704
1696 1705 This adds an output label of "ui.note".
1697 1706 '''
1698 1707 if self.verbose:
1699 1708 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1700 1709
1701 1710 def debug(self, *msg, **opts):
1702 1711 '''write debug message to output (if ui.debugflag is True)
1703 1712
1704 1713 This adds an output label of "ui.debug".
1705 1714 '''
1706 1715 if self.debugflag:
1707 1716 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1708 1717 self.log(b'debug', b'%s', b''.join(msg))
1709 1718
1710 1719 # Aliases to defeat check-code.
1711 1720 statusnoi18n = status
1712 1721 notenoi18n = note
1713 1722 warnnoi18n = warn
1714 1723 writenoi18n = write
1715 1724
1716 1725 def edit(
1717 1726 self,
1718 1727 text,
1719 1728 user,
1720 1729 extra=None,
1721 1730 editform=None,
1722 1731 pending=None,
1723 1732 repopath=None,
1724 1733 action=None,
1725 1734 ):
1726 1735 if action is None:
1727 1736 self.develwarn(
1728 1737 b'action is None but will soon be a required '
1729 1738 b'parameter to ui.edit()'
1730 1739 )
1731 1740 extra_defaults = {
1732 1741 b'prefix': b'editor',
1733 1742 b'suffix': b'.txt',
1734 1743 }
1735 1744 if extra is not None:
1736 1745 if extra.get(b'suffix') is not None:
1737 1746 self.develwarn(
1738 1747 b'extra.suffix is not None but will soon be '
1739 1748 b'ignored by ui.edit()'
1740 1749 )
1741 1750 extra_defaults.update(extra)
1742 1751 extra = extra_defaults
1743 1752
1744 1753 if action == b'diff':
1745 1754 suffix = b'.diff'
1746 1755 elif action:
1747 1756 suffix = b'.%s.hg.txt' % action
1748 1757 else:
1749 1758 suffix = extra[b'suffix']
1750 1759
1751 1760 rdir = None
1752 1761 if self.configbool(b'experimental', b'editortmpinhg'):
1753 1762 rdir = repopath
1754 1763 (fd, name) = pycompat.mkstemp(
1755 1764 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1756 1765 )
1757 1766 try:
1758 1767 f = os.fdopen(fd, r'wb')
1759 1768 f.write(util.tonativeeol(text))
1760 1769 f.close()
1761 1770
1762 1771 environ = {b'HGUSER': user}
1763 1772 if b'transplant_source' in extra:
1764 1773 environ.update(
1765 1774 {b'HGREVISION': hex(extra[b'transplant_source'])}
1766 1775 )
1767 1776 for label in (b'intermediate-source', b'source', b'rebase_source'):
1768 1777 if label in extra:
1769 1778 environ.update({b'HGREVISION': extra[label]})
1770 1779 break
1771 1780 if editform:
1772 1781 environ.update({b'HGEDITFORM': editform})
1773 1782 if pending:
1774 1783 environ.update({b'HG_PENDING': pending})
1775 1784
1776 1785 editor = self.geteditor()
1777 1786
1778 1787 self.system(
1779 1788 b"%s \"%s\"" % (editor, name),
1780 1789 environ=environ,
1781 1790 onerr=error.Abort,
1782 1791 errprefix=_(b"edit failed"),
1783 1792 blockedtag=b'editor',
1784 1793 )
1785 1794
1786 1795 f = open(name, r'rb')
1787 1796 t = util.fromnativeeol(f.read())
1788 1797 f.close()
1789 1798 finally:
1790 1799 os.unlink(name)
1791 1800
1792 1801 return t
1793 1802
1794 1803 def system(
1795 1804 self,
1796 1805 cmd,
1797 1806 environ=None,
1798 1807 cwd=None,
1799 1808 onerr=None,
1800 1809 errprefix=None,
1801 1810 blockedtag=None,
1802 1811 ):
1803 1812 '''execute shell command with appropriate output stream. command
1804 1813 output will be redirected if fout is not stdout.
1805 1814
1806 1815 if command fails and onerr is None, return status, else raise onerr
1807 1816 object as exception.
1808 1817 '''
1809 1818 if blockedtag is None:
1810 1819 # Long cmds tend to be because of an absolute path on cmd. Keep
1811 1820 # the tail end instead
1812 1821 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1813 1822 blockedtag = b'unknown_system_' + cmdsuffix
1814 1823 out = self._fout
1815 1824 if any(s[1] for s in self._bufferstates):
1816 1825 out = self
1817 1826 with self.timeblockedsection(blockedtag):
1818 1827 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1819 1828 if rc and onerr:
1820 1829 errmsg = b'%s %s' % (
1821 1830 os.path.basename(cmd.split(None, 1)[0]),
1822 1831 procutil.explainexit(rc),
1823 1832 )
1824 1833 if errprefix:
1825 1834 errmsg = b'%s: %s' % (errprefix, errmsg)
1826 1835 raise onerr(errmsg)
1827 1836 return rc
1828 1837
1829 1838 def _runsystem(self, cmd, environ, cwd, out):
1830 1839 """actually execute the given shell command (can be overridden by
1831 1840 extensions like chg)"""
1832 1841 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1833 1842
1834 1843 def traceback(self, exc=None, force=False):
1835 1844 '''print exception traceback if traceback printing enabled or forced.
1836 1845 only to call in exception handler. returns true if traceback
1837 1846 printed.'''
1838 1847 if self.tracebackflag or force:
1839 1848 if exc is None:
1840 1849 exc = sys.exc_info()
1841 1850 cause = getattr(exc[1], 'cause', None)
1842 1851
1843 1852 if cause is not None:
1844 1853 causetb = traceback.format_tb(cause[2])
1845 1854 exctb = traceback.format_tb(exc[2])
1846 1855 exconly = traceback.format_exception_only(cause[0], cause[1])
1847 1856
1848 1857 # exclude frame where 'exc' was chained and rethrown from exctb
1849 1858 self.write_err(
1850 1859 b'Traceback (most recent call last):\n',
1851 1860 b''.join(exctb[:-1]),
1852 1861 b''.join(causetb),
1853 1862 b''.join(exconly),
1854 1863 )
1855 1864 else:
1856 1865 output = traceback.format_exception(exc[0], exc[1], exc[2])
1857 1866 self.write_err(encoding.strtolocal(r''.join(output)))
1858 1867 return self.tracebackflag or force
1859 1868
1860 1869 def geteditor(self):
1861 1870 '''return editor to use'''
1862 1871 if pycompat.sysplatform == b'plan9':
1863 1872 # vi is the MIPS instruction simulator on Plan 9. We
1864 1873 # instead default to E to plumb commit messages to
1865 1874 # avoid confusion.
1866 1875 editor = b'E'
1867 1876 else:
1868 1877 editor = b'vi'
1869 1878 return encoding.environ.get(b"HGEDITOR") or self.config(
1870 1879 b"ui", b"editor", editor
1871 1880 )
1872 1881
1873 1882 @util.propertycache
1874 1883 def _progbar(self):
1875 1884 """setup the progbar singleton to the ui object"""
1876 1885 if (
1877 1886 self.quiet
1878 1887 or self.debugflag
1879 1888 or self.configbool(b'progress', b'disable')
1880 1889 or not progress.shouldprint(self)
1881 1890 ):
1882 1891 return None
1883 1892 return getprogbar(self)
1884 1893
1885 1894 def _progclear(self):
1886 1895 """clear progress bar output if any. use it before any output"""
1887 1896 if not haveprogbar(): # nothing loaded yet
1888 1897 return
1889 1898 if self._progbar is not None and self._progbar.printed:
1890 1899 self._progbar.clear()
1891 1900
1892 1901 def progress(self, topic, pos, item=b"", unit=b"", total=None):
1893 1902 '''show a progress message
1894 1903
1895 1904 By default a textual progress bar will be displayed if an operation
1896 1905 takes too long. 'topic' is the current operation, 'item' is a
1897 1906 non-numeric marker of the current position (i.e. the currently
1898 1907 in-process file), 'pos' is the current numeric position (i.e.
1899 1908 revision, bytes, etc.), unit is a corresponding unit label,
1900 1909 and total is the highest expected pos.
1901 1910
1902 1911 Multiple nested topics may be active at a time.
1903 1912
1904 1913 All topics should be marked closed by setting pos to None at
1905 1914 termination.
1906 1915 '''
1907 1916 self.deprecwarn(
1908 1917 b"use ui.makeprogress() instead of ui.progress()", b"5.1"
1909 1918 )
1910 1919 progress = self.makeprogress(topic, unit, total)
1911 1920 if pos is not None:
1912 1921 progress.update(pos, item=item)
1913 1922 else:
1914 1923 progress.complete()
1915 1924
1916 1925 def makeprogress(self, topic, unit=b"", total=None):
1917 1926 """Create a progress helper for the specified topic"""
1918 1927 if getattr(self._fmsgerr, 'structured', False):
1919 1928 # channel for machine-readable output with metadata, just send
1920 1929 # raw information
1921 1930 # TODO: consider porting some useful information (e.g. estimated
1922 1931 # time) from progbar. we might want to support update delay to
1923 1932 # reduce the cost of transferring progress messages.
1924 1933 def updatebar(topic, pos, item, unit, total):
1925 1934 self._fmsgerr.write(
1926 1935 None,
1927 1936 type=b'progress',
1928 1937 topic=topic,
1929 1938 pos=pos,
1930 1939 item=item,
1931 1940 unit=unit,
1932 1941 total=total,
1933 1942 )
1934 1943
1935 1944 elif self._progbar is not None:
1936 1945 updatebar = self._progbar.progress
1937 1946 else:
1938 1947
1939 1948 def updatebar(topic, pos, item, unit, total):
1940 1949 pass
1941 1950
1942 1951 return scmutil.progress(self, updatebar, topic, unit, total)
1943 1952
1944 1953 def getlogger(self, name):
1945 1954 """Returns a logger of the given name; or None if not registered"""
1946 1955 return self._loggers.get(name)
1947 1956
1948 1957 def setlogger(self, name, logger):
1949 1958 """Install logger which can be identified later by the given name
1950 1959
1951 1960 More than one loggers can be registered. Use extension or module
1952 1961 name to uniquely identify the logger instance.
1953 1962 """
1954 1963 self._loggers[name] = logger
1955 1964
1956 1965 def log(self, event, msgfmt, *msgargs, **opts):
1957 1966 '''hook for logging facility extensions
1958 1967
1959 1968 event should be a readily-identifiable subsystem, which will
1960 1969 allow filtering.
1961 1970
1962 1971 msgfmt should be a newline-terminated format string to log, and
1963 1972 *msgargs are %-formatted into it.
1964 1973
1965 1974 **opts currently has no defined meanings.
1966 1975 '''
1967 1976 if not self._loggers:
1968 1977 return
1969 1978 activeloggers = [
1970 1979 l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
1971 1980 ]
1972 1981 if not activeloggers:
1973 1982 return
1974 1983 msg = msgfmt % msgargs
1975 1984 opts = pycompat.byteskwargs(opts)
1976 1985 # guard against recursion from e.g. ui.debug()
1977 1986 registeredloggers = self._loggers
1978 1987 self._loggers = {}
1979 1988 try:
1980 1989 for logger in activeloggers:
1981 1990 logger.log(self, event, msg, opts)
1982 1991 finally:
1983 1992 self._loggers = registeredloggers
1984 1993
1985 1994 def label(self, msg, label):
1986 1995 '''style msg based on supplied label
1987 1996
1988 1997 If some color mode is enabled, this will add the necessary control
1989 1998 characters to apply such color. In addition, 'debug' color mode adds
1990 1999 markup showing which label affects a piece of text.
1991 2000
1992 2001 ui.write(s, 'label') is equivalent to
1993 2002 ui.write(ui.label(s, 'label')).
1994 2003 '''
1995 2004 if self._colormode is not None:
1996 2005 return color.colorlabel(self, msg, label)
1997 2006 return msg
1998 2007
1999 2008 def develwarn(self, msg, stacklevel=1, config=None):
2000 2009 """issue a developer warning message
2001 2010
2002 2011 Use 'stacklevel' to report the offender some layers further up in the
2003 2012 stack.
2004 2013 """
2005 2014 if not self.configbool(b'devel', b'all-warnings'):
2006 2015 if config is None or not self.configbool(b'devel', config):
2007 2016 return
2008 2017 msg = b'devel-warn: ' + msg
2009 2018 stacklevel += 1 # get in develwarn
2010 2019 if self.tracebackflag:
2011 2020 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2012 2021 self.log(
2013 2022 b'develwarn',
2014 2023 b'%s at:\n%s'
2015 2024 % (msg, b''.join(util.getstackframes(stacklevel))),
2016 2025 )
2017 2026 else:
2018 2027 curframe = inspect.currentframe()
2019 2028 calframe = inspect.getouterframes(curframe, 2)
2020 2029 fname, lineno, fmsg = calframe[stacklevel][1:4]
2021 2030 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2022 2031 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2023 2032 self.log(
2024 2033 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2025 2034 )
2026 2035 curframe = calframe = None # avoid cycles
2027 2036
2028 2037 def deprecwarn(self, msg, version, stacklevel=2):
2029 2038 """issue a deprecation warning
2030 2039
2031 2040 - msg: message explaining what is deprecated and how to upgrade,
2032 2041 - version: last version where the API will be supported,
2033 2042 """
2034 2043 if not (
2035 2044 self.configbool(b'devel', b'all-warnings')
2036 2045 or self.configbool(b'devel', b'deprec-warn')
2037 2046 ):
2038 2047 return
2039 2048 msg += (
2040 2049 b"\n(compatibility will be dropped after Mercurial-%s,"
2041 2050 b" update your code.)"
2042 2051 ) % version
2043 2052 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2044 2053
2045 2054 def exportableenviron(self):
2046 2055 """The environment variables that are safe to export, e.g. through
2047 2056 hgweb.
2048 2057 """
2049 2058 return self._exportableenviron
2050 2059
2051 2060 @contextlib.contextmanager
2052 2061 def configoverride(self, overrides, source=b""):
2053 2062 """Context manager for temporary config overrides
2054 2063 `overrides` must be a dict of the following structure:
2055 2064 {(section, name) : value}"""
2056 2065 backups = {}
2057 2066 try:
2058 2067 for (section, name), value in overrides.items():
2059 2068 backups[(section, name)] = self.backupconfig(section, name)
2060 2069 self.setconfig(section, name, value, source)
2061 2070 yield
2062 2071 finally:
2063 2072 for __, backup in backups.items():
2064 2073 self.restoreconfig(backup)
2065 2074 # just restoring ui.quiet config to the previous value is not enough
2066 2075 # as it does not update ui.quiet class member
2067 2076 if (b'ui', b'quiet') in overrides:
2068 2077 self.fixconfig(section=b'ui')
2069 2078
2070 2079
2071 2080 class paths(dict):
2072 2081 """Represents a collection of paths and their configs.
2073 2082
2074 2083 Data is initially derived from ui instances and the config files they have
2075 2084 loaded.
2076 2085 """
2077 2086
2078 2087 def __init__(self, ui):
2079 2088 dict.__init__(self)
2080 2089
2081 2090 for name, loc in ui.configitems(b'paths', ignoresub=True):
2082 2091 # No location is the same as not existing.
2083 2092 if not loc:
2084 2093 continue
2085 2094 loc, sub = ui.configsuboptions(b'paths', name)
2086 2095 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
2087 2096
2088 2097 def getpath(self, name, default=None):
2089 2098 """Return a ``path`` from a string, falling back to default.
2090 2099
2091 2100 ``name`` can be a named path or locations. Locations are filesystem
2092 2101 paths or URIs.
2093 2102
2094 2103 Returns None if ``name`` is not a registered path, a URI, or a local
2095 2104 path to a repo.
2096 2105 """
2097 2106 # Only fall back to default if no path was requested.
2098 2107 if name is None:
2099 2108 if not default:
2100 2109 default = ()
2101 2110 elif not isinstance(default, (tuple, list)):
2102 2111 default = (default,)
2103 2112 for k in default:
2104 2113 try:
2105 2114 return self[k]
2106 2115 except KeyError:
2107 2116 continue
2108 2117 return None
2109 2118
2110 2119 # Most likely empty string.
2111 2120 # This may need to raise in the future.
2112 2121 if not name:
2113 2122 return None
2114 2123
2115 2124 try:
2116 2125 return self[name]
2117 2126 except KeyError:
2118 2127 # Try to resolve as a local path or URI.
2119 2128 try:
2120 2129 # We don't pass sub-options in, so no need to pass ui instance.
2121 2130 return path(None, None, rawloc=name)
2122 2131 except ValueError:
2123 2132 raise error.RepoError(_(b'repository %s does not exist') % name)
2124 2133
2125 2134
2126 2135 _pathsuboptions = {}
2127 2136
2128 2137
2129 2138 def pathsuboption(option, attr):
2130 2139 """Decorator used to declare a path sub-option.
2131 2140
2132 2141 Arguments are the sub-option name and the attribute it should set on
2133 2142 ``path`` instances.
2134 2143
2135 2144 The decorated function will receive as arguments a ``ui`` instance,
2136 2145 ``path`` instance, and the string value of this option from the config.
2137 2146 The function should return the value that will be set on the ``path``
2138 2147 instance.
2139 2148
2140 2149 This decorator can be used to perform additional verification of
2141 2150 sub-options and to change the type of sub-options.
2142 2151 """
2143 2152
2144 2153 def register(func):
2145 2154 _pathsuboptions[option] = (attr, func)
2146 2155 return func
2147 2156
2148 2157 return register
2149 2158
2150 2159
2151 2160 @pathsuboption(b'pushurl', b'pushloc')
2152 2161 def pushurlpathoption(ui, path, value):
2153 2162 u = util.url(value)
2154 2163 # Actually require a URL.
2155 2164 if not u.scheme:
2156 2165 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
2157 2166 return None
2158 2167
2159 2168 # Don't support the #foo syntax in the push URL to declare branch to
2160 2169 # push.
2161 2170 if u.fragment:
2162 2171 ui.warn(
2163 2172 _(
2164 2173 b'("#fragment" in paths.%s:pushurl not supported; '
2165 2174 b'ignoring)\n'
2166 2175 )
2167 2176 % path.name
2168 2177 )
2169 2178 u.fragment = None
2170 2179
2171 2180 return bytes(u)
2172 2181
2173 2182
2174 2183 @pathsuboption(b'pushrev', b'pushrev')
2175 2184 def pushrevpathoption(ui, path, value):
2176 2185 return value
2177 2186
2178 2187
2179 2188 class path(object):
2180 2189 """Represents an individual path and its configuration."""
2181 2190
2182 2191 def __init__(self, ui, name, rawloc=None, suboptions=None):
2183 2192 """Construct a path from its config options.
2184 2193
2185 2194 ``ui`` is the ``ui`` instance the path is coming from.
2186 2195 ``name`` is the symbolic name of the path.
2187 2196 ``rawloc`` is the raw location, as defined in the config.
2188 2197 ``pushloc`` is the raw locations pushes should be made to.
2189 2198
2190 2199 If ``name`` is not defined, we require that the location be a) a local
2191 2200 filesystem path with a .hg directory or b) a URL. If not,
2192 2201 ``ValueError`` is raised.
2193 2202 """
2194 2203 if not rawloc:
2195 2204 raise ValueError(b'rawloc must be defined')
2196 2205
2197 2206 # Locations may define branches via syntax <base>#<branch>.
2198 2207 u = util.url(rawloc)
2199 2208 branch = None
2200 2209 if u.fragment:
2201 2210 branch = u.fragment
2202 2211 u.fragment = None
2203 2212
2204 2213 self.url = u
2205 2214 self.branch = branch
2206 2215
2207 2216 self.name = name
2208 2217 self.rawloc = rawloc
2209 2218 self.loc = b'%s' % u
2210 2219
2211 2220 # When given a raw location but not a symbolic name, validate the
2212 2221 # location is valid.
2213 2222 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2214 2223 raise ValueError(
2215 2224 b'location is not a URL or path to a local '
2216 2225 b'repo: %s' % rawloc
2217 2226 )
2218 2227
2219 2228 suboptions = suboptions or {}
2220 2229
2221 2230 # Now process the sub-options. If a sub-option is registered, its
2222 2231 # attribute will always be present. The value will be None if there
2223 2232 # was no valid sub-option.
2224 2233 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
2225 2234 if suboption not in suboptions:
2226 2235 setattr(self, attr, None)
2227 2236 continue
2228 2237
2229 2238 value = func(ui, self, suboptions[suboption])
2230 2239 setattr(self, attr, value)
2231 2240
2232 2241 def _isvalidlocalpath(self, path):
2233 2242 """Returns True if the given path is a potentially valid repository.
2234 2243 This is its own function so that extensions can change the definition of
2235 2244 'valid' in this case (like when pulling from a git repo into a hg
2236 2245 one)."""
2237 2246 try:
2238 2247 return os.path.isdir(os.path.join(path, b'.hg'))
2239 2248 # Python 2 may return TypeError. Python 3, ValueError.
2240 2249 except (TypeError, ValueError):
2241 2250 return False
2242 2251
2243 2252 @property
2244 2253 def suboptions(self):
2245 2254 """Return sub-options and their values for this path.
2246 2255
2247 2256 This is intended to be used for presentation purposes.
2248 2257 """
2249 2258 d = {}
2250 2259 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
2251 2260 value = getattr(self, attr)
2252 2261 if value is not None:
2253 2262 d[subopt] = value
2254 2263 return d
2255 2264
2256 2265
2257 2266 # we instantiate one globally shared progress bar to avoid
2258 2267 # competing progress bars when multiple UI objects get created
2259 2268 _progresssingleton = None
2260 2269
2261 2270
2262 2271 def getprogbar(ui):
2263 2272 global _progresssingleton
2264 2273 if _progresssingleton is None:
2265 2274 # passing 'ui' object to the singleton is fishy,
2266 2275 # this is how the extension used to work but feel free to rework it.
2267 2276 _progresssingleton = progress.progbar(ui)
2268 2277 return _progresssingleton
2269 2278
2270 2279
2271 2280 def haveprogbar():
2272 2281 return _progresssingleton is not None
2273 2282
2274 2283
2275 2284 def _selectmsgdests(ui):
2276 2285 name = ui.config(b'ui', b'message-output')
2277 2286 if name == b'channel':
2278 2287 if ui.fmsg:
2279 2288 return ui.fmsg, ui.fmsg
2280 2289 else:
2281 2290 # fall back to ferr if channel isn't ready so that status/error
2282 2291 # messages can be printed
2283 2292 return ui.ferr, ui.ferr
2284 2293 if name == b'stdio':
2285 2294 return ui.fout, ui.ferr
2286 2295 if name == b'stderr':
2287 2296 return ui.ferr, ui.ferr
2288 2297 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2289 2298
2290 2299
2291 2300 def _writemsgwith(write, dest, *args, **opts):
2292 2301 """Write ui message with the given ui._write*() function
2293 2302
2294 2303 The specified message type is translated to 'ui.<type>' label if the dest
2295 2304 isn't a structured channel, so that the message will be colorized.
2296 2305 """
2297 2306 # TODO: maybe change 'type' to a mandatory option
2298 2307 if r'type' in opts and not getattr(dest, 'structured', False):
2299 2308 opts[r'label'] = opts.get(r'label', b'') + b' ui.%s' % opts.pop(r'type')
2300 2309 write(dest, *args, **opts)
General Comments 0
You need to be logged in to leave comments. Login now