##// END OF EJS Templates
ui: privatize cdata vars
Matt Mackall -
r8203:3377fa11 default
parent child Browse files
Show More
@@ -1,350 +1,350 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, re, socket, sys, tempfile
9 import errno, getpass, os, re, socket, sys, tempfile
10 import config, traceback, util, error
10 import config, traceback, util, error
11
11
12 _booleans = {'1':True, 'yes':True, 'true':True, 'on':True,
12 _booleans = {'1':True, 'yes':True, 'true':True, 'on':True,
13 '0':False, 'no':False, 'false':False, 'off':False}
13 '0':False, 'no':False, 'false':False, 'off':False}
14
14
15 class ui(object):
15 class ui(object):
16 def __init__(self, src=None):
16 def __init__(self, src=None):
17 self._buffers = []
17 self._buffers = []
18 self.quiet = self.verbose = self.debugflag = self.traceback = False
18 self.quiet = self.verbose = self.debugflag = self.traceback = False
19 self.interactive = self.report_untrusted = True
19 self.interactive = self.report_untrusted = True
20 self.overlay = config.config()
20 self._ocfg = config.config() # overlay
21 self.cdata = config.config()
21 self._tcfg = config.config() # trusted
22 self.ucdata = config.config()
22 self._ucfg = config.config() # untrusted
23 self._trustusers = {}
23 self._trustusers = {}
24 self._trustgroups = {}
24 self._trustgroups = {}
25
25
26 if src:
26 if src:
27 self.cdata = src.cdata.copy()
27 self._tcfg = src._tcfg.copy()
28 self.ucdata = src.ucdata.copy()
28 self._ucfg = src._ucfg.copy()
29 self.overlay = src.overlay.copy()
29 self._ocfg = src._ocfg.copy()
30 self._trustusers = src._trustusers.copy()
30 self._trustusers = src._trustusers.copy()
31 self._trustgroups = src._trustgroups.copy()
31 self._trustgroups = src._trustgroups.copy()
32 self.fixconfig()
32 self.fixconfig()
33 else:
33 else:
34 # we always trust global config files
34 # we always trust global config files
35 for f in util.rcpath():
35 for f in util.rcpath():
36 self.readconfig(f, trust=True)
36 self.readconfig(f, trust=True)
37 def copy(self):
37 def copy(self):
38 return ui(self)
38 return ui(self)
39
39
40 _isatty = None
40 _isatty = None
41 def isatty(self):
41 def isatty(self):
42 if ui._isatty is None:
42 if ui._isatty is None:
43 try:
43 try:
44 ui._isatty = sys.stdin.isatty()
44 ui._isatty = sys.stdin.isatty()
45 except AttributeError: # not a real file object
45 except AttributeError: # not a real file object
46 ui._isatty = False
46 ui._isatty = False
47 except IOError:
47 except IOError:
48 # access to stdin is unsafe in a WSGI environment
48 # access to stdin is unsafe in a WSGI environment
49 ui._isatty = False
49 ui._isatty = False
50 return ui._isatty
50 return ui._isatty
51
51
52 def _is_trusted(self, fp, f):
52 def _is_trusted(self, fp, f):
53 st = util.fstat(fp)
53 st = util.fstat(fp)
54 if util.isowner(fp, st):
54 if util.isowner(fp, st):
55 return True
55 return True
56
56
57 tusers, tgroups = self._trustusers, self._trustgroups
57 tusers, tgroups = self._trustusers, self._trustgroups
58 if '*' in tusers or '*' in tgroups:
58 if '*' in tusers or '*' in tgroups:
59 return True
59 return True
60
60
61 user = util.username(st.st_uid)
61 user = util.username(st.st_uid)
62 group = util.groupname(st.st_gid)
62 group = util.groupname(st.st_gid)
63 if user in tusers or group in tgroups or user == util.username():
63 if user in tusers or group in tgroups or user == util.username():
64 return True
64 return True
65
65
66 if self.report_untrusted:
66 if self.report_untrusted:
67 self.warn(_('Not trusting file %s from untrusted '
67 self.warn(_('Not trusting file %s from untrusted '
68 'user %s, group %s\n') % (f, user, group))
68 'user %s, group %s\n') % (f, user, group))
69 return False
69 return False
70
70
71 def readconfig(self, filename, root=None, trust=False,
71 def readconfig(self, filename, root=None, trust=False,
72 sections = None):
72 sections = None):
73 try:
73 try:
74 fp = open(filename)
74 fp = open(filename)
75 except IOError:
75 except IOError:
76 if not sections: # ignore unless we were looking for something
76 if not sections: # ignore unless we were looking for something
77 return
77 return
78 raise
78 raise
79
79
80 cdata = config.config()
80 cfg = config.config()
81 trusted = sections or trust or self._is_trusted(fp, filename)
81 trusted = sections or trust or self._is_trusted(fp, filename)
82
82
83 try:
83 try:
84 cdata.read(filename, fp, sections=sections)
84 cfg.read(filename, fp, sections=sections)
85 except error.ConfigError, inst:
85 except error.ConfigError, inst:
86 if trusted:
86 if trusted:
87 raise
87 raise
88 self.warn(_("Ignored: %s\n") % str(inst))
88 self.warn(_("Ignored: %s\n") % str(inst))
89
89
90 if trusted:
90 if trusted:
91 self.cdata.update(cdata)
91 self._tcfg.update(cfg)
92 self.cdata.update(self.overlay)
92 self._tcfg.update(self._ocfg)
93 self.ucdata.update(cdata)
93 self._ucfg.update(cfg)
94 self.ucdata.update(self.overlay)
94 self._ucfg.update(self._ocfg)
95
95
96 if root is None:
96 if root is None:
97 root = os.path.expanduser('~')
97 root = os.path.expanduser('~')
98 self.fixconfig(root=root)
98 self.fixconfig(root=root)
99
99
100 def fixconfig(self, root=None):
100 def fixconfig(self, root=None):
101 # translate paths relative to root (or home) into absolute paths
101 # translate paths relative to root (or home) into absolute paths
102 root = root or os.getcwd()
102 root = root or os.getcwd()
103 for c in self.cdata, self.ucdata, self.overlay:
103 for c in self._tcfg, self._ucfg, self._ocfg:
104 for n, p in c.items('paths'):
104 for n, p in c.items('paths'):
105 if p and "://" not in p and not os.path.isabs(p):
105 if p and "://" not in p and not os.path.isabs(p):
106 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
106 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
107
107
108 # update ui options
108 # update ui options
109 self.debugflag = self.configbool('ui', 'debug')
109 self.debugflag = self.configbool('ui', 'debug')
110 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
110 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
111 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
111 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
112 if self.verbose and self.quiet:
112 if self.verbose and self.quiet:
113 self.quiet = self.verbose = False
113 self.quiet = self.verbose = False
114 self.report_untrusted = self.configbool("ui", "report_untrusted", True)
114 self.report_untrusted = self.configbool("ui", "report_untrusted", True)
115 self.interactive = self.configbool("ui", "interactive", self.isatty())
115 self.interactive = self.configbool("ui", "interactive", self.isatty())
116 self.traceback = self.configbool('ui', 'traceback', False)
116 self.traceback = self.configbool('ui', 'traceback', False)
117
117
118 # update trust information
118 # update trust information
119 for user in self.configlist('trusted', 'users'):
119 for user in self.configlist('trusted', 'users'):
120 self._trustusers[user] = 1
120 self._trustusers[user] = 1
121 for group in self.configlist('trusted', 'groups'):
121 for group in self.configlist('trusted', 'groups'):
122 self._trustgroups[group] = 1
122 self._trustgroups[group] = 1
123
123
124 def setconfig(self, section, name, value):
124 def setconfig(self, section, name, value):
125 for cdata in (self.overlay, self.cdata, self.ucdata):
125 for cfg in (self._ocfg, self._tcfg, self._ucfg):
126 cdata.set(section, name, value)
126 cfg.set(section, name, value)
127 self.fixconfig()
127 self.fixconfig()
128
128
129 def _data(self, untrusted):
129 def _data(self, untrusted):
130 return untrusted and self.ucdata or self.cdata
130 return untrusted and self._ucfg or self._tcfg
131
131
132 def configsource(self, section, name, untrusted=False):
132 def configsource(self, section, name, untrusted=False):
133 return self._data(untrusted).source(section, name) or 'none'
133 return self._data(untrusted).source(section, name) or 'none'
134
134
135 def config(self, section, name, default=None, untrusted=False):
135 def config(self, section, name, default=None, untrusted=False):
136 value = self._data(untrusted).get(section, name, default)
136 value = self._data(untrusted).get(section, name, default)
137 if self.debugflag and not untrusted:
137 if self.debugflag and not untrusted:
138 uvalue = self.ucdata.get(section, name)
138 uvalue = self._ucfg.get(section, name)
139 if uvalue is not None and uvalue != value:
139 if uvalue is not None and uvalue != value:
140 self.warn(_("Ignoring untrusted configuration option "
140 self.warn(_("Ignoring untrusted configuration option "
141 "%s.%s = %s\n") % (section, name, uvalue))
141 "%s.%s = %s\n") % (section, name, uvalue))
142 return value
142 return value
143
143
144 def configbool(self, section, name, default=False, untrusted=False):
144 def configbool(self, section, name, default=False, untrusted=False):
145 v = self.config(section, name, None, untrusted)
145 v = self.config(section, name, None, untrusted)
146 if v == None:
146 if v == None:
147 return default
147 return default
148 if v.lower() not in _booleans:
148 if v.lower() not in _booleans:
149 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
149 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
150 % (section, name, v))
150 % (section, name, v))
151 return _booleans[v.lower()]
151 return _booleans[v.lower()]
152
152
153 def configlist(self, section, name, default=None, untrusted=False):
153 def configlist(self, section, name, default=None, untrusted=False):
154 """Return a list of comma/space separated strings"""
154 """Return a list of comma/space separated strings"""
155 result = self.config(section, name, untrusted=untrusted)
155 result = self.config(section, name, untrusted=untrusted)
156 if result is None:
156 if result is None:
157 result = default or []
157 result = default or []
158 if isinstance(result, basestring):
158 if isinstance(result, basestring):
159 result = result.replace(",", " ").split()
159 result = result.replace(",", " ").split()
160 return result
160 return result
161
161
162 def has_section(self, section, untrusted=False):
162 def has_section(self, section, untrusted=False):
163 '''tell whether section exists in config.'''
163 '''tell whether section exists in config.'''
164 return section in self._data(untrusted)
164 return section in self._data(untrusted)
165
165
166 def configitems(self, section, untrusted=False):
166 def configitems(self, section, untrusted=False):
167 items = self._data(untrusted).items(section)
167 items = self._data(untrusted).items(section)
168 if self.debugflag and not untrusted:
168 if self.debugflag and not untrusted:
169 for k,v in self.ucdata.items(section):
169 for k,v in self._ucfg.items(section):
170 if self.cdata.get(section, k) != v:
170 if self._tcfg.get(section, k) != v:
171 self.warn(_("Ignoring untrusted configuration option "
171 self.warn(_("Ignoring untrusted configuration option "
172 "%s.%s = %s\n") % (section, k, v))
172 "%s.%s = %s\n") % (section, k, v))
173 return items
173 return items
174
174
175 def walkconfig(self, untrusted=False):
175 def walkconfig(self, untrusted=False):
176 cdata = self._data(untrusted)
176 cfg = self._data(untrusted)
177 for section in cdata.sections():
177 for section in cfg.sections():
178 for name, value in self.configitems(section, untrusted):
178 for name, value in self.configitems(section, untrusted):
179 yield section, name, str(value).replace('\n', '\\n')
179 yield section, name, str(value).replace('\n', '\\n')
180
180
181 def username(self):
181 def username(self):
182 """Return default username to be used in commits.
182 """Return default username to be used in commits.
183
183
184 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
184 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
185 and stop searching if one of these is set.
185 and stop searching if one of these is set.
186 If not found and ui.askusername is True, ask the user, else use
186 If not found and ui.askusername is True, ask the user, else use
187 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
187 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
188 """
188 """
189 user = os.environ.get("HGUSER")
189 user = os.environ.get("HGUSER")
190 if user is None:
190 if user is None:
191 user = self.config("ui", "username")
191 user = self.config("ui", "username")
192 if user is None:
192 if user is None:
193 user = os.environ.get("EMAIL")
193 user = os.environ.get("EMAIL")
194 if user is None and self.configbool("ui", "askusername"):
194 if user is None and self.configbool("ui", "askusername"):
195 user = self.prompt(_("enter a commit username:"), default=None)
195 user = self.prompt(_("enter a commit username:"), default=None)
196 if user is None:
196 if user is None:
197 try:
197 try:
198 user = '%s@%s' % (util.getuser(), socket.getfqdn())
198 user = '%s@%s' % (util.getuser(), socket.getfqdn())
199 self.warn(_("No username found, using '%s' instead\n") % user)
199 self.warn(_("No username found, using '%s' instead\n") % user)
200 except KeyError:
200 except KeyError:
201 pass
201 pass
202 if not user:
202 if not user:
203 raise util.Abort(_("Please specify a username."))
203 raise util.Abort(_("Please specify a username."))
204 if "\n" in user:
204 if "\n" in user:
205 raise util.Abort(_("username %s contains a newline\n") % repr(user))
205 raise util.Abort(_("username %s contains a newline\n") % repr(user))
206 return user
206 return user
207
207
208 def shortuser(self, user):
208 def shortuser(self, user):
209 """Return a short representation of a user name or email address."""
209 """Return a short representation of a user name or email address."""
210 if not self.verbose: user = util.shortuser(user)
210 if not self.verbose: user = util.shortuser(user)
211 return user
211 return user
212
212
213 def _path(self, loc):
213 def _path(self, loc):
214 p = self.config('paths', loc)
214 p = self.config('paths', loc)
215 if p and '%%' in p:
215 if p and '%%' in p:
216 ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' %
216 ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' %
217 (loc, p, self.configsource('paths', loc)))
217 (loc, p, self.configsource('paths', loc)))
218 p = p.replace('%%', '%')
218 p = p.replace('%%', '%')
219 return p
219 return p
220
220
221 def expandpath(self, loc, default=None):
221 def expandpath(self, loc, default=None):
222 """Return repository location relative to cwd or from [paths]"""
222 """Return repository location relative to cwd or from [paths]"""
223 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
223 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
224 return loc
224 return loc
225
225
226 path = self._path(loc)
226 path = self._path(loc)
227 if not path and default is not None:
227 if not path and default is not None:
228 path = self._path(default)
228 path = self._path(default)
229 return path or loc
229 return path or loc
230
230
231 def pushbuffer(self):
231 def pushbuffer(self):
232 self._buffers.append([])
232 self._buffers.append([])
233
233
234 def popbuffer(self):
234 def popbuffer(self):
235 return "".join(self._buffers.pop())
235 return "".join(self._buffers.pop())
236
236
237 def write(self, *args):
237 def write(self, *args):
238 if self._buffers:
238 if self._buffers:
239 self._buffers[-1].extend([str(a) for a in args])
239 self._buffers[-1].extend([str(a) for a in args])
240 else:
240 else:
241 for a in args:
241 for a in args:
242 sys.stdout.write(str(a))
242 sys.stdout.write(str(a))
243
243
244 def write_err(self, *args):
244 def write_err(self, *args):
245 try:
245 try:
246 if not sys.stdout.closed: sys.stdout.flush()
246 if not sys.stdout.closed: sys.stdout.flush()
247 for a in args:
247 for a in args:
248 sys.stderr.write(str(a))
248 sys.stderr.write(str(a))
249 # stderr may be buffered under win32 when redirected to files,
249 # stderr may be buffered under win32 when redirected to files,
250 # including stdout.
250 # including stdout.
251 if not sys.stderr.closed: sys.stderr.flush()
251 if not sys.stderr.closed: sys.stderr.flush()
252 except IOError, inst:
252 except IOError, inst:
253 if inst.errno != errno.EPIPE:
253 if inst.errno != errno.EPIPE:
254 raise
254 raise
255
255
256 def flush(self):
256 def flush(self):
257 try: sys.stdout.flush()
257 try: sys.stdout.flush()
258 except: pass
258 except: pass
259 try: sys.stderr.flush()
259 try: sys.stderr.flush()
260 except: pass
260 except: pass
261
261
262 def _readline(self, prompt=''):
262 def _readline(self, prompt=''):
263 if self.isatty():
263 if self.isatty():
264 try:
264 try:
265 # magically add command line editing support, where
265 # magically add command line editing support, where
266 # available
266 # available
267 import readline
267 import readline
268 # force demandimport to really load the module
268 # force demandimport to really load the module
269 readline.read_history_file
269 readline.read_history_file
270 # windows sometimes raises something other than ImportError
270 # windows sometimes raises something other than ImportError
271 except Exception:
271 except Exception:
272 pass
272 pass
273 line = raw_input(prompt)
273 line = raw_input(prompt)
274 # When stdin is in binary mode on Windows, it can cause
274 # When stdin is in binary mode on Windows, it can cause
275 # raw_input() to emit an extra trailing carriage return
275 # raw_input() to emit an extra trailing carriage return
276 if os.linesep == '\r\n' and line and line[-1] == '\r':
276 if os.linesep == '\r\n' and line and line[-1] == '\r':
277 line = line[:-1]
277 line = line[:-1]
278 return line
278 return line
279
279
280 def prompt(self, msg, pat=None, default="y"):
280 def prompt(self, msg, pat=None, default="y"):
281 """Prompt user with msg, read response, and ensure it matches pat
281 """Prompt user with msg, read response, and ensure it matches pat
282
282
283 If not interactive -- the default is returned
283 If not interactive -- the default is returned
284 """
284 """
285 if not self.interactive:
285 if not self.interactive:
286 self.note(msg, ' ', default, "\n")
286 self.note(msg, ' ', default, "\n")
287 return default
287 return default
288 while True:
288 while True:
289 try:
289 try:
290 r = self._readline(msg + ' ')
290 r = self._readline(msg + ' ')
291 if not r:
291 if not r:
292 return default
292 return default
293 if not pat or re.match(pat, r):
293 if not pat or re.match(pat, r):
294 return r
294 return r
295 else:
295 else:
296 self.write(_("unrecognized response\n"))
296 self.write(_("unrecognized response\n"))
297 except EOFError:
297 except EOFError:
298 raise util.Abort(_('response expected'))
298 raise util.Abort(_('response expected'))
299
299
300 def getpass(self, prompt=None, default=None):
300 def getpass(self, prompt=None, default=None):
301 if not self.interactive: return default
301 if not self.interactive: return default
302 try:
302 try:
303 return getpass.getpass(prompt or _('password: '))
303 return getpass.getpass(prompt or _('password: '))
304 except EOFError:
304 except EOFError:
305 raise util.Abort(_('response expected'))
305 raise util.Abort(_('response expected'))
306 def status(self, *msg):
306 def status(self, *msg):
307 if not self.quiet: self.write(*msg)
307 if not self.quiet: self.write(*msg)
308 def warn(self, *msg):
308 def warn(self, *msg):
309 self.write_err(*msg)
309 self.write_err(*msg)
310 def note(self, *msg):
310 def note(self, *msg):
311 if self.verbose: self.write(*msg)
311 if self.verbose: self.write(*msg)
312 def debug(self, *msg):
312 def debug(self, *msg):
313 if self.debugflag: self.write(*msg)
313 if self.debugflag: self.write(*msg)
314 def edit(self, text, user):
314 def edit(self, text, user):
315 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
315 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
316 text=True)
316 text=True)
317 try:
317 try:
318 f = os.fdopen(fd, "w")
318 f = os.fdopen(fd, "w")
319 f.write(text)
319 f.write(text)
320 f.close()
320 f.close()
321
321
322 editor = self.geteditor()
322 editor = self.geteditor()
323
323
324 util.system("%s \"%s\"" % (editor, name),
324 util.system("%s \"%s\"" % (editor, name),
325 environ={'HGUSER': user},
325 environ={'HGUSER': user},
326 onerr=util.Abort, errprefix=_("edit failed"))
326 onerr=util.Abort, errprefix=_("edit failed"))
327
327
328 f = open(name)
328 f = open(name)
329 t = f.read()
329 t = f.read()
330 f.close()
330 f.close()
331 t = re.sub("(?m)^HG:.*\n", "", t)
331 t = re.sub("(?m)^HG:.*\n", "", t)
332 finally:
332 finally:
333 os.unlink(name)
333 os.unlink(name)
334
334
335 return t
335 return t
336
336
337 def print_exc(self):
337 def print_exc(self):
338 '''print exception traceback if traceback printing enabled.
338 '''print exception traceback if traceback printing enabled.
339 only to call in exception handler. returns true if traceback
339 only to call in exception handler. returns true if traceback
340 printed.'''
340 printed.'''
341 if self.traceback:
341 if self.traceback:
342 traceback.print_exc()
342 traceback.print_exc()
343 return self.traceback
343 return self.traceback
344
344
345 def geteditor(self):
345 def geteditor(self):
346 '''return editor to use'''
346 '''return editor to use'''
347 return (os.environ.get("HGEDITOR") or
347 return (os.environ.get("HGEDITOR") or
348 self.config("ui", "editor") or
348 self.config("ui", "editor") or
349 os.environ.get("VISUAL") or
349 os.environ.get("VISUAL") or
350 os.environ.get("EDITOR", "vi"))
350 os.environ.get("EDITOR", "vi"))
General Comments 0
You need to be logged in to leave comments. Login now