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