##// END OF EJS Templates
config: move message about leading spaces in config to config.py...
Martin von Zweigbergk -
r46362:0883413e default
parent child Browse files
Show More
@@ -1,311 +1,314 b''
1 1 # config.py - configuration parsing for Mercurial
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12
13 13 from .i18n import _
14 14 from .pycompat import getattr
15 15 from . import (
16 16 encoding,
17 17 error,
18 18 pycompat,
19 19 util,
20 20 )
21 21
22 22
23 23 class config(object):
24 24 def __init__(self, data=None):
25 25 self._data = {}
26 26 self._unset = []
27 27 if data:
28 28 for k in data._data:
29 29 self._data[k] = data[k].copy()
30 30 self._source = data._source.copy()
31 31 else:
32 32 self._source = util.cowdict()
33 33
34 34 def copy(self):
35 35 return config(self)
36 36
37 37 def __contains__(self, section):
38 38 return section in self._data
39 39
40 40 def hasitem(self, section, item):
41 41 return item in self._data.get(section, {})
42 42
43 43 def __getitem__(self, section):
44 44 return self._data.get(section, {})
45 45
46 46 def __iter__(self):
47 47 for d in self.sections():
48 48 yield d
49 49
50 50 def update(self, src):
51 51 self._source = self._source.preparewrite()
52 52 for s, n in src._unset:
53 53 ds = self._data.get(s, None)
54 54 if ds is not None and n in ds:
55 55 self._data[s] = ds.preparewrite()
56 56 del self._data[s][n]
57 57 del self._source[(s, n)]
58 58 for s in src:
59 59 ds = self._data.get(s, None)
60 60 if ds:
61 61 self._data[s] = ds.preparewrite()
62 62 else:
63 63 self._data[s] = util.cowsortdict()
64 64 self._data[s].update(src._data[s])
65 65 self._source.update(src._source)
66 66
67 67 def get(self, section, item, default=None):
68 68 return self._data.get(section, {}).get(item, default)
69 69
70 70 def backup(self, section, item):
71 71 """return a tuple allowing restore to reinstall a previous value
72 72
73 73 The main reason we need it is because it handles the "no data" case.
74 74 """
75 75 try:
76 76 value = self._data[section][item]
77 77 source = self.source(section, item)
78 78 return (section, item, value, source)
79 79 except KeyError:
80 80 return (section, item)
81 81
82 82 def source(self, section, item):
83 83 return self._source.get((section, item), b"")
84 84
85 85 def sections(self):
86 86 return sorted(self._data.keys())
87 87
88 88 def items(self, section):
89 89 return list(pycompat.iteritems(self._data.get(section, {})))
90 90
91 91 def set(self, section, item, value, source=b""):
92 92 if pycompat.ispy3:
93 93 assert not isinstance(
94 94 section, str
95 95 ), b'config section may not be unicode strings on Python 3'
96 96 assert not isinstance(
97 97 item, str
98 98 ), b'config item may not be unicode strings on Python 3'
99 99 assert not isinstance(
100 100 value, str
101 101 ), b'config values may not be unicode strings on Python 3'
102 102 if section not in self:
103 103 self._data[section] = util.cowsortdict()
104 104 else:
105 105 self._data[section] = self._data[section].preparewrite()
106 106 self._data[section][item] = value
107 107 if source:
108 108 self._source = self._source.preparewrite()
109 109 self._source[(section, item)] = source
110 110
111 111 def restore(self, data):
112 112 """restore data returned by self.backup"""
113 113 self._source = self._source.preparewrite()
114 114 if len(data) == 4:
115 115 # restore old data
116 116 section, item, value, source = data
117 117 self._data[section] = self._data[section].preparewrite()
118 118 self._data[section][item] = value
119 119 self._source[(section, item)] = source
120 120 else:
121 121 # no data before, remove everything
122 122 section, item = data
123 123 if section in self._data:
124 124 self._data[section].pop(item, None)
125 125 self._source.pop((section, item), None)
126 126
127 127 def parse(self, src, data, sections=None, remap=None, include=None):
128 128 sectionre = util.re.compile(br'\[([^\[]+)\]')
129 129 itemre = util.re.compile(br'([^=\s][^=]*?)\s*=\s*(.*\S|)')
130 130 contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$')
131 131 emptyre = util.re.compile(br'(;|#|\s*$)')
132 132 commentre = util.re.compile(br'(;|#)')
133 133 unsetre = util.re.compile(br'%unset\s+(\S+)')
134 134 includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$')
135 135 section = b""
136 136 item = None
137 137 line = 0
138 138 cont = False
139 139
140 140 if remap:
141 141 section = remap.get(section, section)
142 142
143 143 for l in data.splitlines(True):
144 144 line += 1
145 145 if line == 1 and l.startswith(b'\xef\xbb\xbf'):
146 146 # Someone set us up the BOM
147 147 l = l[3:]
148 148 if cont:
149 149 if commentre.match(l):
150 150 continue
151 151 m = contre.match(l)
152 152 if m:
153 153 if sections and section not in sections:
154 154 continue
155 155 v = self.get(section, item) + b"\n" + m.group(1)
156 156 self.set(section, item, v, b"%s:%d" % (src, line))
157 157 continue
158 158 item = None
159 159 cont = False
160 160 m = includere.match(l)
161 161
162 162 if m and include:
163 163 expanded = util.expandpath(m.group(1))
164 164 try:
165 165 include(expanded, remap=remap, sections=sections)
166 166 except IOError as inst:
167 167 if inst.errno != errno.ENOENT:
168 168 raise error.ParseError(
169 169 _(b"cannot include %s (%s)")
170 170 % (expanded, encoding.strtolocal(inst.strerror)),
171 171 b"%s:%d" % (src, line),
172 172 )
173 173 continue
174 174 if emptyre.match(l):
175 175 continue
176 176 m = sectionre.match(l)
177 177 if m:
178 178 section = m.group(1)
179 179 if remap:
180 180 section = remap.get(section, section)
181 181 if section not in self:
182 182 self._data[section] = util.cowsortdict()
183 183 continue
184 184 m = itemre.match(l)
185 185 if m:
186 186 item = m.group(1)
187 187 cont = True
188 188 if sections and section not in sections:
189 189 continue
190 190 self.set(section, item, m.group(2), b"%s:%d" % (src, line))
191 191 continue
192 192 m = unsetre.match(l)
193 193 if m:
194 194 name = m.group(1)
195 195 if sections and section not in sections:
196 196 continue
197 197 if self.get(section, name) is not None:
198 198 self._data[section] = self._data[section].preparewrite()
199 199 del self._data[section][name]
200 200 self._unset.append((section, name))
201 201 continue
202 202
203 raise error.ParseError(l.rstrip(), (b"%s:%d" % (src, line)))
203 message = l.rstrip()
204 if l.startswith(b' '):
205 message = b"unexpected leading whitespace: %s" % message
206 raise error.ParseError(message, (b"%s:%d" % (src, line)))
204 207
205 208 def read(self, path, fp=None, sections=None, remap=None):
206 209 if not fp:
207 210 fp = util.posixfile(path, b'rb')
208 211 assert getattr(fp, 'mode', 'rb') == 'rb', (
209 212 b'config files must be opened in binary mode, got fp=%r mode=%r'
210 213 % (fp, fp.mode,)
211 214 )
212 215
213 216 dir = os.path.dirname(path)
214 217
215 218 def include(rel, remap, sections):
216 219 abs = os.path.normpath(os.path.join(dir, rel))
217 220 self.read(abs, remap=remap, sections=sections)
218 221
219 222 self.parse(
220 223 path, fp.read(), sections=sections, remap=remap, include=include
221 224 )
222 225
223 226
224 227 def parselist(value):
225 228 """parse a configuration value as a list of comma/space separated strings
226 229
227 230 >>> parselist(b'this,is "a small" ,test')
228 231 ['this', 'is', 'a small', 'test']
229 232 """
230 233
231 234 def _parse_plain(parts, s, offset):
232 235 whitespace = False
233 236 while offset < len(s) and (
234 237 s[offset : offset + 1].isspace() or s[offset : offset + 1] == b','
235 238 ):
236 239 whitespace = True
237 240 offset += 1
238 241 if offset >= len(s):
239 242 return None, parts, offset
240 243 if whitespace:
241 244 parts.append(b'')
242 245 if s[offset : offset + 1] == b'"' and not parts[-1]:
243 246 return _parse_quote, parts, offset + 1
244 247 elif s[offset : offset + 1] == b'"' and parts[-1][-1:] == b'\\':
245 248 parts[-1] = parts[-1][:-1] + s[offset : offset + 1]
246 249 return _parse_plain, parts, offset + 1
247 250 parts[-1] += s[offset : offset + 1]
248 251 return _parse_plain, parts, offset + 1
249 252
250 253 def _parse_quote(parts, s, offset):
251 254 if offset < len(s) and s[offset : offset + 1] == b'"': # ""
252 255 parts.append(b'')
253 256 offset += 1
254 257 while offset < len(s) and (
255 258 s[offset : offset + 1].isspace()
256 259 or s[offset : offset + 1] == b','
257 260 ):
258 261 offset += 1
259 262 return _parse_plain, parts, offset
260 263
261 264 while offset < len(s) and s[offset : offset + 1] != b'"':
262 265 if (
263 266 s[offset : offset + 1] == b'\\'
264 267 and offset + 1 < len(s)
265 268 and s[offset + 1 : offset + 2] == b'"'
266 269 ):
267 270 offset += 1
268 271 parts[-1] += b'"'
269 272 else:
270 273 parts[-1] += s[offset : offset + 1]
271 274 offset += 1
272 275
273 276 if offset >= len(s):
274 277 real_parts = _configlist(parts[-1])
275 278 if not real_parts:
276 279 parts[-1] = b'"'
277 280 else:
278 281 real_parts[0] = b'"' + real_parts[0]
279 282 parts = parts[:-1]
280 283 parts.extend(real_parts)
281 284 return None, parts, offset
282 285
283 286 offset += 1
284 287 while offset < len(s) and s[offset : offset + 1] in [b' ', b',']:
285 288 offset += 1
286 289
287 290 if offset < len(s):
288 291 if offset + 1 == len(s) and s[offset : offset + 1] == b'"':
289 292 parts[-1] += b'"'
290 293 offset += 1
291 294 else:
292 295 parts.append(b'')
293 296 else:
294 297 return None, parts, offset
295 298
296 299 return _parse_plain, parts, offset
297 300
298 301 def _configlist(s):
299 302 s = s.rstrip(b' ,')
300 303 if not s:
301 304 return []
302 305 parser, parts, offset = _parse_plain, [b''], 0
303 306 while parser:
304 307 parser, parts, offset = parser(parts, s, offset)
305 308 return parts
306 309
307 310 if value is not None and isinstance(value, bytes):
308 311 result = _configlist(value.lstrip(b' ,\n'))
309 312 else:
310 313 result = value
311 314 return result or []
@@ -1,1337 +1,1335 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import difflib
11 11 import errno
12 12 import getopt
13 13 import io
14 14 import os
15 15 import pdb
16 16 import re
17 17 import signal
18 18 import sys
19 19 import traceback
20 20
21 21
22 22 from .i18n import _
23 23 from .pycompat import getattr
24 24
25 25 from hgdemandimport import tracing
26 26
27 27 from . import (
28 28 cmdutil,
29 29 color,
30 30 commands,
31 31 demandimport,
32 32 encoding,
33 33 error,
34 34 extensions,
35 35 fancyopts,
36 36 help,
37 37 hg,
38 38 hook,
39 39 profiling,
40 40 pycompat,
41 41 rcutil,
42 42 registrar,
43 43 scmutil,
44 44 ui as uimod,
45 45 util,
46 46 )
47 47
48 48 from .utils import (
49 49 procutil,
50 50 stringutil,
51 51 )
52 52
53 53
54 54 class request(object):
55 55 def __init__(
56 56 self,
57 57 args,
58 58 ui=None,
59 59 repo=None,
60 60 fin=None,
61 61 fout=None,
62 62 ferr=None,
63 63 fmsg=None,
64 64 prereposetups=None,
65 65 ):
66 66 self.args = args
67 67 self.ui = ui
68 68 self.repo = repo
69 69
70 70 # input/output/error streams
71 71 self.fin = fin
72 72 self.fout = fout
73 73 self.ferr = ferr
74 74 # separate stream for status/error messages
75 75 self.fmsg = fmsg
76 76
77 77 # remember options pre-parsed by _earlyparseopts()
78 78 self.earlyoptions = {}
79 79
80 80 # reposetups which run before extensions, useful for chg to pre-fill
81 81 # low-level repo state (for example, changelog) before extensions.
82 82 self.prereposetups = prereposetups or []
83 83
84 84 # store the parsed and canonical command
85 85 self.canonical_command = None
86 86
87 87 def _runexithandlers(self):
88 88 exc = None
89 89 handlers = self.ui._exithandlers
90 90 try:
91 91 while handlers:
92 92 func, args, kwargs = handlers.pop()
93 93 try:
94 94 func(*args, **kwargs)
95 95 except: # re-raises below
96 96 if exc is None:
97 97 exc = sys.exc_info()[1]
98 98 self.ui.warnnoi18n(b'error in exit handlers:\n')
99 99 self.ui.traceback(force=True)
100 100 finally:
101 101 if exc is not None:
102 102 raise exc
103 103
104 104
105 105 def run():
106 106 """run the command in sys.argv"""
107 107 try:
108 108 initstdio()
109 109 with tracing.log('parse args into request'):
110 110 req = request(pycompat.sysargv[1:])
111 111 err = None
112 112 try:
113 113 status = dispatch(req)
114 114 except error.StdioError as e:
115 115 err = e
116 116 status = -1
117 117
118 118 # In all cases we try to flush stdio streams.
119 119 if util.safehasattr(req.ui, b'fout'):
120 120 assert req.ui is not None # help pytype
121 121 assert req.ui.fout is not None # help pytype
122 122 try:
123 123 req.ui.fout.flush()
124 124 except IOError as e:
125 125 err = e
126 126 status = -1
127 127
128 128 if util.safehasattr(req.ui, b'ferr'):
129 129 assert req.ui is not None # help pytype
130 130 assert req.ui.ferr is not None # help pytype
131 131 try:
132 132 if err is not None and err.errno != errno.EPIPE:
133 133 req.ui.ferr.write(
134 134 b'abort: %s\n' % encoding.strtolocal(err.strerror)
135 135 )
136 136 req.ui.ferr.flush()
137 137 # There's not much we can do about an I/O error here. So (possibly)
138 138 # change the status code and move on.
139 139 except IOError:
140 140 status = -1
141 141
142 142 _silencestdio()
143 143 except KeyboardInterrupt:
144 144 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
145 145 # be printed to console to avoid another IOError/KeyboardInterrupt.
146 146 status = -1
147 147 sys.exit(status & 255)
148 148
149 149
150 150 if pycompat.ispy3:
151 151
152 152 def initstdio():
153 153 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
154 154 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
155 155 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
156 156 # instances, which write to the underlying stdio file descriptor in binary
157 157 # mode. ui.write() uses \n for line endings and no line ending normalization
158 158 # is attempted through this interface. This "just works," even if the system
159 159 # preferred line ending is not \n.
160 160 #
161 161 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
162 162 # and sys.stderr. They will inherit the line ending normalization settings,
163 163 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
164 164 # "just work," here we change the sys.* streams to disable line ending
165 165 # normalization, ensuring compatibility with our ui type.
166 166
167 167 # write_through is new in Python 3.7.
168 168 kwargs = {
169 169 "newline": "\n",
170 170 "line_buffering": sys.stdout.line_buffering,
171 171 }
172 172 if util.safehasattr(sys.stdout, "write_through"):
173 173 kwargs["write_through"] = sys.stdout.write_through
174 174 sys.stdout = io.TextIOWrapper(
175 175 sys.stdout.buffer, sys.stdout.encoding, sys.stdout.errors, **kwargs
176 176 )
177 177
178 178 kwargs = {
179 179 "newline": "\n",
180 180 "line_buffering": sys.stderr.line_buffering,
181 181 }
182 182 if util.safehasattr(sys.stderr, "write_through"):
183 183 kwargs["write_through"] = sys.stderr.write_through
184 184 sys.stderr = io.TextIOWrapper(
185 185 sys.stderr.buffer, sys.stderr.encoding, sys.stderr.errors, **kwargs
186 186 )
187 187
188 188 # No write_through on read-only stream.
189 189 sys.stdin = io.TextIOWrapper(
190 190 sys.stdin.buffer,
191 191 sys.stdin.encoding,
192 192 sys.stdin.errors,
193 193 # None is universal newlines mode.
194 194 newline=None,
195 195 line_buffering=sys.stdin.line_buffering,
196 196 )
197 197
198 198 def _silencestdio():
199 199 for fp in (sys.stdout, sys.stderr):
200 200 # Check if the file is okay
201 201 try:
202 202 fp.flush()
203 203 continue
204 204 except IOError:
205 205 pass
206 206 # Otherwise mark it as closed to silence "Exception ignored in"
207 207 # message emitted by the interpreter finalizer. Be careful to
208 208 # not close procutil.stdout, which may be a fdopen-ed file object
209 209 # and its close() actually closes the underlying file descriptor.
210 210 try:
211 211 fp.close()
212 212 except IOError:
213 213 pass
214 214
215 215
216 216 else:
217 217
218 218 def initstdio():
219 219 for fp in (sys.stdin, sys.stdout, sys.stderr):
220 220 procutil.setbinary(fp)
221 221
222 222 def _silencestdio():
223 223 pass
224 224
225 225
226 226 def _getsimilar(symbols, value):
227 227 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
228 228 # The cutoff for similarity here is pretty arbitrary. It should
229 229 # probably be investigated and tweaked.
230 230 return [s for s in symbols if sim(s) > 0.6]
231 231
232 232
233 233 def _reportsimilar(write, similar):
234 234 if len(similar) == 1:
235 235 write(_(b"(did you mean %s?)\n") % similar[0])
236 236 elif similar:
237 237 ss = b", ".join(sorted(similar))
238 238 write(_(b"(did you mean one of %s?)\n") % ss)
239 239
240 240
241 241 def _formatparse(write, inst):
242 242 similar = []
243 243 if isinstance(inst, error.UnknownIdentifier):
244 244 # make sure to check fileset first, as revset can invoke fileset
245 245 similar = _getsimilar(inst.symbols, inst.function)
246 246 if inst.location is not None:
247 247 write(
248 248 _(b"hg: parse error at %s: %s\n")
249 249 % (pycompat.bytestr(inst.location), inst.message)
250 250 )
251 if inst.message.startswith(b' '):
252 write(_(b"unexpected leading whitespace\n"))
253 251 else:
254 252 write(_(b"hg: parse error: %s\n") % inst.message)
255 253 _reportsimilar(write, similar)
256 254 if inst.hint:
257 255 write(_(b"(%s)\n") % inst.hint)
258 256
259 257
260 258 def _formatargs(args):
261 259 return b' '.join(procutil.shellquote(a) for a in args)
262 260
263 261
264 262 def dispatch(req):
265 263 """run the command specified in req.args; returns an integer status code"""
266 264 with tracing.log('dispatch.dispatch'):
267 265 if req.ferr:
268 266 ferr = req.ferr
269 267 elif req.ui:
270 268 ferr = req.ui.ferr
271 269 else:
272 270 ferr = procutil.stderr
273 271
274 272 try:
275 273 if not req.ui:
276 274 req.ui = uimod.ui.load()
277 275 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
278 276 if req.earlyoptions[b'traceback']:
279 277 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
280 278
281 279 # set ui streams from the request
282 280 if req.fin:
283 281 req.ui.fin = req.fin
284 282 if req.fout:
285 283 req.ui.fout = req.fout
286 284 if req.ferr:
287 285 req.ui.ferr = req.ferr
288 286 if req.fmsg:
289 287 req.ui.fmsg = req.fmsg
290 288 except error.Abort as inst:
291 289 ferr.write(_(b"abort: %s\n") % inst.message)
292 290 if inst.hint:
293 291 ferr.write(_(b"(%s)\n") % inst.hint)
294 292 return -1
295 293 except error.ParseError as inst:
296 294 _formatparse(ferr.write, inst)
297 295 return -1
298 296
299 297 msg = _formatargs(req.args)
300 298 starttime = util.timer()
301 299 ret = 1 # default of Python exit code on unhandled exception
302 300 try:
303 301 ret = _runcatch(req) or 0
304 302 except error.ProgrammingError as inst:
305 303 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
306 304 if inst.hint:
307 305 req.ui.error(_(b'** (%s)\n') % inst.hint)
308 306 raise
309 307 except KeyboardInterrupt as inst:
310 308 try:
311 309 if isinstance(inst, error.SignalInterrupt):
312 310 msg = _(b"killed!\n")
313 311 else:
314 312 msg = _(b"interrupted!\n")
315 313 req.ui.error(msg)
316 314 except error.SignalInterrupt:
317 315 # maybe pager would quit without consuming all the output, and
318 316 # SIGPIPE was raised. we cannot print anything in this case.
319 317 pass
320 318 except IOError as inst:
321 319 if inst.errno != errno.EPIPE:
322 320 raise
323 321 ret = -1
324 322 finally:
325 323 duration = util.timer() - starttime
326 324 req.ui.flush() # record blocked times
327 325 if req.ui.logblockedtimes:
328 326 req.ui._blockedtimes[b'command_duration'] = duration * 1000
329 327 req.ui.log(
330 328 b'uiblocked',
331 329 b'ui blocked ms\n',
332 330 **pycompat.strkwargs(req.ui._blockedtimes)
333 331 )
334 332 return_code = ret & 255
335 333 req.ui.log(
336 334 b"commandfinish",
337 335 b"%s exited %d after %0.2f seconds\n",
338 336 msg,
339 337 return_code,
340 338 duration,
341 339 return_code=return_code,
342 340 duration=duration,
343 341 canonical_command=req.canonical_command,
344 342 )
345 343 try:
346 344 req._runexithandlers()
347 345 except: # exiting, so no re-raises
348 346 ret = ret or -1
349 347 # do flush again since ui.log() and exit handlers may write to ui
350 348 req.ui.flush()
351 349 return ret
352 350
353 351
354 352 def _runcatch(req):
355 353 with tracing.log('dispatch._runcatch'):
356 354
357 355 def catchterm(*args):
358 356 raise error.SignalInterrupt
359 357
360 358 ui = req.ui
361 359 try:
362 360 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
363 361 num = getattr(signal, name, None)
364 362 if num:
365 363 signal.signal(num, catchterm)
366 364 except ValueError:
367 365 pass # happens if called in a thread
368 366
369 367 def _runcatchfunc():
370 368 realcmd = None
371 369 try:
372 370 cmdargs = fancyopts.fancyopts(
373 371 req.args[:], commands.globalopts, {}
374 372 )
375 373 cmd = cmdargs[0]
376 374 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
377 375 realcmd = aliases[0]
378 376 except (
379 377 error.UnknownCommand,
380 378 error.AmbiguousCommand,
381 379 IndexError,
382 380 getopt.GetoptError,
383 381 ):
384 382 # Don't handle this here. We know the command is
385 383 # invalid, but all we're worried about for now is that
386 384 # it's not a command that server operators expect to
387 385 # be safe to offer to users in a sandbox.
388 386 pass
389 387 if realcmd == b'serve' and b'--stdio' in cmdargs:
390 388 # We want to constrain 'hg serve --stdio' instances pretty
391 389 # closely, as many shared-ssh access tools want to grant
392 390 # access to run *only* 'hg -R $repo serve --stdio'. We
393 391 # restrict to exactly that set of arguments, and prohibit
394 392 # any repo name that starts with '--' to prevent
395 393 # shenanigans wherein a user does something like pass
396 394 # --debugger or --config=ui.debugger=1 as a repo
397 395 # name. This used to actually run the debugger.
398 396 if (
399 397 len(req.args) != 4
400 398 or req.args[0] != b'-R'
401 399 or req.args[1].startswith(b'--')
402 400 or req.args[2] != b'serve'
403 401 or req.args[3] != b'--stdio'
404 402 ):
405 403 raise error.Abort(
406 404 _(b'potentially unsafe serve --stdio invocation: %s')
407 405 % (stringutil.pprint(req.args),)
408 406 )
409 407
410 408 try:
411 409 debugger = b'pdb'
412 410 debugtrace = {b'pdb': pdb.set_trace}
413 411 debugmortem = {b'pdb': pdb.post_mortem}
414 412
415 413 # read --config before doing anything else
416 414 # (e.g. to change trust settings for reading .hg/hgrc)
417 415 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
418 416
419 417 if req.repo:
420 418 # copy configs that were passed on the cmdline (--config) to
421 419 # the repo ui
422 420 for sec, name, val in cfgs:
423 421 req.repo.ui.setconfig(
424 422 sec, name, val, source=b'--config'
425 423 )
426 424
427 425 # developer config: ui.debugger
428 426 debugger = ui.config(b"ui", b"debugger")
429 427 debugmod = pdb
430 428 if not debugger or ui.plain():
431 429 # if we are in HGPLAIN mode, then disable custom debugging
432 430 debugger = b'pdb'
433 431 elif req.earlyoptions[b'debugger']:
434 432 # This import can be slow for fancy debuggers, so only
435 433 # do it when absolutely necessary, i.e. when actual
436 434 # debugging has been requested
437 435 with demandimport.deactivated():
438 436 try:
439 437 debugmod = __import__(debugger)
440 438 except ImportError:
441 439 pass # Leave debugmod = pdb
442 440
443 441 debugtrace[debugger] = debugmod.set_trace
444 442 debugmortem[debugger] = debugmod.post_mortem
445 443
446 444 # enter the debugger before command execution
447 445 if req.earlyoptions[b'debugger']:
448 446 ui.warn(
449 447 _(
450 448 b"entering debugger - "
451 449 b"type c to continue starting hg or h for help\n"
452 450 )
453 451 )
454 452
455 453 if (
456 454 debugger != b'pdb'
457 455 and debugtrace[debugger] == debugtrace[b'pdb']
458 456 ):
459 457 ui.warn(
460 458 _(
461 459 b"%s debugger specified "
462 460 b"but its module was not found\n"
463 461 )
464 462 % debugger
465 463 )
466 464 with demandimport.deactivated():
467 465 debugtrace[debugger]()
468 466 try:
469 467 return _dispatch(req)
470 468 finally:
471 469 ui.flush()
472 470 except: # re-raises
473 471 # enter the debugger when we hit an exception
474 472 if req.earlyoptions[b'debugger']:
475 473 traceback.print_exc()
476 474 debugmortem[debugger](sys.exc_info()[2])
477 475 raise
478 476
479 477 return _callcatch(ui, _runcatchfunc)
480 478
481 479
482 480 def _callcatch(ui, func):
483 481 """like scmutil.callcatch but handles more high-level exceptions about
484 482 config parsing and commands. besides, use handlecommandexception to handle
485 483 uncaught exceptions.
486 484 """
487 485 try:
488 486 return scmutil.callcatch(ui, func)
489 487 except error.AmbiguousCommand as inst:
490 488 ui.warn(
491 489 _(b"hg: command '%s' is ambiguous:\n %s\n")
492 490 % (inst.prefix, b" ".join(inst.matches))
493 491 )
494 492 except error.CommandError as inst:
495 493 if inst.command:
496 494 ui.pager(b'help')
497 495 msgbytes = pycompat.bytestr(inst.message)
498 496 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
499 497 commands.help_(ui, inst.command, full=False, command=True)
500 498 else:
501 499 ui.warn(_(b"hg: %s\n") % inst.message)
502 500 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
503 501 except error.ParseError as inst:
504 502 _formatparse(ui.warn, inst)
505 503 return -1
506 504 except error.UnknownCommand as inst:
507 505 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
508 506 try:
509 507 # check if the command is in a disabled extension
510 508 # (but don't check for extensions themselves)
511 509 formatted = help.formattedhelp(
512 510 ui, commands, inst.command, unknowncmd=True
513 511 )
514 512 ui.warn(nocmdmsg)
515 513 ui.write(formatted)
516 514 except (error.UnknownCommand, error.Abort):
517 515 suggested = False
518 516 if inst.all_commands:
519 517 sim = _getsimilar(inst.all_commands, inst.command)
520 518 if sim:
521 519 ui.warn(nocmdmsg)
522 520 _reportsimilar(ui.warn, sim)
523 521 suggested = True
524 522 if not suggested:
525 523 ui.warn(nocmdmsg)
526 524 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
527 525 except IOError:
528 526 raise
529 527 except KeyboardInterrupt:
530 528 raise
531 529 except: # probably re-raises
532 530 if not handlecommandexception(ui):
533 531 raise
534 532
535 533 return -1
536 534
537 535
538 536 def aliasargs(fn, givenargs):
539 537 args = []
540 538 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
541 539 if not util.safehasattr(fn, b'_origfunc'):
542 540 args = getattr(fn, 'args', args)
543 541 if args:
544 542 cmd = b' '.join(map(procutil.shellquote, args))
545 543
546 544 nums = []
547 545
548 546 def replacer(m):
549 547 num = int(m.group(1)) - 1
550 548 nums.append(num)
551 549 if num < len(givenargs):
552 550 return givenargs[num]
553 551 raise error.Abort(_(b'too few arguments for command alias'))
554 552
555 553 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
556 554 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
557 555 args = pycompat.shlexsplit(cmd)
558 556 return args + givenargs
559 557
560 558
561 559 def aliasinterpolate(name, args, cmd):
562 560 '''interpolate args into cmd for shell aliases
563 561
564 562 This also handles $0, $@ and "$@".
565 563 '''
566 564 # util.interpolate can't deal with "$@" (with quotes) because it's only
567 565 # built to match prefix + patterns.
568 566 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
569 567 replacemap[b'$0'] = name
570 568 replacemap[b'$$'] = b'$'
571 569 replacemap[b'$@'] = b' '.join(args)
572 570 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
573 571 # parameters, separated out into words. Emulate the same behavior here by
574 572 # quoting the arguments individually. POSIX shells will then typically
575 573 # tokenize each argument into exactly one word.
576 574 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
577 575 # escape '\$' for regex
578 576 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
579 577 r = re.compile(regex)
580 578 return r.sub(lambda x: replacemap[x.group()], cmd)
581 579
582 580
583 581 class cmdalias(object):
584 582 def __init__(self, ui, name, definition, cmdtable, source):
585 583 self.name = self.cmd = name
586 584 self.cmdname = b''
587 585 self.definition = definition
588 586 self.fn = None
589 587 self.givenargs = []
590 588 self.opts = []
591 589 self.help = b''
592 590 self.badalias = None
593 591 self.unknowncmd = False
594 592 self.source = source
595 593
596 594 try:
597 595 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
598 596 for alias, e in pycompat.iteritems(cmdtable):
599 597 if e is entry:
600 598 self.cmd = alias
601 599 break
602 600 self.shadows = True
603 601 except error.UnknownCommand:
604 602 self.shadows = False
605 603
606 604 if not self.definition:
607 605 self.badalias = _(b"no definition for alias '%s'") % self.name
608 606 return
609 607
610 608 if self.definition.startswith(b'!'):
611 609 shdef = self.definition[1:]
612 610 self.shell = True
613 611
614 612 def fn(ui, *args):
615 613 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
616 614
617 615 def _checkvar(m):
618 616 if m.groups()[0] == b'$':
619 617 return m.group()
620 618 elif int(m.groups()[0]) <= len(args):
621 619 return m.group()
622 620 else:
623 621 ui.debug(
624 622 b"No argument found for substitution "
625 623 b"of %i variable in alias '%s' definition.\n"
626 624 % (int(m.groups()[0]), self.name)
627 625 )
628 626 return b''
629 627
630 628 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
631 629 cmd = aliasinterpolate(self.name, args, cmd)
632 630 return ui.system(
633 631 cmd, environ=env, blockedtag=b'alias_%s' % self.name
634 632 )
635 633
636 634 self.fn = fn
637 635 self.alias = True
638 636 self._populatehelp(ui, name, shdef, self.fn)
639 637 return
640 638
641 639 try:
642 640 args = pycompat.shlexsplit(self.definition)
643 641 except ValueError as inst:
644 642 self.badalias = _(b"error in definition for alias '%s': %s") % (
645 643 self.name,
646 644 stringutil.forcebytestr(inst),
647 645 )
648 646 return
649 647 earlyopts, args = _earlysplitopts(args)
650 648 if earlyopts:
651 649 self.badalias = _(
652 650 b"error in definition for alias '%s': %s may "
653 651 b"only be given on the command line"
654 652 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
655 653 return
656 654 self.cmdname = cmd = args.pop(0)
657 655 self.givenargs = args
658 656
659 657 try:
660 658 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
661 659 if len(tableentry) > 2:
662 660 self.fn, self.opts, cmdhelp = tableentry
663 661 else:
664 662 self.fn, self.opts = tableentry
665 663 cmdhelp = None
666 664
667 665 self.alias = True
668 666 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
669 667
670 668 except error.UnknownCommand:
671 669 self.badalias = _(
672 670 b"alias '%s' resolves to unknown command '%s'"
673 671 ) % (self.name, cmd,)
674 672 self.unknowncmd = True
675 673 except error.AmbiguousCommand:
676 674 self.badalias = _(
677 675 b"alias '%s' resolves to ambiguous command '%s'"
678 676 ) % (self.name, cmd,)
679 677
680 678 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
681 679 # confine strings to be passed to i18n.gettext()
682 680 cfg = {}
683 681 for k in (b'doc', b'help', b'category'):
684 682 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
685 683 if v is None:
686 684 continue
687 685 if not encoding.isasciistr(v):
688 686 self.badalias = _(
689 687 b"non-ASCII character in alias definition '%s:%s'"
690 688 ) % (name, k)
691 689 return
692 690 cfg[k] = v
693 691
694 692 self.help = cfg.get(b'help', defaulthelp or b'')
695 693 if self.help and self.help.startswith(b"hg " + cmd):
696 694 # drop prefix in old-style help lines so hg shows the alias
697 695 self.help = self.help[4 + len(cmd) :]
698 696
699 697 self.owndoc = b'doc' in cfg
700 698 doc = cfg.get(b'doc', pycompat.getdoc(fn))
701 699 if doc is not None:
702 700 doc = pycompat.sysstr(doc)
703 701 self.__doc__ = doc
704 702
705 703 self.helpcategory = cfg.get(
706 704 b'category', registrar.command.CATEGORY_NONE
707 705 )
708 706
709 707 @property
710 708 def args(self):
711 709 args = pycompat.maplist(util.expandpath, self.givenargs)
712 710 return aliasargs(self.fn, args)
713 711
714 712 def __getattr__(self, name):
715 713 adefaults = {
716 714 'norepo': True,
717 715 'intents': set(),
718 716 'optionalrepo': False,
719 717 'inferrepo': False,
720 718 }
721 719 if name not in adefaults:
722 720 raise AttributeError(name)
723 721 if self.badalias or util.safehasattr(self, b'shell'):
724 722 return adefaults[name]
725 723 return getattr(self.fn, name)
726 724
727 725 def __call__(self, ui, *args, **opts):
728 726 if self.badalias:
729 727 hint = None
730 728 if self.unknowncmd:
731 729 try:
732 730 # check if the command is in a disabled extension
733 731 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
734 732 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
735 733 except error.UnknownCommand:
736 734 pass
737 735 raise error.Abort(self.badalias, hint=hint)
738 736 if self.shadows:
739 737 ui.debug(
740 738 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
741 739 )
742 740
743 741 ui.log(
744 742 b'commandalias',
745 743 b"alias '%s' expands to '%s'\n",
746 744 self.name,
747 745 self.definition,
748 746 )
749 747 if util.safehasattr(self, b'shell'):
750 748 return self.fn(ui, *args, **opts)
751 749 else:
752 750 try:
753 751 return util.checksignature(self.fn)(ui, *args, **opts)
754 752 except error.SignatureError:
755 753 args = b' '.join([self.cmdname] + self.args)
756 754 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
757 755 raise
758 756
759 757
760 758 class lazyaliasentry(object):
761 759 """like a typical command entry (func, opts, help), but is lazy"""
762 760
763 761 def __init__(self, ui, name, definition, cmdtable, source):
764 762 self.ui = ui
765 763 self.name = name
766 764 self.definition = definition
767 765 self.cmdtable = cmdtable.copy()
768 766 self.source = source
769 767 self.alias = True
770 768
771 769 @util.propertycache
772 770 def _aliasdef(self):
773 771 return cmdalias(
774 772 self.ui, self.name, self.definition, self.cmdtable, self.source
775 773 )
776 774
777 775 def __getitem__(self, n):
778 776 aliasdef = self._aliasdef
779 777 if n == 0:
780 778 return aliasdef
781 779 elif n == 1:
782 780 return aliasdef.opts
783 781 elif n == 2:
784 782 return aliasdef.help
785 783 else:
786 784 raise IndexError
787 785
788 786 def __iter__(self):
789 787 for i in range(3):
790 788 yield self[i]
791 789
792 790 def __len__(self):
793 791 return 3
794 792
795 793
796 794 def addaliases(ui, cmdtable):
797 795 # aliases are processed after extensions have been loaded, so they
798 796 # may use extension commands. Aliases can also use other alias definitions,
799 797 # but only if they have been defined prior to the current definition.
800 798 for alias, definition in ui.configitems(b'alias', ignoresub=True):
801 799 try:
802 800 if cmdtable[alias].definition == definition:
803 801 continue
804 802 except (KeyError, AttributeError):
805 803 # definition might not exist or it might not be a cmdalias
806 804 pass
807 805
808 806 source = ui.configsource(b'alias', alias)
809 807 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
810 808 cmdtable[alias] = entry
811 809
812 810
813 811 def _parse(ui, args):
814 812 options = {}
815 813 cmdoptions = {}
816 814
817 815 try:
818 816 args = fancyopts.fancyopts(args, commands.globalopts, options)
819 817 except getopt.GetoptError as inst:
820 818 raise error.CommandError(None, stringutil.forcebytestr(inst))
821 819
822 820 if args:
823 821 cmd, args = args[0], args[1:]
824 822 aliases, entry = cmdutil.findcmd(
825 823 cmd, commands.table, ui.configbool(b"ui", b"strict")
826 824 )
827 825 cmd = aliases[0]
828 826 args = aliasargs(entry[0], args)
829 827 defaults = ui.config(b"defaults", cmd)
830 828 if defaults:
831 829 args = (
832 830 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
833 831 + args
834 832 )
835 833 c = list(entry[1])
836 834 else:
837 835 cmd = None
838 836 c = []
839 837
840 838 # combine global options into local
841 839 for o in commands.globalopts:
842 840 c.append((o[0], o[1], options[o[1]], o[3]))
843 841
844 842 try:
845 843 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
846 844 except getopt.GetoptError as inst:
847 845 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
848 846
849 847 # separate global options back out
850 848 for o in commands.globalopts:
851 849 n = o[1]
852 850 options[n] = cmdoptions[n]
853 851 del cmdoptions[n]
854 852
855 853 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
856 854
857 855
858 856 def _parseconfig(ui, config):
859 857 """parse the --config options from the command line"""
860 858 configs = []
861 859
862 860 for cfg in config:
863 861 try:
864 862 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
865 863 section, name = name.split(b'.', 1)
866 864 if not section or not name:
867 865 raise IndexError
868 866 ui.setconfig(section, name, value, b'--config')
869 867 configs.append((section, name, value))
870 868 except (IndexError, ValueError):
871 869 raise error.Abort(
872 870 _(
873 871 b'malformed --config option: %r '
874 872 b'(use --config section.name=value)'
875 873 )
876 874 % pycompat.bytestr(cfg)
877 875 )
878 876
879 877 return configs
880 878
881 879
882 880 def _earlyparseopts(ui, args):
883 881 options = {}
884 882 fancyopts.fancyopts(
885 883 args,
886 884 commands.globalopts,
887 885 options,
888 886 gnu=not ui.plain(b'strictflags'),
889 887 early=True,
890 888 optaliases={b'repository': [b'repo']},
891 889 )
892 890 return options
893 891
894 892
895 893 def _earlysplitopts(args):
896 894 """Split args into a list of possible early options and remainder args"""
897 895 shortoptions = b'R:'
898 896 # TODO: perhaps 'debugger' should be included
899 897 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
900 898 return fancyopts.earlygetopt(
901 899 args, shortoptions, longoptions, gnu=True, keepsep=True
902 900 )
903 901
904 902
905 903 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
906 904 # run pre-hook, and abort if it fails
907 905 hook.hook(
908 906 lui,
909 907 repo,
910 908 b"pre-%s" % cmd,
911 909 True,
912 910 args=b" ".join(fullargs),
913 911 pats=cmdpats,
914 912 opts=cmdoptions,
915 913 )
916 914 try:
917 915 ret = _runcommand(ui, options, cmd, d)
918 916 # run post-hook, passing command result
919 917 hook.hook(
920 918 lui,
921 919 repo,
922 920 b"post-%s" % cmd,
923 921 False,
924 922 args=b" ".join(fullargs),
925 923 result=ret,
926 924 pats=cmdpats,
927 925 opts=cmdoptions,
928 926 )
929 927 except Exception:
930 928 # run failure hook and re-raise
931 929 hook.hook(
932 930 lui,
933 931 repo,
934 932 b"fail-%s" % cmd,
935 933 False,
936 934 args=b" ".join(fullargs),
937 935 pats=cmdpats,
938 936 opts=cmdoptions,
939 937 )
940 938 raise
941 939 return ret
942 940
943 941
944 942 def _getlocal(ui, rpath, wd=None):
945 943 """Return (path, local ui object) for the given target path.
946 944
947 945 Takes paths in [cwd]/.hg/hgrc into account."
948 946 """
949 947 if wd is None:
950 948 try:
951 949 wd = encoding.getcwd()
952 950 except OSError as e:
953 951 raise error.Abort(
954 952 _(b"error getting current working directory: %s")
955 953 % encoding.strtolocal(e.strerror)
956 954 )
957 955
958 956 path = cmdutil.findrepo(wd) or b""
959 957 if not path:
960 958 lui = ui
961 959 else:
962 960 lui = ui.copy()
963 961 if rcutil.use_repo_hgrc():
964 962 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
965 963
966 964 if rpath:
967 965 path = lui.expandpath(rpath)
968 966 lui = ui.copy()
969 967 if rcutil.use_repo_hgrc():
970 968 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
971 969
972 970 return path, lui
973 971
974 972
975 973 def _checkshellalias(lui, ui, args):
976 974 """Return the function to run the shell alias, if it is required"""
977 975 options = {}
978 976
979 977 try:
980 978 args = fancyopts.fancyopts(args, commands.globalopts, options)
981 979 except getopt.GetoptError:
982 980 return
983 981
984 982 if not args:
985 983 return
986 984
987 985 cmdtable = commands.table
988 986
989 987 cmd = args[0]
990 988 try:
991 989 strict = ui.configbool(b"ui", b"strict")
992 990 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
993 991 except (error.AmbiguousCommand, error.UnknownCommand):
994 992 return
995 993
996 994 cmd = aliases[0]
997 995 fn = entry[0]
998 996
999 997 if cmd and util.safehasattr(fn, b'shell'):
1000 998 # shell alias shouldn't receive early options which are consumed by hg
1001 999 _earlyopts, args = _earlysplitopts(args)
1002 1000 d = lambda: fn(ui, *args[1:])
1003 1001 return lambda: runcommand(
1004 1002 lui, None, cmd, args[:1], ui, options, d, [], {}
1005 1003 )
1006 1004
1007 1005
1008 1006 def _dispatch(req):
1009 1007 args = req.args
1010 1008 ui = req.ui
1011 1009
1012 1010 # check for cwd
1013 1011 cwd = req.earlyoptions[b'cwd']
1014 1012 if cwd:
1015 1013 os.chdir(cwd)
1016 1014
1017 1015 rpath = req.earlyoptions[b'repository']
1018 1016 path, lui = _getlocal(ui, rpath)
1019 1017
1020 1018 uis = {ui, lui}
1021 1019
1022 1020 if req.repo:
1023 1021 uis.add(req.repo.ui)
1024 1022
1025 1023 if (
1026 1024 req.earlyoptions[b'verbose']
1027 1025 or req.earlyoptions[b'debug']
1028 1026 or req.earlyoptions[b'quiet']
1029 1027 ):
1030 1028 for opt in (b'verbose', b'debug', b'quiet'):
1031 1029 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1032 1030 for ui_ in uis:
1033 1031 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1034 1032
1035 1033 if req.earlyoptions[b'profile']:
1036 1034 for ui_ in uis:
1037 1035 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1038 1036
1039 1037 profile = lui.configbool(b'profiling', b'enabled')
1040 1038 with profiling.profile(lui, enabled=profile) as profiler:
1041 1039 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1042 1040 # reposetup
1043 1041 extensions.loadall(lui)
1044 1042 # Propagate any changes to lui.__class__ by extensions
1045 1043 ui.__class__ = lui.__class__
1046 1044
1047 1045 # (uisetup and extsetup are handled in extensions.loadall)
1048 1046
1049 1047 # (reposetup is handled in hg.repository)
1050 1048
1051 1049 addaliases(lui, commands.table)
1052 1050
1053 1051 # All aliases and commands are completely defined, now.
1054 1052 # Check abbreviation/ambiguity of shell alias.
1055 1053 shellaliasfn = _checkshellalias(lui, ui, args)
1056 1054 if shellaliasfn:
1057 1055 # no additional configs will be set, set up the ui instances
1058 1056 for ui_ in uis:
1059 1057 extensions.populateui(ui_)
1060 1058 return shellaliasfn()
1061 1059
1062 1060 # check for fallback encoding
1063 1061 fallback = lui.config(b'ui', b'fallbackencoding')
1064 1062 if fallback:
1065 1063 encoding.fallbackencoding = fallback
1066 1064
1067 1065 fullargs = args
1068 1066 cmd, func, args, options, cmdoptions = _parse(lui, args)
1069 1067
1070 1068 # store the canonical command name in request object for later access
1071 1069 req.canonical_command = cmd
1072 1070
1073 1071 if options[b"config"] != req.earlyoptions[b"config"]:
1074 1072 raise error.Abort(_(b"option --config may not be abbreviated!"))
1075 1073 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1076 1074 raise error.Abort(_(b"option --cwd may not be abbreviated!"))
1077 1075 if options[b"repository"] != req.earlyoptions[b"repository"]:
1078 1076 raise error.Abort(
1079 1077 _(
1080 1078 b"option -R has to be separated from other options (e.g. not "
1081 1079 b"-qR) and --repository may only be abbreviated as --repo!"
1082 1080 )
1083 1081 )
1084 1082 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1085 1083 raise error.Abort(_(b"option --debugger may not be abbreviated!"))
1086 1084 # don't validate --profile/--traceback, which can be enabled from now
1087 1085
1088 1086 if options[b"encoding"]:
1089 1087 encoding.encoding = options[b"encoding"]
1090 1088 if options[b"encodingmode"]:
1091 1089 encoding.encodingmode = options[b"encodingmode"]
1092 1090 if options[b"time"]:
1093 1091
1094 1092 def get_times():
1095 1093 t = os.times()
1096 1094 if t[4] == 0.0:
1097 1095 # Windows leaves this as zero, so use time.perf_counter()
1098 1096 t = (t[0], t[1], t[2], t[3], util.timer())
1099 1097 return t
1100 1098
1101 1099 s = get_times()
1102 1100
1103 1101 def print_time():
1104 1102 t = get_times()
1105 1103 ui.warn(
1106 1104 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1107 1105 % (
1108 1106 t[4] - s[4],
1109 1107 t[0] - s[0],
1110 1108 t[2] - s[2],
1111 1109 t[1] - s[1],
1112 1110 t[3] - s[3],
1113 1111 )
1114 1112 )
1115 1113
1116 1114 ui.atexit(print_time)
1117 1115 if options[b"profile"]:
1118 1116 profiler.start()
1119 1117
1120 1118 # if abbreviated version of this were used, take them in account, now
1121 1119 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1122 1120 for opt in (b'verbose', b'debug', b'quiet'):
1123 1121 if options[opt] == req.earlyoptions[opt]:
1124 1122 continue
1125 1123 val = pycompat.bytestr(bool(options[opt]))
1126 1124 for ui_ in uis:
1127 1125 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1128 1126
1129 1127 if options[b'traceback']:
1130 1128 for ui_ in uis:
1131 1129 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1132 1130
1133 1131 if options[b'noninteractive']:
1134 1132 for ui_ in uis:
1135 1133 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1136 1134
1137 1135 if cmdoptions.get(b'insecure', False):
1138 1136 for ui_ in uis:
1139 1137 ui_.insecureconnections = True
1140 1138
1141 1139 # setup color handling before pager, because setting up pager
1142 1140 # might cause incorrect console information
1143 1141 coloropt = options[b'color']
1144 1142 for ui_ in uis:
1145 1143 if coloropt:
1146 1144 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1147 1145 color.setup(ui_)
1148 1146
1149 1147 if stringutil.parsebool(options[b'pager']):
1150 1148 # ui.pager() expects 'internal-always-' prefix in this case
1151 1149 ui.pager(b'internal-always-' + cmd)
1152 1150 elif options[b'pager'] != b'auto':
1153 1151 for ui_ in uis:
1154 1152 ui_.disablepager()
1155 1153
1156 1154 # configs are fully loaded, set up the ui instances
1157 1155 for ui_ in uis:
1158 1156 extensions.populateui(ui_)
1159 1157
1160 1158 if options[b'version']:
1161 1159 return commands.version_(ui)
1162 1160 if options[b'help']:
1163 1161 return commands.help_(ui, cmd, command=cmd is not None)
1164 1162 elif not cmd:
1165 1163 return commands.help_(ui, b'shortlist')
1166 1164
1167 1165 repo = None
1168 1166 cmdpats = args[:]
1169 1167 assert func is not None # help out pytype
1170 1168 if not func.norepo:
1171 1169 # use the repo from the request only if we don't have -R
1172 1170 if not rpath and not cwd:
1173 1171 repo = req.repo
1174 1172
1175 1173 if repo:
1176 1174 # set the descriptors of the repo ui to those of ui
1177 1175 repo.ui.fin = ui.fin
1178 1176 repo.ui.fout = ui.fout
1179 1177 repo.ui.ferr = ui.ferr
1180 1178 repo.ui.fmsg = ui.fmsg
1181 1179 else:
1182 1180 try:
1183 1181 repo = hg.repository(
1184 1182 ui,
1185 1183 path=path,
1186 1184 presetupfuncs=req.prereposetups,
1187 1185 intents=func.intents,
1188 1186 )
1189 1187 if not repo.local():
1190 1188 raise error.Abort(
1191 1189 _(b"repository '%s' is not local") % path
1192 1190 )
1193 1191 repo.ui.setconfig(
1194 1192 b"bundle", b"mainreporoot", repo.root, b'repo'
1195 1193 )
1196 1194 except error.RequirementError:
1197 1195 raise
1198 1196 except error.RepoError:
1199 1197 if rpath: # invalid -R path
1200 1198 raise
1201 1199 if not func.optionalrepo:
1202 1200 if func.inferrepo and args and not path:
1203 1201 # try to infer -R from command args
1204 1202 repos = pycompat.maplist(cmdutil.findrepo, args)
1205 1203 guess = repos[0]
1206 1204 if guess and repos.count(guess) == len(repos):
1207 1205 req.args = [b'--repository', guess] + fullargs
1208 1206 req.earlyoptions[b'repository'] = guess
1209 1207 return _dispatch(req)
1210 1208 if not path:
1211 1209 raise error.RepoError(
1212 1210 _(
1213 1211 b"no repository found in"
1214 1212 b" '%s' (.hg not found)"
1215 1213 )
1216 1214 % encoding.getcwd()
1217 1215 )
1218 1216 raise
1219 1217 if repo:
1220 1218 ui = repo.ui
1221 1219 if options[b'hidden']:
1222 1220 repo = repo.unfiltered()
1223 1221 args.insert(0, repo)
1224 1222 elif rpath:
1225 1223 ui.warn(_(b"warning: --repository ignored\n"))
1226 1224
1227 1225 msg = _formatargs(fullargs)
1228 1226 ui.log(b"command", b'%s\n', msg)
1229 1227 strcmdopt = pycompat.strkwargs(cmdoptions)
1230 1228 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1231 1229 try:
1232 1230 return runcommand(
1233 1231 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1234 1232 )
1235 1233 finally:
1236 1234 if repo and repo != req.repo:
1237 1235 repo.close()
1238 1236
1239 1237
1240 1238 def _runcommand(ui, options, cmd, cmdfunc):
1241 1239 """Run a command function, possibly with profiling enabled."""
1242 1240 try:
1243 1241 with tracing.log("Running %s command" % cmd):
1244 1242 return cmdfunc()
1245 1243 except error.SignatureError:
1246 1244 raise error.CommandError(cmd, _(b'invalid arguments'))
1247 1245
1248 1246
1249 1247 def _exceptionwarning(ui):
1250 1248 """Produce a warning message for the current active exception"""
1251 1249
1252 1250 # For compatibility checking, we discard the portion of the hg
1253 1251 # version after the + on the assumption that if a "normal
1254 1252 # user" is running a build with a + in it the packager
1255 1253 # probably built from fairly close to a tag and anyone with a
1256 1254 # 'make local' copy of hg (where the version number can be out
1257 1255 # of date) will be clueful enough to notice the implausible
1258 1256 # version number and try updating.
1259 1257 ct = util.versiontuple(n=2)
1260 1258 worst = None, ct, b''
1261 1259 if ui.config(b'ui', b'supportcontact') is None:
1262 1260 for name, mod in extensions.extensions():
1263 1261 # 'testedwith' should be bytes, but not all extensions are ported
1264 1262 # to py3 and we don't want UnicodeException because of that.
1265 1263 testedwith = stringutil.forcebytestr(
1266 1264 getattr(mod, 'testedwith', b'')
1267 1265 )
1268 1266 report = getattr(mod, 'buglink', _(b'the extension author.'))
1269 1267 if not testedwith.strip():
1270 1268 # We found an untested extension. It's likely the culprit.
1271 1269 worst = name, b'unknown', report
1272 1270 break
1273 1271
1274 1272 # Never blame on extensions bundled with Mercurial.
1275 1273 if extensions.ismoduleinternal(mod):
1276 1274 continue
1277 1275
1278 1276 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1279 1277 if ct in tested:
1280 1278 continue
1281 1279
1282 1280 lower = [t for t in tested if t < ct]
1283 1281 nearest = max(lower or tested)
1284 1282 if worst[0] is None or nearest < worst[1]:
1285 1283 worst = name, nearest, report
1286 1284 if worst[0] is not None:
1287 1285 name, testedwith, report = worst
1288 1286 if not isinstance(testedwith, (bytes, str)):
1289 1287 testedwith = b'.'.join(
1290 1288 [stringutil.forcebytestr(c) for c in testedwith]
1291 1289 )
1292 1290 warning = _(
1293 1291 b'** Unknown exception encountered with '
1294 1292 b'possibly-broken third-party extension %s\n'
1295 1293 b'** which supports versions %s of Mercurial.\n'
1296 1294 b'** Please disable %s and try your action again.\n'
1297 1295 b'** If that fixes the bug please report it to %s\n'
1298 1296 ) % (name, testedwith, name, stringutil.forcebytestr(report))
1299 1297 else:
1300 1298 bugtracker = ui.config(b'ui', b'supportcontact')
1301 1299 if bugtracker is None:
1302 1300 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1303 1301 warning = (
1304 1302 _(
1305 1303 b"** unknown exception encountered, "
1306 1304 b"please report by visiting\n** "
1307 1305 )
1308 1306 + bugtracker
1309 1307 + b'\n'
1310 1308 )
1311 1309 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1312 1310 warning += (
1313 1311 (_(b"** Python %s\n") % sysversion)
1314 1312 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1315 1313 + (
1316 1314 _(b"** Extensions loaded: %s\n")
1317 1315 % b", ".join([x[0] for x in extensions.extensions()])
1318 1316 )
1319 1317 )
1320 1318 return warning
1321 1319
1322 1320
1323 1321 def handlecommandexception(ui):
1324 1322 """Produce a warning message for broken commands
1325 1323
1326 1324 Called when handling an exception; the exception is reraised if
1327 1325 this function returns False, ignored otherwise.
1328 1326 """
1329 1327 warning = _exceptionwarning(ui)
1330 1328 ui.log(
1331 1329 b"commandexception",
1332 1330 b"%s\n%s\n",
1333 1331 warning,
1334 1332 pycompat.sysbytes(traceback.format_exc()),
1335 1333 )
1336 1334 ui.warn(warning)
1337 1335 return False # re-raise the exception
@@ -1,392 +1,390 b''
1 1 hide outer repo
2 2 $ hg init
3 3
4 4 Invalid syntax: no value
5 5
6 6 $ cat > .hg/hgrc << EOF
7 7 > novaluekey
8 8 > EOF
9 9 $ hg showconfig
10 10 hg: parse error at $TESTTMP/.hg/hgrc:1: novaluekey
11 11 [255]
12 12
13 13 Invalid syntax: no key
14 14
15 15 $ cat > .hg/hgrc << EOF
16 16 > =nokeyvalue
17 17 > EOF
18 18 $ hg showconfig
19 19 hg: parse error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
20 20 [255]
21 21
22 22 Test hint about invalid syntax from leading white space
23 23
24 24 $ cat > .hg/hgrc << EOF
25 25 > key=value
26 26 > EOF
27 27 $ hg showconfig
28 hg: parse error at $TESTTMP/.hg/hgrc:1: key=value
29 unexpected leading whitespace
28 hg: parse error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: key=value
30 29 [255]
31 30
32 31 $ cat > .hg/hgrc << EOF
33 32 > [section]
34 33 > key=value
35 34 > EOF
36 35 $ hg showconfig
37 hg: parse error at $TESTTMP/.hg/hgrc:1: [section]
38 unexpected leading whitespace
36 hg: parse error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: [section]
39 37 [255]
40 38
41 39 Reset hgrc
42 40
43 41 $ echo > .hg/hgrc
44 42
45 43 Test case sensitive configuration
46 44
47 45 $ cat <<EOF >> $HGRCPATH
48 46 > [Section]
49 47 > KeY = Case Sensitive
50 48 > key = lower case
51 49 > EOF
52 50
53 51 $ hg showconfig Section
54 52 Section.KeY=Case Sensitive
55 53 Section.key=lower case
56 54
57 55 $ hg showconfig Section -Tjson
58 56 [
59 57 {
60 58 "defaultvalue": null,
61 59 "name": "Section.KeY",
62 60 "source": "*.hgrc:*", (glob)
63 61 "value": "Case Sensitive"
64 62 },
65 63 {
66 64 "defaultvalue": null,
67 65 "name": "Section.key",
68 66 "source": "*.hgrc:*", (glob)
69 67 "value": "lower case"
70 68 }
71 69 ]
72 70 $ hg showconfig Section.KeY -Tjson
73 71 [
74 72 {
75 73 "defaultvalue": null,
76 74 "name": "Section.KeY",
77 75 "source": "*.hgrc:*", (glob)
78 76 "value": "Case Sensitive"
79 77 }
80 78 ]
81 79 $ hg showconfig -Tjson | tail -7
82 80 {
83 81 "defaultvalue": null,
84 82 "name": "*", (glob)
85 83 "source": "*", (glob)
86 84 "value": "*" (glob)
87 85 }
88 86 ]
89 87
90 88 Test config default of various types:
91 89
92 90 {"defaultvalue": ""} for -T'json(defaultvalue)' looks weird, but that's
93 91 how the templater works. Unknown keywords are evaluated to "".
94 92
95 93 dynamicdefault
96 94
97 95 $ hg config --config alias.foo= alias -Tjson
98 96 [
99 97 {
100 98 "name": "alias.foo",
101 99 "source": "--config",
102 100 "value": ""
103 101 }
104 102 ]
105 103 $ hg config --config alias.foo= alias -T'json(defaultvalue)'
106 104 [
107 105 {"defaultvalue": ""}
108 106 ]
109 107 $ hg config --config alias.foo= alias -T'{defaultvalue}\n'
110 108
111 109
112 110 null
113 111
114 112 $ hg config --config auth.cookiefile= auth -Tjson
115 113 [
116 114 {
117 115 "defaultvalue": null,
118 116 "name": "auth.cookiefile",
119 117 "source": "--config",
120 118 "value": ""
121 119 }
122 120 ]
123 121 $ hg config --config auth.cookiefile= auth -T'json(defaultvalue)'
124 122 [
125 123 {"defaultvalue": null}
126 124 ]
127 125 $ hg config --config auth.cookiefile= auth -T'{defaultvalue}\n'
128 126
129 127
130 128 false
131 129
132 130 $ hg config --config commands.commit.post-status= commands -Tjson
133 131 [
134 132 {
135 133 "defaultvalue": false,
136 134 "name": "commands.commit.post-status",
137 135 "source": "--config",
138 136 "value": ""
139 137 }
140 138 ]
141 139 $ hg config --config commands.commit.post-status= commands -T'json(defaultvalue)'
142 140 [
143 141 {"defaultvalue": false}
144 142 ]
145 143 $ hg config --config commands.commit.post-status= commands -T'{defaultvalue}\n'
146 144 False
147 145
148 146 true
149 147
150 148 $ hg config --config format.dotencode= format -Tjson
151 149 [
152 150 {
153 151 "defaultvalue": true,
154 152 "name": "format.dotencode",
155 153 "source": "--config",
156 154 "value": ""
157 155 }
158 156 ]
159 157 $ hg config --config format.dotencode= format -T'json(defaultvalue)'
160 158 [
161 159 {"defaultvalue": true}
162 160 ]
163 161 $ hg config --config format.dotencode= format -T'{defaultvalue}\n'
164 162 True
165 163
166 164 bytes
167 165
168 166 $ hg config --config commands.resolve.mark-check= commands -Tjson
169 167 [
170 168 {
171 169 "defaultvalue": "none",
172 170 "name": "commands.resolve.mark-check",
173 171 "source": "--config",
174 172 "value": ""
175 173 }
176 174 ]
177 175 $ hg config --config commands.resolve.mark-check= commands -T'json(defaultvalue)'
178 176 [
179 177 {"defaultvalue": "none"}
180 178 ]
181 179 $ hg config --config commands.resolve.mark-check= commands -T'{defaultvalue}\n'
182 180 none
183 181
184 182 empty list
185 183
186 184 $ hg config --config commands.show.aliasprefix= commands -Tjson
187 185 [
188 186 {
189 187 "defaultvalue": [],
190 188 "name": "commands.show.aliasprefix",
191 189 "source": "--config",
192 190 "value": ""
193 191 }
194 192 ]
195 193 $ hg config --config commands.show.aliasprefix= commands -T'json(defaultvalue)'
196 194 [
197 195 {"defaultvalue": []}
198 196 ]
199 197 $ hg config --config commands.show.aliasprefix= commands -T'{defaultvalue}\n'
200 198
201 199
202 200 nonempty list
203 201
204 202 $ hg config --config progress.format= progress -Tjson
205 203 [
206 204 {
207 205 "defaultvalue": ["topic", "bar", "number", "estimate"],
208 206 "name": "progress.format",
209 207 "source": "--config",
210 208 "value": ""
211 209 }
212 210 ]
213 211 $ hg config --config progress.format= progress -T'json(defaultvalue)'
214 212 [
215 213 {"defaultvalue": ["topic", "bar", "number", "estimate"]}
216 214 ]
217 215 $ hg config --config progress.format= progress -T'{defaultvalue}\n'
218 216 topic bar number estimate
219 217
220 218 int
221 219
222 220 $ hg config --config profiling.freq= profiling -Tjson
223 221 [
224 222 {
225 223 "defaultvalue": 1000,
226 224 "name": "profiling.freq",
227 225 "source": "--config",
228 226 "value": ""
229 227 }
230 228 ]
231 229 $ hg config --config profiling.freq= profiling -T'json(defaultvalue)'
232 230 [
233 231 {"defaultvalue": 1000}
234 232 ]
235 233 $ hg config --config profiling.freq= profiling -T'{defaultvalue}\n'
236 234 1000
237 235
238 236 float
239 237
240 238 $ hg config --config profiling.showmax= profiling -Tjson
241 239 [
242 240 {
243 241 "defaultvalue": 0.999,
244 242 "name": "profiling.showmax",
245 243 "source": "--config",
246 244 "value": ""
247 245 }
248 246 ]
249 247 $ hg config --config profiling.showmax= profiling -T'json(defaultvalue)'
250 248 [
251 249 {"defaultvalue": 0.999}
252 250 ]
253 251 $ hg config --config profiling.showmax= profiling -T'{defaultvalue}\n'
254 252 0.999
255 253
256 254 Test empty config source:
257 255
258 256 $ cat <<EOF > emptysource.py
259 257 > def reposetup(ui, repo):
260 258 > ui.setconfig(b'empty', b'source', b'value')
261 259 > EOF
262 260 $ cp .hg/hgrc .hg/hgrc.orig
263 261 $ cat <<EOF >> .hg/hgrc
264 262 > [extensions]
265 263 > emptysource = `pwd`/emptysource.py
266 264 > EOF
267 265
268 266 $ hg config --debug empty.source
269 267 read config from: * (glob)
270 268 none: value
271 269 $ hg config empty.source -Tjson
272 270 [
273 271 {
274 272 "defaultvalue": null,
275 273 "name": "empty.source",
276 274 "source": "",
277 275 "value": "value"
278 276 }
279 277 ]
280 278
281 279 $ cp .hg/hgrc.orig .hg/hgrc
282 280
283 281 Test "%unset"
284 282
285 283 $ cat >> $HGRCPATH <<EOF
286 284 > [unsettest]
287 285 > local-hgrcpath = should be unset (HGRCPATH)
288 286 > %unset local-hgrcpath
289 287 >
290 288 > global = should be unset (HGRCPATH)
291 289 >
292 290 > both = should be unset (HGRCPATH)
293 291 >
294 292 > set-after-unset = should be unset (HGRCPATH)
295 293 > EOF
296 294
297 295 $ cat >> .hg/hgrc <<EOF
298 296 > [unsettest]
299 297 > local-hgrc = should be unset (.hg/hgrc)
300 298 > %unset local-hgrc
301 299 >
302 300 > %unset global
303 301 >
304 302 > both = should be unset (.hg/hgrc)
305 303 > %unset both
306 304 >
307 305 > set-after-unset = should be unset (.hg/hgrc)
308 306 > %unset set-after-unset
309 307 > set-after-unset = should be set (.hg/hgrc)
310 308 > EOF
311 309
312 310 $ hg showconfig unsettest
313 311 unsettest.set-after-unset=should be set (.hg/hgrc)
314 312
315 313 Test exit code when no config matches
316 314
317 315 $ hg config Section.idontexist
318 316 [1]
319 317
320 318 sub-options in [paths] aren't expanded
321 319
322 320 $ cat > .hg/hgrc << EOF
323 321 > [paths]
324 322 > foo = ~/foo
325 323 > foo:suboption = ~/foo
326 324 > EOF
327 325
328 326 $ hg showconfig paths
329 327 paths.foo:suboption=~/foo
330 328 paths.foo=$TESTTMP/foo
331 329
332 330 edit failure
333 331
334 332 $ HGEDITOR=false hg config --edit
335 333 abort: edit failed: false exited with status 1
336 334 [255]
337 335
338 336 config affected by environment variables
339 337
340 338 $ EDITOR=e1 VISUAL=e2 hg config --debug | grep 'ui\.editor'
341 339 $VISUAL: ui.editor=e2
342 340
343 341 $ VISUAL=e2 hg config --debug --config ui.editor=e3 | grep 'ui\.editor'
344 342 --config: ui.editor=e3
345 343
346 344 $ PAGER=p1 hg config --debug | grep 'pager\.pager'
347 345 $PAGER: pager.pager=p1
348 346
349 347 $ PAGER=p1 hg config --debug --config pager.pager=p2 | grep 'pager\.pager'
350 348 --config: pager.pager=p2
351 349
352 350 verify that aliases are evaluated as well
353 351
354 352 $ hg init aliastest
355 353 $ cd aliastest
356 354 $ cat > .hg/hgrc << EOF
357 355 > [ui]
358 356 > user = repo user
359 357 > EOF
360 358 $ touch index
361 359 $ unset HGUSER
362 360 $ hg ci -Am test
363 361 adding index
364 362 $ hg log --template '{author}\n'
365 363 repo user
366 364 $ cd ..
367 365
368 366 alias has lower priority
369 367
370 368 $ hg init aliaspriority
371 369 $ cd aliaspriority
372 370 $ cat > .hg/hgrc << EOF
373 371 > [ui]
374 372 > user = alias user
375 373 > username = repo user
376 374 > EOF
377 375 $ touch index
378 376 $ unset HGUSER
379 377 $ hg ci -Am test
380 378 adding index
381 379 $ hg log --template '{author}\n'
382 380 repo user
383 381 $ cd ..
384 382
385 383 configs should be read in lexicographical order
386 384
387 385 $ mkdir configs
388 386 $ for i in `$TESTDIR/seq.py 10 99`; do
389 387 > printf "[section]\nkey=$i" > configs/$i.rc
390 388 > done
391 389 $ HGRCPATH=configs hg config section.key
392 390 99
@@ -1,309 +1,308 b''
1 1 Use hgrc within $TESTTMP
2 2
3 3 $ HGRCPATH=`pwd`/hgrc
4 4 $ export HGRCPATH
5 5
6 6 hide outer repo
7 7 $ hg init
8 8
9 9 Use an alternate var for scribbling on hgrc to keep check-code from
10 10 complaining about the important settings we may be overwriting:
11 11
12 12 $ HGRC=`pwd`/hgrc
13 13 $ export HGRC
14 14
15 15 Basic syntax error
16 16
17 17 $ echo "invalid" > $HGRC
18 18 $ hg version
19 19 hg: parse error at $TESTTMP/hgrc:1: invalid
20 20 [255]
21 21 $ echo "" > $HGRC
22 22
23 23 Issue1199: Can't use '%' in hgrc (eg url encoded username)
24 24
25 25 $ hg init "foo%bar"
26 26 $ hg clone "foo%bar" foobar
27 27 updating to branch default
28 28 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 29 $ cd foobar
30 30 $ cat .hg/hgrc
31 31 # example repository config (see 'hg help config' for more info)
32 32 [paths]
33 33 default = $TESTTMP/foo%bar
34 34
35 35 # path aliases to other clones of this repo in URLs or filesystem paths
36 36 # (see 'hg help config.paths' for more info)
37 37 #
38 38 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
39 39 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
40 40 # my-clone = /home/jdoe/jdoes-clone
41 41
42 42 [ui]
43 43 # name and email (local to this repository, optional), e.g.
44 44 # username = Jane Doe <jdoe@example.com>
45 45 $ hg paths
46 46 default = $TESTTMP/foo%bar
47 47 $ hg showconfig
48 48 bundle.mainreporoot=$TESTTMP/foobar
49 49 paths.default=$TESTTMP/foo%bar
50 50 $ cd ..
51 51
52 52 Check %include
53 53
54 54 $ echo '[section]' > $TESTTMP/included
55 55 $ echo 'option = value' >> $TESTTMP/included
56 56 $ echo '%include $TESTTMP/included' >> $HGRC
57 57 $ hg showconfig section
58 58 section.option=value
59 59 #if unix-permissions no-root
60 60 $ chmod u-r $TESTTMP/included
61 61 $ hg showconfig section
62 62 hg: parse error at $TESTTMP/hgrc:2: cannot include $TESTTMP/included (Permission denied)
63 63 [255]
64 64 #endif
65 65
66 66 issue1829: wrong indentation
67 67
68 68 $ echo '[foo]' > $HGRC
69 69 $ echo ' x = y' >> $HGRC
70 70 $ hg version
71 hg: parse error at $TESTTMP/hgrc:2: x = y
72 unexpected leading whitespace
71 hg: parse error at $TESTTMP/hgrc:2: unexpected leading whitespace: x = y
73 72 [255]
74 73
75 74 $ "$PYTHON" -c "from __future__ import print_function; print('[foo]\nbar = a\n b\n c \n de\n fg \nbaz = bif cb \n')" \
76 75 > > $HGRC
77 76 $ hg showconfig foo
78 77 foo.bar=a\nb\nc\nde\nfg
79 78 foo.baz=bif cb
80 79
81 80 $ FAKEPATH=/path/to/nowhere
82 81 $ export FAKEPATH
83 82 $ echo '%include $FAKEPATH/no-such-file' > $HGRC
84 83 $ hg version
85 84 Mercurial Distributed SCM (version *) (glob)
86 85 (see https://mercurial-scm.org for more information)
87 86
88 87 Copyright (C) 2005-* Matt Mackall and others (glob)
89 88 This is free software; see the source for copying conditions. There is NO
90 89 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
91 90 $ unset FAKEPATH
92 91
93 92 make sure global options given on the cmdline take precedence
94 93
95 94 $ hg showconfig --config ui.verbose=True --quiet
96 95 bundle.mainreporoot=$TESTTMP
97 96 ui.verbose=False
98 97 ui.debug=False
99 98 ui.quiet=True
100 99
101 100 $ touch foobar/untracked
102 101 $ cat >> foobar/.hg/hgrc <<EOF
103 102 > [ui]
104 103 > verbose=True
105 104 > EOF
106 105 $ hg -R foobar st -q
107 106
108 107 username expansion
109 108
110 109 $ olduser=$HGUSER
111 110 $ unset HGUSER
112 111
113 112 $ FAKEUSER='John Doe'
114 113 $ export FAKEUSER
115 114 $ echo '[ui]' > $HGRC
116 115 $ echo 'username = $FAKEUSER' >> $HGRC
117 116
118 117 $ hg init usertest
119 118 $ cd usertest
120 119 $ touch bar
121 120 $ hg commit --addremove --quiet -m "added bar"
122 121 $ hg log --template "{author}\n"
123 122 John Doe
124 123 $ cd ..
125 124
126 125 $ hg showconfig
127 126 bundle.mainreporoot=$TESTTMP
128 127 ui.username=$FAKEUSER
129 128
130 129 $ unset FAKEUSER
131 130 $ HGUSER=$olduser
132 131 $ export HGUSER
133 132
134 133 showconfig with multiple arguments
135 134
136 135 $ echo "[alias]" > $HGRC
137 136 $ echo "log = log -g" >> $HGRC
138 137 $ echo "[defaults]" >> $HGRC
139 138 $ echo "identify = -n" >> $HGRC
140 139 $ hg showconfig alias defaults
141 140 alias.log=log -g
142 141 defaults.identify=-n
143 142 $ hg showconfig alias alias
144 143 alias.log=log -g
145 144 $ hg showconfig alias.log alias.log
146 145 alias.log=log -g
147 146 $ hg showconfig alias defaults.identify
148 147 alias.log=log -g
149 148 defaults.identify=-n
150 149 $ hg showconfig alias.log defaults.identify
151 150 alias.log=log -g
152 151 defaults.identify=-n
153 152
154 153 HGPLAIN
155 154
156 155 $ echo "[ui]" > $HGRC
157 156 $ echo "debug=true" >> $HGRC
158 157 $ echo "fallbackencoding=ASCII" >> $HGRC
159 158 $ echo "quiet=true" >> $HGRC
160 159 $ echo "slash=true" >> $HGRC
161 160 $ echo "traceback=true" >> $HGRC
162 161 $ echo "verbose=true" >> $HGRC
163 162 $ echo "style=~/.hgstyle" >> $HGRC
164 163 $ echo "logtemplate={node}" >> $HGRC
165 164 $ echo "[defaults]" >> $HGRC
166 165 $ echo "identify=-n" >> $HGRC
167 166 $ echo "[alias]" >> $HGRC
168 167 $ echo "log=log -g" >> $HGRC
169 168
170 169 customized hgrc
171 170
172 171 $ hg showconfig
173 172 read config from: $TESTTMP/hgrc
174 173 $TESTTMP/hgrc:13: alias.log=log -g
175 174 repo: bundle.mainreporoot=$TESTTMP
176 175 $TESTTMP/hgrc:11: defaults.identify=-n
177 176 $TESTTMP/hgrc:2: ui.debug=true
178 177 $TESTTMP/hgrc:3: ui.fallbackencoding=ASCII
179 178 $TESTTMP/hgrc:4: ui.quiet=true
180 179 $TESTTMP/hgrc:5: ui.slash=true
181 180 $TESTTMP/hgrc:6: ui.traceback=true
182 181 $TESTTMP/hgrc:7: ui.verbose=true
183 182 $TESTTMP/hgrc:8: ui.style=~/.hgstyle
184 183 $TESTTMP/hgrc:9: ui.logtemplate={node}
185 184
186 185 plain hgrc
187 186
188 187 $ HGPLAIN=; export HGPLAIN
189 188 $ hg showconfig --config ui.traceback=True --debug
190 189 read config from: $TESTTMP/hgrc
191 190 repo: bundle.mainreporoot=$TESTTMP
192 191 --config: ui.traceback=True
193 192 --verbose: ui.verbose=False
194 193 --debug: ui.debug=True
195 194 --quiet: ui.quiet=False
196 195
197 196 with environment variables
198 197
199 198 $ PAGER=p1 EDITOR=e1 VISUAL=e2 hg showconfig --debug
200 199 read config from: $TESTTMP/hgrc
201 200 repo: bundle.mainreporoot=$TESTTMP
202 201 $PAGER: pager.pager=p1
203 202 $VISUAL: ui.editor=e2
204 203 --verbose: ui.verbose=False
205 204 --debug: ui.debug=True
206 205 --quiet: ui.quiet=False
207 206
208 207 plain mode with exceptions
209 208
210 209 $ cat > plain.py <<EOF
211 210 > from mercurial import commands, extensions
212 211 > def _config(orig, ui, repo, *values, **opts):
213 212 > ui.write(b'plain: %r\n' % ui.plain())
214 213 > return orig(ui, repo, *values, **opts)
215 214 > def uisetup(ui):
216 215 > extensions.wrapcommand(commands.table, b'config', _config)
217 216 > EOF
218 217 $ echo "[extensions]" >> $HGRC
219 218 $ echo "plain=./plain.py" >> $HGRC
220 219 $ HGPLAINEXCEPT=; export HGPLAINEXCEPT
221 220 $ hg showconfig --config ui.traceback=True --debug
222 221 plain: True
223 222 read config from: $TESTTMP/hgrc
224 223 repo: bundle.mainreporoot=$TESTTMP
225 224 $TESTTMP/hgrc:15: extensions.plain=./plain.py
226 225 --config: ui.traceback=True
227 226 --verbose: ui.verbose=False
228 227 --debug: ui.debug=True
229 228 --quiet: ui.quiet=False
230 229 $ unset HGPLAIN
231 230 $ hg showconfig --config ui.traceback=True --debug
232 231 plain: True
233 232 read config from: $TESTTMP/hgrc
234 233 repo: bundle.mainreporoot=$TESTTMP
235 234 $TESTTMP/hgrc:15: extensions.plain=./plain.py
236 235 --config: ui.traceback=True
237 236 --verbose: ui.verbose=False
238 237 --debug: ui.debug=True
239 238 --quiet: ui.quiet=False
240 239 $ HGPLAINEXCEPT=i18n; export HGPLAINEXCEPT
241 240 $ hg showconfig --config ui.traceback=True --debug
242 241 plain: True
243 242 read config from: $TESTTMP/hgrc
244 243 repo: bundle.mainreporoot=$TESTTMP
245 244 $TESTTMP/hgrc:15: extensions.plain=./plain.py
246 245 --config: ui.traceback=True
247 246 --verbose: ui.verbose=False
248 247 --debug: ui.debug=True
249 248 --quiet: ui.quiet=False
250 249
251 250 source of paths is not mangled
252 251
253 252 $ cat >> $HGRCPATH <<EOF
254 253 > [paths]
255 254 > foo = bar
256 255 > EOF
257 256 $ hg showconfig --debug paths
258 257 plain: True
259 258 read config from: $TESTTMP/hgrc
260 259 $TESTTMP/hgrc:17: paths.foo=$TESTTMP/bar
261 260
262 261 Test we can skip the user configuration
263 262
264 263 $ cat >> .hg/hgrc <<EOF
265 264 > [paths]
266 265 > elephant = babar
267 266 > EOF
268 267 $ hg path
269 268 elephant = $TESTTMP/babar
270 269 foo = $TESTTMP/bar
271 270 $ HGRCSKIPREPO=1 hg path
272 271 foo = $TESTTMP/bar
273 272
274 273 $ cat >> .hg/hgrc <<EOF
275 274 > [broken
276 275 > EOF
277 276
278 277 $ hg path
279 278 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
280 279 [255]
281 280 $ HGRCSKIPREPO=1 hg path
282 281 foo = $TESTTMP/bar
283 282
284 283 Check that hgweb respect HGRCSKIPREPO=1
285 284
286 285 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
287 286 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
288 287 [255]
289 288 $ test -f hg.pid && (cat hg.pid >> $DAEMON_PIDS)
290 289 [1]
291 290 $ killdaemons.py
292 291 $ test -f access.log && cat access.log
293 292 [1]
294 293 $ test -f errors.log && cat errors.log
295 294 [1]
296 295
297 296 $ HGRCSKIPREPO=1 hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
298 297 $ cat hg.pid >> $DAEMON_PIDS
299 298 $ killdaemons.py
300 299 $ cat access.log
301 300 $ cat errors.log
302 301
303 302 Check that zeroconf respect HGRCSKIPREPO=1
304 303
305 304 $ hg paths --config extensions.zeroconf=
306 305 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
307 306 [255]
308 307 $ HGRCSKIPREPO=1 hg paths --config extensions.zeroconf=
309 308 foo = $TESTTMP/bar
General Comments 0
You need to be logged in to leave comments. Login now