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