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