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