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