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