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