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