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