##// END OF EJS Templates
ui: try to handle $$ more robustly in prompts (issue4970)
Matt Mackall -
r27392:00aa37c6 stable
parent child Browse files
Show More
@@ -1,1131 +1,1145 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import getpass
11 import getpass
12 import inspect
12 import inspect
13 import os
13 import os
14 import re
14 import socket
15 import socket
15 import sys
16 import sys
16 import tempfile
17 import tempfile
17 import traceback
18 import traceback
18
19
19 from .i18n import _
20 from .i18n import _
20 from .node import hex
21 from .node import hex
21
22
22 from . import (
23 from . import (
23 config,
24 config,
24 error,
25 error,
25 formatter,
26 formatter,
26 progress,
27 progress,
27 scmutil,
28 scmutil,
28 util,
29 util,
29 )
30 )
30
31
31 samplehgrcs = {
32 samplehgrcs = {
32 'user':
33 'user':
33 """# example user config (see "hg help config" for more info)
34 """# example user config (see "hg help config" for more info)
34 [ui]
35 [ui]
35 # name and email, e.g.
36 # name and email, e.g.
36 # username = Jane Doe <jdoe@example.com>
37 # username = Jane Doe <jdoe@example.com>
37 username =
38 username =
38
39
39 [extensions]
40 [extensions]
40 # uncomment these lines to enable some popular extensions
41 # uncomment these lines to enable some popular extensions
41 # (see "hg help extensions" for more info)
42 # (see "hg help extensions" for more info)
42 #
43 #
43 # pager =
44 # pager =
44 # progress =
45 # progress =
45 # color =""",
46 # color =""",
46
47
47 'cloned':
48 'cloned':
48 """# example repository config (see "hg help config" for more info)
49 """# example repository config (see "hg help config" for more info)
49 [paths]
50 [paths]
50 default = %s
51 default = %s
51
52
52 # path aliases to other clones of this repo in URLs or filesystem paths
53 # path aliases to other clones of this repo in URLs or filesystem paths
53 # (see "hg help config.paths" for more info)
54 # (see "hg help config.paths" for more info)
54 #
55 #
55 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
56 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
56 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
57 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
57 # my-clone = /home/jdoe/jdoes-clone
58 # my-clone = /home/jdoe/jdoes-clone
58
59
59 [ui]
60 [ui]
60 # name and email (local to this repository, optional), e.g.
61 # name and email (local to this repository, optional), e.g.
61 # username = Jane Doe <jdoe@example.com>
62 # username = Jane Doe <jdoe@example.com>
62 """,
63 """,
63
64
64 'local':
65 'local':
65 """# example repository config (see "hg help config" for more info)
66 """# example repository config (see "hg help config" for more info)
66 [paths]
67 [paths]
67 # path aliases to other clones of this repo in URLs or filesystem paths
68 # path aliases to other clones of this repo in URLs or filesystem paths
68 # (see "hg help config.paths" for more info)
69 # (see "hg help config.paths" for more info)
69 #
70 #
70 # default = http://example.com/hg/example-repo
71 # default = http://example.com/hg/example-repo
71 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
72 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 # my-clone = /home/jdoe/jdoes-clone
74 # my-clone = /home/jdoe/jdoes-clone
74
75
75 [ui]
76 [ui]
76 # name and email (local to this repository, optional), e.g.
77 # name and email (local to this repository, optional), e.g.
77 # username = Jane Doe <jdoe@example.com>
78 # username = Jane Doe <jdoe@example.com>
78 """,
79 """,
79
80
80 'global':
81 'global':
81 """# example system-wide hg config (see "hg help config" for more info)
82 """# example system-wide hg config (see "hg help config" for more info)
82
83
83 [extensions]
84 [extensions]
84 # uncomment these lines to enable some popular extensions
85 # uncomment these lines to enable some popular extensions
85 # (see "hg help extensions" for more info)
86 # (see "hg help extensions" for more info)
86 #
87 #
87 # blackbox =
88 # blackbox =
88 # progress =
89 # progress =
89 # color =
90 # color =
90 # pager =""",
91 # pager =""",
91 }
92 }
92
93
93 class ui(object):
94 class ui(object):
94 def __init__(self, src=None):
95 def __init__(self, src=None):
95 # _buffers: used for temporary capture of output
96 # _buffers: used for temporary capture of output
96 self._buffers = []
97 self._buffers = []
97 # _bufferstates:
98 # _bufferstates:
98 # should the temporary capture include stderr and subprocess output
99 # should the temporary capture include stderr and subprocess output
99 self._bufferstates = []
100 self._bufferstates = []
100 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
101 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
101 self._reportuntrusted = True
102 self._reportuntrusted = True
102 self._ocfg = config.config() # overlay
103 self._ocfg = config.config() # overlay
103 self._tcfg = config.config() # trusted
104 self._tcfg = config.config() # trusted
104 self._ucfg = config.config() # untrusted
105 self._ucfg = config.config() # untrusted
105 self._trustusers = set()
106 self._trustusers = set()
106 self._trustgroups = set()
107 self._trustgroups = set()
107 self.callhooks = True
108 self.callhooks = True
108
109
109 if src:
110 if src:
110 self.fout = src.fout
111 self.fout = src.fout
111 self.ferr = src.ferr
112 self.ferr = src.ferr
112 self.fin = src.fin
113 self.fin = src.fin
113
114
114 self._tcfg = src._tcfg.copy()
115 self._tcfg = src._tcfg.copy()
115 self._ucfg = src._ucfg.copy()
116 self._ucfg = src._ucfg.copy()
116 self._ocfg = src._ocfg.copy()
117 self._ocfg = src._ocfg.copy()
117 self._trustusers = src._trustusers.copy()
118 self._trustusers = src._trustusers.copy()
118 self._trustgroups = src._trustgroups.copy()
119 self._trustgroups = src._trustgroups.copy()
119 self.environ = src.environ
120 self.environ = src.environ
120 self.callhooks = src.callhooks
121 self.callhooks = src.callhooks
121 self.fixconfig()
122 self.fixconfig()
122 else:
123 else:
123 self.fout = sys.stdout
124 self.fout = sys.stdout
124 self.ferr = sys.stderr
125 self.ferr = sys.stderr
125 self.fin = sys.stdin
126 self.fin = sys.stdin
126
127
127 # shared read-only environment
128 # shared read-only environment
128 self.environ = os.environ
129 self.environ = os.environ
129 # we always trust global config files
130 # we always trust global config files
130 for f in scmutil.rcpath():
131 for f in scmutil.rcpath():
131 self.readconfig(f, trust=True)
132 self.readconfig(f, trust=True)
132
133
133 def copy(self):
134 def copy(self):
134 return self.__class__(self)
135 return self.__class__(self)
135
136
136 def formatter(self, topic, opts):
137 def formatter(self, topic, opts):
137 return formatter.formatter(self, topic, opts)
138 return formatter.formatter(self, topic, opts)
138
139
139 def _trusted(self, fp, f):
140 def _trusted(self, fp, f):
140 st = util.fstat(fp)
141 st = util.fstat(fp)
141 if util.isowner(st):
142 if util.isowner(st):
142 return True
143 return True
143
144
144 tusers, tgroups = self._trustusers, self._trustgroups
145 tusers, tgroups = self._trustusers, self._trustgroups
145 if '*' in tusers or '*' in tgroups:
146 if '*' in tusers or '*' in tgroups:
146 return True
147 return True
147
148
148 user = util.username(st.st_uid)
149 user = util.username(st.st_uid)
149 group = util.groupname(st.st_gid)
150 group = util.groupname(st.st_gid)
150 if user in tusers or group in tgroups or user == util.username():
151 if user in tusers or group in tgroups or user == util.username():
151 return True
152 return True
152
153
153 if self._reportuntrusted:
154 if self._reportuntrusted:
154 self.warn(_('not trusting file %s from untrusted '
155 self.warn(_('not trusting file %s from untrusted '
155 'user %s, group %s\n') % (f, user, group))
156 'user %s, group %s\n') % (f, user, group))
156 return False
157 return False
157
158
158 def readconfig(self, filename, root=None, trust=False,
159 def readconfig(self, filename, root=None, trust=False,
159 sections=None, remap=None):
160 sections=None, remap=None):
160 try:
161 try:
161 fp = open(filename)
162 fp = open(filename)
162 except IOError:
163 except IOError:
163 if not sections: # ignore unless we were looking for something
164 if not sections: # ignore unless we were looking for something
164 return
165 return
165 raise
166 raise
166
167
167 cfg = config.config()
168 cfg = config.config()
168 trusted = sections or trust or self._trusted(fp, filename)
169 trusted = sections or trust or self._trusted(fp, filename)
169
170
170 try:
171 try:
171 cfg.read(filename, fp, sections=sections, remap=remap)
172 cfg.read(filename, fp, sections=sections, remap=remap)
172 fp.close()
173 fp.close()
173 except error.ConfigError as inst:
174 except error.ConfigError as inst:
174 if trusted:
175 if trusted:
175 raise
176 raise
176 self.warn(_("ignored: %s\n") % str(inst))
177 self.warn(_("ignored: %s\n") % str(inst))
177
178
178 if self.plain():
179 if self.plain():
179 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
180 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
180 'logtemplate', 'statuscopies', 'style',
181 'logtemplate', 'statuscopies', 'style',
181 'traceback', 'verbose'):
182 'traceback', 'verbose'):
182 if k in cfg['ui']:
183 if k in cfg['ui']:
183 del cfg['ui'][k]
184 del cfg['ui'][k]
184 for k, v in cfg.items('defaults'):
185 for k, v in cfg.items('defaults'):
185 del cfg['defaults'][k]
186 del cfg['defaults'][k]
186 # Don't remove aliases from the configuration if in the exceptionlist
187 # Don't remove aliases from the configuration if in the exceptionlist
187 if self.plain('alias'):
188 if self.plain('alias'):
188 for k, v in cfg.items('alias'):
189 for k, v in cfg.items('alias'):
189 del cfg['alias'][k]
190 del cfg['alias'][k]
190 if self.plain('revsetalias'):
191 if self.plain('revsetalias'):
191 for k, v in cfg.items('revsetalias'):
192 for k, v in cfg.items('revsetalias'):
192 del cfg['revsetalias'][k]
193 del cfg['revsetalias'][k]
193
194
194 if trusted:
195 if trusted:
195 self._tcfg.update(cfg)
196 self._tcfg.update(cfg)
196 self._tcfg.update(self._ocfg)
197 self._tcfg.update(self._ocfg)
197 self._ucfg.update(cfg)
198 self._ucfg.update(cfg)
198 self._ucfg.update(self._ocfg)
199 self._ucfg.update(self._ocfg)
199
200
200 if root is None:
201 if root is None:
201 root = os.path.expanduser('~')
202 root = os.path.expanduser('~')
202 self.fixconfig(root=root)
203 self.fixconfig(root=root)
203
204
204 def fixconfig(self, root=None, section=None):
205 def fixconfig(self, root=None, section=None):
205 if section in (None, 'paths'):
206 if section in (None, 'paths'):
206 # expand vars and ~
207 # expand vars and ~
207 # translate paths relative to root (or home) into absolute paths
208 # translate paths relative to root (or home) into absolute paths
208 root = root or os.getcwd()
209 root = root or os.getcwd()
209 for c in self._tcfg, self._ucfg, self._ocfg:
210 for c in self._tcfg, self._ucfg, self._ocfg:
210 for n, p in c.items('paths'):
211 for n, p in c.items('paths'):
211 if not p:
212 if not p:
212 continue
213 continue
213 if '%%' in p:
214 if '%%' in p:
214 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
215 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
215 % (n, p, self.configsource('paths', n)))
216 % (n, p, self.configsource('paths', n)))
216 p = p.replace('%%', '%')
217 p = p.replace('%%', '%')
217 p = util.expandpath(p)
218 p = util.expandpath(p)
218 if not util.hasscheme(p) and not os.path.isabs(p):
219 if not util.hasscheme(p) and not os.path.isabs(p):
219 p = os.path.normpath(os.path.join(root, p))
220 p = os.path.normpath(os.path.join(root, p))
220 c.set("paths", n, p)
221 c.set("paths", n, p)
221
222
222 if section in (None, 'ui'):
223 if section in (None, 'ui'):
223 # update ui options
224 # update ui options
224 self.debugflag = self.configbool('ui', 'debug')
225 self.debugflag = self.configbool('ui', 'debug')
225 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
226 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
226 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
227 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
227 if self.verbose and self.quiet:
228 if self.verbose and self.quiet:
228 self.quiet = self.verbose = False
229 self.quiet = self.verbose = False
229 self._reportuntrusted = self.debugflag or self.configbool("ui",
230 self._reportuntrusted = self.debugflag or self.configbool("ui",
230 "report_untrusted", True)
231 "report_untrusted", True)
231 self.tracebackflag = self.configbool('ui', 'traceback', False)
232 self.tracebackflag = self.configbool('ui', 'traceback', False)
232
233
233 if section in (None, 'trusted'):
234 if section in (None, 'trusted'):
234 # update trust information
235 # update trust information
235 self._trustusers.update(self.configlist('trusted', 'users'))
236 self._trustusers.update(self.configlist('trusted', 'users'))
236 self._trustgroups.update(self.configlist('trusted', 'groups'))
237 self._trustgroups.update(self.configlist('trusted', 'groups'))
237
238
238 def backupconfig(self, section, item):
239 def backupconfig(self, section, item):
239 return (self._ocfg.backup(section, item),
240 return (self._ocfg.backup(section, item),
240 self._tcfg.backup(section, item),
241 self._tcfg.backup(section, item),
241 self._ucfg.backup(section, item),)
242 self._ucfg.backup(section, item),)
242 def restoreconfig(self, data):
243 def restoreconfig(self, data):
243 self._ocfg.restore(data[0])
244 self._ocfg.restore(data[0])
244 self._tcfg.restore(data[1])
245 self._tcfg.restore(data[1])
245 self._ucfg.restore(data[2])
246 self._ucfg.restore(data[2])
246
247
247 def setconfig(self, section, name, value, source=''):
248 def setconfig(self, section, name, value, source=''):
248 for cfg in (self._ocfg, self._tcfg, self._ucfg):
249 for cfg in (self._ocfg, self._tcfg, self._ucfg):
249 cfg.set(section, name, value, source)
250 cfg.set(section, name, value, source)
250 self.fixconfig(section=section)
251 self.fixconfig(section=section)
251
252
252 def _data(self, untrusted):
253 def _data(self, untrusted):
253 return untrusted and self._ucfg or self._tcfg
254 return untrusted and self._ucfg or self._tcfg
254
255
255 def configsource(self, section, name, untrusted=False):
256 def configsource(self, section, name, untrusted=False):
256 return self._data(untrusted).source(section, name) or 'none'
257 return self._data(untrusted).source(section, name) or 'none'
257
258
258 def config(self, section, name, default=None, untrusted=False):
259 def config(self, section, name, default=None, untrusted=False):
259 if isinstance(name, list):
260 if isinstance(name, list):
260 alternates = name
261 alternates = name
261 else:
262 else:
262 alternates = [name]
263 alternates = [name]
263
264
264 for n in alternates:
265 for n in alternates:
265 value = self._data(untrusted).get(section, n, None)
266 value = self._data(untrusted).get(section, n, None)
266 if value is not None:
267 if value is not None:
267 name = n
268 name = n
268 break
269 break
269 else:
270 else:
270 value = default
271 value = default
271
272
272 if self.debugflag and not untrusted and self._reportuntrusted:
273 if self.debugflag and not untrusted and self._reportuntrusted:
273 for n in alternates:
274 for n in alternates:
274 uvalue = self._ucfg.get(section, n)
275 uvalue = self._ucfg.get(section, n)
275 if uvalue is not None and uvalue != value:
276 if uvalue is not None and uvalue != value:
276 self.debug("ignoring untrusted configuration option "
277 self.debug("ignoring untrusted configuration option "
277 "%s.%s = %s\n" % (section, n, uvalue))
278 "%s.%s = %s\n" % (section, n, uvalue))
278 return value
279 return value
279
280
280 def configpath(self, section, name, default=None, untrusted=False):
281 def configpath(self, section, name, default=None, untrusted=False):
281 'get a path config item, expanded relative to repo root or config file'
282 'get a path config item, expanded relative to repo root or config file'
282 v = self.config(section, name, default, untrusted)
283 v = self.config(section, name, default, untrusted)
283 if v is None:
284 if v is None:
284 return None
285 return None
285 if not os.path.isabs(v) or "://" not in v:
286 if not os.path.isabs(v) or "://" not in v:
286 src = self.configsource(section, name, untrusted)
287 src = self.configsource(section, name, untrusted)
287 if ':' in src:
288 if ':' in src:
288 base = os.path.dirname(src.rsplit(':')[0])
289 base = os.path.dirname(src.rsplit(':')[0])
289 v = os.path.join(base, os.path.expanduser(v))
290 v = os.path.join(base, os.path.expanduser(v))
290 return v
291 return v
291
292
292 def configbool(self, section, name, default=False, untrusted=False):
293 def configbool(self, section, name, default=False, untrusted=False):
293 """parse a configuration element as a boolean
294 """parse a configuration element as a boolean
294
295
295 >>> u = ui(); s = 'foo'
296 >>> u = ui(); s = 'foo'
296 >>> u.setconfig(s, 'true', 'yes')
297 >>> u.setconfig(s, 'true', 'yes')
297 >>> u.configbool(s, 'true')
298 >>> u.configbool(s, 'true')
298 True
299 True
299 >>> u.setconfig(s, 'false', 'no')
300 >>> u.setconfig(s, 'false', 'no')
300 >>> u.configbool(s, 'false')
301 >>> u.configbool(s, 'false')
301 False
302 False
302 >>> u.configbool(s, 'unknown')
303 >>> u.configbool(s, 'unknown')
303 False
304 False
304 >>> u.configbool(s, 'unknown', True)
305 >>> u.configbool(s, 'unknown', True)
305 True
306 True
306 >>> u.setconfig(s, 'invalid', 'somevalue')
307 >>> u.setconfig(s, 'invalid', 'somevalue')
307 >>> u.configbool(s, 'invalid')
308 >>> u.configbool(s, 'invalid')
308 Traceback (most recent call last):
309 Traceback (most recent call last):
309 ...
310 ...
310 ConfigError: foo.invalid is not a boolean ('somevalue')
311 ConfigError: foo.invalid is not a boolean ('somevalue')
311 """
312 """
312
313
313 v = self.config(section, name, None, untrusted)
314 v = self.config(section, name, None, untrusted)
314 if v is None:
315 if v is None:
315 return default
316 return default
316 if isinstance(v, bool):
317 if isinstance(v, bool):
317 return v
318 return v
318 b = util.parsebool(v)
319 b = util.parsebool(v)
319 if b is None:
320 if b is None:
320 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
321 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
321 % (section, name, v))
322 % (section, name, v))
322 return b
323 return b
323
324
324 def configint(self, section, name, default=None, untrusted=False):
325 def configint(self, section, name, default=None, untrusted=False):
325 """parse a configuration element as an integer
326 """parse a configuration element as an integer
326
327
327 >>> u = ui(); s = 'foo'
328 >>> u = ui(); s = 'foo'
328 >>> u.setconfig(s, 'int1', '42')
329 >>> u.setconfig(s, 'int1', '42')
329 >>> u.configint(s, 'int1')
330 >>> u.configint(s, 'int1')
330 42
331 42
331 >>> u.setconfig(s, 'int2', '-42')
332 >>> u.setconfig(s, 'int2', '-42')
332 >>> u.configint(s, 'int2')
333 >>> u.configint(s, 'int2')
333 -42
334 -42
334 >>> u.configint(s, 'unknown', 7)
335 >>> u.configint(s, 'unknown', 7)
335 7
336 7
336 >>> u.setconfig(s, 'invalid', 'somevalue')
337 >>> u.setconfig(s, 'invalid', 'somevalue')
337 >>> u.configint(s, 'invalid')
338 >>> u.configint(s, 'invalid')
338 Traceback (most recent call last):
339 Traceback (most recent call last):
339 ...
340 ...
340 ConfigError: foo.invalid is not an integer ('somevalue')
341 ConfigError: foo.invalid is not an integer ('somevalue')
341 """
342 """
342
343
343 v = self.config(section, name, None, untrusted)
344 v = self.config(section, name, None, untrusted)
344 if v is None:
345 if v is None:
345 return default
346 return default
346 try:
347 try:
347 return int(v)
348 return int(v)
348 except ValueError:
349 except ValueError:
349 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
350 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
350 % (section, name, v))
351 % (section, name, v))
351
352
352 def configbytes(self, section, name, default=0, untrusted=False):
353 def configbytes(self, section, name, default=0, untrusted=False):
353 """parse a configuration element as a quantity in bytes
354 """parse a configuration element as a quantity in bytes
354
355
355 Units can be specified as b (bytes), k or kb (kilobytes), m or
356 Units can be specified as b (bytes), k or kb (kilobytes), m or
356 mb (megabytes), g or gb (gigabytes).
357 mb (megabytes), g or gb (gigabytes).
357
358
358 >>> u = ui(); s = 'foo'
359 >>> u = ui(); s = 'foo'
359 >>> u.setconfig(s, 'val1', '42')
360 >>> u.setconfig(s, 'val1', '42')
360 >>> u.configbytes(s, 'val1')
361 >>> u.configbytes(s, 'val1')
361 42
362 42
362 >>> u.setconfig(s, 'val2', '42.5 kb')
363 >>> u.setconfig(s, 'val2', '42.5 kb')
363 >>> u.configbytes(s, 'val2')
364 >>> u.configbytes(s, 'val2')
364 43520
365 43520
365 >>> u.configbytes(s, 'unknown', '7 MB')
366 >>> u.configbytes(s, 'unknown', '7 MB')
366 7340032
367 7340032
367 >>> u.setconfig(s, 'invalid', 'somevalue')
368 >>> u.setconfig(s, 'invalid', 'somevalue')
368 >>> u.configbytes(s, 'invalid')
369 >>> u.configbytes(s, 'invalid')
369 Traceback (most recent call last):
370 Traceback (most recent call last):
370 ...
371 ...
371 ConfigError: foo.invalid is not a byte quantity ('somevalue')
372 ConfigError: foo.invalid is not a byte quantity ('somevalue')
372 """
373 """
373
374
374 value = self.config(section, name)
375 value = self.config(section, name)
375 if value is None:
376 if value is None:
376 if not isinstance(default, str):
377 if not isinstance(default, str):
377 return default
378 return default
378 value = default
379 value = default
379 try:
380 try:
380 return util.sizetoint(value)
381 return util.sizetoint(value)
381 except error.ParseError:
382 except error.ParseError:
382 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
383 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
383 % (section, name, value))
384 % (section, name, value))
384
385
385 def configlist(self, section, name, default=None, untrusted=False):
386 def configlist(self, section, name, default=None, untrusted=False):
386 """parse a configuration element as a list of comma/space separated
387 """parse a configuration element as a list of comma/space separated
387 strings
388 strings
388
389
389 >>> u = ui(); s = 'foo'
390 >>> u = ui(); s = 'foo'
390 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
391 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
391 >>> u.configlist(s, 'list1')
392 >>> u.configlist(s, 'list1')
392 ['this', 'is', 'a small', 'test']
393 ['this', 'is', 'a small', 'test']
393 """
394 """
394
395
395 def _parse_plain(parts, s, offset):
396 def _parse_plain(parts, s, offset):
396 whitespace = False
397 whitespace = False
397 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
398 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
398 whitespace = True
399 whitespace = True
399 offset += 1
400 offset += 1
400 if offset >= len(s):
401 if offset >= len(s):
401 return None, parts, offset
402 return None, parts, offset
402 if whitespace:
403 if whitespace:
403 parts.append('')
404 parts.append('')
404 if s[offset] == '"' and not parts[-1]:
405 if s[offset] == '"' and not parts[-1]:
405 return _parse_quote, parts, offset + 1
406 return _parse_quote, parts, offset + 1
406 elif s[offset] == '"' and parts[-1][-1] == '\\':
407 elif s[offset] == '"' and parts[-1][-1] == '\\':
407 parts[-1] = parts[-1][:-1] + s[offset]
408 parts[-1] = parts[-1][:-1] + s[offset]
408 return _parse_plain, parts, offset + 1
409 return _parse_plain, parts, offset + 1
409 parts[-1] += s[offset]
410 parts[-1] += s[offset]
410 return _parse_plain, parts, offset + 1
411 return _parse_plain, parts, offset + 1
411
412
412 def _parse_quote(parts, s, offset):
413 def _parse_quote(parts, s, offset):
413 if offset < len(s) and s[offset] == '"': # ""
414 if offset < len(s) and s[offset] == '"': # ""
414 parts.append('')
415 parts.append('')
415 offset += 1
416 offset += 1
416 while offset < len(s) and (s[offset].isspace() or
417 while offset < len(s) and (s[offset].isspace() or
417 s[offset] == ','):
418 s[offset] == ','):
418 offset += 1
419 offset += 1
419 return _parse_plain, parts, offset
420 return _parse_plain, parts, offset
420
421
421 while offset < len(s) and s[offset] != '"':
422 while offset < len(s) and s[offset] != '"':
422 if (s[offset] == '\\' and offset + 1 < len(s)
423 if (s[offset] == '\\' and offset + 1 < len(s)
423 and s[offset + 1] == '"'):
424 and s[offset + 1] == '"'):
424 offset += 1
425 offset += 1
425 parts[-1] += '"'
426 parts[-1] += '"'
426 else:
427 else:
427 parts[-1] += s[offset]
428 parts[-1] += s[offset]
428 offset += 1
429 offset += 1
429
430
430 if offset >= len(s):
431 if offset >= len(s):
431 real_parts = _configlist(parts[-1])
432 real_parts = _configlist(parts[-1])
432 if not real_parts:
433 if not real_parts:
433 parts[-1] = '"'
434 parts[-1] = '"'
434 else:
435 else:
435 real_parts[0] = '"' + real_parts[0]
436 real_parts[0] = '"' + real_parts[0]
436 parts = parts[:-1]
437 parts = parts[:-1]
437 parts.extend(real_parts)
438 parts.extend(real_parts)
438 return None, parts, offset
439 return None, parts, offset
439
440
440 offset += 1
441 offset += 1
441 while offset < len(s) and s[offset] in [' ', ',']:
442 while offset < len(s) and s[offset] in [' ', ',']:
442 offset += 1
443 offset += 1
443
444
444 if offset < len(s):
445 if offset < len(s):
445 if offset + 1 == len(s) and s[offset] == '"':
446 if offset + 1 == len(s) and s[offset] == '"':
446 parts[-1] += '"'
447 parts[-1] += '"'
447 offset += 1
448 offset += 1
448 else:
449 else:
449 parts.append('')
450 parts.append('')
450 else:
451 else:
451 return None, parts, offset
452 return None, parts, offset
452
453
453 return _parse_plain, parts, offset
454 return _parse_plain, parts, offset
454
455
455 def _configlist(s):
456 def _configlist(s):
456 s = s.rstrip(' ,')
457 s = s.rstrip(' ,')
457 if not s:
458 if not s:
458 return []
459 return []
459 parser, parts, offset = _parse_plain, [''], 0
460 parser, parts, offset = _parse_plain, [''], 0
460 while parser:
461 while parser:
461 parser, parts, offset = parser(parts, s, offset)
462 parser, parts, offset = parser(parts, s, offset)
462 return parts
463 return parts
463
464
464 result = self.config(section, name, untrusted=untrusted)
465 result = self.config(section, name, untrusted=untrusted)
465 if result is None:
466 if result is None:
466 result = default or []
467 result = default or []
467 if isinstance(result, basestring):
468 if isinstance(result, basestring):
468 result = _configlist(result.lstrip(' ,\n'))
469 result = _configlist(result.lstrip(' ,\n'))
469 if result is None:
470 if result is None:
470 result = default or []
471 result = default or []
471 return result
472 return result
472
473
473 def has_section(self, section, untrusted=False):
474 def has_section(self, section, untrusted=False):
474 '''tell whether section exists in config.'''
475 '''tell whether section exists in config.'''
475 return section in self._data(untrusted)
476 return section in self._data(untrusted)
476
477
477 def configitems(self, section, untrusted=False):
478 def configitems(self, section, untrusted=False):
478 items = self._data(untrusted).items(section)
479 items = self._data(untrusted).items(section)
479 if self.debugflag and not untrusted and self._reportuntrusted:
480 if self.debugflag and not untrusted and self._reportuntrusted:
480 for k, v in self._ucfg.items(section):
481 for k, v in self._ucfg.items(section):
481 if self._tcfg.get(section, k) != v:
482 if self._tcfg.get(section, k) != v:
482 self.debug("ignoring untrusted configuration option "
483 self.debug("ignoring untrusted configuration option "
483 "%s.%s = %s\n" % (section, k, v))
484 "%s.%s = %s\n" % (section, k, v))
484 return items
485 return items
485
486
486 def walkconfig(self, untrusted=False):
487 def walkconfig(self, untrusted=False):
487 cfg = self._data(untrusted)
488 cfg = self._data(untrusted)
488 for section in cfg.sections():
489 for section in cfg.sections():
489 for name, value in self.configitems(section, untrusted):
490 for name, value in self.configitems(section, untrusted):
490 yield section, name, value
491 yield section, name, value
491
492
492 def plain(self, feature=None):
493 def plain(self, feature=None):
493 '''is plain mode active?
494 '''is plain mode active?
494
495
495 Plain mode means that all configuration variables which affect
496 Plain mode means that all configuration variables which affect
496 the behavior and output of Mercurial should be
497 the behavior and output of Mercurial should be
497 ignored. Additionally, the output should be stable,
498 ignored. Additionally, the output should be stable,
498 reproducible and suitable for use in scripts or applications.
499 reproducible and suitable for use in scripts or applications.
499
500
500 The only way to trigger plain mode is by setting either the
501 The only way to trigger plain mode is by setting either the
501 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
502 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
502
503
503 The return value can either be
504 The return value can either be
504 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
505 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
505 - True otherwise
506 - True otherwise
506 '''
507 '''
507 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
508 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
508 return False
509 return False
509 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
510 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
510 if feature and exceptions:
511 if feature and exceptions:
511 return feature not in exceptions
512 return feature not in exceptions
512 return True
513 return True
513
514
514 def username(self):
515 def username(self):
515 """Return default username to be used in commits.
516 """Return default username to be used in commits.
516
517
517 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
518 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
518 and stop searching if one of these is set.
519 and stop searching if one of these is set.
519 If not found and ui.askusername is True, ask the user, else use
520 If not found and ui.askusername is True, ask the user, else use
520 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
521 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
521 """
522 """
522 user = os.environ.get("HGUSER")
523 user = os.environ.get("HGUSER")
523 if user is None:
524 if user is None:
524 user = self.config("ui", ["username", "user"])
525 user = self.config("ui", ["username", "user"])
525 if user is not None:
526 if user is not None:
526 user = os.path.expandvars(user)
527 user = os.path.expandvars(user)
527 if user is None:
528 if user is None:
528 user = os.environ.get("EMAIL")
529 user = os.environ.get("EMAIL")
529 if user is None and self.configbool("ui", "askusername"):
530 if user is None and self.configbool("ui", "askusername"):
530 user = self.prompt(_("enter a commit username:"), default=None)
531 user = self.prompt(_("enter a commit username:"), default=None)
531 if user is None and not self.interactive():
532 if user is None and not self.interactive():
532 try:
533 try:
533 user = '%s@%s' % (util.getuser(), socket.getfqdn())
534 user = '%s@%s' % (util.getuser(), socket.getfqdn())
534 self.warn(_("no username found, using '%s' instead\n") % user)
535 self.warn(_("no username found, using '%s' instead\n") % user)
535 except KeyError:
536 except KeyError:
536 pass
537 pass
537 if not user:
538 if not user:
538 raise error.Abort(_('no username supplied'),
539 raise error.Abort(_('no username supplied'),
539 hint=_('use "hg config --edit" '
540 hint=_('use "hg config --edit" '
540 'to set your username'))
541 'to set your username'))
541 if "\n" in user:
542 if "\n" in user:
542 raise error.Abort(_("username %s contains a newline\n")
543 raise error.Abort(_("username %s contains a newline\n")
543 % repr(user))
544 % repr(user))
544 return user
545 return user
545
546
546 def shortuser(self, user):
547 def shortuser(self, user):
547 """Return a short representation of a user name or email address."""
548 """Return a short representation of a user name or email address."""
548 if not self.verbose:
549 if not self.verbose:
549 user = util.shortuser(user)
550 user = util.shortuser(user)
550 return user
551 return user
551
552
552 def expandpath(self, loc, default=None):
553 def expandpath(self, loc, default=None):
553 """Return repository location relative to cwd or from [paths]"""
554 """Return repository location relative to cwd or from [paths]"""
554 try:
555 try:
555 p = self.paths.getpath(loc)
556 p = self.paths.getpath(loc)
556 if p:
557 if p:
557 return p.rawloc
558 return p.rawloc
558 except error.RepoError:
559 except error.RepoError:
559 pass
560 pass
560
561
561 if default:
562 if default:
562 try:
563 try:
563 p = self.paths.getpath(default)
564 p = self.paths.getpath(default)
564 if p:
565 if p:
565 return p.rawloc
566 return p.rawloc
566 except error.RepoError:
567 except error.RepoError:
567 pass
568 pass
568
569
569 return loc
570 return loc
570
571
571 @util.propertycache
572 @util.propertycache
572 def paths(self):
573 def paths(self):
573 return paths(self)
574 return paths(self)
574
575
575 def pushbuffer(self, error=False, subproc=False):
576 def pushbuffer(self, error=False, subproc=False):
576 """install a buffer to capture standard output of the ui object
577 """install a buffer to capture standard output of the ui object
577
578
578 If error is True, the error output will be captured too.
579 If error is True, the error output will be captured too.
579
580
580 If subproc is True, output from subprocesses (typically hooks) will be
581 If subproc is True, output from subprocesses (typically hooks) will be
581 captured too."""
582 captured too."""
582 self._buffers.append([])
583 self._buffers.append([])
583 self._bufferstates.append((error, subproc))
584 self._bufferstates.append((error, subproc))
584
585
585 def popbuffer(self, labeled=False):
586 def popbuffer(self, labeled=False):
586 '''pop the last buffer and return the buffered output
587 '''pop the last buffer and return the buffered output
587
588
588 If labeled is True, any labels associated with buffered
589 If labeled is True, any labels associated with buffered
589 output will be handled. By default, this has no effect
590 output will be handled. By default, this has no effect
590 on the output returned, but extensions and GUI tools may
591 on the output returned, but extensions and GUI tools may
591 handle this argument and returned styled output. If output
592 handle this argument and returned styled output. If output
592 is being buffered so it can be captured and parsed or
593 is being buffered so it can be captured and parsed or
593 processed, labeled should not be set to True.
594 processed, labeled should not be set to True.
594 '''
595 '''
595 self._bufferstates.pop()
596 self._bufferstates.pop()
596 return "".join(self._buffers.pop())
597 return "".join(self._buffers.pop())
597
598
598 def write(self, *args, **opts):
599 def write(self, *args, **opts):
599 '''write args to output
600 '''write args to output
600
601
601 By default, this method simply writes to the buffer or stdout,
602 By default, this method simply writes to the buffer or stdout,
602 but extensions or GUI tools may override this method,
603 but extensions or GUI tools may override this method,
603 write_err(), popbuffer(), and label() to style output from
604 write_err(), popbuffer(), and label() to style output from
604 various parts of hg.
605 various parts of hg.
605
606
606 An optional keyword argument, "label", can be passed in.
607 An optional keyword argument, "label", can be passed in.
607 This should be a string containing label names separated by
608 This should be a string containing label names separated by
608 space. Label names take the form of "topic.type". For example,
609 space. Label names take the form of "topic.type". For example,
609 ui.debug() issues a label of "ui.debug".
610 ui.debug() issues a label of "ui.debug".
610
611
611 When labeling output for a specific command, a label of
612 When labeling output for a specific command, a label of
612 "cmdname.type" is recommended. For example, status issues
613 "cmdname.type" is recommended. For example, status issues
613 a label of "status.modified" for modified files.
614 a label of "status.modified" for modified files.
614 '''
615 '''
615 self._progclear()
616 self._progclear()
616 if self._buffers:
617 if self._buffers:
617 self._buffers[-1].extend([str(a) for a in args])
618 self._buffers[-1].extend([str(a) for a in args])
618 else:
619 else:
619 for a in args:
620 for a in args:
620 self.fout.write(str(a))
621 self.fout.write(str(a))
621
622
622 def write_err(self, *args, **opts):
623 def write_err(self, *args, **opts):
623 self._progclear()
624 self._progclear()
624 try:
625 try:
625 if self._bufferstates and self._bufferstates[-1][0]:
626 if self._bufferstates and self._bufferstates[-1][0]:
626 return self.write(*args, **opts)
627 return self.write(*args, **opts)
627 if not getattr(self.fout, 'closed', False):
628 if not getattr(self.fout, 'closed', False):
628 self.fout.flush()
629 self.fout.flush()
629 for a in args:
630 for a in args:
630 self.ferr.write(str(a))
631 self.ferr.write(str(a))
631 # stderr may be buffered under win32 when redirected to files,
632 # stderr may be buffered under win32 when redirected to files,
632 # including stdout.
633 # including stdout.
633 if not getattr(self.ferr, 'closed', False):
634 if not getattr(self.ferr, 'closed', False):
634 self.ferr.flush()
635 self.ferr.flush()
635 except IOError as inst:
636 except IOError as inst:
636 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
637 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
637 raise
638 raise
638
639
639 def flush(self):
640 def flush(self):
640 try: self.fout.flush()
641 try: self.fout.flush()
641 except (IOError, ValueError): pass
642 except (IOError, ValueError): pass
642 try: self.ferr.flush()
643 try: self.ferr.flush()
643 except (IOError, ValueError): pass
644 except (IOError, ValueError): pass
644
645
645 def _isatty(self, fh):
646 def _isatty(self, fh):
646 if self.configbool('ui', 'nontty', False):
647 if self.configbool('ui', 'nontty', False):
647 return False
648 return False
648 return util.isatty(fh)
649 return util.isatty(fh)
649
650
650 def interactive(self):
651 def interactive(self):
651 '''is interactive input allowed?
652 '''is interactive input allowed?
652
653
653 An interactive session is a session where input can be reasonably read
654 An interactive session is a session where input can be reasonably read
654 from `sys.stdin'. If this function returns false, any attempt to read
655 from `sys.stdin'. If this function returns false, any attempt to read
655 from stdin should fail with an error, unless a sensible default has been
656 from stdin should fail with an error, unless a sensible default has been
656 specified.
657 specified.
657
658
658 Interactiveness is triggered by the value of the `ui.interactive'
659 Interactiveness is triggered by the value of the `ui.interactive'
659 configuration variable or - if it is unset - when `sys.stdin' points
660 configuration variable or - if it is unset - when `sys.stdin' points
660 to a terminal device.
661 to a terminal device.
661
662
662 This function refers to input only; for output, see `ui.formatted()'.
663 This function refers to input only; for output, see `ui.formatted()'.
663 '''
664 '''
664 i = self.configbool("ui", "interactive", None)
665 i = self.configbool("ui", "interactive", None)
665 if i is None:
666 if i is None:
666 # some environments replace stdin without implementing isatty
667 # some environments replace stdin without implementing isatty
667 # usually those are non-interactive
668 # usually those are non-interactive
668 return self._isatty(self.fin)
669 return self._isatty(self.fin)
669
670
670 return i
671 return i
671
672
672 def termwidth(self):
673 def termwidth(self):
673 '''how wide is the terminal in columns?
674 '''how wide is the terminal in columns?
674 '''
675 '''
675 if 'COLUMNS' in os.environ:
676 if 'COLUMNS' in os.environ:
676 try:
677 try:
677 return int(os.environ['COLUMNS'])
678 return int(os.environ['COLUMNS'])
678 except ValueError:
679 except ValueError:
679 pass
680 pass
680 return util.termwidth()
681 return util.termwidth()
681
682
682 def formatted(self):
683 def formatted(self):
683 '''should formatted output be used?
684 '''should formatted output be used?
684
685
685 It is often desirable to format the output to suite the output medium.
686 It is often desirable to format the output to suite the output medium.
686 Examples of this are truncating long lines or colorizing messages.
687 Examples of this are truncating long lines or colorizing messages.
687 However, this is not often not desirable when piping output into other
688 However, this is not often not desirable when piping output into other
688 utilities, e.g. `grep'.
689 utilities, e.g. `grep'.
689
690
690 Formatted output is triggered by the value of the `ui.formatted'
691 Formatted output is triggered by the value of the `ui.formatted'
691 configuration variable or - if it is unset - when `sys.stdout' points
692 configuration variable or - if it is unset - when `sys.stdout' points
692 to a terminal device. Please note that `ui.formatted' should be
693 to a terminal device. Please note that `ui.formatted' should be
693 considered an implementation detail; it is not intended for use outside
694 considered an implementation detail; it is not intended for use outside
694 Mercurial or its extensions.
695 Mercurial or its extensions.
695
696
696 This function refers to output only; for input, see `ui.interactive()'.
697 This function refers to output only; for input, see `ui.interactive()'.
697 This function always returns false when in plain mode, see `ui.plain()'.
698 This function always returns false when in plain mode, see `ui.plain()'.
698 '''
699 '''
699 if self.plain():
700 if self.plain():
700 return False
701 return False
701
702
702 i = self.configbool("ui", "formatted", None)
703 i = self.configbool("ui", "formatted", None)
703 if i is None:
704 if i is None:
704 # some environments replace stdout without implementing isatty
705 # some environments replace stdout without implementing isatty
705 # usually those are non-interactive
706 # usually those are non-interactive
706 return self._isatty(self.fout)
707 return self._isatty(self.fout)
707
708
708 return i
709 return i
709
710
710 def _readline(self, prompt=''):
711 def _readline(self, prompt=''):
711 if self._isatty(self.fin):
712 if self._isatty(self.fin):
712 try:
713 try:
713 # magically add command line editing support, where
714 # magically add command line editing support, where
714 # available
715 # available
715 import readline
716 import readline
716 # force demandimport to really load the module
717 # force demandimport to really load the module
717 readline.read_history_file
718 readline.read_history_file
718 # windows sometimes raises something other than ImportError
719 # windows sometimes raises something other than ImportError
719 except Exception:
720 except Exception:
720 pass
721 pass
721
722
722 # call write() so output goes through subclassed implementation
723 # call write() so output goes through subclassed implementation
723 # e.g. color extension on Windows
724 # e.g. color extension on Windows
724 self.write(prompt)
725 self.write(prompt)
725
726
726 # instead of trying to emulate raw_input, swap (self.fin,
727 # instead of trying to emulate raw_input, swap (self.fin,
727 # self.fout) with (sys.stdin, sys.stdout)
728 # self.fout) with (sys.stdin, sys.stdout)
728 oldin = sys.stdin
729 oldin = sys.stdin
729 oldout = sys.stdout
730 oldout = sys.stdout
730 sys.stdin = self.fin
731 sys.stdin = self.fin
731 sys.stdout = self.fout
732 sys.stdout = self.fout
732 # prompt ' ' must exist; otherwise readline may delete entire line
733 # prompt ' ' must exist; otherwise readline may delete entire line
733 # - http://bugs.python.org/issue12833
734 # - http://bugs.python.org/issue12833
734 line = raw_input(' ')
735 line = raw_input(' ')
735 sys.stdin = oldin
736 sys.stdin = oldin
736 sys.stdout = oldout
737 sys.stdout = oldout
737
738
738 # When stdin is in binary mode on Windows, it can cause
739 # When stdin is in binary mode on Windows, it can cause
739 # raw_input() to emit an extra trailing carriage return
740 # raw_input() to emit an extra trailing carriage return
740 if os.linesep == '\r\n' and line and line[-1] == '\r':
741 if os.linesep == '\r\n' and line and line[-1] == '\r':
741 line = line[:-1]
742 line = line[:-1]
742 return line
743 return line
743
744
744 def prompt(self, msg, default="y"):
745 def prompt(self, msg, default="y"):
745 """Prompt user with msg, read response.
746 """Prompt user with msg, read response.
746 If ui is not interactive, the default is returned.
747 If ui is not interactive, the default is returned.
747 """
748 """
748 if not self.interactive():
749 if not self.interactive():
749 self.write(msg, ' ', default, "\n")
750 self.write(msg, ' ', default, "\n")
750 return default
751 return default
751 try:
752 try:
752 r = self._readline(self.label(msg, 'ui.prompt'))
753 r = self._readline(self.label(msg, 'ui.prompt'))
753 if not r:
754 if not r:
754 r = default
755 r = default
755 if self.configbool('ui', 'promptecho'):
756 if self.configbool('ui', 'promptecho'):
756 self.write(r, "\n")
757 self.write(r, "\n")
757 return r
758 return r
758 except EOFError:
759 except EOFError:
759 raise error.Abort(_('response expected'))
760 raise error.Abort(_('response expected'))
760
761
761 @staticmethod
762 @staticmethod
762 def extractchoices(prompt):
763 def extractchoices(prompt):
763 """Extract prompt message and list of choices from specified prompt.
764 """Extract prompt message and list of choices from specified prompt.
764
765
765 This returns tuple "(message, choices)", and "choices" is the
766 This returns tuple "(message, choices)", and "choices" is the
766 list of tuple "(response character, text without &)".
767 list of tuple "(response character, text without &)".
768
769 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
770 ('awake? ', [('y', 'Yes'), ('n', 'No')])
771 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
772 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
773 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
774 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
767 """
775 """
768 parts = prompt.split('$$')
776
769 msg = parts[0].rstrip(' ')
777 # Sadly, the prompt string may have been built with a filename
770 choices = [p.strip(' ') for p in parts[1:]]
778 # containing "$$" so let's try to find the first valid-looking
779 # prompt to start parsing. Sadly, we also can't rely on
780 # choices containing spaces, ASCII, or basically anything
781 # except an ampersand followed by a character.
782 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
783 msg = m.group(1)
784 choices = [p.strip(' ') for p in m.group(2).split('$$')]
771 return (msg,
785 return (msg,
772 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
786 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
773 for s in choices])
787 for s in choices])
774
788
775 def promptchoice(self, prompt, default=0):
789 def promptchoice(self, prompt, default=0):
776 """Prompt user with a message, read response, and ensure it matches
790 """Prompt user with a message, read response, and ensure it matches
777 one of the provided choices. The prompt is formatted as follows:
791 one of the provided choices. The prompt is formatted as follows:
778
792
779 "would you like fries with that (Yn)? $$ &Yes $$ &No"
793 "would you like fries with that (Yn)? $$ &Yes $$ &No"
780
794
781 The index of the choice is returned. Responses are case
795 The index of the choice is returned. Responses are case
782 insensitive. If ui is not interactive, the default is
796 insensitive. If ui is not interactive, the default is
783 returned.
797 returned.
784 """
798 """
785
799
786 msg, choices = self.extractchoices(prompt)
800 msg, choices = self.extractchoices(prompt)
787 resps = [r for r, t in choices]
801 resps = [r for r, t in choices]
788 while True:
802 while True:
789 r = self.prompt(msg, resps[default])
803 r = self.prompt(msg, resps[default])
790 if r.lower() in resps:
804 if r.lower() in resps:
791 return resps.index(r.lower())
805 return resps.index(r.lower())
792 self.write(_("unrecognized response\n"))
806 self.write(_("unrecognized response\n"))
793
807
794 def getpass(self, prompt=None, default=None):
808 def getpass(self, prompt=None, default=None):
795 if not self.interactive():
809 if not self.interactive():
796 return default
810 return default
797 try:
811 try:
798 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
812 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
799 # disable getpass() only if explicitly specified. it's still valid
813 # disable getpass() only if explicitly specified. it's still valid
800 # to interact with tty even if fin is not a tty.
814 # to interact with tty even if fin is not a tty.
801 if self.configbool('ui', 'nontty'):
815 if self.configbool('ui', 'nontty'):
802 return self.fin.readline().rstrip('\n')
816 return self.fin.readline().rstrip('\n')
803 else:
817 else:
804 return getpass.getpass('')
818 return getpass.getpass('')
805 except EOFError:
819 except EOFError:
806 raise error.Abort(_('response expected'))
820 raise error.Abort(_('response expected'))
807 def status(self, *msg, **opts):
821 def status(self, *msg, **opts):
808 '''write status message to output (if ui.quiet is False)
822 '''write status message to output (if ui.quiet is False)
809
823
810 This adds an output label of "ui.status".
824 This adds an output label of "ui.status".
811 '''
825 '''
812 if not self.quiet:
826 if not self.quiet:
813 opts['label'] = opts.get('label', '') + ' ui.status'
827 opts['label'] = opts.get('label', '') + ' ui.status'
814 self.write(*msg, **opts)
828 self.write(*msg, **opts)
815 def warn(self, *msg, **opts):
829 def warn(self, *msg, **opts):
816 '''write warning message to output (stderr)
830 '''write warning message to output (stderr)
817
831
818 This adds an output label of "ui.warning".
832 This adds an output label of "ui.warning".
819 '''
833 '''
820 opts['label'] = opts.get('label', '') + ' ui.warning'
834 opts['label'] = opts.get('label', '') + ' ui.warning'
821 self.write_err(*msg, **opts)
835 self.write_err(*msg, **opts)
822 def note(self, *msg, **opts):
836 def note(self, *msg, **opts):
823 '''write note to output (if ui.verbose is True)
837 '''write note to output (if ui.verbose is True)
824
838
825 This adds an output label of "ui.note".
839 This adds an output label of "ui.note".
826 '''
840 '''
827 if self.verbose:
841 if self.verbose:
828 opts['label'] = opts.get('label', '') + ' ui.note'
842 opts['label'] = opts.get('label', '') + ' ui.note'
829 self.write(*msg, **opts)
843 self.write(*msg, **opts)
830 def debug(self, *msg, **opts):
844 def debug(self, *msg, **opts):
831 '''write debug message to output (if ui.debugflag is True)
845 '''write debug message to output (if ui.debugflag is True)
832
846
833 This adds an output label of "ui.debug".
847 This adds an output label of "ui.debug".
834 '''
848 '''
835 if self.debugflag:
849 if self.debugflag:
836 opts['label'] = opts.get('label', '') + ' ui.debug'
850 opts['label'] = opts.get('label', '') + ' ui.debug'
837 self.write(*msg, **opts)
851 self.write(*msg, **opts)
838
852
839 def edit(self, text, user, extra=None, editform=None, pending=None):
853 def edit(self, text, user, extra=None, editform=None, pending=None):
840 if extra is None:
854 if extra is None:
841 extra = {}
855 extra = {}
842 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
856 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
843 text=True)
857 text=True)
844 try:
858 try:
845 f = os.fdopen(fd, "w")
859 f = os.fdopen(fd, "w")
846 f.write(text)
860 f.write(text)
847 f.close()
861 f.close()
848
862
849 environ = {'HGUSER': user}
863 environ = {'HGUSER': user}
850 if 'transplant_source' in extra:
864 if 'transplant_source' in extra:
851 environ.update({'HGREVISION': hex(extra['transplant_source'])})
865 environ.update({'HGREVISION': hex(extra['transplant_source'])})
852 for label in ('intermediate-source', 'source', 'rebase_source'):
866 for label in ('intermediate-source', 'source', 'rebase_source'):
853 if label in extra:
867 if label in extra:
854 environ.update({'HGREVISION': extra[label]})
868 environ.update({'HGREVISION': extra[label]})
855 break
869 break
856 if editform:
870 if editform:
857 environ.update({'HGEDITFORM': editform})
871 environ.update({'HGEDITFORM': editform})
858 if pending:
872 if pending:
859 environ.update({'HG_PENDING': pending})
873 environ.update({'HG_PENDING': pending})
860
874
861 editor = self.geteditor()
875 editor = self.geteditor()
862
876
863 self.system("%s \"%s\"" % (editor, name),
877 self.system("%s \"%s\"" % (editor, name),
864 environ=environ,
878 environ=environ,
865 onerr=error.Abort, errprefix=_("edit failed"))
879 onerr=error.Abort, errprefix=_("edit failed"))
866
880
867 f = open(name)
881 f = open(name)
868 t = f.read()
882 t = f.read()
869 f.close()
883 f.close()
870 finally:
884 finally:
871 os.unlink(name)
885 os.unlink(name)
872
886
873 return t
887 return t
874
888
875 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
889 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
876 '''execute shell command with appropriate output stream. command
890 '''execute shell command with appropriate output stream. command
877 output will be redirected if fout is not stdout.
891 output will be redirected if fout is not stdout.
878 '''
892 '''
879 out = self.fout
893 out = self.fout
880 if any(s[1] for s in self._bufferstates):
894 if any(s[1] for s in self._bufferstates):
881 out = self
895 out = self
882 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
896 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
883 errprefix=errprefix, out=out)
897 errprefix=errprefix, out=out)
884
898
885 def traceback(self, exc=None, force=False):
899 def traceback(self, exc=None, force=False):
886 '''print exception traceback if traceback printing enabled or forced.
900 '''print exception traceback if traceback printing enabled or forced.
887 only to call in exception handler. returns true if traceback
901 only to call in exception handler. returns true if traceback
888 printed.'''
902 printed.'''
889 if self.tracebackflag or force:
903 if self.tracebackflag or force:
890 if exc is None:
904 if exc is None:
891 exc = sys.exc_info()
905 exc = sys.exc_info()
892 cause = getattr(exc[1], 'cause', None)
906 cause = getattr(exc[1], 'cause', None)
893
907
894 if cause is not None:
908 if cause is not None:
895 causetb = traceback.format_tb(cause[2])
909 causetb = traceback.format_tb(cause[2])
896 exctb = traceback.format_tb(exc[2])
910 exctb = traceback.format_tb(exc[2])
897 exconly = traceback.format_exception_only(cause[0], cause[1])
911 exconly = traceback.format_exception_only(cause[0], cause[1])
898
912
899 # exclude frame where 'exc' was chained and rethrown from exctb
913 # exclude frame where 'exc' was chained and rethrown from exctb
900 self.write_err('Traceback (most recent call last):\n',
914 self.write_err('Traceback (most recent call last):\n',
901 ''.join(exctb[:-1]),
915 ''.join(exctb[:-1]),
902 ''.join(causetb),
916 ''.join(causetb),
903 ''.join(exconly))
917 ''.join(exconly))
904 else:
918 else:
905 output = traceback.format_exception(exc[0], exc[1], exc[2])
919 output = traceback.format_exception(exc[0], exc[1], exc[2])
906 self.write_err(''.join(output))
920 self.write_err(''.join(output))
907 return self.tracebackflag or force
921 return self.tracebackflag or force
908
922
909 def geteditor(self):
923 def geteditor(self):
910 '''return editor to use'''
924 '''return editor to use'''
911 if sys.platform == 'plan9':
925 if sys.platform == 'plan9':
912 # vi is the MIPS instruction simulator on Plan 9. We
926 # vi is the MIPS instruction simulator on Plan 9. We
913 # instead default to E to plumb commit messages to
927 # instead default to E to plumb commit messages to
914 # avoid confusion.
928 # avoid confusion.
915 editor = 'E'
929 editor = 'E'
916 else:
930 else:
917 editor = 'vi'
931 editor = 'vi'
918 return (os.environ.get("HGEDITOR") or
932 return (os.environ.get("HGEDITOR") or
919 self.config("ui", "editor") or
933 self.config("ui", "editor") or
920 os.environ.get("VISUAL") or
934 os.environ.get("VISUAL") or
921 os.environ.get("EDITOR", editor))
935 os.environ.get("EDITOR", editor))
922
936
923 @util.propertycache
937 @util.propertycache
924 def _progbar(self):
938 def _progbar(self):
925 """setup the progbar singleton to the ui object"""
939 """setup the progbar singleton to the ui object"""
926 if (self.quiet or self.debugflag
940 if (self.quiet or self.debugflag
927 or self.configbool('progress', 'disable', False)
941 or self.configbool('progress', 'disable', False)
928 or not progress.shouldprint(self)):
942 or not progress.shouldprint(self)):
929 return None
943 return None
930 return getprogbar(self)
944 return getprogbar(self)
931
945
932 def _progclear(self):
946 def _progclear(self):
933 """clear progress bar output if any. use it before any output"""
947 """clear progress bar output if any. use it before any output"""
934 if '_progbar' not in vars(self): # nothing loaded yet
948 if '_progbar' not in vars(self): # nothing loaded yet
935 return
949 return
936 if self._progbar is not None and self._progbar.printed:
950 if self._progbar is not None and self._progbar.printed:
937 self._progbar.clear()
951 self._progbar.clear()
938
952
939 def progress(self, topic, pos, item="", unit="", total=None):
953 def progress(self, topic, pos, item="", unit="", total=None):
940 '''show a progress message
954 '''show a progress message
941
955
942 With stock hg, this is simply a debug message that is hidden
956 With stock hg, this is simply a debug message that is hidden
943 by default, but with extensions or GUI tools it may be
957 by default, but with extensions or GUI tools it may be
944 visible. 'topic' is the current operation, 'item' is a
958 visible. 'topic' is the current operation, 'item' is a
945 non-numeric marker of the current position (i.e. the currently
959 non-numeric marker of the current position (i.e. the currently
946 in-process file), 'pos' is the current numeric position (i.e.
960 in-process file), 'pos' is the current numeric position (i.e.
947 revision, bytes, etc.), unit is a corresponding unit label,
961 revision, bytes, etc.), unit is a corresponding unit label,
948 and total is the highest expected pos.
962 and total is the highest expected pos.
949
963
950 Multiple nested topics may be active at a time.
964 Multiple nested topics may be active at a time.
951
965
952 All topics should be marked closed by setting pos to None at
966 All topics should be marked closed by setting pos to None at
953 termination.
967 termination.
954 '''
968 '''
955 if self._progbar is not None:
969 if self._progbar is not None:
956 self._progbar.progress(topic, pos, item=item, unit=unit,
970 self._progbar.progress(topic, pos, item=item, unit=unit,
957 total=total)
971 total=total)
958 if pos is None or not self.configbool('progress', 'debug'):
972 if pos is None or not self.configbool('progress', 'debug'):
959 return
973 return
960
974
961 if unit:
975 if unit:
962 unit = ' ' + unit
976 unit = ' ' + unit
963 if item:
977 if item:
964 item = ' ' + item
978 item = ' ' + item
965
979
966 if total:
980 if total:
967 pct = 100.0 * pos / total
981 pct = 100.0 * pos / total
968 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
982 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
969 % (topic, item, pos, total, unit, pct))
983 % (topic, item, pos, total, unit, pct))
970 else:
984 else:
971 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
985 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
972
986
973 def log(self, service, *msg, **opts):
987 def log(self, service, *msg, **opts):
974 '''hook for logging facility extensions
988 '''hook for logging facility extensions
975
989
976 service should be a readily-identifiable subsystem, which will
990 service should be a readily-identifiable subsystem, which will
977 allow filtering.
991 allow filtering.
978
992
979 *msg should be a newline-terminated format string to log, and
993 *msg should be a newline-terminated format string to log, and
980 then any values to %-format into that format string.
994 then any values to %-format into that format string.
981
995
982 **opts currently has no defined meanings.
996 **opts currently has no defined meanings.
983 '''
997 '''
984
998
985 def label(self, msg, label):
999 def label(self, msg, label):
986 '''style msg based on supplied label
1000 '''style msg based on supplied label
987
1001
988 Like ui.write(), this just returns msg unchanged, but extensions
1002 Like ui.write(), this just returns msg unchanged, but extensions
989 and GUI tools can override it to allow styling output without
1003 and GUI tools can override it to allow styling output without
990 writing it.
1004 writing it.
991
1005
992 ui.write(s, 'label') is equivalent to
1006 ui.write(s, 'label') is equivalent to
993 ui.write(ui.label(s, 'label')).
1007 ui.write(ui.label(s, 'label')).
994 '''
1008 '''
995 return msg
1009 return msg
996
1010
997 def develwarn(self, msg):
1011 def develwarn(self, msg):
998 """issue a developer warning message"""
1012 """issue a developer warning message"""
999 msg = 'devel-warn: ' + msg
1013 msg = 'devel-warn: ' + msg
1000 if self.tracebackflag:
1014 if self.tracebackflag:
1001 util.debugstacktrace(msg, 2, self.ferr, self.fout)
1015 util.debugstacktrace(msg, 2, self.ferr, self.fout)
1002 else:
1016 else:
1003 curframe = inspect.currentframe()
1017 curframe = inspect.currentframe()
1004 calframe = inspect.getouterframes(curframe, 2)
1018 calframe = inspect.getouterframes(curframe, 2)
1005 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
1019 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
1006
1020
1007 class paths(dict):
1021 class paths(dict):
1008 """Represents a collection of paths and their configs.
1022 """Represents a collection of paths and their configs.
1009
1023
1010 Data is initially derived from ui instances and the config files they have
1024 Data is initially derived from ui instances and the config files they have
1011 loaded.
1025 loaded.
1012 """
1026 """
1013 def __init__(self, ui):
1027 def __init__(self, ui):
1014 dict.__init__(self)
1028 dict.__init__(self)
1015
1029
1016 for name, loc in ui.configitems('paths'):
1030 for name, loc in ui.configitems('paths'):
1017 # No location is the same as not existing.
1031 # No location is the same as not existing.
1018 if not loc:
1032 if not loc:
1019 continue
1033 continue
1020
1034
1021 # TODO ignore default-push once all consumers stop referencing it
1035 # TODO ignore default-push once all consumers stop referencing it
1022 # since it is handled specifically below.
1036 # since it is handled specifically below.
1023
1037
1024 self[name] = path(name, rawloc=loc)
1038 self[name] = path(name, rawloc=loc)
1025
1039
1026 # Handle default-push, which is a one-off that defines the push URL for
1040 # Handle default-push, which is a one-off that defines the push URL for
1027 # the "default" path.
1041 # the "default" path.
1028 defaultpush = ui.config('paths', 'default-push')
1042 defaultpush = ui.config('paths', 'default-push')
1029 if defaultpush:
1043 if defaultpush:
1030 # "default-push" can be defined without "default" entry. This is a
1044 # "default-push" can be defined without "default" entry. This is a
1031 # bit weird, but is allowed for backwards compatibility.
1045 # bit weird, but is allowed for backwards compatibility.
1032 if 'default' not in self:
1046 if 'default' not in self:
1033 self['default'] = path('default', rawloc=defaultpush)
1047 self['default'] = path('default', rawloc=defaultpush)
1034 self['default']._pushloc = defaultpush
1048 self['default']._pushloc = defaultpush
1035
1049
1036 def getpath(self, name, default=None):
1050 def getpath(self, name, default=None):
1037 """Return a ``path`` from a string, falling back to a default.
1051 """Return a ``path`` from a string, falling back to a default.
1038
1052
1039 ``name`` can be a named path or locations. Locations are filesystem
1053 ``name`` can be a named path or locations. Locations are filesystem
1040 paths or URIs.
1054 paths or URIs.
1041
1055
1042 Returns None if ``name`` is not a registered path, a URI, or a local
1056 Returns None if ``name`` is not a registered path, a URI, or a local
1043 path to a repo.
1057 path to a repo.
1044 """
1058 """
1045 # Only fall back to default if no path was requested.
1059 # Only fall back to default if no path was requested.
1046 if name is None:
1060 if name is None:
1047 if default:
1061 if default:
1048 try:
1062 try:
1049 return self[default]
1063 return self[default]
1050 except KeyError:
1064 except KeyError:
1051 return None
1065 return None
1052 else:
1066 else:
1053 return None
1067 return None
1054
1068
1055 # Most likely empty string.
1069 # Most likely empty string.
1056 # This may need to raise in the future.
1070 # This may need to raise in the future.
1057 if not name:
1071 if not name:
1058 return None
1072 return None
1059
1073
1060 try:
1074 try:
1061 return self[name]
1075 return self[name]
1062 except KeyError:
1076 except KeyError:
1063 # Try to resolve as a local path or URI.
1077 # Try to resolve as a local path or URI.
1064 try:
1078 try:
1065 return path(None, rawloc=name)
1079 return path(None, rawloc=name)
1066 except ValueError:
1080 except ValueError:
1067 raise error.RepoError(_('repository %s does not exist') %
1081 raise error.RepoError(_('repository %s does not exist') %
1068 name)
1082 name)
1069
1083
1070 assert False
1084 assert False
1071
1085
1072 class path(object):
1086 class path(object):
1073 """Represents an individual path and its configuration."""
1087 """Represents an individual path and its configuration."""
1074
1088
1075 def __init__(self, name, rawloc=None, pushloc=None):
1089 def __init__(self, name, rawloc=None, pushloc=None):
1076 """Construct a path from its config options.
1090 """Construct a path from its config options.
1077
1091
1078 ``name`` is the symbolic name of the path.
1092 ``name`` is the symbolic name of the path.
1079 ``rawloc`` is the raw location, as defined in the config.
1093 ``rawloc`` is the raw location, as defined in the config.
1080 ``pushloc`` is the raw locations pushes should be made to.
1094 ``pushloc`` is the raw locations pushes should be made to.
1081
1095
1082 If ``name`` is not defined, we require that the location be a) a local
1096 If ``name`` is not defined, we require that the location be a) a local
1083 filesystem path with a .hg directory or b) a URL. If not,
1097 filesystem path with a .hg directory or b) a URL. If not,
1084 ``ValueError`` is raised.
1098 ``ValueError`` is raised.
1085 """
1099 """
1086 if not rawloc:
1100 if not rawloc:
1087 raise ValueError('rawloc must be defined')
1101 raise ValueError('rawloc must be defined')
1088
1102
1089 # Locations may define branches via syntax <base>#<branch>.
1103 # Locations may define branches via syntax <base>#<branch>.
1090 u = util.url(rawloc)
1104 u = util.url(rawloc)
1091 branch = None
1105 branch = None
1092 if u.fragment:
1106 if u.fragment:
1093 branch = u.fragment
1107 branch = u.fragment
1094 u.fragment = None
1108 u.fragment = None
1095
1109
1096 self.url = u
1110 self.url = u
1097 self.branch = branch
1111 self.branch = branch
1098
1112
1099 self.name = name
1113 self.name = name
1100 self.rawloc = rawloc
1114 self.rawloc = rawloc
1101 self.loc = str(u)
1115 self.loc = str(u)
1102 self._pushloc = pushloc
1116 self._pushloc = pushloc
1103
1117
1104 # When given a raw location but not a symbolic name, validate the
1118 # When given a raw location but not a symbolic name, validate the
1105 # location is valid.
1119 # location is valid.
1106 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1120 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1107 raise ValueError('location is not a URL or path to a local '
1121 raise ValueError('location is not a URL or path to a local '
1108 'repo: %s' % rawloc)
1122 'repo: %s' % rawloc)
1109
1123
1110 def _isvalidlocalpath(self, path):
1124 def _isvalidlocalpath(self, path):
1111 """Returns True if the given path is a potentially valid repository.
1125 """Returns True if the given path is a potentially valid repository.
1112 This is its own function so that extensions can change the definition of
1126 This is its own function so that extensions can change the definition of
1113 'valid' in this case (like when pulling from a git repo into a hg
1127 'valid' in this case (like when pulling from a git repo into a hg
1114 one)."""
1128 one)."""
1115 return os.path.isdir(os.path.join(path, '.hg'))
1129 return os.path.isdir(os.path.join(path, '.hg'))
1116
1130
1117 @property
1131 @property
1118 def pushloc(self):
1132 def pushloc(self):
1119 return self._pushloc or self.loc
1133 return self._pushloc or self.loc
1120
1134
1121 # we instantiate one globally shared progress bar to avoid
1135 # we instantiate one globally shared progress bar to avoid
1122 # competing progress bars when multiple UI objects get created
1136 # competing progress bars when multiple UI objects get created
1123 _progresssingleton = None
1137 _progresssingleton = None
1124
1138
1125 def getprogbar(ui):
1139 def getprogbar(ui):
1126 global _progresssingleton
1140 global _progresssingleton
1127 if _progresssingleton is None:
1141 if _progresssingleton is None:
1128 # passing 'ui' object to the singleton is fishy,
1142 # passing 'ui' object to the singleton is fishy,
1129 # this is how the extension used to work but feel free to rework it.
1143 # this is how the extension used to work but feel free to rework it.
1130 _progresssingleton = progress.progbar(ui)
1144 _progresssingleton = progress.progbar(ui)
1131 return _progresssingleton
1145 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now