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