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