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