##// END OF EJS Templates
ui: capture push location on path instances...
Gregory Szorc -
r26064:1b1ab6ff default
parent child Browse files
Show More
@@ -1,1074 +1,1090 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 p = self.paths.getpath(loc, default=default)
554 554 if p:
555 555 return p.rawloc
556 556 return loc
557 557
558 558 @util.propertycache
559 559 def paths(self):
560 560 return paths(self)
561 561
562 562 def pushbuffer(self, error=False, subproc=False):
563 563 """install a buffer to capture standard output of the ui object
564 564
565 565 If error is True, the error output will be captured too.
566 566
567 567 If subproc is True, output from subprocesses (typically hooks) will be
568 568 captured too."""
569 569 self._buffers.append([])
570 570 self._bufferstates.append((error, subproc))
571 571
572 572 def popbuffer(self, labeled=False):
573 573 '''pop the last buffer and return the buffered output
574 574
575 575 If labeled is True, any labels associated with buffered
576 576 output will be handled. By default, this has no effect
577 577 on the output returned, but extensions and GUI tools may
578 578 handle this argument and returned styled output. If output
579 579 is being buffered so it can be captured and parsed or
580 580 processed, labeled should not be set to True.
581 581 '''
582 582 self._bufferstates.pop()
583 583 return "".join(self._buffers.pop())
584 584
585 585 def write(self, *args, **opts):
586 586 '''write args to output
587 587
588 588 By default, this method simply writes to the buffer or stdout,
589 589 but extensions or GUI tools may override this method,
590 590 write_err(), popbuffer(), and label() to style output from
591 591 various parts of hg.
592 592
593 593 An optional keyword argument, "label", can be passed in.
594 594 This should be a string containing label names separated by
595 595 space. Label names take the form of "topic.type". For example,
596 596 ui.debug() issues a label of "ui.debug".
597 597
598 598 When labeling output for a specific command, a label of
599 599 "cmdname.type" is recommended. For example, status issues
600 600 a label of "status.modified" for modified files.
601 601 '''
602 602 self._progclear()
603 603 if self._buffers:
604 604 self._buffers[-1].extend([str(a) for a in args])
605 605 else:
606 606 for a in args:
607 607 self.fout.write(str(a))
608 608
609 609 def write_err(self, *args, **opts):
610 610 self._progclear()
611 611 try:
612 612 if self._bufferstates and self._bufferstates[-1][0]:
613 613 return self.write(*args, **opts)
614 614 if not getattr(self.fout, 'closed', False):
615 615 self.fout.flush()
616 616 for a in args:
617 617 self.ferr.write(str(a))
618 618 # stderr may be buffered under win32 when redirected to files,
619 619 # including stdout.
620 620 if not getattr(self.ferr, 'closed', False):
621 621 self.ferr.flush()
622 622 except IOError as inst:
623 623 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
624 624 raise
625 625
626 626 def flush(self):
627 627 try: self.fout.flush()
628 628 except (IOError, ValueError): pass
629 629 try: self.ferr.flush()
630 630 except (IOError, ValueError): pass
631 631
632 632 def _isatty(self, fh):
633 633 if self.configbool('ui', 'nontty', False):
634 634 return False
635 635 return util.isatty(fh)
636 636
637 637 def interactive(self):
638 638 '''is interactive input allowed?
639 639
640 640 An interactive session is a session where input can be reasonably read
641 641 from `sys.stdin'. If this function returns false, any attempt to read
642 642 from stdin should fail with an error, unless a sensible default has been
643 643 specified.
644 644
645 645 Interactiveness is triggered by the value of the `ui.interactive'
646 646 configuration variable or - if it is unset - when `sys.stdin' points
647 647 to a terminal device.
648 648
649 649 This function refers to input only; for output, see `ui.formatted()'.
650 650 '''
651 651 i = self.configbool("ui", "interactive", None)
652 652 if i is None:
653 653 # some environments replace stdin without implementing isatty
654 654 # usually those are non-interactive
655 655 return self._isatty(self.fin)
656 656
657 657 return i
658 658
659 659 def termwidth(self):
660 660 '''how wide is the terminal in columns?
661 661 '''
662 662 if 'COLUMNS' in os.environ:
663 663 try:
664 664 return int(os.environ['COLUMNS'])
665 665 except ValueError:
666 666 pass
667 667 return util.termwidth()
668 668
669 669 def formatted(self):
670 670 '''should formatted output be used?
671 671
672 672 It is often desirable to format the output to suite the output medium.
673 673 Examples of this are truncating long lines or colorizing messages.
674 674 However, this is not often not desirable when piping output into other
675 675 utilities, e.g. `grep'.
676 676
677 677 Formatted output is triggered by the value of the `ui.formatted'
678 678 configuration variable or - if it is unset - when `sys.stdout' points
679 679 to a terminal device. Please note that `ui.formatted' should be
680 680 considered an implementation detail; it is not intended for use outside
681 681 Mercurial or its extensions.
682 682
683 683 This function refers to output only; for input, see `ui.interactive()'.
684 684 This function always returns false when in plain mode, see `ui.plain()'.
685 685 '''
686 686 if self.plain():
687 687 return False
688 688
689 689 i = self.configbool("ui", "formatted", None)
690 690 if i is None:
691 691 # some environments replace stdout without implementing isatty
692 692 # usually those are non-interactive
693 693 return self._isatty(self.fout)
694 694
695 695 return i
696 696
697 697 def _readline(self, prompt=''):
698 698 if self._isatty(self.fin):
699 699 try:
700 700 # magically add command line editing support, where
701 701 # available
702 702 import readline
703 703 # force demandimport to really load the module
704 704 readline.read_history_file
705 705 # windows sometimes raises something other than ImportError
706 706 except Exception:
707 707 pass
708 708
709 709 # call write() so output goes through subclassed implementation
710 710 # e.g. color extension on Windows
711 711 self.write(prompt)
712 712
713 713 # instead of trying to emulate raw_input, swap (self.fin,
714 714 # self.fout) with (sys.stdin, sys.stdout)
715 715 oldin = sys.stdin
716 716 oldout = sys.stdout
717 717 sys.stdin = self.fin
718 718 sys.stdout = self.fout
719 719 # prompt ' ' must exist; otherwise readline may delete entire line
720 720 # - http://bugs.python.org/issue12833
721 721 line = raw_input(' ')
722 722 sys.stdin = oldin
723 723 sys.stdout = oldout
724 724
725 725 # When stdin is in binary mode on Windows, it can cause
726 726 # raw_input() to emit an extra trailing carriage return
727 727 if os.linesep == '\r\n' and line and line[-1] == '\r':
728 728 line = line[:-1]
729 729 return line
730 730
731 731 def prompt(self, msg, default="y"):
732 732 """Prompt user with msg, read response.
733 733 If ui is not interactive, the default is returned.
734 734 """
735 735 if not self.interactive():
736 736 self.write(msg, ' ', default, "\n")
737 737 return default
738 738 try:
739 739 r = self._readline(self.label(msg, 'ui.prompt'))
740 740 if not r:
741 741 r = default
742 742 if self.configbool('ui', 'promptecho'):
743 743 self.write(r, "\n")
744 744 return r
745 745 except EOFError:
746 746 raise util.Abort(_('response expected'))
747 747
748 748 @staticmethod
749 749 def extractchoices(prompt):
750 750 """Extract prompt message and list of choices from specified prompt.
751 751
752 752 This returns tuple "(message, choices)", and "choices" is the
753 753 list of tuple "(response character, text without &)".
754 754 """
755 755 parts = prompt.split('$$')
756 756 msg = parts[0].rstrip(' ')
757 757 choices = [p.strip(' ') for p in parts[1:]]
758 758 return (msg,
759 759 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
760 760 for s in choices])
761 761
762 762 def promptchoice(self, prompt, default=0):
763 763 """Prompt user with a message, read response, and ensure it matches
764 764 one of the provided choices. The prompt is formatted as follows:
765 765
766 766 "would you like fries with that (Yn)? $$ &Yes $$ &No"
767 767
768 768 The index of the choice is returned. Responses are case
769 769 insensitive. If ui is not interactive, the default is
770 770 returned.
771 771 """
772 772
773 773 msg, choices = self.extractchoices(prompt)
774 774 resps = [r for r, t in choices]
775 775 while True:
776 776 r = self.prompt(msg, resps[default])
777 777 if r.lower() in resps:
778 778 return resps.index(r.lower())
779 779 self.write(_("unrecognized response\n"))
780 780
781 781 def getpass(self, prompt=None, default=None):
782 782 if not self.interactive():
783 783 return default
784 784 try:
785 785 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
786 786 # disable getpass() only if explicitly specified. it's still valid
787 787 # to interact with tty even if fin is not a tty.
788 788 if self.configbool('ui', 'nontty'):
789 789 return self.fin.readline().rstrip('\n')
790 790 else:
791 791 return getpass.getpass('')
792 792 except EOFError:
793 793 raise util.Abort(_('response expected'))
794 794 def status(self, *msg, **opts):
795 795 '''write status message to output (if ui.quiet is False)
796 796
797 797 This adds an output label of "ui.status".
798 798 '''
799 799 if not self.quiet:
800 800 opts['label'] = opts.get('label', '') + ' ui.status'
801 801 self.write(*msg, **opts)
802 802 def warn(self, *msg, **opts):
803 803 '''write warning message to output (stderr)
804 804
805 805 This adds an output label of "ui.warning".
806 806 '''
807 807 opts['label'] = opts.get('label', '') + ' ui.warning'
808 808 self.write_err(*msg, **opts)
809 809 def note(self, *msg, **opts):
810 810 '''write note to output (if ui.verbose is True)
811 811
812 812 This adds an output label of "ui.note".
813 813 '''
814 814 if self.verbose:
815 815 opts['label'] = opts.get('label', '') + ' ui.note'
816 816 self.write(*msg, **opts)
817 817 def debug(self, *msg, **opts):
818 818 '''write debug message to output (if ui.debugflag is True)
819 819
820 820 This adds an output label of "ui.debug".
821 821 '''
822 822 if self.debugflag:
823 823 opts['label'] = opts.get('label', '') + ' ui.debug'
824 824 self.write(*msg, **opts)
825 825 def edit(self, text, user, extra={}, editform=None):
826 826 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
827 827 text=True)
828 828 try:
829 829 f = os.fdopen(fd, "w")
830 830 f.write(text)
831 831 f.close()
832 832
833 833 environ = {'HGUSER': user}
834 834 if 'transplant_source' in extra:
835 835 environ.update({'HGREVISION': hex(extra['transplant_source'])})
836 836 for label in ('intermediate-source', 'source', 'rebase_source'):
837 837 if label in extra:
838 838 environ.update({'HGREVISION': extra[label]})
839 839 break
840 840 if editform:
841 841 environ.update({'HGEDITFORM': editform})
842 842
843 843 editor = self.geteditor()
844 844
845 845 self.system("%s \"%s\"" % (editor, name),
846 846 environ=environ,
847 847 onerr=util.Abort, errprefix=_("edit failed"))
848 848
849 849 f = open(name)
850 850 t = f.read()
851 851 f.close()
852 852 finally:
853 853 os.unlink(name)
854 854
855 855 return t
856 856
857 857 def system(self, cmd, environ={}, cwd=None, onerr=None, errprefix=None):
858 858 '''execute shell command with appropriate output stream. command
859 859 output will be redirected if fout is not stdout.
860 860 '''
861 861 out = self.fout
862 862 if any(s[1] for s in self._bufferstates):
863 863 out = self
864 864 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
865 865 errprefix=errprefix, out=out)
866 866
867 867 def traceback(self, exc=None, force=False):
868 868 '''print exception traceback if traceback printing enabled or forced.
869 869 only to call in exception handler. returns true if traceback
870 870 printed.'''
871 871 if self.tracebackflag or force:
872 872 if exc is None:
873 873 exc = sys.exc_info()
874 874 cause = getattr(exc[1], 'cause', None)
875 875
876 876 if cause is not None:
877 877 causetb = traceback.format_tb(cause[2])
878 878 exctb = traceback.format_tb(exc[2])
879 879 exconly = traceback.format_exception_only(cause[0], cause[1])
880 880
881 881 # exclude frame where 'exc' was chained and rethrown from exctb
882 882 self.write_err('Traceback (most recent call last):\n',
883 883 ''.join(exctb[:-1]),
884 884 ''.join(causetb),
885 885 ''.join(exconly))
886 886 else:
887 887 output = traceback.format_exception(exc[0], exc[1], exc[2])
888 888 self.write_err(''.join(output))
889 889 return self.tracebackflag or force
890 890
891 891 def geteditor(self):
892 892 '''return editor to use'''
893 893 if sys.platform == 'plan9':
894 894 # vi is the MIPS instruction simulator on Plan 9. We
895 895 # instead default to E to plumb commit messages to
896 896 # avoid confusion.
897 897 editor = 'E'
898 898 else:
899 899 editor = 'vi'
900 900 return (os.environ.get("HGEDITOR") or
901 901 self.config("ui", "editor") or
902 902 os.environ.get("VISUAL") or
903 903 os.environ.get("EDITOR", editor))
904 904
905 905 @util.propertycache
906 906 def _progbar(self):
907 907 """setup the progbar singleton to the ui object"""
908 908 if (self.quiet or self.debugflag
909 909 or self.configbool('progress', 'disable', False)
910 910 or not progress.shouldprint(self)):
911 911 return None
912 912 return getprogbar(self)
913 913
914 914 def _progclear(self):
915 915 """clear progress bar output if any. use it before any output"""
916 916 if '_progbar' not in vars(self): # nothing loadef yet
917 917 return
918 918 if self._progbar is not None and self._progbar.printed:
919 919 self._progbar.clear()
920 920
921 921 def progress(self, topic, pos, item="", unit="", total=None):
922 922 '''show a progress message
923 923
924 924 With stock hg, this is simply a debug message that is hidden
925 925 by default, but with extensions or GUI tools it may be
926 926 visible. 'topic' is the current operation, 'item' is a
927 927 non-numeric marker of the current position (i.e. the currently
928 928 in-process file), 'pos' is the current numeric position (i.e.
929 929 revision, bytes, etc.), unit is a corresponding unit label,
930 930 and total is the highest expected pos.
931 931
932 932 Multiple nested topics may be active at a time.
933 933
934 934 All topics should be marked closed by setting pos to None at
935 935 termination.
936 936 '''
937 937 if self._progbar is not None:
938 938 self._progbar.progress(topic, pos, item=item, unit=unit,
939 939 total=total)
940 940 if pos is None or not self.configbool('progress', 'debug'):
941 941 return
942 942
943 943 if unit:
944 944 unit = ' ' + unit
945 945 if item:
946 946 item = ' ' + item
947 947
948 948 if total:
949 949 pct = 100.0 * pos / total
950 950 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
951 951 % (topic, item, pos, total, unit, pct))
952 952 else:
953 953 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
954 954
955 955 def log(self, service, *msg, **opts):
956 956 '''hook for logging facility extensions
957 957
958 958 service should be a readily-identifiable subsystem, which will
959 959 allow filtering.
960 960 message should be a newline-terminated string to log.
961 961 '''
962 962 pass
963 963
964 964 def label(self, msg, label):
965 965 '''style msg based on supplied label
966 966
967 967 Like ui.write(), this just returns msg unchanged, but extensions
968 968 and GUI tools can override it to allow styling output without
969 969 writing it.
970 970
971 971 ui.write(s, 'label') is equivalent to
972 972 ui.write(ui.label(s, 'label')).
973 973 '''
974 974 return msg
975 975
976 976 def develwarn(self, msg):
977 977 """issue a developer warning message"""
978 978 msg = 'devel-warn: ' + msg
979 979 if self.tracebackflag:
980 980 util.debugstacktrace(msg, 2)
981 981 else:
982 982 curframe = inspect.currentframe()
983 983 calframe = inspect.getouterframes(curframe, 2)
984 984 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
985 985
986 986 class paths(dict):
987 987 """Represents a collection of paths and their configs.
988 988
989 989 Data is initially derived from ui instances and the config files they have
990 990 loaded.
991 991 """
992 992 def __init__(self, ui):
993 993 dict.__init__(self)
994 994
995 995 for name, loc in ui.configitems('paths'):
996 996 # No location is the same as not existing.
997 997 if not loc:
998 998 continue
999
1000 # TODO ignore default-push once all consumers stop referencing it
1001 # since it is handled specifically below.
1002
999 1003 self[name] = path(name, rawloc=loc)
1000 1004
1005 # Handle default-push, which is a one-off that defines the push URL for
1006 # the "default" path.
1007 defaultpush = ui.config('paths', 'default-push')
1008 if defaultpush and 'default' in self:
1009 self['default']._pushloc = defaultpush
1010
1001 1011 def getpath(self, name, default=None):
1002 1012 """Return a ``path`` from a string, falling back to a default.
1003 1013
1004 1014 ``name`` can be a named path or locations. Locations are filesystem
1005 1015 paths or URIs.
1006 1016
1007 1017 Returns the first of ``name`` or ``default`` that is present, or None
1008 1018 if neither is present.
1009 1019 """
1010 1020 try:
1011 1021 return self[name]
1012 1022 except KeyError:
1013 1023 # Try to resolve as a local path or URI.
1014 1024 try:
1015 1025 return path(None, rawloc=name)
1016 1026 except ValueError:
1017 1027 pass
1018 1028
1019 1029 if default is not None:
1020 1030 try:
1021 1031 return self[default]
1022 1032 except KeyError:
1023 1033 pass
1024 1034
1025 1035 return None
1026 1036
1027 1037 class path(object):
1028 1038 """Represents an individual path and its configuration."""
1029 1039
1030 def __init__(self, name, rawloc=None):
1040 def __init__(self, name, rawloc=None, pushloc=None):
1031 1041 """Construct a path from its config options.
1032 1042
1033 1043 ``name`` is the symbolic name of the path.
1034 1044 ``rawloc`` is the raw location, as defined in the config.
1045 ``pushloc`` is the raw locations pushes should be made to.
1035 1046
1036 1047 If ``name`` is not defined, we require that the location be a) a local
1037 1048 filesystem path with a .hg directory or b) a URL. If not,
1038 1049 ``ValueError`` is raised.
1039 1050 """
1040 1051 if not rawloc:
1041 1052 raise ValueError('rawloc must be defined')
1042 1053
1043 1054 # Locations may define branches via syntax <base>#<branch>.
1044 1055 u = util.url(rawloc)
1045 1056 branch = None
1046 1057 if u.fragment:
1047 1058 branch = u.fragment
1048 1059 u.fragment = None
1049 1060
1050 1061 self.url = u
1051 1062 self.branch = branch
1052 1063
1053 1064 self.name = name
1054 1065 self.rawloc = rawloc
1055 1066 self.loc = str(u)
1067 self._pushloc = pushloc
1056 1068
1057 1069 # When given a raw location but not a symbolic name, validate the
1058 1070 # location is valid.
1059 1071 if (not name and not u.scheme
1060 1072 and not os.path.isdir(os.path.join(str(u), '.hg'))):
1061 1073 raise ValueError('location is not a URL or path to a local '
1062 1074 'repo: %s' % rawloc)
1063 1075
1076 @property
1077 def pushloc(self):
1078 return self._pushloc or self.loc
1079
1064 1080 # we instantiate one globally shared progress bar to avoid
1065 1081 # competing progress bars when multiple UI objects get created
1066 1082 _progresssingleton = None
1067 1083
1068 1084 def getprogbar(ui):
1069 1085 global _progresssingleton
1070 1086 if _progresssingleton is None:
1071 1087 # passing 'ui' object to the singleton is fishy,
1072 1088 # this is how the extension used to work but feel free to rework it.
1073 1089 _progresssingleton = progress.progbar(ui)
1074 1090 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now