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