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