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