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