##// END OF EJS Templates
ui: refactor extractchoices so it doesn't break on Python 3...
Augie Fackler -
r33680:d880a6bc default
parent child Browse files
Show More
@@ -1,1777 +1,1778 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 tempfile
22 22 import traceback
23 23
24 24 from .i18n import _
25 25 from .node import hex
26 26
27 27 from . import (
28 28 color,
29 29 config,
30 30 configitems,
31 31 encoding,
32 32 error,
33 33 formatter,
34 34 progress,
35 35 pycompat,
36 36 rcutil,
37 37 scmutil,
38 38 util,
39 39 )
40 40
41 41 urlreq = util.urlreq
42 42
43 43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 45 if not c.isalnum())
46 46
47 47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 48 tweakrc = """
49 49 [ui]
50 50 # The rollback command is dangerous. As a rule, don't use it.
51 51 rollback = False
52 52
53 53 [commands]
54 54 # Make `hg status` emit cwd-relative paths by default.
55 55 status.relative = yes
56 56
57 57 [diff]
58 58 git = 1
59 59 """
60 60
61 61 samplehgrcs = {
62 62 'user':
63 63 b"""# example user config (see 'hg help config' for more info)
64 64 [ui]
65 65 # name and email, e.g.
66 66 # username = Jane Doe <jdoe@example.com>
67 67 username =
68 68
69 69 # uncomment to disable color in command output
70 70 # (see 'hg help color' for details)
71 71 # color = never
72 72
73 73 # uncomment to disable command output pagination
74 74 # (see 'hg help pager' for details)
75 75 # paginate = never
76 76
77 77 [extensions]
78 78 # uncomment these lines to enable some popular extensions
79 79 # (see 'hg help extensions' for more info)
80 80 #
81 81 # churn =
82 82 """,
83 83
84 84 'cloned':
85 85 b"""# example repository config (see 'hg help config' for more info)
86 86 [paths]
87 87 default = %s
88 88
89 89 # path aliases to other clones of this repo in URLs or filesystem paths
90 90 # (see 'hg help config.paths' for more info)
91 91 #
92 92 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
93 93 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
94 94 # my-clone = /home/jdoe/jdoes-clone
95 95
96 96 [ui]
97 97 # name and email (local to this repository, optional), e.g.
98 98 # username = Jane Doe <jdoe@example.com>
99 99 """,
100 100
101 101 'local':
102 102 b"""# example repository config (see 'hg help config' for more info)
103 103 [paths]
104 104 # path aliases to other clones of this repo in URLs or filesystem paths
105 105 # (see 'hg help config.paths' for more info)
106 106 #
107 107 # default = http://example.com/hg/example-repo
108 108 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
109 109 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
110 110 # my-clone = /home/jdoe/jdoes-clone
111 111
112 112 [ui]
113 113 # name and email (local to this repository, optional), e.g.
114 114 # username = Jane Doe <jdoe@example.com>
115 115 """,
116 116
117 117 'global':
118 118 b"""# example system-wide hg config (see 'hg help config' for more info)
119 119
120 120 [ui]
121 121 # uncomment to disable color in command output
122 122 # (see 'hg help color' for details)
123 123 # color = never
124 124
125 125 # uncomment to disable command output pagination
126 126 # (see 'hg help pager' for details)
127 127 # paginate = never
128 128
129 129 [extensions]
130 130 # uncomment these lines to enable some popular extensions
131 131 # (see 'hg help extensions' for more info)
132 132 #
133 133 # blackbox =
134 134 # churn =
135 135 """,
136 136 }
137 137
138 138
139 139 class httppasswordmgrdbproxy(object):
140 140 """Delays loading urllib2 until it's needed."""
141 141 def __init__(self):
142 142 self._mgr = None
143 143
144 144 def _get_mgr(self):
145 145 if self._mgr is None:
146 146 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
147 147 return self._mgr
148 148
149 149 def add_password(self, *args, **kwargs):
150 150 return self._get_mgr().add_password(*args, **kwargs)
151 151
152 152 def find_user_password(self, *args, **kwargs):
153 153 return self._get_mgr().find_user_password(*args, **kwargs)
154 154
155 155 def _catchterm(*args):
156 156 raise error.SignalInterrupt
157 157
158 158 # unique object used to detect no default value has been provided when
159 159 # retrieving configuration value.
160 160 _unset = object()
161 161
162 162 class ui(object):
163 163 def __init__(self, src=None):
164 164 """Create a fresh new ui object if no src given
165 165
166 166 Use uimod.ui.load() to create a ui which knows global and user configs.
167 167 In most cases, you should use ui.copy() to create a copy of an existing
168 168 ui object.
169 169 """
170 170 # _buffers: used for temporary capture of output
171 171 self._buffers = []
172 172 # _exithandlers: callbacks run at the end of a request
173 173 self._exithandlers = []
174 174 # 3-tuple describing how each buffer in the stack behaves.
175 175 # Values are (capture stderr, capture subprocesses, apply labels).
176 176 self._bufferstates = []
177 177 # When a buffer is active, defines whether we are expanding labels.
178 178 # This exists to prevent an extra list lookup.
179 179 self._bufferapplylabels = None
180 180 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
181 181 self._reportuntrusted = True
182 182 self._knownconfig = configitems.coreitems
183 183 self._ocfg = config.config() # overlay
184 184 self._tcfg = config.config() # trusted
185 185 self._ucfg = config.config() # untrusted
186 186 self._trustusers = set()
187 187 self._trustgroups = set()
188 188 self.callhooks = True
189 189 # Insecure server connections requested.
190 190 self.insecureconnections = False
191 191 # Blocked time
192 192 self.logblockedtimes = False
193 193 # color mode: see mercurial/color.py for possible value
194 194 self._colormode = None
195 195 self._terminfoparams = {}
196 196 self._styles = {}
197 197
198 198 if src:
199 199 self._exithandlers = src._exithandlers
200 200 self.fout = src.fout
201 201 self.ferr = src.ferr
202 202 self.fin = src.fin
203 203 self.pageractive = src.pageractive
204 204 self._disablepager = src._disablepager
205 205 self._tweaked = src._tweaked
206 206
207 207 self._tcfg = src._tcfg.copy()
208 208 self._ucfg = src._ucfg.copy()
209 209 self._ocfg = src._ocfg.copy()
210 210 self._trustusers = src._trustusers.copy()
211 211 self._trustgroups = src._trustgroups.copy()
212 212 self.environ = src.environ
213 213 self.callhooks = src.callhooks
214 214 self.insecureconnections = src.insecureconnections
215 215 self._colormode = src._colormode
216 216 self._terminfoparams = src._terminfoparams.copy()
217 217 self._styles = src._styles.copy()
218 218
219 219 self.fixconfig()
220 220
221 221 self.httppasswordmgrdb = src.httppasswordmgrdb
222 222 self._blockedtimes = src._blockedtimes
223 223 else:
224 224 self.fout = util.stdout
225 225 self.ferr = util.stderr
226 226 self.fin = util.stdin
227 227 self.pageractive = False
228 228 self._disablepager = False
229 229 self._tweaked = False
230 230
231 231 # shared read-only environment
232 232 self.environ = encoding.environ
233 233
234 234 self.httppasswordmgrdb = httppasswordmgrdbproxy()
235 235 self._blockedtimes = collections.defaultdict(int)
236 236
237 237 allowed = self.configlist('experimental', 'exportableenviron')
238 238 if '*' in allowed:
239 239 self._exportableenviron = self.environ
240 240 else:
241 241 self._exportableenviron = {}
242 242 for k in allowed:
243 243 if k in self.environ:
244 244 self._exportableenviron[k] = self.environ[k]
245 245
246 246 @classmethod
247 247 def load(cls):
248 248 """Create a ui and load global and user configs"""
249 249 u = cls()
250 250 # we always trust global config files and environment variables
251 251 for t, f in rcutil.rccomponents():
252 252 if t == 'path':
253 253 u.readconfig(f, trust=True)
254 254 elif t == 'items':
255 255 sections = set()
256 256 for section, name, value, source in f:
257 257 # do not set u._ocfg
258 258 # XXX clean this up once immutable config object is a thing
259 259 u._tcfg.set(section, name, value, source)
260 260 u._ucfg.set(section, name, value, source)
261 261 sections.add(section)
262 262 for section in sections:
263 263 u.fixconfig(section=section)
264 264 else:
265 265 raise error.ProgrammingError('unknown rctype: %s' % t)
266 266 u._maybetweakdefaults()
267 267 return u
268 268
269 269 def _maybetweakdefaults(self):
270 270 if not self.configbool('ui', 'tweakdefaults'):
271 271 return
272 272 if self._tweaked or self.plain('tweakdefaults'):
273 273 return
274 274
275 275 # Note: it is SUPER IMPORTANT that you set self._tweaked to
276 276 # True *before* any calls to setconfig(), otherwise you'll get
277 277 # infinite recursion between setconfig and this method.
278 278 #
279 279 # TODO: We should extract an inner method in setconfig() to
280 280 # avoid this weirdness.
281 281 self._tweaked = True
282 282 tmpcfg = config.config()
283 283 tmpcfg.parse('<tweakdefaults>', tweakrc)
284 284 for section in tmpcfg:
285 285 for name, value in tmpcfg.items(section):
286 286 if not self.hasconfig(section, name):
287 287 self.setconfig(section, name, value, "<tweakdefaults>")
288 288
289 289 def copy(self):
290 290 return self.__class__(self)
291 291
292 292 def resetstate(self):
293 293 """Clear internal state that shouldn't persist across commands"""
294 294 if self._progbar:
295 295 self._progbar.resetstate() # reset last-print time of progress bar
296 296 self.httppasswordmgrdb = httppasswordmgrdbproxy()
297 297
298 298 @contextlib.contextmanager
299 299 def timeblockedsection(self, key):
300 300 # this is open-coded below - search for timeblockedsection to find them
301 301 starttime = util.timer()
302 302 try:
303 303 yield
304 304 finally:
305 305 self._blockedtimes[key + '_blocked'] += \
306 306 (util.timer() - starttime) * 1000
307 307
308 308 def formatter(self, topic, opts):
309 309 return formatter.formatter(self, self, topic, opts)
310 310
311 311 def _trusted(self, fp, f):
312 312 st = util.fstat(fp)
313 313 if util.isowner(st):
314 314 return True
315 315
316 316 tusers, tgroups = self._trustusers, self._trustgroups
317 317 if '*' in tusers or '*' in tgroups:
318 318 return True
319 319
320 320 user = util.username(st.st_uid)
321 321 group = util.groupname(st.st_gid)
322 322 if user in tusers or group in tgroups or user == util.username():
323 323 return True
324 324
325 325 if self._reportuntrusted:
326 326 self.warn(_('not trusting file %s from untrusted '
327 327 'user %s, group %s\n') % (f, user, group))
328 328 return False
329 329
330 330 def readconfig(self, filename, root=None, trust=False,
331 331 sections=None, remap=None):
332 332 try:
333 333 fp = open(filename, u'rb')
334 334 except IOError:
335 335 if not sections: # ignore unless we were looking for something
336 336 return
337 337 raise
338 338
339 339 cfg = config.config()
340 340 trusted = sections or trust or self._trusted(fp, filename)
341 341
342 342 try:
343 343 cfg.read(filename, fp, sections=sections, remap=remap)
344 344 fp.close()
345 345 except error.ConfigError as inst:
346 346 if trusted:
347 347 raise
348 348 self.warn(_("ignored: %s\n") % str(inst))
349 349
350 350 if self.plain():
351 351 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
352 352 'logtemplate', 'statuscopies', 'style',
353 353 'traceback', 'verbose'):
354 354 if k in cfg['ui']:
355 355 del cfg['ui'][k]
356 356 for k, v in cfg.items('defaults'):
357 357 del cfg['defaults'][k]
358 358 for k, v in cfg.items('commands'):
359 359 del cfg['commands'][k]
360 360 # Don't remove aliases from the configuration if in the exceptionlist
361 361 if self.plain('alias'):
362 362 for k, v in cfg.items('alias'):
363 363 del cfg['alias'][k]
364 364 if self.plain('revsetalias'):
365 365 for k, v in cfg.items('revsetalias'):
366 366 del cfg['revsetalias'][k]
367 367 if self.plain('templatealias'):
368 368 for k, v in cfg.items('templatealias'):
369 369 del cfg['templatealias'][k]
370 370
371 371 if trusted:
372 372 self._tcfg.update(cfg)
373 373 self._tcfg.update(self._ocfg)
374 374 self._ucfg.update(cfg)
375 375 self._ucfg.update(self._ocfg)
376 376
377 377 if root is None:
378 378 root = os.path.expanduser('~')
379 379 self.fixconfig(root=root)
380 380
381 381 def fixconfig(self, root=None, section=None):
382 382 if section in (None, 'paths'):
383 383 # expand vars and ~
384 384 # translate paths relative to root (or home) into absolute paths
385 385 root = root or pycompat.getcwd()
386 386 for c in self._tcfg, self._ucfg, self._ocfg:
387 387 for n, p in c.items('paths'):
388 388 # Ignore sub-options.
389 389 if ':' in n:
390 390 continue
391 391 if not p:
392 392 continue
393 393 if '%%' in p:
394 394 s = self.configsource('paths', n) or 'none'
395 395 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
396 396 % (n, p, s))
397 397 p = p.replace('%%', '%')
398 398 p = util.expandpath(p)
399 399 if not util.hasscheme(p) and not os.path.isabs(p):
400 400 p = os.path.normpath(os.path.join(root, p))
401 401 c.set("paths", n, p)
402 402
403 403 if section in (None, 'ui'):
404 404 # update ui options
405 405 self.debugflag = self.configbool('ui', 'debug')
406 406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
407 407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
408 408 if self.verbose and self.quiet:
409 409 self.quiet = self.verbose = False
410 410 self._reportuntrusted = self.debugflag or self.configbool("ui",
411 411 "report_untrusted")
412 412 self.tracebackflag = self.configbool('ui', 'traceback')
413 413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
414 414
415 415 if section in (None, 'trusted'):
416 416 # update trust information
417 417 self._trustusers.update(self.configlist('trusted', 'users'))
418 418 self._trustgroups.update(self.configlist('trusted', 'groups'))
419 419
420 420 def backupconfig(self, section, item):
421 421 return (self._ocfg.backup(section, item),
422 422 self._tcfg.backup(section, item),
423 423 self._ucfg.backup(section, item),)
424 424 def restoreconfig(self, data):
425 425 self._ocfg.restore(data[0])
426 426 self._tcfg.restore(data[1])
427 427 self._ucfg.restore(data[2])
428 428
429 429 def setconfig(self, section, name, value, source=''):
430 430 for cfg in (self._ocfg, self._tcfg, self._ucfg):
431 431 cfg.set(section, name, value, source)
432 432 self.fixconfig(section=section)
433 433 self._maybetweakdefaults()
434 434
435 435 def _data(self, untrusted):
436 436 return untrusted and self._ucfg or self._tcfg
437 437
438 438 def configsource(self, section, name, untrusted=False):
439 439 return self._data(untrusted).source(section, name)
440 440
441 441 def config(self, section, name, default=_unset, untrusted=False):
442 442 """return the plain string version of a config"""
443 443 value = self._config(section, name, default=default,
444 444 untrusted=untrusted)
445 445 if value is _unset:
446 446 return None
447 447 return value
448 448
449 449 def _config(self, section, name, default=_unset, untrusted=False):
450 450 value = default
451 451 item = self._knownconfig.get(section, {}).get(name)
452 452 alternates = [(section, name)]
453 453
454 454 if item is not None:
455 455 alternates.extend(item.alias)
456 456
457 457 if default is _unset:
458 458 if item is None:
459 459 value = default
460 460 elif item.default is configitems.dynamicdefault:
461 461 value = None
462 462 msg = "config item requires an explicit default value: '%s.%s'"
463 463 msg %= (section, name)
464 464 self.develwarn(msg, 2, 'warn-config-default')
465 465 elif callable(item.default):
466 466 value = item.default()
467 467 else:
468 468 value = item.default
469 469 elif (item is not None
470 470 and item.default is not configitems.dynamicdefault):
471 471 msg = ("specifying a default value for a registered "
472 472 "config item: '%s.%s' '%s'")
473 473 msg %= (section, name, default)
474 474 self.develwarn(msg, 2, 'warn-config-default')
475 475
476 476 for s, n in alternates:
477 477 candidate = self._data(untrusted).get(s, n, None)
478 478 if candidate is not None:
479 479 value = candidate
480 480 section = s
481 481 name = n
482 482 break
483 483
484 484 if self.debugflag and not untrusted and self._reportuntrusted:
485 485 for s, n in alternates:
486 486 uvalue = self._ucfg.get(s, n)
487 487 if uvalue is not None and uvalue != value:
488 488 self.debug("ignoring untrusted configuration option "
489 489 "%s.%s = %s\n" % (s, n, uvalue))
490 490 return value
491 491
492 492 def configsuboptions(self, section, name, default=_unset, untrusted=False):
493 493 """Get a config option and all sub-options.
494 494
495 495 Some config options have sub-options that are declared with the
496 496 format "key:opt = value". This method is used to return the main
497 497 option and all its declared sub-options.
498 498
499 499 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
500 500 is a dict of defined sub-options where keys and values are strings.
501 501 """
502 502 main = self.config(section, name, default, untrusted=untrusted)
503 503 data = self._data(untrusted)
504 504 sub = {}
505 505 prefix = '%s:' % name
506 506 for k, v in data.items(section):
507 507 if k.startswith(prefix):
508 508 sub[k[len(prefix):]] = v
509 509
510 510 if self.debugflag and not untrusted and self._reportuntrusted:
511 511 for k, v in sub.items():
512 512 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
513 513 if uvalue is not None and uvalue != v:
514 514 self.debug('ignoring untrusted configuration option '
515 515 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
516 516
517 517 return main, sub
518 518
519 519 def configpath(self, section, name, default=_unset, untrusted=False):
520 520 'get a path config item, expanded relative to repo root or config file'
521 521 v = self.config(section, name, default, untrusted)
522 522 if v is None:
523 523 return None
524 524 if not os.path.isabs(v) or "://" not in v:
525 525 src = self.configsource(section, name, untrusted)
526 526 if ':' in src:
527 527 base = os.path.dirname(src.rsplit(':')[0])
528 528 v = os.path.join(base, os.path.expanduser(v))
529 529 return v
530 530
531 531 def configbool(self, section, name, default=_unset, untrusted=False):
532 532 """parse a configuration element as a boolean
533 533
534 534 >>> u = ui(); s = 'foo'
535 535 >>> u.setconfig(s, 'true', 'yes')
536 536 >>> u.configbool(s, 'true')
537 537 True
538 538 >>> u.setconfig(s, 'false', 'no')
539 539 >>> u.configbool(s, 'false')
540 540 False
541 541 >>> u.configbool(s, 'unknown')
542 542 False
543 543 >>> u.configbool(s, 'unknown', True)
544 544 True
545 545 >>> u.setconfig(s, 'invalid', 'somevalue')
546 546 >>> u.configbool(s, 'invalid')
547 547 Traceback (most recent call last):
548 548 ...
549 549 ConfigError: foo.invalid is not a boolean ('somevalue')
550 550 """
551 551
552 552 v = self._config(section, name, default, untrusted=untrusted)
553 553 if v is None:
554 554 return v
555 555 if v is _unset:
556 556 if default is _unset:
557 557 return False
558 558 return default
559 559 if isinstance(v, bool):
560 560 return v
561 561 b = util.parsebool(v)
562 562 if b is None:
563 563 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
564 564 % (section, name, v))
565 565 return b
566 566
567 567 def configwith(self, convert, section, name, default=_unset,
568 568 desc=None, untrusted=False):
569 569 """parse a configuration element with a conversion function
570 570
571 571 >>> u = ui(); s = 'foo'
572 572 >>> u.setconfig(s, 'float1', '42')
573 573 >>> u.configwith(float, s, 'float1')
574 574 42.0
575 575 >>> u.setconfig(s, 'float2', '-4.25')
576 576 >>> u.configwith(float, s, 'float2')
577 577 -4.25
578 578 >>> u.configwith(float, s, 'unknown', 7)
579 579 7.0
580 580 >>> u.setconfig(s, 'invalid', 'somevalue')
581 581 >>> u.configwith(float, s, 'invalid')
582 582 Traceback (most recent call last):
583 583 ...
584 584 ConfigError: foo.invalid is not a valid float ('somevalue')
585 585 >>> u.configwith(float, s, 'invalid', desc='womble')
586 586 Traceback (most recent call last):
587 587 ...
588 588 ConfigError: foo.invalid is not a valid womble ('somevalue')
589 589 """
590 590
591 591 v = self.config(section, name, default, untrusted)
592 592 if v is None:
593 593 return v # do not attempt to convert None
594 594 try:
595 595 return convert(v)
596 596 except (ValueError, error.ParseError):
597 597 if desc is None:
598 598 desc = convert.__name__
599 599 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
600 600 % (section, name, desc, v))
601 601
602 602 def configint(self, section, name, default=_unset, untrusted=False):
603 603 """parse a configuration element as an integer
604 604
605 605 >>> u = ui(); s = 'foo'
606 606 >>> u.setconfig(s, 'int1', '42')
607 607 >>> u.configint(s, 'int1')
608 608 42
609 609 >>> u.setconfig(s, 'int2', '-42')
610 610 >>> u.configint(s, 'int2')
611 611 -42
612 612 >>> u.configint(s, 'unknown', 7)
613 613 7
614 614 >>> u.setconfig(s, 'invalid', 'somevalue')
615 615 >>> u.configint(s, 'invalid')
616 616 Traceback (most recent call last):
617 617 ...
618 618 ConfigError: foo.invalid is not a valid integer ('somevalue')
619 619 """
620 620
621 621 return self.configwith(int, section, name, default, 'integer',
622 622 untrusted)
623 623
624 624 def configbytes(self, section, name, default=_unset, untrusted=False):
625 625 """parse a configuration element as a quantity in bytes
626 626
627 627 Units can be specified as b (bytes), k or kb (kilobytes), m or
628 628 mb (megabytes), g or gb (gigabytes).
629 629
630 630 >>> u = ui(); s = 'foo'
631 631 >>> u.setconfig(s, 'val1', '42')
632 632 >>> u.configbytes(s, 'val1')
633 633 42
634 634 >>> u.setconfig(s, 'val2', '42.5 kb')
635 635 >>> u.configbytes(s, 'val2')
636 636 43520
637 637 >>> u.configbytes(s, 'unknown', '7 MB')
638 638 7340032
639 639 >>> u.setconfig(s, 'invalid', 'somevalue')
640 640 >>> u.configbytes(s, 'invalid')
641 641 Traceback (most recent call last):
642 642 ...
643 643 ConfigError: foo.invalid is not a byte quantity ('somevalue')
644 644 """
645 645
646 646 value = self._config(section, name, default, untrusted)
647 647 if value is _unset:
648 648 if default is _unset:
649 649 default = 0
650 650 value = default
651 651 if not isinstance(value, bytes):
652 652 return value
653 653 try:
654 654 return util.sizetoint(value)
655 655 except error.ParseError:
656 656 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
657 657 % (section, name, value))
658 658
659 659 def configlist(self, section, name, default=_unset, untrusted=False):
660 660 """parse a configuration element as a list of comma/space separated
661 661 strings
662 662
663 663 >>> u = ui(); s = 'foo'
664 664 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
665 665 >>> u.configlist(s, 'list1')
666 666 ['this', 'is', 'a small', 'test']
667 667 """
668 668 # default is not always a list
669 669 v = self.configwith(config.parselist, section, name, default,
670 670 'list', untrusted)
671 671 if isinstance(v, bytes):
672 672 return config.parselist(v)
673 673 elif v is None:
674 674 return []
675 675 return v
676 676
677 677 def configdate(self, section, name, default=_unset, untrusted=False):
678 678 """parse a configuration element as a tuple of ints
679 679
680 680 >>> u = ui(); s = 'foo'
681 681 >>> u.setconfig(s, 'date', '0 0')
682 682 >>> u.configdate(s, 'date')
683 683 (0, 0)
684 684 """
685 685 if self.config(section, name, default, untrusted):
686 686 return self.configwith(util.parsedate, section, name, default,
687 687 'date', untrusted)
688 688 if default is _unset:
689 689 return None
690 690 return default
691 691
692 692 def hasconfig(self, section, name, untrusted=False):
693 693 return self._data(untrusted).hasitem(section, name)
694 694
695 695 def has_section(self, section, untrusted=False):
696 696 '''tell whether section exists in config.'''
697 697 return section in self._data(untrusted)
698 698
699 699 def configitems(self, section, untrusted=False, ignoresub=False):
700 700 items = self._data(untrusted).items(section)
701 701 if ignoresub:
702 702 newitems = {}
703 703 for k, v in items:
704 704 if ':' not in k:
705 705 newitems[k] = v
706 706 items = newitems.items()
707 707 if self.debugflag and not untrusted and self._reportuntrusted:
708 708 for k, v in self._ucfg.items(section):
709 709 if self._tcfg.get(section, k) != v:
710 710 self.debug("ignoring untrusted configuration option "
711 711 "%s.%s = %s\n" % (section, k, v))
712 712 return items
713 713
714 714 def walkconfig(self, untrusted=False):
715 715 cfg = self._data(untrusted)
716 716 for section in cfg.sections():
717 717 for name, value in self.configitems(section, untrusted):
718 718 yield section, name, value
719 719
720 720 def plain(self, feature=None):
721 721 '''is plain mode active?
722 722
723 723 Plain mode means that all configuration variables which affect
724 724 the behavior and output of Mercurial should be
725 725 ignored. Additionally, the output should be stable,
726 726 reproducible and suitable for use in scripts or applications.
727 727
728 728 The only way to trigger plain mode is by setting either the
729 729 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
730 730
731 731 The return value can either be
732 732 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
733 733 - True otherwise
734 734 '''
735 735 if ('HGPLAIN' not in encoding.environ and
736 736 'HGPLAINEXCEPT' not in encoding.environ):
737 737 return False
738 738 exceptions = encoding.environ.get('HGPLAINEXCEPT',
739 739 '').strip().split(',')
740 740 if feature and exceptions:
741 741 return feature not in exceptions
742 742 return True
743 743
744 744 def username(self):
745 745 """Return default username to be used in commits.
746 746
747 747 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
748 748 and stop searching if one of these is set.
749 749 If not found and ui.askusername is True, ask the user, else use
750 750 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
751 751 """
752 752 user = encoding.environ.get("HGUSER")
753 753 if user is None:
754 754 user = self.config("ui", "username")
755 755 if user is not None:
756 756 user = os.path.expandvars(user)
757 757 if user is None:
758 758 user = encoding.environ.get("EMAIL")
759 759 if user is None and self.configbool("ui", "askusername"):
760 760 user = self.prompt(_("enter a commit username:"), default=None)
761 761 if user is None and not self.interactive():
762 762 try:
763 763 user = '%s@%s' % (util.getuser(), socket.getfqdn())
764 764 self.warn(_("no username found, using '%s' instead\n") % user)
765 765 except KeyError:
766 766 pass
767 767 if not user:
768 768 raise error.Abort(_('no username supplied'),
769 769 hint=_("use 'hg config --edit' "
770 770 'to set your username'))
771 771 if "\n" in user:
772 772 raise error.Abort(_("username %s contains a newline\n")
773 773 % repr(user))
774 774 return user
775 775
776 776 def shortuser(self, user):
777 777 """Return a short representation of a user name or email address."""
778 778 if not self.verbose:
779 779 user = util.shortuser(user)
780 780 return user
781 781
782 782 def expandpath(self, loc, default=None):
783 783 """Return repository location relative to cwd or from [paths]"""
784 784 try:
785 785 p = self.paths.getpath(loc)
786 786 if p:
787 787 return p.rawloc
788 788 except error.RepoError:
789 789 pass
790 790
791 791 if default:
792 792 try:
793 793 p = self.paths.getpath(default)
794 794 if p:
795 795 return p.rawloc
796 796 except error.RepoError:
797 797 pass
798 798
799 799 return loc
800 800
801 801 @util.propertycache
802 802 def paths(self):
803 803 return paths(self)
804 804
805 805 def pushbuffer(self, error=False, subproc=False, labeled=False):
806 806 """install a buffer to capture standard output of the ui object
807 807
808 808 If error is True, the error output will be captured too.
809 809
810 810 If subproc is True, output from subprocesses (typically hooks) will be
811 811 captured too.
812 812
813 813 If labeled is True, any labels associated with buffered
814 814 output will be handled. By default, this has no effect
815 815 on the output returned, but extensions and GUI tools may
816 816 handle this argument and returned styled output. If output
817 817 is being buffered so it can be captured and parsed or
818 818 processed, labeled should not be set to True.
819 819 """
820 820 self._buffers.append([])
821 821 self._bufferstates.append((error, subproc, labeled))
822 822 self._bufferapplylabels = labeled
823 823
824 824 def popbuffer(self):
825 825 '''pop the last buffer and return the buffered output'''
826 826 self._bufferstates.pop()
827 827 if self._bufferstates:
828 828 self._bufferapplylabels = self._bufferstates[-1][2]
829 829 else:
830 830 self._bufferapplylabels = None
831 831
832 832 return "".join(self._buffers.pop())
833 833
834 834 def write(self, *args, **opts):
835 835 '''write args to output
836 836
837 837 By default, this method simply writes to the buffer or stdout.
838 838 Color mode can be set on the UI class to have the output decorated
839 839 with color modifier before being written to stdout.
840 840
841 841 The color used is controlled by an optional keyword argument, "label".
842 842 This should be a string containing label names separated by space.
843 843 Label names take the form of "topic.type". For example, ui.debug()
844 844 issues a label of "ui.debug".
845 845
846 846 When labeling output for a specific command, a label of
847 847 "cmdname.type" is recommended. For example, status issues
848 848 a label of "status.modified" for modified files.
849 849 '''
850 850 if self._buffers and not opts.get('prompt', False):
851 851 if self._bufferapplylabels:
852 852 label = opts.get('label', '')
853 853 self._buffers[-1].extend(self.label(a, label) for a in args)
854 854 else:
855 855 self._buffers[-1].extend(args)
856 856 elif self._colormode == 'win32':
857 857 # windows color printing is its own can of crab, defer to
858 858 # the color module and that is it.
859 859 color.win32print(self, self._write, *args, **opts)
860 860 else:
861 861 msgs = args
862 862 if self._colormode is not None:
863 863 label = opts.get('label', '')
864 864 msgs = [self.label(a, label) for a in args]
865 865 self._write(*msgs, **opts)
866 866
867 867 def _write(self, *msgs, **opts):
868 868 self._progclear()
869 869 # opencode timeblockedsection because this is a critical path
870 870 starttime = util.timer()
871 871 try:
872 872 for a in msgs:
873 873 self.fout.write(a)
874 874 except IOError as err:
875 875 raise error.StdioError(err)
876 876 finally:
877 877 self._blockedtimes['stdio_blocked'] += \
878 878 (util.timer() - starttime) * 1000
879 879
880 880 def write_err(self, *args, **opts):
881 881 self._progclear()
882 882 if self._bufferstates and self._bufferstates[-1][0]:
883 883 self.write(*args, **opts)
884 884 elif self._colormode == 'win32':
885 885 # windows color printing is its own can of crab, defer to
886 886 # the color module and that is it.
887 887 color.win32print(self, self._write_err, *args, **opts)
888 888 else:
889 889 msgs = args
890 890 if self._colormode is not None:
891 891 label = opts.get('label', '')
892 892 msgs = [self.label(a, label) for a in args]
893 893 self._write_err(*msgs, **opts)
894 894
895 895 def _write_err(self, *msgs, **opts):
896 896 try:
897 897 with self.timeblockedsection('stdio'):
898 898 if not getattr(self.fout, 'closed', False):
899 899 self.fout.flush()
900 900 for a in msgs:
901 901 self.ferr.write(a)
902 902 # stderr may be buffered under win32 when redirected to files,
903 903 # including stdout.
904 904 if not getattr(self.ferr, 'closed', False):
905 905 self.ferr.flush()
906 906 except IOError as inst:
907 907 raise error.StdioError(inst)
908 908
909 909 def flush(self):
910 910 # opencode timeblockedsection because this is a critical path
911 911 starttime = util.timer()
912 912 try:
913 913 try:
914 914 self.fout.flush()
915 915 except IOError as err:
916 916 raise error.StdioError(err)
917 917 finally:
918 918 try:
919 919 self.ferr.flush()
920 920 except IOError as err:
921 921 raise error.StdioError(err)
922 922 finally:
923 923 self._blockedtimes['stdio_blocked'] += \
924 924 (util.timer() - starttime) * 1000
925 925
926 926 def _isatty(self, fh):
927 927 if self.configbool('ui', 'nontty'):
928 928 return False
929 929 return util.isatty(fh)
930 930
931 931 def disablepager(self):
932 932 self._disablepager = True
933 933
934 934 def pager(self, command):
935 935 """Start a pager for subsequent command output.
936 936
937 937 Commands which produce a long stream of output should call
938 938 this function to activate the user's preferred pagination
939 939 mechanism (which may be no pager). Calling this function
940 940 precludes any future use of interactive functionality, such as
941 941 prompting the user or activating curses.
942 942
943 943 Args:
944 944 command: The full, non-aliased name of the command. That is, "log"
945 945 not "history, "summary" not "summ", etc.
946 946 """
947 947 if (self._disablepager
948 948 or self.pageractive
949 949 or command in self.configlist('pager', 'ignore')
950 950 or not self.configbool('ui', 'paginate')
951 951 or not self.configbool('pager', 'attend-' + command, True)
952 952 # TODO: if we want to allow HGPLAINEXCEPT=pager,
953 953 # formatted() will need some adjustment.
954 954 or not self.formatted()
955 955 or self.plain()
956 956 # TODO: expose debugger-enabled on the UI object
957 957 or '--debugger' in pycompat.sysargv):
958 958 # We only want to paginate if the ui appears to be
959 959 # interactive, the user didn't say HGPLAIN or
960 960 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
961 961 return
962 962
963 963 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
964 964 if not pagercmd:
965 965 return
966 966
967 967 pagerenv = {}
968 968 for name, value in rcutil.defaultpagerenv().items():
969 969 if name not in encoding.environ:
970 970 pagerenv[name] = value
971 971
972 972 self.debug('starting pager for command %r\n' % command)
973 973 self.flush()
974 974
975 975 wasformatted = self.formatted()
976 976 if util.safehasattr(signal, "SIGPIPE"):
977 977 signal.signal(signal.SIGPIPE, _catchterm)
978 978 if self._runpager(pagercmd, pagerenv):
979 979 self.pageractive = True
980 980 # Preserve the formatted-ness of the UI. This is important
981 981 # because we mess with stdout, which might confuse
982 982 # auto-detection of things being formatted.
983 983 self.setconfig('ui', 'formatted', wasformatted, 'pager')
984 984 self.setconfig('ui', 'interactive', False, 'pager')
985 985
986 986 # If pagermode differs from color.mode, reconfigure color now that
987 987 # pageractive is set.
988 988 cm = self._colormode
989 989 if cm != self.config('color', 'pagermode', cm):
990 990 color.setup(self)
991 991 else:
992 992 # If the pager can't be spawned in dispatch when --pager=on is
993 993 # given, don't try again when the command runs, to avoid a duplicate
994 994 # warning about a missing pager command.
995 995 self.disablepager()
996 996
997 997 def _runpager(self, command, env=None):
998 998 """Actually start the pager and set up file descriptors.
999 999
1000 1000 This is separate in part so that extensions (like chg) can
1001 1001 override how a pager is invoked.
1002 1002 """
1003 1003 if command == 'cat':
1004 1004 # Save ourselves some work.
1005 1005 return False
1006 1006 # If the command doesn't contain any of these characters, we
1007 1007 # assume it's a binary and exec it directly. This means for
1008 1008 # simple pager command configurations, we can degrade
1009 1009 # gracefully and tell the user about their broken pager.
1010 1010 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1011 1011
1012 1012 if pycompat.osname == 'nt' and not shell:
1013 1013 # Window's built-in `more` cannot be invoked with shell=False, but
1014 1014 # its `more.com` can. Hide this implementation detail from the
1015 1015 # user so we can also get sane bad PAGER behavior. MSYS has
1016 1016 # `more.exe`, so do a cmd.exe style resolution of the executable to
1017 1017 # determine which one to use.
1018 1018 fullcmd = util.findexe(command)
1019 1019 if not fullcmd:
1020 1020 self.warn(_("missing pager command '%s', skipping pager\n")
1021 1021 % command)
1022 1022 return False
1023 1023
1024 1024 command = fullcmd
1025 1025
1026 1026 try:
1027 1027 pager = subprocess.Popen(
1028 1028 command, shell=shell, bufsize=-1,
1029 1029 close_fds=util.closefds, stdin=subprocess.PIPE,
1030 1030 stdout=util.stdout, stderr=util.stderr,
1031 1031 env=util.shellenviron(env))
1032 1032 except OSError as e:
1033 1033 if e.errno == errno.ENOENT and not shell:
1034 1034 self.warn(_("missing pager command '%s', skipping pager\n")
1035 1035 % command)
1036 1036 return False
1037 1037 raise
1038 1038
1039 1039 # back up original file descriptors
1040 1040 stdoutfd = os.dup(util.stdout.fileno())
1041 1041 stderrfd = os.dup(util.stderr.fileno())
1042 1042
1043 1043 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1044 1044 if self._isatty(util.stderr):
1045 1045 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1046 1046
1047 1047 @self.atexit
1048 1048 def killpager():
1049 1049 if util.safehasattr(signal, "SIGINT"):
1050 1050 signal.signal(signal.SIGINT, signal.SIG_IGN)
1051 1051 # restore original fds, closing pager.stdin copies in the process
1052 1052 os.dup2(stdoutfd, util.stdout.fileno())
1053 1053 os.dup2(stderrfd, util.stderr.fileno())
1054 1054 pager.stdin.close()
1055 1055 pager.wait()
1056 1056
1057 1057 return True
1058 1058
1059 1059 def atexit(self, func, *args, **kwargs):
1060 1060 '''register a function to run after dispatching a request
1061 1061
1062 1062 Handlers do not stay registered across request boundaries.'''
1063 1063 self._exithandlers.append((func, args, kwargs))
1064 1064 return func
1065 1065
1066 1066 def interface(self, feature):
1067 1067 """what interface to use for interactive console features?
1068 1068
1069 1069 The interface is controlled by the value of `ui.interface` but also by
1070 1070 the value of feature-specific configuration. For example:
1071 1071
1072 1072 ui.interface.histedit = text
1073 1073 ui.interface.chunkselector = curses
1074 1074
1075 1075 Here the features are "histedit" and "chunkselector".
1076 1076
1077 1077 The configuration above means that the default interfaces for commands
1078 1078 is curses, the interface for histedit is text and the interface for
1079 1079 selecting chunk is crecord (the best curses interface available).
1080 1080
1081 1081 Consider the following example:
1082 1082 ui.interface = curses
1083 1083 ui.interface.histedit = text
1084 1084
1085 1085 Then histedit will use the text interface and chunkselector will use
1086 1086 the default curses interface (crecord at the moment).
1087 1087 """
1088 1088 alldefaults = frozenset(["text", "curses"])
1089 1089
1090 1090 featureinterfaces = {
1091 1091 "chunkselector": [
1092 1092 "text",
1093 1093 "curses",
1094 1094 ]
1095 1095 }
1096 1096
1097 1097 # Feature-specific interface
1098 1098 if feature not in featureinterfaces.keys():
1099 1099 # Programming error, not user error
1100 1100 raise ValueError("Unknown feature requested %s" % feature)
1101 1101
1102 1102 availableinterfaces = frozenset(featureinterfaces[feature])
1103 1103 if alldefaults > availableinterfaces:
1104 1104 # Programming error, not user error. We need a use case to
1105 1105 # define the right thing to do here.
1106 1106 raise ValueError(
1107 1107 "Feature %s does not handle all default interfaces" %
1108 1108 feature)
1109 1109
1110 1110 if self.plain():
1111 1111 return "text"
1112 1112
1113 1113 # Default interface for all the features
1114 1114 defaultinterface = "text"
1115 1115 i = self.config("ui", "interface")
1116 1116 if i in alldefaults:
1117 1117 defaultinterface = i
1118 1118
1119 1119 choseninterface = defaultinterface
1120 1120 f = self.config("ui", "interface.%s" % feature, None)
1121 1121 if f in availableinterfaces:
1122 1122 choseninterface = f
1123 1123
1124 1124 if i is not None and defaultinterface != i:
1125 1125 if f is not None:
1126 1126 self.warn(_("invalid value for ui.interface: %s\n") %
1127 1127 (i,))
1128 1128 else:
1129 1129 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1130 1130 (i, choseninterface))
1131 1131 if f is not None and choseninterface != f:
1132 1132 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1133 1133 (feature, f, choseninterface))
1134 1134
1135 1135 return choseninterface
1136 1136
1137 1137 def interactive(self):
1138 1138 '''is interactive input allowed?
1139 1139
1140 1140 An interactive session is a session where input can be reasonably read
1141 1141 from `sys.stdin'. If this function returns false, any attempt to read
1142 1142 from stdin should fail with an error, unless a sensible default has been
1143 1143 specified.
1144 1144
1145 1145 Interactiveness is triggered by the value of the `ui.interactive'
1146 1146 configuration variable or - if it is unset - when `sys.stdin' points
1147 1147 to a terminal device.
1148 1148
1149 1149 This function refers to input only; for output, see `ui.formatted()'.
1150 1150 '''
1151 1151 i = self.configbool("ui", "interactive")
1152 1152 if i is None:
1153 1153 # some environments replace stdin without implementing isatty
1154 1154 # usually those are non-interactive
1155 1155 return self._isatty(self.fin)
1156 1156
1157 1157 return i
1158 1158
1159 1159 def termwidth(self):
1160 1160 '''how wide is the terminal in columns?
1161 1161 '''
1162 1162 if 'COLUMNS' in encoding.environ:
1163 1163 try:
1164 1164 return int(encoding.environ['COLUMNS'])
1165 1165 except ValueError:
1166 1166 pass
1167 1167 return scmutil.termsize(self)[0]
1168 1168
1169 1169 def formatted(self):
1170 1170 '''should formatted output be used?
1171 1171
1172 1172 It is often desirable to format the output to suite the output medium.
1173 1173 Examples of this are truncating long lines or colorizing messages.
1174 1174 However, this is not often not desirable when piping output into other
1175 1175 utilities, e.g. `grep'.
1176 1176
1177 1177 Formatted output is triggered by the value of the `ui.formatted'
1178 1178 configuration variable or - if it is unset - when `sys.stdout' points
1179 1179 to a terminal device. Please note that `ui.formatted' should be
1180 1180 considered an implementation detail; it is not intended for use outside
1181 1181 Mercurial or its extensions.
1182 1182
1183 1183 This function refers to output only; for input, see `ui.interactive()'.
1184 1184 This function always returns false when in plain mode, see `ui.plain()'.
1185 1185 '''
1186 1186 if self.plain():
1187 1187 return False
1188 1188
1189 1189 i = self.configbool("ui", "formatted")
1190 1190 if i is None:
1191 1191 # some environments replace stdout without implementing isatty
1192 1192 # usually those are non-interactive
1193 1193 return self._isatty(self.fout)
1194 1194
1195 1195 return i
1196 1196
1197 1197 def _readline(self, prompt=''):
1198 1198 if self._isatty(self.fin):
1199 1199 try:
1200 1200 # magically add command line editing support, where
1201 1201 # available
1202 1202 import readline
1203 1203 # force demandimport to really load the module
1204 1204 readline.read_history_file
1205 1205 # windows sometimes raises something other than ImportError
1206 1206 except Exception:
1207 1207 pass
1208 1208
1209 1209 # call write() so output goes through subclassed implementation
1210 1210 # e.g. color extension on Windows
1211 1211 self.write(prompt, prompt=True)
1212 1212
1213 1213 # instead of trying to emulate raw_input, swap (self.fin,
1214 1214 # self.fout) with (sys.stdin, sys.stdout)
1215 1215 oldin = sys.stdin
1216 1216 oldout = sys.stdout
1217 1217 sys.stdin = self.fin
1218 1218 sys.stdout = self.fout
1219 1219 # prompt ' ' must exist; otherwise readline may delete entire line
1220 1220 # - http://bugs.python.org/issue12833
1221 1221 with self.timeblockedsection('stdio'):
1222 1222 line = raw_input(' ')
1223 1223 sys.stdin = oldin
1224 1224 sys.stdout = oldout
1225 1225
1226 1226 # When stdin is in binary mode on Windows, it can cause
1227 1227 # raw_input() to emit an extra trailing carriage return
1228 1228 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1229 1229 line = line[:-1]
1230 1230 return line
1231 1231
1232 1232 def prompt(self, msg, default="y"):
1233 1233 """Prompt user with msg, read response.
1234 1234 If ui is not interactive, the default is returned.
1235 1235 """
1236 1236 if not self.interactive():
1237 1237 self.write(msg, ' ', default or '', "\n")
1238 1238 return default
1239 1239 try:
1240 1240 r = self._readline(self.label(msg, 'ui.prompt'))
1241 1241 if not r:
1242 1242 r = default
1243 1243 if self.configbool('ui', 'promptecho'):
1244 1244 self.write(r, "\n")
1245 1245 return r
1246 1246 except EOFError:
1247 1247 raise error.ResponseExpected()
1248 1248
1249 1249 @staticmethod
1250 1250 def extractchoices(prompt):
1251 1251 """Extract prompt message and list of choices from specified prompt.
1252 1252
1253 1253 This returns tuple "(message, choices)", and "choices" is the
1254 1254 list of tuple "(response character, text without &)".
1255 1255
1256 1256 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1257 1257 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1258 1258 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1259 1259 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1260 1260 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1261 1261 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1262 1262 """
1263 1263
1264 1264 # Sadly, the prompt string may have been built with a filename
1265 1265 # containing "$$" so let's try to find the first valid-looking
1266 1266 # prompt to start parsing. Sadly, we also can't rely on
1267 1267 # choices containing spaces, ASCII, or basically anything
1268 1268 # except an ampersand followed by a character.
1269 1269 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1270 1270 msg = m.group(1)
1271 1271 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1272 return (msg,
1273 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1274 for s in choices])
1272 def choicetuple(s):
1273 ampidx = s.index('&')
1274 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1275 return (msg, [choicetuple(s) for s in choices])
1275 1276
1276 1277 def promptchoice(self, prompt, default=0):
1277 1278 """Prompt user with a message, read response, and ensure it matches
1278 1279 one of the provided choices. The prompt is formatted as follows:
1279 1280
1280 1281 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1281 1282
1282 1283 The index of the choice is returned. Responses are case
1283 1284 insensitive. If ui is not interactive, the default is
1284 1285 returned.
1285 1286 """
1286 1287
1287 1288 msg, choices = self.extractchoices(prompt)
1288 1289 resps = [r for r, t in choices]
1289 1290 while True:
1290 1291 r = self.prompt(msg, resps[default])
1291 1292 if r.lower() in resps:
1292 1293 return resps.index(r.lower())
1293 1294 self.write(_("unrecognized response\n"))
1294 1295
1295 1296 def getpass(self, prompt=None, default=None):
1296 1297 if not self.interactive():
1297 1298 return default
1298 1299 try:
1299 1300 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1300 1301 # disable getpass() only if explicitly specified. it's still valid
1301 1302 # to interact with tty even if fin is not a tty.
1302 1303 with self.timeblockedsection('stdio'):
1303 1304 if self.configbool('ui', 'nontty'):
1304 1305 l = self.fin.readline()
1305 1306 if not l:
1306 1307 raise EOFError
1307 1308 return l.rstrip('\n')
1308 1309 else:
1309 1310 return getpass.getpass('')
1310 1311 except EOFError:
1311 1312 raise error.ResponseExpected()
1312 1313 def status(self, *msg, **opts):
1313 1314 '''write status message to output (if ui.quiet is False)
1314 1315
1315 1316 This adds an output label of "ui.status".
1316 1317 '''
1317 1318 if not self.quiet:
1318 1319 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1319 1320 self.write(*msg, **opts)
1320 1321 def warn(self, *msg, **opts):
1321 1322 '''write warning message to output (stderr)
1322 1323
1323 1324 This adds an output label of "ui.warning".
1324 1325 '''
1325 1326 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1326 1327 self.write_err(*msg, **opts)
1327 1328 def note(self, *msg, **opts):
1328 1329 '''write note to output (if ui.verbose is True)
1329 1330
1330 1331 This adds an output label of "ui.note".
1331 1332 '''
1332 1333 if self.verbose:
1333 1334 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1334 1335 self.write(*msg, **opts)
1335 1336 def debug(self, *msg, **opts):
1336 1337 '''write debug message to output (if ui.debugflag is True)
1337 1338
1338 1339 This adds an output label of "ui.debug".
1339 1340 '''
1340 1341 if self.debugflag:
1341 1342 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1342 1343 self.write(*msg, **opts)
1343 1344
1344 1345 def edit(self, text, user, extra=None, editform=None, pending=None,
1345 1346 repopath=None):
1346 1347 extra_defaults = {
1347 1348 'prefix': 'editor',
1348 1349 'suffix': '.txt',
1349 1350 }
1350 1351 if extra is not None:
1351 1352 extra_defaults.update(extra)
1352 1353 extra = extra_defaults
1353 1354
1354 1355 rdir = None
1355 1356 if self.configbool('experimental', 'editortmpinhg'):
1356 1357 rdir = repopath
1357 1358 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1358 1359 suffix=extra['suffix'],
1359 1360 dir=rdir)
1360 1361 try:
1361 1362 f = os.fdopen(fd, r'wb')
1362 1363 f.write(util.tonativeeol(text))
1363 1364 f.close()
1364 1365
1365 1366 environ = {'HGUSER': user}
1366 1367 if 'transplant_source' in extra:
1367 1368 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1368 1369 for label in ('intermediate-source', 'source', 'rebase_source'):
1369 1370 if label in extra:
1370 1371 environ.update({'HGREVISION': extra[label]})
1371 1372 break
1372 1373 if editform:
1373 1374 environ.update({'HGEDITFORM': editform})
1374 1375 if pending:
1375 1376 environ.update({'HG_PENDING': pending})
1376 1377
1377 1378 editor = self.geteditor()
1378 1379
1379 1380 self.system("%s \"%s\"" % (editor, name),
1380 1381 environ=environ,
1381 1382 onerr=error.Abort, errprefix=_("edit failed"),
1382 1383 blockedtag='editor')
1383 1384
1384 1385 f = open(name, r'rb')
1385 1386 t = util.fromnativeeol(f.read())
1386 1387 f.close()
1387 1388 finally:
1388 1389 os.unlink(name)
1389 1390
1390 1391 return t
1391 1392
1392 1393 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1393 1394 blockedtag=None):
1394 1395 '''execute shell command with appropriate output stream. command
1395 1396 output will be redirected if fout is not stdout.
1396 1397
1397 1398 if command fails and onerr is None, return status, else raise onerr
1398 1399 object as exception.
1399 1400 '''
1400 1401 if blockedtag is None:
1401 1402 # Long cmds tend to be because of an absolute path on cmd. Keep
1402 1403 # the tail end instead
1403 1404 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1404 1405 blockedtag = 'unknown_system_' + cmdsuffix
1405 1406 out = self.fout
1406 1407 if any(s[1] for s in self._bufferstates):
1407 1408 out = self
1408 1409 with self.timeblockedsection(blockedtag):
1409 1410 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1410 1411 if rc and onerr:
1411 1412 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1412 1413 util.explainexit(rc)[0])
1413 1414 if errprefix:
1414 1415 errmsg = '%s: %s' % (errprefix, errmsg)
1415 1416 raise onerr(errmsg)
1416 1417 return rc
1417 1418
1418 1419 def _runsystem(self, cmd, environ, cwd, out):
1419 1420 """actually execute the given shell command (can be overridden by
1420 1421 extensions like chg)"""
1421 1422 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1422 1423
1423 1424 def traceback(self, exc=None, force=False):
1424 1425 '''print exception traceback if traceback printing enabled or forced.
1425 1426 only to call in exception handler. returns true if traceback
1426 1427 printed.'''
1427 1428 if self.tracebackflag or force:
1428 1429 if exc is None:
1429 1430 exc = sys.exc_info()
1430 1431 cause = getattr(exc[1], 'cause', None)
1431 1432
1432 1433 if cause is not None:
1433 1434 causetb = traceback.format_tb(cause[2])
1434 1435 exctb = traceback.format_tb(exc[2])
1435 1436 exconly = traceback.format_exception_only(cause[0], cause[1])
1436 1437
1437 1438 # exclude frame where 'exc' was chained and rethrown from exctb
1438 1439 self.write_err('Traceback (most recent call last):\n',
1439 1440 ''.join(exctb[:-1]),
1440 1441 ''.join(causetb),
1441 1442 ''.join(exconly))
1442 1443 else:
1443 1444 output = traceback.format_exception(exc[0], exc[1], exc[2])
1444 1445 data = r''.join(output)
1445 1446 if pycompat.ispy3:
1446 1447 enc = pycompat.sysstr(encoding.encoding)
1447 1448 data = data.encode(enc, errors=r'replace')
1448 1449 self.write_err(data)
1449 1450 return self.tracebackflag or force
1450 1451
1451 1452 def geteditor(self):
1452 1453 '''return editor to use'''
1453 1454 if pycompat.sysplatform == 'plan9':
1454 1455 # vi is the MIPS instruction simulator on Plan 9. We
1455 1456 # instead default to E to plumb commit messages to
1456 1457 # avoid confusion.
1457 1458 editor = 'E'
1458 1459 else:
1459 1460 editor = 'vi'
1460 1461 return (encoding.environ.get("HGEDITOR") or
1461 1462 self.config("ui", "editor", editor))
1462 1463
1463 1464 @util.propertycache
1464 1465 def _progbar(self):
1465 1466 """setup the progbar singleton to the ui object"""
1466 1467 if (self.quiet or self.debugflag
1467 1468 or self.configbool('progress', 'disable')
1468 1469 or not progress.shouldprint(self)):
1469 1470 return None
1470 1471 return getprogbar(self)
1471 1472
1472 1473 def _progclear(self):
1473 1474 """clear progress bar output if any. use it before any output"""
1474 1475 if '_progbar' not in vars(self): # nothing loaded yet
1475 1476 return
1476 1477 if self._progbar is not None and self._progbar.printed:
1477 1478 self._progbar.clear()
1478 1479
1479 1480 def progress(self, topic, pos, item="", unit="", total=None):
1480 1481 '''show a progress message
1481 1482
1482 1483 By default a textual progress bar will be displayed if an operation
1483 1484 takes too long. 'topic' is the current operation, 'item' is a
1484 1485 non-numeric marker of the current position (i.e. the currently
1485 1486 in-process file), 'pos' is the current numeric position (i.e.
1486 1487 revision, bytes, etc.), unit is a corresponding unit label,
1487 1488 and total is the highest expected pos.
1488 1489
1489 1490 Multiple nested topics may be active at a time.
1490 1491
1491 1492 All topics should be marked closed by setting pos to None at
1492 1493 termination.
1493 1494 '''
1494 1495 if self._progbar is not None:
1495 1496 self._progbar.progress(topic, pos, item=item, unit=unit,
1496 1497 total=total)
1497 1498 if pos is None or not self.configbool('progress', 'debug'):
1498 1499 return
1499 1500
1500 1501 if unit:
1501 1502 unit = ' ' + unit
1502 1503 if item:
1503 1504 item = ' ' + item
1504 1505
1505 1506 if total:
1506 1507 pct = 100.0 * pos / total
1507 1508 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1508 1509 % (topic, item, pos, total, unit, pct))
1509 1510 else:
1510 1511 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1511 1512
1512 1513 def log(self, service, *msg, **opts):
1513 1514 '''hook for logging facility extensions
1514 1515
1515 1516 service should be a readily-identifiable subsystem, which will
1516 1517 allow filtering.
1517 1518
1518 1519 *msg should be a newline-terminated format string to log, and
1519 1520 then any values to %-format into that format string.
1520 1521
1521 1522 **opts currently has no defined meanings.
1522 1523 '''
1523 1524
1524 1525 def label(self, msg, label):
1525 1526 '''style msg based on supplied label
1526 1527
1527 1528 If some color mode is enabled, this will add the necessary control
1528 1529 characters to apply such color. In addition, 'debug' color mode adds
1529 1530 markup showing which label affects a piece of text.
1530 1531
1531 1532 ui.write(s, 'label') is equivalent to
1532 1533 ui.write(ui.label(s, 'label')).
1533 1534 '''
1534 1535 if self._colormode is not None:
1535 1536 return color.colorlabel(self, msg, label)
1536 1537 return msg
1537 1538
1538 1539 def develwarn(self, msg, stacklevel=1, config=None):
1539 1540 """issue a developer warning message
1540 1541
1541 1542 Use 'stacklevel' to report the offender some layers further up in the
1542 1543 stack.
1543 1544 """
1544 1545 if not self.configbool('devel', 'all-warnings'):
1545 1546 if config is not None and not self.configbool('devel', config):
1546 1547 return
1547 1548 msg = 'devel-warn: ' + msg
1548 1549 stacklevel += 1 # get in develwarn
1549 1550 if self.tracebackflag:
1550 1551 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1551 1552 self.log('develwarn', '%s at:\n%s' %
1552 1553 (msg, ''.join(util.getstackframes(stacklevel))))
1553 1554 else:
1554 1555 curframe = inspect.currentframe()
1555 1556 calframe = inspect.getouterframes(curframe, 2)
1556 1557 self.write_err('%s at: %s:%s (%s)\n'
1557 1558 % ((msg,) + calframe[stacklevel][1:4]))
1558 1559 self.log('develwarn', '%s at: %s:%s (%s)\n',
1559 1560 msg, *calframe[stacklevel][1:4])
1560 1561 curframe = calframe = None # avoid cycles
1561 1562
1562 1563 def deprecwarn(self, msg, version):
1563 1564 """issue a deprecation warning
1564 1565
1565 1566 - msg: message explaining what is deprecated and how to upgrade,
1566 1567 - version: last version where the API will be supported,
1567 1568 """
1568 1569 if not (self.configbool('devel', 'all-warnings')
1569 1570 or self.configbool('devel', 'deprec-warn')):
1570 1571 return
1571 1572 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1572 1573 " update your code.)") % version
1573 1574 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1574 1575
1575 1576 def exportableenviron(self):
1576 1577 """The environment variables that are safe to export, e.g. through
1577 1578 hgweb.
1578 1579 """
1579 1580 return self._exportableenviron
1580 1581
1581 1582 @contextlib.contextmanager
1582 1583 def configoverride(self, overrides, source=""):
1583 1584 """Context manager for temporary config overrides
1584 1585 `overrides` must be a dict of the following structure:
1585 1586 {(section, name) : value}"""
1586 1587 backups = {}
1587 1588 try:
1588 1589 for (section, name), value in overrides.items():
1589 1590 backups[(section, name)] = self.backupconfig(section, name)
1590 1591 self.setconfig(section, name, value, source)
1591 1592 yield
1592 1593 finally:
1593 1594 for __, backup in backups.items():
1594 1595 self.restoreconfig(backup)
1595 1596 # just restoring ui.quiet config to the previous value is not enough
1596 1597 # as it does not update ui.quiet class member
1597 1598 if ('ui', 'quiet') in overrides:
1598 1599 self.fixconfig(section='ui')
1599 1600
1600 1601 class paths(dict):
1601 1602 """Represents a collection of paths and their configs.
1602 1603
1603 1604 Data is initially derived from ui instances and the config files they have
1604 1605 loaded.
1605 1606 """
1606 1607 def __init__(self, ui):
1607 1608 dict.__init__(self)
1608 1609
1609 1610 for name, loc in ui.configitems('paths', ignoresub=True):
1610 1611 # No location is the same as not existing.
1611 1612 if not loc:
1612 1613 continue
1613 1614 loc, sub = ui.configsuboptions('paths', name)
1614 1615 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1615 1616
1616 1617 def getpath(self, name, default=None):
1617 1618 """Return a ``path`` from a string, falling back to default.
1618 1619
1619 1620 ``name`` can be a named path or locations. Locations are filesystem
1620 1621 paths or URIs.
1621 1622
1622 1623 Returns None if ``name`` is not a registered path, a URI, or a local
1623 1624 path to a repo.
1624 1625 """
1625 1626 # Only fall back to default if no path was requested.
1626 1627 if name is None:
1627 1628 if not default:
1628 1629 default = ()
1629 1630 elif not isinstance(default, (tuple, list)):
1630 1631 default = (default,)
1631 1632 for k in default:
1632 1633 try:
1633 1634 return self[k]
1634 1635 except KeyError:
1635 1636 continue
1636 1637 return None
1637 1638
1638 1639 # Most likely empty string.
1639 1640 # This may need to raise in the future.
1640 1641 if not name:
1641 1642 return None
1642 1643
1643 1644 try:
1644 1645 return self[name]
1645 1646 except KeyError:
1646 1647 # Try to resolve as a local path or URI.
1647 1648 try:
1648 1649 # We don't pass sub-options in, so no need to pass ui instance.
1649 1650 return path(None, None, rawloc=name)
1650 1651 except ValueError:
1651 1652 raise error.RepoError(_('repository %s does not exist') %
1652 1653 name)
1653 1654
1654 1655 _pathsuboptions = {}
1655 1656
1656 1657 def pathsuboption(option, attr):
1657 1658 """Decorator used to declare a path sub-option.
1658 1659
1659 1660 Arguments are the sub-option name and the attribute it should set on
1660 1661 ``path`` instances.
1661 1662
1662 1663 The decorated function will receive as arguments a ``ui`` instance,
1663 1664 ``path`` instance, and the string value of this option from the config.
1664 1665 The function should return the value that will be set on the ``path``
1665 1666 instance.
1666 1667
1667 1668 This decorator can be used to perform additional verification of
1668 1669 sub-options and to change the type of sub-options.
1669 1670 """
1670 1671 def register(func):
1671 1672 _pathsuboptions[option] = (attr, func)
1672 1673 return func
1673 1674 return register
1674 1675
1675 1676 @pathsuboption('pushurl', 'pushloc')
1676 1677 def pushurlpathoption(ui, path, value):
1677 1678 u = util.url(value)
1678 1679 # Actually require a URL.
1679 1680 if not u.scheme:
1680 1681 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1681 1682 return None
1682 1683
1683 1684 # Don't support the #foo syntax in the push URL to declare branch to
1684 1685 # push.
1685 1686 if u.fragment:
1686 1687 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1687 1688 'ignoring)\n') % path.name)
1688 1689 u.fragment = None
1689 1690
1690 1691 return str(u)
1691 1692
1692 1693 @pathsuboption('pushrev', 'pushrev')
1693 1694 def pushrevpathoption(ui, path, value):
1694 1695 return value
1695 1696
1696 1697 class path(object):
1697 1698 """Represents an individual path and its configuration."""
1698 1699
1699 1700 def __init__(self, ui, name, rawloc=None, suboptions=None):
1700 1701 """Construct a path from its config options.
1701 1702
1702 1703 ``ui`` is the ``ui`` instance the path is coming from.
1703 1704 ``name`` is the symbolic name of the path.
1704 1705 ``rawloc`` is the raw location, as defined in the config.
1705 1706 ``pushloc`` is the raw locations pushes should be made to.
1706 1707
1707 1708 If ``name`` is not defined, we require that the location be a) a local
1708 1709 filesystem path with a .hg directory or b) a URL. If not,
1709 1710 ``ValueError`` is raised.
1710 1711 """
1711 1712 if not rawloc:
1712 1713 raise ValueError('rawloc must be defined')
1713 1714
1714 1715 # Locations may define branches via syntax <base>#<branch>.
1715 1716 u = util.url(rawloc)
1716 1717 branch = None
1717 1718 if u.fragment:
1718 1719 branch = u.fragment
1719 1720 u.fragment = None
1720 1721
1721 1722 self.url = u
1722 1723 self.branch = branch
1723 1724
1724 1725 self.name = name
1725 1726 self.rawloc = rawloc
1726 1727 self.loc = '%s' % u
1727 1728
1728 1729 # When given a raw location but not a symbolic name, validate the
1729 1730 # location is valid.
1730 1731 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1731 1732 raise ValueError('location is not a URL or path to a local '
1732 1733 'repo: %s' % rawloc)
1733 1734
1734 1735 suboptions = suboptions or {}
1735 1736
1736 1737 # Now process the sub-options. If a sub-option is registered, its
1737 1738 # attribute will always be present. The value will be None if there
1738 1739 # was no valid sub-option.
1739 1740 for suboption, (attr, func) in _pathsuboptions.iteritems():
1740 1741 if suboption not in suboptions:
1741 1742 setattr(self, attr, None)
1742 1743 continue
1743 1744
1744 1745 value = func(ui, self, suboptions[suboption])
1745 1746 setattr(self, attr, value)
1746 1747
1747 1748 def _isvalidlocalpath(self, path):
1748 1749 """Returns True if the given path is a potentially valid repository.
1749 1750 This is its own function so that extensions can change the definition of
1750 1751 'valid' in this case (like when pulling from a git repo into a hg
1751 1752 one)."""
1752 1753 return os.path.isdir(os.path.join(path, '.hg'))
1753 1754
1754 1755 @property
1755 1756 def suboptions(self):
1756 1757 """Return sub-options and their values for this path.
1757 1758
1758 1759 This is intended to be used for presentation purposes.
1759 1760 """
1760 1761 d = {}
1761 1762 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1762 1763 value = getattr(self, attr)
1763 1764 if value is not None:
1764 1765 d[subopt] = value
1765 1766 return d
1766 1767
1767 1768 # we instantiate one globally shared progress bar to avoid
1768 1769 # competing progress bars when multiple UI objects get created
1769 1770 _progresssingleton = None
1770 1771
1771 1772 def getprogbar(ui):
1772 1773 global _progresssingleton
1773 1774 if _progresssingleton is None:
1774 1775 # passing 'ui' object to the singleton is fishy,
1775 1776 # this is how the extension used to work but feel free to rework it.
1776 1777 _progresssingleton = progress.progbar(ui)
1777 1778 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now