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