##// END OF EJS Templates
ui: restrict length of autogenerated blocked tags...
Simon Farnsworth -
r31535:d0f95ecc default
parent child Browse files
Show More
@@ -1,1613 +1,1616 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 atexit
10 import atexit
11 import collections
11 import collections
12 import contextlib
12 import contextlib
13 import errno
13 import errno
14 import getpass
14 import getpass
15 import inspect
15 import inspect
16 import os
16 import os
17 import re
17 import re
18 import signal
18 import signal
19 import socket
19 import socket
20 import subprocess
20 import subprocess
21 import sys
21 import sys
22 import tempfile
22 import tempfile
23 import traceback
23 import traceback
24
24
25 from .i18n import _
25 from .i18n import _
26 from .node import hex
26 from .node import hex
27
27
28 from . import (
28 from . import (
29 color,
29 color,
30 config,
30 config,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39
39
40 urlreq = util.urlreq
40 urlreq = util.urlreq
41
41
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 if not c.isalnum())
44 if not c.isalnum())
45
45
46 samplehgrcs = {
46 samplehgrcs = {
47 'user':
47 'user':
48 """# example user config (see 'hg help config' for more info)
48 """# example user config (see 'hg help config' for more info)
49 [ui]
49 [ui]
50 # name and email, e.g.
50 # name and email, e.g.
51 # username = Jane Doe <jdoe@example.com>
51 # username = Jane Doe <jdoe@example.com>
52 username =
52 username =
53
53
54 # uncomment to colorize command output
54 # uncomment to colorize command output
55 # color = auto
55 # color = auto
56
56
57 [extensions]
57 [extensions]
58 # uncomment these lines to enable some popular extensions
58 # uncomment these lines to enable some popular extensions
59 # (see 'hg help extensions' for more info)
59 # (see 'hg help extensions' for more info)
60 #
60 #
61 # pager =""",
61 # pager =""",
62
62
63 'cloned':
63 'cloned':
64 """# example repository config (see 'hg help config' for more info)
64 """# example repository config (see 'hg help config' for more info)
65 [paths]
65 [paths]
66 default = %s
66 default = %s
67
67
68 # 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
69 # (see 'hg help config.paths' for more info)
69 # (see 'hg help config.paths' for more info)
70 #
70 #
71 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
71 # default:pushurl = 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 'local':
80 'local':
81 """# example repository config (see 'hg help config' for more info)
81 """# example repository config (see 'hg help config' for more info)
82 [paths]
82 [paths]
83 # path aliases to other clones of this repo in URLs or filesystem paths
83 # path aliases to other clones of this repo in URLs or filesystem paths
84 # (see 'hg help config.paths' for more info)
84 # (see 'hg help config.paths' for more info)
85 #
85 #
86 # default = http://example.com/hg/example-repo
86 # default = http://example.com/hg/example-repo
87 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
87 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
88 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
88 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
89 # my-clone = /home/jdoe/jdoes-clone
89 # my-clone = /home/jdoe/jdoes-clone
90
90
91 [ui]
91 [ui]
92 # name and email (local to this repository, optional), e.g.
92 # name and email (local to this repository, optional), e.g.
93 # username = Jane Doe <jdoe@example.com>
93 # username = Jane Doe <jdoe@example.com>
94 """,
94 """,
95
95
96 'global':
96 'global':
97 """# example system-wide hg config (see 'hg help config' for more info)
97 """# example system-wide hg config (see 'hg help config' for more info)
98
98
99 [ui]
99 [ui]
100 # uncomment to colorize command output
100 # uncomment to colorize command output
101 # color = auto
101 # color = auto
102
102
103 [extensions]
103 [extensions]
104 # uncomment these lines to enable some popular extensions
104 # uncomment these lines to enable some popular extensions
105 # (see 'hg help extensions' for more info)
105 # (see 'hg help extensions' for more info)
106 #
106 #
107 # blackbox =
107 # blackbox =
108 # pager =""",
108 # pager =""",
109 }
109 }
110
110
111
111
112 class httppasswordmgrdbproxy(object):
112 class httppasswordmgrdbproxy(object):
113 """Delays loading urllib2 until it's needed."""
113 """Delays loading urllib2 until it's needed."""
114 def __init__(self):
114 def __init__(self):
115 self._mgr = None
115 self._mgr = None
116
116
117 def _get_mgr(self):
117 def _get_mgr(self):
118 if self._mgr is None:
118 if self._mgr is None:
119 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
119 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
120 return self._mgr
120 return self._mgr
121
121
122 def add_password(self, *args, **kwargs):
122 def add_password(self, *args, **kwargs):
123 return self._get_mgr().add_password(*args, **kwargs)
123 return self._get_mgr().add_password(*args, **kwargs)
124
124
125 def find_user_password(self, *args, **kwargs):
125 def find_user_password(self, *args, **kwargs):
126 return self._get_mgr().find_user_password(*args, **kwargs)
126 return self._get_mgr().find_user_password(*args, **kwargs)
127
127
128 def _catchterm(*args):
128 def _catchterm(*args):
129 raise error.SignalInterrupt
129 raise error.SignalInterrupt
130
130
131 class ui(object):
131 class ui(object):
132 def __init__(self, src=None):
132 def __init__(self, src=None):
133 """Create a fresh new ui object if no src given
133 """Create a fresh new ui object if no src given
134
134
135 Use uimod.ui.load() to create a ui which knows global and user configs.
135 Use uimod.ui.load() to create a ui which knows global and user configs.
136 In most cases, you should use ui.copy() to create a copy of an existing
136 In most cases, you should use ui.copy() to create a copy of an existing
137 ui object.
137 ui object.
138 """
138 """
139 # _buffers: used for temporary capture of output
139 # _buffers: used for temporary capture of output
140 self._buffers = []
140 self._buffers = []
141 # 3-tuple describing how each buffer in the stack behaves.
141 # 3-tuple describing how each buffer in the stack behaves.
142 # Values are (capture stderr, capture subprocesses, apply labels).
142 # Values are (capture stderr, capture subprocesses, apply labels).
143 self._bufferstates = []
143 self._bufferstates = []
144 # When a buffer is active, defines whether we are expanding labels.
144 # When a buffer is active, defines whether we are expanding labels.
145 # This exists to prevent an extra list lookup.
145 # This exists to prevent an extra list lookup.
146 self._bufferapplylabels = None
146 self._bufferapplylabels = None
147 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
147 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
148 self._reportuntrusted = True
148 self._reportuntrusted = True
149 self._ocfg = config.config() # overlay
149 self._ocfg = config.config() # overlay
150 self._tcfg = config.config() # trusted
150 self._tcfg = config.config() # trusted
151 self._ucfg = config.config() # untrusted
151 self._ucfg = config.config() # untrusted
152 self._trustusers = set()
152 self._trustusers = set()
153 self._trustgroups = set()
153 self._trustgroups = set()
154 self.callhooks = True
154 self.callhooks = True
155 # Insecure server connections requested.
155 # Insecure server connections requested.
156 self.insecureconnections = False
156 self.insecureconnections = False
157 # Blocked time
157 # Blocked time
158 self.logblockedtimes = False
158 self.logblockedtimes = False
159 # color mode: see mercurial/color.py for possible value
159 # color mode: see mercurial/color.py for possible value
160 self._colormode = None
160 self._colormode = None
161 self._terminfoparams = {}
161 self._terminfoparams = {}
162 self._styles = {}
162 self._styles = {}
163
163
164 if src:
164 if src:
165 self.fout = src.fout
165 self.fout = src.fout
166 self.ferr = src.ferr
166 self.ferr = src.ferr
167 self.fin = src.fin
167 self.fin = src.fin
168 self.pageractive = src.pageractive
168 self.pageractive = src.pageractive
169 self._disablepager = src._disablepager
169 self._disablepager = src._disablepager
170
170
171 self._tcfg = src._tcfg.copy()
171 self._tcfg = src._tcfg.copy()
172 self._ucfg = src._ucfg.copy()
172 self._ucfg = src._ucfg.copy()
173 self._ocfg = src._ocfg.copy()
173 self._ocfg = src._ocfg.copy()
174 self._trustusers = src._trustusers.copy()
174 self._trustusers = src._trustusers.copy()
175 self._trustgroups = src._trustgroups.copy()
175 self._trustgroups = src._trustgroups.copy()
176 self.environ = src.environ
176 self.environ = src.environ
177 self.callhooks = src.callhooks
177 self.callhooks = src.callhooks
178 self.insecureconnections = src.insecureconnections
178 self.insecureconnections = src.insecureconnections
179 self._colormode = src._colormode
179 self._colormode = src._colormode
180 self._terminfoparams = src._terminfoparams.copy()
180 self._terminfoparams = src._terminfoparams.copy()
181 self._styles = src._styles.copy()
181 self._styles = src._styles.copy()
182
182
183 self.fixconfig()
183 self.fixconfig()
184
184
185 self.httppasswordmgrdb = src.httppasswordmgrdb
185 self.httppasswordmgrdb = src.httppasswordmgrdb
186 self._blockedtimes = src._blockedtimes
186 self._blockedtimes = src._blockedtimes
187 else:
187 else:
188 self.fout = util.stdout
188 self.fout = util.stdout
189 self.ferr = util.stderr
189 self.ferr = util.stderr
190 self.fin = util.stdin
190 self.fin = util.stdin
191 self.pageractive = False
191 self.pageractive = False
192 self._disablepager = False
192 self._disablepager = False
193
193
194 # shared read-only environment
194 # shared read-only environment
195 self.environ = encoding.environ
195 self.environ = encoding.environ
196
196
197 self.httppasswordmgrdb = httppasswordmgrdbproxy()
197 self.httppasswordmgrdb = httppasswordmgrdbproxy()
198 self._blockedtimes = collections.defaultdict(int)
198 self._blockedtimes = collections.defaultdict(int)
199
199
200 allowed = self.configlist('experimental', 'exportableenviron')
200 allowed = self.configlist('experimental', 'exportableenviron')
201 if '*' in allowed:
201 if '*' in allowed:
202 self._exportableenviron = self.environ
202 self._exportableenviron = self.environ
203 else:
203 else:
204 self._exportableenviron = {}
204 self._exportableenviron = {}
205 for k in allowed:
205 for k in allowed:
206 if k in self.environ:
206 if k in self.environ:
207 self._exportableenviron[k] = self.environ[k]
207 self._exportableenviron[k] = self.environ[k]
208
208
209 @classmethod
209 @classmethod
210 def load(cls):
210 def load(cls):
211 """Create a ui and load global and user configs"""
211 """Create a ui and load global and user configs"""
212 u = cls()
212 u = cls()
213 # we always trust global config files
213 # we always trust global config files
214 for f in scmutil.rcpath():
214 for f in scmutil.rcpath():
215 u.readconfig(f, trust=True)
215 u.readconfig(f, trust=True)
216 return u
216 return u
217
217
218 def copy(self):
218 def copy(self):
219 return self.__class__(self)
219 return self.__class__(self)
220
220
221 def resetstate(self):
221 def resetstate(self):
222 """Clear internal state that shouldn't persist across commands"""
222 """Clear internal state that shouldn't persist across commands"""
223 if self._progbar:
223 if self._progbar:
224 self._progbar.resetstate() # reset last-print time of progress bar
224 self._progbar.resetstate() # reset last-print time of progress bar
225 self.httppasswordmgrdb = httppasswordmgrdbproxy()
225 self.httppasswordmgrdb = httppasswordmgrdbproxy()
226
226
227 @contextlib.contextmanager
227 @contextlib.contextmanager
228 def timeblockedsection(self, key):
228 def timeblockedsection(self, key):
229 # this is open-coded below - search for timeblockedsection to find them
229 # this is open-coded below - search for timeblockedsection to find them
230 starttime = util.timer()
230 starttime = util.timer()
231 try:
231 try:
232 yield
232 yield
233 finally:
233 finally:
234 self._blockedtimes[key + '_blocked'] += \
234 self._blockedtimes[key + '_blocked'] += \
235 (util.timer() - starttime) * 1000
235 (util.timer() - starttime) * 1000
236
236
237 def formatter(self, topic, opts):
237 def formatter(self, topic, opts):
238 return formatter.formatter(self, topic, opts)
238 return formatter.formatter(self, topic, opts)
239
239
240 def _trusted(self, fp, f):
240 def _trusted(self, fp, f):
241 st = util.fstat(fp)
241 st = util.fstat(fp)
242 if util.isowner(st):
242 if util.isowner(st):
243 return True
243 return True
244
244
245 tusers, tgroups = self._trustusers, self._trustgroups
245 tusers, tgroups = self._trustusers, self._trustgroups
246 if '*' in tusers or '*' in tgroups:
246 if '*' in tusers or '*' in tgroups:
247 return True
247 return True
248
248
249 user = util.username(st.st_uid)
249 user = util.username(st.st_uid)
250 group = util.groupname(st.st_gid)
250 group = util.groupname(st.st_gid)
251 if user in tusers or group in tgroups or user == util.username():
251 if user in tusers or group in tgroups or user == util.username():
252 return True
252 return True
253
253
254 if self._reportuntrusted:
254 if self._reportuntrusted:
255 self.warn(_('not trusting file %s from untrusted '
255 self.warn(_('not trusting file %s from untrusted '
256 'user %s, group %s\n') % (f, user, group))
256 'user %s, group %s\n') % (f, user, group))
257 return False
257 return False
258
258
259 def readconfig(self, filename, root=None, trust=False,
259 def readconfig(self, filename, root=None, trust=False,
260 sections=None, remap=None):
260 sections=None, remap=None):
261 try:
261 try:
262 fp = open(filename, u'rb')
262 fp = open(filename, u'rb')
263 except IOError:
263 except IOError:
264 if not sections: # ignore unless we were looking for something
264 if not sections: # ignore unless we were looking for something
265 return
265 return
266 raise
266 raise
267
267
268 cfg = config.config()
268 cfg = config.config()
269 trusted = sections or trust or self._trusted(fp, filename)
269 trusted = sections or trust or self._trusted(fp, filename)
270
270
271 try:
271 try:
272 cfg.read(filename, fp, sections=sections, remap=remap)
272 cfg.read(filename, fp, sections=sections, remap=remap)
273 fp.close()
273 fp.close()
274 except error.ConfigError as inst:
274 except error.ConfigError as inst:
275 if trusted:
275 if trusted:
276 raise
276 raise
277 self.warn(_("ignored: %s\n") % str(inst))
277 self.warn(_("ignored: %s\n") % str(inst))
278
278
279 if self.plain():
279 if self.plain():
280 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
280 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
281 'logtemplate', 'statuscopies', 'style',
281 'logtemplate', 'statuscopies', 'style',
282 'traceback', 'verbose'):
282 'traceback', 'verbose'):
283 if k in cfg['ui']:
283 if k in cfg['ui']:
284 del cfg['ui'][k]
284 del cfg['ui'][k]
285 for k, v in cfg.items('defaults'):
285 for k, v in cfg.items('defaults'):
286 del cfg['defaults'][k]
286 del cfg['defaults'][k]
287 # Don't remove aliases from the configuration if in the exceptionlist
287 # Don't remove aliases from the configuration if in the exceptionlist
288 if self.plain('alias'):
288 if self.plain('alias'):
289 for k, v in cfg.items('alias'):
289 for k, v in cfg.items('alias'):
290 del cfg['alias'][k]
290 del cfg['alias'][k]
291 if self.plain('revsetalias'):
291 if self.plain('revsetalias'):
292 for k, v in cfg.items('revsetalias'):
292 for k, v in cfg.items('revsetalias'):
293 del cfg['revsetalias'][k]
293 del cfg['revsetalias'][k]
294 if self.plain('templatealias'):
294 if self.plain('templatealias'):
295 for k, v in cfg.items('templatealias'):
295 for k, v in cfg.items('templatealias'):
296 del cfg['templatealias'][k]
296 del cfg['templatealias'][k]
297
297
298 if trusted:
298 if trusted:
299 self._tcfg.update(cfg)
299 self._tcfg.update(cfg)
300 self._tcfg.update(self._ocfg)
300 self._tcfg.update(self._ocfg)
301 self._ucfg.update(cfg)
301 self._ucfg.update(cfg)
302 self._ucfg.update(self._ocfg)
302 self._ucfg.update(self._ocfg)
303
303
304 if root is None:
304 if root is None:
305 root = os.path.expanduser('~')
305 root = os.path.expanduser('~')
306 self.fixconfig(root=root)
306 self.fixconfig(root=root)
307
307
308 def fixconfig(self, root=None, section=None):
308 def fixconfig(self, root=None, section=None):
309 if section in (None, 'paths'):
309 if section in (None, 'paths'):
310 # expand vars and ~
310 # expand vars and ~
311 # translate paths relative to root (or home) into absolute paths
311 # translate paths relative to root (or home) into absolute paths
312 root = root or pycompat.getcwd()
312 root = root or pycompat.getcwd()
313 for c in self._tcfg, self._ucfg, self._ocfg:
313 for c in self._tcfg, self._ucfg, self._ocfg:
314 for n, p in c.items('paths'):
314 for n, p in c.items('paths'):
315 # Ignore sub-options.
315 # Ignore sub-options.
316 if ':' in n:
316 if ':' in n:
317 continue
317 continue
318 if not p:
318 if not p:
319 continue
319 continue
320 if '%%' in p:
320 if '%%' in p:
321 s = self.configsource('paths', n) or 'none'
321 s = self.configsource('paths', n) or 'none'
322 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
322 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
323 % (n, p, s))
323 % (n, p, s))
324 p = p.replace('%%', '%')
324 p = p.replace('%%', '%')
325 p = util.expandpath(p)
325 p = util.expandpath(p)
326 if not util.hasscheme(p) and not os.path.isabs(p):
326 if not util.hasscheme(p) and not os.path.isabs(p):
327 p = os.path.normpath(os.path.join(root, p))
327 p = os.path.normpath(os.path.join(root, p))
328 c.set("paths", n, p)
328 c.set("paths", n, p)
329
329
330 if section in (None, 'ui'):
330 if section in (None, 'ui'):
331 # update ui options
331 # update ui options
332 self.debugflag = self.configbool('ui', 'debug')
332 self.debugflag = self.configbool('ui', 'debug')
333 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
333 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
334 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
334 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
335 if self.verbose and self.quiet:
335 if self.verbose and self.quiet:
336 self.quiet = self.verbose = False
336 self.quiet = self.verbose = False
337 self._reportuntrusted = self.debugflag or self.configbool("ui",
337 self._reportuntrusted = self.debugflag or self.configbool("ui",
338 "report_untrusted", True)
338 "report_untrusted", True)
339 self.tracebackflag = self.configbool('ui', 'traceback', False)
339 self.tracebackflag = self.configbool('ui', 'traceback', False)
340 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
340 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
341
341
342 if section in (None, 'trusted'):
342 if section in (None, 'trusted'):
343 # update trust information
343 # update trust information
344 self._trustusers.update(self.configlist('trusted', 'users'))
344 self._trustusers.update(self.configlist('trusted', 'users'))
345 self._trustgroups.update(self.configlist('trusted', 'groups'))
345 self._trustgroups.update(self.configlist('trusted', 'groups'))
346
346
347 def backupconfig(self, section, item):
347 def backupconfig(self, section, item):
348 return (self._ocfg.backup(section, item),
348 return (self._ocfg.backup(section, item),
349 self._tcfg.backup(section, item),
349 self._tcfg.backup(section, item),
350 self._ucfg.backup(section, item),)
350 self._ucfg.backup(section, item),)
351 def restoreconfig(self, data):
351 def restoreconfig(self, data):
352 self._ocfg.restore(data[0])
352 self._ocfg.restore(data[0])
353 self._tcfg.restore(data[1])
353 self._tcfg.restore(data[1])
354 self._ucfg.restore(data[2])
354 self._ucfg.restore(data[2])
355
355
356 def setconfig(self, section, name, value, source=''):
356 def setconfig(self, section, name, value, source=''):
357 for cfg in (self._ocfg, self._tcfg, self._ucfg):
357 for cfg in (self._ocfg, self._tcfg, self._ucfg):
358 cfg.set(section, name, value, source)
358 cfg.set(section, name, value, source)
359 self.fixconfig(section=section)
359 self.fixconfig(section=section)
360
360
361 def _data(self, untrusted):
361 def _data(self, untrusted):
362 return untrusted and self._ucfg or self._tcfg
362 return untrusted and self._ucfg or self._tcfg
363
363
364 def configsource(self, section, name, untrusted=False):
364 def configsource(self, section, name, untrusted=False):
365 return self._data(untrusted).source(section, name)
365 return self._data(untrusted).source(section, name)
366
366
367 def config(self, section, name, default=None, untrusted=False):
367 def config(self, section, name, default=None, untrusted=False):
368 if isinstance(name, list):
368 if isinstance(name, list):
369 alternates = name
369 alternates = name
370 else:
370 else:
371 alternates = [name]
371 alternates = [name]
372
372
373 for n in alternates:
373 for n in alternates:
374 value = self._data(untrusted).get(section, n, None)
374 value = self._data(untrusted).get(section, n, None)
375 if value is not None:
375 if value is not None:
376 name = n
376 name = n
377 break
377 break
378 else:
378 else:
379 value = default
379 value = default
380
380
381 if self.debugflag and not untrusted and self._reportuntrusted:
381 if self.debugflag and not untrusted and self._reportuntrusted:
382 for n in alternates:
382 for n in alternates:
383 uvalue = self._ucfg.get(section, n)
383 uvalue = self._ucfg.get(section, n)
384 if uvalue is not None and uvalue != value:
384 if uvalue is not None and uvalue != value:
385 self.debug("ignoring untrusted configuration option "
385 self.debug("ignoring untrusted configuration option "
386 "%s.%s = %s\n" % (section, n, uvalue))
386 "%s.%s = %s\n" % (section, n, uvalue))
387 return value
387 return value
388
388
389 def configsuboptions(self, section, name, default=None, untrusted=False):
389 def configsuboptions(self, section, name, default=None, untrusted=False):
390 """Get a config option and all sub-options.
390 """Get a config option and all sub-options.
391
391
392 Some config options have sub-options that are declared with the
392 Some config options have sub-options that are declared with the
393 format "key:opt = value". This method is used to return the main
393 format "key:opt = value". This method is used to return the main
394 option and all its declared sub-options.
394 option and all its declared sub-options.
395
395
396 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
396 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
397 is a dict of defined sub-options where keys and values are strings.
397 is a dict of defined sub-options where keys and values are strings.
398 """
398 """
399 data = self._data(untrusted)
399 data = self._data(untrusted)
400 main = data.get(section, name, default)
400 main = data.get(section, name, default)
401 if self.debugflag and not untrusted and self._reportuntrusted:
401 if self.debugflag and not untrusted and self._reportuntrusted:
402 uvalue = self._ucfg.get(section, name)
402 uvalue = self._ucfg.get(section, name)
403 if uvalue is not None and uvalue != main:
403 if uvalue is not None and uvalue != main:
404 self.debug('ignoring untrusted configuration option '
404 self.debug('ignoring untrusted configuration option '
405 '%s.%s = %s\n' % (section, name, uvalue))
405 '%s.%s = %s\n' % (section, name, uvalue))
406
406
407 sub = {}
407 sub = {}
408 prefix = '%s:' % name
408 prefix = '%s:' % name
409 for k, v in data.items(section):
409 for k, v in data.items(section):
410 if k.startswith(prefix):
410 if k.startswith(prefix):
411 sub[k[len(prefix):]] = v
411 sub[k[len(prefix):]] = v
412
412
413 if self.debugflag and not untrusted and self._reportuntrusted:
413 if self.debugflag and not untrusted and self._reportuntrusted:
414 for k, v in sub.items():
414 for k, v in sub.items():
415 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
415 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
416 if uvalue is not None and uvalue != v:
416 if uvalue is not None and uvalue != v:
417 self.debug('ignoring untrusted configuration option '
417 self.debug('ignoring untrusted configuration option '
418 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
418 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
419
419
420 return main, sub
420 return main, sub
421
421
422 def configpath(self, section, name, default=None, untrusted=False):
422 def configpath(self, section, name, default=None, untrusted=False):
423 'get a path config item, expanded relative to repo root or config file'
423 'get a path config item, expanded relative to repo root or config file'
424 v = self.config(section, name, default, untrusted)
424 v = self.config(section, name, default, untrusted)
425 if v is None:
425 if v is None:
426 return None
426 return None
427 if not os.path.isabs(v) or "://" not in v:
427 if not os.path.isabs(v) or "://" not in v:
428 src = self.configsource(section, name, untrusted)
428 src = self.configsource(section, name, untrusted)
429 if ':' in src:
429 if ':' in src:
430 base = os.path.dirname(src.rsplit(':')[0])
430 base = os.path.dirname(src.rsplit(':')[0])
431 v = os.path.join(base, os.path.expanduser(v))
431 v = os.path.join(base, os.path.expanduser(v))
432 return v
432 return v
433
433
434 def configbool(self, section, name, default=False, untrusted=False):
434 def configbool(self, section, name, default=False, untrusted=False):
435 """parse a configuration element as a boolean
435 """parse a configuration element as a boolean
436
436
437 >>> u = ui(); s = 'foo'
437 >>> u = ui(); s = 'foo'
438 >>> u.setconfig(s, 'true', 'yes')
438 >>> u.setconfig(s, 'true', 'yes')
439 >>> u.configbool(s, 'true')
439 >>> u.configbool(s, 'true')
440 True
440 True
441 >>> u.setconfig(s, 'false', 'no')
441 >>> u.setconfig(s, 'false', 'no')
442 >>> u.configbool(s, 'false')
442 >>> u.configbool(s, 'false')
443 False
443 False
444 >>> u.configbool(s, 'unknown')
444 >>> u.configbool(s, 'unknown')
445 False
445 False
446 >>> u.configbool(s, 'unknown', True)
446 >>> u.configbool(s, 'unknown', True)
447 True
447 True
448 >>> u.setconfig(s, 'invalid', 'somevalue')
448 >>> u.setconfig(s, 'invalid', 'somevalue')
449 >>> u.configbool(s, 'invalid')
449 >>> u.configbool(s, 'invalid')
450 Traceback (most recent call last):
450 Traceback (most recent call last):
451 ...
451 ...
452 ConfigError: foo.invalid is not a boolean ('somevalue')
452 ConfigError: foo.invalid is not a boolean ('somevalue')
453 """
453 """
454
454
455 v = self.config(section, name, None, untrusted)
455 v = self.config(section, name, None, untrusted)
456 if v is None:
456 if v is None:
457 return default
457 return default
458 if isinstance(v, bool):
458 if isinstance(v, bool):
459 return v
459 return v
460 b = util.parsebool(v)
460 b = util.parsebool(v)
461 if b is None:
461 if b is None:
462 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
462 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
463 % (section, name, v))
463 % (section, name, v))
464 return b
464 return b
465
465
466 def configwith(self, convert, section, name, default=None,
466 def configwith(self, convert, section, name, default=None,
467 desc=None, untrusted=False):
467 desc=None, untrusted=False):
468 """parse a configuration element with a conversion function
468 """parse a configuration element with a conversion function
469
469
470 >>> u = ui(); s = 'foo'
470 >>> u = ui(); s = 'foo'
471 >>> u.setconfig(s, 'float1', '42')
471 >>> u.setconfig(s, 'float1', '42')
472 >>> u.configwith(float, s, 'float1')
472 >>> u.configwith(float, s, 'float1')
473 42.0
473 42.0
474 >>> u.setconfig(s, 'float2', '-4.25')
474 >>> u.setconfig(s, 'float2', '-4.25')
475 >>> u.configwith(float, s, 'float2')
475 >>> u.configwith(float, s, 'float2')
476 -4.25
476 -4.25
477 >>> u.configwith(float, s, 'unknown', 7)
477 >>> u.configwith(float, s, 'unknown', 7)
478 7
478 7
479 >>> u.setconfig(s, 'invalid', 'somevalue')
479 >>> u.setconfig(s, 'invalid', 'somevalue')
480 >>> u.configwith(float, s, 'invalid')
480 >>> u.configwith(float, s, 'invalid')
481 Traceback (most recent call last):
481 Traceback (most recent call last):
482 ...
482 ...
483 ConfigError: foo.invalid is not a valid float ('somevalue')
483 ConfigError: foo.invalid is not a valid float ('somevalue')
484 >>> u.configwith(float, s, 'invalid', desc='womble')
484 >>> u.configwith(float, s, 'invalid', desc='womble')
485 Traceback (most recent call last):
485 Traceback (most recent call last):
486 ...
486 ...
487 ConfigError: foo.invalid is not a valid womble ('somevalue')
487 ConfigError: foo.invalid is not a valid womble ('somevalue')
488 """
488 """
489
489
490 v = self.config(section, name, None, untrusted)
490 v = self.config(section, name, None, untrusted)
491 if v is None:
491 if v is None:
492 return default
492 return default
493 try:
493 try:
494 return convert(v)
494 return convert(v)
495 except ValueError:
495 except ValueError:
496 if desc is None:
496 if desc is None:
497 desc = convert.__name__
497 desc = convert.__name__
498 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
498 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
499 % (section, name, desc, v))
499 % (section, name, desc, v))
500
500
501 def configint(self, section, name, default=None, untrusted=False):
501 def configint(self, section, name, default=None, untrusted=False):
502 """parse a configuration element as an integer
502 """parse a configuration element as an integer
503
503
504 >>> u = ui(); s = 'foo'
504 >>> u = ui(); s = 'foo'
505 >>> u.setconfig(s, 'int1', '42')
505 >>> u.setconfig(s, 'int1', '42')
506 >>> u.configint(s, 'int1')
506 >>> u.configint(s, 'int1')
507 42
507 42
508 >>> u.setconfig(s, 'int2', '-42')
508 >>> u.setconfig(s, 'int2', '-42')
509 >>> u.configint(s, 'int2')
509 >>> u.configint(s, 'int2')
510 -42
510 -42
511 >>> u.configint(s, 'unknown', 7)
511 >>> u.configint(s, 'unknown', 7)
512 7
512 7
513 >>> u.setconfig(s, 'invalid', 'somevalue')
513 >>> u.setconfig(s, 'invalid', 'somevalue')
514 >>> u.configint(s, 'invalid')
514 >>> u.configint(s, 'invalid')
515 Traceback (most recent call last):
515 Traceback (most recent call last):
516 ...
516 ...
517 ConfigError: foo.invalid is not a valid integer ('somevalue')
517 ConfigError: foo.invalid is not a valid integer ('somevalue')
518 """
518 """
519
519
520 return self.configwith(int, section, name, default, 'integer',
520 return self.configwith(int, section, name, default, 'integer',
521 untrusted)
521 untrusted)
522
522
523 def configbytes(self, section, name, default=0, untrusted=False):
523 def configbytes(self, section, name, default=0, untrusted=False):
524 """parse a configuration element as a quantity in bytes
524 """parse a configuration element as a quantity in bytes
525
525
526 Units can be specified as b (bytes), k or kb (kilobytes), m or
526 Units can be specified as b (bytes), k or kb (kilobytes), m or
527 mb (megabytes), g or gb (gigabytes).
527 mb (megabytes), g or gb (gigabytes).
528
528
529 >>> u = ui(); s = 'foo'
529 >>> u = ui(); s = 'foo'
530 >>> u.setconfig(s, 'val1', '42')
530 >>> u.setconfig(s, 'val1', '42')
531 >>> u.configbytes(s, 'val1')
531 >>> u.configbytes(s, 'val1')
532 42
532 42
533 >>> u.setconfig(s, 'val2', '42.5 kb')
533 >>> u.setconfig(s, 'val2', '42.5 kb')
534 >>> u.configbytes(s, 'val2')
534 >>> u.configbytes(s, 'val2')
535 43520
535 43520
536 >>> u.configbytes(s, 'unknown', '7 MB')
536 >>> u.configbytes(s, 'unknown', '7 MB')
537 7340032
537 7340032
538 >>> u.setconfig(s, 'invalid', 'somevalue')
538 >>> u.setconfig(s, 'invalid', 'somevalue')
539 >>> u.configbytes(s, 'invalid')
539 >>> u.configbytes(s, 'invalid')
540 Traceback (most recent call last):
540 Traceback (most recent call last):
541 ...
541 ...
542 ConfigError: foo.invalid is not a byte quantity ('somevalue')
542 ConfigError: foo.invalid is not a byte quantity ('somevalue')
543 """
543 """
544
544
545 value = self.config(section, name, None, untrusted)
545 value = self.config(section, name, None, untrusted)
546 if value is None:
546 if value is None:
547 if not isinstance(default, str):
547 if not isinstance(default, str):
548 return default
548 return default
549 value = default
549 value = default
550 try:
550 try:
551 return util.sizetoint(value)
551 return util.sizetoint(value)
552 except error.ParseError:
552 except error.ParseError:
553 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
553 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
554 % (section, name, value))
554 % (section, name, value))
555
555
556 def configlist(self, section, name, default=None, untrusted=False):
556 def configlist(self, section, name, default=None, untrusted=False):
557 """parse a configuration element as a list of comma/space separated
557 """parse a configuration element as a list of comma/space separated
558 strings
558 strings
559
559
560 >>> u = ui(); s = 'foo'
560 >>> u = ui(); s = 'foo'
561 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
561 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
562 >>> u.configlist(s, 'list1')
562 >>> u.configlist(s, 'list1')
563 ['this', 'is', 'a small', 'test']
563 ['this', 'is', 'a small', 'test']
564 """
564 """
565 # default is not always a list
565 # default is not always a list
566 if isinstance(default, bytes):
566 if isinstance(default, bytes):
567 default = config.parselist(default)
567 default = config.parselist(default)
568 return self.configwith(config.parselist, section, name, default or [],
568 return self.configwith(config.parselist, section, name, default or [],
569 'list', untrusted)
569 'list', untrusted)
570
570
571 def hasconfig(self, section, name, untrusted=False):
571 def hasconfig(self, section, name, untrusted=False):
572 return self._data(untrusted).hasitem(section, name)
572 return self._data(untrusted).hasitem(section, name)
573
573
574 def has_section(self, section, untrusted=False):
574 def has_section(self, section, untrusted=False):
575 '''tell whether section exists in config.'''
575 '''tell whether section exists in config.'''
576 return section in self._data(untrusted)
576 return section in self._data(untrusted)
577
577
578 def configitems(self, section, untrusted=False, ignoresub=False):
578 def configitems(self, section, untrusted=False, ignoresub=False):
579 items = self._data(untrusted).items(section)
579 items = self._data(untrusted).items(section)
580 if ignoresub:
580 if ignoresub:
581 newitems = {}
581 newitems = {}
582 for k, v in items:
582 for k, v in items:
583 if ':' not in k:
583 if ':' not in k:
584 newitems[k] = v
584 newitems[k] = v
585 items = newitems.items()
585 items = newitems.items()
586 if self.debugflag and not untrusted and self._reportuntrusted:
586 if self.debugflag and not untrusted and self._reportuntrusted:
587 for k, v in self._ucfg.items(section):
587 for k, v in self._ucfg.items(section):
588 if self._tcfg.get(section, k) != v:
588 if self._tcfg.get(section, k) != v:
589 self.debug("ignoring untrusted configuration option "
589 self.debug("ignoring untrusted configuration option "
590 "%s.%s = %s\n" % (section, k, v))
590 "%s.%s = %s\n" % (section, k, v))
591 return items
591 return items
592
592
593 def walkconfig(self, untrusted=False):
593 def walkconfig(self, untrusted=False):
594 cfg = self._data(untrusted)
594 cfg = self._data(untrusted)
595 for section in cfg.sections():
595 for section in cfg.sections():
596 for name, value in self.configitems(section, untrusted):
596 for name, value in self.configitems(section, untrusted):
597 yield section, name, value
597 yield section, name, value
598
598
599 def plain(self, feature=None):
599 def plain(self, feature=None):
600 '''is plain mode active?
600 '''is plain mode active?
601
601
602 Plain mode means that all configuration variables which affect
602 Plain mode means that all configuration variables which affect
603 the behavior and output of Mercurial should be
603 the behavior and output of Mercurial should be
604 ignored. Additionally, the output should be stable,
604 ignored. Additionally, the output should be stable,
605 reproducible and suitable for use in scripts or applications.
605 reproducible and suitable for use in scripts or applications.
606
606
607 The only way to trigger plain mode is by setting either the
607 The only way to trigger plain mode is by setting either the
608 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
608 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
609
609
610 The return value can either be
610 The return value can either be
611 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
611 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
612 - True otherwise
612 - True otherwise
613 '''
613 '''
614 if ('HGPLAIN' not in encoding.environ and
614 if ('HGPLAIN' not in encoding.environ and
615 'HGPLAINEXCEPT' not in encoding.environ):
615 'HGPLAINEXCEPT' not in encoding.environ):
616 return False
616 return False
617 exceptions = encoding.environ.get('HGPLAINEXCEPT',
617 exceptions = encoding.environ.get('HGPLAINEXCEPT',
618 '').strip().split(',')
618 '').strip().split(',')
619 if feature and exceptions:
619 if feature and exceptions:
620 return feature not in exceptions
620 return feature not in exceptions
621 return True
621 return True
622
622
623 def username(self):
623 def username(self):
624 """Return default username to be used in commits.
624 """Return default username to be used in commits.
625
625
626 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
626 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
627 and stop searching if one of these is set.
627 and stop searching if one of these is set.
628 If not found and ui.askusername is True, ask the user, else use
628 If not found and ui.askusername is True, ask the user, else use
629 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
629 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
630 """
630 """
631 user = encoding.environ.get("HGUSER")
631 user = encoding.environ.get("HGUSER")
632 if user is None:
632 if user is None:
633 user = self.config("ui", ["username", "user"])
633 user = self.config("ui", ["username", "user"])
634 if user is not None:
634 if user is not None:
635 user = os.path.expandvars(user)
635 user = os.path.expandvars(user)
636 if user is None:
636 if user is None:
637 user = encoding.environ.get("EMAIL")
637 user = encoding.environ.get("EMAIL")
638 if user is None and self.configbool("ui", "askusername"):
638 if user is None and self.configbool("ui", "askusername"):
639 user = self.prompt(_("enter a commit username:"), default=None)
639 user = self.prompt(_("enter a commit username:"), default=None)
640 if user is None and not self.interactive():
640 if user is None and not self.interactive():
641 try:
641 try:
642 user = '%s@%s' % (util.getuser(), socket.getfqdn())
642 user = '%s@%s' % (util.getuser(), socket.getfqdn())
643 self.warn(_("no username found, using '%s' instead\n") % user)
643 self.warn(_("no username found, using '%s' instead\n") % user)
644 except KeyError:
644 except KeyError:
645 pass
645 pass
646 if not user:
646 if not user:
647 raise error.Abort(_('no username supplied'),
647 raise error.Abort(_('no username supplied'),
648 hint=_("use 'hg config --edit' "
648 hint=_("use 'hg config --edit' "
649 'to set your username'))
649 'to set your username'))
650 if "\n" in user:
650 if "\n" in user:
651 raise error.Abort(_("username %s contains a newline\n")
651 raise error.Abort(_("username %s contains a newline\n")
652 % repr(user))
652 % repr(user))
653 return user
653 return user
654
654
655 def shortuser(self, user):
655 def shortuser(self, user):
656 """Return a short representation of a user name or email address."""
656 """Return a short representation of a user name or email address."""
657 if not self.verbose:
657 if not self.verbose:
658 user = util.shortuser(user)
658 user = util.shortuser(user)
659 return user
659 return user
660
660
661 def expandpath(self, loc, default=None):
661 def expandpath(self, loc, default=None):
662 """Return repository location relative to cwd or from [paths]"""
662 """Return repository location relative to cwd or from [paths]"""
663 try:
663 try:
664 p = self.paths.getpath(loc)
664 p = self.paths.getpath(loc)
665 if p:
665 if p:
666 return p.rawloc
666 return p.rawloc
667 except error.RepoError:
667 except error.RepoError:
668 pass
668 pass
669
669
670 if default:
670 if default:
671 try:
671 try:
672 p = self.paths.getpath(default)
672 p = self.paths.getpath(default)
673 if p:
673 if p:
674 return p.rawloc
674 return p.rawloc
675 except error.RepoError:
675 except error.RepoError:
676 pass
676 pass
677
677
678 return loc
678 return loc
679
679
680 @util.propertycache
680 @util.propertycache
681 def paths(self):
681 def paths(self):
682 return paths(self)
682 return paths(self)
683
683
684 def pushbuffer(self, error=False, subproc=False, labeled=False):
684 def pushbuffer(self, error=False, subproc=False, labeled=False):
685 """install a buffer to capture standard output of the ui object
685 """install a buffer to capture standard output of the ui object
686
686
687 If error is True, the error output will be captured too.
687 If error is True, the error output will be captured too.
688
688
689 If subproc is True, output from subprocesses (typically hooks) will be
689 If subproc is True, output from subprocesses (typically hooks) will be
690 captured too.
690 captured too.
691
691
692 If labeled is True, any labels associated with buffered
692 If labeled is True, any labels associated with buffered
693 output will be handled. By default, this has no effect
693 output will be handled. By default, this has no effect
694 on the output returned, but extensions and GUI tools may
694 on the output returned, but extensions and GUI tools may
695 handle this argument and returned styled output. If output
695 handle this argument and returned styled output. If output
696 is being buffered so it can be captured and parsed or
696 is being buffered so it can be captured and parsed or
697 processed, labeled should not be set to True.
697 processed, labeled should not be set to True.
698 """
698 """
699 self._buffers.append([])
699 self._buffers.append([])
700 self._bufferstates.append((error, subproc, labeled))
700 self._bufferstates.append((error, subproc, labeled))
701 self._bufferapplylabels = labeled
701 self._bufferapplylabels = labeled
702
702
703 def popbuffer(self):
703 def popbuffer(self):
704 '''pop the last buffer and return the buffered output'''
704 '''pop the last buffer and return the buffered output'''
705 self._bufferstates.pop()
705 self._bufferstates.pop()
706 if self._bufferstates:
706 if self._bufferstates:
707 self._bufferapplylabels = self._bufferstates[-1][2]
707 self._bufferapplylabels = self._bufferstates[-1][2]
708 else:
708 else:
709 self._bufferapplylabels = None
709 self._bufferapplylabels = None
710
710
711 return "".join(self._buffers.pop())
711 return "".join(self._buffers.pop())
712
712
713 def write(self, *args, **opts):
713 def write(self, *args, **opts):
714 '''write args to output
714 '''write args to output
715
715
716 By default, this method simply writes to the buffer or stdout.
716 By default, this method simply writes to the buffer or stdout.
717 Color mode can be set on the UI class to have the output decorated
717 Color mode can be set on the UI class to have the output decorated
718 with color modifier before being written to stdout.
718 with color modifier before being written to stdout.
719
719
720 The color used is controlled by an optional keyword argument, "label".
720 The color used is controlled by an optional keyword argument, "label".
721 This should be a string containing label names separated by space.
721 This should be a string containing label names separated by space.
722 Label names take the form of "topic.type". For example, ui.debug()
722 Label names take the form of "topic.type". For example, ui.debug()
723 issues a label of "ui.debug".
723 issues a label of "ui.debug".
724
724
725 When labeling output for a specific command, a label of
725 When labeling output for a specific command, a label of
726 "cmdname.type" is recommended. For example, status issues
726 "cmdname.type" is recommended. For example, status issues
727 a label of "status.modified" for modified files.
727 a label of "status.modified" for modified files.
728 '''
728 '''
729 if self._buffers and not opts.get('prompt', False):
729 if self._buffers and not opts.get('prompt', False):
730 if self._bufferapplylabels:
730 if self._bufferapplylabels:
731 label = opts.get('label', '')
731 label = opts.get('label', '')
732 self._buffers[-1].extend(self.label(a, label) for a in args)
732 self._buffers[-1].extend(self.label(a, label) for a in args)
733 else:
733 else:
734 self._buffers[-1].extend(args)
734 self._buffers[-1].extend(args)
735 elif self._colormode == 'win32':
735 elif self._colormode == 'win32':
736 # windows color printing is its own can of crab, defer to
736 # windows color printing is its own can of crab, defer to
737 # the color module and that is it.
737 # the color module and that is it.
738 color.win32print(self, self._write, *args, **opts)
738 color.win32print(self, self._write, *args, **opts)
739 else:
739 else:
740 msgs = args
740 msgs = args
741 if self._colormode is not None:
741 if self._colormode is not None:
742 label = opts.get('label', '')
742 label = opts.get('label', '')
743 msgs = [self.label(a, label) for a in args]
743 msgs = [self.label(a, label) for a in args]
744 self._write(*msgs, **opts)
744 self._write(*msgs, **opts)
745
745
746 def _write(self, *msgs, **opts):
746 def _write(self, *msgs, **opts):
747 self._progclear()
747 self._progclear()
748 # opencode timeblockedsection because this is a critical path
748 # opencode timeblockedsection because this is a critical path
749 starttime = util.timer()
749 starttime = util.timer()
750 try:
750 try:
751 for a in msgs:
751 for a in msgs:
752 self.fout.write(a)
752 self.fout.write(a)
753 finally:
753 finally:
754 self._blockedtimes['stdio_blocked'] += \
754 self._blockedtimes['stdio_blocked'] += \
755 (util.timer() - starttime) * 1000
755 (util.timer() - starttime) * 1000
756
756
757 def write_err(self, *args, **opts):
757 def write_err(self, *args, **opts):
758 self._progclear()
758 self._progclear()
759 if self._bufferstates and self._bufferstates[-1][0]:
759 if self._bufferstates and self._bufferstates[-1][0]:
760 self.write(*args, **opts)
760 self.write(*args, **opts)
761 elif self._colormode == 'win32':
761 elif self._colormode == 'win32':
762 # windows color printing is its own can of crab, defer to
762 # windows color printing is its own can of crab, defer to
763 # the color module and that is it.
763 # the color module and that is it.
764 color.win32print(self, self._write_err, *args, **opts)
764 color.win32print(self, self._write_err, *args, **opts)
765 else:
765 else:
766 msgs = args
766 msgs = args
767 if self._colormode is not None:
767 if self._colormode is not None:
768 label = opts.get('label', '')
768 label = opts.get('label', '')
769 msgs = [self.label(a, label) for a in args]
769 msgs = [self.label(a, label) for a in args]
770 self._write_err(*msgs, **opts)
770 self._write_err(*msgs, **opts)
771
771
772 def _write_err(self, *msgs, **opts):
772 def _write_err(self, *msgs, **opts):
773 try:
773 try:
774 with self.timeblockedsection('stdio'):
774 with self.timeblockedsection('stdio'):
775 if not getattr(self.fout, 'closed', False):
775 if not getattr(self.fout, 'closed', False):
776 self.fout.flush()
776 self.fout.flush()
777 for a in msgs:
777 for a in msgs:
778 self.ferr.write(a)
778 self.ferr.write(a)
779 # stderr may be buffered under win32 when redirected to files,
779 # stderr may be buffered under win32 when redirected to files,
780 # including stdout.
780 # including stdout.
781 if not getattr(self.ferr, 'closed', False):
781 if not getattr(self.ferr, 'closed', False):
782 self.ferr.flush()
782 self.ferr.flush()
783 except IOError as inst:
783 except IOError as inst:
784 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
784 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
785 raise
785 raise
786
786
787 def flush(self):
787 def flush(self):
788 # opencode timeblockedsection because this is a critical path
788 # opencode timeblockedsection because this is a critical path
789 starttime = util.timer()
789 starttime = util.timer()
790 try:
790 try:
791 try: self.fout.flush()
791 try: self.fout.flush()
792 except (IOError, ValueError): pass
792 except (IOError, ValueError): pass
793 try: self.ferr.flush()
793 try: self.ferr.flush()
794 except (IOError, ValueError): pass
794 except (IOError, ValueError): pass
795 finally:
795 finally:
796 self._blockedtimes['stdio_blocked'] += \
796 self._blockedtimes['stdio_blocked'] += \
797 (util.timer() - starttime) * 1000
797 (util.timer() - starttime) * 1000
798
798
799 def _isatty(self, fh):
799 def _isatty(self, fh):
800 if self.configbool('ui', 'nontty', False):
800 if self.configbool('ui', 'nontty', False):
801 return False
801 return False
802 return util.isatty(fh)
802 return util.isatty(fh)
803
803
804 def disablepager(self):
804 def disablepager(self):
805 self._disablepager = True
805 self._disablepager = True
806
806
807 def pager(self, command):
807 def pager(self, command):
808 """Start a pager for subsequent command output.
808 """Start a pager for subsequent command output.
809
809
810 Commands which produce a long stream of output should call
810 Commands which produce a long stream of output should call
811 this function to activate the user's preferred pagination
811 this function to activate the user's preferred pagination
812 mechanism (which may be no pager). Calling this function
812 mechanism (which may be no pager). Calling this function
813 precludes any future use of interactive functionality, such as
813 precludes any future use of interactive functionality, such as
814 prompting the user or activating curses.
814 prompting the user or activating curses.
815
815
816 Args:
816 Args:
817 command: The full, non-aliased name of the command. That is, "log"
817 command: The full, non-aliased name of the command. That is, "log"
818 not "history, "summary" not "summ", etc.
818 not "history, "summary" not "summ", etc.
819 """
819 """
820 if (self._disablepager
820 if (self._disablepager
821 or self.pageractive
821 or self.pageractive
822 or command in self.configlist('pager', 'ignore')
822 or command in self.configlist('pager', 'ignore')
823 or not self.configbool('pager', 'enable', True)
823 or not self.configbool('pager', 'enable', True)
824 or not self.configbool('pager', 'attend-' + command, True)
824 or not self.configbool('pager', 'attend-' + command, True)
825 # TODO: if we want to allow HGPLAINEXCEPT=pager,
825 # TODO: if we want to allow HGPLAINEXCEPT=pager,
826 # formatted() will need some adjustment.
826 # formatted() will need some adjustment.
827 or not self.formatted()
827 or not self.formatted()
828 or self.plain()
828 or self.plain()
829 # TODO: expose debugger-enabled on the UI object
829 # TODO: expose debugger-enabled on the UI object
830 or '--debugger' in pycompat.sysargv):
830 or '--debugger' in pycompat.sysargv):
831 # We only want to paginate if the ui appears to be
831 # We only want to paginate if the ui appears to be
832 # interactive, the user didn't say HGPLAIN or
832 # interactive, the user didn't say HGPLAIN or
833 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
833 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
834 return
834 return
835
835
836 # TODO: add a "system defaults" config section so this default
836 # TODO: add a "system defaults" config section so this default
837 # of more(1) can be easily replaced with a global
837 # of more(1) can be easily replaced with a global
838 # configuration file. For example, on OS X the sane default is
838 # configuration file. For example, on OS X the sane default is
839 # less(1), not more(1), and on debian it's
839 # less(1), not more(1), and on debian it's
840 # sensible-pager(1). We should probably also give the system
840 # sensible-pager(1). We should probably also give the system
841 # default editor command similar treatment.
841 # default editor command similar treatment.
842 envpager = encoding.environ.get('PAGER', 'more')
842 envpager = encoding.environ.get('PAGER', 'more')
843 pagercmd = self.config('pager', 'pager', envpager)
843 pagercmd = self.config('pager', 'pager', envpager)
844 if not pagercmd:
844 if not pagercmd:
845 return
845 return
846
846
847 self.debug('starting pager for command %r\n' % command)
847 self.debug('starting pager for command %r\n' % command)
848 self.flush()
848 self.flush()
849 self.pageractive = True
849 self.pageractive = True
850 # Preserve the formatted-ness of the UI. This is important
850 # Preserve the formatted-ness of the UI. This is important
851 # because we mess with stdout, which might confuse
851 # because we mess with stdout, which might confuse
852 # auto-detection of things being formatted.
852 # auto-detection of things being formatted.
853 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
853 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
854 self.setconfig('ui', 'interactive', False, 'pager')
854 self.setconfig('ui', 'interactive', False, 'pager')
855 if util.safehasattr(signal, "SIGPIPE"):
855 if util.safehasattr(signal, "SIGPIPE"):
856 signal.signal(signal.SIGPIPE, _catchterm)
856 signal.signal(signal.SIGPIPE, _catchterm)
857 self._runpager(pagercmd)
857 self._runpager(pagercmd)
858
858
859 def _runpager(self, command):
859 def _runpager(self, command):
860 """Actually start the pager and set up file descriptors.
860 """Actually start the pager and set up file descriptors.
861
861
862 This is separate in part so that extensions (like chg) can
862 This is separate in part so that extensions (like chg) can
863 override how a pager is invoked.
863 override how a pager is invoked.
864 """
864 """
865 if command == 'cat':
865 if command == 'cat':
866 # Save ourselves some work.
866 # Save ourselves some work.
867 return
867 return
868 # If the command doesn't contain any of these characters, we
868 # If the command doesn't contain any of these characters, we
869 # assume it's a binary and exec it directly. This means for
869 # assume it's a binary and exec it directly. This means for
870 # simple pager command configurations, we can degrade
870 # simple pager command configurations, we can degrade
871 # gracefully and tell the user about their broken pager.
871 # gracefully and tell the user about their broken pager.
872 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
872 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
873 try:
873 try:
874 pager = subprocess.Popen(
874 pager = subprocess.Popen(
875 command, shell=shell, bufsize=-1,
875 command, shell=shell, bufsize=-1,
876 close_fds=util.closefds, stdin=subprocess.PIPE,
876 close_fds=util.closefds, stdin=subprocess.PIPE,
877 stdout=util.stdout, stderr=util.stderr)
877 stdout=util.stdout, stderr=util.stderr)
878 except OSError as e:
878 except OSError as e:
879 if e.errno == errno.ENOENT and not shell:
879 if e.errno == errno.ENOENT and not shell:
880 self.warn(_("missing pager command '%s', skipping pager\n")
880 self.warn(_("missing pager command '%s', skipping pager\n")
881 % command)
881 % command)
882 return
882 return
883 raise
883 raise
884
884
885 # back up original file descriptors
885 # back up original file descriptors
886 stdoutfd = os.dup(util.stdout.fileno())
886 stdoutfd = os.dup(util.stdout.fileno())
887 stderrfd = os.dup(util.stderr.fileno())
887 stderrfd = os.dup(util.stderr.fileno())
888
888
889 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
889 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
890 if self._isatty(util.stderr):
890 if self._isatty(util.stderr):
891 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
891 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
892
892
893 @atexit.register
893 @atexit.register
894 def killpager():
894 def killpager():
895 if util.safehasattr(signal, "SIGINT"):
895 if util.safehasattr(signal, "SIGINT"):
896 signal.signal(signal.SIGINT, signal.SIG_IGN)
896 signal.signal(signal.SIGINT, signal.SIG_IGN)
897 # restore original fds, closing pager.stdin copies in the process
897 # restore original fds, closing pager.stdin copies in the process
898 os.dup2(stdoutfd, util.stdout.fileno())
898 os.dup2(stdoutfd, util.stdout.fileno())
899 os.dup2(stderrfd, util.stderr.fileno())
899 os.dup2(stderrfd, util.stderr.fileno())
900 pager.stdin.close()
900 pager.stdin.close()
901 pager.wait()
901 pager.wait()
902
902
903 def interface(self, feature):
903 def interface(self, feature):
904 """what interface to use for interactive console features?
904 """what interface to use for interactive console features?
905
905
906 The interface is controlled by the value of `ui.interface` but also by
906 The interface is controlled by the value of `ui.interface` but also by
907 the value of feature-specific configuration. For example:
907 the value of feature-specific configuration. For example:
908
908
909 ui.interface.histedit = text
909 ui.interface.histedit = text
910 ui.interface.chunkselector = curses
910 ui.interface.chunkselector = curses
911
911
912 Here the features are "histedit" and "chunkselector".
912 Here the features are "histedit" and "chunkselector".
913
913
914 The configuration above means that the default interfaces for commands
914 The configuration above means that the default interfaces for commands
915 is curses, the interface for histedit is text and the interface for
915 is curses, the interface for histedit is text and the interface for
916 selecting chunk is crecord (the best curses interface available).
916 selecting chunk is crecord (the best curses interface available).
917
917
918 Consider the following example:
918 Consider the following example:
919 ui.interface = curses
919 ui.interface = curses
920 ui.interface.histedit = text
920 ui.interface.histedit = text
921
921
922 Then histedit will use the text interface and chunkselector will use
922 Then histedit will use the text interface and chunkselector will use
923 the default curses interface (crecord at the moment).
923 the default curses interface (crecord at the moment).
924 """
924 """
925 alldefaults = frozenset(["text", "curses"])
925 alldefaults = frozenset(["text", "curses"])
926
926
927 featureinterfaces = {
927 featureinterfaces = {
928 "chunkselector": [
928 "chunkselector": [
929 "text",
929 "text",
930 "curses",
930 "curses",
931 ]
931 ]
932 }
932 }
933
933
934 # Feature-specific interface
934 # Feature-specific interface
935 if feature not in featureinterfaces.keys():
935 if feature not in featureinterfaces.keys():
936 # Programming error, not user error
936 # Programming error, not user error
937 raise ValueError("Unknown feature requested %s" % feature)
937 raise ValueError("Unknown feature requested %s" % feature)
938
938
939 availableinterfaces = frozenset(featureinterfaces[feature])
939 availableinterfaces = frozenset(featureinterfaces[feature])
940 if alldefaults > availableinterfaces:
940 if alldefaults > availableinterfaces:
941 # Programming error, not user error. We need a use case to
941 # Programming error, not user error. We need a use case to
942 # define the right thing to do here.
942 # define the right thing to do here.
943 raise ValueError(
943 raise ValueError(
944 "Feature %s does not handle all default interfaces" %
944 "Feature %s does not handle all default interfaces" %
945 feature)
945 feature)
946
946
947 if self.plain():
947 if self.plain():
948 return "text"
948 return "text"
949
949
950 # Default interface for all the features
950 # Default interface for all the features
951 defaultinterface = "text"
951 defaultinterface = "text"
952 i = self.config("ui", "interface", None)
952 i = self.config("ui", "interface", None)
953 if i in alldefaults:
953 if i in alldefaults:
954 defaultinterface = i
954 defaultinterface = i
955
955
956 choseninterface = defaultinterface
956 choseninterface = defaultinterface
957 f = self.config("ui", "interface.%s" % feature, None)
957 f = self.config("ui", "interface.%s" % feature, None)
958 if f in availableinterfaces:
958 if f in availableinterfaces:
959 choseninterface = f
959 choseninterface = f
960
960
961 if i is not None and defaultinterface != i:
961 if i is not None and defaultinterface != i:
962 if f is not None:
962 if f is not None:
963 self.warn(_("invalid value for ui.interface: %s\n") %
963 self.warn(_("invalid value for ui.interface: %s\n") %
964 (i,))
964 (i,))
965 else:
965 else:
966 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
966 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
967 (i, choseninterface))
967 (i, choseninterface))
968 if f is not None and choseninterface != f:
968 if f is not None and choseninterface != f:
969 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
969 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
970 (feature, f, choseninterface))
970 (feature, f, choseninterface))
971
971
972 return choseninterface
972 return choseninterface
973
973
974 def interactive(self):
974 def interactive(self):
975 '''is interactive input allowed?
975 '''is interactive input allowed?
976
976
977 An interactive session is a session where input can be reasonably read
977 An interactive session is a session where input can be reasonably read
978 from `sys.stdin'. If this function returns false, any attempt to read
978 from `sys.stdin'. If this function returns false, any attempt to read
979 from stdin should fail with an error, unless a sensible default has been
979 from stdin should fail with an error, unless a sensible default has been
980 specified.
980 specified.
981
981
982 Interactiveness is triggered by the value of the `ui.interactive'
982 Interactiveness is triggered by the value of the `ui.interactive'
983 configuration variable or - if it is unset - when `sys.stdin' points
983 configuration variable or - if it is unset - when `sys.stdin' points
984 to a terminal device.
984 to a terminal device.
985
985
986 This function refers to input only; for output, see `ui.formatted()'.
986 This function refers to input only; for output, see `ui.formatted()'.
987 '''
987 '''
988 i = self.configbool("ui", "interactive", None)
988 i = self.configbool("ui", "interactive", None)
989 if i is None:
989 if i is None:
990 # some environments replace stdin without implementing isatty
990 # some environments replace stdin without implementing isatty
991 # usually those are non-interactive
991 # usually those are non-interactive
992 return self._isatty(self.fin)
992 return self._isatty(self.fin)
993
993
994 return i
994 return i
995
995
996 def termwidth(self):
996 def termwidth(self):
997 '''how wide is the terminal in columns?
997 '''how wide is the terminal in columns?
998 '''
998 '''
999 if 'COLUMNS' in encoding.environ:
999 if 'COLUMNS' in encoding.environ:
1000 try:
1000 try:
1001 return int(encoding.environ['COLUMNS'])
1001 return int(encoding.environ['COLUMNS'])
1002 except ValueError:
1002 except ValueError:
1003 pass
1003 pass
1004 return scmutil.termsize(self)[0]
1004 return scmutil.termsize(self)[0]
1005
1005
1006 def formatted(self):
1006 def formatted(self):
1007 '''should formatted output be used?
1007 '''should formatted output be used?
1008
1008
1009 It is often desirable to format the output to suite the output medium.
1009 It is often desirable to format the output to suite the output medium.
1010 Examples of this are truncating long lines or colorizing messages.
1010 Examples of this are truncating long lines or colorizing messages.
1011 However, this is not often not desirable when piping output into other
1011 However, this is not often not desirable when piping output into other
1012 utilities, e.g. `grep'.
1012 utilities, e.g. `grep'.
1013
1013
1014 Formatted output is triggered by the value of the `ui.formatted'
1014 Formatted output is triggered by the value of the `ui.formatted'
1015 configuration variable or - if it is unset - when `sys.stdout' points
1015 configuration variable or - if it is unset - when `sys.stdout' points
1016 to a terminal device. Please note that `ui.formatted' should be
1016 to a terminal device. Please note that `ui.formatted' should be
1017 considered an implementation detail; it is not intended for use outside
1017 considered an implementation detail; it is not intended for use outside
1018 Mercurial or its extensions.
1018 Mercurial or its extensions.
1019
1019
1020 This function refers to output only; for input, see `ui.interactive()'.
1020 This function refers to output only; for input, see `ui.interactive()'.
1021 This function always returns false when in plain mode, see `ui.plain()'.
1021 This function always returns false when in plain mode, see `ui.plain()'.
1022 '''
1022 '''
1023 if self.plain():
1023 if self.plain():
1024 return False
1024 return False
1025
1025
1026 i = self.configbool("ui", "formatted", None)
1026 i = self.configbool("ui", "formatted", None)
1027 if i is None:
1027 if i is None:
1028 # some environments replace stdout without implementing isatty
1028 # some environments replace stdout without implementing isatty
1029 # usually those are non-interactive
1029 # usually those are non-interactive
1030 return self._isatty(self.fout)
1030 return self._isatty(self.fout)
1031
1031
1032 return i
1032 return i
1033
1033
1034 def _readline(self, prompt=''):
1034 def _readline(self, prompt=''):
1035 if self._isatty(self.fin):
1035 if self._isatty(self.fin):
1036 try:
1036 try:
1037 # magically add command line editing support, where
1037 # magically add command line editing support, where
1038 # available
1038 # available
1039 import readline
1039 import readline
1040 # force demandimport to really load the module
1040 # force demandimport to really load the module
1041 readline.read_history_file
1041 readline.read_history_file
1042 # windows sometimes raises something other than ImportError
1042 # windows sometimes raises something other than ImportError
1043 except Exception:
1043 except Exception:
1044 pass
1044 pass
1045
1045
1046 # call write() so output goes through subclassed implementation
1046 # call write() so output goes through subclassed implementation
1047 # e.g. color extension on Windows
1047 # e.g. color extension on Windows
1048 self.write(prompt, prompt=True)
1048 self.write(prompt, prompt=True)
1049
1049
1050 # instead of trying to emulate raw_input, swap (self.fin,
1050 # instead of trying to emulate raw_input, swap (self.fin,
1051 # self.fout) with (sys.stdin, sys.stdout)
1051 # self.fout) with (sys.stdin, sys.stdout)
1052 oldin = sys.stdin
1052 oldin = sys.stdin
1053 oldout = sys.stdout
1053 oldout = sys.stdout
1054 sys.stdin = self.fin
1054 sys.stdin = self.fin
1055 sys.stdout = self.fout
1055 sys.stdout = self.fout
1056 # prompt ' ' must exist; otherwise readline may delete entire line
1056 # prompt ' ' must exist; otherwise readline may delete entire line
1057 # - http://bugs.python.org/issue12833
1057 # - http://bugs.python.org/issue12833
1058 with self.timeblockedsection('stdio'):
1058 with self.timeblockedsection('stdio'):
1059 line = raw_input(' ')
1059 line = raw_input(' ')
1060 sys.stdin = oldin
1060 sys.stdin = oldin
1061 sys.stdout = oldout
1061 sys.stdout = oldout
1062
1062
1063 # When stdin is in binary mode on Windows, it can cause
1063 # When stdin is in binary mode on Windows, it can cause
1064 # raw_input() to emit an extra trailing carriage return
1064 # raw_input() to emit an extra trailing carriage return
1065 if os.linesep == '\r\n' and line and line[-1] == '\r':
1065 if os.linesep == '\r\n' and line and line[-1] == '\r':
1066 line = line[:-1]
1066 line = line[:-1]
1067 return line
1067 return line
1068
1068
1069 def prompt(self, msg, default="y"):
1069 def prompt(self, msg, default="y"):
1070 """Prompt user with msg, read response.
1070 """Prompt user with msg, read response.
1071 If ui is not interactive, the default is returned.
1071 If ui is not interactive, the default is returned.
1072 """
1072 """
1073 if not self.interactive():
1073 if not self.interactive():
1074 self.write(msg, ' ', default or '', "\n")
1074 self.write(msg, ' ', default or '', "\n")
1075 return default
1075 return default
1076 try:
1076 try:
1077 r = self._readline(self.label(msg, 'ui.prompt'))
1077 r = self._readline(self.label(msg, 'ui.prompt'))
1078 if not r:
1078 if not r:
1079 r = default
1079 r = default
1080 if self.configbool('ui', 'promptecho'):
1080 if self.configbool('ui', 'promptecho'):
1081 self.write(r, "\n")
1081 self.write(r, "\n")
1082 return r
1082 return r
1083 except EOFError:
1083 except EOFError:
1084 raise error.ResponseExpected()
1084 raise error.ResponseExpected()
1085
1085
1086 @staticmethod
1086 @staticmethod
1087 def extractchoices(prompt):
1087 def extractchoices(prompt):
1088 """Extract prompt message and list of choices from specified prompt.
1088 """Extract prompt message and list of choices from specified prompt.
1089
1089
1090 This returns tuple "(message, choices)", and "choices" is the
1090 This returns tuple "(message, choices)", and "choices" is the
1091 list of tuple "(response character, text without &)".
1091 list of tuple "(response character, text without &)".
1092
1092
1093 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1093 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1094 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1094 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1095 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1095 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1096 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1096 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1097 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1097 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1098 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1098 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1099 """
1099 """
1100
1100
1101 # Sadly, the prompt string may have been built with a filename
1101 # Sadly, the prompt string may have been built with a filename
1102 # containing "$$" so let's try to find the first valid-looking
1102 # containing "$$" so let's try to find the first valid-looking
1103 # prompt to start parsing. Sadly, we also can't rely on
1103 # prompt to start parsing. Sadly, we also can't rely on
1104 # choices containing spaces, ASCII, or basically anything
1104 # choices containing spaces, ASCII, or basically anything
1105 # except an ampersand followed by a character.
1105 # except an ampersand followed by a character.
1106 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1106 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1107 msg = m.group(1)
1107 msg = m.group(1)
1108 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1108 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1109 return (msg,
1109 return (msg,
1110 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1110 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1111 for s in choices])
1111 for s in choices])
1112
1112
1113 def promptchoice(self, prompt, default=0):
1113 def promptchoice(self, prompt, default=0):
1114 """Prompt user with a message, read response, and ensure it matches
1114 """Prompt user with a message, read response, and ensure it matches
1115 one of the provided choices. The prompt is formatted as follows:
1115 one of the provided choices. The prompt is formatted as follows:
1116
1116
1117 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1117 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1118
1118
1119 The index of the choice is returned. Responses are case
1119 The index of the choice is returned. Responses are case
1120 insensitive. If ui is not interactive, the default is
1120 insensitive. If ui is not interactive, the default is
1121 returned.
1121 returned.
1122 """
1122 """
1123
1123
1124 msg, choices = self.extractchoices(prompt)
1124 msg, choices = self.extractchoices(prompt)
1125 resps = [r for r, t in choices]
1125 resps = [r for r, t in choices]
1126 while True:
1126 while True:
1127 r = self.prompt(msg, resps[default])
1127 r = self.prompt(msg, resps[default])
1128 if r.lower() in resps:
1128 if r.lower() in resps:
1129 return resps.index(r.lower())
1129 return resps.index(r.lower())
1130 self.write(_("unrecognized response\n"))
1130 self.write(_("unrecognized response\n"))
1131
1131
1132 def getpass(self, prompt=None, default=None):
1132 def getpass(self, prompt=None, default=None):
1133 if not self.interactive():
1133 if not self.interactive():
1134 return default
1134 return default
1135 try:
1135 try:
1136 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1136 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1137 # disable getpass() only if explicitly specified. it's still valid
1137 # disable getpass() only if explicitly specified. it's still valid
1138 # to interact with tty even if fin is not a tty.
1138 # to interact with tty even if fin is not a tty.
1139 with self.timeblockedsection('stdio'):
1139 with self.timeblockedsection('stdio'):
1140 if self.configbool('ui', 'nontty'):
1140 if self.configbool('ui', 'nontty'):
1141 l = self.fin.readline()
1141 l = self.fin.readline()
1142 if not l:
1142 if not l:
1143 raise EOFError
1143 raise EOFError
1144 return l.rstrip('\n')
1144 return l.rstrip('\n')
1145 else:
1145 else:
1146 return getpass.getpass('')
1146 return getpass.getpass('')
1147 except EOFError:
1147 except EOFError:
1148 raise error.ResponseExpected()
1148 raise error.ResponseExpected()
1149 def status(self, *msg, **opts):
1149 def status(self, *msg, **opts):
1150 '''write status message to output (if ui.quiet is False)
1150 '''write status message to output (if ui.quiet is False)
1151
1151
1152 This adds an output label of "ui.status".
1152 This adds an output label of "ui.status".
1153 '''
1153 '''
1154 if not self.quiet:
1154 if not self.quiet:
1155 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1155 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1156 self.write(*msg, **opts)
1156 self.write(*msg, **opts)
1157 def warn(self, *msg, **opts):
1157 def warn(self, *msg, **opts):
1158 '''write warning message to output (stderr)
1158 '''write warning message to output (stderr)
1159
1159
1160 This adds an output label of "ui.warning".
1160 This adds an output label of "ui.warning".
1161 '''
1161 '''
1162 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1162 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1163 self.write_err(*msg, **opts)
1163 self.write_err(*msg, **opts)
1164 def note(self, *msg, **opts):
1164 def note(self, *msg, **opts):
1165 '''write note to output (if ui.verbose is True)
1165 '''write note to output (if ui.verbose is True)
1166
1166
1167 This adds an output label of "ui.note".
1167 This adds an output label of "ui.note".
1168 '''
1168 '''
1169 if self.verbose:
1169 if self.verbose:
1170 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1170 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1171 self.write(*msg, **opts)
1171 self.write(*msg, **opts)
1172 def debug(self, *msg, **opts):
1172 def debug(self, *msg, **opts):
1173 '''write debug message to output (if ui.debugflag is True)
1173 '''write debug message to output (if ui.debugflag is True)
1174
1174
1175 This adds an output label of "ui.debug".
1175 This adds an output label of "ui.debug".
1176 '''
1176 '''
1177 if self.debugflag:
1177 if self.debugflag:
1178 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1178 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1179 self.write(*msg, **opts)
1179 self.write(*msg, **opts)
1180
1180
1181 def edit(self, text, user, extra=None, editform=None, pending=None,
1181 def edit(self, text, user, extra=None, editform=None, pending=None,
1182 repopath=None):
1182 repopath=None):
1183 extra_defaults = {
1183 extra_defaults = {
1184 'prefix': 'editor',
1184 'prefix': 'editor',
1185 'suffix': '.txt',
1185 'suffix': '.txt',
1186 }
1186 }
1187 if extra is not None:
1187 if extra is not None:
1188 extra_defaults.update(extra)
1188 extra_defaults.update(extra)
1189 extra = extra_defaults
1189 extra = extra_defaults
1190
1190
1191 rdir = None
1191 rdir = None
1192 if self.configbool('experimental', 'editortmpinhg'):
1192 if self.configbool('experimental', 'editortmpinhg'):
1193 rdir = repopath
1193 rdir = repopath
1194 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1194 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1195 suffix=extra['suffix'], text=True,
1195 suffix=extra['suffix'], text=True,
1196 dir=rdir)
1196 dir=rdir)
1197 try:
1197 try:
1198 f = os.fdopen(fd, pycompat.sysstr("w"))
1198 f = os.fdopen(fd, pycompat.sysstr("w"))
1199 f.write(encoding.strfromlocal(text))
1199 f.write(encoding.strfromlocal(text))
1200 f.close()
1200 f.close()
1201
1201
1202 environ = {'HGUSER': user}
1202 environ = {'HGUSER': user}
1203 if 'transplant_source' in extra:
1203 if 'transplant_source' in extra:
1204 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1204 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1205 for label in ('intermediate-source', 'source', 'rebase_source'):
1205 for label in ('intermediate-source', 'source', 'rebase_source'):
1206 if label in extra:
1206 if label in extra:
1207 environ.update({'HGREVISION': extra[label]})
1207 environ.update({'HGREVISION': extra[label]})
1208 break
1208 break
1209 if editform:
1209 if editform:
1210 environ.update({'HGEDITFORM': editform})
1210 environ.update({'HGEDITFORM': editform})
1211 if pending:
1211 if pending:
1212 environ.update({'HG_PENDING': pending})
1212 environ.update({'HG_PENDING': pending})
1213
1213
1214 editor = self.geteditor()
1214 editor = self.geteditor()
1215
1215
1216 self.system("%s \"%s\"" % (editor, name),
1216 self.system("%s \"%s\"" % (editor, name),
1217 environ=environ,
1217 environ=environ,
1218 onerr=error.Abort, errprefix=_("edit failed"),
1218 onerr=error.Abort, errprefix=_("edit failed"),
1219 blockedtag='editor')
1219 blockedtag='editor')
1220
1220
1221 f = open(name)
1221 f = open(name)
1222 t = encoding.strtolocal(f.read())
1222 t = encoding.strtolocal(f.read())
1223 f.close()
1223 f.close()
1224 finally:
1224 finally:
1225 os.unlink(name)
1225 os.unlink(name)
1226
1226
1227 return t
1227 return t
1228
1228
1229 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1229 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1230 blockedtag=None):
1230 blockedtag=None):
1231 '''execute shell command with appropriate output stream. command
1231 '''execute shell command with appropriate output stream. command
1232 output will be redirected if fout is not stdout.
1232 output will be redirected if fout is not stdout.
1233
1233
1234 if command fails and onerr is None, return status, else raise onerr
1234 if command fails and onerr is None, return status, else raise onerr
1235 object as exception.
1235 object as exception.
1236 '''
1236 '''
1237 if blockedtag is None:
1237 if blockedtag is None:
1238 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1238 # Long cmds tend to be because of an absolute path on cmd. Keep
1239 # the tail end instead
1240 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1241 blockedtag = 'unknown_system_' + cmdsuffix
1239 out = self.fout
1242 out = self.fout
1240 if any(s[1] for s in self._bufferstates):
1243 if any(s[1] for s in self._bufferstates):
1241 out = self
1244 out = self
1242 with self.timeblockedsection(blockedtag):
1245 with self.timeblockedsection(blockedtag):
1243 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1246 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1244 if rc and onerr:
1247 if rc and onerr:
1245 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1248 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1246 util.explainexit(rc)[0])
1249 util.explainexit(rc)[0])
1247 if errprefix:
1250 if errprefix:
1248 errmsg = '%s: %s' % (errprefix, errmsg)
1251 errmsg = '%s: %s' % (errprefix, errmsg)
1249 raise onerr(errmsg)
1252 raise onerr(errmsg)
1250 return rc
1253 return rc
1251
1254
1252 def _runsystem(self, cmd, environ, cwd, out):
1255 def _runsystem(self, cmd, environ, cwd, out):
1253 """actually execute the given shell command (can be overridden by
1256 """actually execute the given shell command (can be overridden by
1254 extensions like chg)"""
1257 extensions like chg)"""
1255 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1258 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1256
1259
1257 def traceback(self, exc=None, force=False):
1260 def traceback(self, exc=None, force=False):
1258 '''print exception traceback if traceback printing enabled or forced.
1261 '''print exception traceback if traceback printing enabled or forced.
1259 only to call in exception handler. returns true if traceback
1262 only to call in exception handler. returns true if traceback
1260 printed.'''
1263 printed.'''
1261 if self.tracebackflag or force:
1264 if self.tracebackflag or force:
1262 if exc is None:
1265 if exc is None:
1263 exc = sys.exc_info()
1266 exc = sys.exc_info()
1264 cause = getattr(exc[1], 'cause', None)
1267 cause = getattr(exc[1], 'cause', None)
1265
1268
1266 if cause is not None:
1269 if cause is not None:
1267 causetb = traceback.format_tb(cause[2])
1270 causetb = traceback.format_tb(cause[2])
1268 exctb = traceback.format_tb(exc[2])
1271 exctb = traceback.format_tb(exc[2])
1269 exconly = traceback.format_exception_only(cause[0], cause[1])
1272 exconly = traceback.format_exception_only(cause[0], cause[1])
1270
1273
1271 # exclude frame where 'exc' was chained and rethrown from exctb
1274 # exclude frame where 'exc' was chained and rethrown from exctb
1272 self.write_err('Traceback (most recent call last):\n',
1275 self.write_err('Traceback (most recent call last):\n',
1273 ''.join(exctb[:-1]),
1276 ''.join(exctb[:-1]),
1274 ''.join(causetb),
1277 ''.join(causetb),
1275 ''.join(exconly))
1278 ''.join(exconly))
1276 else:
1279 else:
1277 output = traceback.format_exception(exc[0], exc[1], exc[2])
1280 output = traceback.format_exception(exc[0], exc[1], exc[2])
1278 data = r''.join(output)
1281 data = r''.join(output)
1279 if pycompat.ispy3:
1282 if pycompat.ispy3:
1280 enc = pycompat.sysstr(encoding.encoding)
1283 enc = pycompat.sysstr(encoding.encoding)
1281 data = data.encode(enc, errors=r'replace')
1284 data = data.encode(enc, errors=r'replace')
1282 self.write_err(data)
1285 self.write_err(data)
1283 return self.tracebackflag or force
1286 return self.tracebackflag or force
1284
1287
1285 def geteditor(self):
1288 def geteditor(self):
1286 '''return editor to use'''
1289 '''return editor to use'''
1287 if pycompat.sysplatform == 'plan9':
1290 if pycompat.sysplatform == 'plan9':
1288 # vi is the MIPS instruction simulator on Plan 9. We
1291 # vi is the MIPS instruction simulator on Plan 9. We
1289 # instead default to E to plumb commit messages to
1292 # instead default to E to plumb commit messages to
1290 # avoid confusion.
1293 # avoid confusion.
1291 editor = 'E'
1294 editor = 'E'
1292 else:
1295 else:
1293 editor = 'vi'
1296 editor = 'vi'
1294 return (encoding.environ.get("HGEDITOR") or
1297 return (encoding.environ.get("HGEDITOR") or
1295 self.config("ui", "editor") or
1298 self.config("ui", "editor") or
1296 encoding.environ.get("VISUAL") or
1299 encoding.environ.get("VISUAL") or
1297 encoding.environ.get("EDITOR", editor))
1300 encoding.environ.get("EDITOR", editor))
1298
1301
1299 @util.propertycache
1302 @util.propertycache
1300 def _progbar(self):
1303 def _progbar(self):
1301 """setup the progbar singleton to the ui object"""
1304 """setup the progbar singleton to the ui object"""
1302 if (self.quiet or self.debugflag
1305 if (self.quiet or self.debugflag
1303 or self.configbool('progress', 'disable', False)
1306 or self.configbool('progress', 'disable', False)
1304 or not progress.shouldprint(self)):
1307 or not progress.shouldprint(self)):
1305 return None
1308 return None
1306 return getprogbar(self)
1309 return getprogbar(self)
1307
1310
1308 def _progclear(self):
1311 def _progclear(self):
1309 """clear progress bar output if any. use it before any output"""
1312 """clear progress bar output if any. use it before any output"""
1310 if '_progbar' not in vars(self): # nothing loaded yet
1313 if '_progbar' not in vars(self): # nothing loaded yet
1311 return
1314 return
1312 if self._progbar is not None and self._progbar.printed:
1315 if self._progbar is not None and self._progbar.printed:
1313 self._progbar.clear()
1316 self._progbar.clear()
1314
1317
1315 def progress(self, topic, pos, item="", unit="", total=None):
1318 def progress(self, topic, pos, item="", unit="", total=None):
1316 '''show a progress message
1319 '''show a progress message
1317
1320
1318 By default a textual progress bar will be displayed if an operation
1321 By default a textual progress bar will be displayed if an operation
1319 takes too long. 'topic' is the current operation, 'item' is a
1322 takes too long. 'topic' is the current operation, 'item' is a
1320 non-numeric marker of the current position (i.e. the currently
1323 non-numeric marker of the current position (i.e. the currently
1321 in-process file), 'pos' is the current numeric position (i.e.
1324 in-process file), 'pos' is the current numeric position (i.e.
1322 revision, bytes, etc.), unit is a corresponding unit label,
1325 revision, bytes, etc.), unit is a corresponding unit label,
1323 and total is the highest expected pos.
1326 and total is the highest expected pos.
1324
1327
1325 Multiple nested topics may be active at a time.
1328 Multiple nested topics may be active at a time.
1326
1329
1327 All topics should be marked closed by setting pos to None at
1330 All topics should be marked closed by setting pos to None at
1328 termination.
1331 termination.
1329 '''
1332 '''
1330 if self._progbar is not None:
1333 if self._progbar is not None:
1331 self._progbar.progress(topic, pos, item=item, unit=unit,
1334 self._progbar.progress(topic, pos, item=item, unit=unit,
1332 total=total)
1335 total=total)
1333 if pos is None or not self.configbool('progress', 'debug'):
1336 if pos is None or not self.configbool('progress', 'debug'):
1334 return
1337 return
1335
1338
1336 if unit:
1339 if unit:
1337 unit = ' ' + unit
1340 unit = ' ' + unit
1338 if item:
1341 if item:
1339 item = ' ' + item
1342 item = ' ' + item
1340
1343
1341 if total:
1344 if total:
1342 pct = 100.0 * pos / total
1345 pct = 100.0 * pos / total
1343 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1346 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1344 % (topic, item, pos, total, unit, pct))
1347 % (topic, item, pos, total, unit, pct))
1345 else:
1348 else:
1346 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1349 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1347
1350
1348 def log(self, service, *msg, **opts):
1351 def log(self, service, *msg, **opts):
1349 '''hook for logging facility extensions
1352 '''hook for logging facility extensions
1350
1353
1351 service should be a readily-identifiable subsystem, which will
1354 service should be a readily-identifiable subsystem, which will
1352 allow filtering.
1355 allow filtering.
1353
1356
1354 *msg should be a newline-terminated format string to log, and
1357 *msg should be a newline-terminated format string to log, and
1355 then any values to %-format into that format string.
1358 then any values to %-format into that format string.
1356
1359
1357 **opts currently has no defined meanings.
1360 **opts currently has no defined meanings.
1358 '''
1361 '''
1359
1362
1360 def label(self, msg, label):
1363 def label(self, msg, label):
1361 '''style msg based on supplied label
1364 '''style msg based on supplied label
1362
1365
1363 If some color mode is enabled, this will add the necessary control
1366 If some color mode is enabled, this will add the necessary control
1364 characters to apply such color. In addition, 'debug' color mode adds
1367 characters to apply such color. In addition, 'debug' color mode adds
1365 markup showing which label affects a piece of text.
1368 markup showing which label affects a piece of text.
1366
1369
1367 ui.write(s, 'label') is equivalent to
1370 ui.write(s, 'label') is equivalent to
1368 ui.write(ui.label(s, 'label')).
1371 ui.write(ui.label(s, 'label')).
1369 '''
1372 '''
1370 if self._colormode is not None:
1373 if self._colormode is not None:
1371 return color.colorlabel(self, msg, label)
1374 return color.colorlabel(self, msg, label)
1372 return msg
1375 return msg
1373
1376
1374 def develwarn(self, msg, stacklevel=1, config=None):
1377 def develwarn(self, msg, stacklevel=1, config=None):
1375 """issue a developer warning message
1378 """issue a developer warning message
1376
1379
1377 Use 'stacklevel' to report the offender some layers further up in the
1380 Use 'stacklevel' to report the offender some layers further up in the
1378 stack.
1381 stack.
1379 """
1382 """
1380 if not self.configbool('devel', 'all-warnings'):
1383 if not self.configbool('devel', 'all-warnings'):
1381 if config is not None and not self.configbool('devel', config):
1384 if config is not None and not self.configbool('devel', config):
1382 return
1385 return
1383 msg = 'devel-warn: ' + msg
1386 msg = 'devel-warn: ' + msg
1384 stacklevel += 1 # get in develwarn
1387 stacklevel += 1 # get in develwarn
1385 if self.tracebackflag:
1388 if self.tracebackflag:
1386 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1389 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1387 self.log('develwarn', '%s at:\n%s' %
1390 self.log('develwarn', '%s at:\n%s' %
1388 (msg, ''.join(util.getstackframes(stacklevel))))
1391 (msg, ''.join(util.getstackframes(stacklevel))))
1389 else:
1392 else:
1390 curframe = inspect.currentframe()
1393 curframe = inspect.currentframe()
1391 calframe = inspect.getouterframes(curframe, 2)
1394 calframe = inspect.getouterframes(curframe, 2)
1392 self.write_err('%s at: %s:%s (%s)\n'
1395 self.write_err('%s at: %s:%s (%s)\n'
1393 % ((msg,) + calframe[stacklevel][1:4]))
1396 % ((msg,) + calframe[stacklevel][1:4]))
1394 self.log('develwarn', '%s at: %s:%s (%s)\n',
1397 self.log('develwarn', '%s at: %s:%s (%s)\n',
1395 msg, *calframe[stacklevel][1:4])
1398 msg, *calframe[stacklevel][1:4])
1396 curframe = calframe = None # avoid cycles
1399 curframe = calframe = None # avoid cycles
1397
1400
1398 def deprecwarn(self, msg, version):
1401 def deprecwarn(self, msg, version):
1399 """issue a deprecation warning
1402 """issue a deprecation warning
1400
1403
1401 - msg: message explaining what is deprecated and how to upgrade,
1404 - msg: message explaining what is deprecated and how to upgrade,
1402 - version: last version where the API will be supported,
1405 - version: last version where the API will be supported,
1403 """
1406 """
1404 if not (self.configbool('devel', 'all-warnings')
1407 if not (self.configbool('devel', 'all-warnings')
1405 or self.configbool('devel', 'deprec-warn')):
1408 or self.configbool('devel', 'deprec-warn')):
1406 return
1409 return
1407 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1410 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1408 " update your code.)") % version
1411 " update your code.)") % version
1409 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1412 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1410
1413
1411 def exportableenviron(self):
1414 def exportableenviron(self):
1412 """The environment variables that are safe to export, e.g. through
1415 """The environment variables that are safe to export, e.g. through
1413 hgweb.
1416 hgweb.
1414 """
1417 """
1415 return self._exportableenviron
1418 return self._exportableenviron
1416
1419
1417 @contextlib.contextmanager
1420 @contextlib.contextmanager
1418 def configoverride(self, overrides, source=""):
1421 def configoverride(self, overrides, source=""):
1419 """Context manager for temporary config overrides
1422 """Context manager for temporary config overrides
1420 `overrides` must be a dict of the following structure:
1423 `overrides` must be a dict of the following structure:
1421 {(section, name) : value}"""
1424 {(section, name) : value}"""
1422 backups = {}
1425 backups = {}
1423 try:
1426 try:
1424 for (section, name), value in overrides.items():
1427 for (section, name), value in overrides.items():
1425 backups[(section, name)] = self.backupconfig(section, name)
1428 backups[(section, name)] = self.backupconfig(section, name)
1426 self.setconfig(section, name, value, source)
1429 self.setconfig(section, name, value, source)
1427 yield
1430 yield
1428 finally:
1431 finally:
1429 for __, backup in backups.items():
1432 for __, backup in backups.items():
1430 self.restoreconfig(backup)
1433 self.restoreconfig(backup)
1431 # just restoring ui.quiet config to the previous value is not enough
1434 # just restoring ui.quiet config to the previous value is not enough
1432 # as it does not update ui.quiet class member
1435 # as it does not update ui.quiet class member
1433 if ('ui', 'quiet') in overrides:
1436 if ('ui', 'quiet') in overrides:
1434 self.fixconfig(section='ui')
1437 self.fixconfig(section='ui')
1435
1438
1436 class paths(dict):
1439 class paths(dict):
1437 """Represents a collection of paths and their configs.
1440 """Represents a collection of paths and their configs.
1438
1441
1439 Data is initially derived from ui instances and the config files they have
1442 Data is initially derived from ui instances and the config files they have
1440 loaded.
1443 loaded.
1441 """
1444 """
1442 def __init__(self, ui):
1445 def __init__(self, ui):
1443 dict.__init__(self)
1446 dict.__init__(self)
1444
1447
1445 for name, loc in ui.configitems('paths', ignoresub=True):
1448 for name, loc in ui.configitems('paths', ignoresub=True):
1446 # No location is the same as not existing.
1449 # No location is the same as not existing.
1447 if not loc:
1450 if not loc:
1448 continue
1451 continue
1449 loc, sub = ui.configsuboptions('paths', name)
1452 loc, sub = ui.configsuboptions('paths', name)
1450 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1453 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1451
1454
1452 def getpath(self, name, default=None):
1455 def getpath(self, name, default=None):
1453 """Return a ``path`` from a string, falling back to default.
1456 """Return a ``path`` from a string, falling back to default.
1454
1457
1455 ``name`` can be a named path or locations. Locations are filesystem
1458 ``name`` can be a named path or locations. Locations are filesystem
1456 paths or URIs.
1459 paths or URIs.
1457
1460
1458 Returns None if ``name`` is not a registered path, a URI, or a local
1461 Returns None if ``name`` is not a registered path, a URI, or a local
1459 path to a repo.
1462 path to a repo.
1460 """
1463 """
1461 # Only fall back to default if no path was requested.
1464 # Only fall back to default if no path was requested.
1462 if name is None:
1465 if name is None:
1463 if not default:
1466 if not default:
1464 default = ()
1467 default = ()
1465 elif not isinstance(default, (tuple, list)):
1468 elif not isinstance(default, (tuple, list)):
1466 default = (default,)
1469 default = (default,)
1467 for k in default:
1470 for k in default:
1468 try:
1471 try:
1469 return self[k]
1472 return self[k]
1470 except KeyError:
1473 except KeyError:
1471 continue
1474 continue
1472 return None
1475 return None
1473
1476
1474 # Most likely empty string.
1477 # Most likely empty string.
1475 # This may need to raise in the future.
1478 # This may need to raise in the future.
1476 if not name:
1479 if not name:
1477 return None
1480 return None
1478
1481
1479 try:
1482 try:
1480 return self[name]
1483 return self[name]
1481 except KeyError:
1484 except KeyError:
1482 # Try to resolve as a local path or URI.
1485 # Try to resolve as a local path or URI.
1483 try:
1486 try:
1484 # We don't pass sub-options in, so no need to pass ui instance.
1487 # We don't pass sub-options in, so no need to pass ui instance.
1485 return path(None, None, rawloc=name)
1488 return path(None, None, rawloc=name)
1486 except ValueError:
1489 except ValueError:
1487 raise error.RepoError(_('repository %s does not exist') %
1490 raise error.RepoError(_('repository %s does not exist') %
1488 name)
1491 name)
1489
1492
1490 _pathsuboptions = {}
1493 _pathsuboptions = {}
1491
1494
1492 def pathsuboption(option, attr):
1495 def pathsuboption(option, attr):
1493 """Decorator used to declare a path sub-option.
1496 """Decorator used to declare a path sub-option.
1494
1497
1495 Arguments are the sub-option name and the attribute it should set on
1498 Arguments are the sub-option name and the attribute it should set on
1496 ``path`` instances.
1499 ``path`` instances.
1497
1500
1498 The decorated function will receive as arguments a ``ui`` instance,
1501 The decorated function will receive as arguments a ``ui`` instance,
1499 ``path`` instance, and the string value of this option from the config.
1502 ``path`` instance, and the string value of this option from the config.
1500 The function should return the value that will be set on the ``path``
1503 The function should return the value that will be set on the ``path``
1501 instance.
1504 instance.
1502
1505
1503 This decorator can be used to perform additional verification of
1506 This decorator can be used to perform additional verification of
1504 sub-options and to change the type of sub-options.
1507 sub-options and to change the type of sub-options.
1505 """
1508 """
1506 def register(func):
1509 def register(func):
1507 _pathsuboptions[option] = (attr, func)
1510 _pathsuboptions[option] = (attr, func)
1508 return func
1511 return func
1509 return register
1512 return register
1510
1513
1511 @pathsuboption('pushurl', 'pushloc')
1514 @pathsuboption('pushurl', 'pushloc')
1512 def pushurlpathoption(ui, path, value):
1515 def pushurlpathoption(ui, path, value):
1513 u = util.url(value)
1516 u = util.url(value)
1514 # Actually require a URL.
1517 # Actually require a URL.
1515 if not u.scheme:
1518 if not u.scheme:
1516 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1519 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1517 return None
1520 return None
1518
1521
1519 # Don't support the #foo syntax in the push URL to declare branch to
1522 # Don't support the #foo syntax in the push URL to declare branch to
1520 # push.
1523 # push.
1521 if u.fragment:
1524 if u.fragment:
1522 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1525 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1523 'ignoring)\n') % path.name)
1526 'ignoring)\n') % path.name)
1524 u.fragment = None
1527 u.fragment = None
1525
1528
1526 return str(u)
1529 return str(u)
1527
1530
1528 @pathsuboption('pushrev', 'pushrev')
1531 @pathsuboption('pushrev', 'pushrev')
1529 def pushrevpathoption(ui, path, value):
1532 def pushrevpathoption(ui, path, value):
1530 return value
1533 return value
1531
1534
1532 class path(object):
1535 class path(object):
1533 """Represents an individual path and its configuration."""
1536 """Represents an individual path and its configuration."""
1534
1537
1535 def __init__(self, ui, name, rawloc=None, suboptions=None):
1538 def __init__(self, ui, name, rawloc=None, suboptions=None):
1536 """Construct a path from its config options.
1539 """Construct a path from its config options.
1537
1540
1538 ``ui`` is the ``ui`` instance the path is coming from.
1541 ``ui`` is the ``ui`` instance the path is coming from.
1539 ``name`` is the symbolic name of the path.
1542 ``name`` is the symbolic name of the path.
1540 ``rawloc`` is the raw location, as defined in the config.
1543 ``rawloc`` is the raw location, as defined in the config.
1541 ``pushloc`` is the raw locations pushes should be made to.
1544 ``pushloc`` is the raw locations pushes should be made to.
1542
1545
1543 If ``name`` is not defined, we require that the location be a) a local
1546 If ``name`` is not defined, we require that the location be a) a local
1544 filesystem path with a .hg directory or b) a URL. If not,
1547 filesystem path with a .hg directory or b) a URL. If not,
1545 ``ValueError`` is raised.
1548 ``ValueError`` is raised.
1546 """
1549 """
1547 if not rawloc:
1550 if not rawloc:
1548 raise ValueError('rawloc must be defined')
1551 raise ValueError('rawloc must be defined')
1549
1552
1550 # Locations may define branches via syntax <base>#<branch>.
1553 # Locations may define branches via syntax <base>#<branch>.
1551 u = util.url(rawloc)
1554 u = util.url(rawloc)
1552 branch = None
1555 branch = None
1553 if u.fragment:
1556 if u.fragment:
1554 branch = u.fragment
1557 branch = u.fragment
1555 u.fragment = None
1558 u.fragment = None
1556
1559
1557 self.url = u
1560 self.url = u
1558 self.branch = branch
1561 self.branch = branch
1559
1562
1560 self.name = name
1563 self.name = name
1561 self.rawloc = rawloc
1564 self.rawloc = rawloc
1562 self.loc = '%s' % u
1565 self.loc = '%s' % u
1563
1566
1564 # When given a raw location but not a symbolic name, validate the
1567 # When given a raw location but not a symbolic name, validate the
1565 # location is valid.
1568 # location is valid.
1566 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1569 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1567 raise ValueError('location is not a URL or path to a local '
1570 raise ValueError('location is not a URL or path to a local '
1568 'repo: %s' % rawloc)
1571 'repo: %s' % rawloc)
1569
1572
1570 suboptions = suboptions or {}
1573 suboptions = suboptions or {}
1571
1574
1572 # Now process the sub-options. If a sub-option is registered, its
1575 # Now process the sub-options. If a sub-option is registered, its
1573 # attribute will always be present. The value will be None if there
1576 # attribute will always be present. The value will be None if there
1574 # was no valid sub-option.
1577 # was no valid sub-option.
1575 for suboption, (attr, func) in _pathsuboptions.iteritems():
1578 for suboption, (attr, func) in _pathsuboptions.iteritems():
1576 if suboption not in suboptions:
1579 if suboption not in suboptions:
1577 setattr(self, attr, None)
1580 setattr(self, attr, None)
1578 continue
1581 continue
1579
1582
1580 value = func(ui, self, suboptions[suboption])
1583 value = func(ui, self, suboptions[suboption])
1581 setattr(self, attr, value)
1584 setattr(self, attr, value)
1582
1585
1583 def _isvalidlocalpath(self, path):
1586 def _isvalidlocalpath(self, path):
1584 """Returns True if the given path is a potentially valid repository.
1587 """Returns True if the given path is a potentially valid repository.
1585 This is its own function so that extensions can change the definition of
1588 This is its own function so that extensions can change the definition of
1586 'valid' in this case (like when pulling from a git repo into a hg
1589 'valid' in this case (like when pulling from a git repo into a hg
1587 one)."""
1590 one)."""
1588 return os.path.isdir(os.path.join(path, '.hg'))
1591 return os.path.isdir(os.path.join(path, '.hg'))
1589
1592
1590 @property
1593 @property
1591 def suboptions(self):
1594 def suboptions(self):
1592 """Return sub-options and their values for this path.
1595 """Return sub-options and their values for this path.
1593
1596
1594 This is intended to be used for presentation purposes.
1597 This is intended to be used for presentation purposes.
1595 """
1598 """
1596 d = {}
1599 d = {}
1597 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1600 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1598 value = getattr(self, attr)
1601 value = getattr(self, attr)
1599 if value is not None:
1602 if value is not None:
1600 d[subopt] = value
1603 d[subopt] = value
1601 return d
1604 return d
1602
1605
1603 # we instantiate one globally shared progress bar to avoid
1606 # we instantiate one globally shared progress bar to avoid
1604 # competing progress bars when multiple UI objects get created
1607 # competing progress bars when multiple UI objects get created
1605 _progresssingleton = None
1608 _progresssingleton = None
1606
1609
1607 def getprogbar(ui):
1610 def getprogbar(ui):
1608 global _progresssingleton
1611 global _progresssingleton
1609 if _progresssingleton is None:
1612 if _progresssingleton is None:
1610 # passing 'ui' object to the singleton is fishy,
1613 # passing 'ui' object to the singleton is fishy,
1611 # this is how the extension used to work but feel free to rework it.
1614 # this is how the extension used to work but feel free to rework it.
1612 _progresssingleton = progress.progbar(ui)
1615 _progresssingleton = progress.progbar(ui)
1613 return _progresssingleton
1616 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now