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