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