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