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