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