##// END OF EJS Templates
configitems: add alias support in config...
David Demelier -
r33329:e7141598 default
parent child Browse files
Show More
@@ -1,210 +1,215
1 # configitems.py - centralized declaration of configuration option
1 # configitems.py - centralized declaration of configuration option
2 #
2 #
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
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 functools
10 import functools
11
11
12 from . import (
12 from . import (
13 error,
13 error,
14 )
14 )
15
15
16 def loadconfigtable(ui, extname, configtable):
16 def loadconfigtable(ui, extname, configtable):
17 """update config item known to the ui with the extension ones"""
17 """update config item known to the ui with the extension ones"""
18 for section, items in configtable.items():
18 for section, items in configtable.items():
19 knownitems = ui._knownconfig.setdefault(section, {})
19 knownitems = ui._knownconfig.setdefault(section, {})
20 knownkeys = set(knownitems)
20 knownkeys = set(knownitems)
21 newkeys = set(items)
21 newkeys = set(items)
22 for key in sorted(knownkeys & newkeys):
22 for key in sorted(knownkeys & newkeys):
23 msg = "extension '%s' overwrite config item '%s.%s'"
23 msg = "extension '%s' overwrite config item '%s.%s'"
24 msg %= (extname, section, key)
24 msg %= (extname, section, key)
25 ui.develwarn(msg, config='warn-config')
25 ui.develwarn(msg, config='warn-config')
26
26
27 knownitems.update(items)
27 knownitems.update(items)
28
28
29 class configitem(object):
29 class configitem(object):
30 """represent a known config item
30 """represent a known config item
31
31
32 :section: the official config section where to find this item,
32 :section: the official config section where to find this item,
33 :name: the official name within the section,
33 :name: the official name within the section,
34 :default: default value for this item,
34 :default: default value for this item,
35 :alias: optional list of tuples as alternatives.
35 """
36 """
36
37
37 def __init__(self, section, name, default=None):
38 def __init__(self, section, name, default=None, alias=()):
38 self.section = section
39 self.section = section
39 self.name = name
40 self.name = name
40 self.default = default
41 self.default = default
42 self.alias = list(alias)
41
43
42 coreitems = {}
44 coreitems = {}
43
45
44 def _register(configtable, *args, **kwargs):
46 def _register(configtable, *args, **kwargs):
45 item = configitem(*args, **kwargs)
47 item = configitem(*args, **kwargs)
46 section = configtable.setdefault(item.section, {})
48 section = configtable.setdefault(item.section, {})
47 if item.name in section:
49 if item.name in section:
48 msg = "duplicated config item registration for '%s.%s'"
50 msg = "duplicated config item registration for '%s.%s'"
49 raise error.ProgrammingError(msg % (item.section, item.name))
51 raise error.ProgrammingError(msg % (item.section, item.name))
50 section[item.name] = item
52 section[item.name] = item
51
53
52 # Registering actual config items
54 # Registering actual config items
53
55
54 def getitemregister(configtable):
56 def getitemregister(configtable):
55 return functools.partial(_register, configtable)
57 return functools.partial(_register, configtable)
56
58
57 coreconfigitem = getitemregister(coreitems)
59 coreconfigitem = getitemregister(coreitems)
58
60
59 coreconfigitem('auth', 'cookiefile',
61 coreconfigitem('auth', 'cookiefile',
60 default=None,
62 default=None,
61 )
63 )
62 # bookmarks.pushing: internal hack for discovery
64 # bookmarks.pushing: internal hack for discovery
63 coreconfigitem('bookmarks', 'pushing',
65 coreconfigitem('bookmarks', 'pushing',
64 default=list,
66 default=list,
65 )
67 )
66 # bundle.mainreporoot: internal hack for bundlerepo
68 # bundle.mainreporoot: internal hack for bundlerepo
67 coreconfigitem('bundle', 'mainreporoot',
69 coreconfigitem('bundle', 'mainreporoot',
68 default='',
70 default='',
69 )
71 )
70 # bundle.reorder: experimental config
72 # bundle.reorder: experimental config
71 coreconfigitem('bundle', 'reorder',
73 coreconfigitem('bundle', 'reorder',
72 default='auto',
74 default='auto',
73 )
75 )
74 coreconfigitem('color', 'mode',
76 coreconfigitem('color', 'mode',
75 default='auto',
77 default='auto',
76 )
78 )
77 coreconfigitem('devel', 'all-warnings',
79 coreconfigitem('devel', 'all-warnings',
78 default=False,
80 default=False,
79 )
81 )
80 coreconfigitem('devel', 'bundle2.debug',
82 coreconfigitem('devel', 'bundle2.debug',
81 default=False,
83 default=False,
82 )
84 )
83 coreconfigitem('devel', 'check-locks',
85 coreconfigitem('devel', 'check-locks',
84 default=False,
86 default=False,
85 )
87 )
86 coreconfigitem('devel', 'check-relroot',
88 coreconfigitem('devel', 'check-relroot',
87 default=False,
89 default=False,
88 )
90 )
89 coreconfigitem('devel', 'disableloaddefaultcerts',
91 coreconfigitem('devel', 'disableloaddefaultcerts',
90 default=False,
92 default=False,
91 )
93 )
92 coreconfigitem('devel', 'legacy.exchange',
94 coreconfigitem('devel', 'legacy.exchange',
93 default=list,
95 default=list,
94 )
96 )
95 coreconfigitem('devel', 'servercafile',
97 coreconfigitem('devel', 'servercafile',
96 default='',
98 default='',
97 )
99 )
98 coreconfigitem('devel', 'serverexactprotocol',
100 coreconfigitem('devel', 'serverexactprotocol',
99 default='',
101 default='',
100 )
102 )
101 coreconfigitem('devel', 'serverrequirecert',
103 coreconfigitem('devel', 'serverrequirecert',
102 default=False,
104 default=False,
103 )
105 )
104 coreconfigitem('devel', 'strip-obsmarkers',
106 coreconfigitem('devel', 'strip-obsmarkers',
105 default=True,
107 default=True,
106 )
108 )
107 coreconfigitem('format', 'aggressivemergedeltas',
109 coreconfigitem('format', 'aggressivemergedeltas',
108 default=False,
110 default=False,
109 )
111 )
110 coreconfigitem('format', 'chunkcachesize',
112 coreconfigitem('format', 'chunkcachesize',
111 default=None,
113 default=None,
112 )
114 )
113 coreconfigitem('format', 'dotencode',
115 coreconfigitem('format', 'dotencode',
114 default=True,
116 default=True,
115 )
117 )
116 coreconfigitem('format', 'generaldelta',
118 coreconfigitem('format', 'generaldelta',
117 default=False,
119 default=False,
118 )
120 )
119 coreconfigitem('format', 'manifestcachesize',
121 coreconfigitem('format', 'manifestcachesize',
120 default=None,
122 default=None,
121 )
123 )
122 coreconfigitem('format', 'maxchainlen',
124 coreconfigitem('format', 'maxchainlen',
123 default=None,
125 default=None,
124 )
126 )
125 coreconfigitem('format', 'obsstore-version',
127 coreconfigitem('format', 'obsstore-version',
126 default=None,
128 default=None,
127 )
129 )
128 coreconfigitem('format', 'usefncache',
130 coreconfigitem('format', 'usefncache',
129 default=True,
131 default=True,
130 )
132 )
131 coreconfigitem('format', 'usegeneraldelta',
133 coreconfigitem('format', 'usegeneraldelta',
132 default=True,
134 default=True,
133 )
135 )
134 coreconfigitem('format', 'usestore',
136 coreconfigitem('format', 'usestore',
135 default=True,
137 default=True,
136 )
138 )
137 coreconfigitem('hostsecurity', 'ciphers',
139 coreconfigitem('hostsecurity', 'ciphers',
138 default=None,
140 default=None,
139 )
141 )
140 coreconfigitem('hostsecurity', 'disabletls10warning',
142 coreconfigitem('hostsecurity', 'disabletls10warning',
141 default=False,
143 default=False,
142 )
144 )
143 coreconfigitem('patch', 'eol',
145 coreconfigitem('patch', 'eol',
144 default='strict',
146 default='strict',
145 )
147 )
146 coreconfigitem('patch', 'fuzz',
148 coreconfigitem('patch', 'fuzz',
147 default=2,
149 default=2,
148 )
150 )
149 coreconfigitem('progress', 'assume-tty',
151 coreconfigitem('progress', 'assume-tty',
150 default=False,
152 default=False,
151 )
153 )
152 coreconfigitem('progress', 'clear-complete',
154 coreconfigitem('progress', 'clear-complete',
153 default=True,
155 default=True,
154 )
156 )
155 coreconfigitem('progress', 'estimate',
157 coreconfigitem('progress', 'estimate',
156 default=2,
158 default=2,
157 )
159 )
158 coreconfigitem('server', 'bundle1',
160 coreconfigitem('server', 'bundle1',
159 default=True,
161 default=True,
160 )
162 )
161 coreconfigitem('server', 'bundle1gd',
163 coreconfigitem('server', 'bundle1gd',
162 default=None,
164 default=None,
163 )
165 )
164 coreconfigitem('server', 'compressionengines',
166 coreconfigitem('server', 'compressionengines',
165 default=list,
167 default=list,
166 )
168 )
167 coreconfigitem('server', 'concurrent-push-mode',
169 coreconfigitem('server', 'concurrent-push-mode',
168 default='strict',
170 default='strict',
169 )
171 )
170 coreconfigitem('server', 'disablefullbundle',
172 coreconfigitem('server', 'disablefullbundle',
171 default=False,
173 default=False,
172 )
174 )
173 coreconfigitem('server', 'maxhttpheaderlen',
175 coreconfigitem('server', 'maxhttpheaderlen',
174 default=1024,
176 default=1024,
175 )
177 )
176 coreconfigitem('server', 'preferuncompressed',
178 coreconfigitem('server', 'preferuncompressed',
177 default=False,
179 default=False,
178 )
180 )
179 coreconfigitem('server', 'uncompressedallowsecret',
181 coreconfigitem('server', 'uncompressedallowsecret',
180 default=False,
182 default=False,
181 )
183 )
182 coreconfigitem('server', 'validate',
184 coreconfigitem('server', 'validate',
183 default=False,
185 default=False,
184 )
186 )
185 coreconfigitem('server', 'zliblevel',
187 coreconfigitem('server', 'zliblevel',
186 default=-1,
188 default=-1,
187 )
189 )
188 coreconfigitem('ui', 'clonebundleprefers',
190 coreconfigitem('ui', 'clonebundleprefers',
189 default=list,
191 default=list,
190 )
192 )
191 coreconfigitem('ui', 'interactive',
193 coreconfigitem('ui', 'interactive',
192 default=None,
194 default=None,
193 )
195 )
194 coreconfigitem('ui', 'quiet',
196 coreconfigitem('ui', 'quiet',
195 default=False,
197 default=False,
196 )
198 )
199 coreconfigitem('ui', 'username',
200 alias=[('ui', 'user')]
201 )
197 # Windows defaults to a limit of 512 open files. A buffer of 128
202 # Windows defaults to a limit of 512 open files. A buffer of 128
198 # should give us enough headway.
203 # should give us enough headway.
199 coreconfigitem('worker', 'backgroundclosemaxqueue',
204 coreconfigitem('worker', 'backgroundclosemaxqueue',
200 default=384,
205 default=384,
201 )
206 )
202 coreconfigitem('worker', 'backgroundcloseminfilecount',
207 coreconfigitem('worker', 'backgroundcloseminfilecount',
203 default=2048,
208 default=2048,
204 )
209 )
205 coreconfigitem('worker', 'backgroundclosethreadcount',
210 coreconfigitem('worker', 'backgroundclosethreadcount',
206 default=4,
211 default=4,
207 )
212 )
208 coreconfigitem('worker', 'numcpus',
213 coreconfigitem('worker', 'numcpus',
209 default=None,
214 default=None,
210 )
215 )
@@ -1,1770 +1,1771
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 errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
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
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40
40
41 urlreq = util.urlreq
41 urlreq = util.urlreq
42
42
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 if not c.isalnum())
45 if not c.isalnum())
46
46
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 tweakrc = """
48 tweakrc = """
49 [ui]
49 [ui]
50 # The rollback command is dangerous. As a rule, don't use it.
50 # The rollback command is dangerous. As a rule, don't use it.
51 rollback = False
51 rollback = False
52
52
53 [commands]
53 [commands]
54 # Make `hg status` emit cwd-relative paths by default.
54 # Make `hg status` emit cwd-relative paths by default.
55 status.relative = yes
55 status.relative = yes
56
56
57 [diff]
57 [diff]
58 git = 1
58 git = 1
59 """
59 """
60
60
61 samplehgrcs = {
61 samplehgrcs = {
62 'user':
62 'user':
63 """# example user config (see 'hg help config' for more info)
63 """# example user config (see 'hg help config' for more info)
64 [ui]
64 [ui]
65 # name and email, e.g.
65 # name and email, e.g.
66 # username = Jane Doe <jdoe@example.com>
66 # username = Jane Doe <jdoe@example.com>
67 username =
67 username =
68
68
69 # uncomment to disable color in command output
69 # uncomment to disable color in command output
70 # (see 'hg help color' for details)
70 # (see 'hg help color' for details)
71 # color = never
71 # color = never
72
72
73 # uncomment to disable command output pagination
73 # uncomment to disable command output pagination
74 # (see 'hg help pager' for details)
74 # (see 'hg help pager' for details)
75 # paginate = never
75 # paginate = never
76
76
77 [extensions]
77 [extensions]
78 # uncomment these lines to enable some popular extensions
78 # uncomment these lines to enable some popular extensions
79 # (see 'hg help extensions' for more info)
79 # (see 'hg help extensions' for more info)
80 #
80 #
81 # churn =
81 # churn =
82 """,
82 """,
83
83
84 'cloned':
84 'cloned':
85 """# example repository config (see 'hg help config' for more info)
85 """# example repository config (see 'hg help config' for more info)
86 [paths]
86 [paths]
87 default = %s
87 default = %s
88
88
89 # path aliases to other clones of this repo in URLs or filesystem paths
89 # path aliases to other clones of this repo in URLs or filesystem paths
90 # (see 'hg help config.paths' for more info)
90 # (see 'hg help config.paths' for more info)
91 #
91 #
92 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
92 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
93 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
93 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
94 # my-clone = /home/jdoe/jdoes-clone
94 # my-clone = /home/jdoe/jdoes-clone
95
95
96 [ui]
96 [ui]
97 # name and email (local to this repository, optional), e.g.
97 # name and email (local to this repository, optional), e.g.
98 # username = Jane Doe <jdoe@example.com>
98 # username = Jane Doe <jdoe@example.com>
99 """,
99 """,
100
100
101 'local':
101 'local':
102 """# example repository config (see 'hg help config' for more info)
102 """# example repository config (see 'hg help config' for more info)
103 [paths]
103 [paths]
104 # path aliases to other clones of this repo in URLs or filesystem paths
104 # path aliases to other clones of this repo in URLs or filesystem paths
105 # (see 'hg help config.paths' for more info)
105 # (see 'hg help config.paths' for more info)
106 #
106 #
107 # default = http://example.com/hg/example-repo
107 # default = http://example.com/hg/example-repo
108 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
108 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
109 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
109 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
110 # my-clone = /home/jdoe/jdoes-clone
110 # my-clone = /home/jdoe/jdoes-clone
111
111
112 [ui]
112 [ui]
113 # name and email (local to this repository, optional), e.g.
113 # name and email (local to this repository, optional), e.g.
114 # username = Jane Doe <jdoe@example.com>
114 # username = Jane Doe <jdoe@example.com>
115 """,
115 """,
116
116
117 'global':
117 'global':
118 """# example system-wide hg config (see 'hg help config' for more info)
118 """# example system-wide hg config (see 'hg help config' for more info)
119
119
120 [ui]
120 [ui]
121 # uncomment to disable color in command output
121 # uncomment to disable color in command output
122 # (see 'hg help color' for details)
122 # (see 'hg help color' for details)
123 # color = never
123 # color = never
124
124
125 # uncomment to disable command output pagination
125 # uncomment to disable command output pagination
126 # (see 'hg help pager' for details)
126 # (see 'hg help pager' for details)
127 # paginate = never
127 # paginate = never
128
128
129 [extensions]
129 [extensions]
130 # uncomment these lines to enable some popular extensions
130 # uncomment these lines to enable some popular extensions
131 # (see 'hg help extensions' for more info)
131 # (see 'hg help extensions' for more info)
132 #
132 #
133 # blackbox =
133 # blackbox =
134 # churn =
134 # churn =
135 """,
135 """,
136 }
136 }
137
137
138
138
139 class httppasswordmgrdbproxy(object):
139 class httppasswordmgrdbproxy(object):
140 """Delays loading urllib2 until it's needed."""
140 """Delays loading urllib2 until it's needed."""
141 def __init__(self):
141 def __init__(self):
142 self._mgr = None
142 self._mgr = None
143
143
144 def _get_mgr(self):
144 def _get_mgr(self):
145 if self._mgr is None:
145 if self._mgr is None:
146 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
146 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
147 return self._mgr
147 return self._mgr
148
148
149 def add_password(self, *args, **kwargs):
149 def add_password(self, *args, **kwargs):
150 return self._get_mgr().add_password(*args, **kwargs)
150 return self._get_mgr().add_password(*args, **kwargs)
151
151
152 def find_user_password(self, *args, **kwargs):
152 def find_user_password(self, *args, **kwargs):
153 return self._get_mgr().find_user_password(*args, **kwargs)
153 return self._get_mgr().find_user_password(*args, **kwargs)
154
154
155 def _catchterm(*args):
155 def _catchterm(*args):
156 raise error.SignalInterrupt
156 raise error.SignalInterrupt
157
157
158 # unique object used to detect no default value has been provided when
158 # unique object used to detect no default value has been provided when
159 # retrieving configuration value.
159 # retrieving configuration value.
160 _unset = object()
160 _unset = object()
161
161
162 class ui(object):
162 class ui(object):
163 def __init__(self, src=None):
163 def __init__(self, src=None):
164 """Create a fresh new ui object if no src given
164 """Create a fresh new ui object if no src given
165
165
166 Use uimod.ui.load() to create a ui which knows global and user configs.
166 Use uimod.ui.load() to create a ui which knows global and user configs.
167 In most cases, you should use ui.copy() to create a copy of an existing
167 In most cases, you should use ui.copy() to create a copy of an existing
168 ui object.
168 ui object.
169 """
169 """
170 # _buffers: used for temporary capture of output
170 # _buffers: used for temporary capture of output
171 self._buffers = []
171 self._buffers = []
172 # _exithandlers: callbacks run at the end of a request
172 # _exithandlers: callbacks run at the end of a request
173 self._exithandlers = []
173 self._exithandlers = []
174 # 3-tuple describing how each buffer in the stack behaves.
174 # 3-tuple describing how each buffer in the stack behaves.
175 # Values are (capture stderr, capture subprocesses, apply labels).
175 # Values are (capture stderr, capture subprocesses, apply labels).
176 self._bufferstates = []
176 self._bufferstates = []
177 # When a buffer is active, defines whether we are expanding labels.
177 # When a buffer is active, defines whether we are expanding labels.
178 # This exists to prevent an extra list lookup.
178 # This exists to prevent an extra list lookup.
179 self._bufferapplylabels = None
179 self._bufferapplylabels = None
180 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
180 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
181 self._reportuntrusted = True
181 self._reportuntrusted = True
182 self._knownconfig = configitems.coreitems
182 self._knownconfig = configitems.coreitems
183 self._ocfg = config.config() # overlay
183 self._ocfg = config.config() # overlay
184 self._tcfg = config.config() # trusted
184 self._tcfg = config.config() # trusted
185 self._ucfg = config.config() # untrusted
185 self._ucfg = config.config() # untrusted
186 self._trustusers = set()
186 self._trustusers = set()
187 self._trustgroups = set()
187 self._trustgroups = set()
188 self.callhooks = True
188 self.callhooks = True
189 # Insecure server connections requested.
189 # Insecure server connections requested.
190 self.insecureconnections = False
190 self.insecureconnections = False
191 # Blocked time
191 # Blocked time
192 self.logblockedtimes = False
192 self.logblockedtimes = False
193 # color mode: see mercurial/color.py for possible value
193 # color mode: see mercurial/color.py for possible value
194 self._colormode = None
194 self._colormode = None
195 self._terminfoparams = {}
195 self._terminfoparams = {}
196 self._styles = {}
196 self._styles = {}
197
197
198 if src:
198 if src:
199 self._exithandlers = src._exithandlers
199 self._exithandlers = src._exithandlers
200 self.fout = src.fout
200 self.fout = src.fout
201 self.ferr = src.ferr
201 self.ferr = src.ferr
202 self.fin = src.fin
202 self.fin = src.fin
203 self.pageractive = src.pageractive
203 self.pageractive = src.pageractive
204 self._disablepager = src._disablepager
204 self._disablepager = src._disablepager
205 self._tweaked = src._tweaked
205 self._tweaked = src._tweaked
206
206
207 self._tcfg = src._tcfg.copy()
207 self._tcfg = src._tcfg.copy()
208 self._ucfg = src._ucfg.copy()
208 self._ucfg = src._ucfg.copy()
209 self._ocfg = src._ocfg.copy()
209 self._ocfg = src._ocfg.copy()
210 self._trustusers = src._trustusers.copy()
210 self._trustusers = src._trustusers.copy()
211 self._trustgroups = src._trustgroups.copy()
211 self._trustgroups = src._trustgroups.copy()
212 self.environ = src.environ
212 self.environ = src.environ
213 self.callhooks = src.callhooks
213 self.callhooks = src.callhooks
214 self.insecureconnections = src.insecureconnections
214 self.insecureconnections = src.insecureconnections
215 self._colormode = src._colormode
215 self._colormode = src._colormode
216 self._terminfoparams = src._terminfoparams.copy()
216 self._terminfoparams = src._terminfoparams.copy()
217 self._styles = src._styles.copy()
217 self._styles = src._styles.copy()
218
218
219 self.fixconfig()
219 self.fixconfig()
220
220
221 self.httppasswordmgrdb = src.httppasswordmgrdb
221 self.httppasswordmgrdb = src.httppasswordmgrdb
222 self._blockedtimes = src._blockedtimes
222 self._blockedtimes = src._blockedtimes
223 else:
223 else:
224 self.fout = util.stdout
224 self.fout = util.stdout
225 self.ferr = util.stderr
225 self.ferr = util.stderr
226 self.fin = util.stdin
226 self.fin = util.stdin
227 self.pageractive = False
227 self.pageractive = False
228 self._disablepager = False
228 self._disablepager = False
229 self._tweaked = False
229 self._tweaked = False
230
230
231 # shared read-only environment
231 # shared read-only environment
232 self.environ = encoding.environ
232 self.environ = encoding.environ
233
233
234 self.httppasswordmgrdb = httppasswordmgrdbproxy()
234 self.httppasswordmgrdb = httppasswordmgrdbproxy()
235 self._blockedtimes = collections.defaultdict(int)
235 self._blockedtimes = collections.defaultdict(int)
236
236
237 allowed = self.configlist('experimental', 'exportableenviron')
237 allowed = self.configlist('experimental', 'exportableenviron')
238 if '*' in allowed:
238 if '*' in allowed:
239 self._exportableenviron = self.environ
239 self._exportableenviron = self.environ
240 else:
240 else:
241 self._exportableenviron = {}
241 self._exportableenviron = {}
242 for k in allowed:
242 for k in allowed:
243 if k in self.environ:
243 if k in self.environ:
244 self._exportableenviron[k] = self.environ[k]
244 self._exportableenviron[k] = self.environ[k]
245
245
246 @classmethod
246 @classmethod
247 def load(cls):
247 def load(cls):
248 """Create a ui and load global and user configs"""
248 """Create a ui and load global and user configs"""
249 u = cls()
249 u = cls()
250 # we always trust global config files and environment variables
250 # we always trust global config files and environment variables
251 for t, f in rcutil.rccomponents():
251 for t, f in rcutil.rccomponents():
252 if t == 'path':
252 if t == 'path':
253 u.readconfig(f, trust=True)
253 u.readconfig(f, trust=True)
254 elif t == 'items':
254 elif t == 'items':
255 sections = set()
255 sections = set()
256 for section, name, value, source in f:
256 for section, name, value, source in f:
257 # do not set u._ocfg
257 # do not set u._ocfg
258 # XXX clean this up once immutable config object is a thing
258 # XXX clean this up once immutable config object is a thing
259 u._tcfg.set(section, name, value, source)
259 u._tcfg.set(section, name, value, source)
260 u._ucfg.set(section, name, value, source)
260 u._ucfg.set(section, name, value, source)
261 sections.add(section)
261 sections.add(section)
262 for section in sections:
262 for section in sections:
263 u.fixconfig(section=section)
263 u.fixconfig(section=section)
264 else:
264 else:
265 raise error.ProgrammingError('unknown rctype: %s' % t)
265 raise error.ProgrammingError('unknown rctype: %s' % t)
266 u._maybetweakdefaults()
266 u._maybetweakdefaults()
267 return u
267 return u
268
268
269 def _maybetweakdefaults(self):
269 def _maybetweakdefaults(self):
270 if not self.configbool('ui', 'tweakdefaults'):
270 if not self.configbool('ui', 'tweakdefaults'):
271 return
271 return
272 if self._tweaked or self.plain('tweakdefaults'):
272 if self._tweaked or self.plain('tweakdefaults'):
273 return
273 return
274
274
275 # Note: it is SUPER IMPORTANT that you set self._tweaked to
275 # Note: it is SUPER IMPORTANT that you set self._tweaked to
276 # True *before* any calls to setconfig(), otherwise you'll get
276 # True *before* any calls to setconfig(), otherwise you'll get
277 # infinite recursion between setconfig and this method.
277 # infinite recursion between setconfig and this method.
278 #
278 #
279 # TODO: We should extract an inner method in setconfig() to
279 # TODO: We should extract an inner method in setconfig() to
280 # avoid this weirdness.
280 # avoid this weirdness.
281 self._tweaked = True
281 self._tweaked = True
282 tmpcfg = config.config()
282 tmpcfg = config.config()
283 tmpcfg.parse('<tweakdefaults>', tweakrc)
283 tmpcfg.parse('<tweakdefaults>', tweakrc)
284 for section in tmpcfg:
284 for section in tmpcfg:
285 for name, value in tmpcfg.items(section):
285 for name, value in tmpcfg.items(section):
286 if not self.hasconfig(section, name):
286 if not self.hasconfig(section, name):
287 self.setconfig(section, name, value, "<tweakdefaults>")
287 self.setconfig(section, name, value, "<tweakdefaults>")
288
288
289 def copy(self):
289 def copy(self):
290 return self.__class__(self)
290 return self.__class__(self)
291
291
292 def resetstate(self):
292 def resetstate(self):
293 """Clear internal state that shouldn't persist across commands"""
293 """Clear internal state that shouldn't persist across commands"""
294 if self._progbar:
294 if self._progbar:
295 self._progbar.resetstate() # reset last-print time of progress bar
295 self._progbar.resetstate() # reset last-print time of progress bar
296 self.httppasswordmgrdb = httppasswordmgrdbproxy()
296 self.httppasswordmgrdb = httppasswordmgrdbproxy()
297
297
298 @contextlib.contextmanager
298 @contextlib.contextmanager
299 def timeblockedsection(self, key):
299 def timeblockedsection(self, key):
300 # this is open-coded below - search for timeblockedsection to find them
300 # this is open-coded below - search for timeblockedsection to find them
301 starttime = util.timer()
301 starttime = util.timer()
302 try:
302 try:
303 yield
303 yield
304 finally:
304 finally:
305 self._blockedtimes[key + '_blocked'] += \
305 self._blockedtimes[key + '_blocked'] += \
306 (util.timer() - starttime) * 1000
306 (util.timer() - starttime) * 1000
307
307
308 def formatter(self, topic, opts):
308 def formatter(self, topic, opts):
309 return formatter.formatter(self, self, topic, opts)
309 return formatter.formatter(self, self, topic, opts)
310
310
311 def _trusted(self, fp, f):
311 def _trusted(self, fp, f):
312 st = util.fstat(fp)
312 st = util.fstat(fp)
313 if util.isowner(st):
313 if util.isowner(st):
314 return True
314 return True
315
315
316 tusers, tgroups = self._trustusers, self._trustgroups
316 tusers, tgroups = self._trustusers, self._trustgroups
317 if '*' in tusers or '*' in tgroups:
317 if '*' in tusers or '*' in tgroups:
318 return True
318 return True
319
319
320 user = util.username(st.st_uid)
320 user = util.username(st.st_uid)
321 group = util.groupname(st.st_gid)
321 group = util.groupname(st.st_gid)
322 if user in tusers or group in tgroups or user == util.username():
322 if user in tusers or group in tgroups or user == util.username():
323 return True
323 return True
324
324
325 if self._reportuntrusted:
325 if self._reportuntrusted:
326 self.warn(_('not trusting file %s from untrusted '
326 self.warn(_('not trusting file %s from untrusted '
327 'user %s, group %s\n') % (f, user, group))
327 'user %s, group %s\n') % (f, user, group))
328 return False
328 return False
329
329
330 def readconfig(self, filename, root=None, trust=False,
330 def readconfig(self, filename, root=None, trust=False,
331 sections=None, remap=None):
331 sections=None, remap=None):
332 try:
332 try:
333 fp = open(filename, u'rb')
333 fp = open(filename, u'rb')
334 except IOError:
334 except IOError:
335 if not sections: # ignore unless we were looking for something
335 if not sections: # ignore unless we were looking for something
336 return
336 return
337 raise
337 raise
338
338
339 cfg = config.config()
339 cfg = config.config()
340 trusted = sections or trust or self._trusted(fp, filename)
340 trusted = sections or trust or self._trusted(fp, filename)
341
341
342 try:
342 try:
343 cfg.read(filename, fp, sections=sections, remap=remap)
343 cfg.read(filename, fp, sections=sections, remap=remap)
344 fp.close()
344 fp.close()
345 except error.ConfigError as inst:
345 except error.ConfigError as inst:
346 if trusted:
346 if trusted:
347 raise
347 raise
348 self.warn(_("ignored: %s\n") % str(inst))
348 self.warn(_("ignored: %s\n") % str(inst))
349
349
350 if self.plain():
350 if self.plain():
351 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
351 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
352 'logtemplate', 'statuscopies', 'style',
352 'logtemplate', 'statuscopies', 'style',
353 'traceback', 'verbose'):
353 'traceback', 'verbose'):
354 if k in cfg['ui']:
354 if k in cfg['ui']:
355 del cfg['ui'][k]
355 del cfg['ui'][k]
356 for k, v in cfg.items('defaults'):
356 for k, v in cfg.items('defaults'):
357 del cfg['defaults'][k]
357 del cfg['defaults'][k]
358 for k, v in cfg.items('commands'):
358 for k, v in cfg.items('commands'):
359 del cfg['commands'][k]
359 del cfg['commands'][k]
360 # Don't remove aliases from the configuration if in the exceptionlist
360 # Don't remove aliases from the configuration if in the exceptionlist
361 if self.plain('alias'):
361 if self.plain('alias'):
362 for k, v in cfg.items('alias'):
362 for k, v in cfg.items('alias'):
363 del cfg['alias'][k]
363 del cfg['alias'][k]
364 if self.plain('revsetalias'):
364 if self.plain('revsetalias'):
365 for k, v in cfg.items('revsetalias'):
365 for k, v in cfg.items('revsetalias'):
366 del cfg['revsetalias'][k]
366 del cfg['revsetalias'][k]
367 if self.plain('templatealias'):
367 if self.plain('templatealias'):
368 for k, v in cfg.items('templatealias'):
368 for k, v in cfg.items('templatealias'):
369 del cfg['templatealias'][k]
369 del cfg['templatealias'][k]
370
370
371 if trusted:
371 if trusted:
372 self._tcfg.update(cfg)
372 self._tcfg.update(cfg)
373 self._tcfg.update(self._ocfg)
373 self._tcfg.update(self._ocfg)
374 self._ucfg.update(cfg)
374 self._ucfg.update(cfg)
375 self._ucfg.update(self._ocfg)
375 self._ucfg.update(self._ocfg)
376
376
377 if root is None:
377 if root is None:
378 root = os.path.expanduser('~')
378 root = os.path.expanduser('~')
379 self.fixconfig(root=root)
379 self.fixconfig(root=root)
380
380
381 def fixconfig(self, root=None, section=None):
381 def fixconfig(self, root=None, section=None):
382 if section in (None, 'paths'):
382 if section in (None, 'paths'):
383 # expand vars and ~
383 # expand vars and ~
384 # translate paths relative to root (or home) into absolute paths
384 # translate paths relative to root (or home) into absolute paths
385 root = root or pycompat.getcwd()
385 root = root or pycompat.getcwd()
386 for c in self._tcfg, self._ucfg, self._ocfg:
386 for c in self._tcfg, self._ucfg, self._ocfg:
387 for n, p in c.items('paths'):
387 for n, p in c.items('paths'):
388 # Ignore sub-options.
388 # Ignore sub-options.
389 if ':' in n:
389 if ':' in n:
390 continue
390 continue
391 if not p:
391 if not p:
392 continue
392 continue
393 if '%%' in p:
393 if '%%' in p:
394 s = self.configsource('paths', n) or 'none'
394 s = self.configsource('paths', n) or 'none'
395 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
395 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
396 % (n, p, s))
396 % (n, p, s))
397 p = p.replace('%%', '%')
397 p = p.replace('%%', '%')
398 p = util.expandpath(p)
398 p = util.expandpath(p)
399 if not util.hasscheme(p) and not os.path.isabs(p):
399 if not util.hasscheme(p) and not os.path.isabs(p):
400 p = os.path.normpath(os.path.join(root, p))
400 p = os.path.normpath(os.path.join(root, p))
401 c.set("paths", n, p)
401 c.set("paths", n, p)
402
402
403 if section in (None, 'ui'):
403 if section in (None, 'ui'):
404 # update ui options
404 # update ui options
405 self.debugflag = self.configbool('ui', 'debug')
405 self.debugflag = self.configbool('ui', 'debug')
406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
408 if self.verbose and self.quiet:
408 if self.verbose and self.quiet:
409 self.quiet = self.verbose = False
409 self.quiet = self.verbose = False
410 self._reportuntrusted = self.debugflag or self.configbool("ui",
410 self._reportuntrusted = self.debugflag or self.configbool("ui",
411 "report_untrusted", True)
411 "report_untrusted", True)
412 self.tracebackflag = self.configbool('ui', 'traceback', False)
412 self.tracebackflag = self.configbool('ui', 'traceback', False)
413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
414
414
415 if section in (None, 'trusted'):
415 if section in (None, 'trusted'):
416 # update trust information
416 # update trust information
417 self._trustusers.update(self.configlist('trusted', 'users'))
417 self._trustusers.update(self.configlist('trusted', 'users'))
418 self._trustgroups.update(self.configlist('trusted', 'groups'))
418 self._trustgroups.update(self.configlist('trusted', 'groups'))
419
419
420 def backupconfig(self, section, item):
420 def backupconfig(self, section, item):
421 return (self._ocfg.backup(section, item),
421 return (self._ocfg.backup(section, item),
422 self._tcfg.backup(section, item),
422 self._tcfg.backup(section, item),
423 self._ucfg.backup(section, item),)
423 self._ucfg.backup(section, item),)
424 def restoreconfig(self, data):
424 def restoreconfig(self, data):
425 self._ocfg.restore(data[0])
425 self._ocfg.restore(data[0])
426 self._tcfg.restore(data[1])
426 self._tcfg.restore(data[1])
427 self._ucfg.restore(data[2])
427 self._ucfg.restore(data[2])
428
428
429 def setconfig(self, section, name, value, source=''):
429 def setconfig(self, section, name, value, source=''):
430 for cfg in (self._ocfg, self._tcfg, self._ucfg):
430 for cfg in (self._ocfg, self._tcfg, self._ucfg):
431 cfg.set(section, name, value, source)
431 cfg.set(section, name, value, source)
432 self.fixconfig(section=section)
432 self.fixconfig(section=section)
433 self._maybetweakdefaults()
433 self._maybetweakdefaults()
434
434
435 def _data(self, untrusted):
435 def _data(self, untrusted):
436 return untrusted and self._ucfg or self._tcfg
436 return untrusted and self._ucfg or self._tcfg
437
437
438 def configsource(self, section, name, untrusted=False):
438 def configsource(self, section, name, untrusted=False):
439 return self._data(untrusted).source(section, name)
439 return self._data(untrusted).source(section, name)
440
440
441 def config(self, section, name, default=_unset, untrusted=False):
441 def config(self, section, name, default=_unset, untrusted=False):
442 """return the plain string version of a config"""
442 """return the plain string version of a config"""
443 value = self._config(section, name, default=default,
443 value = self._config(section, name, default=default,
444 untrusted=untrusted)
444 untrusted=untrusted)
445 if value is _unset:
445 if value is _unset:
446 return None
446 return None
447 return value
447 return value
448
448
449 def _config(self, section, name, default=_unset, untrusted=False):
449 def _config(self, section, name, default=_unset, untrusted=False):
450 value = default
450 value = default
451 if isinstance(name, list):
452 alternates = name
453 else:
454 item = self._knownconfig.get(section, {}).get(name)
451 item = self._knownconfig.get(section, {}).get(name)
452 alternates = [(section, name)]
453
454 if item is not None:
455 alternates.extend(item.alias)
456
455 if default is _unset:
457 if default is _unset:
456 if item is None:
458 if item is None:
457 value = default
459 value = default
458 elif callable(item.default):
460 elif callable(item.default):
459 value = item.default()
461 value = item.default()
460 else:
462 else:
461 value = item.default
463 value = item.default
462 elif item is not None:
464 elif item is not None:
463 msg = ("specifying a default value for a registered "
465 msg = ("specifying a default value for a registered "
464 "config item: '%s.%s' '%s'")
466 "config item: '%s.%s' '%s'")
465 msg %= (section, name, default)
467 msg %= (section, name, default)
466 self.develwarn(msg, 2, 'warn-config-default')
468 self.develwarn(msg, 2, 'warn-config-default')
467
469
468 alternates = [name]
470 for s, n in alternates:
469
471 candidate = self._data(untrusted).get(s, n, None)
470 for n in alternates:
471 candidate = self._data(untrusted).get(section, n, None)
472 if candidate is not None:
472 if candidate is not None:
473 value = candidate
473 value = candidate
474 section = s
474 name = n
475 name = n
475 break
476 break
476
477
477 if self.debugflag and not untrusted and self._reportuntrusted:
478 if self.debugflag and not untrusted and self._reportuntrusted:
478 for n in alternates:
479 for s, n in alternates:
479 uvalue = self._ucfg.get(section, n)
480 uvalue = self._ucfg.get(s, n)
480 if uvalue is not None and uvalue != value:
481 if uvalue is not None and uvalue != value:
481 self.debug("ignoring untrusted configuration option "
482 self.debug("ignoring untrusted configuration option "
482 "%s.%s = %s\n" % (section, n, uvalue))
483 "%s.%s = %s\n" % (s, n, uvalue))
483 return value
484 return value
484
485
485 def configsuboptions(self, section, name, default=_unset, untrusted=False):
486 def configsuboptions(self, section, name, default=_unset, untrusted=False):
486 """Get a config option and all sub-options.
487 """Get a config option and all sub-options.
487
488
488 Some config options have sub-options that are declared with the
489 Some config options have sub-options that are declared with the
489 format "key:opt = value". This method is used to return the main
490 format "key:opt = value". This method is used to return the main
490 option and all its declared sub-options.
491 option and all its declared sub-options.
491
492
492 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
493 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
493 is a dict of defined sub-options where keys and values are strings.
494 is a dict of defined sub-options where keys and values are strings.
494 """
495 """
495 main = self.config(section, name, default, untrusted=untrusted)
496 main = self.config(section, name, default, untrusted=untrusted)
496 data = self._data(untrusted)
497 data = self._data(untrusted)
497 sub = {}
498 sub = {}
498 prefix = '%s:' % name
499 prefix = '%s:' % name
499 for k, v in data.items(section):
500 for k, v in data.items(section):
500 if k.startswith(prefix):
501 if k.startswith(prefix):
501 sub[k[len(prefix):]] = v
502 sub[k[len(prefix):]] = v
502
503
503 if self.debugflag and not untrusted and self._reportuntrusted:
504 if self.debugflag and not untrusted and self._reportuntrusted:
504 for k, v in sub.items():
505 for k, v in sub.items():
505 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
506 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
506 if uvalue is not None and uvalue != v:
507 if uvalue is not None and uvalue != v:
507 self.debug('ignoring untrusted configuration option '
508 self.debug('ignoring untrusted configuration option '
508 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
509 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
509
510
510 return main, sub
511 return main, sub
511
512
512 def configpath(self, section, name, default=_unset, untrusted=False):
513 def configpath(self, section, name, default=_unset, untrusted=False):
513 'get a path config item, expanded relative to repo root or config file'
514 'get a path config item, expanded relative to repo root or config file'
514 v = self.config(section, name, default, untrusted)
515 v = self.config(section, name, default, untrusted)
515 if v is None:
516 if v is None:
516 return None
517 return None
517 if not os.path.isabs(v) or "://" not in v:
518 if not os.path.isabs(v) or "://" not in v:
518 src = self.configsource(section, name, untrusted)
519 src = self.configsource(section, name, untrusted)
519 if ':' in src:
520 if ':' in src:
520 base = os.path.dirname(src.rsplit(':')[0])
521 base = os.path.dirname(src.rsplit(':')[0])
521 v = os.path.join(base, os.path.expanduser(v))
522 v = os.path.join(base, os.path.expanduser(v))
522 return v
523 return v
523
524
524 def configbool(self, section, name, default=_unset, untrusted=False):
525 def configbool(self, section, name, default=_unset, untrusted=False):
525 """parse a configuration element as a boolean
526 """parse a configuration element as a boolean
526
527
527 >>> u = ui(); s = 'foo'
528 >>> u = ui(); s = 'foo'
528 >>> u.setconfig(s, 'true', 'yes')
529 >>> u.setconfig(s, 'true', 'yes')
529 >>> u.configbool(s, 'true')
530 >>> u.configbool(s, 'true')
530 True
531 True
531 >>> u.setconfig(s, 'false', 'no')
532 >>> u.setconfig(s, 'false', 'no')
532 >>> u.configbool(s, 'false')
533 >>> u.configbool(s, 'false')
533 False
534 False
534 >>> u.configbool(s, 'unknown')
535 >>> u.configbool(s, 'unknown')
535 False
536 False
536 >>> u.configbool(s, 'unknown', True)
537 >>> u.configbool(s, 'unknown', True)
537 True
538 True
538 >>> u.setconfig(s, 'invalid', 'somevalue')
539 >>> u.setconfig(s, 'invalid', 'somevalue')
539 >>> u.configbool(s, 'invalid')
540 >>> u.configbool(s, 'invalid')
540 Traceback (most recent call last):
541 Traceback (most recent call last):
541 ...
542 ...
542 ConfigError: foo.invalid is not a boolean ('somevalue')
543 ConfigError: foo.invalid is not a boolean ('somevalue')
543 """
544 """
544
545
545 v = self._config(section, name, default, untrusted=untrusted)
546 v = self._config(section, name, default, untrusted=untrusted)
546 if v is None:
547 if v is None:
547 return v
548 return v
548 if v is _unset:
549 if v is _unset:
549 if default is _unset:
550 if default is _unset:
550 return False
551 return False
551 return default
552 return default
552 if isinstance(v, bool):
553 if isinstance(v, bool):
553 return v
554 return v
554 b = util.parsebool(v)
555 b = util.parsebool(v)
555 if b is None:
556 if b is None:
556 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
557 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
557 % (section, name, v))
558 % (section, name, v))
558 return b
559 return b
559
560
560 def configwith(self, convert, section, name, default=_unset,
561 def configwith(self, convert, section, name, default=_unset,
561 desc=None, untrusted=False):
562 desc=None, untrusted=False):
562 """parse a configuration element with a conversion function
563 """parse a configuration element with a conversion function
563
564
564 >>> u = ui(); s = 'foo'
565 >>> u = ui(); s = 'foo'
565 >>> u.setconfig(s, 'float1', '42')
566 >>> u.setconfig(s, 'float1', '42')
566 >>> u.configwith(float, s, 'float1')
567 >>> u.configwith(float, s, 'float1')
567 42.0
568 42.0
568 >>> u.setconfig(s, 'float2', '-4.25')
569 >>> u.setconfig(s, 'float2', '-4.25')
569 >>> u.configwith(float, s, 'float2')
570 >>> u.configwith(float, s, 'float2')
570 -4.25
571 -4.25
571 >>> u.configwith(float, s, 'unknown', 7)
572 >>> u.configwith(float, s, 'unknown', 7)
572 7.0
573 7.0
573 >>> u.setconfig(s, 'invalid', 'somevalue')
574 >>> u.setconfig(s, 'invalid', 'somevalue')
574 >>> u.configwith(float, s, 'invalid')
575 >>> u.configwith(float, s, 'invalid')
575 Traceback (most recent call last):
576 Traceback (most recent call last):
576 ...
577 ...
577 ConfigError: foo.invalid is not a valid float ('somevalue')
578 ConfigError: foo.invalid is not a valid float ('somevalue')
578 >>> u.configwith(float, s, 'invalid', desc='womble')
579 >>> u.configwith(float, s, 'invalid', desc='womble')
579 Traceback (most recent call last):
580 Traceback (most recent call last):
580 ...
581 ...
581 ConfigError: foo.invalid is not a valid womble ('somevalue')
582 ConfigError: foo.invalid is not a valid womble ('somevalue')
582 """
583 """
583
584
584 v = self.config(section, name, default, untrusted)
585 v = self.config(section, name, default, untrusted)
585 if v is None:
586 if v is None:
586 return v # do not attempt to convert None
587 return v # do not attempt to convert None
587 try:
588 try:
588 return convert(v)
589 return convert(v)
589 except (ValueError, error.ParseError):
590 except (ValueError, error.ParseError):
590 if desc is None:
591 if desc is None:
591 desc = convert.__name__
592 desc = convert.__name__
592 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
593 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
593 % (section, name, desc, v))
594 % (section, name, desc, v))
594
595
595 def configint(self, section, name, default=_unset, untrusted=False):
596 def configint(self, section, name, default=_unset, untrusted=False):
596 """parse a configuration element as an integer
597 """parse a configuration element as an integer
597
598
598 >>> u = ui(); s = 'foo'
599 >>> u = ui(); s = 'foo'
599 >>> u.setconfig(s, 'int1', '42')
600 >>> u.setconfig(s, 'int1', '42')
600 >>> u.configint(s, 'int1')
601 >>> u.configint(s, 'int1')
601 42
602 42
602 >>> u.setconfig(s, 'int2', '-42')
603 >>> u.setconfig(s, 'int2', '-42')
603 >>> u.configint(s, 'int2')
604 >>> u.configint(s, 'int2')
604 -42
605 -42
605 >>> u.configint(s, 'unknown', 7)
606 >>> u.configint(s, 'unknown', 7)
606 7
607 7
607 >>> u.setconfig(s, 'invalid', 'somevalue')
608 >>> u.setconfig(s, 'invalid', 'somevalue')
608 >>> u.configint(s, 'invalid')
609 >>> u.configint(s, 'invalid')
609 Traceback (most recent call last):
610 Traceback (most recent call last):
610 ...
611 ...
611 ConfigError: foo.invalid is not a valid integer ('somevalue')
612 ConfigError: foo.invalid is not a valid integer ('somevalue')
612 """
613 """
613
614
614 return self.configwith(int, section, name, default, 'integer',
615 return self.configwith(int, section, name, default, 'integer',
615 untrusted)
616 untrusted)
616
617
617 def configbytes(self, section, name, default=_unset, untrusted=False):
618 def configbytes(self, section, name, default=_unset, untrusted=False):
618 """parse a configuration element as a quantity in bytes
619 """parse a configuration element as a quantity in bytes
619
620
620 Units can be specified as b (bytes), k or kb (kilobytes), m or
621 Units can be specified as b (bytes), k or kb (kilobytes), m or
621 mb (megabytes), g or gb (gigabytes).
622 mb (megabytes), g or gb (gigabytes).
622
623
623 >>> u = ui(); s = 'foo'
624 >>> u = ui(); s = 'foo'
624 >>> u.setconfig(s, 'val1', '42')
625 >>> u.setconfig(s, 'val1', '42')
625 >>> u.configbytes(s, 'val1')
626 >>> u.configbytes(s, 'val1')
626 42
627 42
627 >>> u.setconfig(s, 'val2', '42.5 kb')
628 >>> u.setconfig(s, 'val2', '42.5 kb')
628 >>> u.configbytes(s, 'val2')
629 >>> u.configbytes(s, 'val2')
629 43520
630 43520
630 >>> u.configbytes(s, 'unknown', '7 MB')
631 >>> u.configbytes(s, 'unknown', '7 MB')
631 7340032
632 7340032
632 >>> u.setconfig(s, 'invalid', 'somevalue')
633 >>> u.setconfig(s, 'invalid', 'somevalue')
633 >>> u.configbytes(s, 'invalid')
634 >>> u.configbytes(s, 'invalid')
634 Traceback (most recent call last):
635 Traceback (most recent call last):
635 ...
636 ...
636 ConfigError: foo.invalid is not a byte quantity ('somevalue')
637 ConfigError: foo.invalid is not a byte quantity ('somevalue')
637 """
638 """
638
639
639 value = self._config(section, name, default, untrusted)
640 value = self._config(section, name, default, untrusted)
640 if value is _unset:
641 if value is _unset:
641 if default is _unset:
642 if default is _unset:
642 default = 0
643 default = 0
643 value = default
644 value = default
644 if not isinstance(value, str):
645 if not isinstance(value, str):
645 return value
646 return value
646 try:
647 try:
647 return util.sizetoint(value)
648 return util.sizetoint(value)
648 except error.ParseError:
649 except error.ParseError:
649 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
650 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
650 % (section, name, value))
651 % (section, name, value))
651
652
652 def configlist(self, section, name, default=_unset, untrusted=False):
653 def configlist(self, section, name, default=_unset, untrusted=False):
653 """parse a configuration element as a list of comma/space separated
654 """parse a configuration element as a list of comma/space separated
654 strings
655 strings
655
656
656 >>> u = ui(); s = 'foo'
657 >>> u = ui(); s = 'foo'
657 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
658 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
658 >>> u.configlist(s, 'list1')
659 >>> u.configlist(s, 'list1')
659 ['this', 'is', 'a small', 'test']
660 ['this', 'is', 'a small', 'test']
660 """
661 """
661 # default is not always a list
662 # default is not always a list
662 v = self.configwith(config.parselist, section, name, default,
663 v = self.configwith(config.parselist, section, name, default,
663 'list', untrusted)
664 'list', untrusted)
664 if isinstance(v, bytes):
665 if isinstance(v, bytes):
665 return config.parselist(v)
666 return config.parselist(v)
666 elif v is None:
667 elif v is None:
667 return []
668 return []
668 return v
669 return v
669
670
670 def configdate(self, section, name, default=_unset, untrusted=False):
671 def configdate(self, section, name, default=_unset, untrusted=False):
671 """parse a configuration element as a tuple of ints
672 """parse a configuration element as a tuple of ints
672
673
673 >>> u = ui(); s = 'foo'
674 >>> u = ui(); s = 'foo'
674 >>> u.setconfig(s, 'date', '0 0')
675 >>> u.setconfig(s, 'date', '0 0')
675 >>> u.configdate(s, 'date')
676 >>> u.configdate(s, 'date')
676 (0, 0)
677 (0, 0)
677 """
678 """
678 if self.config(section, name, default, untrusted):
679 if self.config(section, name, default, untrusted):
679 return self.configwith(util.parsedate, section, name, default,
680 return self.configwith(util.parsedate, section, name, default,
680 'date', untrusted)
681 'date', untrusted)
681 if default is _unset:
682 if default is _unset:
682 return None
683 return None
683 return default
684 return default
684
685
685 def hasconfig(self, section, name, untrusted=False):
686 def hasconfig(self, section, name, untrusted=False):
686 return self._data(untrusted).hasitem(section, name)
687 return self._data(untrusted).hasitem(section, name)
687
688
688 def has_section(self, section, untrusted=False):
689 def has_section(self, section, untrusted=False):
689 '''tell whether section exists in config.'''
690 '''tell whether section exists in config.'''
690 return section in self._data(untrusted)
691 return section in self._data(untrusted)
691
692
692 def configitems(self, section, untrusted=False, ignoresub=False):
693 def configitems(self, section, untrusted=False, ignoresub=False):
693 items = self._data(untrusted).items(section)
694 items = self._data(untrusted).items(section)
694 if ignoresub:
695 if ignoresub:
695 newitems = {}
696 newitems = {}
696 for k, v in items:
697 for k, v in items:
697 if ':' not in k:
698 if ':' not in k:
698 newitems[k] = v
699 newitems[k] = v
699 items = newitems.items()
700 items = newitems.items()
700 if self.debugflag and not untrusted and self._reportuntrusted:
701 if self.debugflag and not untrusted and self._reportuntrusted:
701 for k, v in self._ucfg.items(section):
702 for k, v in self._ucfg.items(section):
702 if self._tcfg.get(section, k) != v:
703 if self._tcfg.get(section, k) != v:
703 self.debug("ignoring untrusted configuration option "
704 self.debug("ignoring untrusted configuration option "
704 "%s.%s = %s\n" % (section, k, v))
705 "%s.%s = %s\n" % (section, k, v))
705 return items
706 return items
706
707
707 def walkconfig(self, untrusted=False):
708 def walkconfig(self, untrusted=False):
708 cfg = self._data(untrusted)
709 cfg = self._data(untrusted)
709 for section in cfg.sections():
710 for section in cfg.sections():
710 for name, value in self.configitems(section, untrusted):
711 for name, value in self.configitems(section, untrusted):
711 yield section, name, value
712 yield section, name, value
712
713
713 def plain(self, feature=None):
714 def plain(self, feature=None):
714 '''is plain mode active?
715 '''is plain mode active?
715
716
716 Plain mode means that all configuration variables which affect
717 Plain mode means that all configuration variables which affect
717 the behavior and output of Mercurial should be
718 the behavior and output of Mercurial should be
718 ignored. Additionally, the output should be stable,
719 ignored. Additionally, the output should be stable,
719 reproducible and suitable for use in scripts or applications.
720 reproducible and suitable for use in scripts or applications.
720
721
721 The only way to trigger plain mode is by setting either the
722 The only way to trigger plain mode is by setting either the
722 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
723 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
723
724
724 The return value can either be
725 The return value can either be
725 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
726 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
726 - True otherwise
727 - True otherwise
727 '''
728 '''
728 if ('HGPLAIN' not in encoding.environ and
729 if ('HGPLAIN' not in encoding.environ and
729 'HGPLAINEXCEPT' not in encoding.environ):
730 'HGPLAINEXCEPT' not in encoding.environ):
730 return False
731 return False
731 exceptions = encoding.environ.get('HGPLAINEXCEPT',
732 exceptions = encoding.environ.get('HGPLAINEXCEPT',
732 '').strip().split(',')
733 '').strip().split(',')
733 if feature and exceptions:
734 if feature and exceptions:
734 return feature not in exceptions
735 return feature not in exceptions
735 return True
736 return True
736
737
737 def username(self):
738 def username(self):
738 """Return default username to be used in commits.
739 """Return default username to be used in commits.
739
740
740 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
741 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
741 and stop searching if one of these is set.
742 and stop searching if one of these is set.
742 If not found and ui.askusername is True, ask the user, else use
743 If not found and ui.askusername is True, ask the user, else use
743 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
744 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
744 """
745 """
745 user = encoding.environ.get("HGUSER")
746 user = encoding.environ.get("HGUSER")
746 if user is None:
747 if user is None:
747 user = self.config("ui", ["username", "user"])
748 user = self.config("ui", "username")
748 if user is not None:
749 if user is not None:
749 user = os.path.expandvars(user)
750 user = os.path.expandvars(user)
750 if user is None:
751 if user is None:
751 user = encoding.environ.get("EMAIL")
752 user = encoding.environ.get("EMAIL")
752 if user is None and self.configbool("ui", "askusername"):
753 if user is None and self.configbool("ui", "askusername"):
753 user = self.prompt(_("enter a commit username:"), default=None)
754 user = self.prompt(_("enter a commit username:"), default=None)
754 if user is None and not self.interactive():
755 if user is None and not self.interactive():
755 try:
756 try:
756 user = '%s@%s' % (util.getuser(), socket.getfqdn())
757 user = '%s@%s' % (util.getuser(), socket.getfqdn())
757 self.warn(_("no username found, using '%s' instead\n") % user)
758 self.warn(_("no username found, using '%s' instead\n") % user)
758 except KeyError:
759 except KeyError:
759 pass
760 pass
760 if not user:
761 if not user:
761 raise error.Abort(_('no username supplied'),
762 raise error.Abort(_('no username supplied'),
762 hint=_("use 'hg config --edit' "
763 hint=_("use 'hg config --edit' "
763 'to set your username'))
764 'to set your username'))
764 if "\n" in user:
765 if "\n" in user:
765 raise error.Abort(_("username %s contains a newline\n")
766 raise error.Abort(_("username %s contains a newline\n")
766 % repr(user))
767 % repr(user))
767 return user
768 return user
768
769
769 def shortuser(self, user):
770 def shortuser(self, user):
770 """Return a short representation of a user name or email address."""
771 """Return a short representation of a user name or email address."""
771 if not self.verbose:
772 if not self.verbose:
772 user = util.shortuser(user)
773 user = util.shortuser(user)
773 return user
774 return user
774
775
775 def expandpath(self, loc, default=None):
776 def expandpath(self, loc, default=None):
776 """Return repository location relative to cwd or from [paths]"""
777 """Return repository location relative to cwd or from [paths]"""
777 try:
778 try:
778 p = self.paths.getpath(loc)
779 p = self.paths.getpath(loc)
779 if p:
780 if p:
780 return p.rawloc
781 return p.rawloc
781 except error.RepoError:
782 except error.RepoError:
782 pass
783 pass
783
784
784 if default:
785 if default:
785 try:
786 try:
786 p = self.paths.getpath(default)
787 p = self.paths.getpath(default)
787 if p:
788 if p:
788 return p.rawloc
789 return p.rawloc
789 except error.RepoError:
790 except error.RepoError:
790 pass
791 pass
791
792
792 return loc
793 return loc
793
794
794 @util.propertycache
795 @util.propertycache
795 def paths(self):
796 def paths(self):
796 return paths(self)
797 return paths(self)
797
798
798 def pushbuffer(self, error=False, subproc=False, labeled=False):
799 def pushbuffer(self, error=False, subproc=False, labeled=False):
799 """install a buffer to capture standard output of the ui object
800 """install a buffer to capture standard output of the ui object
800
801
801 If error is True, the error output will be captured too.
802 If error is True, the error output will be captured too.
802
803
803 If subproc is True, output from subprocesses (typically hooks) will be
804 If subproc is True, output from subprocesses (typically hooks) will be
804 captured too.
805 captured too.
805
806
806 If labeled is True, any labels associated with buffered
807 If labeled is True, any labels associated with buffered
807 output will be handled. By default, this has no effect
808 output will be handled. By default, this has no effect
808 on the output returned, but extensions and GUI tools may
809 on the output returned, but extensions and GUI tools may
809 handle this argument and returned styled output. If output
810 handle this argument and returned styled output. If output
810 is being buffered so it can be captured and parsed or
811 is being buffered so it can be captured and parsed or
811 processed, labeled should not be set to True.
812 processed, labeled should not be set to True.
812 """
813 """
813 self._buffers.append([])
814 self._buffers.append([])
814 self._bufferstates.append((error, subproc, labeled))
815 self._bufferstates.append((error, subproc, labeled))
815 self._bufferapplylabels = labeled
816 self._bufferapplylabels = labeled
816
817
817 def popbuffer(self):
818 def popbuffer(self):
818 '''pop the last buffer and return the buffered output'''
819 '''pop the last buffer and return the buffered output'''
819 self._bufferstates.pop()
820 self._bufferstates.pop()
820 if self._bufferstates:
821 if self._bufferstates:
821 self._bufferapplylabels = self._bufferstates[-1][2]
822 self._bufferapplylabels = self._bufferstates[-1][2]
822 else:
823 else:
823 self._bufferapplylabels = None
824 self._bufferapplylabels = None
824
825
825 return "".join(self._buffers.pop())
826 return "".join(self._buffers.pop())
826
827
827 def write(self, *args, **opts):
828 def write(self, *args, **opts):
828 '''write args to output
829 '''write args to output
829
830
830 By default, this method simply writes to the buffer or stdout.
831 By default, this method simply writes to the buffer or stdout.
831 Color mode can be set on the UI class to have the output decorated
832 Color mode can be set on the UI class to have the output decorated
832 with color modifier before being written to stdout.
833 with color modifier before being written to stdout.
833
834
834 The color used is controlled by an optional keyword argument, "label".
835 The color used is controlled by an optional keyword argument, "label".
835 This should be a string containing label names separated by space.
836 This should be a string containing label names separated by space.
836 Label names take the form of "topic.type". For example, ui.debug()
837 Label names take the form of "topic.type". For example, ui.debug()
837 issues a label of "ui.debug".
838 issues a label of "ui.debug".
838
839
839 When labeling output for a specific command, a label of
840 When labeling output for a specific command, a label of
840 "cmdname.type" is recommended. For example, status issues
841 "cmdname.type" is recommended. For example, status issues
841 a label of "status.modified" for modified files.
842 a label of "status.modified" for modified files.
842 '''
843 '''
843 if self._buffers and not opts.get('prompt', False):
844 if self._buffers and not opts.get('prompt', False):
844 if self._bufferapplylabels:
845 if self._bufferapplylabels:
845 label = opts.get('label', '')
846 label = opts.get('label', '')
846 self._buffers[-1].extend(self.label(a, label) for a in args)
847 self._buffers[-1].extend(self.label(a, label) for a in args)
847 else:
848 else:
848 self._buffers[-1].extend(args)
849 self._buffers[-1].extend(args)
849 elif self._colormode == 'win32':
850 elif self._colormode == 'win32':
850 # windows color printing is its own can of crab, defer to
851 # windows color printing is its own can of crab, defer to
851 # the color module and that is it.
852 # the color module and that is it.
852 color.win32print(self, self._write, *args, **opts)
853 color.win32print(self, self._write, *args, **opts)
853 else:
854 else:
854 msgs = args
855 msgs = args
855 if self._colormode is not None:
856 if self._colormode is not None:
856 label = opts.get('label', '')
857 label = opts.get('label', '')
857 msgs = [self.label(a, label) for a in args]
858 msgs = [self.label(a, label) for a in args]
858 self._write(*msgs, **opts)
859 self._write(*msgs, **opts)
859
860
860 def _write(self, *msgs, **opts):
861 def _write(self, *msgs, **opts):
861 self._progclear()
862 self._progclear()
862 # opencode timeblockedsection because this is a critical path
863 # opencode timeblockedsection because this is a critical path
863 starttime = util.timer()
864 starttime = util.timer()
864 try:
865 try:
865 for a in msgs:
866 for a in msgs:
866 self.fout.write(a)
867 self.fout.write(a)
867 except IOError as err:
868 except IOError as err:
868 raise error.StdioError(err)
869 raise error.StdioError(err)
869 finally:
870 finally:
870 self._blockedtimes['stdio_blocked'] += \
871 self._blockedtimes['stdio_blocked'] += \
871 (util.timer() - starttime) * 1000
872 (util.timer() - starttime) * 1000
872
873
873 def write_err(self, *args, **opts):
874 def write_err(self, *args, **opts):
874 self._progclear()
875 self._progclear()
875 if self._bufferstates and self._bufferstates[-1][0]:
876 if self._bufferstates and self._bufferstates[-1][0]:
876 self.write(*args, **opts)
877 self.write(*args, **opts)
877 elif self._colormode == 'win32':
878 elif self._colormode == 'win32':
878 # windows color printing is its own can of crab, defer to
879 # windows color printing is its own can of crab, defer to
879 # the color module and that is it.
880 # the color module and that is it.
880 color.win32print(self, self._write_err, *args, **opts)
881 color.win32print(self, self._write_err, *args, **opts)
881 else:
882 else:
882 msgs = args
883 msgs = args
883 if self._colormode is not None:
884 if self._colormode is not None:
884 label = opts.get('label', '')
885 label = opts.get('label', '')
885 msgs = [self.label(a, label) for a in args]
886 msgs = [self.label(a, label) for a in args]
886 self._write_err(*msgs, **opts)
887 self._write_err(*msgs, **opts)
887
888
888 def _write_err(self, *msgs, **opts):
889 def _write_err(self, *msgs, **opts):
889 try:
890 try:
890 with self.timeblockedsection('stdio'):
891 with self.timeblockedsection('stdio'):
891 if not getattr(self.fout, 'closed', False):
892 if not getattr(self.fout, 'closed', False):
892 self.fout.flush()
893 self.fout.flush()
893 for a in msgs:
894 for a in msgs:
894 self.ferr.write(a)
895 self.ferr.write(a)
895 # stderr may be buffered under win32 when redirected to files,
896 # stderr may be buffered under win32 when redirected to files,
896 # including stdout.
897 # including stdout.
897 if not getattr(self.ferr, 'closed', False):
898 if not getattr(self.ferr, 'closed', False):
898 self.ferr.flush()
899 self.ferr.flush()
899 except IOError as inst:
900 except IOError as inst:
900 raise error.StdioError(inst)
901 raise error.StdioError(inst)
901
902
902 def flush(self):
903 def flush(self):
903 # opencode timeblockedsection because this is a critical path
904 # opencode timeblockedsection because this is a critical path
904 starttime = util.timer()
905 starttime = util.timer()
905 try:
906 try:
906 try:
907 try:
907 self.fout.flush()
908 self.fout.flush()
908 except IOError as err:
909 except IOError as err:
909 raise error.StdioError(err)
910 raise error.StdioError(err)
910 finally:
911 finally:
911 try:
912 try:
912 self.ferr.flush()
913 self.ferr.flush()
913 except IOError as err:
914 except IOError as err:
914 raise error.StdioError(err)
915 raise error.StdioError(err)
915 finally:
916 finally:
916 self._blockedtimes['stdio_blocked'] += \
917 self._blockedtimes['stdio_blocked'] += \
917 (util.timer() - starttime) * 1000
918 (util.timer() - starttime) * 1000
918
919
919 def _isatty(self, fh):
920 def _isatty(self, fh):
920 if self.configbool('ui', 'nontty', False):
921 if self.configbool('ui', 'nontty', False):
921 return False
922 return False
922 return util.isatty(fh)
923 return util.isatty(fh)
923
924
924 def disablepager(self):
925 def disablepager(self):
925 self._disablepager = True
926 self._disablepager = True
926
927
927 def pager(self, command):
928 def pager(self, command):
928 """Start a pager for subsequent command output.
929 """Start a pager for subsequent command output.
929
930
930 Commands which produce a long stream of output should call
931 Commands which produce a long stream of output should call
931 this function to activate the user's preferred pagination
932 this function to activate the user's preferred pagination
932 mechanism (which may be no pager). Calling this function
933 mechanism (which may be no pager). Calling this function
933 precludes any future use of interactive functionality, such as
934 precludes any future use of interactive functionality, such as
934 prompting the user or activating curses.
935 prompting the user or activating curses.
935
936
936 Args:
937 Args:
937 command: The full, non-aliased name of the command. That is, "log"
938 command: The full, non-aliased name of the command. That is, "log"
938 not "history, "summary" not "summ", etc.
939 not "history, "summary" not "summ", etc.
939 """
940 """
940 if (self._disablepager
941 if (self._disablepager
941 or self.pageractive
942 or self.pageractive
942 or command in self.configlist('pager', 'ignore')
943 or command in self.configlist('pager', 'ignore')
943 or not self.configbool('ui', 'paginate', True)
944 or not self.configbool('ui', 'paginate', True)
944 or not self.configbool('pager', 'attend-' + command, True)
945 or not self.configbool('pager', 'attend-' + command, True)
945 # TODO: if we want to allow HGPLAINEXCEPT=pager,
946 # TODO: if we want to allow HGPLAINEXCEPT=pager,
946 # formatted() will need some adjustment.
947 # formatted() will need some adjustment.
947 or not self.formatted()
948 or not self.formatted()
948 or self.plain()
949 or self.plain()
949 # TODO: expose debugger-enabled on the UI object
950 # TODO: expose debugger-enabled on the UI object
950 or '--debugger' in pycompat.sysargv):
951 or '--debugger' in pycompat.sysargv):
951 # We only want to paginate if the ui appears to be
952 # We only want to paginate if the ui appears to be
952 # interactive, the user didn't say HGPLAIN or
953 # interactive, the user didn't say HGPLAIN or
953 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
954 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
954 return
955 return
955
956
956 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
957 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
957 if not pagercmd:
958 if not pagercmd:
958 return
959 return
959
960
960 pagerenv = {}
961 pagerenv = {}
961 for name, value in rcutil.defaultpagerenv().items():
962 for name, value in rcutil.defaultpagerenv().items():
962 if name not in encoding.environ:
963 if name not in encoding.environ:
963 pagerenv[name] = value
964 pagerenv[name] = value
964
965
965 self.debug('starting pager for command %r\n' % command)
966 self.debug('starting pager for command %r\n' % command)
966 self.flush()
967 self.flush()
967
968
968 wasformatted = self.formatted()
969 wasformatted = self.formatted()
969 if util.safehasattr(signal, "SIGPIPE"):
970 if util.safehasattr(signal, "SIGPIPE"):
970 signal.signal(signal.SIGPIPE, _catchterm)
971 signal.signal(signal.SIGPIPE, _catchterm)
971 if self._runpager(pagercmd, pagerenv):
972 if self._runpager(pagercmd, pagerenv):
972 self.pageractive = True
973 self.pageractive = True
973 # Preserve the formatted-ness of the UI. This is important
974 # Preserve the formatted-ness of the UI. This is important
974 # because we mess with stdout, which might confuse
975 # because we mess with stdout, which might confuse
975 # auto-detection of things being formatted.
976 # auto-detection of things being formatted.
976 self.setconfig('ui', 'formatted', wasformatted, 'pager')
977 self.setconfig('ui', 'formatted', wasformatted, 'pager')
977 self.setconfig('ui', 'interactive', False, 'pager')
978 self.setconfig('ui', 'interactive', False, 'pager')
978
979
979 # If pagermode differs from color.mode, reconfigure color now that
980 # If pagermode differs from color.mode, reconfigure color now that
980 # pageractive is set.
981 # pageractive is set.
981 cm = self._colormode
982 cm = self._colormode
982 if cm != self.config('color', 'pagermode', cm):
983 if cm != self.config('color', 'pagermode', cm):
983 color.setup(self)
984 color.setup(self)
984 else:
985 else:
985 # If the pager can't be spawned in dispatch when --pager=on is
986 # If the pager can't be spawned in dispatch when --pager=on is
986 # given, don't try again when the command runs, to avoid a duplicate
987 # given, don't try again when the command runs, to avoid a duplicate
987 # warning about a missing pager command.
988 # warning about a missing pager command.
988 self.disablepager()
989 self.disablepager()
989
990
990 def _runpager(self, command, env=None):
991 def _runpager(self, command, env=None):
991 """Actually start the pager and set up file descriptors.
992 """Actually start the pager and set up file descriptors.
992
993
993 This is separate in part so that extensions (like chg) can
994 This is separate in part so that extensions (like chg) can
994 override how a pager is invoked.
995 override how a pager is invoked.
995 """
996 """
996 if command == 'cat':
997 if command == 'cat':
997 # Save ourselves some work.
998 # Save ourselves some work.
998 return False
999 return False
999 # If the command doesn't contain any of these characters, we
1000 # If the command doesn't contain any of these characters, we
1000 # assume it's a binary and exec it directly. This means for
1001 # assume it's a binary and exec it directly. This means for
1001 # simple pager command configurations, we can degrade
1002 # simple pager command configurations, we can degrade
1002 # gracefully and tell the user about their broken pager.
1003 # gracefully and tell the user about their broken pager.
1003 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1004 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1004
1005
1005 if pycompat.osname == 'nt' and not shell:
1006 if pycompat.osname == 'nt' and not shell:
1006 # Window's built-in `more` cannot be invoked with shell=False, but
1007 # Window's built-in `more` cannot be invoked with shell=False, but
1007 # its `more.com` can. Hide this implementation detail from the
1008 # its `more.com` can. Hide this implementation detail from the
1008 # user so we can also get sane bad PAGER behavior. MSYS has
1009 # user so we can also get sane bad PAGER behavior. MSYS has
1009 # `more.exe`, so do a cmd.exe style resolution of the executable to
1010 # `more.exe`, so do a cmd.exe style resolution of the executable to
1010 # determine which one to use.
1011 # determine which one to use.
1011 fullcmd = util.findexe(command)
1012 fullcmd = util.findexe(command)
1012 if not fullcmd:
1013 if not fullcmd:
1013 self.warn(_("missing pager command '%s', skipping pager\n")
1014 self.warn(_("missing pager command '%s', skipping pager\n")
1014 % command)
1015 % command)
1015 return False
1016 return False
1016
1017
1017 command = fullcmd
1018 command = fullcmd
1018
1019
1019 try:
1020 try:
1020 pager = subprocess.Popen(
1021 pager = subprocess.Popen(
1021 command, shell=shell, bufsize=-1,
1022 command, shell=shell, bufsize=-1,
1022 close_fds=util.closefds, stdin=subprocess.PIPE,
1023 close_fds=util.closefds, stdin=subprocess.PIPE,
1023 stdout=util.stdout, stderr=util.stderr,
1024 stdout=util.stdout, stderr=util.stderr,
1024 env=util.shellenviron(env))
1025 env=util.shellenviron(env))
1025 except OSError as e:
1026 except OSError as e:
1026 if e.errno == errno.ENOENT and not shell:
1027 if e.errno == errno.ENOENT and not shell:
1027 self.warn(_("missing pager command '%s', skipping pager\n")
1028 self.warn(_("missing pager command '%s', skipping pager\n")
1028 % command)
1029 % command)
1029 return False
1030 return False
1030 raise
1031 raise
1031
1032
1032 # back up original file descriptors
1033 # back up original file descriptors
1033 stdoutfd = os.dup(util.stdout.fileno())
1034 stdoutfd = os.dup(util.stdout.fileno())
1034 stderrfd = os.dup(util.stderr.fileno())
1035 stderrfd = os.dup(util.stderr.fileno())
1035
1036
1036 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1037 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1037 if self._isatty(util.stderr):
1038 if self._isatty(util.stderr):
1038 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1039 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1039
1040
1040 @self.atexit
1041 @self.atexit
1041 def killpager():
1042 def killpager():
1042 if util.safehasattr(signal, "SIGINT"):
1043 if util.safehasattr(signal, "SIGINT"):
1043 signal.signal(signal.SIGINT, signal.SIG_IGN)
1044 signal.signal(signal.SIGINT, signal.SIG_IGN)
1044 # restore original fds, closing pager.stdin copies in the process
1045 # restore original fds, closing pager.stdin copies in the process
1045 os.dup2(stdoutfd, util.stdout.fileno())
1046 os.dup2(stdoutfd, util.stdout.fileno())
1046 os.dup2(stderrfd, util.stderr.fileno())
1047 os.dup2(stderrfd, util.stderr.fileno())
1047 pager.stdin.close()
1048 pager.stdin.close()
1048 pager.wait()
1049 pager.wait()
1049
1050
1050 return True
1051 return True
1051
1052
1052 def atexit(self, func, *args, **kwargs):
1053 def atexit(self, func, *args, **kwargs):
1053 '''register a function to run after dispatching a request
1054 '''register a function to run after dispatching a request
1054
1055
1055 Handlers do not stay registered across request boundaries.'''
1056 Handlers do not stay registered across request boundaries.'''
1056 self._exithandlers.append((func, args, kwargs))
1057 self._exithandlers.append((func, args, kwargs))
1057 return func
1058 return func
1058
1059
1059 def interface(self, feature):
1060 def interface(self, feature):
1060 """what interface to use for interactive console features?
1061 """what interface to use for interactive console features?
1061
1062
1062 The interface is controlled by the value of `ui.interface` but also by
1063 The interface is controlled by the value of `ui.interface` but also by
1063 the value of feature-specific configuration. For example:
1064 the value of feature-specific configuration. For example:
1064
1065
1065 ui.interface.histedit = text
1066 ui.interface.histedit = text
1066 ui.interface.chunkselector = curses
1067 ui.interface.chunkselector = curses
1067
1068
1068 Here the features are "histedit" and "chunkselector".
1069 Here the features are "histedit" and "chunkselector".
1069
1070
1070 The configuration above means that the default interfaces for commands
1071 The configuration above means that the default interfaces for commands
1071 is curses, the interface for histedit is text and the interface for
1072 is curses, the interface for histedit is text and the interface for
1072 selecting chunk is crecord (the best curses interface available).
1073 selecting chunk is crecord (the best curses interface available).
1073
1074
1074 Consider the following example:
1075 Consider the following example:
1075 ui.interface = curses
1076 ui.interface = curses
1076 ui.interface.histedit = text
1077 ui.interface.histedit = text
1077
1078
1078 Then histedit will use the text interface and chunkselector will use
1079 Then histedit will use the text interface and chunkselector will use
1079 the default curses interface (crecord at the moment).
1080 the default curses interface (crecord at the moment).
1080 """
1081 """
1081 alldefaults = frozenset(["text", "curses"])
1082 alldefaults = frozenset(["text", "curses"])
1082
1083
1083 featureinterfaces = {
1084 featureinterfaces = {
1084 "chunkselector": [
1085 "chunkselector": [
1085 "text",
1086 "text",
1086 "curses",
1087 "curses",
1087 ]
1088 ]
1088 }
1089 }
1089
1090
1090 # Feature-specific interface
1091 # Feature-specific interface
1091 if feature not in featureinterfaces.keys():
1092 if feature not in featureinterfaces.keys():
1092 # Programming error, not user error
1093 # Programming error, not user error
1093 raise ValueError("Unknown feature requested %s" % feature)
1094 raise ValueError("Unknown feature requested %s" % feature)
1094
1095
1095 availableinterfaces = frozenset(featureinterfaces[feature])
1096 availableinterfaces = frozenset(featureinterfaces[feature])
1096 if alldefaults > availableinterfaces:
1097 if alldefaults > availableinterfaces:
1097 # Programming error, not user error. We need a use case to
1098 # Programming error, not user error. We need a use case to
1098 # define the right thing to do here.
1099 # define the right thing to do here.
1099 raise ValueError(
1100 raise ValueError(
1100 "Feature %s does not handle all default interfaces" %
1101 "Feature %s does not handle all default interfaces" %
1101 feature)
1102 feature)
1102
1103
1103 if self.plain():
1104 if self.plain():
1104 return "text"
1105 return "text"
1105
1106
1106 # Default interface for all the features
1107 # Default interface for all the features
1107 defaultinterface = "text"
1108 defaultinterface = "text"
1108 i = self.config("ui", "interface", None)
1109 i = self.config("ui", "interface", None)
1109 if i in alldefaults:
1110 if i in alldefaults:
1110 defaultinterface = i
1111 defaultinterface = i
1111
1112
1112 choseninterface = defaultinterface
1113 choseninterface = defaultinterface
1113 f = self.config("ui", "interface.%s" % feature, None)
1114 f = self.config("ui", "interface.%s" % feature, None)
1114 if f in availableinterfaces:
1115 if f in availableinterfaces:
1115 choseninterface = f
1116 choseninterface = f
1116
1117
1117 if i is not None and defaultinterface != i:
1118 if i is not None and defaultinterface != i:
1118 if f is not None:
1119 if f is not None:
1119 self.warn(_("invalid value for ui.interface: %s\n") %
1120 self.warn(_("invalid value for ui.interface: %s\n") %
1120 (i,))
1121 (i,))
1121 else:
1122 else:
1122 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1123 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1123 (i, choseninterface))
1124 (i, choseninterface))
1124 if f is not None and choseninterface != f:
1125 if f is not None and choseninterface != f:
1125 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1126 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1126 (feature, f, choseninterface))
1127 (feature, f, choseninterface))
1127
1128
1128 return choseninterface
1129 return choseninterface
1129
1130
1130 def interactive(self):
1131 def interactive(self):
1131 '''is interactive input allowed?
1132 '''is interactive input allowed?
1132
1133
1133 An interactive session is a session where input can be reasonably read
1134 An interactive session is a session where input can be reasonably read
1134 from `sys.stdin'. If this function returns false, any attempt to read
1135 from `sys.stdin'. If this function returns false, any attempt to read
1135 from stdin should fail with an error, unless a sensible default has been
1136 from stdin should fail with an error, unless a sensible default has been
1136 specified.
1137 specified.
1137
1138
1138 Interactiveness is triggered by the value of the `ui.interactive'
1139 Interactiveness is triggered by the value of the `ui.interactive'
1139 configuration variable or - if it is unset - when `sys.stdin' points
1140 configuration variable or - if it is unset - when `sys.stdin' points
1140 to a terminal device.
1141 to a terminal device.
1141
1142
1142 This function refers to input only; for output, see `ui.formatted()'.
1143 This function refers to input only; for output, see `ui.formatted()'.
1143 '''
1144 '''
1144 i = self.configbool("ui", "interactive")
1145 i = self.configbool("ui", "interactive")
1145 if i is None:
1146 if i is None:
1146 # some environments replace stdin without implementing isatty
1147 # some environments replace stdin without implementing isatty
1147 # usually those are non-interactive
1148 # usually those are non-interactive
1148 return self._isatty(self.fin)
1149 return self._isatty(self.fin)
1149
1150
1150 return i
1151 return i
1151
1152
1152 def termwidth(self):
1153 def termwidth(self):
1153 '''how wide is the terminal in columns?
1154 '''how wide is the terminal in columns?
1154 '''
1155 '''
1155 if 'COLUMNS' in encoding.environ:
1156 if 'COLUMNS' in encoding.environ:
1156 try:
1157 try:
1157 return int(encoding.environ['COLUMNS'])
1158 return int(encoding.environ['COLUMNS'])
1158 except ValueError:
1159 except ValueError:
1159 pass
1160 pass
1160 return scmutil.termsize(self)[0]
1161 return scmutil.termsize(self)[0]
1161
1162
1162 def formatted(self):
1163 def formatted(self):
1163 '''should formatted output be used?
1164 '''should formatted output be used?
1164
1165
1165 It is often desirable to format the output to suite the output medium.
1166 It is often desirable to format the output to suite the output medium.
1166 Examples of this are truncating long lines or colorizing messages.
1167 Examples of this are truncating long lines or colorizing messages.
1167 However, this is not often not desirable when piping output into other
1168 However, this is not often not desirable when piping output into other
1168 utilities, e.g. `grep'.
1169 utilities, e.g. `grep'.
1169
1170
1170 Formatted output is triggered by the value of the `ui.formatted'
1171 Formatted output is triggered by the value of the `ui.formatted'
1171 configuration variable or - if it is unset - when `sys.stdout' points
1172 configuration variable or - if it is unset - when `sys.stdout' points
1172 to a terminal device. Please note that `ui.formatted' should be
1173 to a terminal device. Please note that `ui.formatted' should be
1173 considered an implementation detail; it is not intended for use outside
1174 considered an implementation detail; it is not intended for use outside
1174 Mercurial or its extensions.
1175 Mercurial or its extensions.
1175
1176
1176 This function refers to output only; for input, see `ui.interactive()'.
1177 This function refers to output only; for input, see `ui.interactive()'.
1177 This function always returns false when in plain mode, see `ui.plain()'.
1178 This function always returns false when in plain mode, see `ui.plain()'.
1178 '''
1179 '''
1179 if self.plain():
1180 if self.plain():
1180 return False
1181 return False
1181
1182
1182 i = self.configbool("ui", "formatted", None)
1183 i = self.configbool("ui", "formatted", None)
1183 if i is None:
1184 if i is None:
1184 # some environments replace stdout without implementing isatty
1185 # some environments replace stdout without implementing isatty
1185 # usually those are non-interactive
1186 # usually those are non-interactive
1186 return self._isatty(self.fout)
1187 return self._isatty(self.fout)
1187
1188
1188 return i
1189 return i
1189
1190
1190 def _readline(self, prompt=''):
1191 def _readline(self, prompt=''):
1191 if self._isatty(self.fin):
1192 if self._isatty(self.fin):
1192 try:
1193 try:
1193 # magically add command line editing support, where
1194 # magically add command line editing support, where
1194 # available
1195 # available
1195 import readline
1196 import readline
1196 # force demandimport to really load the module
1197 # force demandimport to really load the module
1197 readline.read_history_file
1198 readline.read_history_file
1198 # windows sometimes raises something other than ImportError
1199 # windows sometimes raises something other than ImportError
1199 except Exception:
1200 except Exception:
1200 pass
1201 pass
1201
1202
1202 # call write() so output goes through subclassed implementation
1203 # call write() so output goes through subclassed implementation
1203 # e.g. color extension on Windows
1204 # e.g. color extension on Windows
1204 self.write(prompt, prompt=True)
1205 self.write(prompt, prompt=True)
1205
1206
1206 # instead of trying to emulate raw_input, swap (self.fin,
1207 # instead of trying to emulate raw_input, swap (self.fin,
1207 # self.fout) with (sys.stdin, sys.stdout)
1208 # self.fout) with (sys.stdin, sys.stdout)
1208 oldin = sys.stdin
1209 oldin = sys.stdin
1209 oldout = sys.stdout
1210 oldout = sys.stdout
1210 sys.stdin = self.fin
1211 sys.stdin = self.fin
1211 sys.stdout = self.fout
1212 sys.stdout = self.fout
1212 # prompt ' ' must exist; otherwise readline may delete entire line
1213 # prompt ' ' must exist; otherwise readline may delete entire line
1213 # - http://bugs.python.org/issue12833
1214 # - http://bugs.python.org/issue12833
1214 with self.timeblockedsection('stdio'):
1215 with self.timeblockedsection('stdio'):
1215 line = raw_input(' ')
1216 line = raw_input(' ')
1216 sys.stdin = oldin
1217 sys.stdin = oldin
1217 sys.stdout = oldout
1218 sys.stdout = oldout
1218
1219
1219 # When stdin is in binary mode on Windows, it can cause
1220 # When stdin is in binary mode on Windows, it can cause
1220 # raw_input() to emit an extra trailing carriage return
1221 # raw_input() to emit an extra trailing carriage return
1221 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1222 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1222 line = line[:-1]
1223 line = line[:-1]
1223 return line
1224 return line
1224
1225
1225 def prompt(self, msg, default="y"):
1226 def prompt(self, msg, default="y"):
1226 """Prompt user with msg, read response.
1227 """Prompt user with msg, read response.
1227 If ui is not interactive, the default is returned.
1228 If ui is not interactive, the default is returned.
1228 """
1229 """
1229 if not self.interactive():
1230 if not self.interactive():
1230 self.write(msg, ' ', default or '', "\n")
1231 self.write(msg, ' ', default or '', "\n")
1231 return default
1232 return default
1232 try:
1233 try:
1233 r = self._readline(self.label(msg, 'ui.prompt'))
1234 r = self._readline(self.label(msg, 'ui.prompt'))
1234 if not r:
1235 if not r:
1235 r = default
1236 r = default
1236 if self.configbool('ui', 'promptecho'):
1237 if self.configbool('ui', 'promptecho'):
1237 self.write(r, "\n")
1238 self.write(r, "\n")
1238 return r
1239 return r
1239 except EOFError:
1240 except EOFError:
1240 raise error.ResponseExpected()
1241 raise error.ResponseExpected()
1241
1242
1242 @staticmethod
1243 @staticmethod
1243 def extractchoices(prompt):
1244 def extractchoices(prompt):
1244 """Extract prompt message and list of choices from specified prompt.
1245 """Extract prompt message and list of choices from specified prompt.
1245
1246
1246 This returns tuple "(message, choices)", and "choices" is the
1247 This returns tuple "(message, choices)", and "choices" is the
1247 list of tuple "(response character, text without &)".
1248 list of tuple "(response character, text without &)".
1248
1249
1249 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1250 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1250 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1251 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1251 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1252 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1252 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1253 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1253 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1254 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1254 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1255 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1255 """
1256 """
1256
1257
1257 # Sadly, the prompt string may have been built with a filename
1258 # Sadly, the prompt string may have been built with a filename
1258 # containing "$$" so let's try to find the first valid-looking
1259 # containing "$$" so let's try to find the first valid-looking
1259 # prompt to start parsing. Sadly, we also can't rely on
1260 # prompt to start parsing. Sadly, we also can't rely on
1260 # choices containing spaces, ASCII, or basically anything
1261 # choices containing spaces, ASCII, or basically anything
1261 # except an ampersand followed by a character.
1262 # except an ampersand followed by a character.
1262 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1263 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1263 msg = m.group(1)
1264 msg = m.group(1)
1264 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1265 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1265 return (msg,
1266 return (msg,
1266 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1267 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1267 for s in choices])
1268 for s in choices])
1268
1269
1269 def promptchoice(self, prompt, default=0):
1270 def promptchoice(self, prompt, default=0):
1270 """Prompt user with a message, read response, and ensure it matches
1271 """Prompt user with a message, read response, and ensure it matches
1271 one of the provided choices. The prompt is formatted as follows:
1272 one of the provided choices. The prompt is formatted as follows:
1272
1273
1273 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1274 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1274
1275
1275 The index of the choice is returned. Responses are case
1276 The index of the choice is returned. Responses are case
1276 insensitive. If ui is not interactive, the default is
1277 insensitive. If ui is not interactive, the default is
1277 returned.
1278 returned.
1278 """
1279 """
1279
1280
1280 msg, choices = self.extractchoices(prompt)
1281 msg, choices = self.extractchoices(prompt)
1281 resps = [r for r, t in choices]
1282 resps = [r for r, t in choices]
1282 while True:
1283 while True:
1283 r = self.prompt(msg, resps[default])
1284 r = self.prompt(msg, resps[default])
1284 if r.lower() in resps:
1285 if r.lower() in resps:
1285 return resps.index(r.lower())
1286 return resps.index(r.lower())
1286 self.write(_("unrecognized response\n"))
1287 self.write(_("unrecognized response\n"))
1287
1288
1288 def getpass(self, prompt=None, default=None):
1289 def getpass(self, prompt=None, default=None):
1289 if not self.interactive():
1290 if not self.interactive():
1290 return default
1291 return default
1291 try:
1292 try:
1292 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1293 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1293 # disable getpass() only if explicitly specified. it's still valid
1294 # disable getpass() only if explicitly specified. it's still valid
1294 # to interact with tty even if fin is not a tty.
1295 # to interact with tty even if fin is not a tty.
1295 with self.timeblockedsection('stdio'):
1296 with self.timeblockedsection('stdio'):
1296 if self.configbool('ui', 'nontty'):
1297 if self.configbool('ui', 'nontty'):
1297 l = self.fin.readline()
1298 l = self.fin.readline()
1298 if not l:
1299 if not l:
1299 raise EOFError
1300 raise EOFError
1300 return l.rstrip('\n')
1301 return l.rstrip('\n')
1301 else:
1302 else:
1302 return getpass.getpass('')
1303 return getpass.getpass('')
1303 except EOFError:
1304 except EOFError:
1304 raise error.ResponseExpected()
1305 raise error.ResponseExpected()
1305 def status(self, *msg, **opts):
1306 def status(self, *msg, **opts):
1306 '''write status message to output (if ui.quiet is False)
1307 '''write status message to output (if ui.quiet is False)
1307
1308
1308 This adds an output label of "ui.status".
1309 This adds an output label of "ui.status".
1309 '''
1310 '''
1310 if not self.quiet:
1311 if not self.quiet:
1311 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1312 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1312 self.write(*msg, **opts)
1313 self.write(*msg, **opts)
1313 def warn(self, *msg, **opts):
1314 def warn(self, *msg, **opts):
1314 '''write warning message to output (stderr)
1315 '''write warning message to output (stderr)
1315
1316
1316 This adds an output label of "ui.warning".
1317 This adds an output label of "ui.warning".
1317 '''
1318 '''
1318 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1319 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1319 self.write_err(*msg, **opts)
1320 self.write_err(*msg, **opts)
1320 def note(self, *msg, **opts):
1321 def note(self, *msg, **opts):
1321 '''write note to output (if ui.verbose is True)
1322 '''write note to output (if ui.verbose is True)
1322
1323
1323 This adds an output label of "ui.note".
1324 This adds an output label of "ui.note".
1324 '''
1325 '''
1325 if self.verbose:
1326 if self.verbose:
1326 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1327 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1327 self.write(*msg, **opts)
1328 self.write(*msg, **opts)
1328 def debug(self, *msg, **opts):
1329 def debug(self, *msg, **opts):
1329 '''write debug message to output (if ui.debugflag is True)
1330 '''write debug message to output (if ui.debugflag is True)
1330
1331
1331 This adds an output label of "ui.debug".
1332 This adds an output label of "ui.debug".
1332 '''
1333 '''
1333 if self.debugflag:
1334 if self.debugflag:
1334 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1335 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1335 self.write(*msg, **opts)
1336 self.write(*msg, **opts)
1336
1337
1337 def edit(self, text, user, extra=None, editform=None, pending=None,
1338 def edit(self, text, user, extra=None, editform=None, pending=None,
1338 repopath=None):
1339 repopath=None):
1339 extra_defaults = {
1340 extra_defaults = {
1340 'prefix': 'editor',
1341 'prefix': 'editor',
1341 'suffix': '.txt',
1342 'suffix': '.txt',
1342 }
1343 }
1343 if extra is not None:
1344 if extra is not None:
1344 extra_defaults.update(extra)
1345 extra_defaults.update(extra)
1345 extra = extra_defaults
1346 extra = extra_defaults
1346
1347
1347 rdir = None
1348 rdir = None
1348 if self.configbool('experimental', 'editortmpinhg'):
1349 if self.configbool('experimental', 'editortmpinhg'):
1349 rdir = repopath
1350 rdir = repopath
1350 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1351 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1351 suffix=extra['suffix'],
1352 suffix=extra['suffix'],
1352 dir=rdir)
1353 dir=rdir)
1353 try:
1354 try:
1354 f = os.fdopen(fd, r'wb')
1355 f = os.fdopen(fd, r'wb')
1355 f.write(util.tonativeeol(text))
1356 f.write(util.tonativeeol(text))
1356 f.close()
1357 f.close()
1357
1358
1358 environ = {'HGUSER': user}
1359 environ = {'HGUSER': user}
1359 if 'transplant_source' in extra:
1360 if 'transplant_source' in extra:
1360 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1361 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1361 for label in ('intermediate-source', 'source', 'rebase_source'):
1362 for label in ('intermediate-source', 'source', 'rebase_source'):
1362 if label in extra:
1363 if label in extra:
1363 environ.update({'HGREVISION': extra[label]})
1364 environ.update({'HGREVISION': extra[label]})
1364 break
1365 break
1365 if editform:
1366 if editform:
1366 environ.update({'HGEDITFORM': editform})
1367 environ.update({'HGEDITFORM': editform})
1367 if pending:
1368 if pending:
1368 environ.update({'HG_PENDING': pending})
1369 environ.update({'HG_PENDING': pending})
1369
1370
1370 editor = self.geteditor()
1371 editor = self.geteditor()
1371
1372
1372 self.system("%s \"%s\"" % (editor, name),
1373 self.system("%s \"%s\"" % (editor, name),
1373 environ=environ,
1374 environ=environ,
1374 onerr=error.Abort, errprefix=_("edit failed"),
1375 onerr=error.Abort, errprefix=_("edit failed"),
1375 blockedtag='editor')
1376 blockedtag='editor')
1376
1377
1377 f = open(name, r'rb')
1378 f = open(name, r'rb')
1378 t = util.fromnativeeol(f.read())
1379 t = util.fromnativeeol(f.read())
1379 f.close()
1380 f.close()
1380 finally:
1381 finally:
1381 os.unlink(name)
1382 os.unlink(name)
1382
1383
1383 return t
1384 return t
1384
1385
1385 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1386 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1386 blockedtag=None):
1387 blockedtag=None):
1387 '''execute shell command with appropriate output stream. command
1388 '''execute shell command with appropriate output stream. command
1388 output will be redirected if fout is not stdout.
1389 output will be redirected if fout is not stdout.
1389
1390
1390 if command fails and onerr is None, return status, else raise onerr
1391 if command fails and onerr is None, return status, else raise onerr
1391 object as exception.
1392 object as exception.
1392 '''
1393 '''
1393 if blockedtag is None:
1394 if blockedtag is None:
1394 # Long cmds tend to be because of an absolute path on cmd. Keep
1395 # Long cmds tend to be because of an absolute path on cmd. Keep
1395 # the tail end instead
1396 # the tail end instead
1396 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1397 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1397 blockedtag = 'unknown_system_' + cmdsuffix
1398 blockedtag = 'unknown_system_' + cmdsuffix
1398 out = self.fout
1399 out = self.fout
1399 if any(s[1] for s in self._bufferstates):
1400 if any(s[1] for s in self._bufferstates):
1400 out = self
1401 out = self
1401 with self.timeblockedsection(blockedtag):
1402 with self.timeblockedsection(blockedtag):
1402 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1403 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1403 if rc and onerr:
1404 if rc and onerr:
1404 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1405 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1405 util.explainexit(rc)[0])
1406 util.explainexit(rc)[0])
1406 if errprefix:
1407 if errprefix:
1407 errmsg = '%s: %s' % (errprefix, errmsg)
1408 errmsg = '%s: %s' % (errprefix, errmsg)
1408 raise onerr(errmsg)
1409 raise onerr(errmsg)
1409 return rc
1410 return rc
1410
1411
1411 def _runsystem(self, cmd, environ, cwd, out):
1412 def _runsystem(self, cmd, environ, cwd, out):
1412 """actually execute the given shell command (can be overridden by
1413 """actually execute the given shell command (can be overridden by
1413 extensions like chg)"""
1414 extensions like chg)"""
1414 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1415 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1415
1416
1416 def traceback(self, exc=None, force=False):
1417 def traceback(self, exc=None, force=False):
1417 '''print exception traceback if traceback printing enabled or forced.
1418 '''print exception traceback if traceback printing enabled or forced.
1418 only to call in exception handler. returns true if traceback
1419 only to call in exception handler. returns true if traceback
1419 printed.'''
1420 printed.'''
1420 if self.tracebackflag or force:
1421 if self.tracebackflag or force:
1421 if exc is None:
1422 if exc is None:
1422 exc = sys.exc_info()
1423 exc = sys.exc_info()
1423 cause = getattr(exc[1], 'cause', None)
1424 cause = getattr(exc[1], 'cause', None)
1424
1425
1425 if cause is not None:
1426 if cause is not None:
1426 causetb = traceback.format_tb(cause[2])
1427 causetb = traceback.format_tb(cause[2])
1427 exctb = traceback.format_tb(exc[2])
1428 exctb = traceback.format_tb(exc[2])
1428 exconly = traceback.format_exception_only(cause[0], cause[1])
1429 exconly = traceback.format_exception_only(cause[0], cause[1])
1429
1430
1430 # exclude frame where 'exc' was chained and rethrown from exctb
1431 # exclude frame where 'exc' was chained and rethrown from exctb
1431 self.write_err('Traceback (most recent call last):\n',
1432 self.write_err('Traceback (most recent call last):\n',
1432 ''.join(exctb[:-1]),
1433 ''.join(exctb[:-1]),
1433 ''.join(causetb),
1434 ''.join(causetb),
1434 ''.join(exconly))
1435 ''.join(exconly))
1435 else:
1436 else:
1436 output = traceback.format_exception(exc[0], exc[1], exc[2])
1437 output = traceback.format_exception(exc[0], exc[1], exc[2])
1437 data = r''.join(output)
1438 data = r''.join(output)
1438 if pycompat.ispy3:
1439 if pycompat.ispy3:
1439 enc = pycompat.sysstr(encoding.encoding)
1440 enc = pycompat.sysstr(encoding.encoding)
1440 data = data.encode(enc, errors=r'replace')
1441 data = data.encode(enc, errors=r'replace')
1441 self.write_err(data)
1442 self.write_err(data)
1442 return self.tracebackflag or force
1443 return self.tracebackflag or force
1443
1444
1444 def geteditor(self):
1445 def geteditor(self):
1445 '''return editor to use'''
1446 '''return editor to use'''
1446 if pycompat.sysplatform == 'plan9':
1447 if pycompat.sysplatform == 'plan9':
1447 # vi is the MIPS instruction simulator on Plan 9. We
1448 # vi is the MIPS instruction simulator on Plan 9. We
1448 # instead default to E to plumb commit messages to
1449 # instead default to E to plumb commit messages to
1449 # avoid confusion.
1450 # avoid confusion.
1450 editor = 'E'
1451 editor = 'E'
1451 else:
1452 else:
1452 editor = 'vi'
1453 editor = 'vi'
1453 return (encoding.environ.get("HGEDITOR") or
1454 return (encoding.environ.get("HGEDITOR") or
1454 self.config("ui", "editor", editor))
1455 self.config("ui", "editor", editor))
1455
1456
1456 @util.propertycache
1457 @util.propertycache
1457 def _progbar(self):
1458 def _progbar(self):
1458 """setup the progbar singleton to the ui object"""
1459 """setup the progbar singleton to the ui object"""
1459 if (self.quiet or self.debugflag
1460 if (self.quiet or self.debugflag
1460 or self.configbool('progress', 'disable', False)
1461 or self.configbool('progress', 'disable', False)
1461 or not progress.shouldprint(self)):
1462 or not progress.shouldprint(self)):
1462 return None
1463 return None
1463 return getprogbar(self)
1464 return getprogbar(self)
1464
1465
1465 def _progclear(self):
1466 def _progclear(self):
1466 """clear progress bar output if any. use it before any output"""
1467 """clear progress bar output if any. use it before any output"""
1467 if '_progbar' not in vars(self): # nothing loaded yet
1468 if '_progbar' not in vars(self): # nothing loaded yet
1468 return
1469 return
1469 if self._progbar is not None and self._progbar.printed:
1470 if self._progbar is not None and self._progbar.printed:
1470 self._progbar.clear()
1471 self._progbar.clear()
1471
1472
1472 def progress(self, topic, pos, item="", unit="", total=None):
1473 def progress(self, topic, pos, item="", unit="", total=None):
1473 '''show a progress message
1474 '''show a progress message
1474
1475
1475 By default a textual progress bar will be displayed if an operation
1476 By default a textual progress bar will be displayed if an operation
1476 takes too long. 'topic' is the current operation, 'item' is a
1477 takes too long. 'topic' is the current operation, 'item' is a
1477 non-numeric marker of the current position (i.e. the currently
1478 non-numeric marker of the current position (i.e. the currently
1478 in-process file), 'pos' is the current numeric position (i.e.
1479 in-process file), 'pos' is the current numeric position (i.e.
1479 revision, bytes, etc.), unit is a corresponding unit label,
1480 revision, bytes, etc.), unit is a corresponding unit label,
1480 and total is the highest expected pos.
1481 and total is the highest expected pos.
1481
1482
1482 Multiple nested topics may be active at a time.
1483 Multiple nested topics may be active at a time.
1483
1484
1484 All topics should be marked closed by setting pos to None at
1485 All topics should be marked closed by setting pos to None at
1485 termination.
1486 termination.
1486 '''
1487 '''
1487 if self._progbar is not None:
1488 if self._progbar is not None:
1488 self._progbar.progress(topic, pos, item=item, unit=unit,
1489 self._progbar.progress(topic, pos, item=item, unit=unit,
1489 total=total)
1490 total=total)
1490 if pos is None or not self.configbool('progress', 'debug'):
1491 if pos is None or not self.configbool('progress', 'debug'):
1491 return
1492 return
1492
1493
1493 if unit:
1494 if unit:
1494 unit = ' ' + unit
1495 unit = ' ' + unit
1495 if item:
1496 if item:
1496 item = ' ' + item
1497 item = ' ' + item
1497
1498
1498 if total:
1499 if total:
1499 pct = 100.0 * pos / total
1500 pct = 100.0 * pos / total
1500 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1501 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1501 % (topic, item, pos, total, unit, pct))
1502 % (topic, item, pos, total, unit, pct))
1502 else:
1503 else:
1503 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1504 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1504
1505
1505 def log(self, service, *msg, **opts):
1506 def log(self, service, *msg, **opts):
1506 '''hook for logging facility extensions
1507 '''hook for logging facility extensions
1507
1508
1508 service should be a readily-identifiable subsystem, which will
1509 service should be a readily-identifiable subsystem, which will
1509 allow filtering.
1510 allow filtering.
1510
1511
1511 *msg should be a newline-terminated format string to log, and
1512 *msg should be a newline-terminated format string to log, and
1512 then any values to %-format into that format string.
1513 then any values to %-format into that format string.
1513
1514
1514 **opts currently has no defined meanings.
1515 **opts currently has no defined meanings.
1515 '''
1516 '''
1516
1517
1517 def label(self, msg, label):
1518 def label(self, msg, label):
1518 '''style msg based on supplied label
1519 '''style msg based on supplied label
1519
1520
1520 If some color mode is enabled, this will add the necessary control
1521 If some color mode is enabled, this will add the necessary control
1521 characters to apply such color. In addition, 'debug' color mode adds
1522 characters to apply such color. In addition, 'debug' color mode adds
1522 markup showing which label affects a piece of text.
1523 markup showing which label affects a piece of text.
1523
1524
1524 ui.write(s, 'label') is equivalent to
1525 ui.write(s, 'label') is equivalent to
1525 ui.write(ui.label(s, 'label')).
1526 ui.write(ui.label(s, 'label')).
1526 '''
1527 '''
1527 if self._colormode is not None:
1528 if self._colormode is not None:
1528 return color.colorlabel(self, msg, label)
1529 return color.colorlabel(self, msg, label)
1529 return msg
1530 return msg
1530
1531
1531 def develwarn(self, msg, stacklevel=1, config=None):
1532 def develwarn(self, msg, stacklevel=1, config=None):
1532 """issue a developer warning message
1533 """issue a developer warning message
1533
1534
1534 Use 'stacklevel' to report the offender some layers further up in the
1535 Use 'stacklevel' to report the offender some layers further up in the
1535 stack.
1536 stack.
1536 """
1537 """
1537 if not self.configbool('devel', 'all-warnings'):
1538 if not self.configbool('devel', 'all-warnings'):
1538 if config is not None and not self.configbool('devel', config):
1539 if config is not None and not self.configbool('devel', config):
1539 return
1540 return
1540 msg = 'devel-warn: ' + msg
1541 msg = 'devel-warn: ' + msg
1541 stacklevel += 1 # get in develwarn
1542 stacklevel += 1 # get in develwarn
1542 if self.tracebackflag:
1543 if self.tracebackflag:
1543 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1544 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1544 self.log('develwarn', '%s at:\n%s' %
1545 self.log('develwarn', '%s at:\n%s' %
1545 (msg, ''.join(util.getstackframes(stacklevel))))
1546 (msg, ''.join(util.getstackframes(stacklevel))))
1546 else:
1547 else:
1547 curframe = inspect.currentframe()
1548 curframe = inspect.currentframe()
1548 calframe = inspect.getouterframes(curframe, 2)
1549 calframe = inspect.getouterframes(curframe, 2)
1549 self.write_err('%s at: %s:%s (%s)\n'
1550 self.write_err('%s at: %s:%s (%s)\n'
1550 % ((msg,) + calframe[stacklevel][1:4]))
1551 % ((msg,) + calframe[stacklevel][1:4]))
1551 self.log('develwarn', '%s at: %s:%s (%s)\n',
1552 self.log('develwarn', '%s at: %s:%s (%s)\n',
1552 msg, *calframe[stacklevel][1:4])
1553 msg, *calframe[stacklevel][1:4])
1553 curframe = calframe = None # avoid cycles
1554 curframe = calframe = None # avoid cycles
1554
1555
1555 def deprecwarn(self, msg, version):
1556 def deprecwarn(self, msg, version):
1556 """issue a deprecation warning
1557 """issue a deprecation warning
1557
1558
1558 - msg: message explaining what is deprecated and how to upgrade,
1559 - msg: message explaining what is deprecated and how to upgrade,
1559 - version: last version where the API will be supported,
1560 - version: last version where the API will be supported,
1560 """
1561 """
1561 if not (self.configbool('devel', 'all-warnings')
1562 if not (self.configbool('devel', 'all-warnings')
1562 or self.configbool('devel', 'deprec-warn')):
1563 or self.configbool('devel', 'deprec-warn')):
1563 return
1564 return
1564 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1565 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1565 " update your code.)") % version
1566 " update your code.)") % version
1566 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1567 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1567
1568
1568 def exportableenviron(self):
1569 def exportableenviron(self):
1569 """The environment variables that are safe to export, e.g. through
1570 """The environment variables that are safe to export, e.g. through
1570 hgweb.
1571 hgweb.
1571 """
1572 """
1572 return self._exportableenviron
1573 return self._exportableenviron
1573
1574
1574 @contextlib.contextmanager
1575 @contextlib.contextmanager
1575 def configoverride(self, overrides, source=""):
1576 def configoverride(self, overrides, source=""):
1576 """Context manager for temporary config overrides
1577 """Context manager for temporary config overrides
1577 `overrides` must be a dict of the following structure:
1578 `overrides` must be a dict of the following structure:
1578 {(section, name) : value}"""
1579 {(section, name) : value}"""
1579 backups = {}
1580 backups = {}
1580 try:
1581 try:
1581 for (section, name), value in overrides.items():
1582 for (section, name), value in overrides.items():
1582 backups[(section, name)] = self.backupconfig(section, name)
1583 backups[(section, name)] = self.backupconfig(section, name)
1583 self.setconfig(section, name, value, source)
1584 self.setconfig(section, name, value, source)
1584 yield
1585 yield
1585 finally:
1586 finally:
1586 for __, backup in backups.items():
1587 for __, backup in backups.items():
1587 self.restoreconfig(backup)
1588 self.restoreconfig(backup)
1588 # just restoring ui.quiet config to the previous value is not enough
1589 # just restoring ui.quiet config to the previous value is not enough
1589 # as it does not update ui.quiet class member
1590 # as it does not update ui.quiet class member
1590 if ('ui', 'quiet') in overrides:
1591 if ('ui', 'quiet') in overrides:
1591 self.fixconfig(section='ui')
1592 self.fixconfig(section='ui')
1592
1593
1593 class paths(dict):
1594 class paths(dict):
1594 """Represents a collection of paths and their configs.
1595 """Represents a collection of paths and their configs.
1595
1596
1596 Data is initially derived from ui instances and the config files they have
1597 Data is initially derived from ui instances and the config files they have
1597 loaded.
1598 loaded.
1598 """
1599 """
1599 def __init__(self, ui):
1600 def __init__(self, ui):
1600 dict.__init__(self)
1601 dict.__init__(self)
1601
1602
1602 for name, loc in ui.configitems('paths', ignoresub=True):
1603 for name, loc in ui.configitems('paths', ignoresub=True):
1603 # No location is the same as not existing.
1604 # No location is the same as not existing.
1604 if not loc:
1605 if not loc:
1605 continue
1606 continue
1606 loc, sub = ui.configsuboptions('paths', name)
1607 loc, sub = ui.configsuboptions('paths', name)
1607 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1608 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1608
1609
1609 def getpath(self, name, default=None):
1610 def getpath(self, name, default=None):
1610 """Return a ``path`` from a string, falling back to default.
1611 """Return a ``path`` from a string, falling back to default.
1611
1612
1612 ``name`` can be a named path or locations. Locations are filesystem
1613 ``name`` can be a named path or locations. Locations are filesystem
1613 paths or URIs.
1614 paths or URIs.
1614
1615
1615 Returns None if ``name`` is not a registered path, a URI, or a local
1616 Returns None if ``name`` is not a registered path, a URI, or a local
1616 path to a repo.
1617 path to a repo.
1617 """
1618 """
1618 # Only fall back to default if no path was requested.
1619 # Only fall back to default if no path was requested.
1619 if name is None:
1620 if name is None:
1620 if not default:
1621 if not default:
1621 default = ()
1622 default = ()
1622 elif not isinstance(default, (tuple, list)):
1623 elif not isinstance(default, (tuple, list)):
1623 default = (default,)
1624 default = (default,)
1624 for k in default:
1625 for k in default:
1625 try:
1626 try:
1626 return self[k]
1627 return self[k]
1627 except KeyError:
1628 except KeyError:
1628 continue
1629 continue
1629 return None
1630 return None
1630
1631
1631 # Most likely empty string.
1632 # Most likely empty string.
1632 # This may need to raise in the future.
1633 # This may need to raise in the future.
1633 if not name:
1634 if not name:
1634 return None
1635 return None
1635
1636
1636 try:
1637 try:
1637 return self[name]
1638 return self[name]
1638 except KeyError:
1639 except KeyError:
1639 # Try to resolve as a local path or URI.
1640 # Try to resolve as a local path or URI.
1640 try:
1641 try:
1641 # We don't pass sub-options in, so no need to pass ui instance.
1642 # We don't pass sub-options in, so no need to pass ui instance.
1642 return path(None, None, rawloc=name)
1643 return path(None, None, rawloc=name)
1643 except ValueError:
1644 except ValueError:
1644 raise error.RepoError(_('repository %s does not exist') %
1645 raise error.RepoError(_('repository %s does not exist') %
1645 name)
1646 name)
1646
1647
1647 _pathsuboptions = {}
1648 _pathsuboptions = {}
1648
1649
1649 def pathsuboption(option, attr):
1650 def pathsuboption(option, attr):
1650 """Decorator used to declare a path sub-option.
1651 """Decorator used to declare a path sub-option.
1651
1652
1652 Arguments are the sub-option name and the attribute it should set on
1653 Arguments are the sub-option name and the attribute it should set on
1653 ``path`` instances.
1654 ``path`` instances.
1654
1655
1655 The decorated function will receive as arguments a ``ui`` instance,
1656 The decorated function will receive as arguments a ``ui`` instance,
1656 ``path`` instance, and the string value of this option from the config.
1657 ``path`` instance, and the string value of this option from the config.
1657 The function should return the value that will be set on the ``path``
1658 The function should return the value that will be set on the ``path``
1658 instance.
1659 instance.
1659
1660
1660 This decorator can be used to perform additional verification of
1661 This decorator can be used to perform additional verification of
1661 sub-options and to change the type of sub-options.
1662 sub-options and to change the type of sub-options.
1662 """
1663 """
1663 def register(func):
1664 def register(func):
1664 _pathsuboptions[option] = (attr, func)
1665 _pathsuboptions[option] = (attr, func)
1665 return func
1666 return func
1666 return register
1667 return register
1667
1668
1668 @pathsuboption('pushurl', 'pushloc')
1669 @pathsuboption('pushurl', 'pushloc')
1669 def pushurlpathoption(ui, path, value):
1670 def pushurlpathoption(ui, path, value):
1670 u = util.url(value)
1671 u = util.url(value)
1671 # Actually require a URL.
1672 # Actually require a URL.
1672 if not u.scheme:
1673 if not u.scheme:
1673 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1674 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1674 return None
1675 return None
1675
1676
1676 # Don't support the #foo syntax in the push URL to declare branch to
1677 # Don't support the #foo syntax in the push URL to declare branch to
1677 # push.
1678 # push.
1678 if u.fragment:
1679 if u.fragment:
1679 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1680 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1680 'ignoring)\n') % path.name)
1681 'ignoring)\n') % path.name)
1681 u.fragment = None
1682 u.fragment = None
1682
1683
1683 return str(u)
1684 return str(u)
1684
1685
1685 @pathsuboption('pushrev', 'pushrev')
1686 @pathsuboption('pushrev', 'pushrev')
1686 def pushrevpathoption(ui, path, value):
1687 def pushrevpathoption(ui, path, value):
1687 return value
1688 return value
1688
1689
1689 class path(object):
1690 class path(object):
1690 """Represents an individual path and its configuration."""
1691 """Represents an individual path and its configuration."""
1691
1692
1692 def __init__(self, ui, name, rawloc=None, suboptions=None):
1693 def __init__(self, ui, name, rawloc=None, suboptions=None):
1693 """Construct a path from its config options.
1694 """Construct a path from its config options.
1694
1695
1695 ``ui`` is the ``ui`` instance the path is coming from.
1696 ``ui`` is the ``ui`` instance the path is coming from.
1696 ``name`` is the symbolic name of the path.
1697 ``name`` is the symbolic name of the path.
1697 ``rawloc`` is the raw location, as defined in the config.
1698 ``rawloc`` is the raw location, as defined in the config.
1698 ``pushloc`` is the raw locations pushes should be made to.
1699 ``pushloc`` is the raw locations pushes should be made to.
1699
1700
1700 If ``name`` is not defined, we require that the location be a) a local
1701 If ``name`` is not defined, we require that the location be a) a local
1701 filesystem path with a .hg directory or b) a URL. If not,
1702 filesystem path with a .hg directory or b) a URL. If not,
1702 ``ValueError`` is raised.
1703 ``ValueError`` is raised.
1703 """
1704 """
1704 if not rawloc:
1705 if not rawloc:
1705 raise ValueError('rawloc must be defined')
1706 raise ValueError('rawloc must be defined')
1706
1707
1707 # Locations may define branches via syntax <base>#<branch>.
1708 # Locations may define branches via syntax <base>#<branch>.
1708 u = util.url(rawloc)
1709 u = util.url(rawloc)
1709 branch = None
1710 branch = None
1710 if u.fragment:
1711 if u.fragment:
1711 branch = u.fragment
1712 branch = u.fragment
1712 u.fragment = None
1713 u.fragment = None
1713
1714
1714 self.url = u
1715 self.url = u
1715 self.branch = branch
1716 self.branch = branch
1716
1717
1717 self.name = name
1718 self.name = name
1718 self.rawloc = rawloc
1719 self.rawloc = rawloc
1719 self.loc = '%s' % u
1720 self.loc = '%s' % u
1720
1721
1721 # When given a raw location but not a symbolic name, validate the
1722 # When given a raw location but not a symbolic name, validate the
1722 # location is valid.
1723 # location is valid.
1723 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1724 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1724 raise ValueError('location is not a URL or path to a local '
1725 raise ValueError('location is not a URL or path to a local '
1725 'repo: %s' % rawloc)
1726 'repo: %s' % rawloc)
1726
1727
1727 suboptions = suboptions or {}
1728 suboptions = suboptions or {}
1728
1729
1729 # Now process the sub-options. If a sub-option is registered, its
1730 # Now process the sub-options. If a sub-option is registered, its
1730 # attribute will always be present. The value will be None if there
1731 # attribute will always be present. The value will be None if there
1731 # was no valid sub-option.
1732 # was no valid sub-option.
1732 for suboption, (attr, func) in _pathsuboptions.iteritems():
1733 for suboption, (attr, func) in _pathsuboptions.iteritems():
1733 if suboption not in suboptions:
1734 if suboption not in suboptions:
1734 setattr(self, attr, None)
1735 setattr(self, attr, None)
1735 continue
1736 continue
1736
1737
1737 value = func(ui, self, suboptions[suboption])
1738 value = func(ui, self, suboptions[suboption])
1738 setattr(self, attr, value)
1739 setattr(self, attr, value)
1739
1740
1740 def _isvalidlocalpath(self, path):
1741 def _isvalidlocalpath(self, path):
1741 """Returns True if the given path is a potentially valid repository.
1742 """Returns True if the given path is a potentially valid repository.
1742 This is its own function so that extensions can change the definition of
1743 This is its own function so that extensions can change the definition of
1743 'valid' in this case (like when pulling from a git repo into a hg
1744 'valid' in this case (like when pulling from a git repo into a hg
1744 one)."""
1745 one)."""
1745 return os.path.isdir(os.path.join(path, '.hg'))
1746 return os.path.isdir(os.path.join(path, '.hg'))
1746
1747
1747 @property
1748 @property
1748 def suboptions(self):
1749 def suboptions(self):
1749 """Return sub-options and their values for this path.
1750 """Return sub-options and their values for this path.
1750
1751
1751 This is intended to be used for presentation purposes.
1752 This is intended to be used for presentation purposes.
1752 """
1753 """
1753 d = {}
1754 d = {}
1754 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1755 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1755 value = getattr(self, attr)
1756 value = getattr(self, attr)
1756 if value is not None:
1757 if value is not None:
1757 d[subopt] = value
1758 d[subopt] = value
1758 return d
1759 return d
1759
1760
1760 # we instantiate one globally shared progress bar to avoid
1761 # we instantiate one globally shared progress bar to avoid
1761 # competing progress bars when multiple UI objects get created
1762 # competing progress bars when multiple UI objects get created
1762 _progresssingleton = None
1763 _progresssingleton = None
1763
1764
1764 def getprogbar(ui):
1765 def getprogbar(ui):
1765 global _progresssingleton
1766 global _progresssingleton
1766 if _progresssingleton is None:
1767 if _progresssingleton is None:
1767 # passing 'ui' object to the singleton is fishy,
1768 # passing 'ui' object to the singleton is fishy,
1768 # this is how the extension used to work but feel free to rework it.
1769 # this is how the extension used to work but feel free to rework it.
1769 _progresssingleton = progress.progbar(ui)
1770 _progresssingleton = progress.progbar(ui)
1770 return _progresssingleton
1771 return _progresssingleton
@@ -1,180 +1,213
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 hg: parse error at $TESTTMP/.hg/hgrc:1: novaluekey (glob)
10 hg: parse error at $TESTTMP/.hg/hgrc:1: novaluekey (glob)
11 [255]
11 [255]
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 hg: parse error at $TESTTMP/.hg/hgrc:1: =nokeyvalue (glob)
19 hg: parse error at $TESTTMP/.hg/hgrc:1: =nokeyvalue (glob)
20 [255]
20 [255]
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 hg: parse error at $TESTTMP/.hg/hgrc:1: key=value (glob)
28 hg: parse error at $TESTTMP/.hg/hgrc:1: key=value (glob)
29 unexpected leading whitespace
29 unexpected leading whitespace
30 [255]
30 [255]
31
31
32 $ cat > .hg/hgrc << EOF
32 $ cat > .hg/hgrc << EOF
33 > [section]
33 > [section]
34 > key=value
34 > key=value
35 > EOF
35 > EOF
36 $ hg showconfig
36 $ hg showconfig
37 hg: parse error at $TESTTMP/.hg/hgrc:1: [section] (glob)
37 hg: parse error at $TESTTMP/.hg/hgrc:1: [section] (glob)
38 unexpected leading whitespace
38 unexpected leading whitespace
39 [255]
39 [255]
40
40
41 Reset hgrc
41 Reset hgrc
42
42
43 $ echo > .hg/hgrc
43 $ echo > .hg/hgrc
44
44
45 Test case sensitive configuration
45 Test case sensitive configuration
46
46
47 $ cat <<EOF >> $HGRCPATH
47 $ cat <<EOF >> $HGRCPATH
48 > [Section]
48 > [Section]
49 > KeY = Case Sensitive
49 > KeY = Case Sensitive
50 > key = lower case
50 > key = lower case
51 > EOF
51 > EOF
52
52
53 $ hg showconfig Section
53 $ hg showconfig Section
54 Section.KeY=Case Sensitive
54 Section.KeY=Case Sensitive
55 Section.key=lower case
55 Section.key=lower case
56
56
57 $ hg showconfig Section -Tjson
57 $ hg showconfig Section -Tjson
58 [
58 [
59 {
59 {
60 "name": "Section.KeY",
60 "name": "Section.KeY",
61 "source": "*.hgrc:*", (glob)
61 "source": "*.hgrc:*", (glob)
62 "value": "Case Sensitive"
62 "value": "Case Sensitive"
63 },
63 },
64 {
64 {
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 "name": "Section.KeY",
73 "name": "Section.KeY",
74 "source": "*.hgrc:*", (glob)
74 "source": "*.hgrc:*", (glob)
75 "value": "Case Sensitive"
75 "value": "Case Sensitive"
76 }
76 }
77 ]
77 ]
78 $ hg showconfig -Tjson | tail -7
78 $ hg showconfig -Tjson | tail -7
79 },
79 },
80 {
80 {
81 "name": "*", (glob)
81 "name": "*", (glob)
82 "source": "*", (glob)
82 "source": "*", (glob)
83 "value": "*" (glob)
83 "value": "*" (glob)
84 }
84 }
85 ]
85 ]
86
86
87 Test empty config source:
87 Test empty config source:
88
88
89 $ cat <<EOF > emptysource.py
89 $ cat <<EOF > emptysource.py
90 > def reposetup(ui, repo):
90 > def reposetup(ui, repo):
91 > ui.setconfig('empty', 'source', 'value')
91 > ui.setconfig('empty', 'source', 'value')
92 > EOF
92 > EOF
93 $ cp .hg/hgrc .hg/hgrc.orig
93 $ cp .hg/hgrc .hg/hgrc.orig
94 $ cat <<EOF >> .hg/hgrc
94 $ cat <<EOF >> .hg/hgrc
95 > [extensions]
95 > [extensions]
96 > emptysource = `pwd`/emptysource.py
96 > emptysource = `pwd`/emptysource.py
97 > EOF
97 > EOF
98
98
99 $ hg config --debug empty.source
99 $ hg config --debug empty.source
100 read config from: * (glob)
100 read config from: * (glob)
101 none: value
101 none: value
102 $ hg config empty.source -Tjson
102 $ hg config empty.source -Tjson
103 [
103 [
104 {
104 {
105 "name": "empty.source",
105 "name": "empty.source",
106 "source": "",
106 "source": "",
107 "value": "value"
107 "value": "value"
108 }
108 }
109 ]
109 ]
110
110
111 $ cp .hg/hgrc.orig .hg/hgrc
111 $ cp .hg/hgrc.orig .hg/hgrc
112
112
113 Test "%unset"
113 Test "%unset"
114
114
115 $ cat >> $HGRCPATH <<EOF
115 $ cat >> $HGRCPATH <<EOF
116 > [unsettest]
116 > [unsettest]
117 > local-hgrcpath = should be unset (HGRCPATH)
117 > local-hgrcpath = should be unset (HGRCPATH)
118 > %unset local-hgrcpath
118 > %unset local-hgrcpath
119 >
119 >
120 > global = should be unset (HGRCPATH)
120 > global = should be unset (HGRCPATH)
121 >
121 >
122 > both = should be unset (HGRCPATH)
122 > both = should be unset (HGRCPATH)
123 >
123 >
124 > set-after-unset = should be unset (HGRCPATH)
124 > set-after-unset = should be unset (HGRCPATH)
125 > EOF
125 > EOF
126
126
127 $ cat >> .hg/hgrc <<EOF
127 $ cat >> .hg/hgrc <<EOF
128 > [unsettest]
128 > [unsettest]
129 > local-hgrc = should be unset (.hg/hgrc)
129 > local-hgrc = should be unset (.hg/hgrc)
130 > %unset local-hgrc
130 > %unset local-hgrc
131 >
131 >
132 > %unset global
132 > %unset global
133 >
133 >
134 > both = should be unset (.hg/hgrc)
134 > both = should be unset (.hg/hgrc)
135 > %unset both
135 > %unset both
136 >
136 >
137 > set-after-unset = should be unset (.hg/hgrc)
137 > set-after-unset = should be unset (.hg/hgrc)
138 > %unset set-after-unset
138 > %unset set-after-unset
139 > set-after-unset = should be set (.hg/hgrc)
139 > set-after-unset = should be set (.hg/hgrc)
140 > EOF
140 > EOF
141
141
142 $ hg showconfig unsettest
142 $ hg showconfig unsettest
143 unsettest.set-after-unset=should be set (.hg/hgrc)
143 unsettest.set-after-unset=should be set (.hg/hgrc)
144
144
145 Test exit code when no config matches
145 Test exit code when no config matches
146
146
147 $ hg config Section.idontexist
147 $ hg config Section.idontexist
148 [1]
148 [1]
149
149
150 sub-options in [paths] aren't expanded
150 sub-options in [paths] aren't expanded
151
151
152 $ cat > .hg/hgrc << EOF
152 $ cat > .hg/hgrc << EOF
153 > [paths]
153 > [paths]
154 > foo = ~/foo
154 > foo = ~/foo
155 > foo:suboption = ~/foo
155 > foo:suboption = ~/foo
156 > EOF
156 > EOF
157
157
158 $ hg showconfig paths
158 $ hg showconfig paths
159 paths.foo:suboption=~/foo
159 paths.foo:suboption=~/foo
160 paths.foo=$TESTTMP/foo
160 paths.foo=$TESTTMP/foo
161
161
162 edit failure
162 edit failure
163
163
164 $ HGEDITOR=false hg config --edit
164 $ HGEDITOR=false hg config --edit
165 abort: edit failed: false exited with status 1
165 abort: edit failed: false exited with status 1
166 [255]
166 [255]
167
167
168 config affected by environment variables
168 config affected by environment variables
169
169
170 $ EDITOR=e1 VISUAL=e2 hg config --debug | grep 'ui\.editor'
170 $ EDITOR=e1 VISUAL=e2 hg config --debug | grep 'ui\.editor'
171 $VISUAL: ui.editor=e2
171 $VISUAL: ui.editor=e2
172
172
173 $ VISUAL=e2 hg config --debug --config ui.editor=e3 | grep 'ui\.editor'
173 $ VISUAL=e2 hg config --debug --config ui.editor=e3 | grep 'ui\.editor'
174 --config: ui.editor=e3
174 --config: ui.editor=e3
175
175
176 $ PAGER=p1 hg config --debug | grep 'pager\.pager'
176 $ PAGER=p1 hg config --debug | grep 'pager\.pager'
177 $PAGER: pager.pager=p1
177 $PAGER: pager.pager=p1
178
178
179 $ PAGER=p1 hg config --debug --config pager.pager=p2 | grep 'pager\.pager'
179 $ PAGER=p1 hg config --debug --config pager.pager=p2 | grep 'pager\.pager'
180 --config: pager.pager=p2
180 --config: pager.pager=p2
181
182 verify that aliases are evaluated as well
183
184 $ hg init aliastest
185 $ cd aliastest
186 $ cat > .hg/hgrc << EOF
187 > [ui]
188 > user = repo user
189 > EOF
190 $ touch index
191 $ unset HGUSER
192 $ hg ci -Am test
193 adding index
194 $ hg log --template '{author}\n'
195 repo user
196 $ cd ..
197
198 alias has lower priority
199
200 $ hg init aliaspriority
201 $ cd aliaspriority
202 $ cat > .hg/hgrc << EOF
203 > [ui]
204 > user = alias user
205 > username = repo user
206 > EOF
207 $ touch index
208 $ unset HGUSER
209 $ hg ci -Am test
210 adding index
211 $ hg log --template '{author}\n'
212 repo user
213 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now