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