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