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