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