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