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