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