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