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