##// END OF EJS Templates
ui: optionally ignore sub-options from configitems()...
Gregory Szorc -
r27253:f43988e5 default
parent child Browse files
Show More
@@ -1,1175 +1,1181 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 errno
11 11 import getpass
12 12 import inspect
13 13 import os
14 14 import socket
15 15 import sys
16 16 import tempfile
17 17 import traceback
18 18
19 19 from .i18n import _
20 20 from .node import hex
21 21
22 22 from . import (
23 23 config,
24 24 error,
25 25 formatter,
26 26 progress,
27 27 scmutil,
28 28 util,
29 29 )
30 30
31 31 samplehgrcs = {
32 32 'user':
33 33 """# example user config (see "hg help config" for more info)
34 34 [ui]
35 35 # name and email, e.g.
36 36 # username = Jane Doe <jdoe@example.com>
37 37 username =
38 38
39 39 [extensions]
40 40 # uncomment these lines to enable some popular extensions
41 41 # (see "hg help extensions" for more info)
42 42 #
43 43 # pager =
44 44 # progress =
45 45 # color =""",
46 46
47 47 'cloned':
48 48 """# example repository config (see "hg help config" for more info)
49 49 [paths]
50 50 default = %s
51 51
52 52 # path aliases to other clones of this repo in URLs or filesystem paths
53 53 # (see "hg help config.paths" for more info)
54 54 #
55 55 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
56 56 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
57 57 # my-clone = /home/jdoe/jdoes-clone
58 58
59 59 [ui]
60 60 # name and email (local to this repository, optional), e.g.
61 61 # username = Jane Doe <jdoe@example.com>
62 62 """,
63 63
64 64 'local':
65 65 """# example repository config (see "hg help config" for more info)
66 66 [paths]
67 67 # path aliases to other clones of this repo in URLs or filesystem paths
68 68 # (see "hg help config.paths" for more info)
69 69 #
70 70 # default = http://example.com/hg/example-repo
71 71 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
72 72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 73 # my-clone = /home/jdoe/jdoes-clone
74 74
75 75 [ui]
76 76 # name and email (local to this repository, optional), e.g.
77 77 # username = Jane Doe <jdoe@example.com>
78 78 """,
79 79
80 80 'global':
81 81 """# example system-wide hg config (see "hg help config" for more info)
82 82
83 83 [extensions]
84 84 # uncomment these lines to enable some popular extensions
85 85 # (see "hg help extensions" for more info)
86 86 #
87 87 # blackbox =
88 88 # progress =
89 89 # color =
90 90 # pager =""",
91 91 }
92 92
93 93 class ui(object):
94 94 def __init__(self, src=None):
95 95 # _buffers: used for temporary capture of output
96 96 self._buffers = []
97 97 # 3-tuple describing how each buffer in the stack behaves.
98 98 # Values are (capture stderr, capture subprocesses, apply labels).
99 99 self._bufferstates = []
100 100 # When a buffer is active, defines whether we are expanding labels.
101 101 # This exists to prevent an extra list lookup.
102 102 self._bufferapplylabels = None
103 103 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
104 104 self._reportuntrusted = True
105 105 self._ocfg = config.config() # overlay
106 106 self._tcfg = config.config() # trusted
107 107 self._ucfg = config.config() # untrusted
108 108 self._trustusers = set()
109 109 self._trustgroups = set()
110 110 self.callhooks = True
111 111
112 112 if src:
113 113 self.fout = src.fout
114 114 self.ferr = src.ferr
115 115 self.fin = src.fin
116 116
117 117 self._tcfg = src._tcfg.copy()
118 118 self._ucfg = src._ucfg.copy()
119 119 self._ocfg = src._ocfg.copy()
120 120 self._trustusers = src._trustusers.copy()
121 121 self._trustgroups = src._trustgroups.copy()
122 122 self.environ = src.environ
123 123 self.callhooks = src.callhooks
124 124 self.fixconfig()
125 125 else:
126 126 self.fout = sys.stdout
127 127 self.ferr = sys.stderr
128 128 self.fin = sys.stdin
129 129
130 130 # shared read-only environment
131 131 self.environ = os.environ
132 132 # we always trust global config files
133 133 for f in scmutil.rcpath():
134 134 self.readconfig(f, trust=True)
135 135
136 136 def copy(self):
137 137 return self.__class__(self)
138 138
139 139 def formatter(self, topic, opts):
140 140 return formatter.formatter(self, topic, opts)
141 141
142 142 def _trusted(self, fp, f):
143 143 st = util.fstat(fp)
144 144 if util.isowner(st):
145 145 return True
146 146
147 147 tusers, tgroups = self._trustusers, self._trustgroups
148 148 if '*' in tusers or '*' in tgroups:
149 149 return True
150 150
151 151 user = util.username(st.st_uid)
152 152 group = util.groupname(st.st_gid)
153 153 if user in tusers or group in tgroups or user == util.username():
154 154 return True
155 155
156 156 if self._reportuntrusted:
157 157 self.warn(_('not trusting file %s from untrusted '
158 158 'user %s, group %s\n') % (f, user, group))
159 159 return False
160 160
161 161 def readconfig(self, filename, root=None, trust=False,
162 162 sections=None, remap=None):
163 163 try:
164 164 fp = open(filename)
165 165 except IOError:
166 166 if not sections: # ignore unless we were looking for something
167 167 return
168 168 raise
169 169
170 170 cfg = config.config()
171 171 trusted = sections or trust or self._trusted(fp, filename)
172 172
173 173 try:
174 174 cfg.read(filename, fp, sections=sections, remap=remap)
175 175 fp.close()
176 176 except error.ConfigError as inst:
177 177 if trusted:
178 178 raise
179 179 self.warn(_("ignored: %s\n") % str(inst))
180 180
181 181 if self.plain():
182 182 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
183 183 'logtemplate', 'statuscopies', 'style',
184 184 'traceback', 'verbose'):
185 185 if k in cfg['ui']:
186 186 del cfg['ui'][k]
187 187 for k, v in cfg.items('defaults'):
188 188 del cfg['defaults'][k]
189 189 # Don't remove aliases from the configuration if in the exceptionlist
190 190 if self.plain('alias'):
191 191 for k, v in cfg.items('alias'):
192 192 del cfg['alias'][k]
193 193 if self.plain('revsetalias'):
194 194 for k, v in cfg.items('revsetalias'):
195 195 del cfg['revsetalias'][k]
196 196
197 197 if trusted:
198 198 self._tcfg.update(cfg)
199 199 self._tcfg.update(self._ocfg)
200 200 self._ucfg.update(cfg)
201 201 self._ucfg.update(self._ocfg)
202 202
203 203 if root is None:
204 204 root = os.path.expanduser('~')
205 205 self.fixconfig(root=root)
206 206
207 207 def fixconfig(self, root=None, section=None):
208 208 if section in (None, 'paths'):
209 209 # expand vars and ~
210 210 # translate paths relative to root (or home) into absolute paths
211 211 root = root or os.getcwd()
212 212 for c in self._tcfg, self._ucfg, self._ocfg:
213 213 for n, p in c.items('paths'):
214 214 if not p:
215 215 continue
216 216 if '%%' in p:
217 217 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
218 218 % (n, p, self.configsource('paths', n)))
219 219 p = p.replace('%%', '%')
220 220 p = util.expandpath(p)
221 221 if not util.hasscheme(p) and not os.path.isabs(p):
222 222 p = os.path.normpath(os.path.join(root, p))
223 223 c.set("paths", n, p)
224 224
225 225 if section in (None, 'ui'):
226 226 # update ui options
227 227 self.debugflag = self.configbool('ui', 'debug')
228 228 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
229 229 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
230 230 if self.verbose and self.quiet:
231 231 self.quiet = self.verbose = False
232 232 self._reportuntrusted = self.debugflag or self.configbool("ui",
233 233 "report_untrusted", True)
234 234 self.tracebackflag = self.configbool('ui', 'traceback', False)
235 235
236 236 if section in (None, 'trusted'):
237 237 # update trust information
238 238 self._trustusers.update(self.configlist('trusted', 'users'))
239 239 self._trustgroups.update(self.configlist('trusted', 'groups'))
240 240
241 241 def backupconfig(self, section, item):
242 242 return (self._ocfg.backup(section, item),
243 243 self._tcfg.backup(section, item),
244 244 self._ucfg.backup(section, item),)
245 245 def restoreconfig(self, data):
246 246 self._ocfg.restore(data[0])
247 247 self._tcfg.restore(data[1])
248 248 self._ucfg.restore(data[2])
249 249
250 250 def setconfig(self, section, name, value, source=''):
251 251 for cfg in (self._ocfg, self._tcfg, self._ucfg):
252 252 cfg.set(section, name, value, source)
253 253 self.fixconfig(section=section)
254 254
255 255 def _data(self, untrusted):
256 256 return untrusted and self._ucfg or self._tcfg
257 257
258 258 def configsource(self, section, name, untrusted=False):
259 259 return self._data(untrusted).source(section, name) or 'none'
260 260
261 261 def config(self, section, name, default=None, untrusted=False):
262 262 if isinstance(name, list):
263 263 alternates = name
264 264 else:
265 265 alternates = [name]
266 266
267 267 for n in alternates:
268 268 value = self._data(untrusted).get(section, n, None)
269 269 if value is not None:
270 270 name = n
271 271 break
272 272 else:
273 273 value = default
274 274
275 275 if self.debugflag and not untrusted and self._reportuntrusted:
276 276 for n in alternates:
277 277 uvalue = self._ucfg.get(section, n)
278 278 if uvalue is not None and uvalue != value:
279 279 self.debug("ignoring untrusted configuration option "
280 280 "%s.%s = %s\n" % (section, n, uvalue))
281 281 return value
282 282
283 283 def configsuboptions(self, section, name, default=None, untrusted=False):
284 284 """Get a config option and all sub-options.
285 285
286 286 Some config options have sub-options that are declared with the
287 287 format "key:opt = value". This method is used to return the main
288 288 option and all its declared sub-options.
289 289
290 290 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
291 291 is a dict of defined sub-options where keys and values are strings.
292 292 """
293 293 data = self._data(untrusted)
294 294 main = data.get(section, name, default)
295 295 if self.debugflag and not untrusted and self._reportuntrusted:
296 296 uvalue = self._ucfg.get(section, name)
297 297 if uvalue is not None and uvalue != main:
298 298 self.debug('ignoring untrusted configuration option '
299 299 '%s.%s = %s\n' % (section, name, uvalue))
300 300
301 301 sub = {}
302 302 prefix = '%s:' % name
303 303 for k, v in data.items(section):
304 304 if k.startswith(prefix):
305 305 sub[k[len(prefix):]] = v
306 306
307 307 if self.debugflag and not untrusted and self._reportuntrusted:
308 308 for k, v in sub.items():
309 309 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
310 310 if uvalue is not None and uvalue != v:
311 311 self.debug('ignoring untrusted configuration option '
312 312 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
313 313
314 314 return main, sub
315 315
316 316 def configpath(self, section, name, default=None, untrusted=False):
317 317 'get a path config item, expanded relative to repo root or config file'
318 318 v = self.config(section, name, default, untrusted)
319 319 if v is None:
320 320 return None
321 321 if not os.path.isabs(v) or "://" not in v:
322 322 src = self.configsource(section, name, untrusted)
323 323 if ':' in src:
324 324 base = os.path.dirname(src.rsplit(':')[0])
325 325 v = os.path.join(base, os.path.expanduser(v))
326 326 return v
327 327
328 328 def configbool(self, section, name, default=False, untrusted=False):
329 329 """parse a configuration element as a boolean
330 330
331 331 >>> u = ui(); s = 'foo'
332 332 >>> u.setconfig(s, 'true', 'yes')
333 333 >>> u.configbool(s, 'true')
334 334 True
335 335 >>> u.setconfig(s, 'false', 'no')
336 336 >>> u.configbool(s, 'false')
337 337 False
338 338 >>> u.configbool(s, 'unknown')
339 339 False
340 340 >>> u.configbool(s, 'unknown', True)
341 341 True
342 342 >>> u.setconfig(s, 'invalid', 'somevalue')
343 343 >>> u.configbool(s, 'invalid')
344 344 Traceback (most recent call last):
345 345 ...
346 346 ConfigError: foo.invalid is not a boolean ('somevalue')
347 347 """
348 348
349 349 v = self.config(section, name, None, untrusted)
350 350 if v is None:
351 351 return default
352 352 if isinstance(v, bool):
353 353 return v
354 354 b = util.parsebool(v)
355 355 if b is None:
356 356 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
357 357 % (section, name, v))
358 358 return b
359 359
360 360 def configint(self, section, name, default=None, untrusted=False):
361 361 """parse a configuration element as an integer
362 362
363 363 >>> u = ui(); s = 'foo'
364 364 >>> u.setconfig(s, 'int1', '42')
365 365 >>> u.configint(s, 'int1')
366 366 42
367 367 >>> u.setconfig(s, 'int2', '-42')
368 368 >>> u.configint(s, 'int2')
369 369 -42
370 370 >>> u.configint(s, 'unknown', 7)
371 371 7
372 372 >>> u.setconfig(s, 'invalid', 'somevalue')
373 373 >>> u.configint(s, 'invalid')
374 374 Traceback (most recent call last):
375 375 ...
376 376 ConfigError: foo.invalid is not an integer ('somevalue')
377 377 """
378 378
379 379 v = self.config(section, name, None, untrusted)
380 380 if v is None:
381 381 return default
382 382 try:
383 383 return int(v)
384 384 except ValueError:
385 385 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
386 386 % (section, name, v))
387 387
388 388 def configbytes(self, section, name, default=0, untrusted=False):
389 389 """parse a configuration element as a quantity in bytes
390 390
391 391 Units can be specified as b (bytes), k or kb (kilobytes), m or
392 392 mb (megabytes), g or gb (gigabytes).
393 393
394 394 >>> u = ui(); s = 'foo'
395 395 >>> u.setconfig(s, 'val1', '42')
396 396 >>> u.configbytes(s, 'val1')
397 397 42
398 398 >>> u.setconfig(s, 'val2', '42.5 kb')
399 399 >>> u.configbytes(s, 'val2')
400 400 43520
401 401 >>> u.configbytes(s, 'unknown', '7 MB')
402 402 7340032
403 403 >>> u.setconfig(s, 'invalid', 'somevalue')
404 404 >>> u.configbytes(s, 'invalid')
405 405 Traceback (most recent call last):
406 406 ...
407 407 ConfigError: foo.invalid is not a byte quantity ('somevalue')
408 408 """
409 409
410 410 value = self.config(section, name)
411 411 if value is None:
412 412 if not isinstance(default, str):
413 413 return default
414 414 value = default
415 415 try:
416 416 return util.sizetoint(value)
417 417 except error.ParseError:
418 418 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
419 419 % (section, name, value))
420 420
421 421 def configlist(self, section, name, default=None, untrusted=False):
422 422 """parse a configuration element as a list of comma/space separated
423 423 strings
424 424
425 425 >>> u = ui(); s = 'foo'
426 426 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
427 427 >>> u.configlist(s, 'list1')
428 428 ['this', 'is', 'a small', 'test']
429 429 """
430 430
431 431 def _parse_plain(parts, s, offset):
432 432 whitespace = False
433 433 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
434 434 whitespace = True
435 435 offset += 1
436 436 if offset >= len(s):
437 437 return None, parts, offset
438 438 if whitespace:
439 439 parts.append('')
440 440 if s[offset] == '"' and not parts[-1]:
441 441 return _parse_quote, parts, offset + 1
442 442 elif s[offset] == '"' and parts[-1][-1] == '\\':
443 443 parts[-1] = parts[-1][:-1] + s[offset]
444 444 return _parse_plain, parts, offset + 1
445 445 parts[-1] += s[offset]
446 446 return _parse_plain, parts, offset + 1
447 447
448 448 def _parse_quote(parts, s, offset):
449 449 if offset < len(s) and s[offset] == '"': # ""
450 450 parts.append('')
451 451 offset += 1
452 452 while offset < len(s) and (s[offset].isspace() or
453 453 s[offset] == ','):
454 454 offset += 1
455 455 return _parse_plain, parts, offset
456 456
457 457 while offset < len(s) and s[offset] != '"':
458 458 if (s[offset] == '\\' and offset + 1 < len(s)
459 459 and s[offset + 1] == '"'):
460 460 offset += 1
461 461 parts[-1] += '"'
462 462 else:
463 463 parts[-1] += s[offset]
464 464 offset += 1
465 465
466 466 if offset >= len(s):
467 467 real_parts = _configlist(parts[-1])
468 468 if not real_parts:
469 469 parts[-1] = '"'
470 470 else:
471 471 real_parts[0] = '"' + real_parts[0]
472 472 parts = parts[:-1]
473 473 parts.extend(real_parts)
474 474 return None, parts, offset
475 475
476 476 offset += 1
477 477 while offset < len(s) and s[offset] in [' ', ',']:
478 478 offset += 1
479 479
480 480 if offset < len(s):
481 481 if offset + 1 == len(s) and s[offset] == '"':
482 482 parts[-1] += '"'
483 483 offset += 1
484 484 else:
485 485 parts.append('')
486 486 else:
487 487 return None, parts, offset
488 488
489 489 return _parse_plain, parts, offset
490 490
491 491 def _configlist(s):
492 492 s = s.rstrip(' ,')
493 493 if not s:
494 494 return []
495 495 parser, parts, offset = _parse_plain, [''], 0
496 496 while parser:
497 497 parser, parts, offset = parser(parts, s, offset)
498 498 return parts
499 499
500 500 result = self.config(section, name, untrusted=untrusted)
501 501 if result is None:
502 502 result = default or []
503 503 if isinstance(result, basestring):
504 504 result = _configlist(result.lstrip(' ,\n'))
505 505 if result is None:
506 506 result = default or []
507 507 return result
508 508
509 509 def has_section(self, section, untrusted=False):
510 510 '''tell whether section exists in config.'''
511 511 return section in self._data(untrusted)
512 512
513 def configitems(self, section, untrusted=False):
513 def configitems(self, section, untrusted=False, ignoresub=False):
514 514 items = self._data(untrusted).items(section)
515 if ignoresub:
516 newitems = {}
517 for k, v in items:
518 if ':' not in k:
519 newitems[k] = v
520 items = newitems.items()
515 521 if self.debugflag and not untrusted and self._reportuntrusted:
516 522 for k, v in self._ucfg.items(section):
517 523 if self._tcfg.get(section, k) != v:
518 524 self.debug("ignoring untrusted configuration option "
519 525 "%s.%s = %s\n" % (section, k, v))
520 526 return items
521 527
522 528 def walkconfig(self, untrusted=False):
523 529 cfg = self._data(untrusted)
524 530 for section in cfg.sections():
525 531 for name, value in self.configitems(section, untrusted):
526 532 yield section, name, value
527 533
528 534 def plain(self, feature=None):
529 535 '''is plain mode active?
530 536
531 537 Plain mode means that all configuration variables which affect
532 538 the behavior and output of Mercurial should be
533 539 ignored. Additionally, the output should be stable,
534 540 reproducible and suitable for use in scripts or applications.
535 541
536 542 The only way to trigger plain mode is by setting either the
537 543 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
538 544
539 545 The return value can either be
540 546 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
541 547 - True otherwise
542 548 '''
543 549 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
544 550 return False
545 551 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
546 552 if feature and exceptions:
547 553 return feature not in exceptions
548 554 return True
549 555
550 556 def username(self):
551 557 """Return default username to be used in commits.
552 558
553 559 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
554 560 and stop searching if one of these is set.
555 561 If not found and ui.askusername is True, ask the user, else use
556 562 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
557 563 """
558 564 user = os.environ.get("HGUSER")
559 565 if user is None:
560 566 user = self.config("ui", ["username", "user"])
561 567 if user is not None:
562 568 user = os.path.expandvars(user)
563 569 if user is None:
564 570 user = os.environ.get("EMAIL")
565 571 if user is None and self.configbool("ui", "askusername"):
566 572 user = self.prompt(_("enter a commit username:"), default=None)
567 573 if user is None and not self.interactive():
568 574 try:
569 575 user = '%s@%s' % (util.getuser(), socket.getfqdn())
570 576 self.warn(_("no username found, using '%s' instead\n") % user)
571 577 except KeyError:
572 578 pass
573 579 if not user:
574 580 raise error.Abort(_('no username supplied'),
575 581 hint=_('use "hg config --edit" '
576 582 'to set your username'))
577 583 if "\n" in user:
578 584 raise error.Abort(_("username %s contains a newline\n")
579 585 % repr(user))
580 586 return user
581 587
582 588 def shortuser(self, user):
583 589 """Return a short representation of a user name or email address."""
584 590 if not self.verbose:
585 591 user = util.shortuser(user)
586 592 return user
587 593
588 594 def expandpath(self, loc, default=None):
589 595 """Return repository location relative to cwd or from [paths]"""
590 596 try:
591 597 p = self.paths.getpath(loc)
592 598 if p:
593 599 return p.rawloc
594 600 except error.RepoError:
595 601 pass
596 602
597 603 if default:
598 604 try:
599 605 p = self.paths.getpath(default)
600 606 if p:
601 607 return p.rawloc
602 608 except error.RepoError:
603 609 pass
604 610
605 611 return loc
606 612
607 613 @util.propertycache
608 614 def paths(self):
609 615 return paths(self)
610 616
611 617 def pushbuffer(self, error=False, subproc=False, labeled=False):
612 618 """install a buffer to capture standard output of the ui object
613 619
614 620 If error is True, the error output will be captured too.
615 621
616 622 If subproc is True, output from subprocesses (typically hooks) will be
617 623 captured too.
618 624
619 625 If labeled is True, any labels associated with buffered
620 626 output will be handled. By default, this has no effect
621 627 on the output returned, but extensions and GUI tools may
622 628 handle this argument and returned styled output. If output
623 629 is being buffered so it can be captured and parsed or
624 630 processed, labeled should not be set to True.
625 631 """
626 632 self._buffers.append([])
627 633 self._bufferstates.append((error, subproc, labeled))
628 634 self._bufferapplylabels = labeled
629 635
630 636 def popbuffer(self):
631 637 '''pop the last buffer and return the buffered output'''
632 638 self._bufferstates.pop()
633 639 if self._bufferstates:
634 640 self._bufferapplylabels = self._bufferstates[-1][2]
635 641 else:
636 642 self._bufferapplylabels = None
637 643
638 644 return "".join(self._buffers.pop())
639 645
640 646 def write(self, *args, **opts):
641 647 '''write args to output
642 648
643 649 By default, this method simply writes to the buffer or stdout,
644 650 but extensions or GUI tools may override this method,
645 651 write_err(), popbuffer(), and label() to style output from
646 652 various parts of hg.
647 653
648 654 An optional keyword argument, "label", can be passed in.
649 655 This should be a string containing label names separated by
650 656 space. Label names take the form of "topic.type". For example,
651 657 ui.debug() issues a label of "ui.debug".
652 658
653 659 When labeling output for a specific command, a label of
654 660 "cmdname.type" is recommended. For example, status issues
655 661 a label of "status.modified" for modified files.
656 662 '''
657 663 if self._buffers:
658 664 self._buffers[-1].extend(a for a in args)
659 665 else:
660 666 self._progclear()
661 667 for a in args:
662 668 self.fout.write(a)
663 669
664 670 def write_err(self, *args, **opts):
665 671 self._progclear()
666 672 try:
667 673 if self._bufferstates and self._bufferstates[-1][0]:
668 674 return self.write(*args, **opts)
669 675 if not getattr(self.fout, 'closed', False):
670 676 self.fout.flush()
671 677 for a in args:
672 678 self.ferr.write(a)
673 679 # stderr may be buffered under win32 when redirected to files,
674 680 # including stdout.
675 681 if not getattr(self.ferr, 'closed', False):
676 682 self.ferr.flush()
677 683 except IOError as inst:
678 684 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
679 685 raise
680 686
681 687 def flush(self):
682 688 try: self.fout.flush()
683 689 except (IOError, ValueError): pass
684 690 try: self.ferr.flush()
685 691 except (IOError, ValueError): pass
686 692
687 693 def _isatty(self, fh):
688 694 if self.configbool('ui', 'nontty', False):
689 695 return False
690 696 return util.isatty(fh)
691 697
692 698 def interactive(self):
693 699 '''is interactive input allowed?
694 700
695 701 An interactive session is a session where input can be reasonably read
696 702 from `sys.stdin'. If this function returns false, any attempt to read
697 703 from stdin should fail with an error, unless a sensible default has been
698 704 specified.
699 705
700 706 Interactiveness is triggered by the value of the `ui.interactive'
701 707 configuration variable or - if it is unset - when `sys.stdin' points
702 708 to a terminal device.
703 709
704 710 This function refers to input only; for output, see `ui.formatted()'.
705 711 '''
706 712 i = self.configbool("ui", "interactive", None)
707 713 if i is None:
708 714 # some environments replace stdin without implementing isatty
709 715 # usually those are non-interactive
710 716 return self._isatty(self.fin)
711 717
712 718 return i
713 719
714 720 def termwidth(self):
715 721 '''how wide is the terminal in columns?
716 722 '''
717 723 if 'COLUMNS' in os.environ:
718 724 try:
719 725 return int(os.environ['COLUMNS'])
720 726 except ValueError:
721 727 pass
722 728 return util.termwidth()
723 729
724 730 def formatted(self):
725 731 '''should formatted output be used?
726 732
727 733 It is often desirable to format the output to suite the output medium.
728 734 Examples of this are truncating long lines or colorizing messages.
729 735 However, this is not often not desirable when piping output into other
730 736 utilities, e.g. `grep'.
731 737
732 738 Formatted output is triggered by the value of the `ui.formatted'
733 739 configuration variable or - if it is unset - when `sys.stdout' points
734 740 to a terminal device. Please note that `ui.formatted' should be
735 741 considered an implementation detail; it is not intended for use outside
736 742 Mercurial or its extensions.
737 743
738 744 This function refers to output only; for input, see `ui.interactive()'.
739 745 This function always returns false when in plain mode, see `ui.plain()'.
740 746 '''
741 747 if self.plain():
742 748 return False
743 749
744 750 i = self.configbool("ui", "formatted", None)
745 751 if i is None:
746 752 # some environments replace stdout without implementing isatty
747 753 # usually those are non-interactive
748 754 return self._isatty(self.fout)
749 755
750 756 return i
751 757
752 758 def _readline(self, prompt=''):
753 759 if self._isatty(self.fin):
754 760 try:
755 761 # magically add command line editing support, where
756 762 # available
757 763 import readline
758 764 # force demandimport to really load the module
759 765 readline.read_history_file
760 766 # windows sometimes raises something other than ImportError
761 767 except Exception:
762 768 pass
763 769
764 770 # call write() so output goes through subclassed implementation
765 771 # e.g. color extension on Windows
766 772 self.write(prompt)
767 773
768 774 # instead of trying to emulate raw_input, swap (self.fin,
769 775 # self.fout) with (sys.stdin, sys.stdout)
770 776 oldin = sys.stdin
771 777 oldout = sys.stdout
772 778 sys.stdin = self.fin
773 779 sys.stdout = self.fout
774 780 # prompt ' ' must exist; otherwise readline may delete entire line
775 781 # - http://bugs.python.org/issue12833
776 782 line = raw_input(' ')
777 783 sys.stdin = oldin
778 784 sys.stdout = oldout
779 785
780 786 # When stdin is in binary mode on Windows, it can cause
781 787 # raw_input() to emit an extra trailing carriage return
782 788 if os.linesep == '\r\n' and line and line[-1] == '\r':
783 789 line = line[:-1]
784 790 return line
785 791
786 792 def prompt(self, msg, default="y"):
787 793 """Prompt user with msg, read response.
788 794 If ui is not interactive, the default is returned.
789 795 """
790 796 if not self.interactive():
791 797 self.write(msg, ' ', default, "\n")
792 798 return default
793 799 try:
794 800 r = self._readline(self.label(msg, 'ui.prompt'))
795 801 if not r:
796 802 r = default
797 803 if self.configbool('ui', 'promptecho'):
798 804 self.write(r, "\n")
799 805 return r
800 806 except EOFError:
801 807 raise error.ResponseExpected()
802 808
803 809 @staticmethod
804 810 def extractchoices(prompt):
805 811 """Extract prompt message and list of choices from specified prompt.
806 812
807 813 This returns tuple "(message, choices)", and "choices" is the
808 814 list of tuple "(response character, text without &)".
809 815 """
810 816 parts = prompt.split('$$')
811 817 msg = parts[0].rstrip(' ')
812 818 choices = [p.strip(' ') for p in parts[1:]]
813 819 return (msg,
814 820 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
815 821 for s in choices])
816 822
817 823 def promptchoice(self, prompt, default=0):
818 824 """Prompt user with a message, read response, and ensure it matches
819 825 one of the provided choices. The prompt is formatted as follows:
820 826
821 827 "would you like fries with that (Yn)? $$ &Yes $$ &No"
822 828
823 829 The index of the choice is returned. Responses are case
824 830 insensitive. If ui is not interactive, the default is
825 831 returned.
826 832 """
827 833
828 834 msg, choices = self.extractchoices(prompt)
829 835 resps = [r for r, t in choices]
830 836 while True:
831 837 r = self.prompt(msg, resps[default])
832 838 if r.lower() in resps:
833 839 return resps.index(r.lower())
834 840 self.write(_("unrecognized response\n"))
835 841
836 842 def getpass(self, prompt=None, default=None):
837 843 if not self.interactive():
838 844 return default
839 845 try:
840 846 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
841 847 # disable getpass() only if explicitly specified. it's still valid
842 848 # to interact with tty even if fin is not a tty.
843 849 if self.configbool('ui', 'nontty'):
844 850 return self.fin.readline().rstrip('\n')
845 851 else:
846 852 return getpass.getpass('')
847 853 except EOFError:
848 854 raise error.ResponseExpected()
849 855 def status(self, *msg, **opts):
850 856 '''write status message to output (if ui.quiet is False)
851 857
852 858 This adds an output label of "ui.status".
853 859 '''
854 860 if not self.quiet:
855 861 opts['label'] = opts.get('label', '') + ' ui.status'
856 862 self.write(*msg, **opts)
857 863 def warn(self, *msg, **opts):
858 864 '''write warning message to output (stderr)
859 865
860 866 This adds an output label of "ui.warning".
861 867 '''
862 868 opts['label'] = opts.get('label', '') + ' ui.warning'
863 869 self.write_err(*msg, **opts)
864 870 def note(self, *msg, **opts):
865 871 '''write note to output (if ui.verbose is True)
866 872
867 873 This adds an output label of "ui.note".
868 874 '''
869 875 if self.verbose:
870 876 opts['label'] = opts.get('label', '') + ' ui.note'
871 877 self.write(*msg, **opts)
872 878 def debug(self, *msg, **opts):
873 879 '''write debug message to output (if ui.debugflag is True)
874 880
875 881 This adds an output label of "ui.debug".
876 882 '''
877 883 if self.debugflag:
878 884 opts['label'] = opts.get('label', '') + ' ui.debug'
879 885 self.write(*msg, **opts)
880 886
881 887 def edit(self, text, user, extra=None, editform=None, pending=None):
882 888 extra_defaults = { 'prefix': 'editor' }
883 889 if extra is not None:
884 890 extra_defaults.update(extra)
885 891 extra = extra_defaults
886 892 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
887 893 suffix=".txt", text=True)
888 894 try:
889 895 f = os.fdopen(fd, "w")
890 896 f.write(text)
891 897 f.close()
892 898
893 899 environ = {'HGUSER': user}
894 900 if 'transplant_source' in extra:
895 901 environ.update({'HGREVISION': hex(extra['transplant_source'])})
896 902 for label in ('intermediate-source', 'source', 'rebase_source'):
897 903 if label in extra:
898 904 environ.update({'HGREVISION': extra[label]})
899 905 break
900 906 if editform:
901 907 environ.update({'HGEDITFORM': editform})
902 908 if pending:
903 909 environ.update({'HG_PENDING': pending})
904 910
905 911 editor = self.geteditor()
906 912
907 913 self.system("%s \"%s\"" % (editor, name),
908 914 environ=environ,
909 915 onerr=error.Abort, errprefix=_("edit failed"))
910 916
911 917 f = open(name)
912 918 t = f.read()
913 919 f.close()
914 920 finally:
915 921 os.unlink(name)
916 922
917 923 return t
918 924
919 925 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
920 926 '''execute shell command with appropriate output stream. command
921 927 output will be redirected if fout is not stdout.
922 928 '''
923 929 out = self.fout
924 930 if any(s[1] for s in self._bufferstates):
925 931 out = self
926 932 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
927 933 errprefix=errprefix, out=out)
928 934
929 935 def traceback(self, exc=None, force=False):
930 936 '''print exception traceback if traceback printing enabled or forced.
931 937 only to call in exception handler. returns true if traceback
932 938 printed.'''
933 939 if self.tracebackflag or force:
934 940 if exc is None:
935 941 exc = sys.exc_info()
936 942 cause = getattr(exc[1], 'cause', None)
937 943
938 944 if cause is not None:
939 945 causetb = traceback.format_tb(cause[2])
940 946 exctb = traceback.format_tb(exc[2])
941 947 exconly = traceback.format_exception_only(cause[0], cause[1])
942 948
943 949 # exclude frame where 'exc' was chained and rethrown from exctb
944 950 self.write_err('Traceback (most recent call last):\n',
945 951 ''.join(exctb[:-1]),
946 952 ''.join(causetb),
947 953 ''.join(exconly))
948 954 else:
949 955 output = traceback.format_exception(exc[0], exc[1], exc[2])
950 956 self.write_err(''.join(output))
951 957 return self.tracebackflag or force
952 958
953 959 def geteditor(self):
954 960 '''return editor to use'''
955 961 if sys.platform == 'plan9':
956 962 # vi is the MIPS instruction simulator on Plan 9. We
957 963 # instead default to E to plumb commit messages to
958 964 # avoid confusion.
959 965 editor = 'E'
960 966 else:
961 967 editor = 'vi'
962 968 return (os.environ.get("HGEDITOR") or
963 969 self.config("ui", "editor") or
964 970 os.environ.get("VISUAL") or
965 971 os.environ.get("EDITOR", editor))
966 972
967 973 @util.propertycache
968 974 def _progbar(self):
969 975 """setup the progbar singleton to the ui object"""
970 976 if (self.quiet or self.debugflag
971 977 or self.configbool('progress', 'disable', False)
972 978 or not progress.shouldprint(self)):
973 979 return None
974 980 return getprogbar(self)
975 981
976 982 def _progclear(self):
977 983 """clear progress bar output if any. use it before any output"""
978 984 if '_progbar' not in vars(self): # nothing loaded yet
979 985 return
980 986 if self._progbar is not None and self._progbar.printed:
981 987 self._progbar.clear()
982 988
983 989 def progress(self, topic, pos, item="", unit="", total=None):
984 990 '''show a progress message
985 991
986 992 With stock hg, this is simply a debug message that is hidden
987 993 by default, but with extensions or GUI tools it may be
988 994 visible. 'topic' is the current operation, 'item' is a
989 995 non-numeric marker of the current position (i.e. the currently
990 996 in-process file), 'pos' is the current numeric position (i.e.
991 997 revision, bytes, etc.), unit is a corresponding unit label,
992 998 and total is the highest expected pos.
993 999
994 1000 Multiple nested topics may be active at a time.
995 1001
996 1002 All topics should be marked closed by setting pos to None at
997 1003 termination.
998 1004 '''
999 1005 if self._progbar is not None:
1000 1006 self._progbar.progress(topic, pos, item=item, unit=unit,
1001 1007 total=total)
1002 1008 if pos is None or not self.configbool('progress', 'debug'):
1003 1009 return
1004 1010
1005 1011 if unit:
1006 1012 unit = ' ' + unit
1007 1013 if item:
1008 1014 item = ' ' + item
1009 1015
1010 1016 if total:
1011 1017 pct = 100.0 * pos / total
1012 1018 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1013 1019 % (topic, item, pos, total, unit, pct))
1014 1020 else:
1015 1021 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1016 1022
1017 1023 def log(self, service, *msg, **opts):
1018 1024 '''hook for logging facility extensions
1019 1025
1020 1026 service should be a readily-identifiable subsystem, which will
1021 1027 allow filtering.
1022 1028
1023 1029 *msg should be a newline-terminated format string to log, and
1024 1030 then any values to %-format into that format string.
1025 1031
1026 1032 **opts currently has no defined meanings.
1027 1033 '''
1028 1034
1029 1035 def label(self, msg, label):
1030 1036 '''style msg based on supplied label
1031 1037
1032 1038 Like ui.write(), this just returns msg unchanged, but extensions
1033 1039 and GUI tools can override it to allow styling output without
1034 1040 writing it.
1035 1041
1036 1042 ui.write(s, 'label') is equivalent to
1037 1043 ui.write(ui.label(s, 'label')).
1038 1044 '''
1039 1045 return msg
1040 1046
1041 1047 def develwarn(self, msg):
1042 1048 """issue a developer warning message"""
1043 1049 msg = 'devel-warn: ' + msg
1044 1050 if self.tracebackflag:
1045 1051 util.debugstacktrace(msg, 2, self.ferr, self.fout)
1046 1052 else:
1047 1053 curframe = inspect.currentframe()
1048 1054 calframe = inspect.getouterframes(curframe, 2)
1049 1055 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
1050 1056
1051 1057 class paths(dict):
1052 1058 """Represents a collection of paths and their configs.
1053 1059
1054 1060 Data is initially derived from ui instances and the config files they have
1055 1061 loaded.
1056 1062 """
1057 1063 def __init__(self, ui):
1058 1064 dict.__init__(self)
1059 1065
1060 1066 for name, loc in ui.configitems('paths'):
1061 1067 # No location is the same as not existing.
1062 1068 if not loc:
1063 1069 continue
1064 1070
1065 1071 # TODO ignore default-push once all consumers stop referencing it
1066 1072 # since it is handled specifically below.
1067 1073
1068 1074 self[name] = path(name, rawloc=loc)
1069 1075
1070 1076 # Handle default-push, which is a one-off that defines the push URL for
1071 1077 # the "default" path.
1072 1078 defaultpush = ui.config('paths', 'default-push')
1073 1079 if defaultpush:
1074 1080 # "default-push" can be defined without "default" entry. This is a
1075 1081 # bit weird, but is allowed for backwards compatibility.
1076 1082 if 'default' not in self:
1077 1083 self['default'] = path('default', rawloc=defaultpush)
1078 1084 self['default']._pushloc = defaultpush
1079 1085
1080 1086 def getpath(self, name, default=None):
1081 1087 """Return a ``path`` from a string, falling back to a default.
1082 1088
1083 1089 ``name`` can be a named path or locations. Locations are filesystem
1084 1090 paths or URIs.
1085 1091
1086 1092 Returns None if ``name`` is not a registered path, a URI, or a local
1087 1093 path to a repo.
1088 1094 """
1089 1095 # Only fall back to default if no path was requested.
1090 1096 if name is None:
1091 1097 if default:
1092 1098 try:
1093 1099 return self[default]
1094 1100 except KeyError:
1095 1101 return None
1096 1102 else:
1097 1103 return None
1098 1104
1099 1105 # Most likely empty string.
1100 1106 # This may need to raise in the future.
1101 1107 if not name:
1102 1108 return None
1103 1109
1104 1110 try:
1105 1111 return self[name]
1106 1112 except KeyError:
1107 1113 # Try to resolve as a local path or URI.
1108 1114 try:
1109 1115 return path(None, rawloc=name)
1110 1116 except ValueError:
1111 1117 raise error.RepoError(_('repository %s does not exist') %
1112 1118 name)
1113 1119
1114 1120 assert False
1115 1121
1116 1122 class path(object):
1117 1123 """Represents an individual path and its configuration."""
1118 1124
1119 1125 def __init__(self, name, rawloc=None, pushloc=None):
1120 1126 """Construct a path from its config options.
1121 1127
1122 1128 ``name`` is the symbolic name of the path.
1123 1129 ``rawloc`` is the raw location, as defined in the config.
1124 1130 ``pushloc`` is the raw locations pushes should be made to.
1125 1131
1126 1132 If ``name`` is not defined, we require that the location be a) a local
1127 1133 filesystem path with a .hg directory or b) a URL. If not,
1128 1134 ``ValueError`` is raised.
1129 1135 """
1130 1136 if not rawloc:
1131 1137 raise ValueError('rawloc must be defined')
1132 1138
1133 1139 # Locations may define branches via syntax <base>#<branch>.
1134 1140 u = util.url(rawloc)
1135 1141 branch = None
1136 1142 if u.fragment:
1137 1143 branch = u.fragment
1138 1144 u.fragment = None
1139 1145
1140 1146 self.url = u
1141 1147 self.branch = branch
1142 1148
1143 1149 self.name = name
1144 1150 self.rawloc = rawloc
1145 1151 self.loc = str(u)
1146 1152 self._pushloc = pushloc
1147 1153
1148 1154 # When given a raw location but not a symbolic name, validate the
1149 1155 # location is valid.
1150 1156 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1151 1157 raise ValueError('location is not a URL or path to a local '
1152 1158 'repo: %s' % rawloc)
1153 1159
1154 1160 def _isvalidlocalpath(self, path):
1155 1161 """Returns True if the given path is a potentially valid repository.
1156 1162 This is its own function so that extensions can change the definition of
1157 1163 'valid' in this case (like when pulling from a git repo into a hg
1158 1164 one)."""
1159 1165 return os.path.isdir(os.path.join(path, '.hg'))
1160 1166
1161 1167 @property
1162 1168 def pushloc(self):
1163 1169 return self._pushloc or self.loc
1164 1170
1165 1171 # we instantiate one globally shared progress bar to avoid
1166 1172 # competing progress bars when multiple UI objects get created
1167 1173 _progresssingleton = None
1168 1174
1169 1175 def getprogbar(ui):
1170 1176 global _progresssingleton
1171 1177 if _progresssingleton is None:
1172 1178 # passing 'ui' object to the singleton is fishy,
1173 1179 # this is how the extension used to work but feel free to rework it.
1174 1180 _progresssingleton = progress.progbar(ui)
1175 1181 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now