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