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