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