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