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