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