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