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