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