##// END OF EJS Templates
ui: manage logger instances and event filtering by core ui...
Yuya Nishihara -
r40761:55b053af default
parent child Browse files
Show More
@@ -1,278 +1,264
1 # blackbox.py - log repository events to a file for post-mortem debugging
1 # blackbox.py - log repository events to a file for post-mortem debugging
2 #
2 #
3 # Copyright 2010 Nicolas Dumazet
3 # Copyright 2010 Nicolas Dumazet
4 # Copyright 2013 Facebook, Inc.
4 # Copyright 2013 Facebook, Inc.
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """log repository events to a blackbox for debugging
9 """log repository events to a blackbox for debugging
10
10
11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
12 The events that get logged can be configured via the blackbox.track config key.
12 The events that get logged can be configured via the blackbox.track config key.
13
13
14 Examples::
14 Examples::
15
15
16 [blackbox]
16 [blackbox]
17 track = *
17 track = *
18 # dirty is *EXPENSIVE* (slow);
18 # dirty is *EXPENSIVE* (slow);
19 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
19 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
20 dirty = True
20 dirty = True
21 # record the source of log messages
21 # record the source of log messages
22 logsource = True
22 logsource = True
23
23
24 [blackbox]
24 [blackbox]
25 track = command, commandfinish, commandexception, exthook, pythonhook
25 track = command, commandfinish, commandexception, exthook, pythonhook
26
26
27 [blackbox]
27 [blackbox]
28 track = incoming
28 track = incoming
29
29
30 [blackbox]
30 [blackbox]
31 # limit the size of a log file
31 # limit the size of a log file
32 maxsize = 1.5 MB
32 maxsize = 1.5 MB
33 # rotate up to N log files when the current one gets too big
33 # rotate up to N log files when the current one gets too big
34 maxfiles = 3
34 maxfiles = 3
35
35
36 [blackbox]
36 [blackbox]
37 # Include nanoseconds in log entries with %f (see Python function
37 # Include nanoseconds in log entries with %f (see Python function
38 # datetime.datetime.strftime)
38 # datetime.datetime.strftime)
39 date-format = '%Y-%m-%d @ %H:%M:%S.%f'
39 date-format = '%Y-%m-%d @ %H:%M:%S.%f'
40
40
41 """
41 """
42
42
43 from __future__ import absolute_import
43 from __future__ import absolute_import
44
44
45 import errno
45 import errno
46 import re
46 import re
47
47
48 from mercurial.i18n import _
48 from mercurial.i18n import _
49 from mercurial.node import hex
49 from mercurial.node import hex
50
50
51 from mercurial import (
51 from mercurial import (
52 encoding,
52 encoding,
53 pycompat,
53 pycompat,
54 registrar,
54 registrar,
55 ui as uimod,
55 ui as uimod,
56 util,
57 )
56 )
58 from mercurial.utils import (
57 from mercurial.utils import (
59 dateutil,
58 dateutil,
60 procutil,
59 procutil,
61 )
60 )
62
61
63 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
62 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
64 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
63 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
65 # be specifying the version(s) of Mercurial they are tested with, or
64 # be specifying the version(s) of Mercurial they are tested with, or
66 # leave the attribute unspecified.
65 # leave the attribute unspecified.
67 testedwith = 'ships-with-hg-core'
66 testedwith = 'ships-with-hg-core'
68
67
69 cmdtable = {}
68 cmdtable = {}
70 command = registrar.command(cmdtable)
69 command = registrar.command(cmdtable)
71
70
72 configtable = {}
71 configtable = {}
73 configitem = registrar.configitem(configtable)
72 configitem = registrar.configitem(configtable)
74
73
75 configitem('blackbox', 'dirty',
74 configitem('blackbox', 'dirty',
76 default=False,
75 default=False,
77 )
76 )
78 configitem('blackbox', 'maxsize',
77 configitem('blackbox', 'maxsize',
79 default='1 MB',
78 default='1 MB',
80 )
79 )
81 configitem('blackbox', 'logsource',
80 configitem('blackbox', 'logsource',
82 default=False,
81 default=False,
83 )
82 )
84 configitem('blackbox', 'maxfiles',
83 configitem('blackbox', 'maxfiles',
85 default=7,
84 default=7,
86 )
85 )
87 configitem('blackbox', 'track',
86 configitem('blackbox', 'track',
88 default=lambda: ['*'],
87 default=lambda: ['*'],
89 )
88 )
90 configitem('blackbox', 'date-format',
89 configitem('blackbox', 'date-format',
91 default='%Y/%m/%d %H:%M:%S',
90 default='%Y/%m/%d %H:%M:%S',
92 )
91 )
93
92
94 _lastlogger = None
93 _lastlogger = None
95
94
96 def _openlogfile(ui, vfs):
95 def _openlogfile(ui, vfs):
97 def rotate(oldpath, newpath):
96 def rotate(oldpath, newpath):
98 try:
97 try:
99 vfs.unlink(newpath)
98 vfs.unlink(newpath)
100 except OSError as err:
99 except OSError as err:
101 if err.errno != errno.ENOENT:
100 if err.errno != errno.ENOENT:
102 ui.debug("warning: cannot remove '%s': %s\n" %
101 ui.debug("warning: cannot remove '%s': %s\n" %
103 (newpath, err.strerror))
102 (newpath, err.strerror))
104 try:
103 try:
105 if newpath:
104 if newpath:
106 vfs.rename(oldpath, newpath)
105 vfs.rename(oldpath, newpath)
107 except OSError as err:
106 except OSError as err:
108 if err.errno != errno.ENOENT:
107 if err.errno != errno.ENOENT:
109 ui.debug("warning: cannot rename '%s' to '%s': %s\n" %
108 ui.debug("warning: cannot rename '%s' to '%s': %s\n" %
110 (newpath, oldpath, err.strerror))
109 (newpath, oldpath, err.strerror))
111
110
112 maxsize = ui.configbytes('blackbox', 'maxsize')
111 maxsize = ui.configbytes('blackbox', 'maxsize')
113 name = 'blackbox.log'
112 name = 'blackbox.log'
114 if maxsize > 0:
113 if maxsize > 0:
115 try:
114 try:
116 st = vfs.stat(name)
115 st = vfs.stat(name)
117 except OSError:
116 except OSError:
118 pass
117 pass
119 else:
118 else:
120 if st.st_size >= maxsize:
119 if st.st_size >= maxsize:
121 path = vfs.join(name)
120 path = vfs.join(name)
122 maxfiles = ui.configint('blackbox', 'maxfiles')
121 maxfiles = ui.configint('blackbox', 'maxfiles')
123 for i in pycompat.xrange(maxfiles - 1, 1, -1):
122 for i in pycompat.xrange(maxfiles - 1, 1, -1):
124 rotate(oldpath='%s.%d' % (path, i - 1),
123 rotate(oldpath='%s.%d' % (path, i - 1),
125 newpath='%s.%d' % (path, i))
124 newpath='%s.%d' % (path, i))
126 rotate(oldpath=path,
125 rotate(oldpath=path,
127 newpath=maxfiles > 0 and path + '.1')
126 newpath=maxfiles > 0 and path + '.1')
128 return vfs(name, 'a')
127 return vfs(name, 'a')
129
128
130 class blackboxlogger(object):
129 class blackboxlogger(object):
131 def __init__(self, ui):
130 def __init__(self, ui):
132 self._repo = None
131 self._repo = None
133 self._inlog = False
132 self._inlog = False
134 self._trackedevents = set(ui.configlist('blackbox', 'track'))
133 self._trackedevents = set(ui.configlist('blackbox', 'track'))
135
134
136 @property
135 @property
137 def _bbvfs(self):
136 def _bbvfs(self):
138 vfs = None
137 vfs = None
139 if self._repo:
138 if self._repo:
140 vfs = self._repo.vfs
139 vfs = self._repo.vfs
141 if not vfs.isdir('.'):
140 if not vfs.isdir('.'):
142 vfs = None
141 vfs = None
143 return vfs
142 return vfs
144
143
145 def tracked(self, event):
144 def tracked(self, event):
146 return b'*' in self._trackedevents or event in self._trackedevents
145 return b'*' in self._trackedevents or event in self._trackedevents
147
146
148 def log(self, ui, event, msg, opts):
147 def log(self, ui, event, msg, opts):
149 global _lastlogger
148 global _lastlogger
150 if not self.tracked(event):
151 return
152
153 if self._bbvfs:
149 if self._bbvfs:
154 _lastlogger = self
150 _lastlogger = self
155 elif _lastlogger and _lastlogger._bbvfs:
151 elif _lastlogger and _lastlogger._bbvfs:
156 # certain logger instances exist outside the context of
152 # certain logger instances exist outside the context of
157 # a repo, so just default to the last blackbox logger that
153 # a repo, so just default to the last blackbox logger that
158 # was seen.
154 # was seen.
159 pass
155 pass
160 else:
156 else:
161 return
157 return
162 _lastlogger._log(ui, event, msg, opts)
158 _lastlogger._log(ui, event, msg, opts)
163
159
164 def _log(self, ui, event, msg, opts):
160 def _log(self, ui, event, msg, opts):
165 if self._inlog:
161 if self._inlog:
166 # recursion and failure guard
162 # recursion and failure guard
167 return
163 return
168 self._inlog = True
164 self._inlog = True
169 default = ui.configdate('devel', 'default-date')
165 default = ui.configdate('devel', 'default-date')
170 date = dateutil.datestr(default, ui.config('blackbox', 'date-format'))
166 date = dateutil.datestr(default, ui.config('blackbox', 'date-format'))
171 user = procutil.getuser()
167 user = procutil.getuser()
172 pid = '%d' % procutil.getpid()
168 pid = '%d' % procutil.getpid()
173 formattedmsg = msg[0] % msg[1:]
169 formattedmsg = msg[0] % msg[1:]
174 rev = '(unknown)'
170 rev = '(unknown)'
175 changed = ''
171 changed = ''
176 ctx = self._repo[None]
172 ctx = self._repo[None]
177 parents = ctx.parents()
173 parents = ctx.parents()
178 rev = ('+'.join([hex(p.node()) for p in parents]))
174 rev = ('+'.join([hex(p.node()) for p in parents]))
179 if (ui.configbool('blackbox', 'dirty') and
175 if (ui.configbool('blackbox', 'dirty') and
180 ctx.dirty(missing=True, merge=False, branch=False)):
176 ctx.dirty(missing=True, merge=False, branch=False)):
181 changed = '+'
177 changed = '+'
182 if ui.configbool('blackbox', 'logsource'):
178 if ui.configbool('blackbox', 'logsource'):
183 src = ' [%s]' % event
179 src = ' [%s]' % event
184 else:
180 else:
185 src = ''
181 src = ''
186 try:
182 try:
187 fmt = '%s %s @%s%s (%s)%s> %s'
183 fmt = '%s %s @%s%s (%s)%s> %s'
188 args = (date, user, rev, changed, pid, src, formattedmsg)
184 args = (date, user, rev, changed, pid, src, formattedmsg)
189 with _openlogfile(ui, self._bbvfs) as fp:
185 with _openlogfile(ui, self._bbvfs) as fp:
190 fp.write(fmt % args)
186 fp.write(fmt % args)
191 except (IOError, OSError) as err:
187 except (IOError, OSError) as err:
192 ui.debug('warning: cannot write to blackbox.log: %s\n' %
188 ui.debug('warning: cannot write to blackbox.log: %s\n' %
193 encoding.strtolocal(err.strerror))
189 encoding.strtolocal(err.strerror))
194 # do not restore _inlog intentionally to avoid failed
190 # do not restore _inlog intentionally to avoid failed
195 # logging again
191 # logging again
196 else:
192 else:
197 self._inlog = False
193 self._inlog = False
198
194
199 def setrepo(self, repo):
195 def setrepo(self, repo):
200 self._repo = repo
196 self._repo = repo
201
197
202 def wrapui(ui):
198 def wrapui(ui):
203 class blackboxui(ui.__class__):
199 class blackboxui(ui.__class__):
204 def __init__(self, src=None):
205 super(blackboxui, self).__init__(src)
206 if src and r'_bblogger' in src.__dict__:
207 self._bblogger = src._bblogger
208
209 # trick to initialize logger after configuration is loaded, which
210 # can be replaced later with blackboxlogger(ui) in uisetup(), where
211 # both user and repo configurations should be available.
212 @util.propertycache
213 def _bblogger(self):
214 return blackboxlogger(self)
215
216 def debug(self, *msg, **opts):
200 def debug(self, *msg, **opts):
217 super(blackboxui, self).debug(*msg, **opts)
201 super(blackboxui, self).debug(*msg, **opts)
218 if self.debugflag:
202 if self.debugflag:
219 self.log('debug', '%s', ''.join(msg))
203 self.log('debug', '%s', ''.join(msg))
220
204
221 def log(self, event, *msg, **opts):
222 super(blackboxui, self).log(event, *msg, **opts)
223 self._bblogger.log(self, event, msg, opts)
224
225 ui.__class__ = blackboxui
205 ui.__class__ = blackboxui
226 uimod.ui = blackboxui
206 uimod.ui = blackboxui
227
207
228 def uisetup(ui):
208 def uisetup(ui):
229 wrapui(ui)
209 wrapui(ui)
230
210
211 def uipopulate(ui):
212 ui.setlogger(b'blackbox', blackboxlogger(ui))
213
231 def reposetup(ui, repo):
214 def reposetup(ui, repo):
232 # During 'hg pull' a httppeer repo is created to represent the remote repo.
215 # During 'hg pull' a httppeer repo is created to represent the remote repo.
233 # It doesn't have a .hg directory to put a blackbox in, so we don't do
216 # It doesn't have a .hg directory to put a blackbox in, so we don't do
234 # the blackbox setup for it.
217 # the blackbox setup for it.
235 if not repo.local():
218 if not repo.local():
236 return
219 return
237
220
238 logger = getattr(ui, '_bblogger', None)
221 # Since blackbox.log is stored in the repo directory, the logger should be
222 # instantiated per repository.
223 logger = blackboxlogger(ui)
224 ui.setlogger(b'blackbox', logger)
239 if logger:
225 if logger:
240 logger.setrepo(repo)
226 logger.setrepo(repo)
241
227
242 # Set _lastlogger even if ui.log is not called. This gives blackbox a
228 # Set _lastlogger even if ui.log is not called. This gives blackbox a
243 # fallback place to log.
229 # fallback place to log.
244 global _lastlogger
230 global _lastlogger
245 if _lastlogger is None:
231 if _lastlogger is None:
246 _lastlogger = logger
232 _lastlogger = logger
247
233
248 repo._wlockfreeprefix.add('blackbox.log')
234 repo._wlockfreeprefix.add('blackbox.log')
249
235
250 @command('blackbox',
236 @command('blackbox',
251 [('l', 'limit', 10, _('the number of events to show')),
237 [('l', 'limit', 10, _('the number of events to show')),
252 ],
238 ],
253 _('hg blackbox [OPTION]...'),
239 _('hg blackbox [OPTION]...'),
254 helpcategory=command.CATEGORY_MAINTENANCE,
240 helpcategory=command.CATEGORY_MAINTENANCE,
255 helpbasic=True)
241 helpbasic=True)
256 def blackbox(ui, repo, *revs, **opts):
242 def blackbox(ui, repo, *revs, **opts):
257 '''view the recent repository events
243 '''view the recent repository events
258 '''
244 '''
259
245
260 if not repo.vfs.exists('blackbox.log'):
246 if not repo.vfs.exists('blackbox.log'):
261 return
247 return
262
248
263 limit = opts.get(r'limit')
249 limit = opts.get(r'limit')
264 fp = repo.vfs('blackbox.log', 'r')
250 fp = repo.vfs('blackbox.log', 'r')
265 lines = fp.read().split('\n')
251 lines = fp.read().split('\n')
266
252
267 count = 0
253 count = 0
268 output = []
254 output = []
269 for line in reversed(lines):
255 for line in reversed(lines):
270 if count >= limit:
256 if count >= limit:
271 break
257 break
272
258
273 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
259 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
274 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
260 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
275 count += 1
261 count += 1
276 output.append(line)
262 output.append(line)
277
263
278 ui.status('\n'.join(reversed(output)))
264 ui.status('\n'.join(reversed(output)))
@@ -1,100 +1,78
1 # logtoprocess.py - send ui.log() data to a subprocess
1 # logtoprocess.py - send ui.log() data to a subprocess
2 #
2 #
3 # Copyright 2016 Facebook, Inc.
3 # Copyright 2016 Facebook, Inc.
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 """send ui.log() data to a subprocess (EXPERIMENTAL)
7 """send ui.log() data to a subprocess (EXPERIMENTAL)
8
8
9 This extension lets you specify a shell command per ui.log() event,
9 This extension lets you specify a shell command per ui.log() event,
10 sending all remaining arguments to as environment variables to that command.
10 sending all remaining arguments to as environment variables to that command.
11
11
12 Positional arguments construct a log message, which is passed in the `MSG1`
12 Positional arguments construct a log message, which is passed in the `MSG1`
13 environment variables. Each keyword argument is set as a `OPT_UPPERCASE_KEY`
13 environment variables. Each keyword argument is set as a `OPT_UPPERCASE_KEY`
14 variable (so the key is uppercased, and prefixed with `OPT_`). The original
14 variable (so the key is uppercased, and prefixed with `OPT_`). The original
15 event name is passed in the `EVENT` environment variable, and the process ID
15 event name is passed in the `EVENT` environment variable, and the process ID
16 of mercurial is given in `HGPID`.
16 of mercurial is given in `HGPID`.
17
17
18 So given a call `ui.log('foo', 'bar %s\n', 'baz', spam='eggs'), a script
18 So given a call `ui.log('foo', 'bar %s\n', 'baz', spam='eggs'), a script
19 configured for the `foo` event can expect an environment with `MSG1=bar baz`,
19 configured for the `foo` event can expect an environment with `MSG1=bar baz`,
20 and `OPT_SPAM=eggs`.
20 and `OPT_SPAM=eggs`.
21
21
22 Scripts are configured in the `[logtoprocess]` section, each key an event name.
22 Scripts are configured in the `[logtoprocess]` section, each key an event name.
23 For example::
23 For example::
24
24
25 [logtoprocess]
25 [logtoprocess]
26 commandexception = echo "$MSG1" > /var/log/mercurial_exceptions.log
26 commandexception = echo "$MSG1" > /var/log/mercurial_exceptions.log
27
27
28 would log the warning message and traceback of any failed command dispatch.
28 would log the warning message and traceback of any failed command dispatch.
29
29
30 Scripts are run asynchronously as detached daemon processes; mercurial will
30 Scripts are run asynchronously as detached daemon processes; mercurial will
31 not ensure that they exit cleanly.
31 not ensure that they exit cleanly.
32
32
33 """
33 """
34
34
35 from __future__ import absolute_import
35 from __future__ import absolute_import
36
36
37 import os
37 import os
38
38
39 from mercurial import (
39 from mercurial import (
40 pycompat,
40 pycompat,
41 util,
42 )
41 )
43 from mercurial.utils import (
42 from mercurial.utils import (
44 procutil,
43 procutil,
45 )
44 )
46
45
47 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
46 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
48 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
47 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
49 # be specifying the version(s) of Mercurial they are tested with, or
48 # be specifying the version(s) of Mercurial they are tested with, or
50 # leave the attribute unspecified.
49 # leave the attribute unspecified.
51 testedwith = 'ships-with-hg-core'
50 testedwith = 'ships-with-hg-core'
52
51
53 class processlogger(object):
52 class processlogger(object):
54 """Map log events to external commands
53 """Map log events to external commands
55
54
56 Arguments are passed on as environment variables.
55 Arguments are passed on as environment variables.
57 """
56 """
58
57
59 def __init__(self, ui):
58 def __init__(self, ui):
60 self._scripts = dict(ui.configitems(b'logtoprocess'))
59 self._scripts = dict(ui.configitems(b'logtoprocess'))
61
60
62 def tracked(self, event):
61 def tracked(self, event):
63 return bool(self._scripts.get(event))
62 return bool(self._scripts.get(event))
64
63
65 def log(self, ui, event, msg, opts):
64 def log(self, ui, event, msg, opts):
66 script = self._scripts.get(event)
65 script = self._scripts[event]
67 if not script:
68 return
69 env = {
66 env = {
70 b'EVENT': event,
67 b'EVENT': event,
71 b'HGPID': os.getpid(),
68 b'HGPID': os.getpid(),
72 b'MSG1': msg[0] % msg[1:],
69 b'MSG1': msg[0] % msg[1:],
73 }
70 }
74 # keyword arguments get prefixed with OPT_ and uppercased
71 # keyword arguments get prefixed with OPT_ and uppercased
75 env.update((b'OPT_%s' % key.upper(), value)
72 env.update((b'OPT_%s' % key.upper(), value)
76 for key, value in pycompat.byteskwargs(opts).items())
73 for key, value in pycompat.byteskwargs(opts).items())
77 fullenv = procutil.shellenviron(env)
74 fullenv = procutil.shellenviron(env)
78 procutil.runbgcommand(script, fullenv, shell=True)
75 procutil.runbgcommand(script, fullenv, shell=True)
79
76
80 def uisetup(ui):
77 def uipopulate(ui):
81
78 ui.setlogger(b'logtoprocess', processlogger(ui))
82 class logtoprocessui(ui.__class__):
83 def __init__(self, src=None):
84 super(logtoprocessui, self).__init__(src)
85 if src and r'_ltplogger' in src.__dict__:
86 self._ltplogger = src._ltplogger
87
88 # trick to initialize logger after configuration is loaded, which
89 # can be replaced later with processlogger(ui) in uisetup(), where
90 # both user and repo configurations should be available.
91 @util.propertycache
92 def _ltplogger(self):
93 return processlogger(self)
94
95 def log(self, event, *msg, **opts):
96 self._ltplogger.log(self, event, msg, opts)
97 return super(logtoprocessui, self).log(event, *msg, **opts)
98
99 # Replace the class for this instance and all clones created from it:
100 ui.__class__ = logtoprocessui
@@ -1,2008 +1,2030
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 collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import traceback
21 import traceback
22
22
23 from .i18n import _
23 from .i18n import _
24 from .node import hex
24 from .node import hex
25
25
26 from . import (
26 from . import (
27 color,
27 color,
28 config,
28 config,
29 configitems,
29 configitems,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 progress,
33 progress,
34 pycompat,
34 pycompat,
35 rcutil,
35 rcutil,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39 from .utils import (
39 from .utils import (
40 dateutil,
40 dateutil,
41 procutil,
41 procutil,
42 stringutil,
42 stringutil,
43 )
43 )
44
44
45 urlreq = util.urlreq
45 urlreq = util.urlreq
46
46
47 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
47 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
48 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
48 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
49 if not c.isalnum())
49 if not c.isalnum())
50
50
51 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
51 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
52 tweakrc = b"""
52 tweakrc = b"""
53 [ui]
53 [ui]
54 # The rollback command is dangerous. As a rule, don't use it.
54 # The rollback command is dangerous. As a rule, don't use it.
55 rollback = False
55 rollback = False
56 # Make `hg status` report copy information
56 # Make `hg status` report copy information
57 statuscopies = yes
57 statuscopies = yes
58 # Prefer curses UIs when available. Revert to plain-text with `text`.
58 # Prefer curses UIs when available. Revert to plain-text with `text`.
59 interface = curses
59 interface = curses
60
60
61 [commands]
61 [commands]
62 # Grep working directory by default.
62 # Grep working directory by default.
63 grep.all-files = True
63 grep.all-files = True
64 # Make `hg status` emit cwd-relative paths by default.
64 # Make `hg status` emit cwd-relative paths by default.
65 status.relative = yes
65 status.relative = yes
66 # Refuse to perform an `hg update` that would cause a file content merge
66 # Refuse to perform an `hg update` that would cause a file content merge
67 update.check = noconflict
67 update.check = noconflict
68 # Show conflicts information in `hg status`
68 # Show conflicts information in `hg status`
69 status.verbose = True
69 status.verbose = True
70
70
71 [diff]
71 [diff]
72 git = 1
72 git = 1
73 showfunc = 1
73 showfunc = 1
74 word-diff = 1
74 word-diff = 1
75 """
75 """
76
76
77 samplehgrcs = {
77 samplehgrcs = {
78 'user':
78 'user':
79 b"""# example user config (see 'hg help config' for more info)
79 b"""# example user config (see 'hg help config' for more info)
80 [ui]
80 [ui]
81 # name and email, e.g.
81 # name and email, e.g.
82 # username = Jane Doe <jdoe@example.com>
82 # username = Jane Doe <jdoe@example.com>
83 username =
83 username =
84
84
85 # We recommend enabling tweakdefaults to get slight improvements to
85 # We recommend enabling tweakdefaults to get slight improvements to
86 # the UI over time. Make sure to set HGPLAIN in the environment when
86 # the UI over time. Make sure to set HGPLAIN in the environment when
87 # writing scripts!
87 # writing scripts!
88 # tweakdefaults = True
88 # tweakdefaults = True
89
89
90 # uncomment to disable color in command output
90 # uncomment to disable color in command output
91 # (see 'hg help color' for details)
91 # (see 'hg help color' for details)
92 # color = never
92 # color = never
93
93
94 # uncomment to disable command output pagination
94 # uncomment to disable command output pagination
95 # (see 'hg help pager' for details)
95 # (see 'hg help pager' for details)
96 # paginate = never
96 # paginate = never
97
97
98 [extensions]
98 [extensions]
99 # uncomment these lines to enable some popular extensions
99 # uncomment these lines to enable some popular extensions
100 # (see 'hg help extensions' for more info)
100 # (see 'hg help extensions' for more info)
101 #
101 #
102 # churn =
102 # churn =
103 """,
103 """,
104
104
105 'cloned':
105 'cloned':
106 b"""# example repository config (see 'hg help config' for more info)
106 b"""# example repository config (see 'hg help config' for more info)
107 [paths]
107 [paths]
108 default = %s
108 default = %s
109
109
110 # path aliases to other clones of this repo in URLs or filesystem paths
110 # path aliases to other clones of this repo in URLs or filesystem paths
111 # (see 'hg help config.paths' for more info)
111 # (see 'hg help config.paths' for more info)
112 #
112 #
113 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
113 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
114 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
114 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
115 # my-clone = /home/jdoe/jdoes-clone
115 # my-clone = /home/jdoe/jdoes-clone
116
116
117 [ui]
117 [ui]
118 # name and email (local to this repository, optional), e.g.
118 # name and email (local to this repository, optional), e.g.
119 # username = Jane Doe <jdoe@example.com>
119 # username = Jane Doe <jdoe@example.com>
120 """,
120 """,
121
121
122 'local':
122 'local':
123 b"""# example repository config (see 'hg help config' for more info)
123 b"""# example repository config (see 'hg help config' for more info)
124 [paths]
124 [paths]
125 # path aliases to other clones of this repo in URLs or filesystem paths
125 # path aliases to other clones of this repo in URLs or filesystem paths
126 # (see 'hg help config.paths' for more info)
126 # (see 'hg help config.paths' for more info)
127 #
127 #
128 # default = http://example.com/hg/example-repo
128 # default = http://example.com/hg/example-repo
129 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
129 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
130 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
130 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
131 # my-clone = /home/jdoe/jdoes-clone
131 # my-clone = /home/jdoe/jdoes-clone
132
132
133 [ui]
133 [ui]
134 # name and email (local to this repository, optional), e.g.
134 # name and email (local to this repository, optional), e.g.
135 # username = Jane Doe <jdoe@example.com>
135 # username = Jane Doe <jdoe@example.com>
136 """,
136 """,
137
137
138 'global':
138 'global':
139 b"""# example system-wide hg config (see 'hg help config' for more info)
139 b"""# example system-wide hg config (see 'hg help config' for more info)
140
140
141 [ui]
141 [ui]
142 # uncomment to disable color in command output
142 # uncomment to disable color in command output
143 # (see 'hg help color' for details)
143 # (see 'hg help color' for details)
144 # color = never
144 # color = never
145
145
146 # uncomment to disable command output pagination
146 # uncomment to disable command output pagination
147 # (see 'hg help pager' for details)
147 # (see 'hg help pager' for details)
148 # paginate = never
148 # paginate = never
149
149
150 [extensions]
150 [extensions]
151 # uncomment these lines to enable some popular extensions
151 # uncomment these lines to enable some popular extensions
152 # (see 'hg help extensions' for more info)
152 # (see 'hg help extensions' for more info)
153 #
153 #
154 # blackbox =
154 # blackbox =
155 # churn =
155 # churn =
156 """,
156 """,
157 }
157 }
158
158
159 def _maybestrurl(maybebytes):
159 def _maybestrurl(maybebytes):
160 return pycompat.rapply(pycompat.strurl, maybebytes)
160 return pycompat.rapply(pycompat.strurl, maybebytes)
161
161
162 def _maybebytesurl(maybestr):
162 def _maybebytesurl(maybestr):
163 return pycompat.rapply(pycompat.bytesurl, maybestr)
163 return pycompat.rapply(pycompat.bytesurl, maybestr)
164
164
165 class httppasswordmgrdbproxy(object):
165 class httppasswordmgrdbproxy(object):
166 """Delays loading urllib2 until it's needed."""
166 """Delays loading urllib2 until it's needed."""
167 def __init__(self):
167 def __init__(self):
168 self._mgr = None
168 self._mgr = None
169
169
170 def _get_mgr(self):
170 def _get_mgr(self):
171 if self._mgr is None:
171 if self._mgr is None:
172 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
172 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
173 return self._mgr
173 return self._mgr
174
174
175 def add_password(self, realm, uris, user, passwd):
175 def add_password(self, realm, uris, user, passwd):
176 return self._get_mgr().add_password(
176 return self._get_mgr().add_password(
177 _maybestrurl(realm), _maybestrurl(uris),
177 _maybestrurl(realm), _maybestrurl(uris),
178 _maybestrurl(user), _maybestrurl(passwd))
178 _maybestrurl(user), _maybestrurl(passwd))
179
179
180 def find_user_password(self, realm, uri):
180 def find_user_password(self, realm, uri):
181 mgr = self._get_mgr()
181 mgr = self._get_mgr()
182 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
182 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
183 _maybestrurl(uri)))
183 _maybestrurl(uri)))
184
184
185 def _catchterm(*args):
185 def _catchterm(*args):
186 raise error.SignalInterrupt
186 raise error.SignalInterrupt
187
187
188 # unique object used to detect no default value has been provided when
188 # unique object used to detect no default value has been provided when
189 # retrieving configuration value.
189 # retrieving configuration value.
190 _unset = object()
190 _unset = object()
191
191
192 # _reqexithandlers: callbacks run at the end of a request
192 # _reqexithandlers: callbacks run at the end of a request
193 _reqexithandlers = []
193 _reqexithandlers = []
194
194
195 class ui(object):
195 class ui(object):
196 def __init__(self, src=None):
196 def __init__(self, src=None):
197 """Create a fresh new ui object if no src given
197 """Create a fresh new ui object if no src given
198
198
199 Use uimod.ui.load() to create a ui which knows global and user configs.
199 Use uimod.ui.load() to create a ui which knows global and user configs.
200 In most cases, you should use ui.copy() to create a copy of an existing
200 In most cases, you should use ui.copy() to create a copy of an existing
201 ui object.
201 ui object.
202 """
202 """
203 # _buffers: used for temporary capture of output
203 # _buffers: used for temporary capture of output
204 self._buffers = []
204 self._buffers = []
205 # 3-tuple describing how each buffer in the stack behaves.
205 # 3-tuple describing how each buffer in the stack behaves.
206 # Values are (capture stderr, capture subprocesses, apply labels).
206 # Values are (capture stderr, capture subprocesses, apply labels).
207 self._bufferstates = []
207 self._bufferstates = []
208 # When a buffer is active, defines whether we are expanding labels.
208 # When a buffer is active, defines whether we are expanding labels.
209 # This exists to prevent an extra list lookup.
209 # This exists to prevent an extra list lookup.
210 self._bufferapplylabels = None
210 self._bufferapplylabels = None
211 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
211 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
212 self._reportuntrusted = True
212 self._reportuntrusted = True
213 self._knownconfig = configitems.coreitems
213 self._knownconfig = configitems.coreitems
214 self._ocfg = config.config() # overlay
214 self._ocfg = config.config() # overlay
215 self._tcfg = config.config() # trusted
215 self._tcfg = config.config() # trusted
216 self._ucfg = config.config() # untrusted
216 self._ucfg = config.config() # untrusted
217 self._trustusers = set()
217 self._trustusers = set()
218 self._trustgroups = set()
218 self._trustgroups = set()
219 self.callhooks = True
219 self.callhooks = True
220 # Insecure server connections requested.
220 # Insecure server connections requested.
221 self.insecureconnections = False
221 self.insecureconnections = False
222 # Blocked time
222 # Blocked time
223 self.logblockedtimes = False
223 self.logblockedtimes = False
224 # color mode: see mercurial/color.py for possible value
224 # color mode: see mercurial/color.py for possible value
225 self._colormode = None
225 self._colormode = None
226 self._terminfoparams = {}
226 self._terminfoparams = {}
227 self._styles = {}
227 self._styles = {}
228 self._uninterruptible = False
228 self._uninterruptible = False
229
229
230 if src:
230 if src:
231 self._fout = src._fout
231 self._fout = src._fout
232 self._ferr = src._ferr
232 self._ferr = src._ferr
233 self._fin = src._fin
233 self._fin = src._fin
234 self._fmsg = src._fmsg
234 self._fmsg = src._fmsg
235 self._fmsgout = src._fmsgout
235 self._fmsgout = src._fmsgout
236 self._fmsgerr = src._fmsgerr
236 self._fmsgerr = src._fmsgerr
237 self._finoutredirected = src._finoutredirected
237 self._finoutredirected = src._finoutredirected
238 self._loggers = src._loggers.copy()
238 self.pageractive = src.pageractive
239 self.pageractive = src.pageractive
239 self._disablepager = src._disablepager
240 self._disablepager = src._disablepager
240 self._tweaked = src._tweaked
241 self._tweaked = src._tweaked
241
242
242 self._tcfg = src._tcfg.copy()
243 self._tcfg = src._tcfg.copy()
243 self._ucfg = src._ucfg.copy()
244 self._ucfg = src._ucfg.copy()
244 self._ocfg = src._ocfg.copy()
245 self._ocfg = src._ocfg.copy()
245 self._trustusers = src._trustusers.copy()
246 self._trustusers = src._trustusers.copy()
246 self._trustgroups = src._trustgroups.copy()
247 self._trustgroups = src._trustgroups.copy()
247 self.environ = src.environ
248 self.environ = src.environ
248 self.callhooks = src.callhooks
249 self.callhooks = src.callhooks
249 self.insecureconnections = src.insecureconnections
250 self.insecureconnections = src.insecureconnections
250 self._colormode = src._colormode
251 self._colormode = src._colormode
251 self._terminfoparams = src._terminfoparams.copy()
252 self._terminfoparams = src._terminfoparams.copy()
252 self._styles = src._styles.copy()
253 self._styles = src._styles.copy()
253
254
254 self.fixconfig()
255 self.fixconfig()
255
256
256 self.httppasswordmgrdb = src.httppasswordmgrdb
257 self.httppasswordmgrdb = src.httppasswordmgrdb
257 self._blockedtimes = src._blockedtimes
258 self._blockedtimes = src._blockedtimes
258 else:
259 else:
259 self._fout = procutil.stdout
260 self._fout = procutil.stdout
260 self._ferr = procutil.stderr
261 self._ferr = procutil.stderr
261 self._fin = procutil.stdin
262 self._fin = procutil.stdin
262 self._fmsg = None
263 self._fmsg = None
263 self._fmsgout = self.fout # configurable
264 self._fmsgout = self.fout # configurable
264 self._fmsgerr = self.ferr # configurable
265 self._fmsgerr = self.ferr # configurable
265 self._finoutredirected = False
266 self._finoutredirected = False
267 self._loggers = {}
266 self.pageractive = False
268 self.pageractive = False
267 self._disablepager = False
269 self._disablepager = False
268 self._tweaked = False
270 self._tweaked = False
269
271
270 # shared read-only environment
272 # shared read-only environment
271 self.environ = encoding.environ
273 self.environ = encoding.environ
272
274
273 self.httppasswordmgrdb = httppasswordmgrdbproxy()
275 self.httppasswordmgrdb = httppasswordmgrdbproxy()
274 self._blockedtimes = collections.defaultdict(int)
276 self._blockedtimes = collections.defaultdict(int)
275
277
276 allowed = self.configlist('experimental', 'exportableenviron')
278 allowed = self.configlist('experimental', 'exportableenviron')
277 if '*' in allowed:
279 if '*' in allowed:
278 self._exportableenviron = self.environ
280 self._exportableenviron = self.environ
279 else:
281 else:
280 self._exportableenviron = {}
282 self._exportableenviron = {}
281 for k in allowed:
283 for k in allowed:
282 if k in self.environ:
284 if k in self.environ:
283 self._exportableenviron[k] = self.environ[k]
285 self._exportableenviron[k] = self.environ[k]
284
286
285 @classmethod
287 @classmethod
286 def load(cls):
288 def load(cls):
287 """Create a ui and load global and user configs"""
289 """Create a ui and load global and user configs"""
288 u = cls()
290 u = cls()
289 # we always trust global config files and environment variables
291 # we always trust global config files and environment variables
290 for t, f in rcutil.rccomponents():
292 for t, f in rcutil.rccomponents():
291 if t == 'path':
293 if t == 'path':
292 u.readconfig(f, trust=True)
294 u.readconfig(f, trust=True)
293 elif t == 'items':
295 elif t == 'items':
294 sections = set()
296 sections = set()
295 for section, name, value, source in f:
297 for section, name, value, source in f:
296 # do not set u._ocfg
298 # do not set u._ocfg
297 # XXX clean this up once immutable config object is a thing
299 # XXX clean this up once immutable config object is a thing
298 u._tcfg.set(section, name, value, source)
300 u._tcfg.set(section, name, value, source)
299 u._ucfg.set(section, name, value, source)
301 u._ucfg.set(section, name, value, source)
300 sections.add(section)
302 sections.add(section)
301 for section in sections:
303 for section in sections:
302 u.fixconfig(section=section)
304 u.fixconfig(section=section)
303 else:
305 else:
304 raise error.ProgrammingError('unknown rctype: %s' % t)
306 raise error.ProgrammingError('unknown rctype: %s' % t)
305 u._maybetweakdefaults()
307 u._maybetweakdefaults()
306 return u
308 return u
307
309
308 def _maybetweakdefaults(self):
310 def _maybetweakdefaults(self):
309 if not self.configbool('ui', 'tweakdefaults'):
311 if not self.configbool('ui', 'tweakdefaults'):
310 return
312 return
311 if self._tweaked or self.plain('tweakdefaults'):
313 if self._tweaked or self.plain('tweakdefaults'):
312 return
314 return
313
315
314 # Note: it is SUPER IMPORTANT that you set self._tweaked to
316 # Note: it is SUPER IMPORTANT that you set self._tweaked to
315 # True *before* any calls to setconfig(), otherwise you'll get
317 # True *before* any calls to setconfig(), otherwise you'll get
316 # infinite recursion between setconfig and this method.
318 # infinite recursion between setconfig and this method.
317 #
319 #
318 # TODO: We should extract an inner method in setconfig() to
320 # TODO: We should extract an inner method in setconfig() to
319 # avoid this weirdness.
321 # avoid this weirdness.
320 self._tweaked = True
322 self._tweaked = True
321 tmpcfg = config.config()
323 tmpcfg = config.config()
322 tmpcfg.parse('<tweakdefaults>', tweakrc)
324 tmpcfg.parse('<tweakdefaults>', tweakrc)
323 for section in tmpcfg:
325 for section in tmpcfg:
324 for name, value in tmpcfg.items(section):
326 for name, value in tmpcfg.items(section):
325 if not self.hasconfig(section, name):
327 if not self.hasconfig(section, name):
326 self.setconfig(section, name, value, "<tweakdefaults>")
328 self.setconfig(section, name, value, "<tweakdefaults>")
327
329
328 def copy(self):
330 def copy(self):
329 return self.__class__(self)
331 return self.__class__(self)
330
332
331 def resetstate(self):
333 def resetstate(self):
332 """Clear internal state that shouldn't persist across commands"""
334 """Clear internal state that shouldn't persist across commands"""
333 if self._progbar:
335 if self._progbar:
334 self._progbar.resetstate() # reset last-print time of progress bar
336 self._progbar.resetstate() # reset last-print time of progress bar
335 self.httppasswordmgrdb = httppasswordmgrdbproxy()
337 self.httppasswordmgrdb = httppasswordmgrdbproxy()
336
338
337 @contextlib.contextmanager
339 @contextlib.contextmanager
338 def timeblockedsection(self, key):
340 def timeblockedsection(self, key):
339 # this is open-coded below - search for timeblockedsection to find them
341 # this is open-coded below - search for timeblockedsection to find them
340 starttime = util.timer()
342 starttime = util.timer()
341 try:
343 try:
342 yield
344 yield
343 finally:
345 finally:
344 self._blockedtimes[key + '_blocked'] += \
346 self._blockedtimes[key + '_blocked'] += \
345 (util.timer() - starttime) * 1000
347 (util.timer() - starttime) * 1000
346
348
347 @contextlib.contextmanager
349 @contextlib.contextmanager
348 def uninterruptable(self):
350 def uninterruptable(self):
349 """Mark an operation as unsafe.
351 """Mark an operation as unsafe.
350
352
351 Most operations on a repository are safe to interrupt, but a
353 Most operations on a repository are safe to interrupt, but a
352 few are risky (for example repair.strip). This context manager
354 few are risky (for example repair.strip). This context manager
353 lets you advise Mercurial that something risky is happening so
355 lets you advise Mercurial that something risky is happening so
354 that control-C etc can be blocked if desired.
356 that control-C etc can be blocked if desired.
355 """
357 """
356 enabled = self.configbool('experimental', 'nointerrupt')
358 enabled = self.configbool('experimental', 'nointerrupt')
357 if (enabled and
359 if (enabled and
358 self.configbool('experimental', 'nointerrupt-interactiveonly')):
360 self.configbool('experimental', 'nointerrupt-interactiveonly')):
359 enabled = self.interactive()
361 enabled = self.interactive()
360 if self._uninterruptible or not enabled:
362 if self._uninterruptible or not enabled:
361 # if nointerrupt support is turned off, the process isn't
363 # if nointerrupt support is turned off, the process isn't
362 # interactive, or we're already in an uninterruptable
364 # interactive, or we're already in an uninterruptable
363 # block, do nothing.
365 # block, do nothing.
364 yield
366 yield
365 return
367 return
366 def warn():
368 def warn():
367 self.warn(_("shutting down cleanly\n"))
369 self.warn(_("shutting down cleanly\n"))
368 self.warn(
370 self.warn(
369 _("press ^C again to terminate immediately (dangerous)\n"))
371 _("press ^C again to terminate immediately (dangerous)\n"))
370 return True
372 return True
371 with procutil.uninterruptable(warn):
373 with procutil.uninterruptable(warn):
372 try:
374 try:
373 self._uninterruptible = True
375 self._uninterruptible = True
374 yield
376 yield
375 finally:
377 finally:
376 self._uninterruptible = False
378 self._uninterruptible = False
377
379
378 def formatter(self, topic, opts):
380 def formatter(self, topic, opts):
379 return formatter.formatter(self, self, topic, opts)
381 return formatter.formatter(self, self, topic, opts)
380
382
381 def _trusted(self, fp, f):
383 def _trusted(self, fp, f):
382 st = util.fstat(fp)
384 st = util.fstat(fp)
383 if util.isowner(st):
385 if util.isowner(st):
384 return True
386 return True
385
387
386 tusers, tgroups = self._trustusers, self._trustgroups
388 tusers, tgroups = self._trustusers, self._trustgroups
387 if '*' in tusers or '*' in tgroups:
389 if '*' in tusers or '*' in tgroups:
388 return True
390 return True
389
391
390 user = util.username(st.st_uid)
392 user = util.username(st.st_uid)
391 group = util.groupname(st.st_gid)
393 group = util.groupname(st.st_gid)
392 if user in tusers or group in tgroups or user == util.username():
394 if user in tusers or group in tgroups or user == util.username():
393 return True
395 return True
394
396
395 if self._reportuntrusted:
397 if self._reportuntrusted:
396 self.warn(_('not trusting file %s from untrusted '
398 self.warn(_('not trusting file %s from untrusted '
397 'user %s, group %s\n') % (f, user, group))
399 'user %s, group %s\n') % (f, user, group))
398 return False
400 return False
399
401
400 def readconfig(self, filename, root=None, trust=False,
402 def readconfig(self, filename, root=None, trust=False,
401 sections=None, remap=None):
403 sections=None, remap=None):
402 try:
404 try:
403 fp = open(filename, r'rb')
405 fp = open(filename, r'rb')
404 except IOError:
406 except IOError:
405 if not sections: # ignore unless we were looking for something
407 if not sections: # ignore unless we were looking for something
406 return
408 return
407 raise
409 raise
408
410
409 cfg = config.config()
411 cfg = config.config()
410 trusted = sections or trust or self._trusted(fp, filename)
412 trusted = sections or trust or self._trusted(fp, filename)
411
413
412 try:
414 try:
413 cfg.read(filename, fp, sections=sections, remap=remap)
415 cfg.read(filename, fp, sections=sections, remap=remap)
414 fp.close()
416 fp.close()
415 except error.ConfigError as inst:
417 except error.ConfigError as inst:
416 if trusted:
418 if trusted:
417 raise
419 raise
418 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
420 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
419
421
420 if self.plain():
422 if self.plain():
421 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
423 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
422 'logtemplate', 'message-output', 'statuscopies', 'style',
424 'logtemplate', 'message-output', 'statuscopies', 'style',
423 'traceback', 'verbose'):
425 'traceback', 'verbose'):
424 if k in cfg['ui']:
426 if k in cfg['ui']:
425 del cfg['ui'][k]
427 del cfg['ui'][k]
426 for k, v in cfg.items('defaults'):
428 for k, v in cfg.items('defaults'):
427 del cfg['defaults'][k]
429 del cfg['defaults'][k]
428 for k, v in cfg.items('commands'):
430 for k, v in cfg.items('commands'):
429 del cfg['commands'][k]
431 del cfg['commands'][k]
430 # Don't remove aliases from the configuration if in the exceptionlist
432 # Don't remove aliases from the configuration if in the exceptionlist
431 if self.plain('alias'):
433 if self.plain('alias'):
432 for k, v in cfg.items('alias'):
434 for k, v in cfg.items('alias'):
433 del cfg['alias'][k]
435 del cfg['alias'][k]
434 if self.plain('revsetalias'):
436 if self.plain('revsetalias'):
435 for k, v in cfg.items('revsetalias'):
437 for k, v in cfg.items('revsetalias'):
436 del cfg['revsetalias'][k]
438 del cfg['revsetalias'][k]
437 if self.plain('templatealias'):
439 if self.plain('templatealias'):
438 for k, v in cfg.items('templatealias'):
440 for k, v in cfg.items('templatealias'):
439 del cfg['templatealias'][k]
441 del cfg['templatealias'][k]
440
442
441 if trusted:
443 if trusted:
442 self._tcfg.update(cfg)
444 self._tcfg.update(cfg)
443 self._tcfg.update(self._ocfg)
445 self._tcfg.update(self._ocfg)
444 self._ucfg.update(cfg)
446 self._ucfg.update(cfg)
445 self._ucfg.update(self._ocfg)
447 self._ucfg.update(self._ocfg)
446
448
447 if root is None:
449 if root is None:
448 root = os.path.expanduser('~')
450 root = os.path.expanduser('~')
449 self.fixconfig(root=root)
451 self.fixconfig(root=root)
450
452
451 def fixconfig(self, root=None, section=None):
453 def fixconfig(self, root=None, section=None):
452 if section in (None, 'paths'):
454 if section in (None, 'paths'):
453 # expand vars and ~
455 # expand vars and ~
454 # translate paths relative to root (or home) into absolute paths
456 # translate paths relative to root (or home) into absolute paths
455 root = root or encoding.getcwd()
457 root = root or encoding.getcwd()
456 for c in self._tcfg, self._ucfg, self._ocfg:
458 for c in self._tcfg, self._ucfg, self._ocfg:
457 for n, p in c.items('paths'):
459 for n, p in c.items('paths'):
458 # Ignore sub-options.
460 # Ignore sub-options.
459 if ':' in n:
461 if ':' in n:
460 continue
462 continue
461 if not p:
463 if not p:
462 continue
464 continue
463 if '%%' in p:
465 if '%%' in p:
464 s = self.configsource('paths', n) or 'none'
466 s = self.configsource('paths', n) or 'none'
465 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
467 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
466 % (n, p, s))
468 % (n, p, s))
467 p = p.replace('%%', '%')
469 p = p.replace('%%', '%')
468 p = util.expandpath(p)
470 p = util.expandpath(p)
469 if not util.hasscheme(p) and not os.path.isabs(p):
471 if not util.hasscheme(p) and not os.path.isabs(p):
470 p = os.path.normpath(os.path.join(root, p))
472 p = os.path.normpath(os.path.join(root, p))
471 c.set("paths", n, p)
473 c.set("paths", n, p)
472
474
473 if section in (None, 'ui'):
475 if section in (None, 'ui'):
474 # update ui options
476 # update ui options
475 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
477 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
476 self.debugflag = self.configbool('ui', 'debug')
478 self.debugflag = self.configbool('ui', 'debug')
477 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
479 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
478 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
480 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
479 if self.verbose and self.quiet:
481 if self.verbose and self.quiet:
480 self.quiet = self.verbose = False
482 self.quiet = self.verbose = False
481 self._reportuntrusted = self.debugflag or self.configbool("ui",
483 self._reportuntrusted = self.debugflag or self.configbool("ui",
482 "report_untrusted")
484 "report_untrusted")
483 self.tracebackflag = self.configbool('ui', 'traceback')
485 self.tracebackflag = self.configbool('ui', 'traceback')
484 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
486 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
485
487
486 if section in (None, 'trusted'):
488 if section in (None, 'trusted'):
487 # update trust information
489 # update trust information
488 self._trustusers.update(self.configlist('trusted', 'users'))
490 self._trustusers.update(self.configlist('trusted', 'users'))
489 self._trustgroups.update(self.configlist('trusted', 'groups'))
491 self._trustgroups.update(self.configlist('trusted', 'groups'))
490
492
491 def backupconfig(self, section, item):
493 def backupconfig(self, section, item):
492 return (self._ocfg.backup(section, item),
494 return (self._ocfg.backup(section, item),
493 self._tcfg.backup(section, item),
495 self._tcfg.backup(section, item),
494 self._ucfg.backup(section, item),)
496 self._ucfg.backup(section, item),)
495 def restoreconfig(self, data):
497 def restoreconfig(self, data):
496 self._ocfg.restore(data[0])
498 self._ocfg.restore(data[0])
497 self._tcfg.restore(data[1])
499 self._tcfg.restore(data[1])
498 self._ucfg.restore(data[2])
500 self._ucfg.restore(data[2])
499
501
500 def setconfig(self, section, name, value, source=''):
502 def setconfig(self, section, name, value, source=''):
501 for cfg in (self._ocfg, self._tcfg, self._ucfg):
503 for cfg in (self._ocfg, self._tcfg, self._ucfg):
502 cfg.set(section, name, value, source)
504 cfg.set(section, name, value, source)
503 self.fixconfig(section=section)
505 self.fixconfig(section=section)
504 self._maybetweakdefaults()
506 self._maybetweakdefaults()
505
507
506 def _data(self, untrusted):
508 def _data(self, untrusted):
507 return untrusted and self._ucfg or self._tcfg
509 return untrusted and self._ucfg or self._tcfg
508
510
509 def configsource(self, section, name, untrusted=False):
511 def configsource(self, section, name, untrusted=False):
510 return self._data(untrusted).source(section, name)
512 return self._data(untrusted).source(section, name)
511
513
512 def config(self, section, name, default=_unset, untrusted=False):
514 def config(self, section, name, default=_unset, untrusted=False):
513 """return the plain string version of a config"""
515 """return the plain string version of a config"""
514 value = self._config(section, name, default=default,
516 value = self._config(section, name, default=default,
515 untrusted=untrusted)
517 untrusted=untrusted)
516 if value is _unset:
518 if value is _unset:
517 return None
519 return None
518 return value
520 return value
519
521
520 def _config(self, section, name, default=_unset, untrusted=False):
522 def _config(self, section, name, default=_unset, untrusted=False):
521 value = itemdefault = default
523 value = itemdefault = default
522 item = self._knownconfig.get(section, {}).get(name)
524 item = self._knownconfig.get(section, {}).get(name)
523 alternates = [(section, name)]
525 alternates = [(section, name)]
524
526
525 if item is not None:
527 if item is not None:
526 alternates.extend(item.alias)
528 alternates.extend(item.alias)
527 if callable(item.default):
529 if callable(item.default):
528 itemdefault = item.default()
530 itemdefault = item.default()
529 else:
531 else:
530 itemdefault = item.default
532 itemdefault = item.default
531 else:
533 else:
532 msg = ("accessing unregistered config item: '%s.%s'")
534 msg = ("accessing unregistered config item: '%s.%s'")
533 msg %= (section, name)
535 msg %= (section, name)
534 self.develwarn(msg, 2, 'warn-config-unknown')
536 self.develwarn(msg, 2, 'warn-config-unknown')
535
537
536 if default is _unset:
538 if default is _unset:
537 if item is None:
539 if item is None:
538 value = default
540 value = default
539 elif item.default is configitems.dynamicdefault:
541 elif item.default is configitems.dynamicdefault:
540 value = None
542 value = None
541 msg = "config item requires an explicit default value: '%s.%s'"
543 msg = "config item requires an explicit default value: '%s.%s'"
542 msg %= (section, name)
544 msg %= (section, name)
543 self.develwarn(msg, 2, 'warn-config-default')
545 self.develwarn(msg, 2, 'warn-config-default')
544 else:
546 else:
545 value = itemdefault
547 value = itemdefault
546 elif (item is not None
548 elif (item is not None
547 and item.default is not configitems.dynamicdefault
549 and item.default is not configitems.dynamicdefault
548 and default != itemdefault):
550 and default != itemdefault):
549 msg = ("specifying a mismatched default value for a registered "
551 msg = ("specifying a mismatched default value for a registered "
550 "config item: '%s.%s' '%s'")
552 "config item: '%s.%s' '%s'")
551 msg %= (section, name, pycompat.bytestr(default))
553 msg %= (section, name, pycompat.bytestr(default))
552 self.develwarn(msg, 2, 'warn-config-default')
554 self.develwarn(msg, 2, 'warn-config-default')
553
555
554 for s, n in alternates:
556 for s, n in alternates:
555 candidate = self._data(untrusted).get(s, n, None)
557 candidate = self._data(untrusted).get(s, n, None)
556 if candidate is not None:
558 if candidate is not None:
557 value = candidate
559 value = candidate
558 section = s
560 section = s
559 name = n
561 name = n
560 break
562 break
561
563
562 if self.debugflag and not untrusted and self._reportuntrusted:
564 if self.debugflag and not untrusted and self._reportuntrusted:
563 for s, n in alternates:
565 for s, n in alternates:
564 uvalue = self._ucfg.get(s, n)
566 uvalue = self._ucfg.get(s, n)
565 if uvalue is not None and uvalue != value:
567 if uvalue is not None and uvalue != value:
566 self.debug("ignoring untrusted configuration option "
568 self.debug("ignoring untrusted configuration option "
567 "%s.%s = %s\n" % (s, n, uvalue))
569 "%s.%s = %s\n" % (s, n, uvalue))
568 return value
570 return value
569
571
570 def configsuboptions(self, section, name, default=_unset, untrusted=False):
572 def configsuboptions(self, section, name, default=_unset, untrusted=False):
571 """Get a config option and all sub-options.
573 """Get a config option and all sub-options.
572
574
573 Some config options have sub-options that are declared with the
575 Some config options have sub-options that are declared with the
574 format "key:opt = value". This method is used to return the main
576 format "key:opt = value". This method is used to return the main
575 option and all its declared sub-options.
577 option and all its declared sub-options.
576
578
577 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
579 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
578 is a dict of defined sub-options where keys and values are strings.
580 is a dict of defined sub-options where keys and values are strings.
579 """
581 """
580 main = self.config(section, name, default, untrusted=untrusted)
582 main = self.config(section, name, default, untrusted=untrusted)
581 data = self._data(untrusted)
583 data = self._data(untrusted)
582 sub = {}
584 sub = {}
583 prefix = '%s:' % name
585 prefix = '%s:' % name
584 for k, v in data.items(section):
586 for k, v in data.items(section):
585 if k.startswith(prefix):
587 if k.startswith(prefix):
586 sub[k[len(prefix):]] = v
588 sub[k[len(prefix):]] = v
587
589
588 if self.debugflag and not untrusted and self._reportuntrusted:
590 if self.debugflag and not untrusted and self._reportuntrusted:
589 for k, v in sub.items():
591 for k, v in sub.items():
590 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
592 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
591 if uvalue is not None and uvalue != v:
593 if uvalue is not None and uvalue != v:
592 self.debug('ignoring untrusted configuration option '
594 self.debug('ignoring untrusted configuration option '
593 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
595 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
594
596
595 return main, sub
597 return main, sub
596
598
597 def configpath(self, section, name, default=_unset, untrusted=False):
599 def configpath(self, section, name, default=_unset, untrusted=False):
598 'get a path config item, expanded relative to repo root or config file'
600 'get a path config item, expanded relative to repo root or config file'
599 v = self.config(section, name, default, untrusted)
601 v = self.config(section, name, default, untrusted)
600 if v is None:
602 if v is None:
601 return None
603 return None
602 if not os.path.isabs(v) or "://" not in v:
604 if not os.path.isabs(v) or "://" not in v:
603 src = self.configsource(section, name, untrusted)
605 src = self.configsource(section, name, untrusted)
604 if ':' in src:
606 if ':' in src:
605 base = os.path.dirname(src.rsplit(':')[0])
607 base = os.path.dirname(src.rsplit(':')[0])
606 v = os.path.join(base, os.path.expanduser(v))
608 v = os.path.join(base, os.path.expanduser(v))
607 return v
609 return v
608
610
609 def configbool(self, section, name, default=_unset, untrusted=False):
611 def configbool(self, section, name, default=_unset, untrusted=False):
610 """parse a configuration element as a boolean
612 """parse a configuration element as a boolean
611
613
612 >>> u = ui(); s = b'foo'
614 >>> u = ui(); s = b'foo'
613 >>> u.setconfig(s, b'true', b'yes')
615 >>> u.setconfig(s, b'true', b'yes')
614 >>> u.configbool(s, b'true')
616 >>> u.configbool(s, b'true')
615 True
617 True
616 >>> u.setconfig(s, b'false', b'no')
618 >>> u.setconfig(s, b'false', b'no')
617 >>> u.configbool(s, b'false')
619 >>> u.configbool(s, b'false')
618 False
620 False
619 >>> u.configbool(s, b'unknown')
621 >>> u.configbool(s, b'unknown')
620 False
622 False
621 >>> u.configbool(s, b'unknown', True)
623 >>> u.configbool(s, b'unknown', True)
622 True
624 True
623 >>> u.setconfig(s, b'invalid', b'somevalue')
625 >>> u.setconfig(s, b'invalid', b'somevalue')
624 >>> u.configbool(s, b'invalid')
626 >>> u.configbool(s, b'invalid')
625 Traceback (most recent call last):
627 Traceback (most recent call last):
626 ...
628 ...
627 ConfigError: foo.invalid is not a boolean ('somevalue')
629 ConfigError: foo.invalid is not a boolean ('somevalue')
628 """
630 """
629
631
630 v = self._config(section, name, default, untrusted=untrusted)
632 v = self._config(section, name, default, untrusted=untrusted)
631 if v is None:
633 if v is None:
632 return v
634 return v
633 if v is _unset:
635 if v is _unset:
634 if default is _unset:
636 if default is _unset:
635 return False
637 return False
636 return default
638 return default
637 if isinstance(v, bool):
639 if isinstance(v, bool):
638 return v
640 return v
639 b = stringutil.parsebool(v)
641 b = stringutil.parsebool(v)
640 if b is None:
642 if b is None:
641 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
643 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
642 % (section, name, v))
644 % (section, name, v))
643 return b
645 return b
644
646
645 def configwith(self, convert, section, name, default=_unset,
647 def configwith(self, convert, section, name, default=_unset,
646 desc=None, untrusted=False):
648 desc=None, untrusted=False):
647 """parse a configuration element with a conversion function
649 """parse a configuration element with a conversion function
648
650
649 >>> u = ui(); s = b'foo'
651 >>> u = ui(); s = b'foo'
650 >>> u.setconfig(s, b'float1', b'42')
652 >>> u.setconfig(s, b'float1', b'42')
651 >>> u.configwith(float, s, b'float1')
653 >>> u.configwith(float, s, b'float1')
652 42.0
654 42.0
653 >>> u.setconfig(s, b'float2', b'-4.25')
655 >>> u.setconfig(s, b'float2', b'-4.25')
654 >>> u.configwith(float, s, b'float2')
656 >>> u.configwith(float, s, b'float2')
655 -4.25
657 -4.25
656 >>> u.configwith(float, s, b'unknown', 7)
658 >>> u.configwith(float, s, b'unknown', 7)
657 7.0
659 7.0
658 >>> u.setconfig(s, b'invalid', b'somevalue')
660 >>> u.setconfig(s, b'invalid', b'somevalue')
659 >>> u.configwith(float, s, b'invalid')
661 >>> u.configwith(float, s, b'invalid')
660 Traceback (most recent call last):
662 Traceback (most recent call last):
661 ...
663 ...
662 ConfigError: foo.invalid is not a valid float ('somevalue')
664 ConfigError: foo.invalid is not a valid float ('somevalue')
663 >>> u.configwith(float, s, b'invalid', desc=b'womble')
665 >>> u.configwith(float, s, b'invalid', desc=b'womble')
664 Traceback (most recent call last):
666 Traceback (most recent call last):
665 ...
667 ...
666 ConfigError: foo.invalid is not a valid womble ('somevalue')
668 ConfigError: foo.invalid is not a valid womble ('somevalue')
667 """
669 """
668
670
669 v = self.config(section, name, default, untrusted)
671 v = self.config(section, name, default, untrusted)
670 if v is None:
672 if v is None:
671 return v # do not attempt to convert None
673 return v # do not attempt to convert None
672 try:
674 try:
673 return convert(v)
675 return convert(v)
674 except (ValueError, error.ParseError):
676 except (ValueError, error.ParseError):
675 if desc is None:
677 if desc is None:
676 desc = pycompat.sysbytes(convert.__name__)
678 desc = pycompat.sysbytes(convert.__name__)
677 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
679 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
678 % (section, name, desc, v))
680 % (section, name, desc, v))
679
681
680 def configint(self, section, name, default=_unset, untrusted=False):
682 def configint(self, section, name, default=_unset, untrusted=False):
681 """parse a configuration element as an integer
683 """parse a configuration element as an integer
682
684
683 >>> u = ui(); s = b'foo'
685 >>> u = ui(); s = b'foo'
684 >>> u.setconfig(s, b'int1', b'42')
686 >>> u.setconfig(s, b'int1', b'42')
685 >>> u.configint(s, b'int1')
687 >>> u.configint(s, b'int1')
686 42
688 42
687 >>> u.setconfig(s, b'int2', b'-42')
689 >>> u.setconfig(s, b'int2', b'-42')
688 >>> u.configint(s, b'int2')
690 >>> u.configint(s, b'int2')
689 -42
691 -42
690 >>> u.configint(s, b'unknown', 7)
692 >>> u.configint(s, b'unknown', 7)
691 7
693 7
692 >>> u.setconfig(s, b'invalid', b'somevalue')
694 >>> u.setconfig(s, b'invalid', b'somevalue')
693 >>> u.configint(s, b'invalid')
695 >>> u.configint(s, b'invalid')
694 Traceback (most recent call last):
696 Traceback (most recent call last):
695 ...
697 ...
696 ConfigError: foo.invalid is not a valid integer ('somevalue')
698 ConfigError: foo.invalid is not a valid integer ('somevalue')
697 """
699 """
698
700
699 return self.configwith(int, section, name, default, 'integer',
701 return self.configwith(int, section, name, default, 'integer',
700 untrusted)
702 untrusted)
701
703
702 def configbytes(self, section, name, default=_unset, untrusted=False):
704 def configbytes(self, section, name, default=_unset, untrusted=False):
703 """parse a configuration element as a quantity in bytes
705 """parse a configuration element as a quantity in bytes
704
706
705 Units can be specified as b (bytes), k or kb (kilobytes), m or
707 Units can be specified as b (bytes), k or kb (kilobytes), m or
706 mb (megabytes), g or gb (gigabytes).
708 mb (megabytes), g or gb (gigabytes).
707
709
708 >>> u = ui(); s = b'foo'
710 >>> u = ui(); s = b'foo'
709 >>> u.setconfig(s, b'val1', b'42')
711 >>> u.setconfig(s, b'val1', b'42')
710 >>> u.configbytes(s, b'val1')
712 >>> u.configbytes(s, b'val1')
711 42
713 42
712 >>> u.setconfig(s, b'val2', b'42.5 kb')
714 >>> u.setconfig(s, b'val2', b'42.5 kb')
713 >>> u.configbytes(s, b'val2')
715 >>> u.configbytes(s, b'val2')
714 43520
716 43520
715 >>> u.configbytes(s, b'unknown', b'7 MB')
717 >>> u.configbytes(s, b'unknown', b'7 MB')
716 7340032
718 7340032
717 >>> u.setconfig(s, b'invalid', b'somevalue')
719 >>> u.setconfig(s, b'invalid', b'somevalue')
718 >>> u.configbytes(s, b'invalid')
720 >>> u.configbytes(s, b'invalid')
719 Traceback (most recent call last):
721 Traceback (most recent call last):
720 ...
722 ...
721 ConfigError: foo.invalid is not a byte quantity ('somevalue')
723 ConfigError: foo.invalid is not a byte quantity ('somevalue')
722 """
724 """
723
725
724 value = self._config(section, name, default, untrusted)
726 value = self._config(section, name, default, untrusted)
725 if value is _unset:
727 if value is _unset:
726 if default is _unset:
728 if default is _unset:
727 default = 0
729 default = 0
728 value = default
730 value = default
729 if not isinstance(value, bytes):
731 if not isinstance(value, bytes):
730 return value
732 return value
731 try:
733 try:
732 return util.sizetoint(value)
734 return util.sizetoint(value)
733 except error.ParseError:
735 except error.ParseError:
734 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
736 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
735 % (section, name, value))
737 % (section, name, value))
736
738
737 def configlist(self, section, name, default=_unset, untrusted=False):
739 def configlist(self, section, name, default=_unset, untrusted=False):
738 """parse a configuration element as a list of comma/space separated
740 """parse a configuration element as a list of comma/space separated
739 strings
741 strings
740
742
741 >>> u = ui(); s = b'foo'
743 >>> u = ui(); s = b'foo'
742 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
744 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
743 >>> u.configlist(s, b'list1')
745 >>> u.configlist(s, b'list1')
744 ['this', 'is', 'a small', 'test']
746 ['this', 'is', 'a small', 'test']
745 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
747 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
746 >>> u.configlist(s, b'list2')
748 >>> u.configlist(s, b'list2')
747 ['this', 'is', 'a small', 'test']
749 ['this', 'is', 'a small', 'test']
748 """
750 """
749 # default is not always a list
751 # default is not always a list
750 v = self.configwith(config.parselist, section, name, default,
752 v = self.configwith(config.parselist, section, name, default,
751 'list', untrusted)
753 'list', untrusted)
752 if isinstance(v, bytes):
754 if isinstance(v, bytes):
753 return config.parselist(v)
755 return config.parselist(v)
754 elif v is None:
756 elif v is None:
755 return []
757 return []
756 return v
758 return v
757
759
758 def configdate(self, section, name, default=_unset, untrusted=False):
760 def configdate(self, section, name, default=_unset, untrusted=False):
759 """parse a configuration element as a tuple of ints
761 """parse a configuration element as a tuple of ints
760
762
761 >>> u = ui(); s = b'foo'
763 >>> u = ui(); s = b'foo'
762 >>> u.setconfig(s, b'date', b'0 0')
764 >>> u.setconfig(s, b'date', b'0 0')
763 >>> u.configdate(s, b'date')
765 >>> u.configdate(s, b'date')
764 (0, 0)
766 (0, 0)
765 """
767 """
766 if self.config(section, name, default, untrusted):
768 if self.config(section, name, default, untrusted):
767 return self.configwith(dateutil.parsedate, section, name, default,
769 return self.configwith(dateutil.parsedate, section, name, default,
768 'date', untrusted)
770 'date', untrusted)
769 if default is _unset:
771 if default is _unset:
770 return None
772 return None
771 return default
773 return default
772
774
773 def hasconfig(self, section, name, untrusted=False):
775 def hasconfig(self, section, name, untrusted=False):
774 return self._data(untrusted).hasitem(section, name)
776 return self._data(untrusted).hasitem(section, name)
775
777
776 def has_section(self, section, untrusted=False):
778 def has_section(self, section, untrusted=False):
777 '''tell whether section exists in config.'''
779 '''tell whether section exists in config.'''
778 return section in self._data(untrusted)
780 return section in self._data(untrusted)
779
781
780 def configitems(self, section, untrusted=False, ignoresub=False):
782 def configitems(self, section, untrusted=False, ignoresub=False):
781 items = self._data(untrusted).items(section)
783 items = self._data(untrusted).items(section)
782 if ignoresub:
784 if ignoresub:
783 items = [i for i in items if ':' not in i[0]]
785 items = [i for i in items if ':' not in i[0]]
784 if self.debugflag and not untrusted and self._reportuntrusted:
786 if self.debugflag and not untrusted and self._reportuntrusted:
785 for k, v in self._ucfg.items(section):
787 for k, v in self._ucfg.items(section):
786 if self._tcfg.get(section, k) != v:
788 if self._tcfg.get(section, k) != v:
787 self.debug("ignoring untrusted configuration option "
789 self.debug("ignoring untrusted configuration option "
788 "%s.%s = %s\n" % (section, k, v))
790 "%s.%s = %s\n" % (section, k, v))
789 return items
791 return items
790
792
791 def walkconfig(self, untrusted=False):
793 def walkconfig(self, untrusted=False):
792 cfg = self._data(untrusted)
794 cfg = self._data(untrusted)
793 for section in cfg.sections():
795 for section in cfg.sections():
794 for name, value in self.configitems(section, untrusted):
796 for name, value in self.configitems(section, untrusted):
795 yield section, name, value
797 yield section, name, value
796
798
797 def plain(self, feature=None):
799 def plain(self, feature=None):
798 '''is plain mode active?
800 '''is plain mode active?
799
801
800 Plain mode means that all configuration variables which affect
802 Plain mode means that all configuration variables which affect
801 the behavior and output of Mercurial should be
803 the behavior and output of Mercurial should be
802 ignored. Additionally, the output should be stable,
804 ignored. Additionally, the output should be stable,
803 reproducible and suitable for use in scripts or applications.
805 reproducible and suitable for use in scripts or applications.
804
806
805 The only way to trigger plain mode is by setting either the
807 The only way to trigger plain mode is by setting either the
806 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
808 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
807
809
808 The return value can either be
810 The return value can either be
809 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
811 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
810 - False if feature is disabled by default and not included in HGPLAIN
812 - False if feature is disabled by default and not included in HGPLAIN
811 - True otherwise
813 - True otherwise
812 '''
814 '''
813 if ('HGPLAIN' not in encoding.environ and
815 if ('HGPLAIN' not in encoding.environ and
814 'HGPLAINEXCEPT' not in encoding.environ):
816 'HGPLAINEXCEPT' not in encoding.environ):
815 return False
817 return False
816 exceptions = encoding.environ.get('HGPLAINEXCEPT',
818 exceptions = encoding.environ.get('HGPLAINEXCEPT',
817 '').strip().split(',')
819 '').strip().split(',')
818 # TODO: add support for HGPLAIN=+feature,-feature syntax
820 # TODO: add support for HGPLAIN=+feature,-feature syntax
819 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
821 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
820 exceptions.append('strictflags')
822 exceptions.append('strictflags')
821 if feature and exceptions:
823 if feature and exceptions:
822 return feature not in exceptions
824 return feature not in exceptions
823 return True
825 return True
824
826
825 def username(self, acceptempty=False):
827 def username(self, acceptempty=False):
826 """Return default username to be used in commits.
828 """Return default username to be used in commits.
827
829
828 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
830 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
829 and stop searching if one of these is set.
831 and stop searching if one of these is set.
830 If not found and acceptempty is True, returns None.
832 If not found and acceptempty is True, returns None.
831 If not found and ui.askusername is True, ask the user, else use
833 If not found and ui.askusername is True, ask the user, else use
832 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
834 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
833 If no username could be found, raise an Abort error.
835 If no username could be found, raise an Abort error.
834 """
836 """
835 user = encoding.environ.get("HGUSER")
837 user = encoding.environ.get("HGUSER")
836 if user is None:
838 if user is None:
837 user = self.config("ui", "username")
839 user = self.config("ui", "username")
838 if user is not None:
840 if user is not None:
839 user = os.path.expandvars(user)
841 user = os.path.expandvars(user)
840 if user is None:
842 if user is None:
841 user = encoding.environ.get("EMAIL")
843 user = encoding.environ.get("EMAIL")
842 if user is None and acceptempty:
844 if user is None and acceptempty:
843 return user
845 return user
844 if user is None and self.configbool("ui", "askusername"):
846 if user is None and self.configbool("ui", "askusername"):
845 user = self.prompt(_("enter a commit username:"), default=None)
847 user = self.prompt(_("enter a commit username:"), default=None)
846 if user is None and not self.interactive():
848 if user is None and not self.interactive():
847 try:
849 try:
848 user = '%s@%s' % (procutil.getuser(),
850 user = '%s@%s' % (procutil.getuser(),
849 encoding.strtolocal(socket.getfqdn()))
851 encoding.strtolocal(socket.getfqdn()))
850 self.warn(_("no username found, using '%s' instead\n") % user)
852 self.warn(_("no username found, using '%s' instead\n") % user)
851 except KeyError:
853 except KeyError:
852 pass
854 pass
853 if not user:
855 if not user:
854 raise error.Abort(_('no username supplied'),
856 raise error.Abort(_('no username supplied'),
855 hint=_("use 'hg config --edit' "
857 hint=_("use 'hg config --edit' "
856 'to set your username'))
858 'to set your username'))
857 if "\n" in user:
859 if "\n" in user:
858 raise error.Abort(_("username %r contains a newline\n")
860 raise error.Abort(_("username %r contains a newline\n")
859 % pycompat.bytestr(user))
861 % pycompat.bytestr(user))
860 return user
862 return user
861
863
862 def shortuser(self, user):
864 def shortuser(self, user):
863 """Return a short representation of a user name or email address."""
865 """Return a short representation of a user name or email address."""
864 if not self.verbose:
866 if not self.verbose:
865 user = stringutil.shortuser(user)
867 user = stringutil.shortuser(user)
866 return user
868 return user
867
869
868 def expandpath(self, loc, default=None):
870 def expandpath(self, loc, default=None):
869 """Return repository location relative to cwd or from [paths]"""
871 """Return repository location relative to cwd or from [paths]"""
870 try:
872 try:
871 p = self.paths.getpath(loc)
873 p = self.paths.getpath(loc)
872 if p:
874 if p:
873 return p.rawloc
875 return p.rawloc
874 except error.RepoError:
876 except error.RepoError:
875 pass
877 pass
876
878
877 if default:
879 if default:
878 try:
880 try:
879 p = self.paths.getpath(default)
881 p = self.paths.getpath(default)
880 if p:
882 if p:
881 return p.rawloc
883 return p.rawloc
882 except error.RepoError:
884 except error.RepoError:
883 pass
885 pass
884
886
885 return loc
887 return loc
886
888
887 @util.propertycache
889 @util.propertycache
888 def paths(self):
890 def paths(self):
889 return paths(self)
891 return paths(self)
890
892
891 @property
893 @property
892 def fout(self):
894 def fout(self):
893 return self._fout
895 return self._fout
894
896
895 @fout.setter
897 @fout.setter
896 def fout(self, f):
898 def fout(self, f):
897 self._fout = f
899 self._fout = f
898 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
900 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
899
901
900 @property
902 @property
901 def ferr(self):
903 def ferr(self):
902 return self._ferr
904 return self._ferr
903
905
904 @ferr.setter
906 @ferr.setter
905 def ferr(self, f):
907 def ferr(self, f):
906 self._ferr = f
908 self._ferr = f
907 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
909 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
908
910
909 @property
911 @property
910 def fin(self):
912 def fin(self):
911 return self._fin
913 return self._fin
912
914
913 @fin.setter
915 @fin.setter
914 def fin(self, f):
916 def fin(self, f):
915 self._fin = f
917 self._fin = f
916
918
917 @property
919 @property
918 def fmsg(self):
920 def fmsg(self):
919 """Stream dedicated for status/error messages; may be None if
921 """Stream dedicated for status/error messages; may be None if
920 fout/ferr are used"""
922 fout/ferr are used"""
921 return self._fmsg
923 return self._fmsg
922
924
923 @fmsg.setter
925 @fmsg.setter
924 def fmsg(self, f):
926 def fmsg(self, f):
925 self._fmsg = f
927 self._fmsg = f
926 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
928 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
927
929
928 def pushbuffer(self, error=False, subproc=False, labeled=False):
930 def pushbuffer(self, error=False, subproc=False, labeled=False):
929 """install a buffer to capture standard output of the ui object
931 """install a buffer to capture standard output of the ui object
930
932
931 If error is True, the error output will be captured too.
933 If error is True, the error output will be captured too.
932
934
933 If subproc is True, output from subprocesses (typically hooks) will be
935 If subproc is True, output from subprocesses (typically hooks) will be
934 captured too.
936 captured too.
935
937
936 If labeled is True, any labels associated with buffered
938 If labeled is True, any labels associated with buffered
937 output will be handled. By default, this has no effect
939 output will be handled. By default, this has no effect
938 on the output returned, but extensions and GUI tools may
940 on the output returned, but extensions and GUI tools may
939 handle this argument and returned styled output. If output
941 handle this argument and returned styled output. If output
940 is being buffered so it can be captured and parsed or
942 is being buffered so it can be captured and parsed or
941 processed, labeled should not be set to True.
943 processed, labeled should not be set to True.
942 """
944 """
943 self._buffers.append([])
945 self._buffers.append([])
944 self._bufferstates.append((error, subproc, labeled))
946 self._bufferstates.append((error, subproc, labeled))
945 self._bufferapplylabels = labeled
947 self._bufferapplylabels = labeled
946
948
947 def popbuffer(self):
949 def popbuffer(self):
948 '''pop the last buffer and return the buffered output'''
950 '''pop the last buffer and return the buffered output'''
949 self._bufferstates.pop()
951 self._bufferstates.pop()
950 if self._bufferstates:
952 if self._bufferstates:
951 self._bufferapplylabels = self._bufferstates[-1][2]
953 self._bufferapplylabels = self._bufferstates[-1][2]
952 else:
954 else:
953 self._bufferapplylabels = None
955 self._bufferapplylabels = None
954
956
955 return "".join(self._buffers.pop())
957 return "".join(self._buffers.pop())
956
958
957 def _isbuffered(self, dest):
959 def _isbuffered(self, dest):
958 if dest is self._fout:
960 if dest is self._fout:
959 return bool(self._buffers)
961 return bool(self._buffers)
960 if dest is self._ferr:
962 if dest is self._ferr:
961 return bool(self._bufferstates and self._bufferstates[-1][0])
963 return bool(self._bufferstates and self._bufferstates[-1][0])
962 return False
964 return False
963
965
964 def canwritewithoutlabels(self):
966 def canwritewithoutlabels(self):
965 '''check if write skips the label'''
967 '''check if write skips the label'''
966 if self._buffers and not self._bufferapplylabels:
968 if self._buffers and not self._bufferapplylabels:
967 return True
969 return True
968 return self._colormode is None
970 return self._colormode is None
969
971
970 def canbatchlabeledwrites(self):
972 def canbatchlabeledwrites(self):
971 '''check if write calls with labels are batchable'''
973 '''check if write calls with labels are batchable'''
972 # Windows color printing is special, see ``write``.
974 # Windows color printing is special, see ``write``.
973 return self._colormode != 'win32'
975 return self._colormode != 'win32'
974
976
975 def write(self, *args, **opts):
977 def write(self, *args, **opts):
976 '''write args to output
978 '''write args to output
977
979
978 By default, this method simply writes to the buffer or stdout.
980 By default, this method simply writes to the buffer or stdout.
979 Color mode can be set on the UI class to have the output decorated
981 Color mode can be set on the UI class to have the output decorated
980 with color modifier before being written to stdout.
982 with color modifier before being written to stdout.
981
983
982 The color used is controlled by an optional keyword argument, "label".
984 The color used is controlled by an optional keyword argument, "label".
983 This should be a string containing label names separated by space.
985 This should be a string containing label names separated by space.
984 Label names take the form of "topic.type". For example, ui.debug()
986 Label names take the form of "topic.type". For example, ui.debug()
985 issues a label of "ui.debug".
987 issues a label of "ui.debug".
986
988
987 When labeling output for a specific command, a label of
989 When labeling output for a specific command, a label of
988 "cmdname.type" is recommended. For example, status issues
990 "cmdname.type" is recommended. For example, status issues
989 a label of "status.modified" for modified files.
991 a label of "status.modified" for modified files.
990 '''
992 '''
991 self._write(self._fout, *args, **opts)
993 self._write(self._fout, *args, **opts)
992
994
993 def write_err(self, *args, **opts):
995 def write_err(self, *args, **opts):
994 self._write(self._ferr, *args, **opts)
996 self._write(self._ferr, *args, **opts)
995
997
996 def _write(self, dest, *args, **opts):
998 def _write(self, dest, *args, **opts):
997 if self._isbuffered(dest):
999 if self._isbuffered(dest):
998 if self._bufferapplylabels:
1000 if self._bufferapplylabels:
999 label = opts.get(r'label', '')
1001 label = opts.get(r'label', '')
1000 self._buffers[-1].extend(self.label(a, label) for a in args)
1002 self._buffers[-1].extend(self.label(a, label) for a in args)
1001 else:
1003 else:
1002 self._buffers[-1].extend(args)
1004 self._buffers[-1].extend(args)
1003 else:
1005 else:
1004 self._writenobuf(dest, *args, **opts)
1006 self._writenobuf(dest, *args, **opts)
1005
1007
1006 def _writenobuf(self, dest, *args, **opts):
1008 def _writenobuf(self, dest, *args, **opts):
1007 self._progclear()
1009 self._progclear()
1008 msg = b''.join(args)
1010 msg = b''.join(args)
1009
1011
1010 # opencode timeblockedsection because this is a critical path
1012 # opencode timeblockedsection because this is a critical path
1011 starttime = util.timer()
1013 starttime = util.timer()
1012 try:
1014 try:
1013 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1015 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1014 self._fout.flush()
1016 self._fout.flush()
1015 if getattr(dest, 'structured', False):
1017 if getattr(dest, 'structured', False):
1016 # channel for machine-readable output with metadata, where
1018 # channel for machine-readable output with metadata, where
1017 # no extra colorization is necessary.
1019 # no extra colorization is necessary.
1018 dest.write(msg, **opts)
1020 dest.write(msg, **opts)
1019 elif self._colormode == 'win32':
1021 elif self._colormode == 'win32':
1020 # windows color printing is its own can of crab, defer to
1022 # windows color printing is its own can of crab, defer to
1021 # the color module and that is it.
1023 # the color module and that is it.
1022 color.win32print(self, dest.write, msg, **opts)
1024 color.win32print(self, dest.write, msg, **opts)
1023 else:
1025 else:
1024 if self._colormode is not None:
1026 if self._colormode is not None:
1025 label = opts.get(r'label', '')
1027 label = opts.get(r'label', '')
1026 msg = self.label(msg, label)
1028 msg = self.label(msg, label)
1027 dest.write(msg)
1029 dest.write(msg)
1028 # stderr may be buffered under win32 when redirected to files,
1030 # stderr may be buffered under win32 when redirected to files,
1029 # including stdout.
1031 # including stdout.
1030 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1032 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1031 dest.flush()
1033 dest.flush()
1032 except IOError as err:
1034 except IOError as err:
1033 if (dest is self._ferr
1035 if (dest is self._ferr
1034 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1036 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1035 # no way to report the error, so ignore it
1037 # no way to report the error, so ignore it
1036 return
1038 return
1037 raise error.StdioError(err)
1039 raise error.StdioError(err)
1038 finally:
1040 finally:
1039 self._blockedtimes['stdio_blocked'] += \
1041 self._blockedtimes['stdio_blocked'] += \
1040 (util.timer() - starttime) * 1000
1042 (util.timer() - starttime) * 1000
1041
1043
1042 def _writemsg(self, dest, *args, **opts):
1044 def _writemsg(self, dest, *args, **opts):
1043 _writemsgwith(self._write, dest, *args, **opts)
1045 _writemsgwith(self._write, dest, *args, **opts)
1044
1046
1045 def _writemsgnobuf(self, dest, *args, **opts):
1047 def _writemsgnobuf(self, dest, *args, **opts):
1046 _writemsgwith(self._writenobuf, dest, *args, **opts)
1048 _writemsgwith(self._writenobuf, dest, *args, **opts)
1047
1049
1048 def flush(self):
1050 def flush(self):
1049 # opencode timeblockedsection because this is a critical path
1051 # opencode timeblockedsection because this is a critical path
1050 starttime = util.timer()
1052 starttime = util.timer()
1051 try:
1053 try:
1052 try:
1054 try:
1053 self._fout.flush()
1055 self._fout.flush()
1054 except IOError as err:
1056 except IOError as err:
1055 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1057 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1056 raise error.StdioError(err)
1058 raise error.StdioError(err)
1057 finally:
1059 finally:
1058 try:
1060 try:
1059 self._ferr.flush()
1061 self._ferr.flush()
1060 except IOError as err:
1062 except IOError as err:
1061 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1063 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1062 raise error.StdioError(err)
1064 raise error.StdioError(err)
1063 finally:
1065 finally:
1064 self._blockedtimes['stdio_blocked'] += \
1066 self._blockedtimes['stdio_blocked'] += \
1065 (util.timer() - starttime) * 1000
1067 (util.timer() - starttime) * 1000
1066
1068
1067 def _isatty(self, fh):
1069 def _isatty(self, fh):
1068 if self.configbool('ui', 'nontty'):
1070 if self.configbool('ui', 'nontty'):
1069 return False
1071 return False
1070 return procutil.isatty(fh)
1072 return procutil.isatty(fh)
1071
1073
1072 def disablepager(self):
1074 def disablepager(self):
1073 self._disablepager = True
1075 self._disablepager = True
1074
1076
1075 def pager(self, command):
1077 def pager(self, command):
1076 """Start a pager for subsequent command output.
1078 """Start a pager for subsequent command output.
1077
1079
1078 Commands which produce a long stream of output should call
1080 Commands which produce a long stream of output should call
1079 this function to activate the user's preferred pagination
1081 this function to activate the user's preferred pagination
1080 mechanism (which may be no pager). Calling this function
1082 mechanism (which may be no pager). Calling this function
1081 precludes any future use of interactive functionality, such as
1083 precludes any future use of interactive functionality, such as
1082 prompting the user or activating curses.
1084 prompting the user or activating curses.
1083
1085
1084 Args:
1086 Args:
1085 command: The full, non-aliased name of the command. That is, "log"
1087 command: The full, non-aliased name of the command. That is, "log"
1086 not "history, "summary" not "summ", etc.
1088 not "history, "summary" not "summ", etc.
1087 """
1089 """
1088 if (self._disablepager
1090 if (self._disablepager
1089 or self.pageractive):
1091 or self.pageractive):
1090 # how pager should do is already determined
1092 # how pager should do is already determined
1091 return
1093 return
1092
1094
1093 if not command.startswith('internal-always-') and (
1095 if not command.startswith('internal-always-') and (
1094 # explicit --pager=on (= 'internal-always-' prefix) should
1096 # explicit --pager=on (= 'internal-always-' prefix) should
1095 # take precedence over disabling factors below
1097 # take precedence over disabling factors below
1096 command in self.configlist('pager', 'ignore')
1098 command in self.configlist('pager', 'ignore')
1097 or not self.configbool('ui', 'paginate')
1099 or not self.configbool('ui', 'paginate')
1098 or not self.configbool('pager', 'attend-' + command, True)
1100 or not self.configbool('pager', 'attend-' + command, True)
1099 or encoding.environ.get('TERM') == 'dumb'
1101 or encoding.environ.get('TERM') == 'dumb'
1100 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1102 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1101 # formatted() will need some adjustment.
1103 # formatted() will need some adjustment.
1102 or not self.formatted()
1104 or not self.formatted()
1103 or self.plain()
1105 or self.plain()
1104 or self._buffers
1106 or self._buffers
1105 # TODO: expose debugger-enabled on the UI object
1107 # TODO: expose debugger-enabled on the UI object
1106 or '--debugger' in pycompat.sysargv):
1108 or '--debugger' in pycompat.sysargv):
1107 # We only want to paginate if the ui appears to be
1109 # We only want to paginate if the ui appears to be
1108 # interactive, the user didn't say HGPLAIN or
1110 # interactive, the user didn't say HGPLAIN or
1109 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1111 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1110 return
1112 return
1111
1113
1112 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1114 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1113 if not pagercmd:
1115 if not pagercmd:
1114 return
1116 return
1115
1117
1116 pagerenv = {}
1118 pagerenv = {}
1117 for name, value in rcutil.defaultpagerenv().items():
1119 for name, value in rcutil.defaultpagerenv().items():
1118 if name not in encoding.environ:
1120 if name not in encoding.environ:
1119 pagerenv[name] = value
1121 pagerenv[name] = value
1120
1122
1121 self.debug('starting pager for command %s\n' %
1123 self.debug('starting pager for command %s\n' %
1122 stringutil.pprint(command))
1124 stringutil.pprint(command))
1123 self.flush()
1125 self.flush()
1124
1126
1125 wasformatted = self.formatted()
1127 wasformatted = self.formatted()
1126 if util.safehasattr(signal, "SIGPIPE"):
1128 if util.safehasattr(signal, "SIGPIPE"):
1127 signal.signal(signal.SIGPIPE, _catchterm)
1129 signal.signal(signal.SIGPIPE, _catchterm)
1128 if self._runpager(pagercmd, pagerenv):
1130 if self._runpager(pagercmd, pagerenv):
1129 self.pageractive = True
1131 self.pageractive = True
1130 # Preserve the formatted-ness of the UI. This is important
1132 # Preserve the formatted-ness of the UI. This is important
1131 # because we mess with stdout, which might confuse
1133 # because we mess with stdout, which might confuse
1132 # auto-detection of things being formatted.
1134 # auto-detection of things being formatted.
1133 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1135 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1134 self.setconfig('ui', 'interactive', False, 'pager')
1136 self.setconfig('ui', 'interactive', False, 'pager')
1135
1137
1136 # If pagermode differs from color.mode, reconfigure color now that
1138 # If pagermode differs from color.mode, reconfigure color now that
1137 # pageractive is set.
1139 # pageractive is set.
1138 cm = self._colormode
1140 cm = self._colormode
1139 if cm != self.config('color', 'pagermode', cm):
1141 if cm != self.config('color', 'pagermode', cm):
1140 color.setup(self)
1142 color.setup(self)
1141 else:
1143 else:
1142 # If the pager can't be spawned in dispatch when --pager=on is
1144 # If the pager can't be spawned in dispatch when --pager=on is
1143 # given, don't try again when the command runs, to avoid a duplicate
1145 # given, don't try again when the command runs, to avoid a duplicate
1144 # warning about a missing pager command.
1146 # warning about a missing pager command.
1145 self.disablepager()
1147 self.disablepager()
1146
1148
1147 def _runpager(self, command, env=None):
1149 def _runpager(self, command, env=None):
1148 """Actually start the pager and set up file descriptors.
1150 """Actually start the pager and set up file descriptors.
1149
1151
1150 This is separate in part so that extensions (like chg) can
1152 This is separate in part so that extensions (like chg) can
1151 override how a pager is invoked.
1153 override how a pager is invoked.
1152 """
1154 """
1153 if command == 'cat':
1155 if command == 'cat':
1154 # Save ourselves some work.
1156 # Save ourselves some work.
1155 return False
1157 return False
1156 # If the command doesn't contain any of these characters, we
1158 # If the command doesn't contain any of these characters, we
1157 # assume it's a binary and exec it directly. This means for
1159 # assume it's a binary and exec it directly. This means for
1158 # simple pager command configurations, we can degrade
1160 # simple pager command configurations, we can degrade
1159 # gracefully and tell the user about their broken pager.
1161 # gracefully and tell the user about their broken pager.
1160 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1162 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1161
1163
1162 if pycompat.iswindows and not shell:
1164 if pycompat.iswindows and not shell:
1163 # Window's built-in `more` cannot be invoked with shell=False, but
1165 # Window's built-in `more` cannot be invoked with shell=False, but
1164 # its `more.com` can. Hide this implementation detail from the
1166 # its `more.com` can. Hide this implementation detail from the
1165 # user so we can also get sane bad PAGER behavior. MSYS has
1167 # user so we can also get sane bad PAGER behavior. MSYS has
1166 # `more.exe`, so do a cmd.exe style resolution of the executable to
1168 # `more.exe`, so do a cmd.exe style resolution of the executable to
1167 # determine which one to use.
1169 # determine which one to use.
1168 fullcmd = procutil.findexe(command)
1170 fullcmd = procutil.findexe(command)
1169 if not fullcmd:
1171 if not fullcmd:
1170 self.warn(_("missing pager command '%s', skipping pager\n")
1172 self.warn(_("missing pager command '%s', skipping pager\n")
1171 % command)
1173 % command)
1172 return False
1174 return False
1173
1175
1174 command = fullcmd
1176 command = fullcmd
1175
1177
1176 try:
1178 try:
1177 pager = subprocess.Popen(
1179 pager = subprocess.Popen(
1178 procutil.tonativestr(command), shell=shell, bufsize=-1,
1180 procutil.tonativestr(command), shell=shell, bufsize=-1,
1179 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1181 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1180 stdout=procutil.stdout, stderr=procutil.stderr,
1182 stdout=procutil.stdout, stderr=procutil.stderr,
1181 env=procutil.tonativeenv(procutil.shellenviron(env)))
1183 env=procutil.tonativeenv(procutil.shellenviron(env)))
1182 except OSError as e:
1184 except OSError as e:
1183 if e.errno == errno.ENOENT and not shell:
1185 if e.errno == errno.ENOENT and not shell:
1184 self.warn(_("missing pager command '%s', skipping pager\n")
1186 self.warn(_("missing pager command '%s', skipping pager\n")
1185 % command)
1187 % command)
1186 return False
1188 return False
1187 raise
1189 raise
1188
1190
1189 # back up original file descriptors
1191 # back up original file descriptors
1190 stdoutfd = os.dup(procutil.stdout.fileno())
1192 stdoutfd = os.dup(procutil.stdout.fileno())
1191 stderrfd = os.dup(procutil.stderr.fileno())
1193 stderrfd = os.dup(procutil.stderr.fileno())
1192
1194
1193 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1195 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1194 if self._isatty(procutil.stderr):
1196 if self._isatty(procutil.stderr):
1195 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1197 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1196
1198
1197 @self.atexit
1199 @self.atexit
1198 def killpager():
1200 def killpager():
1199 if util.safehasattr(signal, "SIGINT"):
1201 if util.safehasattr(signal, "SIGINT"):
1200 signal.signal(signal.SIGINT, signal.SIG_IGN)
1202 signal.signal(signal.SIGINT, signal.SIG_IGN)
1201 # restore original fds, closing pager.stdin copies in the process
1203 # restore original fds, closing pager.stdin copies in the process
1202 os.dup2(stdoutfd, procutil.stdout.fileno())
1204 os.dup2(stdoutfd, procutil.stdout.fileno())
1203 os.dup2(stderrfd, procutil.stderr.fileno())
1205 os.dup2(stderrfd, procutil.stderr.fileno())
1204 pager.stdin.close()
1206 pager.stdin.close()
1205 pager.wait()
1207 pager.wait()
1206
1208
1207 return True
1209 return True
1208
1210
1209 @property
1211 @property
1210 def _exithandlers(self):
1212 def _exithandlers(self):
1211 return _reqexithandlers
1213 return _reqexithandlers
1212
1214
1213 def atexit(self, func, *args, **kwargs):
1215 def atexit(self, func, *args, **kwargs):
1214 '''register a function to run after dispatching a request
1216 '''register a function to run after dispatching a request
1215
1217
1216 Handlers do not stay registered across request boundaries.'''
1218 Handlers do not stay registered across request boundaries.'''
1217 self._exithandlers.append((func, args, kwargs))
1219 self._exithandlers.append((func, args, kwargs))
1218 return func
1220 return func
1219
1221
1220 def interface(self, feature):
1222 def interface(self, feature):
1221 """what interface to use for interactive console features?
1223 """what interface to use for interactive console features?
1222
1224
1223 The interface is controlled by the value of `ui.interface` but also by
1225 The interface is controlled by the value of `ui.interface` but also by
1224 the value of feature-specific configuration. For example:
1226 the value of feature-specific configuration. For example:
1225
1227
1226 ui.interface.histedit = text
1228 ui.interface.histedit = text
1227 ui.interface.chunkselector = curses
1229 ui.interface.chunkselector = curses
1228
1230
1229 Here the features are "histedit" and "chunkselector".
1231 Here the features are "histedit" and "chunkselector".
1230
1232
1231 The configuration above means that the default interfaces for commands
1233 The configuration above means that the default interfaces for commands
1232 is curses, the interface for histedit is text and the interface for
1234 is curses, the interface for histedit is text and the interface for
1233 selecting chunk is crecord (the best curses interface available).
1235 selecting chunk is crecord (the best curses interface available).
1234
1236
1235 Consider the following example:
1237 Consider the following example:
1236 ui.interface = curses
1238 ui.interface = curses
1237 ui.interface.histedit = text
1239 ui.interface.histedit = text
1238
1240
1239 Then histedit will use the text interface and chunkselector will use
1241 Then histedit will use the text interface and chunkselector will use
1240 the default curses interface (crecord at the moment).
1242 the default curses interface (crecord at the moment).
1241 """
1243 """
1242 alldefaults = frozenset(["text", "curses"])
1244 alldefaults = frozenset(["text", "curses"])
1243
1245
1244 featureinterfaces = {
1246 featureinterfaces = {
1245 "chunkselector": [
1247 "chunkselector": [
1246 "text",
1248 "text",
1247 "curses",
1249 "curses",
1248 ],
1250 ],
1249 "histedit": [
1251 "histedit": [
1250 "text",
1252 "text",
1251 "curses",
1253 "curses",
1252 ],
1254 ],
1253 }
1255 }
1254
1256
1255 # Feature-specific interface
1257 # Feature-specific interface
1256 if feature not in featureinterfaces.keys():
1258 if feature not in featureinterfaces.keys():
1257 # Programming error, not user error
1259 # Programming error, not user error
1258 raise ValueError("Unknown feature requested %s" % feature)
1260 raise ValueError("Unknown feature requested %s" % feature)
1259
1261
1260 availableinterfaces = frozenset(featureinterfaces[feature])
1262 availableinterfaces = frozenset(featureinterfaces[feature])
1261 if alldefaults > availableinterfaces:
1263 if alldefaults > availableinterfaces:
1262 # Programming error, not user error. We need a use case to
1264 # Programming error, not user error. We need a use case to
1263 # define the right thing to do here.
1265 # define the right thing to do here.
1264 raise ValueError(
1266 raise ValueError(
1265 "Feature %s does not handle all default interfaces" %
1267 "Feature %s does not handle all default interfaces" %
1266 feature)
1268 feature)
1267
1269
1268 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1270 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1269 return "text"
1271 return "text"
1270
1272
1271 # Default interface for all the features
1273 # Default interface for all the features
1272 defaultinterface = "text"
1274 defaultinterface = "text"
1273 i = self.config("ui", "interface")
1275 i = self.config("ui", "interface")
1274 if i in alldefaults:
1276 if i in alldefaults:
1275 defaultinterface = i
1277 defaultinterface = i
1276
1278
1277 choseninterface = defaultinterface
1279 choseninterface = defaultinterface
1278 f = self.config("ui", "interface.%s" % feature)
1280 f = self.config("ui", "interface.%s" % feature)
1279 if f in availableinterfaces:
1281 if f in availableinterfaces:
1280 choseninterface = f
1282 choseninterface = f
1281
1283
1282 if i is not None and defaultinterface != i:
1284 if i is not None and defaultinterface != i:
1283 if f is not None:
1285 if f is not None:
1284 self.warn(_("invalid value for ui.interface: %s\n") %
1286 self.warn(_("invalid value for ui.interface: %s\n") %
1285 (i,))
1287 (i,))
1286 else:
1288 else:
1287 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1289 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1288 (i, choseninterface))
1290 (i, choseninterface))
1289 if f is not None and choseninterface != f:
1291 if f is not None and choseninterface != f:
1290 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1292 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1291 (feature, f, choseninterface))
1293 (feature, f, choseninterface))
1292
1294
1293 return choseninterface
1295 return choseninterface
1294
1296
1295 def interactive(self):
1297 def interactive(self):
1296 '''is interactive input allowed?
1298 '''is interactive input allowed?
1297
1299
1298 An interactive session is a session where input can be reasonably read
1300 An interactive session is a session where input can be reasonably read
1299 from `sys.stdin'. If this function returns false, any attempt to read
1301 from `sys.stdin'. If this function returns false, any attempt to read
1300 from stdin should fail with an error, unless a sensible default has been
1302 from stdin should fail with an error, unless a sensible default has been
1301 specified.
1303 specified.
1302
1304
1303 Interactiveness is triggered by the value of the `ui.interactive'
1305 Interactiveness is triggered by the value of the `ui.interactive'
1304 configuration variable or - if it is unset - when `sys.stdin' points
1306 configuration variable or - if it is unset - when `sys.stdin' points
1305 to a terminal device.
1307 to a terminal device.
1306
1308
1307 This function refers to input only; for output, see `ui.formatted()'.
1309 This function refers to input only; for output, see `ui.formatted()'.
1308 '''
1310 '''
1309 i = self.configbool("ui", "interactive")
1311 i = self.configbool("ui", "interactive")
1310 if i is None:
1312 if i is None:
1311 # some environments replace stdin without implementing isatty
1313 # some environments replace stdin without implementing isatty
1312 # usually those are non-interactive
1314 # usually those are non-interactive
1313 return self._isatty(self._fin)
1315 return self._isatty(self._fin)
1314
1316
1315 return i
1317 return i
1316
1318
1317 def termwidth(self):
1319 def termwidth(self):
1318 '''how wide is the terminal in columns?
1320 '''how wide is the terminal in columns?
1319 '''
1321 '''
1320 if 'COLUMNS' in encoding.environ:
1322 if 'COLUMNS' in encoding.environ:
1321 try:
1323 try:
1322 return int(encoding.environ['COLUMNS'])
1324 return int(encoding.environ['COLUMNS'])
1323 except ValueError:
1325 except ValueError:
1324 pass
1326 pass
1325 return scmutil.termsize(self)[0]
1327 return scmutil.termsize(self)[0]
1326
1328
1327 def formatted(self):
1329 def formatted(self):
1328 '''should formatted output be used?
1330 '''should formatted output be used?
1329
1331
1330 It is often desirable to format the output to suite the output medium.
1332 It is often desirable to format the output to suite the output medium.
1331 Examples of this are truncating long lines or colorizing messages.
1333 Examples of this are truncating long lines or colorizing messages.
1332 However, this is not often not desirable when piping output into other
1334 However, this is not often not desirable when piping output into other
1333 utilities, e.g. `grep'.
1335 utilities, e.g. `grep'.
1334
1336
1335 Formatted output is triggered by the value of the `ui.formatted'
1337 Formatted output is triggered by the value of the `ui.formatted'
1336 configuration variable or - if it is unset - when `sys.stdout' points
1338 configuration variable or - if it is unset - when `sys.stdout' points
1337 to a terminal device. Please note that `ui.formatted' should be
1339 to a terminal device. Please note that `ui.formatted' should be
1338 considered an implementation detail; it is not intended for use outside
1340 considered an implementation detail; it is not intended for use outside
1339 Mercurial or its extensions.
1341 Mercurial or its extensions.
1340
1342
1341 This function refers to output only; for input, see `ui.interactive()'.
1343 This function refers to output only; for input, see `ui.interactive()'.
1342 This function always returns false when in plain mode, see `ui.plain()'.
1344 This function always returns false when in plain mode, see `ui.plain()'.
1343 '''
1345 '''
1344 if self.plain():
1346 if self.plain():
1345 return False
1347 return False
1346
1348
1347 i = self.configbool("ui", "formatted")
1349 i = self.configbool("ui", "formatted")
1348 if i is None:
1350 if i is None:
1349 # some environments replace stdout without implementing isatty
1351 # some environments replace stdout without implementing isatty
1350 # usually those are non-interactive
1352 # usually those are non-interactive
1351 return self._isatty(self._fout)
1353 return self._isatty(self._fout)
1352
1354
1353 return i
1355 return i
1354
1356
1355 def _readline(self):
1357 def _readline(self):
1356 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1358 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1357 # because they have to be text streams with *no buffering*. Instead,
1359 # because they have to be text streams with *no buffering*. Instead,
1358 # we use rawinput() only if call_readline() will be invoked by
1360 # we use rawinput() only if call_readline() will be invoked by
1359 # PyOS_Readline(), so no I/O will be made at Python layer.
1361 # PyOS_Readline(), so no I/O will be made at Python layer.
1360 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1362 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1361 and procutil.isstdin(self._fin)
1363 and procutil.isstdin(self._fin)
1362 and procutil.isstdout(self._fout))
1364 and procutil.isstdout(self._fout))
1363 if usereadline:
1365 if usereadline:
1364 try:
1366 try:
1365 # magically add command line editing support, where
1367 # magically add command line editing support, where
1366 # available
1368 # available
1367 import readline
1369 import readline
1368 # force demandimport to really load the module
1370 # force demandimport to really load the module
1369 readline.read_history_file
1371 readline.read_history_file
1370 # windows sometimes raises something other than ImportError
1372 # windows sometimes raises something other than ImportError
1371 except Exception:
1373 except Exception:
1372 usereadline = False
1374 usereadline = False
1373
1375
1374 # prompt ' ' must exist; otherwise readline may delete entire line
1376 # prompt ' ' must exist; otherwise readline may delete entire line
1375 # - http://bugs.python.org/issue12833
1377 # - http://bugs.python.org/issue12833
1376 with self.timeblockedsection('stdio'):
1378 with self.timeblockedsection('stdio'):
1377 if usereadline:
1379 if usereadline:
1378 line = encoding.strtolocal(pycompat.rawinput(r' '))
1380 line = encoding.strtolocal(pycompat.rawinput(r' '))
1379 # When stdin is in binary mode on Windows, it can cause
1381 # When stdin is in binary mode on Windows, it can cause
1380 # raw_input() to emit an extra trailing carriage return
1382 # raw_input() to emit an extra trailing carriage return
1381 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1383 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1382 line = line[:-1]
1384 line = line[:-1]
1383 else:
1385 else:
1384 self._fout.write(b' ')
1386 self._fout.write(b' ')
1385 self._fout.flush()
1387 self._fout.flush()
1386 line = self._fin.readline()
1388 line = self._fin.readline()
1387 if not line:
1389 if not line:
1388 raise EOFError
1390 raise EOFError
1389 line = line.rstrip(pycompat.oslinesep)
1391 line = line.rstrip(pycompat.oslinesep)
1390
1392
1391 return line
1393 return line
1392
1394
1393 def prompt(self, msg, default="y"):
1395 def prompt(self, msg, default="y"):
1394 """Prompt user with msg, read response.
1396 """Prompt user with msg, read response.
1395 If ui is not interactive, the default is returned.
1397 If ui is not interactive, the default is returned.
1396 """
1398 """
1397 return self._prompt(msg, default=default)
1399 return self._prompt(msg, default=default)
1398
1400
1399 def _prompt(self, msg, **opts):
1401 def _prompt(self, msg, **opts):
1400 default = opts[r'default']
1402 default = opts[r'default']
1401 if not self.interactive():
1403 if not self.interactive():
1402 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1404 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1403 self._writemsg(self._fmsgout, default or '', "\n",
1405 self._writemsg(self._fmsgout, default or '', "\n",
1404 type='promptecho')
1406 type='promptecho')
1405 return default
1407 return default
1406 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1408 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1407 self.flush()
1409 self.flush()
1408 try:
1410 try:
1409 r = self._readline()
1411 r = self._readline()
1410 if not r:
1412 if not r:
1411 r = default
1413 r = default
1412 if self.configbool('ui', 'promptecho'):
1414 if self.configbool('ui', 'promptecho'):
1413 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1415 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1414 return r
1416 return r
1415 except EOFError:
1417 except EOFError:
1416 raise error.ResponseExpected()
1418 raise error.ResponseExpected()
1417
1419
1418 @staticmethod
1420 @staticmethod
1419 def extractchoices(prompt):
1421 def extractchoices(prompt):
1420 """Extract prompt message and list of choices from specified prompt.
1422 """Extract prompt message and list of choices from specified prompt.
1421
1423
1422 This returns tuple "(message, choices)", and "choices" is the
1424 This returns tuple "(message, choices)", and "choices" is the
1423 list of tuple "(response character, text without &)".
1425 list of tuple "(response character, text without &)".
1424
1426
1425 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1427 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1426 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1428 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1427 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1429 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1428 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1430 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1429 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1431 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1430 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1432 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1431 """
1433 """
1432
1434
1433 # Sadly, the prompt string may have been built with a filename
1435 # Sadly, the prompt string may have been built with a filename
1434 # containing "$$" so let's try to find the first valid-looking
1436 # containing "$$" so let's try to find the first valid-looking
1435 # prompt to start parsing. Sadly, we also can't rely on
1437 # prompt to start parsing. Sadly, we also can't rely on
1436 # choices containing spaces, ASCII, or basically anything
1438 # choices containing spaces, ASCII, or basically anything
1437 # except an ampersand followed by a character.
1439 # except an ampersand followed by a character.
1438 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1440 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1439 msg = m.group(1)
1441 msg = m.group(1)
1440 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1442 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1441 def choicetuple(s):
1443 def choicetuple(s):
1442 ampidx = s.index('&')
1444 ampidx = s.index('&')
1443 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1445 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1444 return (msg, [choicetuple(s) for s in choices])
1446 return (msg, [choicetuple(s) for s in choices])
1445
1447
1446 def promptchoice(self, prompt, default=0):
1448 def promptchoice(self, prompt, default=0):
1447 """Prompt user with a message, read response, and ensure it matches
1449 """Prompt user with a message, read response, and ensure it matches
1448 one of the provided choices. The prompt is formatted as follows:
1450 one of the provided choices. The prompt is formatted as follows:
1449
1451
1450 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1452 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1451
1453
1452 The index of the choice is returned. Responses are case
1454 The index of the choice is returned. Responses are case
1453 insensitive. If ui is not interactive, the default is
1455 insensitive. If ui is not interactive, the default is
1454 returned.
1456 returned.
1455 """
1457 """
1456
1458
1457 msg, choices = self.extractchoices(prompt)
1459 msg, choices = self.extractchoices(prompt)
1458 resps = [r for r, t in choices]
1460 resps = [r for r, t in choices]
1459 while True:
1461 while True:
1460 r = self._prompt(msg, default=resps[default], choices=choices)
1462 r = self._prompt(msg, default=resps[default], choices=choices)
1461 if r.lower() in resps:
1463 if r.lower() in resps:
1462 return resps.index(r.lower())
1464 return resps.index(r.lower())
1463 # TODO: shouldn't it be a warning?
1465 # TODO: shouldn't it be a warning?
1464 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1466 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1465
1467
1466 def getpass(self, prompt=None, default=None):
1468 def getpass(self, prompt=None, default=None):
1467 if not self.interactive():
1469 if not self.interactive():
1468 return default
1470 return default
1469 try:
1471 try:
1470 self._writemsg(self._fmsgerr, prompt or _('password: '),
1472 self._writemsg(self._fmsgerr, prompt or _('password: '),
1471 type='prompt', password=True)
1473 type='prompt', password=True)
1472 # disable getpass() only if explicitly specified. it's still valid
1474 # disable getpass() only if explicitly specified. it's still valid
1473 # to interact with tty even if fin is not a tty.
1475 # to interact with tty even if fin is not a tty.
1474 with self.timeblockedsection('stdio'):
1476 with self.timeblockedsection('stdio'):
1475 if self.configbool('ui', 'nontty'):
1477 if self.configbool('ui', 'nontty'):
1476 l = self._fin.readline()
1478 l = self._fin.readline()
1477 if not l:
1479 if not l:
1478 raise EOFError
1480 raise EOFError
1479 return l.rstrip('\n')
1481 return l.rstrip('\n')
1480 else:
1482 else:
1481 return getpass.getpass('')
1483 return getpass.getpass('')
1482 except EOFError:
1484 except EOFError:
1483 raise error.ResponseExpected()
1485 raise error.ResponseExpected()
1484
1486
1485 def status(self, *msg, **opts):
1487 def status(self, *msg, **opts):
1486 '''write status message to output (if ui.quiet is False)
1488 '''write status message to output (if ui.quiet is False)
1487
1489
1488 This adds an output label of "ui.status".
1490 This adds an output label of "ui.status".
1489 '''
1491 '''
1490 if not self.quiet:
1492 if not self.quiet:
1491 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1493 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1492
1494
1493 def warn(self, *msg, **opts):
1495 def warn(self, *msg, **opts):
1494 '''write warning message to output (stderr)
1496 '''write warning message to output (stderr)
1495
1497
1496 This adds an output label of "ui.warning".
1498 This adds an output label of "ui.warning".
1497 '''
1499 '''
1498 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1500 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1499
1501
1500 def error(self, *msg, **opts):
1502 def error(self, *msg, **opts):
1501 '''write error message to output (stderr)
1503 '''write error message to output (stderr)
1502
1504
1503 This adds an output label of "ui.error".
1505 This adds an output label of "ui.error".
1504 '''
1506 '''
1505 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1507 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1506
1508
1507 def note(self, *msg, **opts):
1509 def note(self, *msg, **opts):
1508 '''write note to output (if ui.verbose is True)
1510 '''write note to output (if ui.verbose is True)
1509
1511
1510 This adds an output label of "ui.note".
1512 This adds an output label of "ui.note".
1511 '''
1513 '''
1512 if self.verbose:
1514 if self.verbose:
1513 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1515 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1514
1516
1515 def debug(self, *msg, **opts):
1517 def debug(self, *msg, **opts):
1516 '''write debug message to output (if ui.debugflag is True)
1518 '''write debug message to output (if ui.debugflag is True)
1517
1519
1518 This adds an output label of "ui.debug".
1520 This adds an output label of "ui.debug".
1519 '''
1521 '''
1520 if self.debugflag:
1522 if self.debugflag:
1521 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1523 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1522
1524
1523 def edit(self, text, user, extra=None, editform=None, pending=None,
1525 def edit(self, text, user, extra=None, editform=None, pending=None,
1524 repopath=None, action=None):
1526 repopath=None, action=None):
1525 if action is None:
1527 if action is None:
1526 self.develwarn('action is None but will soon be a required '
1528 self.develwarn('action is None but will soon be a required '
1527 'parameter to ui.edit()')
1529 'parameter to ui.edit()')
1528 extra_defaults = {
1530 extra_defaults = {
1529 'prefix': 'editor',
1531 'prefix': 'editor',
1530 'suffix': '.txt',
1532 'suffix': '.txt',
1531 }
1533 }
1532 if extra is not None:
1534 if extra is not None:
1533 if extra.get('suffix') is not None:
1535 if extra.get('suffix') is not None:
1534 self.develwarn('extra.suffix is not None but will soon be '
1536 self.develwarn('extra.suffix is not None but will soon be '
1535 'ignored by ui.edit()')
1537 'ignored by ui.edit()')
1536 extra_defaults.update(extra)
1538 extra_defaults.update(extra)
1537 extra = extra_defaults
1539 extra = extra_defaults
1538
1540
1539 if action == 'diff':
1541 if action == 'diff':
1540 suffix = '.diff'
1542 suffix = '.diff'
1541 elif action:
1543 elif action:
1542 suffix = '.%s.hg.txt' % action
1544 suffix = '.%s.hg.txt' % action
1543 else:
1545 else:
1544 suffix = extra['suffix']
1546 suffix = extra['suffix']
1545
1547
1546 rdir = None
1548 rdir = None
1547 if self.configbool('experimental', 'editortmpinhg'):
1549 if self.configbool('experimental', 'editortmpinhg'):
1548 rdir = repopath
1550 rdir = repopath
1549 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1551 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1550 suffix=suffix,
1552 suffix=suffix,
1551 dir=rdir)
1553 dir=rdir)
1552 try:
1554 try:
1553 f = os.fdopen(fd, r'wb')
1555 f = os.fdopen(fd, r'wb')
1554 f.write(util.tonativeeol(text))
1556 f.write(util.tonativeeol(text))
1555 f.close()
1557 f.close()
1556
1558
1557 environ = {'HGUSER': user}
1559 environ = {'HGUSER': user}
1558 if 'transplant_source' in extra:
1560 if 'transplant_source' in extra:
1559 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1561 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1560 for label in ('intermediate-source', 'source', 'rebase_source'):
1562 for label in ('intermediate-source', 'source', 'rebase_source'):
1561 if label in extra:
1563 if label in extra:
1562 environ.update({'HGREVISION': extra[label]})
1564 environ.update({'HGREVISION': extra[label]})
1563 break
1565 break
1564 if editform:
1566 if editform:
1565 environ.update({'HGEDITFORM': editform})
1567 environ.update({'HGEDITFORM': editform})
1566 if pending:
1568 if pending:
1567 environ.update({'HG_PENDING': pending})
1569 environ.update({'HG_PENDING': pending})
1568
1570
1569 editor = self.geteditor()
1571 editor = self.geteditor()
1570
1572
1571 self.system("%s \"%s\"" % (editor, name),
1573 self.system("%s \"%s\"" % (editor, name),
1572 environ=environ,
1574 environ=environ,
1573 onerr=error.Abort, errprefix=_("edit failed"),
1575 onerr=error.Abort, errprefix=_("edit failed"),
1574 blockedtag='editor')
1576 blockedtag='editor')
1575
1577
1576 f = open(name, r'rb')
1578 f = open(name, r'rb')
1577 t = util.fromnativeeol(f.read())
1579 t = util.fromnativeeol(f.read())
1578 f.close()
1580 f.close()
1579 finally:
1581 finally:
1580 os.unlink(name)
1582 os.unlink(name)
1581
1583
1582 return t
1584 return t
1583
1585
1584 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1586 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1585 blockedtag=None):
1587 blockedtag=None):
1586 '''execute shell command with appropriate output stream. command
1588 '''execute shell command with appropriate output stream. command
1587 output will be redirected if fout is not stdout.
1589 output will be redirected if fout is not stdout.
1588
1590
1589 if command fails and onerr is None, return status, else raise onerr
1591 if command fails and onerr is None, return status, else raise onerr
1590 object as exception.
1592 object as exception.
1591 '''
1593 '''
1592 if blockedtag is None:
1594 if blockedtag is None:
1593 # Long cmds tend to be because of an absolute path on cmd. Keep
1595 # Long cmds tend to be because of an absolute path on cmd. Keep
1594 # the tail end instead
1596 # the tail end instead
1595 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1597 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1596 blockedtag = 'unknown_system_' + cmdsuffix
1598 blockedtag = 'unknown_system_' + cmdsuffix
1597 out = self._fout
1599 out = self._fout
1598 if any(s[1] for s in self._bufferstates):
1600 if any(s[1] for s in self._bufferstates):
1599 out = self
1601 out = self
1600 with self.timeblockedsection(blockedtag):
1602 with self.timeblockedsection(blockedtag):
1601 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1603 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1602 if rc and onerr:
1604 if rc and onerr:
1603 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1605 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1604 procutil.explainexit(rc))
1606 procutil.explainexit(rc))
1605 if errprefix:
1607 if errprefix:
1606 errmsg = '%s: %s' % (errprefix, errmsg)
1608 errmsg = '%s: %s' % (errprefix, errmsg)
1607 raise onerr(errmsg)
1609 raise onerr(errmsg)
1608 return rc
1610 return rc
1609
1611
1610 def _runsystem(self, cmd, environ, cwd, out):
1612 def _runsystem(self, cmd, environ, cwd, out):
1611 """actually execute the given shell command (can be overridden by
1613 """actually execute the given shell command (can be overridden by
1612 extensions like chg)"""
1614 extensions like chg)"""
1613 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1615 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1614
1616
1615 def traceback(self, exc=None, force=False):
1617 def traceback(self, exc=None, force=False):
1616 '''print exception traceback if traceback printing enabled or forced.
1618 '''print exception traceback if traceback printing enabled or forced.
1617 only to call in exception handler. returns true if traceback
1619 only to call in exception handler. returns true if traceback
1618 printed.'''
1620 printed.'''
1619 if self.tracebackflag or force:
1621 if self.tracebackflag or force:
1620 if exc is None:
1622 if exc is None:
1621 exc = sys.exc_info()
1623 exc = sys.exc_info()
1622 cause = getattr(exc[1], 'cause', None)
1624 cause = getattr(exc[1], 'cause', None)
1623
1625
1624 if cause is not None:
1626 if cause is not None:
1625 causetb = traceback.format_tb(cause[2])
1627 causetb = traceback.format_tb(cause[2])
1626 exctb = traceback.format_tb(exc[2])
1628 exctb = traceback.format_tb(exc[2])
1627 exconly = traceback.format_exception_only(cause[0], cause[1])
1629 exconly = traceback.format_exception_only(cause[0], cause[1])
1628
1630
1629 # exclude frame where 'exc' was chained and rethrown from exctb
1631 # exclude frame where 'exc' was chained and rethrown from exctb
1630 self.write_err('Traceback (most recent call last):\n',
1632 self.write_err('Traceback (most recent call last):\n',
1631 ''.join(exctb[:-1]),
1633 ''.join(exctb[:-1]),
1632 ''.join(causetb),
1634 ''.join(causetb),
1633 ''.join(exconly))
1635 ''.join(exconly))
1634 else:
1636 else:
1635 output = traceback.format_exception(exc[0], exc[1], exc[2])
1637 output = traceback.format_exception(exc[0], exc[1], exc[2])
1636 self.write_err(encoding.strtolocal(r''.join(output)))
1638 self.write_err(encoding.strtolocal(r''.join(output)))
1637 return self.tracebackflag or force
1639 return self.tracebackflag or force
1638
1640
1639 def geteditor(self):
1641 def geteditor(self):
1640 '''return editor to use'''
1642 '''return editor to use'''
1641 if pycompat.sysplatform == 'plan9':
1643 if pycompat.sysplatform == 'plan9':
1642 # vi is the MIPS instruction simulator on Plan 9. We
1644 # vi is the MIPS instruction simulator on Plan 9. We
1643 # instead default to E to plumb commit messages to
1645 # instead default to E to plumb commit messages to
1644 # avoid confusion.
1646 # avoid confusion.
1645 editor = 'E'
1647 editor = 'E'
1646 else:
1648 else:
1647 editor = 'vi'
1649 editor = 'vi'
1648 return (encoding.environ.get("HGEDITOR") or
1650 return (encoding.environ.get("HGEDITOR") or
1649 self.config("ui", "editor", editor))
1651 self.config("ui", "editor", editor))
1650
1652
1651 @util.propertycache
1653 @util.propertycache
1652 def _progbar(self):
1654 def _progbar(self):
1653 """setup the progbar singleton to the ui object"""
1655 """setup the progbar singleton to the ui object"""
1654 if (self.quiet or self.debugflag
1656 if (self.quiet or self.debugflag
1655 or self.configbool('progress', 'disable')
1657 or self.configbool('progress', 'disable')
1656 or not progress.shouldprint(self)):
1658 or not progress.shouldprint(self)):
1657 return None
1659 return None
1658 return getprogbar(self)
1660 return getprogbar(self)
1659
1661
1660 def _progclear(self):
1662 def _progclear(self):
1661 """clear progress bar output if any. use it before any output"""
1663 """clear progress bar output if any. use it before any output"""
1662 if not haveprogbar(): # nothing loaded yet
1664 if not haveprogbar(): # nothing loaded yet
1663 return
1665 return
1664 if self._progbar is not None and self._progbar.printed:
1666 if self._progbar is not None and self._progbar.printed:
1665 self._progbar.clear()
1667 self._progbar.clear()
1666
1668
1667 def progress(self, topic, pos, item="", unit="", total=None):
1669 def progress(self, topic, pos, item="", unit="", total=None):
1668 '''show a progress message
1670 '''show a progress message
1669
1671
1670 By default a textual progress bar will be displayed if an operation
1672 By default a textual progress bar will be displayed if an operation
1671 takes too long. 'topic' is the current operation, 'item' is a
1673 takes too long. 'topic' is the current operation, 'item' is a
1672 non-numeric marker of the current position (i.e. the currently
1674 non-numeric marker of the current position (i.e. the currently
1673 in-process file), 'pos' is the current numeric position (i.e.
1675 in-process file), 'pos' is the current numeric position (i.e.
1674 revision, bytes, etc.), unit is a corresponding unit label,
1676 revision, bytes, etc.), unit is a corresponding unit label,
1675 and total is the highest expected pos.
1677 and total is the highest expected pos.
1676
1678
1677 Multiple nested topics may be active at a time.
1679 Multiple nested topics may be active at a time.
1678
1680
1679 All topics should be marked closed by setting pos to None at
1681 All topics should be marked closed by setting pos to None at
1680 termination.
1682 termination.
1681 '''
1683 '''
1682 if getattr(self._fmsgerr, 'structured', False):
1684 if getattr(self._fmsgerr, 'structured', False):
1683 # channel for machine-readable output with metadata, just send
1685 # channel for machine-readable output with metadata, just send
1684 # raw information
1686 # raw information
1685 # TODO: consider porting some useful information (e.g. estimated
1687 # TODO: consider porting some useful information (e.g. estimated
1686 # time) from progbar. we might want to support update delay to
1688 # time) from progbar. we might want to support update delay to
1687 # reduce the cost of transferring progress messages.
1689 # reduce the cost of transferring progress messages.
1688 self._fmsgerr.write(None, type=b'progress', topic=topic, pos=pos,
1690 self._fmsgerr.write(None, type=b'progress', topic=topic, pos=pos,
1689 item=item, unit=unit, total=total)
1691 item=item, unit=unit, total=total)
1690 elif self._progbar is not None:
1692 elif self._progbar is not None:
1691 self._progbar.progress(topic, pos, item=item, unit=unit,
1693 self._progbar.progress(topic, pos, item=item, unit=unit,
1692 total=total)
1694 total=total)
1693 if pos is None or not self.configbool('progress', 'debug'):
1695 if pos is None or not self.configbool('progress', 'debug'):
1694 return
1696 return
1695
1697
1696 if unit:
1698 if unit:
1697 unit = ' ' + unit
1699 unit = ' ' + unit
1698 if item:
1700 if item:
1699 item = ' ' + item
1701 item = ' ' + item
1700
1702
1701 if total:
1703 if total:
1702 pct = 100.0 * pos / total
1704 pct = 100.0 * pos / total
1703 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1705 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1704 % (topic, item, pos, total, unit, pct))
1706 % (topic, item, pos, total, unit, pct))
1705 else:
1707 else:
1706 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1708 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1707
1709
1708 def makeprogress(self, topic, unit="", total=None):
1710 def makeprogress(self, topic, unit="", total=None):
1709 '''exists only so low-level modules won't need to import scmutil'''
1711 '''exists only so low-level modules won't need to import scmutil'''
1710 return scmutil.progress(self, topic, unit, total)
1712 return scmutil.progress(self, topic, unit, total)
1711
1713
1714 def getlogger(self, name):
1715 """Returns a logger of the given name; or None if not registered"""
1716 return self._loggers.get(name)
1717
1718 def setlogger(self, name, logger):
1719 """Install logger which can be identified later by the given name
1720
1721 More than one loggers can be registered. Use extension or module
1722 name to uniquely identify the logger instance.
1723 """
1724 self._loggers[name] = logger
1725
1712 def log(self, event, *msg, **opts):
1726 def log(self, event, *msg, **opts):
1713 '''hook for logging facility extensions
1727 '''hook for logging facility extensions
1714
1728
1715 event should be a readily-identifiable subsystem, which will
1729 event should be a readily-identifiable subsystem, which will
1716 allow filtering.
1730 allow filtering.
1717
1731
1718 *msg should be a newline-terminated format string to log, and
1732 *msg should be a newline-terminated format string to log, and
1719 then any values to %-format into that format string.
1733 then any values to %-format into that format string.
1720
1734
1721 **opts currently has no defined meanings.
1735 **opts currently has no defined meanings.
1722 '''
1736 '''
1737 if not self._loggers:
1738 return
1739 activeloggers = [l for l in self._loggers.itervalues()
1740 if l.tracked(event)]
1741 if not activeloggers:
1742 return
1743 for logger in activeloggers:
1744 logger.log(self, event, msg, opts)
1723
1745
1724 def label(self, msg, label):
1746 def label(self, msg, label):
1725 '''style msg based on supplied label
1747 '''style msg based on supplied label
1726
1748
1727 If some color mode is enabled, this will add the necessary control
1749 If some color mode is enabled, this will add the necessary control
1728 characters to apply such color. In addition, 'debug' color mode adds
1750 characters to apply such color. In addition, 'debug' color mode adds
1729 markup showing which label affects a piece of text.
1751 markup showing which label affects a piece of text.
1730
1752
1731 ui.write(s, 'label') is equivalent to
1753 ui.write(s, 'label') is equivalent to
1732 ui.write(ui.label(s, 'label')).
1754 ui.write(ui.label(s, 'label')).
1733 '''
1755 '''
1734 if self._colormode is not None:
1756 if self._colormode is not None:
1735 return color.colorlabel(self, msg, label)
1757 return color.colorlabel(self, msg, label)
1736 return msg
1758 return msg
1737
1759
1738 def develwarn(self, msg, stacklevel=1, config=None):
1760 def develwarn(self, msg, stacklevel=1, config=None):
1739 """issue a developer warning message
1761 """issue a developer warning message
1740
1762
1741 Use 'stacklevel' to report the offender some layers further up in the
1763 Use 'stacklevel' to report the offender some layers further up in the
1742 stack.
1764 stack.
1743 """
1765 """
1744 if not self.configbool('devel', 'all-warnings'):
1766 if not self.configbool('devel', 'all-warnings'):
1745 if config is None or not self.configbool('devel', config):
1767 if config is None or not self.configbool('devel', config):
1746 return
1768 return
1747 msg = 'devel-warn: ' + msg
1769 msg = 'devel-warn: ' + msg
1748 stacklevel += 1 # get in develwarn
1770 stacklevel += 1 # get in develwarn
1749 if self.tracebackflag:
1771 if self.tracebackflag:
1750 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1772 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1751 self.log('develwarn', '%s at:\n%s' %
1773 self.log('develwarn', '%s at:\n%s' %
1752 (msg, ''.join(util.getstackframes(stacklevel))))
1774 (msg, ''.join(util.getstackframes(stacklevel))))
1753 else:
1775 else:
1754 curframe = inspect.currentframe()
1776 curframe = inspect.currentframe()
1755 calframe = inspect.getouterframes(curframe, 2)
1777 calframe = inspect.getouterframes(curframe, 2)
1756 fname, lineno, fmsg = calframe[stacklevel][1:4]
1778 fname, lineno, fmsg = calframe[stacklevel][1:4]
1757 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1779 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1758 self.write_err('%s at: %s:%d (%s)\n'
1780 self.write_err('%s at: %s:%d (%s)\n'
1759 % (msg, fname, lineno, fmsg))
1781 % (msg, fname, lineno, fmsg))
1760 self.log('develwarn', '%s at: %s:%d (%s)\n',
1782 self.log('develwarn', '%s at: %s:%d (%s)\n',
1761 msg, fname, lineno, fmsg)
1783 msg, fname, lineno, fmsg)
1762 curframe = calframe = None # avoid cycles
1784 curframe = calframe = None # avoid cycles
1763
1785
1764 def deprecwarn(self, msg, version, stacklevel=2):
1786 def deprecwarn(self, msg, version, stacklevel=2):
1765 """issue a deprecation warning
1787 """issue a deprecation warning
1766
1788
1767 - msg: message explaining what is deprecated and how to upgrade,
1789 - msg: message explaining what is deprecated and how to upgrade,
1768 - version: last version where the API will be supported,
1790 - version: last version where the API will be supported,
1769 """
1791 """
1770 if not (self.configbool('devel', 'all-warnings')
1792 if not (self.configbool('devel', 'all-warnings')
1771 or self.configbool('devel', 'deprec-warn')):
1793 or self.configbool('devel', 'deprec-warn')):
1772 return
1794 return
1773 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1795 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1774 " update your code.)") % version
1796 " update your code.)") % version
1775 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1797 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1776
1798
1777 def exportableenviron(self):
1799 def exportableenviron(self):
1778 """The environment variables that are safe to export, e.g. through
1800 """The environment variables that are safe to export, e.g. through
1779 hgweb.
1801 hgweb.
1780 """
1802 """
1781 return self._exportableenviron
1803 return self._exportableenviron
1782
1804
1783 @contextlib.contextmanager
1805 @contextlib.contextmanager
1784 def configoverride(self, overrides, source=""):
1806 def configoverride(self, overrides, source=""):
1785 """Context manager for temporary config overrides
1807 """Context manager for temporary config overrides
1786 `overrides` must be a dict of the following structure:
1808 `overrides` must be a dict of the following structure:
1787 {(section, name) : value}"""
1809 {(section, name) : value}"""
1788 backups = {}
1810 backups = {}
1789 try:
1811 try:
1790 for (section, name), value in overrides.items():
1812 for (section, name), value in overrides.items():
1791 backups[(section, name)] = self.backupconfig(section, name)
1813 backups[(section, name)] = self.backupconfig(section, name)
1792 self.setconfig(section, name, value, source)
1814 self.setconfig(section, name, value, source)
1793 yield
1815 yield
1794 finally:
1816 finally:
1795 for __, backup in backups.items():
1817 for __, backup in backups.items():
1796 self.restoreconfig(backup)
1818 self.restoreconfig(backup)
1797 # just restoring ui.quiet config to the previous value is not enough
1819 # just restoring ui.quiet config to the previous value is not enough
1798 # as it does not update ui.quiet class member
1820 # as it does not update ui.quiet class member
1799 if ('ui', 'quiet') in overrides:
1821 if ('ui', 'quiet') in overrides:
1800 self.fixconfig(section='ui')
1822 self.fixconfig(section='ui')
1801
1823
1802 class paths(dict):
1824 class paths(dict):
1803 """Represents a collection of paths and their configs.
1825 """Represents a collection of paths and their configs.
1804
1826
1805 Data is initially derived from ui instances and the config files they have
1827 Data is initially derived from ui instances and the config files they have
1806 loaded.
1828 loaded.
1807 """
1829 """
1808 def __init__(self, ui):
1830 def __init__(self, ui):
1809 dict.__init__(self)
1831 dict.__init__(self)
1810
1832
1811 for name, loc in ui.configitems('paths', ignoresub=True):
1833 for name, loc in ui.configitems('paths', ignoresub=True):
1812 # No location is the same as not existing.
1834 # No location is the same as not existing.
1813 if not loc:
1835 if not loc:
1814 continue
1836 continue
1815 loc, sub = ui.configsuboptions('paths', name)
1837 loc, sub = ui.configsuboptions('paths', name)
1816 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1838 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1817
1839
1818 def getpath(self, name, default=None):
1840 def getpath(self, name, default=None):
1819 """Return a ``path`` from a string, falling back to default.
1841 """Return a ``path`` from a string, falling back to default.
1820
1842
1821 ``name`` can be a named path or locations. Locations are filesystem
1843 ``name`` can be a named path or locations. Locations are filesystem
1822 paths or URIs.
1844 paths or URIs.
1823
1845
1824 Returns None if ``name`` is not a registered path, a URI, or a local
1846 Returns None if ``name`` is not a registered path, a URI, or a local
1825 path to a repo.
1847 path to a repo.
1826 """
1848 """
1827 # Only fall back to default if no path was requested.
1849 # Only fall back to default if no path was requested.
1828 if name is None:
1850 if name is None:
1829 if not default:
1851 if not default:
1830 default = ()
1852 default = ()
1831 elif not isinstance(default, (tuple, list)):
1853 elif not isinstance(default, (tuple, list)):
1832 default = (default,)
1854 default = (default,)
1833 for k in default:
1855 for k in default:
1834 try:
1856 try:
1835 return self[k]
1857 return self[k]
1836 except KeyError:
1858 except KeyError:
1837 continue
1859 continue
1838 return None
1860 return None
1839
1861
1840 # Most likely empty string.
1862 # Most likely empty string.
1841 # This may need to raise in the future.
1863 # This may need to raise in the future.
1842 if not name:
1864 if not name:
1843 return None
1865 return None
1844
1866
1845 try:
1867 try:
1846 return self[name]
1868 return self[name]
1847 except KeyError:
1869 except KeyError:
1848 # Try to resolve as a local path or URI.
1870 # Try to resolve as a local path or URI.
1849 try:
1871 try:
1850 # We don't pass sub-options in, so no need to pass ui instance.
1872 # We don't pass sub-options in, so no need to pass ui instance.
1851 return path(None, None, rawloc=name)
1873 return path(None, None, rawloc=name)
1852 except ValueError:
1874 except ValueError:
1853 raise error.RepoError(_('repository %s does not exist') %
1875 raise error.RepoError(_('repository %s does not exist') %
1854 name)
1876 name)
1855
1877
1856 _pathsuboptions = {}
1878 _pathsuboptions = {}
1857
1879
1858 def pathsuboption(option, attr):
1880 def pathsuboption(option, attr):
1859 """Decorator used to declare a path sub-option.
1881 """Decorator used to declare a path sub-option.
1860
1882
1861 Arguments are the sub-option name and the attribute it should set on
1883 Arguments are the sub-option name and the attribute it should set on
1862 ``path`` instances.
1884 ``path`` instances.
1863
1885
1864 The decorated function will receive as arguments a ``ui`` instance,
1886 The decorated function will receive as arguments a ``ui`` instance,
1865 ``path`` instance, and the string value of this option from the config.
1887 ``path`` instance, and the string value of this option from the config.
1866 The function should return the value that will be set on the ``path``
1888 The function should return the value that will be set on the ``path``
1867 instance.
1889 instance.
1868
1890
1869 This decorator can be used to perform additional verification of
1891 This decorator can be used to perform additional verification of
1870 sub-options and to change the type of sub-options.
1892 sub-options and to change the type of sub-options.
1871 """
1893 """
1872 def register(func):
1894 def register(func):
1873 _pathsuboptions[option] = (attr, func)
1895 _pathsuboptions[option] = (attr, func)
1874 return func
1896 return func
1875 return register
1897 return register
1876
1898
1877 @pathsuboption('pushurl', 'pushloc')
1899 @pathsuboption('pushurl', 'pushloc')
1878 def pushurlpathoption(ui, path, value):
1900 def pushurlpathoption(ui, path, value):
1879 u = util.url(value)
1901 u = util.url(value)
1880 # Actually require a URL.
1902 # Actually require a URL.
1881 if not u.scheme:
1903 if not u.scheme:
1882 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1904 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1883 return None
1905 return None
1884
1906
1885 # Don't support the #foo syntax in the push URL to declare branch to
1907 # Don't support the #foo syntax in the push URL to declare branch to
1886 # push.
1908 # push.
1887 if u.fragment:
1909 if u.fragment:
1888 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1910 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1889 'ignoring)\n') % path.name)
1911 'ignoring)\n') % path.name)
1890 u.fragment = None
1912 u.fragment = None
1891
1913
1892 return bytes(u)
1914 return bytes(u)
1893
1915
1894 @pathsuboption('pushrev', 'pushrev')
1916 @pathsuboption('pushrev', 'pushrev')
1895 def pushrevpathoption(ui, path, value):
1917 def pushrevpathoption(ui, path, value):
1896 return value
1918 return value
1897
1919
1898 class path(object):
1920 class path(object):
1899 """Represents an individual path and its configuration."""
1921 """Represents an individual path and its configuration."""
1900
1922
1901 def __init__(self, ui, name, rawloc=None, suboptions=None):
1923 def __init__(self, ui, name, rawloc=None, suboptions=None):
1902 """Construct a path from its config options.
1924 """Construct a path from its config options.
1903
1925
1904 ``ui`` is the ``ui`` instance the path is coming from.
1926 ``ui`` is the ``ui`` instance the path is coming from.
1905 ``name`` is the symbolic name of the path.
1927 ``name`` is the symbolic name of the path.
1906 ``rawloc`` is the raw location, as defined in the config.
1928 ``rawloc`` is the raw location, as defined in the config.
1907 ``pushloc`` is the raw locations pushes should be made to.
1929 ``pushloc`` is the raw locations pushes should be made to.
1908
1930
1909 If ``name`` is not defined, we require that the location be a) a local
1931 If ``name`` is not defined, we require that the location be a) a local
1910 filesystem path with a .hg directory or b) a URL. If not,
1932 filesystem path with a .hg directory or b) a URL. If not,
1911 ``ValueError`` is raised.
1933 ``ValueError`` is raised.
1912 """
1934 """
1913 if not rawloc:
1935 if not rawloc:
1914 raise ValueError('rawloc must be defined')
1936 raise ValueError('rawloc must be defined')
1915
1937
1916 # Locations may define branches via syntax <base>#<branch>.
1938 # Locations may define branches via syntax <base>#<branch>.
1917 u = util.url(rawloc)
1939 u = util.url(rawloc)
1918 branch = None
1940 branch = None
1919 if u.fragment:
1941 if u.fragment:
1920 branch = u.fragment
1942 branch = u.fragment
1921 u.fragment = None
1943 u.fragment = None
1922
1944
1923 self.url = u
1945 self.url = u
1924 self.branch = branch
1946 self.branch = branch
1925
1947
1926 self.name = name
1948 self.name = name
1927 self.rawloc = rawloc
1949 self.rawloc = rawloc
1928 self.loc = '%s' % u
1950 self.loc = '%s' % u
1929
1951
1930 # When given a raw location but not a symbolic name, validate the
1952 # When given a raw location but not a symbolic name, validate the
1931 # location is valid.
1953 # location is valid.
1932 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1954 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1933 raise ValueError('location is not a URL or path to a local '
1955 raise ValueError('location is not a URL or path to a local '
1934 'repo: %s' % rawloc)
1956 'repo: %s' % rawloc)
1935
1957
1936 suboptions = suboptions or {}
1958 suboptions = suboptions or {}
1937
1959
1938 # Now process the sub-options. If a sub-option is registered, its
1960 # Now process the sub-options. If a sub-option is registered, its
1939 # attribute will always be present. The value will be None if there
1961 # attribute will always be present. The value will be None if there
1940 # was no valid sub-option.
1962 # was no valid sub-option.
1941 for suboption, (attr, func) in _pathsuboptions.iteritems():
1963 for suboption, (attr, func) in _pathsuboptions.iteritems():
1942 if suboption not in suboptions:
1964 if suboption not in suboptions:
1943 setattr(self, attr, None)
1965 setattr(self, attr, None)
1944 continue
1966 continue
1945
1967
1946 value = func(ui, self, suboptions[suboption])
1968 value = func(ui, self, suboptions[suboption])
1947 setattr(self, attr, value)
1969 setattr(self, attr, value)
1948
1970
1949 def _isvalidlocalpath(self, path):
1971 def _isvalidlocalpath(self, path):
1950 """Returns True if the given path is a potentially valid repository.
1972 """Returns True if the given path is a potentially valid repository.
1951 This is its own function so that extensions can change the definition of
1973 This is its own function so that extensions can change the definition of
1952 'valid' in this case (like when pulling from a git repo into a hg
1974 'valid' in this case (like when pulling from a git repo into a hg
1953 one)."""
1975 one)."""
1954 return os.path.isdir(os.path.join(path, '.hg'))
1976 return os.path.isdir(os.path.join(path, '.hg'))
1955
1977
1956 @property
1978 @property
1957 def suboptions(self):
1979 def suboptions(self):
1958 """Return sub-options and their values for this path.
1980 """Return sub-options and their values for this path.
1959
1981
1960 This is intended to be used for presentation purposes.
1982 This is intended to be used for presentation purposes.
1961 """
1983 """
1962 d = {}
1984 d = {}
1963 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1985 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1964 value = getattr(self, attr)
1986 value = getattr(self, attr)
1965 if value is not None:
1987 if value is not None:
1966 d[subopt] = value
1988 d[subopt] = value
1967 return d
1989 return d
1968
1990
1969 # we instantiate one globally shared progress bar to avoid
1991 # we instantiate one globally shared progress bar to avoid
1970 # competing progress bars when multiple UI objects get created
1992 # competing progress bars when multiple UI objects get created
1971 _progresssingleton = None
1993 _progresssingleton = None
1972
1994
1973 def getprogbar(ui):
1995 def getprogbar(ui):
1974 global _progresssingleton
1996 global _progresssingleton
1975 if _progresssingleton is None:
1997 if _progresssingleton is None:
1976 # passing 'ui' object to the singleton is fishy,
1998 # passing 'ui' object to the singleton is fishy,
1977 # this is how the extension used to work but feel free to rework it.
1999 # this is how the extension used to work but feel free to rework it.
1978 _progresssingleton = progress.progbar(ui)
2000 _progresssingleton = progress.progbar(ui)
1979 return _progresssingleton
2001 return _progresssingleton
1980
2002
1981 def haveprogbar():
2003 def haveprogbar():
1982 return _progresssingleton is not None
2004 return _progresssingleton is not None
1983
2005
1984 def _selectmsgdests(ui):
2006 def _selectmsgdests(ui):
1985 name = ui.config(b'ui', b'message-output')
2007 name = ui.config(b'ui', b'message-output')
1986 if name == b'channel':
2008 if name == b'channel':
1987 if ui.fmsg:
2009 if ui.fmsg:
1988 return ui.fmsg, ui.fmsg
2010 return ui.fmsg, ui.fmsg
1989 else:
2011 else:
1990 # fall back to ferr if channel isn't ready so that status/error
2012 # fall back to ferr if channel isn't ready so that status/error
1991 # messages can be printed
2013 # messages can be printed
1992 return ui.ferr, ui.ferr
2014 return ui.ferr, ui.ferr
1993 if name == b'stdio':
2015 if name == b'stdio':
1994 return ui.fout, ui.ferr
2016 return ui.fout, ui.ferr
1995 if name == b'stderr':
2017 if name == b'stderr':
1996 return ui.ferr, ui.ferr
2018 return ui.ferr, ui.ferr
1997 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2019 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
1998
2020
1999 def _writemsgwith(write, dest, *args, **opts):
2021 def _writemsgwith(write, dest, *args, **opts):
2000 """Write ui message with the given ui._write*() function
2022 """Write ui message with the given ui._write*() function
2001
2023
2002 The specified message type is translated to 'ui.<type>' label if the dest
2024 The specified message type is translated to 'ui.<type>' label if the dest
2003 isn't a structured channel, so that the message will be colorized.
2025 isn't a structured channel, so that the message will be colorized.
2004 """
2026 """
2005 # TODO: maybe change 'type' to a mandatory option
2027 # TODO: maybe change 'type' to a mandatory option
2006 if r'type' in opts and not getattr(dest, 'structured', False):
2028 if r'type' in opts and not getattr(dest, 'structured', False):
2007 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2029 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2008 write(dest, *args, **opts)
2030 write(dest, *args, **opts)
General Comments 0
You need to be logged in to leave comments. Login now