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