##// END OF EJS Templates
ui: use a context manager to handle file streams in edit()...
Matt Harbison -
r44328:e5f69e3b default
parent child Browse files
Show More
@@ -1,2311 +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, '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 """get a path config item, expanded relative to repo root or config
657 657 file"""
658 658 v = self.config(section, name, default, untrusted)
659 659 if v is None:
660 660 return None
661 661 if not os.path.isabs(v) or b"://" not in v:
662 662 src = self.configsource(section, name, untrusted)
663 663 if b':' in src:
664 664 base = os.path.dirname(src.rsplit(b':')[0])
665 665 v = os.path.join(base, os.path.expanduser(v))
666 666 return v
667 667
668 668 def configbool(self, section, name, default=_unset, untrusted=False):
669 669 """parse a configuration element as a boolean
670 670
671 671 >>> u = ui(); s = b'foo'
672 672 >>> u.setconfig(s, b'true', b'yes')
673 673 >>> u.configbool(s, b'true')
674 674 True
675 675 >>> u.setconfig(s, b'false', b'no')
676 676 >>> u.configbool(s, b'false')
677 677 False
678 678 >>> u.configbool(s, b'unknown')
679 679 False
680 680 >>> u.configbool(s, b'unknown', True)
681 681 True
682 682 >>> u.setconfig(s, b'invalid', b'somevalue')
683 683 >>> u.configbool(s, b'invalid')
684 684 Traceback (most recent call last):
685 685 ...
686 686 ConfigError: foo.invalid is not a boolean ('somevalue')
687 687 """
688 688
689 689 v = self._config(section, name, default, untrusted=untrusted)
690 690 if v is None:
691 691 return v
692 692 if v is _unset:
693 693 if default is _unset:
694 694 return False
695 695 return default
696 696 if isinstance(v, bool):
697 697 return v
698 698 b = stringutil.parsebool(v)
699 699 if b is None:
700 700 raise error.ConfigError(
701 701 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
702 702 )
703 703 return b
704 704
705 705 def configwith(
706 706 self, convert, section, name, default=_unset, desc=None, untrusted=False
707 707 ):
708 708 """parse a configuration element with a conversion function
709 709
710 710 >>> u = ui(); s = b'foo'
711 711 >>> u.setconfig(s, b'float1', b'42')
712 712 >>> u.configwith(float, s, b'float1')
713 713 42.0
714 714 >>> u.setconfig(s, b'float2', b'-4.25')
715 715 >>> u.configwith(float, s, b'float2')
716 716 -4.25
717 717 >>> u.configwith(float, s, b'unknown', 7)
718 718 7.0
719 719 >>> u.setconfig(s, b'invalid', b'somevalue')
720 720 >>> u.configwith(float, s, b'invalid')
721 721 Traceback (most recent call last):
722 722 ...
723 723 ConfigError: foo.invalid is not a valid float ('somevalue')
724 724 >>> u.configwith(float, s, b'invalid', desc=b'womble')
725 725 Traceback (most recent call last):
726 726 ...
727 727 ConfigError: foo.invalid is not a valid womble ('somevalue')
728 728 """
729 729
730 730 v = self.config(section, name, default, untrusted)
731 731 if v is None:
732 732 return v # do not attempt to convert None
733 733 try:
734 734 return convert(v)
735 735 except (ValueError, error.ParseError):
736 736 if desc is None:
737 737 desc = pycompat.sysbytes(convert.__name__)
738 738 raise error.ConfigError(
739 739 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
740 740 )
741 741
742 742 def configint(self, section, name, default=_unset, untrusted=False):
743 743 """parse a configuration element as an integer
744 744
745 745 >>> u = ui(); s = b'foo'
746 746 >>> u.setconfig(s, b'int1', b'42')
747 747 >>> u.configint(s, b'int1')
748 748 42
749 749 >>> u.setconfig(s, b'int2', b'-42')
750 750 >>> u.configint(s, b'int2')
751 751 -42
752 752 >>> u.configint(s, b'unknown', 7)
753 753 7
754 754 >>> u.setconfig(s, b'invalid', b'somevalue')
755 755 >>> u.configint(s, b'invalid')
756 756 Traceback (most recent call last):
757 757 ...
758 758 ConfigError: foo.invalid is not a valid integer ('somevalue')
759 759 """
760 760
761 761 return self.configwith(
762 762 int, section, name, default, b'integer', untrusted
763 763 )
764 764
765 765 def configbytes(self, section, name, default=_unset, untrusted=False):
766 766 """parse a configuration element as a quantity in bytes
767 767
768 768 Units can be specified as b (bytes), k or kb (kilobytes), m or
769 769 mb (megabytes), g or gb (gigabytes).
770 770
771 771 >>> u = ui(); s = b'foo'
772 772 >>> u.setconfig(s, b'val1', b'42')
773 773 >>> u.configbytes(s, b'val1')
774 774 42
775 775 >>> u.setconfig(s, b'val2', b'42.5 kb')
776 776 >>> u.configbytes(s, b'val2')
777 777 43520
778 778 >>> u.configbytes(s, b'unknown', b'7 MB')
779 779 7340032
780 780 >>> u.setconfig(s, b'invalid', b'somevalue')
781 781 >>> u.configbytes(s, b'invalid')
782 782 Traceback (most recent call last):
783 783 ...
784 784 ConfigError: foo.invalid is not a byte quantity ('somevalue')
785 785 """
786 786
787 787 value = self._config(section, name, default, untrusted)
788 788 if value is _unset:
789 789 if default is _unset:
790 790 default = 0
791 791 value = default
792 792 if not isinstance(value, bytes):
793 793 return value
794 794 try:
795 795 return util.sizetoint(value)
796 796 except error.ParseError:
797 797 raise error.ConfigError(
798 798 _(b"%s.%s is not a byte quantity ('%s')")
799 799 % (section, name, value)
800 800 )
801 801
802 802 def configlist(self, section, name, default=_unset, untrusted=False):
803 803 """parse a configuration element as a list of comma/space separated
804 804 strings
805 805
806 806 >>> u = ui(); s = b'foo'
807 807 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
808 808 >>> u.configlist(s, b'list1')
809 809 ['this', 'is', 'a small', 'test']
810 810 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
811 811 >>> u.configlist(s, b'list2')
812 812 ['this', 'is', 'a small', 'test']
813 813 """
814 814 # default is not always a list
815 815 v = self.configwith(
816 816 config.parselist, section, name, default, b'list', untrusted
817 817 )
818 818 if isinstance(v, bytes):
819 819 return config.parselist(v)
820 820 elif v is None:
821 821 return []
822 822 return v
823 823
824 824 def configdate(self, section, name, default=_unset, untrusted=False):
825 825 """parse a configuration element as a tuple of ints
826 826
827 827 >>> u = ui(); s = b'foo'
828 828 >>> u.setconfig(s, b'date', b'0 0')
829 829 >>> u.configdate(s, b'date')
830 830 (0, 0)
831 831 """
832 832 if self.config(section, name, default, untrusted):
833 833 return self.configwith(
834 834 dateutil.parsedate, section, name, default, b'date', untrusted
835 835 )
836 836 if default is _unset:
837 837 return None
838 838 return default
839 839
840 840 def configdefault(self, section, name):
841 841 """returns the default value of the config item"""
842 842 item = self._knownconfig.get(section, {}).get(name)
843 843 itemdefault = None
844 844 if item is not None:
845 845 if callable(item.default):
846 846 itemdefault = item.default()
847 847 else:
848 848 itemdefault = item.default
849 849 return itemdefault
850 850
851 851 def hasconfig(self, section, name, untrusted=False):
852 852 return self._data(untrusted).hasitem(section, name)
853 853
854 854 def has_section(self, section, untrusted=False):
855 855 '''tell whether section exists in config.'''
856 856 return section in self._data(untrusted)
857 857
858 858 def configitems(self, section, untrusted=False, ignoresub=False):
859 859 items = self._data(untrusted).items(section)
860 860 if ignoresub:
861 861 items = [i for i in items if b':' not in i[0]]
862 862 if self.debugflag and not untrusted and self._reportuntrusted:
863 863 for k, v in self._ucfg.items(section):
864 864 if self._tcfg.get(section, k) != v:
865 865 self.debug(
866 866 b"ignoring untrusted configuration option "
867 867 b"%s.%s = %s\n" % (section, k, v)
868 868 )
869 869 return items
870 870
871 871 def walkconfig(self, untrusted=False):
872 872 cfg = self._data(untrusted)
873 873 for section in cfg.sections():
874 874 for name, value in self.configitems(section, untrusted):
875 875 yield section, name, value
876 876
877 877 def plain(self, feature=None):
878 878 '''is plain mode active?
879 879
880 880 Plain mode means that all configuration variables which affect
881 881 the behavior and output of Mercurial should be
882 882 ignored. Additionally, the output should be stable,
883 883 reproducible and suitable for use in scripts or applications.
884 884
885 885 The only way to trigger plain mode is by setting either the
886 886 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
887 887
888 888 The return value can either be
889 889 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
890 890 - False if feature is disabled by default and not included in HGPLAIN
891 891 - True otherwise
892 892 '''
893 893 if (
894 894 b'HGPLAIN' not in encoding.environ
895 895 and b'HGPLAINEXCEPT' not in encoding.environ
896 896 ):
897 897 return False
898 898 exceptions = (
899 899 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
900 900 )
901 901 # TODO: add support for HGPLAIN=+feature,-feature syntax
902 902 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
903 903 b','
904 904 ):
905 905 exceptions.append(b'strictflags')
906 906 if feature and exceptions:
907 907 return feature not in exceptions
908 908 return True
909 909
910 910 def username(self, acceptempty=False):
911 911 """Return default username to be used in commits.
912 912
913 913 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
914 914 and stop searching if one of these is set.
915 915 If not found and acceptempty is True, returns None.
916 916 If not found and ui.askusername is True, ask the user, else use
917 917 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
918 918 If no username could be found, raise an Abort error.
919 919 """
920 920 user = encoding.environ.get(b"HGUSER")
921 921 if user is None:
922 922 user = self.config(b"ui", b"username")
923 923 if user is not None:
924 924 user = os.path.expandvars(user)
925 925 if user is None:
926 926 user = encoding.environ.get(b"EMAIL")
927 927 if user is None and acceptempty:
928 928 return user
929 929 if user is None and self.configbool(b"ui", b"askusername"):
930 930 user = self.prompt(_(b"enter a commit username:"), default=None)
931 931 if user is None and not self.interactive():
932 932 try:
933 933 user = b'%s@%s' % (
934 934 procutil.getuser(),
935 935 encoding.strtolocal(socket.getfqdn()),
936 936 )
937 937 self.warn(_(b"no username found, using '%s' instead\n") % user)
938 938 except KeyError:
939 939 pass
940 940 if not user:
941 941 raise error.Abort(
942 942 _(b'no username supplied'),
943 943 hint=_(b"use 'hg config --edit' " b'to set your username'),
944 944 )
945 945 if b"\n" in user:
946 946 raise error.Abort(
947 947 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
948 948 )
949 949 return user
950 950
951 951 def shortuser(self, user):
952 952 """Return a short representation of a user name or email address."""
953 953 if not self.verbose:
954 954 user = stringutil.shortuser(user)
955 955 return user
956 956
957 957 def expandpath(self, loc, default=None):
958 958 """Return repository location relative to cwd or from [paths]"""
959 959 try:
960 960 p = self.paths.getpath(loc)
961 961 if p:
962 962 return p.rawloc
963 963 except error.RepoError:
964 964 pass
965 965
966 966 if default:
967 967 try:
968 968 p = self.paths.getpath(default)
969 969 if p:
970 970 return p.rawloc
971 971 except error.RepoError:
972 972 pass
973 973
974 974 return loc
975 975
976 976 @util.propertycache
977 977 def paths(self):
978 978 return paths(self)
979 979
980 980 @property
981 981 def fout(self):
982 982 return self._fout
983 983
984 984 @fout.setter
985 985 def fout(self, f):
986 986 self._fout = f
987 987 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
988 988
989 989 @property
990 990 def ferr(self):
991 991 return self._ferr
992 992
993 993 @ferr.setter
994 994 def ferr(self, f):
995 995 self._ferr = f
996 996 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
997 997
998 998 @property
999 999 def fin(self):
1000 1000 return self._fin
1001 1001
1002 1002 @fin.setter
1003 1003 def fin(self, f):
1004 1004 self._fin = f
1005 1005
1006 1006 @property
1007 1007 def fmsg(self):
1008 1008 """Stream dedicated for status/error messages; may be None if
1009 1009 fout/ferr are used"""
1010 1010 return self._fmsg
1011 1011
1012 1012 @fmsg.setter
1013 1013 def fmsg(self, f):
1014 1014 self._fmsg = f
1015 1015 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1016 1016
1017 1017 def pushbuffer(self, error=False, subproc=False, labeled=False):
1018 1018 """install a buffer to capture standard output of the ui object
1019 1019
1020 1020 If error is True, the error output will be captured too.
1021 1021
1022 1022 If subproc is True, output from subprocesses (typically hooks) will be
1023 1023 captured too.
1024 1024
1025 1025 If labeled is True, any labels associated with buffered
1026 1026 output will be handled. By default, this has no effect
1027 1027 on the output returned, but extensions and GUI tools may
1028 1028 handle this argument and returned styled output. If output
1029 1029 is being buffered so it can be captured and parsed or
1030 1030 processed, labeled should not be set to True.
1031 1031 """
1032 1032 self._buffers.append([])
1033 1033 self._bufferstates.append((error, subproc, labeled))
1034 1034 self._bufferapplylabels = labeled
1035 1035
1036 1036 def popbuffer(self):
1037 1037 '''pop the last buffer and return the buffered output'''
1038 1038 self._bufferstates.pop()
1039 1039 if self._bufferstates:
1040 1040 self._bufferapplylabels = self._bufferstates[-1][2]
1041 1041 else:
1042 1042 self._bufferapplylabels = None
1043 1043
1044 1044 return b"".join(self._buffers.pop())
1045 1045
1046 1046 def _isbuffered(self, dest):
1047 1047 if dest is self._fout:
1048 1048 return bool(self._buffers)
1049 1049 if dest is self._ferr:
1050 1050 return bool(self._bufferstates and self._bufferstates[-1][0])
1051 1051 return False
1052 1052
1053 1053 def canwritewithoutlabels(self):
1054 1054 '''check if write skips the label'''
1055 1055 if self._buffers and not self._bufferapplylabels:
1056 1056 return True
1057 1057 return self._colormode is None
1058 1058
1059 1059 def canbatchlabeledwrites(self):
1060 1060 '''check if write calls with labels are batchable'''
1061 1061 # Windows color printing is special, see ``write``.
1062 1062 return self._colormode != b'win32'
1063 1063
1064 1064 def write(self, *args, **opts):
1065 1065 '''write args to output
1066 1066
1067 1067 By default, this method simply writes to the buffer or stdout.
1068 1068 Color mode can be set on the UI class to have the output decorated
1069 1069 with color modifier before being written to stdout.
1070 1070
1071 1071 The color used is controlled by an optional keyword argument, "label".
1072 1072 This should be a string containing label names separated by space.
1073 1073 Label names take the form of "topic.type". For example, ui.debug()
1074 1074 issues a label of "ui.debug".
1075 1075
1076 1076 Progress reports via stderr are normally cleared before writing as
1077 1077 stdout and stderr go to the same terminal. This can be skipped with
1078 1078 the optional keyword argument "keepprogressbar". The progress bar
1079 1079 will continue to occupy a partial line on stderr in that case.
1080 1080 This functionality is intended when Mercurial acts as data source
1081 1081 in a pipe.
1082 1082
1083 1083 When labeling output for a specific command, a label of
1084 1084 "cmdname.type" is recommended. For example, status issues
1085 1085 a label of "status.modified" for modified files.
1086 1086 '''
1087 1087 dest = self._fout
1088 1088
1089 1089 # inlined _write() for speed
1090 1090 if self._buffers:
1091 1091 label = opts.get('label', b'')
1092 1092 if label and self._bufferapplylabels:
1093 1093 self._buffers[-1].extend(self.label(a, label) for a in args)
1094 1094 else:
1095 1095 self._buffers[-1].extend(args)
1096 1096 return
1097 1097
1098 1098 # inlined _writenobuf() for speed
1099 1099 if not opts.get('keepprogressbar', False):
1100 1100 self._progclear()
1101 1101 msg = b''.join(args)
1102 1102
1103 1103 # opencode timeblockedsection because this is a critical path
1104 1104 starttime = util.timer()
1105 1105 try:
1106 1106 if self._colormode == b'win32':
1107 1107 # windows color printing is its own can of crab, defer to
1108 1108 # the color module and that is it.
1109 1109 color.win32print(self, dest.write, msg, **opts)
1110 1110 else:
1111 1111 if self._colormode is not None:
1112 1112 label = opts.get('label', b'')
1113 1113 msg = self.label(msg, label)
1114 1114 dest.write(msg)
1115 1115 except IOError as err:
1116 1116 raise error.StdioError(err)
1117 1117 finally:
1118 1118 self._blockedtimes[b'stdio_blocked'] += (
1119 1119 util.timer() - starttime
1120 1120 ) * 1000
1121 1121
1122 1122 def write_err(self, *args, **opts):
1123 1123 self._write(self._ferr, *args, **opts)
1124 1124
1125 1125 def _write(self, dest, *args, **opts):
1126 1126 # update write() as well if you touch this code
1127 1127 if self._isbuffered(dest):
1128 1128 label = opts.get('label', b'')
1129 1129 if label and self._bufferapplylabels:
1130 1130 self._buffers[-1].extend(self.label(a, label) for a in args)
1131 1131 else:
1132 1132 self._buffers[-1].extend(args)
1133 1133 else:
1134 1134 self._writenobuf(dest, *args, **opts)
1135 1135
1136 1136 def _writenobuf(self, dest, *args, **opts):
1137 1137 # update write() as well if you touch this code
1138 1138 if not opts.get('keepprogressbar', False):
1139 1139 self._progclear()
1140 1140 msg = b''.join(args)
1141 1141
1142 1142 # opencode timeblockedsection because this is a critical path
1143 1143 starttime = util.timer()
1144 1144 try:
1145 1145 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1146 1146 self._fout.flush()
1147 1147 if getattr(dest, 'structured', False):
1148 1148 # channel for machine-readable output with metadata, where
1149 1149 # no extra colorization is necessary.
1150 1150 dest.write(msg, **opts)
1151 1151 elif self._colormode == b'win32':
1152 1152 # windows color printing is its own can of crab, defer to
1153 1153 # the color module and that is it.
1154 1154 color.win32print(self, dest.write, msg, **opts)
1155 1155 else:
1156 1156 if self._colormode is not None:
1157 1157 label = opts.get('label', b'')
1158 1158 msg = self.label(msg, label)
1159 1159 dest.write(msg)
1160 1160 # stderr may be buffered under win32 when redirected to files,
1161 1161 # including stdout.
1162 1162 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1163 1163 dest.flush()
1164 1164 except IOError as err:
1165 1165 if dest is self._ferr and err.errno in (
1166 1166 errno.EPIPE,
1167 1167 errno.EIO,
1168 1168 errno.EBADF,
1169 1169 ):
1170 1170 # no way to report the error, so ignore it
1171 1171 return
1172 1172 raise error.StdioError(err)
1173 1173 finally:
1174 1174 self._blockedtimes[b'stdio_blocked'] += (
1175 1175 util.timer() - starttime
1176 1176 ) * 1000
1177 1177
1178 1178 def _writemsg(self, dest, *args, **opts):
1179 1179 _writemsgwith(self._write, dest, *args, **opts)
1180 1180
1181 1181 def _writemsgnobuf(self, dest, *args, **opts):
1182 1182 _writemsgwith(self._writenobuf, dest, *args, **opts)
1183 1183
1184 1184 def flush(self):
1185 1185 # opencode timeblockedsection because this is a critical path
1186 1186 starttime = util.timer()
1187 1187 try:
1188 1188 try:
1189 1189 self._fout.flush()
1190 1190 except IOError as err:
1191 1191 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1192 1192 raise error.StdioError(err)
1193 1193 finally:
1194 1194 try:
1195 1195 self._ferr.flush()
1196 1196 except IOError as err:
1197 1197 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1198 1198 raise error.StdioError(err)
1199 1199 finally:
1200 1200 self._blockedtimes[b'stdio_blocked'] += (
1201 1201 util.timer() - starttime
1202 1202 ) * 1000
1203 1203
1204 1204 def _isatty(self, fh):
1205 1205 if self.configbool(b'ui', b'nontty'):
1206 1206 return False
1207 1207 return procutil.isatty(fh)
1208 1208
1209 1209 def protectfinout(self):
1210 1210 """Duplicate ui streams and redirect original if they are stdio
1211 1211
1212 1212 Returns (fin, fout) which point to the original ui fds, but may be
1213 1213 copy of them. The returned streams can be considered "owned" in that
1214 1214 print(), exec(), etc. never reach to them.
1215 1215 """
1216 1216 if self._finoutredirected:
1217 1217 # if already redirected, protectstdio() would just create another
1218 1218 # nullfd pair, which is equivalent to returning self._fin/_fout.
1219 1219 return self._fin, self._fout
1220 1220 fin, fout = procutil.protectstdio(self._fin, self._fout)
1221 1221 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1222 1222 return fin, fout
1223 1223
1224 1224 def restorefinout(self, fin, fout):
1225 1225 """Restore ui streams from possibly duplicated (fin, fout)"""
1226 1226 if (fin, fout) == (self._fin, self._fout):
1227 1227 return
1228 1228 procutil.restorestdio(self._fin, self._fout, fin, fout)
1229 1229 # protectfinout() won't create more than one duplicated streams,
1230 1230 # so we can just turn the redirection flag off.
1231 1231 self._finoutredirected = False
1232 1232
1233 1233 @contextlib.contextmanager
1234 1234 def protectedfinout(self):
1235 1235 """Run code block with protected standard streams"""
1236 1236 fin, fout = self.protectfinout()
1237 1237 try:
1238 1238 yield fin, fout
1239 1239 finally:
1240 1240 self.restorefinout(fin, fout)
1241 1241
1242 1242 def disablepager(self):
1243 1243 self._disablepager = True
1244 1244
1245 1245 def pager(self, command):
1246 1246 """Start a pager for subsequent command output.
1247 1247
1248 1248 Commands which produce a long stream of output should call
1249 1249 this function to activate the user's preferred pagination
1250 1250 mechanism (which may be no pager). Calling this function
1251 1251 precludes any future use of interactive functionality, such as
1252 1252 prompting the user or activating curses.
1253 1253
1254 1254 Args:
1255 1255 command: The full, non-aliased name of the command. That is, "log"
1256 1256 not "history, "summary" not "summ", etc.
1257 1257 """
1258 1258 if self._disablepager or self.pageractive:
1259 1259 # how pager should do is already determined
1260 1260 return
1261 1261
1262 1262 if not command.startswith(b'internal-always-') and (
1263 1263 # explicit --pager=on (= 'internal-always-' prefix) should
1264 1264 # take precedence over disabling factors below
1265 1265 command in self.configlist(b'pager', b'ignore')
1266 1266 or not self.configbool(b'ui', b'paginate')
1267 1267 or not self.configbool(b'pager', b'attend-' + command, True)
1268 1268 or encoding.environ.get(b'TERM') == b'dumb'
1269 1269 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1270 1270 # formatted() will need some adjustment.
1271 1271 or not self.formatted()
1272 1272 or self.plain()
1273 1273 or self._buffers
1274 1274 # TODO: expose debugger-enabled on the UI object
1275 1275 or b'--debugger' in pycompat.sysargv
1276 1276 ):
1277 1277 # We only want to paginate if the ui appears to be
1278 1278 # interactive, the user didn't say HGPLAIN or
1279 1279 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1280 1280 return
1281 1281
1282 1282 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1283 1283 if not pagercmd:
1284 1284 return
1285 1285
1286 1286 pagerenv = {}
1287 1287 for name, value in rcutil.defaultpagerenv().items():
1288 1288 if name not in encoding.environ:
1289 1289 pagerenv[name] = value
1290 1290
1291 1291 self.debug(
1292 1292 b'starting pager for command %s\n' % stringutil.pprint(command)
1293 1293 )
1294 1294 self.flush()
1295 1295
1296 1296 wasformatted = self.formatted()
1297 1297 if util.safehasattr(signal, b"SIGPIPE"):
1298 1298 signal.signal(signal.SIGPIPE, _catchterm)
1299 1299 if self._runpager(pagercmd, pagerenv):
1300 1300 self.pageractive = True
1301 1301 # Preserve the formatted-ness of the UI. This is important
1302 1302 # because we mess with stdout, which might confuse
1303 1303 # auto-detection of things being formatted.
1304 1304 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1305 1305 self.setconfig(b'ui', b'interactive', False, b'pager')
1306 1306
1307 1307 # If pagermode differs from color.mode, reconfigure color now that
1308 1308 # pageractive is set.
1309 1309 cm = self._colormode
1310 1310 if cm != self.config(b'color', b'pagermode', cm):
1311 1311 color.setup(self)
1312 1312 else:
1313 1313 # If the pager can't be spawned in dispatch when --pager=on is
1314 1314 # given, don't try again when the command runs, to avoid a duplicate
1315 1315 # warning about a missing pager command.
1316 1316 self.disablepager()
1317 1317
1318 1318 def _runpager(self, command, env=None):
1319 1319 """Actually start the pager and set up file descriptors.
1320 1320
1321 1321 This is separate in part so that extensions (like chg) can
1322 1322 override how a pager is invoked.
1323 1323 """
1324 1324 if command == b'cat':
1325 1325 # Save ourselves some work.
1326 1326 return False
1327 1327 # If the command doesn't contain any of these characters, we
1328 1328 # assume it's a binary and exec it directly. This means for
1329 1329 # simple pager command configurations, we can degrade
1330 1330 # gracefully and tell the user about their broken pager.
1331 1331 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1332 1332
1333 1333 if pycompat.iswindows and not shell:
1334 1334 # Window's built-in `more` cannot be invoked with shell=False, but
1335 1335 # its `more.com` can. Hide this implementation detail from the
1336 1336 # user so we can also get sane bad PAGER behavior. MSYS has
1337 1337 # `more.exe`, so do a cmd.exe style resolution of the executable to
1338 1338 # determine which one to use.
1339 1339 fullcmd = procutil.findexe(command)
1340 1340 if not fullcmd:
1341 1341 self.warn(
1342 1342 _(b"missing pager command '%s', skipping pager\n") % command
1343 1343 )
1344 1344 return False
1345 1345
1346 1346 command = fullcmd
1347 1347
1348 1348 try:
1349 1349 pager = subprocess.Popen(
1350 1350 procutil.tonativestr(command),
1351 1351 shell=shell,
1352 1352 bufsize=-1,
1353 1353 close_fds=procutil.closefds,
1354 1354 stdin=subprocess.PIPE,
1355 1355 stdout=procutil.stdout,
1356 1356 stderr=procutil.stderr,
1357 1357 env=procutil.tonativeenv(procutil.shellenviron(env)),
1358 1358 )
1359 1359 except OSError as e:
1360 1360 if e.errno == errno.ENOENT and not shell:
1361 1361 self.warn(
1362 1362 _(b"missing pager command '%s', skipping pager\n") % command
1363 1363 )
1364 1364 return False
1365 1365 raise
1366 1366
1367 1367 # back up original file descriptors
1368 1368 stdoutfd = os.dup(procutil.stdout.fileno())
1369 1369 stderrfd = os.dup(procutil.stderr.fileno())
1370 1370
1371 1371 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1372 1372 if self._isatty(procutil.stderr):
1373 1373 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1374 1374
1375 1375 @self.atexit
1376 1376 def killpager():
1377 1377 if util.safehasattr(signal, b"SIGINT"):
1378 1378 signal.signal(signal.SIGINT, signal.SIG_IGN)
1379 1379 # restore original fds, closing pager.stdin copies in the process
1380 1380 os.dup2(stdoutfd, procutil.stdout.fileno())
1381 1381 os.dup2(stderrfd, procutil.stderr.fileno())
1382 1382 pager.stdin.close()
1383 1383 pager.wait()
1384 1384
1385 1385 return True
1386 1386
1387 1387 @property
1388 1388 def _exithandlers(self):
1389 1389 return _reqexithandlers
1390 1390
1391 1391 def atexit(self, func, *args, **kwargs):
1392 1392 '''register a function to run after dispatching a request
1393 1393
1394 1394 Handlers do not stay registered across request boundaries.'''
1395 1395 self._exithandlers.append((func, args, kwargs))
1396 1396 return func
1397 1397
1398 1398 def interface(self, feature):
1399 1399 """what interface to use for interactive console features?
1400 1400
1401 1401 The interface is controlled by the value of `ui.interface` but also by
1402 1402 the value of feature-specific configuration. For example:
1403 1403
1404 1404 ui.interface.histedit = text
1405 1405 ui.interface.chunkselector = curses
1406 1406
1407 1407 Here the features are "histedit" and "chunkselector".
1408 1408
1409 1409 The configuration above means that the default interfaces for commands
1410 1410 is curses, the interface for histedit is text and the interface for
1411 1411 selecting chunk is crecord (the best curses interface available).
1412 1412
1413 1413 Consider the following example:
1414 1414 ui.interface = curses
1415 1415 ui.interface.histedit = text
1416 1416
1417 1417 Then histedit will use the text interface and chunkselector will use
1418 1418 the default curses interface (crecord at the moment).
1419 1419 """
1420 1420 alldefaults = frozenset([b"text", b"curses"])
1421 1421
1422 1422 featureinterfaces = {
1423 1423 b"chunkselector": [b"text", b"curses",],
1424 1424 b"histedit": [b"text", b"curses",],
1425 1425 }
1426 1426
1427 1427 # Feature-specific interface
1428 1428 if feature not in featureinterfaces.keys():
1429 1429 # Programming error, not user error
1430 1430 raise ValueError(b"Unknown feature requested %s" % feature)
1431 1431
1432 1432 availableinterfaces = frozenset(featureinterfaces[feature])
1433 1433 if alldefaults > availableinterfaces:
1434 1434 # Programming error, not user error. We need a use case to
1435 1435 # define the right thing to do here.
1436 1436 raise ValueError(
1437 1437 b"Feature %s does not handle all default interfaces" % feature
1438 1438 )
1439 1439
1440 1440 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1441 1441 return b"text"
1442 1442
1443 1443 # Default interface for all the features
1444 1444 defaultinterface = b"text"
1445 1445 i = self.config(b"ui", b"interface")
1446 1446 if i in alldefaults:
1447 1447 defaultinterface = i
1448 1448
1449 1449 choseninterface = defaultinterface
1450 1450 f = self.config(b"ui", b"interface.%s" % feature)
1451 1451 if f in availableinterfaces:
1452 1452 choseninterface = f
1453 1453
1454 1454 if i is not None and defaultinterface != i:
1455 1455 if f is not None:
1456 1456 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1457 1457 else:
1458 1458 self.warn(
1459 1459 _(b"invalid value for ui.interface: %s (using %s)\n")
1460 1460 % (i, choseninterface)
1461 1461 )
1462 1462 if f is not None and choseninterface != f:
1463 1463 self.warn(
1464 1464 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1465 1465 % (feature, f, choseninterface)
1466 1466 )
1467 1467
1468 1468 return choseninterface
1469 1469
1470 1470 def interactive(self):
1471 1471 '''is interactive input allowed?
1472 1472
1473 1473 An interactive session is a session where input can be reasonably read
1474 1474 from `sys.stdin'. If this function returns false, any attempt to read
1475 1475 from stdin should fail with an error, unless a sensible default has been
1476 1476 specified.
1477 1477
1478 1478 Interactiveness is triggered by the value of the `ui.interactive'
1479 1479 configuration variable or - if it is unset - when `sys.stdin' points
1480 1480 to a terminal device.
1481 1481
1482 1482 This function refers to input only; for output, see `ui.formatted()'.
1483 1483 '''
1484 1484 i = self.configbool(b"ui", b"interactive")
1485 1485 if i is None:
1486 1486 # some environments replace stdin without implementing isatty
1487 1487 # usually those are non-interactive
1488 1488 return self._isatty(self._fin)
1489 1489
1490 1490 return i
1491 1491
1492 1492 def termwidth(self):
1493 1493 '''how wide is the terminal in columns?
1494 1494 '''
1495 1495 if b'COLUMNS' in encoding.environ:
1496 1496 try:
1497 1497 return int(encoding.environ[b'COLUMNS'])
1498 1498 except ValueError:
1499 1499 pass
1500 1500 return scmutil.termsize(self)[0]
1501 1501
1502 1502 def formatted(self):
1503 1503 '''should formatted output be used?
1504 1504
1505 1505 It is often desirable to format the output to suite the output medium.
1506 1506 Examples of this are truncating long lines or colorizing messages.
1507 1507 However, this is not often not desirable when piping output into other
1508 1508 utilities, e.g. `grep'.
1509 1509
1510 1510 Formatted output is triggered by the value of the `ui.formatted'
1511 1511 configuration variable or - if it is unset - when `sys.stdout' points
1512 1512 to a terminal device. Please note that `ui.formatted' should be
1513 1513 considered an implementation detail; it is not intended for use outside
1514 1514 Mercurial or its extensions.
1515 1515
1516 1516 This function refers to output only; for input, see `ui.interactive()'.
1517 1517 This function always returns false when in plain mode, see `ui.plain()'.
1518 1518 '''
1519 1519 if self.plain():
1520 1520 return False
1521 1521
1522 1522 i = self.configbool(b"ui", b"formatted")
1523 1523 if i is None:
1524 1524 # some environments replace stdout without implementing isatty
1525 1525 # usually those are non-interactive
1526 1526 return self._isatty(self._fout)
1527 1527
1528 1528 return i
1529 1529
1530 1530 def _readline(self, prompt=b' ', promptopts=None):
1531 1531 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1532 1532 # because they have to be text streams with *no buffering*. Instead,
1533 1533 # we use rawinput() only if call_readline() will be invoked by
1534 1534 # PyOS_Readline(), so no I/O will be made at Python layer.
1535 1535 usereadline = (
1536 1536 self._isatty(self._fin)
1537 1537 and self._isatty(self._fout)
1538 1538 and procutil.isstdin(self._fin)
1539 1539 and procutil.isstdout(self._fout)
1540 1540 )
1541 1541 if usereadline:
1542 1542 try:
1543 1543 # magically add command line editing support, where
1544 1544 # available
1545 1545 import readline
1546 1546
1547 1547 # force demandimport to really load the module
1548 1548 readline.read_history_file
1549 1549 # windows sometimes raises something other than ImportError
1550 1550 except Exception:
1551 1551 usereadline = False
1552 1552
1553 1553 if self._colormode == b'win32' or not usereadline:
1554 1554 if not promptopts:
1555 1555 promptopts = {}
1556 1556 self._writemsgnobuf(
1557 1557 self._fmsgout, prompt, type=b'prompt', **promptopts
1558 1558 )
1559 1559 self.flush()
1560 1560 prompt = b' '
1561 1561 else:
1562 1562 prompt = self.label(prompt, b'ui.prompt') + b' '
1563 1563
1564 1564 # prompt ' ' must exist; otherwise readline may delete entire line
1565 1565 # - http://bugs.python.org/issue12833
1566 1566 with self.timeblockedsection(b'stdio'):
1567 1567 if usereadline:
1568 1568 self.flush()
1569 1569 prompt = encoding.strfromlocal(prompt)
1570 1570 line = encoding.strtolocal(pycompat.rawinput(prompt))
1571 1571 # When stdin is in binary mode on Windows, it can cause
1572 1572 # raw_input() to emit an extra trailing carriage return
1573 1573 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1574 1574 line = line[:-1]
1575 1575 else:
1576 1576 self._fout.write(pycompat.bytestr(prompt))
1577 1577 self._fout.flush()
1578 1578 line = self._fin.readline()
1579 1579 if not line:
1580 1580 raise EOFError
1581 1581 line = line.rstrip(pycompat.oslinesep)
1582 1582
1583 1583 return line
1584 1584
1585 1585 def prompt(self, msg, default=b"y"):
1586 1586 """Prompt user with msg, read response.
1587 1587 If ui is not interactive, the default is returned.
1588 1588 """
1589 1589 return self._prompt(msg, default=default)
1590 1590
1591 1591 def _prompt(self, msg, **opts):
1592 1592 default = opts['default']
1593 1593 if not self.interactive():
1594 1594 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1595 1595 self._writemsg(
1596 1596 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1597 1597 )
1598 1598 return default
1599 1599 try:
1600 1600 r = self._readline(prompt=msg, promptopts=opts)
1601 1601 if not r:
1602 1602 r = default
1603 1603 if self.configbool(b'ui', b'promptecho'):
1604 1604 self._writemsg(self._fmsgout, r, b"\n", type=b'promptecho')
1605 1605 return r
1606 1606 except EOFError:
1607 1607 raise error.ResponseExpected()
1608 1608
1609 1609 @staticmethod
1610 1610 def extractchoices(prompt):
1611 1611 """Extract prompt message and list of choices from specified prompt.
1612 1612
1613 1613 This returns tuple "(message, choices)", and "choices" is the
1614 1614 list of tuple "(response character, text without &)".
1615 1615
1616 1616 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1617 1617 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1618 1618 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1619 1619 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1620 1620 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1621 1621 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1622 1622 """
1623 1623
1624 1624 # Sadly, the prompt string may have been built with a filename
1625 1625 # containing "$$" so let's try to find the first valid-looking
1626 1626 # prompt to start parsing. Sadly, we also can't rely on
1627 1627 # choices containing spaces, ASCII, or basically anything
1628 1628 # except an ampersand followed by a character.
1629 1629 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1630 1630 msg = m.group(1)
1631 1631 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1632 1632
1633 1633 def choicetuple(s):
1634 1634 ampidx = s.index(b'&')
1635 1635 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1636 1636
1637 1637 return (msg, [choicetuple(s) for s in choices])
1638 1638
1639 1639 def promptchoice(self, prompt, default=0):
1640 1640 """Prompt user with a message, read response, and ensure it matches
1641 1641 one of the provided choices. The prompt is formatted as follows:
1642 1642
1643 1643 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1644 1644
1645 1645 The index of the choice is returned. Responses are case
1646 1646 insensitive. If ui is not interactive, the default is
1647 1647 returned.
1648 1648 """
1649 1649
1650 1650 msg, choices = self.extractchoices(prompt)
1651 1651 resps = [r for r, t in choices]
1652 1652 while True:
1653 1653 r = self._prompt(msg, default=resps[default], choices=choices)
1654 1654 if r.lower() in resps:
1655 1655 return resps.index(r.lower())
1656 1656 # TODO: shouldn't it be a warning?
1657 1657 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1658 1658
1659 1659 def getpass(self, prompt=None, default=None):
1660 1660 if not self.interactive():
1661 1661 return default
1662 1662 try:
1663 1663 self._writemsg(
1664 1664 self._fmsgerr,
1665 1665 prompt or _(b'password: '),
1666 1666 type=b'prompt',
1667 1667 password=True,
1668 1668 )
1669 1669 # disable getpass() only if explicitly specified. it's still valid
1670 1670 # to interact with tty even if fin is not a tty.
1671 1671 with self.timeblockedsection(b'stdio'):
1672 1672 if self.configbool(b'ui', b'nontty'):
1673 1673 l = self._fin.readline()
1674 1674 if not l:
1675 1675 raise EOFError
1676 1676 return l.rstrip(b'\n')
1677 1677 else:
1678 1678 return getpass.getpass('')
1679 1679 except EOFError:
1680 1680 raise error.ResponseExpected()
1681 1681
1682 1682 def status(self, *msg, **opts):
1683 1683 '''write status message to output (if ui.quiet is False)
1684 1684
1685 1685 This adds an output label of "ui.status".
1686 1686 '''
1687 1687 if not self.quiet:
1688 1688 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1689 1689
1690 1690 def warn(self, *msg, **opts):
1691 1691 '''write warning message to output (stderr)
1692 1692
1693 1693 This adds an output label of "ui.warning".
1694 1694 '''
1695 1695 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1696 1696
1697 1697 def error(self, *msg, **opts):
1698 1698 '''write error message to output (stderr)
1699 1699
1700 1700 This adds an output label of "ui.error".
1701 1701 '''
1702 1702 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1703 1703
1704 1704 def note(self, *msg, **opts):
1705 1705 '''write note to output (if ui.verbose is True)
1706 1706
1707 1707 This adds an output label of "ui.note".
1708 1708 '''
1709 1709 if self.verbose:
1710 1710 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1711 1711
1712 1712 def debug(self, *msg, **opts):
1713 1713 '''write debug message to output (if ui.debugflag is True)
1714 1714
1715 1715 This adds an output label of "ui.debug".
1716 1716 '''
1717 1717 if self.debugflag:
1718 1718 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1719 1719 self.log(b'debug', b'%s', b''.join(msg))
1720 1720
1721 1721 # Aliases to defeat check-code.
1722 1722 statusnoi18n = status
1723 1723 notenoi18n = note
1724 1724 warnnoi18n = warn
1725 1725 writenoi18n = write
1726 1726
1727 1727 def edit(
1728 1728 self,
1729 1729 text,
1730 1730 user,
1731 1731 extra=None,
1732 1732 editform=None,
1733 1733 pending=None,
1734 1734 repopath=None,
1735 1735 action=None,
1736 1736 ):
1737 1737 if action is None:
1738 1738 self.develwarn(
1739 1739 b'action is None but will soon be a required '
1740 1740 b'parameter to ui.edit()'
1741 1741 )
1742 1742 extra_defaults = {
1743 1743 b'prefix': b'editor',
1744 1744 b'suffix': b'.txt',
1745 1745 }
1746 1746 if extra is not None:
1747 1747 if extra.get(b'suffix') is not None:
1748 1748 self.develwarn(
1749 1749 b'extra.suffix is not None but will soon be '
1750 1750 b'ignored by ui.edit()'
1751 1751 )
1752 1752 extra_defaults.update(extra)
1753 1753 extra = extra_defaults
1754 1754
1755 1755 if action == b'diff':
1756 1756 suffix = b'.diff'
1757 1757 elif action:
1758 1758 suffix = b'.%s.hg.txt' % action
1759 1759 else:
1760 1760 suffix = extra[b'suffix']
1761 1761
1762 1762 rdir = None
1763 1763 if self.configbool(b'experimental', b'editortmpinhg'):
1764 1764 rdir = repopath
1765 1765 (fd, name) = pycompat.mkstemp(
1766 1766 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1767 1767 )
1768 1768 try:
1769 f = os.fdopen(fd, 'wb')
1770 f.write(util.tonativeeol(text))
1771 f.close()
1769 with os.fdopen(fd, 'wb') as f:
1770 f.write(util.tonativeeol(text))
1772 1771
1773 1772 environ = {b'HGUSER': user}
1774 1773 if b'transplant_source' in extra:
1775 1774 environ.update(
1776 1775 {b'HGREVISION': hex(extra[b'transplant_source'])}
1777 1776 )
1778 1777 for label in (b'intermediate-source', b'source', b'rebase_source'):
1779 1778 if label in extra:
1780 1779 environ.update({b'HGREVISION': extra[label]})
1781 1780 break
1782 1781 if editform:
1783 1782 environ.update({b'HGEDITFORM': editform})
1784 1783 if pending:
1785 1784 environ.update({b'HG_PENDING': pending})
1786 1785
1787 1786 editor = self.geteditor()
1788 1787
1789 1788 self.system(
1790 1789 b"%s \"%s\"" % (editor, name),
1791 1790 environ=environ,
1792 1791 onerr=error.Abort,
1793 1792 errprefix=_(b"edit failed"),
1794 1793 blockedtag=b'editor',
1795 1794 )
1796 1795
1797 f = open(name, 'rb')
1798 t = util.fromnativeeol(f.read())
1799 f.close()
1796 with open(name, 'rb') as f:
1797 t = util.fromnativeeol(f.read())
1800 1798 finally:
1801 1799 os.unlink(name)
1802 1800
1803 1801 return t
1804 1802
1805 1803 def system(
1806 1804 self,
1807 1805 cmd,
1808 1806 environ=None,
1809 1807 cwd=None,
1810 1808 onerr=None,
1811 1809 errprefix=None,
1812 1810 blockedtag=None,
1813 1811 ):
1814 1812 '''execute shell command with appropriate output stream. command
1815 1813 output will be redirected if fout is not stdout.
1816 1814
1817 1815 if command fails and onerr is None, return status, else raise onerr
1818 1816 object as exception.
1819 1817 '''
1820 1818 if blockedtag is None:
1821 1819 # Long cmds tend to be because of an absolute path on cmd. Keep
1822 1820 # the tail end instead
1823 1821 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1824 1822 blockedtag = b'unknown_system_' + cmdsuffix
1825 1823 out = self._fout
1826 1824 if any(s[1] for s in self._bufferstates):
1827 1825 out = self
1828 1826 with self.timeblockedsection(blockedtag):
1829 1827 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1830 1828 if rc and onerr:
1831 1829 errmsg = b'%s %s' % (
1832 1830 os.path.basename(cmd.split(None, 1)[0]),
1833 1831 procutil.explainexit(rc),
1834 1832 )
1835 1833 if errprefix:
1836 1834 errmsg = b'%s: %s' % (errprefix, errmsg)
1837 1835 raise onerr(errmsg)
1838 1836 return rc
1839 1837
1840 1838 def _runsystem(self, cmd, environ, cwd, out):
1841 1839 """actually execute the given shell command (can be overridden by
1842 1840 extensions like chg)"""
1843 1841 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1844 1842
1845 1843 def traceback(self, exc=None, force=False):
1846 1844 '''print exception traceback if traceback printing enabled or forced.
1847 1845 only to call in exception handler. returns true if traceback
1848 1846 printed.'''
1849 1847 if self.tracebackflag or force:
1850 1848 if exc is None:
1851 1849 exc = sys.exc_info()
1852 1850 cause = getattr(exc[1], 'cause', None)
1853 1851
1854 1852 if cause is not None:
1855 1853 causetb = traceback.format_tb(cause[2])
1856 1854 exctb = traceback.format_tb(exc[2])
1857 1855 exconly = traceback.format_exception_only(cause[0], cause[1])
1858 1856
1859 1857 # exclude frame where 'exc' was chained and rethrown from exctb
1860 1858 self.write_err(
1861 1859 b'Traceback (most recent call last):\n',
1862 1860 b''.join(exctb[:-1]),
1863 1861 b''.join(causetb),
1864 1862 b''.join(exconly),
1865 1863 )
1866 1864 else:
1867 1865 output = traceback.format_exception(exc[0], exc[1], exc[2])
1868 1866 self.write_err(encoding.strtolocal(''.join(output)))
1869 1867 return self.tracebackflag or force
1870 1868
1871 1869 def geteditor(self):
1872 1870 '''return editor to use'''
1873 1871 if pycompat.sysplatform == b'plan9':
1874 1872 # vi is the MIPS instruction simulator on Plan 9. We
1875 1873 # instead default to E to plumb commit messages to
1876 1874 # avoid confusion.
1877 1875 editor = b'E'
1878 1876 else:
1879 1877 editor = b'vi'
1880 1878 return encoding.environ.get(b"HGEDITOR") or self.config(
1881 1879 b"ui", b"editor", editor
1882 1880 )
1883 1881
1884 1882 @util.propertycache
1885 1883 def _progbar(self):
1886 1884 """setup the progbar singleton to the ui object"""
1887 1885 if (
1888 1886 self.quiet
1889 1887 or self.debugflag
1890 1888 or self.configbool(b'progress', b'disable')
1891 1889 or not progress.shouldprint(self)
1892 1890 ):
1893 1891 return None
1894 1892 return getprogbar(self)
1895 1893
1896 1894 def _progclear(self):
1897 1895 """clear progress bar output if any. use it before any output"""
1898 1896 if not haveprogbar(): # nothing loaded yet
1899 1897 return
1900 1898 if self._progbar is not None and self._progbar.printed:
1901 1899 self._progbar.clear()
1902 1900
1903 1901 def progress(self, topic, pos, item=b"", unit=b"", total=None):
1904 1902 '''show a progress message
1905 1903
1906 1904 By default a textual progress bar will be displayed if an operation
1907 1905 takes too long. 'topic' is the current operation, 'item' is a
1908 1906 non-numeric marker of the current position (i.e. the currently
1909 1907 in-process file), 'pos' is the current numeric position (i.e.
1910 1908 revision, bytes, etc.), unit is a corresponding unit label,
1911 1909 and total is the highest expected pos.
1912 1910
1913 1911 Multiple nested topics may be active at a time.
1914 1912
1915 1913 All topics should be marked closed by setting pos to None at
1916 1914 termination.
1917 1915 '''
1918 1916 self.deprecwarn(
1919 1917 b"use ui.makeprogress() instead of ui.progress()", b"5.1"
1920 1918 )
1921 1919 progress = self.makeprogress(topic, unit, total)
1922 1920 if pos is not None:
1923 1921 progress.update(pos, item=item)
1924 1922 else:
1925 1923 progress.complete()
1926 1924
1927 1925 def makeprogress(self, topic, unit=b"", total=None):
1928 1926 """Create a progress helper for the specified topic"""
1929 1927 if getattr(self._fmsgerr, 'structured', False):
1930 1928 # channel for machine-readable output with metadata, just send
1931 1929 # raw information
1932 1930 # TODO: consider porting some useful information (e.g. estimated
1933 1931 # time) from progbar. we might want to support update delay to
1934 1932 # reduce the cost of transferring progress messages.
1935 1933 def updatebar(topic, pos, item, unit, total):
1936 1934 self._fmsgerr.write(
1937 1935 None,
1938 1936 type=b'progress',
1939 1937 topic=topic,
1940 1938 pos=pos,
1941 1939 item=item,
1942 1940 unit=unit,
1943 1941 total=total,
1944 1942 )
1945 1943
1946 1944 elif self._progbar is not None:
1947 1945 updatebar = self._progbar.progress
1948 1946 else:
1949 1947
1950 1948 def updatebar(topic, pos, item, unit, total):
1951 1949 pass
1952 1950
1953 1951 return scmutil.progress(self, updatebar, topic, unit, total)
1954 1952
1955 1953 def getlogger(self, name):
1956 1954 """Returns a logger of the given name; or None if not registered"""
1957 1955 return self._loggers.get(name)
1958 1956
1959 1957 def setlogger(self, name, logger):
1960 1958 """Install logger which can be identified later by the given name
1961 1959
1962 1960 More than one loggers can be registered. Use extension or module
1963 1961 name to uniquely identify the logger instance.
1964 1962 """
1965 1963 self._loggers[name] = logger
1966 1964
1967 1965 def log(self, event, msgfmt, *msgargs, **opts):
1968 1966 '''hook for logging facility extensions
1969 1967
1970 1968 event should be a readily-identifiable subsystem, which will
1971 1969 allow filtering.
1972 1970
1973 1971 msgfmt should be a newline-terminated format string to log, and
1974 1972 *msgargs are %-formatted into it.
1975 1973
1976 1974 **opts currently has no defined meanings.
1977 1975 '''
1978 1976 if not self._loggers:
1979 1977 return
1980 1978 activeloggers = [
1981 1979 l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
1982 1980 ]
1983 1981 if not activeloggers:
1984 1982 return
1985 1983 msg = msgfmt % msgargs
1986 1984 opts = pycompat.byteskwargs(opts)
1987 1985 # guard against recursion from e.g. ui.debug()
1988 1986 registeredloggers = self._loggers
1989 1987 self._loggers = {}
1990 1988 try:
1991 1989 for logger in activeloggers:
1992 1990 logger.log(self, event, msg, opts)
1993 1991 finally:
1994 1992 self._loggers = registeredloggers
1995 1993
1996 1994 def label(self, msg, label):
1997 1995 '''style msg based on supplied label
1998 1996
1999 1997 If some color mode is enabled, this will add the necessary control
2000 1998 characters to apply such color. In addition, 'debug' color mode adds
2001 1999 markup showing which label affects a piece of text.
2002 2000
2003 2001 ui.write(s, 'label') is equivalent to
2004 2002 ui.write(ui.label(s, 'label')).
2005 2003 '''
2006 2004 if self._colormode is not None:
2007 2005 return color.colorlabel(self, msg, label)
2008 2006 return msg
2009 2007
2010 2008 def develwarn(self, msg, stacklevel=1, config=None):
2011 2009 """issue a developer warning message
2012 2010
2013 2011 Use 'stacklevel' to report the offender some layers further up in the
2014 2012 stack.
2015 2013 """
2016 2014 if not self.configbool(b'devel', b'all-warnings'):
2017 2015 if config is None or not self.configbool(b'devel', config):
2018 2016 return
2019 2017 msg = b'devel-warn: ' + msg
2020 2018 stacklevel += 1 # get in develwarn
2021 2019 if self.tracebackflag:
2022 2020 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2023 2021 self.log(
2024 2022 b'develwarn',
2025 2023 b'%s at:\n%s'
2026 2024 % (msg, b''.join(util.getstackframes(stacklevel))),
2027 2025 )
2028 2026 else:
2029 2027 curframe = inspect.currentframe()
2030 2028 calframe = inspect.getouterframes(curframe, 2)
2031 2029 fname, lineno, fmsg = calframe[stacklevel][1:4]
2032 2030 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2033 2031 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2034 2032 self.log(
2035 2033 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2036 2034 )
2037 2035 curframe = calframe = None # avoid cycles
2038 2036
2039 2037 def deprecwarn(self, msg, version, stacklevel=2):
2040 2038 """issue a deprecation warning
2041 2039
2042 2040 - msg: message explaining what is deprecated and how to upgrade,
2043 2041 - version: last version where the API will be supported,
2044 2042 """
2045 2043 if not (
2046 2044 self.configbool(b'devel', b'all-warnings')
2047 2045 or self.configbool(b'devel', b'deprec-warn')
2048 2046 ):
2049 2047 return
2050 2048 msg += (
2051 2049 b"\n(compatibility will be dropped after Mercurial-%s,"
2052 2050 b" update your code.)"
2053 2051 ) % version
2054 2052 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2055 2053
2056 2054 def exportableenviron(self):
2057 2055 """The environment variables that are safe to export, e.g. through
2058 2056 hgweb.
2059 2057 """
2060 2058 return self._exportableenviron
2061 2059
2062 2060 @contextlib.contextmanager
2063 2061 def configoverride(self, overrides, source=b""):
2064 2062 """Context manager for temporary config overrides
2065 2063 `overrides` must be a dict of the following structure:
2066 2064 {(section, name) : value}"""
2067 2065 backups = {}
2068 2066 try:
2069 2067 for (section, name), value in overrides.items():
2070 2068 backups[(section, name)] = self.backupconfig(section, name)
2071 2069 self.setconfig(section, name, value, source)
2072 2070 yield
2073 2071 finally:
2074 2072 for __, backup in backups.items():
2075 2073 self.restoreconfig(backup)
2076 2074 # just restoring ui.quiet config to the previous value is not enough
2077 2075 # as it does not update ui.quiet class member
2078 2076 if (b'ui', b'quiet') in overrides:
2079 2077 self.fixconfig(section=b'ui')
2080 2078
2081 2079
2082 2080 class paths(dict):
2083 2081 """Represents a collection of paths and their configs.
2084 2082
2085 2083 Data is initially derived from ui instances and the config files they have
2086 2084 loaded.
2087 2085 """
2088 2086
2089 2087 def __init__(self, ui):
2090 2088 dict.__init__(self)
2091 2089
2092 2090 for name, loc in ui.configitems(b'paths', ignoresub=True):
2093 2091 # No location is the same as not existing.
2094 2092 if not loc:
2095 2093 continue
2096 2094 loc, sub = ui.configsuboptions(b'paths', name)
2097 2095 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
2098 2096
2099 2097 def getpath(self, name, default=None):
2100 2098 """Return a ``path`` from a string, falling back to default.
2101 2099
2102 2100 ``name`` can be a named path or locations. Locations are filesystem
2103 2101 paths or URIs.
2104 2102
2105 2103 Returns None if ``name`` is not a registered path, a URI, or a local
2106 2104 path to a repo.
2107 2105 """
2108 2106 # Only fall back to default if no path was requested.
2109 2107 if name is None:
2110 2108 if not default:
2111 2109 default = ()
2112 2110 elif not isinstance(default, (tuple, list)):
2113 2111 default = (default,)
2114 2112 for k in default:
2115 2113 try:
2116 2114 return self[k]
2117 2115 except KeyError:
2118 2116 continue
2119 2117 return None
2120 2118
2121 2119 # Most likely empty string.
2122 2120 # This may need to raise in the future.
2123 2121 if not name:
2124 2122 return None
2125 2123
2126 2124 try:
2127 2125 return self[name]
2128 2126 except KeyError:
2129 2127 # Try to resolve as a local path or URI.
2130 2128 try:
2131 2129 # We don't pass sub-options in, so no need to pass ui instance.
2132 2130 return path(None, None, rawloc=name)
2133 2131 except ValueError:
2134 2132 raise error.RepoError(_(b'repository %s does not exist') % name)
2135 2133
2136 2134
2137 2135 _pathsuboptions = {}
2138 2136
2139 2137
2140 2138 def pathsuboption(option, attr):
2141 2139 """Decorator used to declare a path sub-option.
2142 2140
2143 2141 Arguments are the sub-option name and the attribute it should set on
2144 2142 ``path`` instances.
2145 2143
2146 2144 The decorated function will receive as arguments a ``ui`` instance,
2147 2145 ``path`` instance, and the string value of this option from the config.
2148 2146 The function should return the value that will be set on the ``path``
2149 2147 instance.
2150 2148
2151 2149 This decorator can be used to perform additional verification of
2152 2150 sub-options and to change the type of sub-options.
2153 2151 """
2154 2152
2155 2153 def register(func):
2156 2154 _pathsuboptions[option] = (attr, func)
2157 2155 return func
2158 2156
2159 2157 return register
2160 2158
2161 2159
2162 2160 @pathsuboption(b'pushurl', b'pushloc')
2163 2161 def pushurlpathoption(ui, path, value):
2164 2162 u = util.url(value)
2165 2163 # Actually require a URL.
2166 2164 if not u.scheme:
2167 2165 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
2168 2166 return None
2169 2167
2170 2168 # Don't support the #foo syntax in the push URL to declare branch to
2171 2169 # push.
2172 2170 if u.fragment:
2173 2171 ui.warn(
2174 2172 _(
2175 2173 b'("#fragment" in paths.%s:pushurl not supported; '
2176 2174 b'ignoring)\n'
2177 2175 )
2178 2176 % path.name
2179 2177 )
2180 2178 u.fragment = None
2181 2179
2182 2180 return bytes(u)
2183 2181
2184 2182
2185 2183 @pathsuboption(b'pushrev', b'pushrev')
2186 2184 def pushrevpathoption(ui, path, value):
2187 2185 return value
2188 2186
2189 2187
2190 2188 class path(object):
2191 2189 """Represents an individual path and its configuration."""
2192 2190
2193 2191 def __init__(self, ui, name, rawloc=None, suboptions=None):
2194 2192 """Construct a path from its config options.
2195 2193
2196 2194 ``ui`` is the ``ui`` instance the path is coming from.
2197 2195 ``name`` is the symbolic name of the path.
2198 2196 ``rawloc`` is the raw location, as defined in the config.
2199 2197 ``pushloc`` is the raw locations pushes should be made to.
2200 2198
2201 2199 If ``name`` is not defined, we require that the location be a) a local
2202 2200 filesystem path with a .hg directory or b) a URL. If not,
2203 2201 ``ValueError`` is raised.
2204 2202 """
2205 2203 if not rawloc:
2206 2204 raise ValueError(b'rawloc must be defined')
2207 2205
2208 2206 # Locations may define branches via syntax <base>#<branch>.
2209 2207 u = util.url(rawloc)
2210 2208 branch = None
2211 2209 if u.fragment:
2212 2210 branch = u.fragment
2213 2211 u.fragment = None
2214 2212
2215 2213 self.url = u
2216 2214 self.branch = branch
2217 2215
2218 2216 self.name = name
2219 2217 self.rawloc = rawloc
2220 2218 self.loc = b'%s' % u
2221 2219
2222 2220 # When given a raw location but not a symbolic name, validate the
2223 2221 # location is valid.
2224 2222 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2225 2223 raise ValueError(
2226 2224 b'location is not a URL or path to a local '
2227 2225 b'repo: %s' % rawloc
2228 2226 )
2229 2227
2230 2228 suboptions = suboptions or {}
2231 2229
2232 2230 # Now process the sub-options. If a sub-option is registered, its
2233 2231 # attribute will always be present. The value will be None if there
2234 2232 # was no valid sub-option.
2235 2233 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
2236 2234 if suboption not in suboptions:
2237 2235 setattr(self, attr, None)
2238 2236 continue
2239 2237
2240 2238 value = func(ui, self, suboptions[suboption])
2241 2239 setattr(self, attr, value)
2242 2240
2243 2241 def _isvalidlocalpath(self, path):
2244 2242 """Returns True if the given path is a potentially valid repository.
2245 2243 This is its own function so that extensions can change the definition of
2246 2244 'valid' in this case (like when pulling from a git repo into a hg
2247 2245 one)."""
2248 2246 try:
2249 2247 return os.path.isdir(os.path.join(path, b'.hg'))
2250 2248 # Python 2 may return TypeError. Python 3, ValueError.
2251 2249 except (TypeError, ValueError):
2252 2250 return False
2253 2251
2254 2252 @property
2255 2253 def suboptions(self):
2256 2254 """Return sub-options and their values for this path.
2257 2255
2258 2256 This is intended to be used for presentation purposes.
2259 2257 """
2260 2258 d = {}
2261 2259 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
2262 2260 value = getattr(self, attr)
2263 2261 if value is not None:
2264 2262 d[subopt] = value
2265 2263 return d
2266 2264
2267 2265
2268 2266 # we instantiate one globally shared progress bar to avoid
2269 2267 # competing progress bars when multiple UI objects get created
2270 2268 _progresssingleton = None
2271 2269
2272 2270
2273 2271 def getprogbar(ui):
2274 2272 global _progresssingleton
2275 2273 if _progresssingleton is None:
2276 2274 # passing 'ui' object to the singleton is fishy,
2277 2275 # this is how the extension used to work but feel free to rework it.
2278 2276 _progresssingleton = progress.progbar(ui)
2279 2277 return _progresssingleton
2280 2278
2281 2279
2282 2280 def haveprogbar():
2283 2281 return _progresssingleton is not None
2284 2282
2285 2283
2286 2284 def _selectmsgdests(ui):
2287 2285 name = ui.config(b'ui', b'message-output')
2288 2286 if name == b'channel':
2289 2287 if ui.fmsg:
2290 2288 return ui.fmsg, ui.fmsg
2291 2289 else:
2292 2290 # fall back to ferr if channel isn't ready so that status/error
2293 2291 # messages can be printed
2294 2292 return ui.ferr, ui.ferr
2295 2293 if name == b'stdio':
2296 2294 return ui.fout, ui.ferr
2297 2295 if name == b'stderr':
2298 2296 return ui.ferr, ui.ferr
2299 2297 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2300 2298
2301 2299
2302 2300 def _writemsgwith(write, dest, *args, **opts):
2303 2301 """Write ui message with the given ui._write*() function
2304 2302
2305 2303 The specified message type is translated to 'ui.<type>' label if the dest
2306 2304 isn't a structured channel, so that the message will be colorized.
2307 2305 """
2308 2306 # TODO: maybe change 'type' to a mandatory option
2309 2307 if 'type' in opts and not getattr(dest, 'structured', False):
2310 2308 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2311 2309 write(dest, *args, **opts)
General Comments 0
You need to be logged in to leave comments. Login now