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