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