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