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