##// END OF EJS Templates
config: use the new '_unset' value for 'configbytes'...
marmoute -
r32962:0bf986cf default
parent child Browse files
Show More
@@ -1,1745 +1,1747 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import traceback
22 import traceback
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import hex
25 from .node import hex
26
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 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=_unset, 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=_unset, 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, default, untrusted)
622 if value is None:
622 if value is None:
623 if not isinstance(default, str):
623 if default is _unset:
624 return default
624 default = 0
625 value = default
625 value = default
626 if not isinstance(value, str):
627 return value
626 try:
628 try:
627 return util.sizetoint(value)
629 return util.sizetoint(value)
628 except error.ParseError:
630 except error.ParseError:
629 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
631 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
630 % (section, name, value))
632 % (section, name, value))
631
633
632 def configlist(self, section, name, default=None, untrusted=False):
634 def configlist(self, section, name, default=None, untrusted=False):
633 """parse a configuration element as a list of comma/space separated
635 """parse a configuration element as a list of comma/space separated
634 strings
636 strings
635
637
636 >>> u = ui(); s = 'foo'
638 >>> u = ui(); s = 'foo'
637 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
639 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
638 >>> u.configlist(s, 'list1')
640 >>> u.configlist(s, 'list1')
639 ['this', 'is', 'a small', 'test']
641 ['this', 'is', 'a small', 'test']
640 """
642 """
641 # default is not always a list
643 # default is not always a list
642 if isinstance(default, bytes):
644 if isinstance(default, bytes):
643 default = config.parselist(default)
645 default = config.parselist(default)
644 return self.configwith(config.parselist, section, name, default or [],
646 return self.configwith(config.parselist, section, name, default or [],
645 'list', untrusted)
647 'list', untrusted)
646
648
647 def configdate(self, section, name, default=None, untrusted=False):
649 def configdate(self, section, name, default=None, untrusted=False):
648 """parse a configuration element as a tuple of ints
650 """parse a configuration element as a tuple of ints
649
651
650 >>> u = ui(); s = 'foo'
652 >>> u = ui(); s = 'foo'
651 >>> u.setconfig(s, 'date', '0 0')
653 >>> u.setconfig(s, 'date', '0 0')
652 >>> u.configdate(s, 'date')
654 >>> u.configdate(s, 'date')
653 (0, 0)
655 (0, 0)
654 """
656 """
655 if self.config(section, name, default, untrusted):
657 if self.config(section, name, default, untrusted):
656 return self.configwith(util.parsedate, section, name, default,
658 return self.configwith(util.parsedate, section, name, default,
657 'date', untrusted)
659 'date', untrusted)
658 return default
660 return default
659
661
660 def hasconfig(self, section, name, untrusted=False):
662 def hasconfig(self, section, name, untrusted=False):
661 return self._data(untrusted).hasitem(section, name)
663 return self._data(untrusted).hasitem(section, name)
662
664
663 def has_section(self, section, untrusted=False):
665 def has_section(self, section, untrusted=False):
664 '''tell whether section exists in config.'''
666 '''tell whether section exists in config.'''
665 return section in self._data(untrusted)
667 return section in self._data(untrusted)
666
668
667 def configitems(self, section, untrusted=False, ignoresub=False):
669 def configitems(self, section, untrusted=False, ignoresub=False):
668 items = self._data(untrusted).items(section)
670 items = self._data(untrusted).items(section)
669 if ignoresub:
671 if ignoresub:
670 newitems = {}
672 newitems = {}
671 for k, v in items:
673 for k, v in items:
672 if ':' not in k:
674 if ':' not in k:
673 newitems[k] = v
675 newitems[k] = v
674 items = newitems.items()
676 items = newitems.items()
675 if self.debugflag and not untrusted and self._reportuntrusted:
677 if self.debugflag and not untrusted and self._reportuntrusted:
676 for k, v in self._ucfg.items(section):
678 for k, v in self._ucfg.items(section):
677 if self._tcfg.get(section, k) != v:
679 if self._tcfg.get(section, k) != v:
678 self.debug("ignoring untrusted configuration option "
680 self.debug("ignoring untrusted configuration option "
679 "%s.%s = %s\n" % (section, k, v))
681 "%s.%s = %s\n" % (section, k, v))
680 return items
682 return items
681
683
682 def walkconfig(self, untrusted=False):
684 def walkconfig(self, untrusted=False):
683 cfg = self._data(untrusted)
685 cfg = self._data(untrusted)
684 for section in cfg.sections():
686 for section in cfg.sections():
685 for name, value in self.configitems(section, untrusted):
687 for name, value in self.configitems(section, untrusted):
686 yield section, name, value
688 yield section, name, value
687
689
688 def plain(self, feature=None):
690 def plain(self, feature=None):
689 '''is plain mode active?
691 '''is plain mode active?
690
692
691 Plain mode means that all configuration variables which affect
693 Plain mode means that all configuration variables which affect
692 the behavior and output of Mercurial should be
694 the behavior and output of Mercurial should be
693 ignored. Additionally, the output should be stable,
695 ignored. Additionally, the output should be stable,
694 reproducible and suitable for use in scripts or applications.
696 reproducible and suitable for use in scripts or applications.
695
697
696 The only way to trigger plain mode is by setting either the
698 The only way to trigger plain mode is by setting either the
697 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
699 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
698
700
699 The return value can either be
701 The return value can either be
700 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
702 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
701 - True otherwise
703 - True otherwise
702 '''
704 '''
703 if ('HGPLAIN' not in encoding.environ and
705 if ('HGPLAIN' not in encoding.environ and
704 'HGPLAINEXCEPT' not in encoding.environ):
706 'HGPLAINEXCEPT' not in encoding.environ):
705 return False
707 return False
706 exceptions = encoding.environ.get('HGPLAINEXCEPT',
708 exceptions = encoding.environ.get('HGPLAINEXCEPT',
707 '').strip().split(',')
709 '').strip().split(',')
708 if feature and exceptions:
710 if feature and exceptions:
709 return feature not in exceptions
711 return feature not in exceptions
710 return True
712 return True
711
713
712 def username(self):
714 def username(self):
713 """Return default username to be used in commits.
715 """Return default username to be used in commits.
714
716
715 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
717 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
716 and stop searching if one of these is set.
718 and stop searching if one of these is set.
717 If not found and ui.askusername is True, ask the user, else use
719 If not found and ui.askusername is True, ask the user, else use
718 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
720 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
719 """
721 """
720 user = encoding.environ.get("HGUSER")
722 user = encoding.environ.get("HGUSER")
721 if user is None:
723 if user is None:
722 user = self.config("ui", ["username", "user"])
724 user = self.config("ui", ["username", "user"])
723 if user is not None:
725 if user is not None:
724 user = os.path.expandvars(user)
726 user = os.path.expandvars(user)
725 if user is None:
727 if user is None:
726 user = encoding.environ.get("EMAIL")
728 user = encoding.environ.get("EMAIL")
727 if user is None and self.configbool("ui", "askusername"):
729 if user is None and self.configbool("ui", "askusername"):
728 user = self.prompt(_("enter a commit username:"), default=None)
730 user = self.prompt(_("enter a commit username:"), default=None)
729 if user is None and not self.interactive():
731 if user is None and not self.interactive():
730 try:
732 try:
731 user = '%s@%s' % (util.getuser(), socket.getfqdn())
733 user = '%s@%s' % (util.getuser(), socket.getfqdn())
732 self.warn(_("no username found, using '%s' instead\n") % user)
734 self.warn(_("no username found, using '%s' instead\n") % user)
733 except KeyError:
735 except KeyError:
734 pass
736 pass
735 if not user:
737 if not user:
736 raise error.Abort(_('no username supplied'),
738 raise error.Abort(_('no username supplied'),
737 hint=_("use 'hg config --edit' "
739 hint=_("use 'hg config --edit' "
738 'to set your username'))
740 'to set your username'))
739 if "\n" in user:
741 if "\n" in user:
740 raise error.Abort(_("username %s contains a newline\n")
742 raise error.Abort(_("username %s contains a newline\n")
741 % repr(user))
743 % repr(user))
742 return user
744 return user
743
745
744 def shortuser(self, user):
746 def shortuser(self, user):
745 """Return a short representation of a user name or email address."""
747 """Return a short representation of a user name or email address."""
746 if not self.verbose:
748 if not self.verbose:
747 user = util.shortuser(user)
749 user = util.shortuser(user)
748 return user
750 return user
749
751
750 def expandpath(self, loc, default=None):
752 def expandpath(self, loc, default=None):
751 """Return repository location relative to cwd or from [paths]"""
753 """Return repository location relative to cwd or from [paths]"""
752 try:
754 try:
753 p = self.paths.getpath(loc)
755 p = self.paths.getpath(loc)
754 if p:
756 if p:
755 return p.rawloc
757 return p.rawloc
756 except error.RepoError:
758 except error.RepoError:
757 pass
759 pass
758
760
759 if default:
761 if default:
760 try:
762 try:
761 p = self.paths.getpath(default)
763 p = self.paths.getpath(default)
762 if p:
764 if p:
763 return p.rawloc
765 return p.rawloc
764 except error.RepoError:
766 except error.RepoError:
765 pass
767 pass
766
768
767 return loc
769 return loc
768
770
769 @util.propertycache
771 @util.propertycache
770 def paths(self):
772 def paths(self):
771 return paths(self)
773 return paths(self)
772
774
773 def pushbuffer(self, error=False, subproc=False, labeled=False):
775 def pushbuffer(self, error=False, subproc=False, labeled=False):
774 """install a buffer to capture standard output of the ui object
776 """install a buffer to capture standard output of the ui object
775
777
776 If error is True, the error output will be captured too.
778 If error is True, the error output will be captured too.
777
779
778 If subproc is True, output from subprocesses (typically hooks) will be
780 If subproc is True, output from subprocesses (typically hooks) will be
779 captured too.
781 captured too.
780
782
781 If labeled is True, any labels associated with buffered
783 If labeled is True, any labels associated with buffered
782 output will be handled. By default, this has no effect
784 output will be handled. By default, this has no effect
783 on the output returned, but extensions and GUI tools may
785 on the output returned, but extensions and GUI tools may
784 handle this argument and returned styled output. If output
786 handle this argument and returned styled output. If output
785 is being buffered so it can be captured and parsed or
787 is being buffered so it can be captured and parsed or
786 processed, labeled should not be set to True.
788 processed, labeled should not be set to True.
787 """
789 """
788 self._buffers.append([])
790 self._buffers.append([])
789 self._bufferstates.append((error, subproc, labeled))
791 self._bufferstates.append((error, subproc, labeled))
790 self._bufferapplylabels = labeled
792 self._bufferapplylabels = labeled
791
793
792 def popbuffer(self):
794 def popbuffer(self):
793 '''pop the last buffer and return the buffered output'''
795 '''pop the last buffer and return the buffered output'''
794 self._bufferstates.pop()
796 self._bufferstates.pop()
795 if self._bufferstates:
797 if self._bufferstates:
796 self._bufferapplylabels = self._bufferstates[-1][2]
798 self._bufferapplylabels = self._bufferstates[-1][2]
797 else:
799 else:
798 self._bufferapplylabels = None
800 self._bufferapplylabels = None
799
801
800 return "".join(self._buffers.pop())
802 return "".join(self._buffers.pop())
801
803
802 def write(self, *args, **opts):
804 def write(self, *args, **opts):
803 '''write args to output
805 '''write args to output
804
806
805 By default, this method simply writes to the buffer or stdout.
807 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
808 Color mode can be set on the UI class to have the output decorated
807 with color modifier before being written to stdout.
809 with color modifier before being written to stdout.
808
810
809 The color used is controlled by an optional keyword argument, "label".
811 The color used is controlled by an optional keyword argument, "label".
810 This should be a string containing label names separated by space.
812 This should be a string containing label names separated by space.
811 Label names take the form of "topic.type". For example, ui.debug()
813 Label names take the form of "topic.type". For example, ui.debug()
812 issues a label of "ui.debug".
814 issues a label of "ui.debug".
813
815
814 When labeling output for a specific command, a label of
816 When labeling output for a specific command, a label of
815 "cmdname.type" is recommended. For example, status issues
817 "cmdname.type" is recommended. For example, status issues
816 a label of "status.modified" for modified files.
818 a label of "status.modified" for modified files.
817 '''
819 '''
818 if self._buffers and not opts.get('prompt', False):
820 if self._buffers and not opts.get('prompt', False):
819 if self._bufferapplylabels:
821 if self._bufferapplylabels:
820 label = opts.get('label', '')
822 label = opts.get('label', '')
821 self._buffers[-1].extend(self.label(a, label) for a in args)
823 self._buffers[-1].extend(self.label(a, label) for a in args)
822 else:
824 else:
823 self._buffers[-1].extend(args)
825 self._buffers[-1].extend(args)
824 elif self._colormode == 'win32':
826 elif self._colormode == 'win32':
825 # windows color printing is its own can of crab, defer to
827 # windows color printing is its own can of crab, defer to
826 # the color module and that is it.
828 # the color module and that is it.
827 color.win32print(self, self._write, *args, **opts)
829 color.win32print(self, self._write, *args, **opts)
828 else:
830 else:
829 msgs = args
831 msgs = args
830 if self._colormode is not None:
832 if self._colormode is not None:
831 label = opts.get('label', '')
833 label = opts.get('label', '')
832 msgs = [self.label(a, label) for a in args]
834 msgs = [self.label(a, label) for a in args]
833 self._write(*msgs, **opts)
835 self._write(*msgs, **opts)
834
836
835 def _write(self, *msgs, **opts):
837 def _write(self, *msgs, **opts):
836 self._progclear()
838 self._progclear()
837 # opencode timeblockedsection because this is a critical path
839 # opencode timeblockedsection because this is a critical path
838 starttime = util.timer()
840 starttime = util.timer()
839 try:
841 try:
840 for a in msgs:
842 for a in msgs:
841 self.fout.write(a)
843 self.fout.write(a)
842 except IOError as err:
844 except IOError as err:
843 raise error.StdioError(err)
845 raise error.StdioError(err)
844 finally:
846 finally:
845 self._blockedtimes['stdio_blocked'] += \
847 self._blockedtimes['stdio_blocked'] += \
846 (util.timer() - starttime) * 1000
848 (util.timer() - starttime) * 1000
847
849
848 def write_err(self, *args, **opts):
850 def write_err(self, *args, **opts):
849 self._progclear()
851 self._progclear()
850 if self._bufferstates and self._bufferstates[-1][0]:
852 if self._bufferstates and self._bufferstates[-1][0]:
851 self.write(*args, **opts)
853 self.write(*args, **opts)
852 elif self._colormode == 'win32':
854 elif self._colormode == 'win32':
853 # windows color printing is its own can of crab, defer to
855 # windows color printing is its own can of crab, defer to
854 # the color module and that is it.
856 # the color module and that is it.
855 color.win32print(self, self._write_err, *args, **opts)
857 color.win32print(self, self._write_err, *args, **opts)
856 else:
858 else:
857 msgs = args
859 msgs = args
858 if self._colormode is not None:
860 if self._colormode is not None:
859 label = opts.get('label', '')
861 label = opts.get('label', '')
860 msgs = [self.label(a, label) for a in args]
862 msgs = [self.label(a, label) for a in args]
861 self._write_err(*msgs, **opts)
863 self._write_err(*msgs, **opts)
862
864
863 def _write_err(self, *msgs, **opts):
865 def _write_err(self, *msgs, **opts):
864 try:
866 try:
865 with self.timeblockedsection('stdio'):
867 with self.timeblockedsection('stdio'):
866 if not getattr(self.fout, 'closed', False):
868 if not getattr(self.fout, 'closed', False):
867 self.fout.flush()
869 self.fout.flush()
868 for a in msgs:
870 for a in msgs:
869 self.ferr.write(a)
871 self.ferr.write(a)
870 # stderr may be buffered under win32 when redirected to files,
872 # stderr may be buffered under win32 when redirected to files,
871 # including stdout.
873 # including stdout.
872 if not getattr(self.ferr, 'closed', False):
874 if not getattr(self.ferr, 'closed', False):
873 self.ferr.flush()
875 self.ferr.flush()
874 except IOError as inst:
876 except IOError as inst:
875 raise error.StdioError(inst)
877 raise error.StdioError(inst)
876
878
877 def flush(self):
879 def flush(self):
878 # opencode timeblockedsection because this is a critical path
880 # opencode timeblockedsection because this is a critical path
879 starttime = util.timer()
881 starttime = util.timer()
880 try:
882 try:
881 try:
883 try:
882 self.fout.flush()
884 self.fout.flush()
883 except IOError as err:
885 except IOError as err:
884 raise error.StdioError(err)
886 raise error.StdioError(err)
885 finally:
887 finally:
886 try:
888 try:
887 self.ferr.flush()
889 self.ferr.flush()
888 except IOError as err:
890 except IOError as err:
889 raise error.StdioError(err)
891 raise error.StdioError(err)
890 finally:
892 finally:
891 self._blockedtimes['stdio_blocked'] += \
893 self._blockedtimes['stdio_blocked'] += \
892 (util.timer() - starttime) * 1000
894 (util.timer() - starttime) * 1000
893
895
894 def _isatty(self, fh):
896 def _isatty(self, fh):
895 if self.configbool('ui', 'nontty', False):
897 if self.configbool('ui', 'nontty', False):
896 return False
898 return False
897 return util.isatty(fh)
899 return util.isatty(fh)
898
900
899 def disablepager(self):
901 def disablepager(self):
900 self._disablepager = True
902 self._disablepager = True
901
903
902 def pager(self, command):
904 def pager(self, command):
903 """Start a pager for subsequent command output.
905 """Start a pager for subsequent command output.
904
906
905 Commands which produce a long stream of output should call
907 Commands which produce a long stream of output should call
906 this function to activate the user's preferred pagination
908 this function to activate the user's preferred pagination
907 mechanism (which may be no pager). Calling this function
909 mechanism (which may be no pager). Calling this function
908 precludes any future use of interactive functionality, such as
910 precludes any future use of interactive functionality, such as
909 prompting the user or activating curses.
911 prompting the user or activating curses.
910
912
911 Args:
913 Args:
912 command: The full, non-aliased name of the command. That is, "log"
914 command: The full, non-aliased name of the command. That is, "log"
913 not "history, "summary" not "summ", etc.
915 not "history, "summary" not "summ", etc.
914 """
916 """
915 if (self._disablepager
917 if (self._disablepager
916 or self.pageractive
918 or self.pageractive
917 or command in self.configlist('pager', 'ignore')
919 or command in self.configlist('pager', 'ignore')
918 or not self.configbool('ui', 'paginate', True)
920 or not self.configbool('ui', 'paginate', True)
919 or not self.configbool('pager', 'attend-' + command, True)
921 or not self.configbool('pager', 'attend-' + command, True)
920 # TODO: if we want to allow HGPLAINEXCEPT=pager,
922 # TODO: if we want to allow HGPLAINEXCEPT=pager,
921 # formatted() will need some adjustment.
923 # formatted() will need some adjustment.
922 or not self.formatted()
924 or not self.formatted()
923 or self.plain()
925 or self.plain()
924 # TODO: expose debugger-enabled on the UI object
926 # TODO: expose debugger-enabled on the UI object
925 or '--debugger' in pycompat.sysargv):
927 or '--debugger' in pycompat.sysargv):
926 # We only want to paginate if the ui appears to be
928 # We only want to paginate if the ui appears to be
927 # interactive, the user didn't say HGPLAIN or
929 # interactive, the user didn't say HGPLAIN or
928 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
930 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
929 return
931 return
930
932
931 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
933 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
932 if not pagercmd:
934 if not pagercmd:
933 return
935 return
934
936
935 pagerenv = {}
937 pagerenv = {}
936 for name, value in rcutil.defaultpagerenv().items():
938 for name, value in rcutil.defaultpagerenv().items():
937 if name not in encoding.environ:
939 if name not in encoding.environ:
938 pagerenv[name] = value
940 pagerenv[name] = value
939
941
940 self.debug('starting pager for command %r\n' % command)
942 self.debug('starting pager for command %r\n' % command)
941 self.flush()
943 self.flush()
942
944
943 wasformatted = self.formatted()
945 wasformatted = self.formatted()
944 if util.safehasattr(signal, "SIGPIPE"):
946 if util.safehasattr(signal, "SIGPIPE"):
945 signal.signal(signal.SIGPIPE, _catchterm)
947 signal.signal(signal.SIGPIPE, _catchterm)
946 if self._runpager(pagercmd, pagerenv):
948 if self._runpager(pagercmd, pagerenv):
947 self.pageractive = True
949 self.pageractive = True
948 # Preserve the formatted-ness of the UI. This is important
950 # Preserve the formatted-ness of the UI. This is important
949 # because we mess with stdout, which might confuse
951 # because we mess with stdout, which might confuse
950 # auto-detection of things being formatted.
952 # auto-detection of things being formatted.
951 self.setconfig('ui', 'formatted', wasformatted, 'pager')
953 self.setconfig('ui', 'formatted', wasformatted, 'pager')
952 self.setconfig('ui', 'interactive', False, 'pager')
954 self.setconfig('ui', 'interactive', False, 'pager')
953
955
954 # If pagermode differs from color.mode, reconfigure color now that
956 # If pagermode differs from color.mode, reconfigure color now that
955 # pageractive is set.
957 # pageractive is set.
956 cm = self._colormode
958 cm = self._colormode
957 if cm != self.config('color', 'pagermode', cm):
959 if cm != self.config('color', 'pagermode', cm):
958 color.setup(self)
960 color.setup(self)
959 else:
961 else:
960 # If the pager can't be spawned in dispatch when --pager=on is
962 # 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
963 # given, don't try again when the command runs, to avoid a duplicate
962 # warning about a missing pager command.
964 # warning about a missing pager command.
963 self.disablepager()
965 self.disablepager()
964
966
965 def _runpager(self, command, env=None):
967 def _runpager(self, command, env=None):
966 """Actually start the pager and set up file descriptors.
968 """Actually start the pager and set up file descriptors.
967
969
968 This is separate in part so that extensions (like chg) can
970 This is separate in part so that extensions (like chg) can
969 override how a pager is invoked.
971 override how a pager is invoked.
970 """
972 """
971 if command == 'cat':
973 if command == 'cat':
972 # Save ourselves some work.
974 # Save ourselves some work.
973 return False
975 return False
974 # If the command doesn't contain any of these characters, we
976 # If the command doesn't contain any of these characters, we
975 # assume it's a binary and exec it directly. This means for
977 # assume it's a binary and exec it directly. This means for
976 # simple pager command configurations, we can degrade
978 # simple pager command configurations, we can degrade
977 # gracefully and tell the user about their broken pager.
979 # gracefully and tell the user about their broken pager.
978 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
980 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
979
981
980 if pycompat.osname == 'nt' and not shell:
982 if pycompat.osname == 'nt' and not shell:
981 # Window's built-in `more` cannot be invoked with shell=False, but
983 # Window's built-in `more` cannot be invoked with shell=False, but
982 # its `more.com` can. Hide this implementation detail from the
984 # its `more.com` can. Hide this implementation detail from the
983 # user so we can also get sane bad PAGER behavior. MSYS has
985 # 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
986 # `more.exe`, so do a cmd.exe style resolution of the executable to
985 # determine which one to use.
987 # determine which one to use.
986 fullcmd = util.findexe(command)
988 fullcmd = util.findexe(command)
987 if not fullcmd:
989 if not fullcmd:
988 self.warn(_("missing pager command '%s', skipping pager\n")
990 self.warn(_("missing pager command '%s', skipping pager\n")
989 % command)
991 % command)
990 return False
992 return False
991
993
992 command = fullcmd
994 command = fullcmd
993
995
994 try:
996 try:
995 pager = subprocess.Popen(
997 pager = subprocess.Popen(
996 command, shell=shell, bufsize=-1,
998 command, shell=shell, bufsize=-1,
997 close_fds=util.closefds, stdin=subprocess.PIPE,
999 close_fds=util.closefds, stdin=subprocess.PIPE,
998 stdout=util.stdout, stderr=util.stderr,
1000 stdout=util.stdout, stderr=util.stderr,
999 env=util.shellenviron(env))
1001 env=util.shellenviron(env))
1000 except OSError as e:
1002 except OSError as e:
1001 if e.errno == errno.ENOENT and not shell:
1003 if e.errno == errno.ENOENT and not shell:
1002 self.warn(_("missing pager command '%s', skipping pager\n")
1004 self.warn(_("missing pager command '%s', skipping pager\n")
1003 % command)
1005 % command)
1004 return False
1006 return False
1005 raise
1007 raise
1006
1008
1007 # back up original file descriptors
1009 # back up original file descriptors
1008 stdoutfd = os.dup(util.stdout.fileno())
1010 stdoutfd = os.dup(util.stdout.fileno())
1009 stderrfd = os.dup(util.stderr.fileno())
1011 stderrfd = os.dup(util.stderr.fileno())
1010
1012
1011 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1013 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1012 if self._isatty(util.stderr):
1014 if self._isatty(util.stderr):
1013 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1015 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1014
1016
1015 @self.atexit
1017 @self.atexit
1016 def killpager():
1018 def killpager():
1017 if util.safehasattr(signal, "SIGINT"):
1019 if util.safehasattr(signal, "SIGINT"):
1018 signal.signal(signal.SIGINT, signal.SIG_IGN)
1020 signal.signal(signal.SIGINT, signal.SIG_IGN)
1019 # restore original fds, closing pager.stdin copies in the process
1021 # restore original fds, closing pager.stdin copies in the process
1020 os.dup2(stdoutfd, util.stdout.fileno())
1022 os.dup2(stdoutfd, util.stdout.fileno())
1021 os.dup2(stderrfd, util.stderr.fileno())
1023 os.dup2(stderrfd, util.stderr.fileno())
1022 pager.stdin.close()
1024 pager.stdin.close()
1023 pager.wait()
1025 pager.wait()
1024
1026
1025 return True
1027 return True
1026
1028
1027 def atexit(self, func, *args, **kwargs):
1029 def atexit(self, func, *args, **kwargs):
1028 '''register a function to run after dispatching a request
1030 '''register a function to run after dispatching a request
1029
1031
1030 Handlers do not stay registered across request boundaries.'''
1032 Handlers do not stay registered across request boundaries.'''
1031 self._exithandlers.append((func, args, kwargs))
1033 self._exithandlers.append((func, args, kwargs))
1032 return func
1034 return func
1033
1035
1034 def interface(self, feature):
1036 def interface(self, feature):
1035 """what interface to use for interactive console features?
1037 """what interface to use for interactive console features?
1036
1038
1037 The interface is controlled by the value of `ui.interface` but also by
1039 The interface is controlled by the value of `ui.interface` but also by
1038 the value of feature-specific configuration. For example:
1040 the value of feature-specific configuration. For example:
1039
1041
1040 ui.interface.histedit = text
1042 ui.interface.histedit = text
1041 ui.interface.chunkselector = curses
1043 ui.interface.chunkselector = curses
1042
1044
1043 Here the features are "histedit" and "chunkselector".
1045 Here the features are "histedit" and "chunkselector".
1044
1046
1045 The configuration above means that the default interfaces for commands
1047 The configuration above means that the default interfaces for commands
1046 is curses, the interface for histedit is text and the interface for
1048 is curses, the interface for histedit is text and the interface for
1047 selecting chunk is crecord (the best curses interface available).
1049 selecting chunk is crecord (the best curses interface available).
1048
1050
1049 Consider the following example:
1051 Consider the following example:
1050 ui.interface = curses
1052 ui.interface = curses
1051 ui.interface.histedit = text
1053 ui.interface.histedit = text
1052
1054
1053 Then histedit will use the text interface and chunkselector will use
1055 Then histedit will use the text interface and chunkselector will use
1054 the default curses interface (crecord at the moment).
1056 the default curses interface (crecord at the moment).
1055 """
1057 """
1056 alldefaults = frozenset(["text", "curses"])
1058 alldefaults = frozenset(["text", "curses"])
1057
1059
1058 featureinterfaces = {
1060 featureinterfaces = {
1059 "chunkselector": [
1061 "chunkselector": [
1060 "text",
1062 "text",
1061 "curses",
1063 "curses",
1062 ]
1064 ]
1063 }
1065 }
1064
1066
1065 # Feature-specific interface
1067 # Feature-specific interface
1066 if feature not in featureinterfaces.keys():
1068 if feature not in featureinterfaces.keys():
1067 # Programming error, not user error
1069 # Programming error, not user error
1068 raise ValueError("Unknown feature requested %s" % feature)
1070 raise ValueError("Unknown feature requested %s" % feature)
1069
1071
1070 availableinterfaces = frozenset(featureinterfaces[feature])
1072 availableinterfaces = frozenset(featureinterfaces[feature])
1071 if alldefaults > availableinterfaces:
1073 if alldefaults > availableinterfaces:
1072 # Programming error, not user error. We need a use case to
1074 # Programming error, not user error. We need a use case to
1073 # define the right thing to do here.
1075 # define the right thing to do here.
1074 raise ValueError(
1076 raise ValueError(
1075 "Feature %s does not handle all default interfaces" %
1077 "Feature %s does not handle all default interfaces" %
1076 feature)
1078 feature)
1077
1079
1078 if self.plain():
1080 if self.plain():
1079 return "text"
1081 return "text"
1080
1082
1081 # Default interface for all the features
1083 # Default interface for all the features
1082 defaultinterface = "text"
1084 defaultinterface = "text"
1083 i = self.config("ui", "interface", None)
1085 i = self.config("ui", "interface", None)
1084 if i in alldefaults:
1086 if i in alldefaults:
1085 defaultinterface = i
1087 defaultinterface = i
1086
1088
1087 choseninterface = defaultinterface
1089 choseninterface = defaultinterface
1088 f = self.config("ui", "interface.%s" % feature, None)
1090 f = self.config("ui", "interface.%s" % feature, None)
1089 if f in availableinterfaces:
1091 if f in availableinterfaces:
1090 choseninterface = f
1092 choseninterface = f
1091
1093
1092 if i is not None and defaultinterface != i:
1094 if i is not None and defaultinterface != i:
1093 if f is not None:
1095 if f is not None:
1094 self.warn(_("invalid value for ui.interface: %s\n") %
1096 self.warn(_("invalid value for ui.interface: %s\n") %
1095 (i,))
1097 (i,))
1096 else:
1098 else:
1097 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1099 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1098 (i, choseninterface))
1100 (i, choseninterface))
1099 if f is not None and choseninterface != f:
1101 if f is not None and choseninterface != f:
1100 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1102 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1101 (feature, f, choseninterface))
1103 (feature, f, choseninterface))
1102
1104
1103 return choseninterface
1105 return choseninterface
1104
1106
1105 def interactive(self):
1107 def interactive(self):
1106 '''is interactive input allowed?
1108 '''is interactive input allowed?
1107
1109
1108 An interactive session is a session where input can be reasonably read
1110 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
1111 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
1112 from stdin should fail with an error, unless a sensible default has been
1111 specified.
1113 specified.
1112
1114
1113 Interactiveness is triggered by the value of the `ui.interactive'
1115 Interactiveness is triggered by the value of the `ui.interactive'
1114 configuration variable or - if it is unset - when `sys.stdin' points
1116 configuration variable or - if it is unset - when `sys.stdin' points
1115 to a terminal device.
1117 to a terminal device.
1116
1118
1117 This function refers to input only; for output, see `ui.formatted()'.
1119 This function refers to input only; for output, see `ui.formatted()'.
1118 '''
1120 '''
1119 i = self.configbool("ui", "interactive", None)
1121 i = self.configbool("ui", "interactive", None)
1120 if i is None:
1122 if i is None:
1121 # some environments replace stdin without implementing isatty
1123 # some environments replace stdin without implementing isatty
1122 # usually those are non-interactive
1124 # usually those are non-interactive
1123 return self._isatty(self.fin)
1125 return self._isatty(self.fin)
1124
1126
1125 return i
1127 return i
1126
1128
1127 def termwidth(self):
1129 def termwidth(self):
1128 '''how wide is the terminal in columns?
1130 '''how wide is the terminal in columns?
1129 '''
1131 '''
1130 if 'COLUMNS' in encoding.environ:
1132 if 'COLUMNS' in encoding.environ:
1131 try:
1133 try:
1132 return int(encoding.environ['COLUMNS'])
1134 return int(encoding.environ['COLUMNS'])
1133 except ValueError:
1135 except ValueError:
1134 pass
1136 pass
1135 return scmutil.termsize(self)[0]
1137 return scmutil.termsize(self)[0]
1136
1138
1137 def formatted(self):
1139 def formatted(self):
1138 '''should formatted output be used?
1140 '''should formatted output be used?
1139
1141
1140 It is often desirable to format the output to suite the output medium.
1142 It is often desirable to format the output to suite the output medium.
1141 Examples of this are truncating long lines or colorizing messages.
1143 Examples of this are truncating long lines or colorizing messages.
1142 However, this is not often not desirable when piping output into other
1144 However, this is not often not desirable when piping output into other
1143 utilities, e.g. `grep'.
1145 utilities, e.g. `grep'.
1144
1146
1145 Formatted output is triggered by the value of the `ui.formatted'
1147 Formatted output is triggered by the value of the `ui.formatted'
1146 configuration variable or - if it is unset - when `sys.stdout' points
1148 configuration variable or - if it is unset - when `sys.stdout' points
1147 to a terminal device. Please note that `ui.formatted' should be
1149 to a terminal device. Please note that `ui.formatted' should be
1148 considered an implementation detail; it is not intended for use outside
1150 considered an implementation detail; it is not intended for use outside
1149 Mercurial or its extensions.
1151 Mercurial or its extensions.
1150
1152
1151 This function refers to output only; for input, see `ui.interactive()'.
1153 This function refers to output only; for input, see `ui.interactive()'.
1152 This function always returns false when in plain mode, see `ui.plain()'.
1154 This function always returns false when in plain mode, see `ui.plain()'.
1153 '''
1155 '''
1154 if self.plain():
1156 if self.plain():
1155 return False
1157 return False
1156
1158
1157 i = self.configbool("ui", "formatted", None)
1159 i = self.configbool("ui", "formatted", None)
1158 if i is None:
1160 if i is None:
1159 # some environments replace stdout without implementing isatty
1161 # some environments replace stdout without implementing isatty
1160 # usually those are non-interactive
1162 # usually those are non-interactive
1161 return self._isatty(self.fout)
1163 return self._isatty(self.fout)
1162
1164
1163 return i
1165 return i
1164
1166
1165 def _readline(self, prompt=''):
1167 def _readline(self, prompt=''):
1166 if self._isatty(self.fin):
1168 if self._isatty(self.fin):
1167 try:
1169 try:
1168 # magically add command line editing support, where
1170 # magically add command line editing support, where
1169 # available
1171 # available
1170 import readline
1172 import readline
1171 # force demandimport to really load the module
1173 # force demandimport to really load the module
1172 readline.read_history_file
1174 readline.read_history_file
1173 # windows sometimes raises something other than ImportError
1175 # windows sometimes raises something other than ImportError
1174 except Exception:
1176 except Exception:
1175 pass
1177 pass
1176
1178
1177 # call write() so output goes through subclassed implementation
1179 # call write() so output goes through subclassed implementation
1178 # e.g. color extension on Windows
1180 # e.g. color extension on Windows
1179 self.write(prompt, prompt=True)
1181 self.write(prompt, prompt=True)
1180
1182
1181 # instead of trying to emulate raw_input, swap (self.fin,
1183 # instead of trying to emulate raw_input, swap (self.fin,
1182 # self.fout) with (sys.stdin, sys.stdout)
1184 # self.fout) with (sys.stdin, sys.stdout)
1183 oldin = sys.stdin
1185 oldin = sys.stdin
1184 oldout = sys.stdout
1186 oldout = sys.stdout
1185 sys.stdin = self.fin
1187 sys.stdin = self.fin
1186 sys.stdout = self.fout
1188 sys.stdout = self.fout
1187 # prompt ' ' must exist; otherwise readline may delete entire line
1189 # prompt ' ' must exist; otherwise readline may delete entire line
1188 # - http://bugs.python.org/issue12833
1190 # - http://bugs.python.org/issue12833
1189 with self.timeblockedsection('stdio'):
1191 with self.timeblockedsection('stdio'):
1190 line = raw_input(' ')
1192 line = raw_input(' ')
1191 sys.stdin = oldin
1193 sys.stdin = oldin
1192 sys.stdout = oldout
1194 sys.stdout = oldout
1193
1195
1194 # When stdin is in binary mode on Windows, it can cause
1196 # When stdin is in binary mode on Windows, it can cause
1195 # raw_input() to emit an extra trailing carriage return
1197 # raw_input() to emit an extra trailing carriage return
1196 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1198 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1197 line = line[:-1]
1199 line = line[:-1]
1198 return line
1200 return line
1199
1201
1200 def prompt(self, msg, default="y"):
1202 def prompt(self, msg, default="y"):
1201 """Prompt user with msg, read response.
1203 """Prompt user with msg, read response.
1202 If ui is not interactive, the default is returned.
1204 If ui is not interactive, the default is returned.
1203 """
1205 """
1204 if not self.interactive():
1206 if not self.interactive():
1205 self.write(msg, ' ', default or '', "\n")
1207 self.write(msg, ' ', default or '', "\n")
1206 return default
1208 return default
1207 try:
1209 try:
1208 r = self._readline(self.label(msg, 'ui.prompt'))
1210 r = self._readline(self.label(msg, 'ui.prompt'))
1209 if not r:
1211 if not r:
1210 r = default
1212 r = default
1211 if self.configbool('ui', 'promptecho'):
1213 if self.configbool('ui', 'promptecho'):
1212 self.write(r, "\n")
1214 self.write(r, "\n")
1213 return r
1215 return r
1214 except EOFError:
1216 except EOFError:
1215 raise error.ResponseExpected()
1217 raise error.ResponseExpected()
1216
1218
1217 @staticmethod
1219 @staticmethod
1218 def extractchoices(prompt):
1220 def extractchoices(prompt):
1219 """Extract prompt message and list of choices from specified prompt.
1221 """Extract prompt message and list of choices from specified prompt.
1220
1222
1221 This returns tuple "(message, choices)", and "choices" is the
1223 This returns tuple "(message, choices)", and "choices" is the
1222 list of tuple "(response character, text without &)".
1224 list of tuple "(response character, text without &)".
1223
1225
1224 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1226 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1225 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1227 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1226 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1228 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1227 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1229 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1228 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1230 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1229 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1231 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1230 """
1232 """
1231
1233
1232 # Sadly, the prompt string may have been built with a filename
1234 # Sadly, the prompt string may have been built with a filename
1233 # containing "$$" so let's try to find the first valid-looking
1235 # containing "$$" so let's try to find the first valid-looking
1234 # prompt to start parsing. Sadly, we also can't rely on
1236 # prompt to start parsing. Sadly, we also can't rely on
1235 # choices containing spaces, ASCII, or basically anything
1237 # choices containing spaces, ASCII, or basically anything
1236 # except an ampersand followed by a character.
1238 # except an ampersand followed by a character.
1237 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1239 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1238 msg = m.group(1)
1240 msg = m.group(1)
1239 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1241 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1240 return (msg,
1242 return (msg,
1241 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1243 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1242 for s in choices])
1244 for s in choices])
1243
1245
1244 def promptchoice(self, prompt, default=0):
1246 def promptchoice(self, prompt, default=0):
1245 """Prompt user with a message, read response, and ensure it matches
1247 """Prompt user with a message, read response, and ensure it matches
1246 one of the provided choices. The prompt is formatted as follows:
1248 one of the provided choices. The prompt is formatted as follows:
1247
1249
1248 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1250 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1249
1251
1250 The index of the choice is returned. Responses are case
1252 The index of the choice is returned. Responses are case
1251 insensitive. If ui is not interactive, the default is
1253 insensitive. If ui is not interactive, the default is
1252 returned.
1254 returned.
1253 """
1255 """
1254
1256
1255 msg, choices = self.extractchoices(prompt)
1257 msg, choices = self.extractchoices(prompt)
1256 resps = [r for r, t in choices]
1258 resps = [r for r, t in choices]
1257 while True:
1259 while True:
1258 r = self.prompt(msg, resps[default])
1260 r = self.prompt(msg, resps[default])
1259 if r.lower() in resps:
1261 if r.lower() in resps:
1260 return resps.index(r.lower())
1262 return resps.index(r.lower())
1261 self.write(_("unrecognized response\n"))
1263 self.write(_("unrecognized response\n"))
1262
1264
1263 def getpass(self, prompt=None, default=None):
1265 def getpass(self, prompt=None, default=None):
1264 if not self.interactive():
1266 if not self.interactive():
1265 return default
1267 return default
1266 try:
1268 try:
1267 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1269 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1268 # disable getpass() only if explicitly specified. it's still valid
1270 # disable getpass() only if explicitly specified. it's still valid
1269 # to interact with tty even if fin is not a tty.
1271 # to interact with tty even if fin is not a tty.
1270 with self.timeblockedsection('stdio'):
1272 with self.timeblockedsection('stdio'):
1271 if self.configbool('ui', 'nontty'):
1273 if self.configbool('ui', 'nontty'):
1272 l = self.fin.readline()
1274 l = self.fin.readline()
1273 if not l:
1275 if not l:
1274 raise EOFError
1276 raise EOFError
1275 return l.rstrip('\n')
1277 return l.rstrip('\n')
1276 else:
1278 else:
1277 return getpass.getpass('')
1279 return getpass.getpass('')
1278 except EOFError:
1280 except EOFError:
1279 raise error.ResponseExpected()
1281 raise error.ResponseExpected()
1280 def status(self, *msg, **opts):
1282 def status(self, *msg, **opts):
1281 '''write status message to output (if ui.quiet is False)
1283 '''write status message to output (if ui.quiet is False)
1282
1284
1283 This adds an output label of "ui.status".
1285 This adds an output label of "ui.status".
1284 '''
1286 '''
1285 if not self.quiet:
1287 if not self.quiet:
1286 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1288 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1287 self.write(*msg, **opts)
1289 self.write(*msg, **opts)
1288 def warn(self, *msg, **opts):
1290 def warn(self, *msg, **opts):
1289 '''write warning message to output (stderr)
1291 '''write warning message to output (stderr)
1290
1292
1291 This adds an output label of "ui.warning".
1293 This adds an output label of "ui.warning".
1292 '''
1294 '''
1293 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1295 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1294 self.write_err(*msg, **opts)
1296 self.write_err(*msg, **opts)
1295 def note(self, *msg, **opts):
1297 def note(self, *msg, **opts):
1296 '''write note to output (if ui.verbose is True)
1298 '''write note to output (if ui.verbose is True)
1297
1299
1298 This adds an output label of "ui.note".
1300 This adds an output label of "ui.note".
1299 '''
1301 '''
1300 if self.verbose:
1302 if self.verbose:
1301 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1303 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1302 self.write(*msg, **opts)
1304 self.write(*msg, **opts)
1303 def debug(self, *msg, **opts):
1305 def debug(self, *msg, **opts):
1304 '''write debug message to output (if ui.debugflag is True)
1306 '''write debug message to output (if ui.debugflag is True)
1305
1307
1306 This adds an output label of "ui.debug".
1308 This adds an output label of "ui.debug".
1307 '''
1309 '''
1308 if self.debugflag:
1310 if self.debugflag:
1309 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1311 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1310 self.write(*msg, **opts)
1312 self.write(*msg, **opts)
1311
1313
1312 def edit(self, text, user, extra=None, editform=None, pending=None,
1314 def edit(self, text, user, extra=None, editform=None, pending=None,
1313 repopath=None):
1315 repopath=None):
1314 extra_defaults = {
1316 extra_defaults = {
1315 'prefix': 'editor',
1317 'prefix': 'editor',
1316 'suffix': '.txt',
1318 'suffix': '.txt',
1317 }
1319 }
1318 if extra is not None:
1320 if extra is not None:
1319 extra_defaults.update(extra)
1321 extra_defaults.update(extra)
1320 extra = extra_defaults
1322 extra = extra_defaults
1321
1323
1322 rdir = None
1324 rdir = None
1323 if self.configbool('experimental', 'editortmpinhg'):
1325 if self.configbool('experimental', 'editortmpinhg'):
1324 rdir = repopath
1326 rdir = repopath
1325 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1327 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1326 suffix=extra['suffix'],
1328 suffix=extra['suffix'],
1327 dir=rdir)
1329 dir=rdir)
1328 try:
1330 try:
1329 f = os.fdopen(fd, r'wb')
1331 f = os.fdopen(fd, r'wb')
1330 f.write(util.tonativeeol(text))
1332 f.write(util.tonativeeol(text))
1331 f.close()
1333 f.close()
1332
1334
1333 environ = {'HGUSER': user}
1335 environ = {'HGUSER': user}
1334 if 'transplant_source' in extra:
1336 if 'transplant_source' in extra:
1335 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1337 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1336 for label in ('intermediate-source', 'source', 'rebase_source'):
1338 for label in ('intermediate-source', 'source', 'rebase_source'):
1337 if label in extra:
1339 if label in extra:
1338 environ.update({'HGREVISION': extra[label]})
1340 environ.update({'HGREVISION': extra[label]})
1339 break
1341 break
1340 if editform:
1342 if editform:
1341 environ.update({'HGEDITFORM': editform})
1343 environ.update({'HGEDITFORM': editform})
1342 if pending:
1344 if pending:
1343 environ.update({'HG_PENDING': pending})
1345 environ.update({'HG_PENDING': pending})
1344
1346
1345 editor = self.geteditor()
1347 editor = self.geteditor()
1346
1348
1347 self.system("%s \"%s\"" % (editor, name),
1349 self.system("%s \"%s\"" % (editor, name),
1348 environ=environ,
1350 environ=environ,
1349 onerr=error.Abort, errprefix=_("edit failed"),
1351 onerr=error.Abort, errprefix=_("edit failed"),
1350 blockedtag='editor')
1352 blockedtag='editor')
1351
1353
1352 f = open(name, r'rb')
1354 f = open(name, r'rb')
1353 t = util.fromnativeeol(f.read())
1355 t = util.fromnativeeol(f.read())
1354 f.close()
1356 f.close()
1355 finally:
1357 finally:
1356 os.unlink(name)
1358 os.unlink(name)
1357
1359
1358 return t
1360 return t
1359
1361
1360 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1362 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1361 blockedtag=None):
1363 blockedtag=None):
1362 '''execute shell command with appropriate output stream. command
1364 '''execute shell command with appropriate output stream. command
1363 output will be redirected if fout is not stdout.
1365 output will be redirected if fout is not stdout.
1364
1366
1365 if command fails and onerr is None, return status, else raise onerr
1367 if command fails and onerr is None, return status, else raise onerr
1366 object as exception.
1368 object as exception.
1367 '''
1369 '''
1368 if blockedtag is None:
1370 if blockedtag is None:
1369 # Long cmds tend to be because of an absolute path on cmd. Keep
1371 # Long cmds tend to be because of an absolute path on cmd. Keep
1370 # the tail end instead
1372 # the tail end instead
1371 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1373 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1372 blockedtag = 'unknown_system_' + cmdsuffix
1374 blockedtag = 'unknown_system_' + cmdsuffix
1373 out = self.fout
1375 out = self.fout
1374 if any(s[1] for s in self._bufferstates):
1376 if any(s[1] for s in self._bufferstates):
1375 out = self
1377 out = self
1376 with self.timeblockedsection(blockedtag):
1378 with self.timeblockedsection(blockedtag):
1377 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1379 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1378 if rc and onerr:
1380 if rc and onerr:
1379 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1381 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1380 util.explainexit(rc)[0])
1382 util.explainexit(rc)[0])
1381 if errprefix:
1383 if errprefix:
1382 errmsg = '%s: %s' % (errprefix, errmsg)
1384 errmsg = '%s: %s' % (errprefix, errmsg)
1383 raise onerr(errmsg)
1385 raise onerr(errmsg)
1384 return rc
1386 return rc
1385
1387
1386 def _runsystem(self, cmd, environ, cwd, out):
1388 def _runsystem(self, cmd, environ, cwd, out):
1387 """actually execute the given shell command (can be overridden by
1389 """actually execute the given shell command (can be overridden by
1388 extensions like chg)"""
1390 extensions like chg)"""
1389 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1391 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1390
1392
1391 def traceback(self, exc=None, force=False):
1393 def traceback(self, exc=None, force=False):
1392 '''print exception traceback if traceback printing enabled or forced.
1394 '''print exception traceback if traceback printing enabled or forced.
1393 only to call in exception handler. returns true if traceback
1395 only to call in exception handler. returns true if traceback
1394 printed.'''
1396 printed.'''
1395 if self.tracebackflag or force:
1397 if self.tracebackflag or force:
1396 if exc is None:
1398 if exc is None:
1397 exc = sys.exc_info()
1399 exc = sys.exc_info()
1398 cause = getattr(exc[1], 'cause', None)
1400 cause = getattr(exc[1], 'cause', None)
1399
1401
1400 if cause is not None:
1402 if cause is not None:
1401 causetb = traceback.format_tb(cause[2])
1403 causetb = traceback.format_tb(cause[2])
1402 exctb = traceback.format_tb(exc[2])
1404 exctb = traceback.format_tb(exc[2])
1403 exconly = traceback.format_exception_only(cause[0], cause[1])
1405 exconly = traceback.format_exception_only(cause[0], cause[1])
1404
1406
1405 # exclude frame where 'exc' was chained and rethrown from exctb
1407 # exclude frame where 'exc' was chained and rethrown from exctb
1406 self.write_err('Traceback (most recent call last):\n',
1408 self.write_err('Traceback (most recent call last):\n',
1407 ''.join(exctb[:-1]),
1409 ''.join(exctb[:-1]),
1408 ''.join(causetb),
1410 ''.join(causetb),
1409 ''.join(exconly))
1411 ''.join(exconly))
1410 else:
1412 else:
1411 output = traceback.format_exception(exc[0], exc[1], exc[2])
1413 output = traceback.format_exception(exc[0], exc[1], exc[2])
1412 data = r''.join(output)
1414 data = r''.join(output)
1413 if pycompat.ispy3:
1415 if pycompat.ispy3:
1414 enc = pycompat.sysstr(encoding.encoding)
1416 enc = pycompat.sysstr(encoding.encoding)
1415 data = data.encode(enc, errors=r'replace')
1417 data = data.encode(enc, errors=r'replace')
1416 self.write_err(data)
1418 self.write_err(data)
1417 return self.tracebackflag or force
1419 return self.tracebackflag or force
1418
1420
1419 def geteditor(self):
1421 def geteditor(self):
1420 '''return editor to use'''
1422 '''return editor to use'''
1421 if pycompat.sysplatform == 'plan9':
1423 if pycompat.sysplatform == 'plan9':
1422 # vi is the MIPS instruction simulator on Plan 9. We
1424 # vi is the MIPS instruction simulator on Plan 9. We
1423 # instead default to E to plumb commit messages to
1425 # instead default to E to plumb commit messages to
1424 # avoid confusion.
1426 # avoid confusion.
1425 editor = 'E'
1427 editor = 'E'
1426 else:
1428 else:
1427 editor = 'vi'
1429 editor = 'vi'
1428 return (encoding.environ.get("HGEDITOR") or
1430 return (encoding.environ.get("HGEDITOR") or
1429 self.config("ui", "editor", editor))
1431 self.config("ui", "editor", editor))
1430
1432
1431 @util.propertycache
1433 @util.propertycache
1432 def _progbar(self):
1434 def _progbar(self):
1433 """setup the progbar singleton to the ui object"""
1435 """setup the progbar singleton to the ui object"""
1434 if (self.quiet or self.debugflag
1436 if (self.quiet or self.debugflag
1435 or self.configbool('progress', 'disable', False)
1437 or self.configbool('progress', 'disable', False)
1436 or not progress.shouldprint(self)):
1438 or not progress.shouldprint(self)):
1437 return None
1439 return None
1438 return getprogbar(self)
1440 return getprogbar(self)
1439
1441
1440 def _progclear(self):
1442 def _progclear(self):
1441 """clear progress bar output if any. use it before any output"""
1443 """clear progress bar output if any. use it before any output"""
1442 if '_progbar' not in vars(self): # nothing loaded yet
1444 if '_progbar' not in vars(self): # nothing loaded yet
1443 return
1445 return
1444 if self._progbar is not None and self._progbar.printed:
1446 if self._progbar is not None and self._progbar.printed:
1445 self._progbar.clear()
1447 self._progbar.clear()
1446
1448
1447 def progress(self, topic, pos, item="", unit="", total=None):
1449 def progress(self, topic, pos, item="", unit="", total=None):
1448 '''show a progress message
1450 '''show a progress message
1449
1451
1450 By default a textual progress bar will be displayed if an operation
1452 By default a textual progress bar will be displayed if an operation
1451 takes too long. 'topic' is the current operation, 'item' is a
1453 takes too long. 'topic' is the current operation, 'item' is a
1452 non-numeric marker of the current position (i.e. the currently
1454 non-numeric marker of the current position (i.e. the currently
1453 in-process file), 'pos' is the current numeric position (i.e.
1455 in-process file), 'pos' is the current numeric position (i.e.
1454 revision, bytes, etc.), unit is a corresponding unit label,
1456 revision, bytes, etc.), unit is a corresponding unit label,
1455 and total is the highest expected pos.
1457 and total is the highest expected pos.
1456
1458
1457 Multiple nested topics may be active at a time.
1459 Multiple nested topics may be active at a time.
1458
1460
1459 All topics should be marked closed by setting pos to None at
1461 All topics should be marked closed by setting pos to None at
1460 termination.
1462 termination.
1461 '''
1463 '''
1462 if self._progbar is not None:
1464 if self._progbar is not None:
1463 self._progbar.progress(topic, pos, item=item, unit=unit,
1465 self._progbar.progress(topic, pos, item=item, unit=unit,
1464 total=total)
1466 total=total)
1465 if pos is None or not self.configbool('progress', 'debug'):
1467 if pos is None or not self.configbool('progress', 'debug'):
1466 return
1468 return
1467
1469
1468 if unit:
1470 if unit:
1469 unit = ' ' + unit
1471 unit = ' ' + unit
1470 if item:
1472 if item:
1471 item = ' ' + item
1473 item = ' ' + item
1472
1474
1473 if total:
1475 if total:
1474 pct = 100.0 * pos / total
1476 pct = 100.0 * pos / total
1475 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1477 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1476 % (topic, item, pos, total, unit, pct))
1478 % (topic, item, pos, total, unit, pct))
1477 else:
1479 else:
1478 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1480 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1479
1481
1480 def log(self, service, *msg, **opts):
1482 def log(self, service, *msg, **opts):
1481 '''hook for logging facility extensions
1483 '''hook for logging facility extensions
1482
1484
1483 service should be a readily-identifiable subsystem, which will
1485 service should be a readily-identifiable subsystem, which will
1484 allow filtering.
1486 allow filtering.
1485
1487
1486 *msg should be a newline-terminated format string to log, and
1488 *msg should be a newline-terminated format string to log, and
1487 then any values to %-format into that format string.
1489 then any values to %-format into that format string.
1488
1490
1489 **opts currently has no defined meanings.
1491 **opts currently has no defined meanings.
1490 '''
1492 '''
1491
1493
1492 def label(self, msg, label):
1494 def label(self, msg, label):
1493 '''style msg based on supplied label
1495 '''style msg based on supplied label
1494
1496
1495 If some color mode is enabled, this will add the necessary control
1497 If some color mode is enabled, this will add the necessary control
1496 characters to apply such color. In addition, 'debug' color mode adds
1498 characters to apply such color. In addition, 'debug' color mode adds
1497 markup showing which label affects a piece of text.
1499 markup showing which label affects a piece of text.
1498
1500
1499 ui.write(s, 'label') is equivalent to
1501 ui.write(s, 'label') is equivalent to
1500 ui.write(ui.label(s, 'label')).
1502 ui.write(ui.label(s, 'label')).
1501 '''
1503 '''
1502 if self._colormode is not None:
1504 if self._colormode is not None:
1503 return color.colorlabel(self, msg, label)
1505 return color.colorlabel(self, msg, label)
1504 return msg
1506 return msg
1505
1507
1506 def develwarn(self, msg, stacklevel=1, config=None):
1508 def develwarn(self, msg, stacklevel=1, config=None):
1507 """issue a developer warning message
1509 """issue a developer warning message
1508
1510
1509 Use 'stacklevel' to report the offender some layers further up in the
1511 Use 'stacklevel' to report the offender some layers further up in the
1510 stack.
1512 stack.
1511 """
1513 """
1512 if not self.configbool('devel', 'all-warnings'):
1514 if not self.configbool('devel', 'all-warnings'):
1513 if config is not None and not self.configbool('devel', config):
1515 if config is not None and not self.configbool('devel', config):
1514 return
1516 return
1515 msg = 'devel-warn: ' + msg
1517 msg = 'devel-warn: ' + msg
1516 stacklevel += 1 # get in develwarn
1518 stacklevel += 1 # get in develwarn
1517 if self.tracebackflag:
1519 if self.tracebackflag:
1518 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1520 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1519 self.log('develwarn', '%s at:\n%s' %
1521 self.log('develwarn', '%s at:\n%s' %
1520 (msg, ''.join(util.getstackframes(stacklevel))))
1522 (msg, ''.join(util.getstackframes(stacklevel))))
1521 else:
1523 else:
1522 curframe = inspect.currentframe()
1524 curframe = inspect.currentframe()
1523 calframe = inspect.getouterframes(curframe, 2)
1525 calframe = inspect.getouterframes(curframe, 2)
1524 self.write_err('%s at: %s:%s (%s)\n'
1526 self.write_err('%s at: %s:%s (%s)\n'
1525 % ((msg,) + calframe[stacklevel][1:4]))
1527 % ((msg,) + calframe[stacklevel][1:4]))
1526 self.log('develwarn', '%s at: %s:%s (%s)\n',
1528 self.log('develwarn', '%s at: %s:%s (%s)\n',
1527 msg, *calframe[stacklevel][1:4])
1529 msg, *calframe[stacklevel][1:4])
1528 curframe = calframe = None # avoid cycles
1530 curframe = calframe = None # avoid cycles
1529
1531
1530 def deprecwarn(self, msg, version):
1532 def deprecwarn(self, msg, version):
1531 """issue a deprecation warning
1533 """issue a deprecation warning
1532
1534
1533 - msg: message explaining what is deprecated and how to upgrade,
1535 - msg: message explaining what is deprecated and how to upgrade,
1534 - version: last version where the API will be supported,
1536 - version: last version where the API will be supported,
1535 """
1537 """
1536 if not (self.configbool('devel', 'all-warnings')
1538 if not (self.configbool('devel', 'all-warnings')
1537 or self.configbool('devel', 'deprec-warn')):
1539 or self.configbool('devel', 'deprec-warn')):
1538 return
1540 return
1539 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1541 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1540 " update your code.)") % version
1542 " update your code.)") % version
1541 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1543 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1542
1544
1543 def exportableenviron(self):
1545 def exportableenviron(self):
1544 """The environment variables that are safe to export, e.g. through
1546 """The environment variables that are safe to export, e.g. through
1545 hgweb.
1547 hgweb.
1546 """
1548 """
1547 return self._exportableenviron
1549 return self._exportableenviron
1548
1550
1549 @contextlib.contextmanager
1551 @contextlib.contextmanager
1550 def configoverride(self, overrides, source=""):
1552 def configoverride(self, overrides, source=""):
1551 """Context manager for temporary config overrides
1553 """Context manager for temporary config overrides
1552 `overrides` must be a dict of the following structure:
1554 `overrides` must be a dict of the following structure:
1553 {(section, name) : value}"""
1555 {(section, name) : value}"""
1554 backups = {}
1556 backups = {}
1555 try:
1557 try:
1556 for (section, name), value in overrides.items():
1558 for (section, name), value in overrides.items():
1557 backups[(section, name)] = self.backupconfig(section, name)
1559 backups[(section, name)] = self.backupconfig(section, name)
1558 self.setconfig(section, name, value, source)
1560 self.setconfig(section, name, value, source)
1559 yield
1561 yield
1560 finally:
1562 finally:
1561 for __, backup in backups.items():
1563 for __, backup in backups.items():
1562 self.restoreconfig(backup)
1564 self.restoreconfig(backup)
1563 # just restoring ui.quiet config to the previous value is not enough
1565 # just restoring ui.quiet config to the previous value is not enough
1564 # as it does not update ui.quiet class member
1566 # as it does not update ui.quiet class member
1565 if ('ui', 'quiet') in overrides:
1567 if ('ui', 'quiet') in overrides:
1566 self.fixconfig(section='ui')
1568 self.fixconfig(section='ui')
1567
1569
1568 class paths(dict):
1570 class paths(dict):
1569 """Represents a collection of paths and their configs.
1571 """Represents a collection of paths and their configs.
1570
1572
1571 Data is initially derived from ui instances and the config files they have
1573 Data is initially derived from ui instances and the config files they have
1572 loaded.
1574 loaded.
1573 """
1575 """
1574 def __init__(self, ui):
1576 def __init__(self, ui):
1575 dict.__init__(self)
1577 dict.__init__(self)
1576
1578
1577 for name, loc in ui.configitems('paths', ignoresub=True):
1579 for name, loc in ui.configitems('paths', ignoresub=True):
1578 # No location is the same as not existing.
1580 # No location is the same as not existing.
1579 if not loc:
1581 if not loc:
1580 continue
1582 continue
1581 loc, sub = ui.configsuboptions('paths', name)
1583 loc, sub = ui.configsuboptions('paths', name)
1582 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1584 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1583
1585
1584 def getpath(self, name, default=None):
1586 def getpath(self, name, default=None):
1585 """Return a ``path`` from a string, falling back to default.
1587 """Return a ``path`` from a string, falling back to default.
1586
1588
1587 ``name`` can be a named path or locations. Locations are filesystem
1589 ``name`` can be a named path or locations. Locations are filesystem
1588 paths or URIs.
1590 paths or URIs.
1589
1591
1590 Returns None if ``name`` is not a registered path, a URI, or a local
1592 Returns None if ``name`` is not a registered path, a URI, or a local
1591 path to a repo.
1593 path to a repo.
1592 """
1594 """
1593 # Only fall back to default if no path was requested.
1595 # Only fall back to default if no path was requested.
1594 if name is None:
1596 if name is None:
1595 if not default:
1597 if not default:
1596 default = ()
1598 default = ()
1597 elif not isinstance(default, (tuple, list)):
1599 elif not isinstance(default, (tuple, list)):
1598 default = (default,)
1600 default = (default,)
1599 for k in default:
1601 for k in default:
1600 try:
1602 try:
1601 return self[k]
1603 return self[k]
1602 except KeyError:
1604 except KeyError:
1603 continue
1605 continue
1604 return None
1606 return None
1605
1607
1606 # Most likely empty string.
1608 # Most likely empty string.
1607 # This may need to raise in the future.
1609 # This may need to raise in the future.
1608 if not name:
1610 if not name:
1609 return None
1611 return None
1610
1612
1611 try:
1613 try:
1612 return self[name]
1614 return self[name]
1613 except KeyError:
1615 except KeyError:
1614 # Try to resolve as a local path or URI.
1616 # Try to resolve as a local path or URI.
1615 try:
1617 try:
1616 # We don't pass sub-options in, so no need to pass ui instance.
1618 # We don't pass sub-options in, so no need to pass ui instance.
1617 return path(None, None, rawloc=name)
1619 return path(None, None, rawloc=name)
1618 except ValueError:
1620 except ValueError:
1619 raise error.RepoError(_('repository %s does not exist') %
1621 raise error.RepoError(_('repository %s does not exist') %
1620 name)
1622 name)
1621
1623
1622 _pathsuboptions = {}
1624 _pathsuboptions = {}
1623
1625
1624 def pathsuboption(option, attr):
1626 def pathsuboption(option, attr):
1625 """Decorator used to declare a path sub-option.
1627 """Decorator used to declare a path sub-option.
1626
1628
1627 Arguments are the sub-option name and the attribute it should set on
1629 Arguments are the sub-option name and the attribute it should set on
1628 ``path`` instances.
1630 ``path`` instances.
1629
1631
1630 The decorated function will receive as arguments a ``ui`` instance,
1632 The decorated function will receive as arguments a ``ui`` instance,
1631 ``path`` instance, and the string value of this option from the config.
1633 ``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``
1634 The function should return the value that will be set on the ``path``
1633 instance.
1635 instance.
1634
1636
1635 This decorator can be used to perform additional verification of
1637 This decorator can be used to perform additional verification of
1636 sub-options and to change the type of sub-options.
1638 sub-options and to change the type of sub-options.
1637 """
1639 """
1638 def register(func):
1640 def register(func):
1639 _pathsuboptions[option] = (attr, func)
1641 _pathsuboptions[option] = (attr, func)
1640 return func
1642 return func
1641 return register
1643 return register
1642
1644
1643 @pathsuboption('pushurl', 'pushloc')
1645 @pathsuboption('pushurl', 'pushloc')
1644 def pushurlpathoption(ui, path, value):
1646 def pushurlpathoption(ui, path, value):
1645 u = util.url(value)
1647 u = util.url(value)
1646 # Actually require a URL.
1648 # Actually require a URL.
1647 if not u.scheme:
1649 if not u.scheme:
1648 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1650 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1649 return None
1651 return None
1650
1652
1651 # Don't support the #foo syntax in the push URL to declare branch to
1653 # Don't support the #foo syntax in the push URL to declare branch to
1652 # push.
1654 # push.
1653 if u.fragment:
1655 if u.fragment:
1654 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1656 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1655 'ignoring)\n') % path.name)
1657 'ignoring)\n') % path.name)
1656 u.fragment = None
1658 u.fragment = None
1657
1659
1658 return str(u)
1660 return str(u)
1659
1661
1660 @pathsuboption('pushrev', 'pushrev')
1662 @pathsuboption('pushrev', 'pushrev')
1661 def pushrevpathoption(ui, path, value):
1663 def pushrevpathoption(ui, path, value):
1662 return value
1664 return value
1663
1665
1664 class path(object):
1666 class path(object):
1665 """Represents an individual path and its configuration."""
1667 """Represents an individual path and its configuration."""
1666
1668
1667 def __init__(self, ui, name, rawloc=None, suboptions=None):
1669 def __init__(self, ui, name, rawloc=None, suboptions=None):
1668 """Construct a path from its config options.
1670 """Construct a path from its config options.
1669
1671
1670 ``ui`` is the ``ui`` instance the path is coming from.
1672 ``ui`` is the ``ui`` instance the path is coming from.
1671 ``name`` is the symbolic name of the path.
1673 ``name`` is the symbolic name of the path.
1672 ``rawloc`` is the raw location, as defined in the config.
1674 ``rawloc`` is the raw location, as defined in the config.
1673 ``pushloc`` is the raw locations pushes should be made to.
1675 ``pushloc`` is the raw locations pushes should be made to.
1674
1676
1675 If ``name`` is not defined, we require that the location be a) a local
1677 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,
1678 filesystem path with a .hg directory or b) a URL. If not,
1677 ``ValueError`` is raised.
1679 ``ValueError`` is raised.
1678 """
1680 """
1679 if not rawloc:
1681 if not rawloc:
1680 raise ValueError('rawloc must be defined')
1682 raise ValueError('rawloc must be defined')
1681
1683
1682 # Locations may define branches via syntax <base>#<branch>.
1684 # Locations may define branches via syntax <base>#<branch>.
1683 u = util.url(rawloc)
1685 u = util.url(rawloc)
1684 branch = None
1686 branch = None
1685 if u.fragment:
1687 if u.fragment:
1686 branch = u.fragment
1688 branch = u.fragment
1687 u.fragment = None
1689 u.fragment = None
1688
1690
1689 self.url = u
1691 self.url = u
1690 self.branch = branch
1692 self.branch = branch
1691
1693
1692 self.name = name
1694 self.name = name
1693 self.rawloc = rawloc
1695 self.rawloc = rawloc
1694 self.loc = '%s' % u
1696 self.loc = '%s' % u
1695
1697
1696 # When given a raw location but not a symbolic name, validate the
1698 # When given a raw location but not a symbolic name, validate the
1697 # location is valid.
1699 # location is valid.
1698 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1700 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 '
1701 raise ValueError('location is not a URL or path to a local '
1700 'repo: %s' % rawloc)
1702 'repo: %s' % rawloc)
1701
1703
1702 suboptions = suboptions or {}
1704 suboptions = suboptions or {}
1703
1705
1704 # Now process the sub-options. If a sub-option is registered, its
1706 # 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
1707 # attribute will always be present. The value will be None if there
1706 # was no valid sub-option.
1708 # was no valid sub-option.
1707 for suboption, (attr, func) in _pathsuboptions.iteritems():
1709 for suboption, (attr, func) in _pathsuboptions.iteritems():
1708 if suboption not in suboptions:
1710 if suboption not in suboptions:
1709 setattr(self, attr, None)
1711 setattr(self, attr, None)
1710 continue
1712 continue
1711
1713
1712 value = func(ui, self, suboptions[suboption])
1714 value = func(ui, self, suboptions[suboption])
1713 setattr(self, attr, value)
1715 setattr(self, attr, value)
1714
1716
1715 def _isvalidlocalpath(self, path):
1717 def _isvalidlocalpath(self, path):
1716 """Returns True if the given path is a potentially valid repository.
1718 """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
1719 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
1720 'valid' in this case (like when pulling from a git repo into a hg
1719 one)."""
1721 one)."""
1720 return os.path.isdir(os.path.join(path, '.hg'))
1722 return os.path.isdir(os.path.join(path, '.hg'))
1721
1723
1722 @property
1724 @property
1723 def suboptions(self):
1725 def suboptions(self):
1724 """Return sub-options and their values for this path.
1726 """Return sub-options and their values for this path.
1725
1727
1726 This is intended to be used for presentation purposes.
1728 This is intended to be used for presentation purposes.
1727 """
1729 """
1728 d = {}
1730 d = {}
1729 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1731 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1730 value = getattr(self, attr)
1732 value = getattr(self, attr)
1731 if value is not None:
1733 if value is not None:
1732 d[subopt] = value
1734 d[subopt] = value
1733 return d
1735 return d
1734
1736
1735 # we instantiate one globally shared progress bar to avoid
1737 # we instantiate one globally shared progress bar to avoid
1736 # competing progress bars when multiple UI objects get created
1738 # competing progress bars when multiple UI objects get created
1737 _progresssingleton = None
1739 _progresssingleton = None
1738
1740
1739 def getprogbar(ui):
1741 def getprogbar(ui):
1740 global _progresssingleton
1742 global _progresssingleton
1741 if _progresssingleton is None:
1743 if _progresssingleton is None:
1742 # passing 'ui' object to the singleton is fishy,
1744 # passing 'ui' object to the singleton is fishy,
1743 # this is how the extension used to work but feel free to rework it.
1745 # this is how the extension used to work but feel free to rework it.
1744 _progresssingleton = progress.progbar(ui)
1746 _progresssingleton = progress.progbar(ui)
1745 return _progresssingleton
1747 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now