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