##// END OF EJS Templates
ui: swallow EBADF on stderr...
Kevin Bullock -
r16367:c14898df stable
parent child Browse files
Show More
@@ -1,743 +1,743
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 i18n import _
8 from i18n import _
9 import errno, getpass, os, socket, sys, tempfile, traceback
9 import errno, getpass, os, socket, sys, tempfile, traceback
10 import config, scmutil, util, error
10 import config, scmutil, util, error
11
11
12 class ui(object):
12 class ui(object):
13 def __init__(self, src=None):
13 def __init__(self, src=None):
14 self._buffers = []
14 self._buffers = []
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 self._reportuntrusted = True
16 self._reportuntrusted = True
17 self._ocfg = config.config() # overlay
17 self._ocfg = config.config() # overlay
18 self._tcfg = config.config() # trusted
18 self._tcfg = config.config() # trusted
19 self._ucfg = config.config() # untrusted
19 self._ucfg = config.config() # untrusted
20 self._trustusers = set()
20 self._trustusers = set()
21 self._trustgroups = set()
21 self._trustgroups = set()
22
22
23 if src:
23 if src:
24 self.fout = src.fout
24 self.fout = src.fout
25 self.ferr = src.ferr
25 self.ferr = src.ferr
26 self.fin = src.fin
26 self.fin = src.fin
27
27
28 self._tcfg = src._tcfg.copy()
28 self._tcfg = src._tcfg.copy()
29 self._ucfg = src._ucfg.copy()
29 self._ucfg = src._ucfg.copy()
30 self._ocfg = src._ocfg.copy()
30 self._ocfg = src._ocfg.copy()
31 self._trustusers = src._trustusers.copy()
31 self._trustusers = src._trustusers.copy()
32 self._trustgroups = src._trustgroups.copy()
32 self._trustgroups = src._trustgroups.copy()
33 self.environ = src.environ
33 self.environ = src.environ
34 self.fixconfig()
34 self.fixconfig()
35 else:
35 else:
36 self.fout = sys.stdout
36 self.fout = sys.stdout
37 self.ferr = sys.stderr
37 self.ferr = sys.stderr
38 self.fin = sys.stdin
38 self.fin = sys.stdin
39
39
40 # shared read-only environment
40 # shared read-only environment
41 self.environ = os.environ
41 self.environ = os.environ
42 # we always trust global config files
42 # we always trust global config files
43 for f in scmutil.rcpath():
43 for f in scmutil.rcpath():
44 self.readconfig(f, trust=True)
44 self.readconfig(f, trust=True)
45
45
46 def copy(self):
46 def copy(self):
47 return self.__class__(self)
47 return self.__class__(self)
48
48
49 def _trusted(self, fp, f):
49 def _trusted(self, fp, f):
50 st = util.fstat(fp)
50 st = util.fstat(fp)
51 if util.isowner(st):
51 if util.isowner(st):
52 return True
52 return True
53
53
54 tusers, tgroups = self._trustusers, self._trustgroups
54 tusers, tgroups = self._trustusers, self._trustgroups
55 if '*' in tusers or '*' in tgroups:
55 if '*' in tusers or '*' in tgroups:
56 return True
56 return True
57
57
58 user = util.username(st.st_uid)
58 user = util.username(st.st_uid)
59 group = util.groupname(st.st_gid)
59 group = util.groupname(st.st_gid)
60 if user in tusers or group in tgroups or user == util.username():
60 if user in tusers or group in tgroups or user == util.username():
61 return True
61 return True
62
62
63 if self._reportuntrusted:
63 if self._reportuntrusted:
64 self.warn(_('Not trusting file %s from untrusted '
64 self.warn(_('Not trusting file %s from untrusted '
65 'user %s, group %s\n') % (f, user, group))
65 'user %s, group %s\n') % (f, user, group))
66 return False
66 return False
67
67
68 def readconfig(self, filename, root=None, trust=False,
68 def readconfig(self, filename, root=None, trust=False,
69 sections=None, remap=None):
69 sections=None, remap=None):
70 try:
70 try:
71 fp = open(filename)
71 fp = open(filename)
72 except IOError:
72 except IOError:
73 if not sections: # ignore unless we were looking for something
73 if not sections: # ignore unless we were looking for something
74 return
74 return
75 raise
75 raise
76
76
77 cfg = config.config()
77 cfg = config.config()
78 trusted = sections or trust or self._trusted(fp, filename)
78 trusted = sections or trust or self._trusted(fp, filename)
79
79
80 try:
80 try:
81 cfg.read(filename, fp, sections=sections, remap=remap)
81 cfg.read(filename, fp, sections=sections, remap=remap)
82 fp.close()
82 fp.close()
83 except error.ConfigError, inst:
83 except error.ConfigError, inst:
84 if trusted:
84 if trusted:
85 raise
85 raise
86 self.warn(_("Ignored: %s\n") % str(inst))
86 self.warn(_("Ignored: %s\n") % str(inst))
87
87
88 if self.plain():
88 if self.plain():
89 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
89 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
90 'logtemplate', 'style',
90 'logtemplate', 'style',
91 'traceback', 'verbose'):
91 'traceback', 'verbose'):
92 if k in cfg['ui']:
92 if k in cfg['ui']:
93 del cfg['ui'][k]
93 del cfg['ui'][k]
94 for k, v in cfg.items('defaults'):
94 for k, v in cfg.items('defaults'):
95 del cfg['defaults'][k]
95 del cfg['defaults'][k]
96 # Don't remove aliases from the configuration if in the exceptionlist
96 # Don't remove aliases from the configuration if in the exceptionlist
97 if self.plain('alias'):
97 if self.plain('alias'):
98 for k, v in cfg.items('alias'):
98 for k, v in cfg.items('alias'):
99 del cfg['alias'][k]
99 del cfg['alias'][k]
100
100
101 if trusted:
101 if trusted:
102 self._tcfg.update(cfg)
102 self._tcfg.update(cfg)
103 self._tcfg.update(self._ocfg)
103 self._tcfg.update(self._ocfg)
104 self._ucfg.update(cfg)
104 self._ucfg.update(cfg)
105 self._ucfg.update(self._ocfg)
105 self._ucfg.update(self._ocfg)
106
106
107 if root is None:
107 if root is None:
108 root = os.path.expanduser('~')
108 root = os.path.expanduser('~')
109 self.fixconfig(root=root)
109 self.fixconfig(root=root)
110
110
111 def fixconfig(self, root=None, section=None):
111 def fixconfig(self, root=None, section=None):
112 if section in (None, 'paths'):
112 if section in (None, 'paths'):
113 # expand vars and ~
113 # expand vars and ~
114 # translate paths relative to root (or home) into absolute paths
114 # translate paths relative to root (or home) into absolute paths
115 root = root or os.getcwd()
115 root = root or os.getcwd()
116 for c in self._tcfg, self._ucfg, self._ocfg:
116 for c in self._tcfg, self._ucfg, self._ocfg:
117 for n, p in c.items('paths'):
117 for n, p in c.items('paths'):
118 if not p:
118 if not p:
119 continue
119 continue
120 if '%%' in p:
120 if '%%' in p:
121 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
121 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
122 % (n, p, self.configsource('paths', n)))
122 % (n, p, self.configsource('paths', n)))
123 p = p.replace('%%', '%')
123 p = p.replace('%%', '%')
124 p = util.expandpath(p)
124 p = util.expandpath(p)
125 if not util.hasscheme(p) and not os.path.isabs(p):
125 if not util.hasscheme(p) and not os.path.isabs(p):
126 p = os.path.normpath(os.path.join(root, p))
126 p = os.path.normpath(os.path.join(root, p))
127 c.set("paths", n, p)
127 c.set("paths", n, p)
128
128
129 if section in (None, 'ui'):
129 if section in (None, 'ui'):
130 # update ui options
130 # update ui options
131 self.debugflag = self.configbool('ui', 'debug')
131 self.debugflag = self.configbool('ui', 'debug')
132 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
132 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
133 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
133 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
134 if self.verbose and self.quiet:
134 if self.verbose and self.quiet:
135 self.quiet = self.verbose = False
135 self.quiet = self.verbose = False
136 self._reportuntrusted = self.debugflag or self.configbool("ui",
136 self._reportuntrusted = self.debugflag or self.configbool("ui",
137 "report_untrusted", True)
137 "report_untrusted", True)
138 self.tracebackflag = self.configbool('ui', 'traceback', False)
138 self.tracebackflag = self.configbool('ui', 'traceback', False)
139
139
140 if section in (None, 'trusted'):
140 if section in (None, 'trusted'):
141 # update trust information
141 # update trust information
142 self._trustusers.update(self.configlist('trusted', 'users'))
142 self._trustusers.update(self.configlist('trusted', 'users'))
143 self._trustgroups.update(self.configlist('trusted', 'groups'))
143 self._trustgroups.update(self.configlist('trusted', 'groups'))
144
144
145 def backupconfig(self, section, item):
145 def backupconfig(self, section, item):
146 return (self._ocfg.backup(section, item),
146 return (self._ocfg.backup(section, item),
147 self._tcfg.backup(section, item),
147 self._tcfg.backup(section, item),
148 self._ucfg.backup(section, item),)
148 self._ucfg.backup(section, item),)
149 def restoreconfig(self, data):
149 def restoreconfig(self, data):
150 self._ocfg.restore(data[0])
150 self._ocfg.restore(data[0])
151 self._tcfg.restore(data[1])
151 self._tcfg.restore(data[1])
152 self._ucfg.restore(data[2])
152 self._ucfg.restore(data[2])
153
153
154 def setconfig(self, section, name, value, overlay=True):
154 def setconfig(self, section, name, value, overlay=True):
155 if overlay:
155 if overlay:
156 self._ocfg.set(section, name, value)
156 self._ocfg.set(section, name, value)
157 self._tcfg.set(section, name, value)
157 self._tcfg.set(section, name, value)
158 self._ucfg.set(section, name, value)
158 self._ucfg.set(section, name, value)
159 self.fixconfig(section=section)
159 self.fixconfig(section=section)
160
160
161 def _data(self, untrusted):
161 def _data(self, untrusted):
162 return untrusted and self._ucfg or self._tcfg
162 return untrusted and self._ucfg or self._tcfg
163
163
164 def configsource(self, section, name, untrusted=False):
164 def configsource(self, section, name, untrusted=False):
165 return self._data(untrusted).source(section, name) or 'none'
165 return self._data(untrusted).source(section, name) or 'none'
166
166
167 def config(self, section, name, default=None, untrusted=False):
167 def config(self, section, name, default=None, untrusted=False):
168 if isinstance(name, list):
168 if isinstance(name, list):
169 alternates = name
169 alternates = name
170 else:
170 else:
171 alternates = [name]
171 alternates = [name]
172
172
173 for n in alternates:
173 for n in alternates:
174 value = self._data(untrusted).get(section, name, None)
174 value = self._data(untrusted).get(section, name, None)
175 if value is not None:
175 if value is not None:
176 name = n
176 name = n
177 break
177 break
178 else:
178 else:
179 value = default
179 value = default
180
180
181 if self.debugflag and not untrusted and self._reportuntrusted:
181 if self.debugflag and not untrusted and self._reportuntrusted:
182 uvalue = self._ucfg.get(section, name)
182 uvalue = self._ucfg.get(section, name)
183 if uvalue is not None and uvalue != value:
183 if uvalue is not None and uvalue != value:
184 self.debug("ignoring untrusted configuration option "
184 self.debug("ignoring untrusted configuration option "
185 "%s.%s = %s\n" % (section, name, uvalue))
185 "%s.%s = %s\n" % (section, name, uvalue))
186 return value
186 return value
187
187
188 def configpath(self, section, name, default=None, untrusted=False):
188 def configpath(self, section, name, default=None, untrusted=False):
189 'get a path config item, expanded relative to repo root or config file'
189 'get a path config item, expanded relative to repo root or config file'
190 v = self.config(section, name, default, untrusted)
190 v = self.config(section, name, default, untrusted)
191 if v is None:
191 if v is None:
192 return None
192 return None
193 if not os.path.isabs(v) or "://" not in v:
193 if not os.path.isabs(v) or "://" not in v:
194 src = self.configsource(section, name, untrusted)
194 src = self.configsource(section, name, untrusted)
195 if ':' in src:
195 if ':' in src:
196 base = os.path.dirname(src.rsplit(':')[0])
196 base = os.path.dirname(src.rsplit(':')[0])
197 v = os.path.join(base, os.path.expanduser(v))
197 v = os.path.join(base, os.path.expanduser(v))
198 return v
198 return v
199
199
200 def configbool(self, section, name, default=False, untrusted=False):
200 def configbool(self, section, name, default=False, untrusted=False):
201 """parse a configuration element as a boolean
201 """parse a configuration element as a boolean
202
202
203 >>> u = ui(); s = 'foo'
203 >>> u = ui(); s = 'foo'
204 >>> u.setconfig(s, 'true', 'yes')
204 >>> u.setconfig(s, 'true', 'yes')
205 >>> u.configbool(s, 'true')
205 >>> u.configbool(s, 'true')
206 True
206 True
207 >>> u.setconfig(s, 'false', 'no')
207 >>> u.setconfig(s, 'false', 'no')
208 >>> u.configbool(s, 'false')
208 >>> u.configbool(s, 'false')
209 False
209 False
210 >>> u.configbool(s, 'unknown')
210 >>> u.configbool(s, 'unknown')
211 False
211 False
212 >>> u.configbool(s, 'unknown', True)
212 >>> u.configbool(s, 'unknown', True)
213 True
213 True
214 >>> u.setconfig(s, 'invalid', 'somevalue')
214 >>> u.setconfig(s, 'invalid', 'somevalue')
215 >>> u.configbool(s, 'invalid')
215 >>> u.configbool(s, 'invalid')
216 Traceback (most recent call last):
216 Traceback (most recent call last):
217 ...
217 ...
218 ConfigError: foo.invalid is not a boolean ('somevalue')
218 ConfigError: foo.invalid is not a boolean ('somevalue')
219 """
219 """
220
220
221 v = self.config(section, name, None, untrusted)
221 v = self.config(section, name, None, untrusted)
222 if v is None:
222 if v is None:
223 return default
223 return default
224 if isinstance(v, bool):
224 if isinstance(v, bool):
225 return v
225 return v
226 b = util.parsebool(v)
226 b = util.parsebool(v)
227 if b is None:
227 if b is None:
228 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
228 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
229 % (section, name, v))
229 % (section, name, v))
230 return b
230 return b
231
231
232 def configint(self, section, name, default=None, untrusted=False):
232 def configint(self, section, name, default=None, untrusted=False):
233 """parse a configuration element as an integer
233 """parse a configuration element as an integer
234
234
235 >>> u = ui(); s = 'foo'
235 >>> u = ui(); s = 'foo'
236 >>> u.setconfig(s, 'int1', '42')
236 >>> u.setconfig(s, 'int1', '42')
237 >>> u.configint(s, 'int1')
237 >>> u.configint(s, 'int1')
238 42
238 42
239 >>> u.setconfig(s, 'int2', '-42')
239 >>> u.setconfig(s, 'int2', '-42')
240 >>> u.configint(s, 'int2')
240 >>> u.configint(s, 'int2')
241 -42
241 -42
242 >>> u.configint(s, 'unknown', 7)
242 >>> u.configint(s, 'unknown', 7)
243 7
243 7
244 >>> u.setconfig(s, 'invalid', 'somevalue')
244 >>> u.setconfig(s, 'invalid', 'somevalue')
245 >>> u.configint(s, 'invalid')
245 >>> u.configint(s, 'invalid')
246 Traceback (most recent call last):
246 Traceback (most recent call last):
247 ...
247 ...
248 ConfigError: foo.invalid is not an integer ('somevalue')
248 ConfigError: foo.invalid is not an integer ('somevalue')
249 """
249 """
250
250
251 v = self.config(section, name, None, untrusted)
251 v = self.config(section, name, None, untrusted)
252 if v is None:
252 if v is None:
253 return default
253 return default
254 try:
254 try:
255 return int(v)
255 return int(v)
256 except ValueError:
256 except ValueError:
257 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
257 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
258 % (section, name, v))
258 % (section, name, v))
259
259
260 def configlist(self, section, name, default=None, untrusted=False):
260 def configlist(self, section, name, default=None, untrusted=False):
261 """parse a configuration element as a list of comma/space separated
261 """parse a configuration element as a list of comma/space separated
262 strings
262 strings
263
263
264 >>> u = ui(); s = 'foo'
264 >>> u = ui(); s = 'foo'
265 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
265 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
266 >>> u.configlist(s, 'list1')
266 >>> u.configlist(s, 'list1')
267 ['this', 'is', 'a small', 'test']
267 ['this', 'is', 'a small', 'test']
268 """
268 """
269
269
270 def _parse_plain(parts, s, offset):
270 def _parse_plain(parts, s, offset):
271 whitespace = False
271 whitespace = False
272 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
272 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
273 whitespace = True
273 whitespace = True
274 offset += 1
274 offset += 1
275 if offset >= len(s):
275 if offset >= len(s):
276 return None, parts, offset
276 return None, parts, offset
277 if whitespace:
277 if whitespace:
278 parts.append('')
278 parts.append('')
279 if s[offset] == '"' and not parts[-1]:
279 if s[offset] == '"' and not parts[-1]:
280 return _parse_quote, parts, offset + 1
280 return _parse_quote, parts, offset + 1
281 elif s[offset] == '"' and parts[-1][-1] == '\\':
281 elif s[offset] == '"' and parts[-1][-1] == '\\':
282 parts[-1] = parts[-1][:-1] + s[offset]
282 parts[-1] = parts[-1][:-1] + s[offset]
283 return _parse_plain, parts, offset + 1
283 return _parse_plain, parts, offset + 1
284 parts[-1] += s[offset]
284 parts[-1] += s[offset]
285 return _parse_plain, parts, offset + 1
285 return _parse_plain, parts, offset + 1
286
286
287 def _parse_quote(parts, s, offset):
287 def _parse_quote(parts, s, offset):
288 if offset < len(s) and s[offset] == '"': # ""
288 if offset < len(s) and s[offset] == '"': # ""
289 parts.append('')
289 parts.append('')
290 offset += 1
290 offset += 1
291 while offset < len(s) and (s[offset].isspace() or
291 while offset < len(s) and (s[offset].isspace() or
292 s[offset] == ','):
292 s[offset] == ','):
293 offset += 1
293 offset += 1
294 return _parse_plain, parts, offset
294 return _parse_plain, parts, offset
295
295
296 while offset < len(s) and s[offset] != '"':
296 while offset < len(s) and s[offset] != '"':
297 if (s[offset] == '\\' and offset + 1 < len(s)
297 if (s[offset] == '\\' and offset + 1 < len(s)
298 and s[offset + 1] == '"'):
298 and s[offset + 1] == '"'):
299 offset += 1
299 offset += 1
300 parts[-1] += '"'
300 parts[-1] += '"'
301 else:
301 else:
302 parts[-1] += s[offset]
302 parts[-1] += s[offset]
303 offset += 1
303 offset += 1
304
304
305 if offset >= len(s):
305 if offset >= len(s):
306 real_parts = _configlist(parts[-1])
306 real_parts = _configlist(parts[-1])
307 if not real_parts:
307 if not real_parts:
308 parts[-1] = '"'
308 parts[-1] = '"'
309 else:
309 else:
310 real_parts[0] = '"' + real_parts[0]
310 real_parts[0] = '"' + real_parts[0]
311 parts = parts[:-1]
311 parts = parts[:-1]
312 parts.extend(real_parts)
312 parts.extend(real_parts)
313 return None, parts, offset
313 return None, parts, offset
314
314
315 offset += 1
315 offset += 1
316 while offset < len(s) and s[offset] in [' ', ',']:
316 while offset < len(s) and s[offset] in [' ', ',']:
317 offset += 1
317 offset += 1
318
318
319 if offset < len(s):
319 if offset < len(s):
320 if offset + 1 == len(s) and s[offset] == '"':
320 if offset + 1 == len(s) and s[offset] == '"':
321 parts[-1] += '"'
321 parts[-1] += '"'
322 offset += 1
322 offset += 1
323 else:
323 else:
324 parts.append('')
324 parts.append('')
325 else:
325 else:
326 return None, parts, offset
326 return None, parts, offset
327
327
328 return _parse_plain, parts, offset
328 return _parse_plain, parts, offset
329
329
330 def _configlist(s):
330 def _configlist(s):
331 s = s.rstrip(' ,')
331 s = s.rstrip(' ,')
332 if not s:
332 if not s:
333 return []
333 return []
334 parser, parts, offset = _parse_plain, [''], 0
334 parser, parts, offset = _parse_plain, [''], 0
335 while parser:
335 while parser:
336 parser, parts, offset = parser(parts, s, offset)
336 parser, parts, offset = parser(parts, s, offset)
337 return parts
337 return parts
338
338
339 result = self.config(section, name, untrusted=untrusted)
339 result = self.config(section, name, untrusted=untrusted)
340 if result is None:
340 if result is None:
341 result = default or []
341 result = default or []
342 if isinstance(result, basestring):
342 if isinstance(result, basestring):
343 result = _configlist(result.lstrip(' ,\n'))
343 result = _configlist(result.lstrip(' ,\n'))
344 if result is None:
344 if result is None:
345 result = default or []
345 result = default or []
346 return result
346 return result
347
347
348 def has_section(self, section, untrusted=False):
348 def has_section(self, section, untrusted=False):
349 '''tell whether section exists in config.'''
349 '''tell whether section exists in config.'''
350 return section in self._data(untrusted)
350 return section in self._data(untrusted)
351
351
352 def configitems(self, section, untrusted=False):
352 def configitems(self, section, untrusted=False):
353 items = self._data(untrusted).items(section)
353 items = self._data(untrusted).items(section)
354 if self.debugflag and not untrusted and self._reportuntrusted:
354 if self.debugflag and not untrusted and self._reportuntrusted:
355 for k, v in self._ucfg.items(section):
355 for k, v in self._ucfg.items(section):
356 if self._tcfg.get(section, k) != v:
356 if self._tcfg.get(section, k) != v:
357 self.debug("ignoring untrusted configuration option "
357 self.debug("ignoring untrusted configuration option "
358 "%s.%s = %s\n" % (section, k, v))
358 "%s.%s = %s\n" % (section, k, v))
359 return items
359 return items
360
360
361 def walkconfig(self, untrusted=False):
361 def walkconfig(self, untrusted=False):
362 cfg = self._data(untrusted)
362 cfg = self._data(untrusted)
363 for section in cfg.sections():
363 for section in cfg.sections():
364 for name, value in self.configitems(section, untrusted):
364 for name, value in self.configitems(section, untrusted):
365 yield section, name, value
365 yield section, name, value
366
366
367 def plain(self, feature=None):
367 def plain(self, feature=None):
368 '''is plain mode active?
368 '''is plain mode active?
369
369
370 Plain mode means that all configuration variables which affect
370 Plain mode means that all configuration variables which affect
371 the behavior and output of Mercurial should be
371 the behavior and output of Mercurial should be
372 ignored. Additionally, the output should be stable,
372 ignored. Additionally, the output should be stable,
373 reproducible and suitable for use in scripts or applications.
373 reproducible and suitable for use in scripts or applications.
374
374
375 The only way to trigger plain mode is by setting either the
375 The only way to trigger plain mode is by setting either the
376 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
376 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
377
377
378 The return value can either be
378 The return value can either be
379 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
379 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
380 - True otherwise
380 - True otherwise
381 '''
381 '''
382 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
382 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
383 return False
383 return False
384 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
384 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
385 if feature and exceptions:
385 if feature and exceptions:
386 return feature not in exceptions
386 return feature not in exceptions
387 return True
387 return True
388
388
389 def username(self):
389 def username(self):
390 """Return default username to be used in commits.
390 """Return default username to be used in commits.
391
391
392 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
392 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
393 and stop searching if one of these is set.
393 and stop searching if one of these is set.
394 If not found and ui.askusername is True, ask the user, else use
394 If not found and ui.askusername is True, ask the user, else use
395 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
395 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
396 """
396 """
397 user = os.environ.get("HGUSER")
397 user = os.environ.get("HGUSER")
398 if user is None:
398 if user is None:
399 user = self.config("ui", "username")
399 user = self.config("ui", "username")
400 if user is not None:
400 if user is not None:
401 user = os.path.expandvars(user)
401 user = os.path.expandvars(user)
402 if user is None:
402 if user is None:
403 user = os.environ.get("EMAIL")
403 user = os.environ.get("EMAIL")
404 if user is None and self.configbool("ui", "askusername"):
404 if user is None and self.configbool("ui", "askusername"):
405 user = self.prompt(_("enter a commit username:"), default=None)
405 user = self.prompt(_("enter a commit username:"), default=None)
406 if user is None and not self.interactive():
406 if user is None and not self.interactive():
407 try:
407 try:
408 user = '%s@%s' % (util.getuser(), socket.getfqdn())
408 user = '%s@%s' % (util.getuser(), socket.getfqdn())
409 self.warn(_("No username found, using '%s' instead\n") % user)
409 self.warn(_("No username found, using '%s' instead\n") % user)
410 except KeyError:
410 except KeyError:
411 pass
411 pass
412 if not user:
412 if not user:
413 raise util.Abort(_('no username supplied (see "hg help config")'))
413 raise util.Abort(_('no username supplied (see "hg help config")'))
414 if "\n" in user:
414 if "\n" in user:
415 raise util.Abort(_("username %s contains a newline\n") % repr(user))
415 raise util.Abort(_("username %s contains a newline\n") % repr(user))
416 return user
416 return user
417
417
418 def shortuser(self, user):
418 def shortuser(self, user):
419 """Return a short representation of a user name or email address."""
419 """Return a short representation of a user name or email address."""
420 if not self.verbose:
420 if not self.verbose:
421 user = util.shortuser(user)
421 user = util.shortuser(user)
422 return user
422 return user
423
423
424 def expandpath(self, loc, default=None):
424 def expandpath(self, loc, default=None):
425 """Return repository location relative to cwd or from [paths]"""
425 """Return repository location relative to cwd or from [paths]"""
426 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
426 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
427 return loc
427 return loc
428
428
429 path = self.config('paths', loc)
429 path = self.config('paths', loc)
430 if not path and default is not None:
430 if not path and default is not None:
431 path = self.config('paths', default)
431 path = self.config('paths', default)
432 return path or loc
432 return path or loc
433
433
434 def pushbuffer(self):
434 def pushbuffer(self):
435 self._buffers.append([])
435 self._buffers.append([])
436
436
437 def popbuffer(self, labeled=False):
437 def popbuffer(self, labeled=False):
438 '''pop the last buffer and return the buffered output
438 '''pop the last buffer and return the buffered output
439
439
440 If labeled is True, any labels associated with buffered
440 If labeled is True, any labels associated with buffered
441 output will be handled. By default, this has no effect
441 output will be handled. By default, this has no effect
442 on the output returned, but extensions and GUI tools may
442 on the output returned, but extensions and GUI tools may
443 handle this argument and returned styled output. If output
443 handle this argument and returned styled output. If output
444 is being buffered so it can be captured and parsed or
444 is being buffered so it can be captured and parsed or
445 processed, labeled should not be set to True.
445 processed, labeled should not be set to True.
446 '''
446 '''
447 return "".join(self._buffers.pop())
447 return "".join(self._buffers.pop())
448
448
449 def write(self, *args, **opts):
449 def write(self, *args, **opts):
450 '''write args to output
450 '''write args to output
451
451
452 By default, this method simply writes to the buffer or stdout,
452 By default, this method simply writes to the buffer or stdout,
453 but extensions or GUI tools may override this method,
453 but extensions or GUI tools may override this method,
454 write_err(), popbuffer(), and label() to style output from
454 write_err(), popbuffer(), and label() to style output from
455 various parts of hg.
455 various parts of hg.
456
456
457 An optional keyword argument, "label", can be passed in.
457 An optional keyword argument, "label", can be passed in.
458 This should be a string containing label names separated by
458 This should be a string containing label names separated by
459 space. Label names take the form of "topic.type". For example,
459 space. Label names take the form of "topic.type". For example,
460 ui.debug() issues a label of "ui.debug".
460 ui.debug() issues a label of "ui.debug".
461
461
462 When labeling output for a specific command, a label of
462 When labeling output for a specific command, a label of
463 "cmdname.type" is recommended. For example, status issues
463 "cmdname.type" is recommended. For example, status issues
464 a label of "status.modified" for modified files.
464 a label of "status.modified" for modified files.
465 '''
465 '''
466 if self._buffers:
466 if self._buffers:
467 self._buffers[-1].extend([str(a) for a in args])
467 self._buffers[-1].extend([str(a) for a in args])
468 else:
468 else:
469 for a in args:
469 for a in args:
470 self.fout.write(str(a))
470 self.fout.write(str(a))
471
471
472 def write_err(self, *args, **opts):
472 def write_err(self, *args, **opts):
473 try:
473 try:
474 if not getattr(self.fout, 'closed', False):
474 if not getattr(self.fout, 'closed', False):
475 self.fout.flush()
475 self.fout.flush()
476 for a in args:
476 for a in args:
477 self.ferr.write(str(a))
477 self.ferr.write(str(a))
478 # stderr may be buffered under win32 when redirected to files,
478 # stderr may be buffered under win32 when redirected to files,
479 # including stdout.
479 # including stdout.
480 if not getattr(self.ferr, 'closed', False):
480 if not getattr(self.ferr, 'closed', False):
481 self.ferr.flush()
481 self.ferr.flush()
482 except IOError, inst:
482 except IOError, inst:
483 if inst.errno not in (errno.EPIPE, errno.EIO):
483 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
484 raise
484 raise
485
485
486 def flush(self):
486 def flush(self):
487 try: self.fout.flush()
487 try: self.fout.flush()
488 except: pass
488 except: pass
489 try: self.ferr.flush()
489 try: self.ferr.flush()
490 except: pass
490 except: pass
491
491
492 def interactive(self):
492 def interactive(self):
493 '''is interactive input allowed?
493 '''is interactive input allowed?
494
494
495 An interactive session is a session where input can be reasonably read
495 An interactive session is a session where input can be reasonably read
496 from `sys.stdin'. If this function returns false, any attempt to read
496 from `sys.stdin'. If this function returns false, any attempt to read
497 from stdin should fail with an error, unless a sensible default has been
497 from stdin should fail with an error, unless a sensible default has been
498 specified.
498 specified.
499
499
500 Interactiveness is triggered by the value of the `ui.interactive'
500 Interactiveness is triggered by the value of the `ui.interactive'
501 configuration variable or - if it is unset - when `sys.stdin' points
501 configuration variable or - if it is unset - when `sys.stdin' points
502 to a terminal device.
502 to a terminal device.
503
503
504 This function refers to input only; for output, see `ui.formatted()'.
504 This function refers to input only; for output, see `ui.formatted()'.
505 '''
505 '''
506 i = self.configbool("ui", "interactive", None)
506 i = self.configbool("ui", "interactive", None)
507 if i is None:
507 if i is None:
508 # some environments replace stdin without implementing isatty
508 # some environments replace stdin without implementing isatty
509 # usually those are non-interactive
509 # usually those are non-interactive
510 return util.isatty(self.fin)
510 return util.isatty(self.fin)
511
511
512 return i
512 return i
513
513
514 def termwidth(self):
514 def termwidth(self):
515 '''how wide is the terminal in columns?
515 '''how wide is the terminal in columns?
516 '''
516 '''
517 if 'COLUMNS' in os.environ:
517 if 'COLUMNS' in os.environ:
518 try:
518 try:
519 return int(os.environ['COLUMNS'])
519 return int(os.environ['COLUMNS'])
520 except ValueError:
520 except ValueError:
521 pass
521 pass
522 return util.termwidth()
522 return util.termwidth()
523
523
524 def formatted(self):
524 def formatted(self):
525 '''should formatted output be used?
525 '''should formatted output be used?
526
526
527 It is often desirable to format the output to suite the output medium.
527 It is often desirable to format the output to suite the output medium.
528 Examples of this are truncating long lines or colorizing messages.
528 Examples of this are truncating long lines or colorizing messages.
529 However, this is not often not desirable when piping output into other
529 However, this is not often not desirable when piping output into other
530 utilities, e.g. `grep'.
530 utilities, e.g. `grep'.
531
531
532 Formatted output is triggered by the value of the `ui.formatted'
532 Formatted output is triggered by the value of the `ui.formatted'
533 configuration variable or - if it is unset - when `sys.stdout' points
533 configuration variable or - if it is unset - when `sys.stdout' points
534 to a terminal device. Please note that `ui.formatted' should be
534 to a terminal device. Please note that `ui.formatted' should be
535 considered an implementation detail; it is not intended for use outside
535 considered an implementation detail; it is not intended for use outside
536 Mercurial or its extensions.
536 Mercurial or its extensions.
537
537
538 This function refers to output only; for input, see `ui.interactive()'.
538 This function refers to output only; for input, see `ui.interactive()'.
539 This function always returns false when in plain mode, see `ui.plain()'.
539 This function always returns false when in plain mode, see `ui.plain()'.
540 '''
540 '''
541 if self.plain():
541 if self.plain():
542 return False
542 return False
543
543
544 i = self.configbool("ui", "formatted", None)
544 i = self.configbool("ui", "formatted", None)
545 if i is None:
545 if i is None:
546 # some environments replace stdout without implementing isatty
546 # some environments replace stdout without implementing isatty
547 # usually those are non-interactive
547 # usually those are non-interactive
548 return util.isatty(self.fout)
548 return util.isatty(self.fout)
549
549
550 return i
550 return i
551
551
552 def _readline(self, prompt=''):
552 def _readline(self, prompt=''):
553 if util.isatty(self.fin):
553 if util.isatty(self.fin):
554 try:
554 try:
555 # magically add command line editing support, where
555 # magically add command line editing support, where
556 # available
556 # available
557 import readline
557 import readline
558 # force demandimport to really load the module
558 # force demandimport to really load the module
559 readline.read_history_file
559 readline.read_history_file
560 # windows sometimes raises something other than ImportError
560 # windows sometimes raises something other than ImportError
561 except Exception:
561 except Exception:
562 pass
562 pass
563
563
564 # call write() so output goes through subclassed implementation
564 # call write() so output goes through subclassed implementation
565 # e.g. color extension on Windows
565 # e.g. color extension on Windows
566 self.write(prompt)
566 self.write(prompt)
567
567
568 # instead of trying to emulate raw_input, swap (self.fin,
568 # instead of trying to emulate raw_input, swap (self.fin,
569 # self.fout) with (sys.stdin, sys.stdout)
569 # self.fout) with (sys.stdin, sys.stdout)
570 oldin = sys.stdin
570 oldin = sys.stdin
571 oldout = sys.stdout
571 oldout = sys.stdout
572 sys.stdin = self.fin
572 sys.stdin = self.fin
573 sys.stdout = self.fout
573 sys.stdout = self.fout
574 line = raw_input(' ')
574 line = raw_input(' ')
575 sys.stdin = oldin
575 sys.stdin = oldin
576 sys.stdout = oldout
576 sys.stdout = oldout
577
577
578 # When stdin is in binary mode on Windows, it can cause
578 # When stdin is in binary mode on Windows, it can cause
579 # raw_input() to emit an extra trailing carriage return
579 # raw_input() to emit an extra trailing carriage return
580 if os.linesep == '\r\n' and line and line[-1] == '\r':
580 if os.linesep == '\r\n' and line and line[-1] == '\r':
581 line = line[:-1]
581 line = line[:-1]
582 return line
582 return line
583
583
584 def prompt(self, msg, default="y"):
584 def prompt(self, msg, default="y"):
585 """Prompt user with msg, read response.
585 """Prompt user with msg, read response.
586 If ui is not interactive, the default is returned.
586 If ui is not interactive, the default is returned.
587 """
587 """
588 if not self.interactive():
588 if not self.interactive():
589 self.write(msg, ' ', default, "\n")
589 self.write(msg, ' ', default, "\n")
590 return default
590 return default
591 try:
591 try:
592 r = self._readline(self.label(msg, 'ui.prompt'))
592 r = self._readline(self.label(msg, 'ui.prompt'))
593 if not r:
593 if not r:
594 return default
594 return default
595 return r
595 return r
596 except EOFError:
596 except EOFError:
597 raise util.Abort(_('response expected'))
597 raise util.Abort(_('response expected'))
598
598
599 def promptchoice(self, msg, choices, default=0):
599 def promptchoice(self, msg, choices, default=0):
600 """Prompt user with msg, read response, and ensure it matches
600 """Prompt user with msg, read response, and ensure it matches
601 one of the provided choices. The index of the choice is returned.
601 one of the provided choices. The index of the choice is returned.
602 choices is a sequence of acceptable responses with the format:
602 choices is a sequence of acceptable responses with the format:
603 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
603 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
604 If ui is not interactive, the default is returned.
604 If ui is not interactive, the default is returned.
605 """
605 """
606 resps = [s[s.index('&')+1].lower() for s in choices]
606 resps = [s[s.index('&')+1].lower() for s in choices]
607 while True:
607 while True:
608 r = self.prompt(msg, resps[default])
608 r = self.prompt(msg, resps[default])
609 if r.lower() in resps:
609 if r.lower() in resps:
610 return resps.index(r.lower())
610 return resps.index(r.lower())
611 self.write(_("unrecognized response\n"))
611 self.write(_("unrecognized response\n"))
612
612
613 def getpass(self, prompt=None, default=None):
613 def getpass(self, prompt=None, default=None):
614 if not self.interactive():
614 if not self.interactive():
615 return default
615 return default
616 try:
616 try:
617 return getpass.getpass(prompt or _('password: '))
617 return getpass.getpass(prompt or _('password: '))
618 except EOFError:
618 except EOFError:
619 raise util.Abort(_('response expected'))
619 raise util.Abort(_('response expected'))
620 def status(self, *msg, **opts):
620 def status(self, *msg, **opts):
621 '''write status message to output (if ui.quiet is False)
621 '''write status message to output (if ui.quiet is False)
622
622
623 This adds an output label of "ui.status".
623 This adds an output label of "ui.status".
624 '''
624 '''
625 if not self.quiet:
625 if not self.quiet:
626 opts['label'] = opts.get('label', '') + ' ui.status'
626 opts['label'] = opts.get('label', '') + ' ui.status'
627 self.write(*msg, **opts)
627 self.write(*msg, **opts)
628 def warn(self, *msg, **opts):
628 def warn(self, *msg, **opts):
629 '''write warning message to output (stderr)
629 '''write warning message to output (stderr)
630
630
631 This adds an output label of "ui.warning".
631 This adds an output label of "ui.warning".
632 '''
632 '''
633 opts['label'] = opts.get('label', '') + ' ui.warning'
633 opts['label'] = opts.get('label', '') + ' ui.warning'
634 self.write_err(*msg, **opts)
634 self.write_err(*msg, **opts)
635 def note(self, *msg, **opts):
635 def note(self, *msg, **opts):
636 '''write note to output (if ui.verbose is True)
636 '''write note to output (if ui.verbose is True)
637
637
638 This adds an output label of "ui.note".
638 This adds an output label of "ui.note".
639 '''
639 '''
640 if self.verbose:
640 if self.verbose:
641 opts['label'] = opts.get('label', '') + ' ui.note'
641 opts['label'] = opts.get('label', '') + ' ui.note'
642 self.write(*msg, **opts)
642 self.write(*msg, **opts)
643 def debug(self, *msg, **opts):
643 def debug(self, *msg, **opts):
644 '''write debug message to output (if ui.debugflag is True)
644 '''write debug message to output (if ui.debugflag is True)
645
645
646 This adds an output label of "ui.debug".
646 This adds an output label of "ui.debug".
647 '''
647 '''
648 if self.debugflag:
648 if self.debugflag:
649 opts['label'] = opts.get('label', '') + ' ui.debug'
649 opts['label'] = opts.get('label', '') + ' ui.debug'
650 self.write(*msg, **opts)
650 self.write(*msg, **opts)
651 def edit(self, text, user):
651 def edit(self, text, user):
652 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
652 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
653 text=True)
653 text=True)
654 try:
654 try:
655 f = os.fdopen(fd, "w")
655 f = os.fdopen(fd, "w")
656 f.write(text)
656 f.write(text)
657 f.close()
657 f.close()
658
658
659 editor = self.geteditor()
659 editor = self.geteditor()
660
660
661 util.system("%s \"%s\"" % (editor, name),
661 util.system("%s \"%s\"" % (editor, name),
662 environ={'HGUSER': user},
662 environ={'HGUSER': user},
663 onerr=util.Abort, errprefix=_("edit failed"),
663 onerr=util.Abort, errprefix=_("edit failed"),
664 out=self.fout)
664 out=self.fout)
665
665
666 f = open(name)
666 f = open(name)
667 t = f.read()
667 t = f.read()
668 f.close()
668 f.close()
669 finally:
669 finally:
670 os.unlink(name)
670 os.unlink(name)
671
671
672 return t
672 return t
673
673
674 def traceback(self, exc=None):
674 def traceback(self, exc=None):
675 '''print exception traceback if traceback printing enabled.
675 '''print exception traceback if traceback printing enabled.
676 only to call in exception handler. returns true if traceback
676 only to call in exception handler. returns true if traceback
677 printed.'''
677 printed.'''
678 if self.tracebackflag:
678 if self.tracebackflag:
679 if exc:
679 if exc:
680 traceback.print_exception(exc[0], exc[1], exc[2], file=self.ferr)
680 traceback.print_exception(exc[0], exc[1], exc[2], file=self.ferr)
681 else:
681 else:
682 traceback.print_exc(file=self.ferr)
682 traceback.print_exc(file=self.ferr)
683 return self.tracebackflag
683 return self.tracebackflag
684
684
685 def geteditor(self):
685 def geteditor(self):
686 '''return editor to use'''
686 '''return editor to use'''
687 return (os.environ.get("HGEDITOR") or
687 return (os.environ.get("HGEDITOR") or
688 self.config("ui", "editor") or
688 self.config("ui", "editor") or
689 os.environ.get("VISUAL") or
689 os.environ.get("VISUAL") or
690 os.environ.get("EDITOR", "vi"))
690 os.environ.get("EDITOR", "vi"))
691
691
692 def progress(self, topic, pos, item="", unit="", total=None):
692 def progress(self, topic, pos, item="", unit="", total=None):
693 '''show a progress message
693 '''show a progress message
694
694
695 With stock hg, this is simply a debug message that is hidden
695 With stock hg, this is simply a debug message that is hidden
696 by default, but with extensions or GUI tools it may be
696 by default, but with extensions or GUI tools it may be
697 visible. 'topic' is the current operation, 'item' is a
697 visible. 'topic' is the current operation, 'item' is a
698 non-numeric marker of the current position (ie the currently
698 non-numeric marker of the current position (ie the currently
699 in-process file), 'pos' is the current numeric position (ie
699 in-process file), 'pos' is the current numeric position (ie
700 revision, bytes, etc.), unit is a corresponding unit label,
700 revision, bytes, etc.), unit is a corresponding unit label,
701 and total is the highest expected pos.
701 and total is the highest expected pos.
702
702
703 Multiple nested topics may be active at a time.
703 Multiple nested topics may be active at a time.
704
704
705 All topics should be marked closed by setting pos to None at
705 All topics should be marked closed by setting pos to None at
706 termination.
706 termination.
707 '''
707 '''
708
708
709 if pos is None or not self.debugflag:
709 if pos is None or not self.debugflag:
710 return
710 return
711
711
712 if unit:
712 if unit:
713 unit = ' ' + unit
713 unit = ' ' + unit
714 if item:
714 if item:
715 item = ' ' + item
715 item = ' ' + item
716
716
717 if total:
717 if total:
718 pct = 100.0 * pos / total
718 pct = 100.0 * pos / total
719 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
719 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
720 % (topic, item, pos, total, unit, pct))
720 % (topic, item, pos, total, unit, pct))
721 else:
721 else:
722 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
722 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
723
723
724 def log(self, service, message):
724 def log(self, service, message):
725 '''hook for logging facility extensions
725 '''hook for logging facility extensions
726
726
727 service should be a readily-identifiable subsystem, which will
727 service should be a readily-identifiable subsystem, which will
728 allow filtering.
728 allow filtering.
729 message should be a newline-terminated string to log.
729 message should be a newline-terminated string to log.
730 '''
730 '''
731 pass
731 pass
732
732
733 def label(self, msg, label):
733 def label(self, msg, label):
734 '''style msg based on supplied label
734 '''style msg based on supplied label
735
735
736 Like ui.write(), this just returns msg unchanged, but extensions
736 Like ui.write(), this just returns msg unchanged, but extensions
737 and GUI tools can override it to allow styling output without
737 and GUI tools can override it to allow styling output without
738 writing it.
738 writing it.
739
739
740 ui.write(s, 'label') is equivalent to
740 ui.write(s, 'label') is equivalent to
741 ui.write(ui.label(s, 'label')).
741 ui.write(ui.label(s, 'label')).
742 '''
742 '''
743 return msg
743 return msg
General Comments 0
You need to be logged in to leave comments. Login now