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