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