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