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