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