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