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