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