##// END OF EJS Templates
dispatch: move initialization of sys.std* files...
Yuya Nishihara -
r34534:163fa0ae default
parent child Browse files
Show More
@@ -1,100 +1,97 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # hgperf - measure performance of Mercurial commands
3 # hgperf - measure performance of Mercurial commands
4 #
4 #
5 # Copyright 2014 Matt Mackall <mpm@selenic.com>
5 # Copyright 2014 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 '''measure performance of Mercurial commands
10 '''measure performance of Mercurial commands
11
11
12 Using ``hgperf`` instead of ``hg`` measures performance of the target
12 Using ``hgperf`` instead of ``hg`` measures performance of the target
13 Mercurial command. For example, the execution below measures
13 Mercurial command. For example, the execution below measures
14 performance of :hg:`heads --topo`::
14 performance of :hg:`heads --topo`::
15
15
16 $ hgperf heads --topo
16 $ hgperf heads --topo
17
17
18 All command output via ``ui`` is suppressed, and just measurement
18 All command output via ``ui`` is suppressed, and just measurement
19 result is displayed: see also "perf" extension in "contrib".
19 result is displayed: see also "perf" extension in "contrib".
20
20
21 Costs of processing before dispatching to the command function like
21 Costs of processing before dispatching to the command function like
22 below are not measured::
22 below are not measured::
23
23
24 - parsing command line (e.g. option validity check)
24 - parsing command line (e.g. option validity check)
25 - reading configuration files in
25 - reading configuration files in
26
26
27 But ``pre-`` and ``post-`` hook invocation for the target command is
27 But ``pre-`` and ``post-`` hook invocation for the target command is
28 measured, even though these are invoked before or after dispatching to
28 measured, even though these are invoked before or after dispatching to
29 the command function, because these may be required to repeat
29 the command function, because these may be required to repeat
30 execution of the target command correctly.
30 execution of the target command correctly.
31 '''
31 '''
32
32
33 import os
33 import os
34 import sys
34 import sys
35
35
36 libdir = '@LIBDIR@'
36 libdir = '@LIBDIR@'
37
37
38 if libdir != '@' 'LIBDIR' '@':
38 if libdir != '@' 'LIBDIR' '@':
39 if not os.path.isabs(libdir):
39 if not os.path.isabs(libdir):
40 libdir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
40 libdir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
41 libdir)
41 libdir)
42 libdir = os.path.abspath(libdir)
42 libdir = os.path.abspath(libdir)
43 sys.path.insert(0, libdir)
43 sys.path.insert(0, libdir)
44
44
45 # enable importing on demand to reduce startup time
45 # enable importing on demand to reduce startup time
46 try:
46 try:
47 from mercurial import demandimport; demandimport.enable()
47 from mercurial import demandimport; demandimport.enable()
48 except ImportError:
48 except ImportError:
49 import sys
49 import sys
50 sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
50 sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
51 ' '.join(sys.path))
51 ' '.join(sys.path))
52 sys.stderr.write("(check your install and PYTHONPATH)\n")
52 sys.stderr.write("(check your install and PYTHONPATH)\n")
53 sys.exit(-1)
53 sys.exit(-1)
54
54
55 from mercurial import (
55 from mercurial import (
56 dispatch,
56 dispatch,
57 util,
57 util,
58 )
58 )
59
59
60 def timer(func, title=None):
60 def timer(func, title=None):
61 results = []
61 results = []
62 begin = util.timer()
62 begin = util.timer()
63 count = 0
63 count = 0
64 while True:
64 while True:
65 ostart = os.times()
65 ostart = os.times()
66 cstart = util.timer()
66 cstart = util.timer()
67 r = func()
67 r = func()
68 cstop = util.timer()
68 cstop = util.timer()
69 ostop = os.times()
69 ostop = os.times()
70 count += 1
70 count += 1
71 a, b = ostart, ostop
71 a, b = ostart, ostop
72 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
72 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
73 if cstop - begin > 3 and count >= 100:
73 if cstop - begin > 3 and count >= 100:
74 break
74 break
75 if cstop - begin > 10 and count >= 3:
75 if cstop - begin > 10 and count >= 3:
76 break
76 break
77 if title:
77 if title:
78 sys.stderr.write("! %s\n" % title)
78 sys.stderr.write("! %s\n" % title)
79 if r:
79 if r:
80 sys.stderr.write("! result: %s\n" % r)
80 sys.stderr.write("! result: %s\n" % r)
81 m = min(results)
81 m = min(results)
82 sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
82 sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
83 % (m[0], m[1] + m[2], m[1], m[2], count))
83 % (m[0], m[1] + m[2], m[1], m[2], count))
84
84
85 orgruncommand = dispatch.runcommand
85 orgruncommand = dispatch.runcommand
86
86
87 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
87 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
88 ui.pushbuffer()
88 ui.pushbuffer()
89 lui.pushbuffer()
89 lui.pushbuffer()
90 timer(lambda : orgruncommand(lui, repo, cmd, fullargs, ui,
90 timer(lambda : orgruncommand(lui, repo, cmd, fullargs, ui,
91 options, d, cmdpats, cmdoptions))
91 options, d, cmdpats, cmdoptions))
92 ui.popbuffer()
92 ui.popbuffer()
93 lui.popbuffer()
93 lui.popbuffer()
94
94
95 dispatch.runcommand = runcommand
95 dispatch.runcommand = runcommand
96
96
97 for fp in (sys.stdin, sys.stdout, sys.stderr):
98 util.setbinary(fp)
99
100 dispatch.run()
97 dispatch.run()
@@ -1,47 +1,41 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # mercurial - scalable distributed SCM
3 # mercurial - scalable distributed SCM
4 #
4 #
5 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import os
11 import os
12 import sys
12 import sys
13
13
14 if os.environ.get('HGUNICODEPEDANTRY', False):
14 if os.environ.get('HGUNICODEPEDANTRY', False):
15 try:
15 try:
16 reload(sys)
16 reload(sys)
17 sys.setdefaultencoding("undefined")
17 sys.setdefaultencoding("undefined")
18 except NameError:
18 except NameError:
19 pass
19 pass
20
20
21 libdir = '@LIBDIR@'
21 libdir = '@LIBDIR@'
22
22
23 if libdir != '@' 'LIBDIR' '@':
23 if libdir != '@' 'LIBDIR' '@':
24 if not os.path.isabs(libdir):
24 if not os.path.isabs(libdir):
25 libdir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
25 libdir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
26 libdir)
26 libdir)
27 libdir = os.path.abspath(libdir)
27 libdir = os.path.abspath(libdir)
28 sys.path.insert(0, libdir)
28 sys.path.insert(0, libdir)
29
29
30 # enable importing on demand to reduce startup time
30 # enable importing on demand to reduce startup time
31 try:
31 try:
32 if sys.version_info[0] < 3 or sys.version_info >= (3, 6):
32 if sys.version_info[0] < 3 or sys.version_info >= (3, 6):
33 import hgdemandimport; hgdemandimport.enable()
33 import hgdemandimport; hgdemandimport.enable()
34 except ImportError:
34 except ImportError:
35 sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
35 sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
36 ' '.join(sys.path))
36 ' '.join(sys.path))
37 sys.stderr.write("(check your install and PYTHONPATH)\n")
37 sys.stderr.write("(check your install and PYTHONPATH)\n")
38 sys.exit(-1)
38 sys.exit(-1)
39
39
40 from mercurial import (
40 from mercurial import dispatch
41 dispatch,
42 util,
43 )
44 for fp in (sys.stdin, sys.stdout, sys.stderr):
45 util.setbinary(fp)
46
47 dispatch.run()
41 dispatch.run()
@@ -1,1015 +1,1020 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import getopt
12 import getopt
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import signal
16 import signal
17 import sys
17 import sys
18 import time
18 import time
19 import traceback
19 import traceback
20
20
21
21
22 from .i18n import _
22 from .i18n import _
23
23
24 from . import (
24 from . import (
25 cmdutil,
25 cmdutil,
26 color,
26 color,
27 commands,
27 commands,
28 demandimport,
28 demandimport,
29 encoding,
29 encoding,
30 error,
30 error,
31 extensions,
31 extensions,
32 fancyopts,
32 fancyopts,
33 help,
33 help,
34 hg,
34 hg,
35 hook,
35 hook,
36 profiling,
36 profiling,
37 pycompat,
37 pycompat,
38 scmutil,
38 scmutil,
39 ui as uimod,
39 ui as uimod,
40 util,
40 util,
41 )
41 )
42
42
43 class request(object):
43 class request(object):
44 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
44 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
45 ferr=None, prereposetups=None):
45 ferr=None, prereposetups=None):
46 self.args = args
46 self.args = args
47 self.ui = ui
47 self.ui = ui
48 self.repo = repo
48 self.repo = repo
49
49
50 # input/output/error streams
50 # input/output/error streams
51 self.fin = fin
51 self.fin = fin
52 self.fout = fout
52 self.fout = fout
53 self.ferr = ferr
53 self.ferr = ferr
54
54
55 # reposetups which run before extensions, useful for chg to pre-fill
55 # reposetups which run before extensions, useful for chg to pre-fill
56 # low-level repo state (for example, changelog) before extensions.
56 # low-level repo state (for example, changelog) before extensions.
57 self.prereposetups = prereposetups or []
57 self.prereposetups = prereposetups or []
58
58
59 def _runexithandlers(self):
59 def _runexithandlers(self):
60 exc = None
60 exc = None
61 handlers = self.ui._exithandlers
61 handlers = self.ui._exithandlers
62 try:
62 try:
63 while handlers:
63 while handlers:
64 func, args, kwargs = handlers.pop()
64 func, args, kwargs = handlers.pop()
65 try:
65 try:
66 func(*args, **kwargs)
66 func(*args, **kwargs)
67 except: # re-raises below
67 except: # re-raises below
68 if exc is None:
68 if exc is None:
69 exc = sys.exc_info()[1]
69 exc = sys.exc_info()[1]
70 self.ui.warn(('error in exit handlers:\n'))
70 self.ui.warn(('error in exit handlers:\n'))
71 self.ui.traceback(force=True)
71 self.ui.traceback(force=True)
72 finally:
72 finally:
73 if exc is not None:
73 if exc is not None:
74 raise exc
74 raise exc
75
75
76 def run():
76 def run():
77 "run the command in sys.argv"
77 "run the command in sys.argv"
78 _initstdio()
78 req = request(pycompat.sysargv[1:])
79 req = request(pycompat.sysargv[1:])
79 err = None
80 err = None
80 try:
81 try:
81 status = (dispatch(req) or 0) & 255
82 status = (dispatch(req) or 0) & 255
82 except error.StdioError as e:
83 except error.StdioError as e:
83 err = e
84 err = e
84 status = -1
85 status = -1
85 if util.safehasattr(req.ui, 'fout'):
86 if util.safehasattr(req.ui, 'fout'):
86 try:
87 try:
87 req.ui.fout.flush()
88 req.ui.fout.flush()
88 except IOError as e:
89 except IOError as e:
89 err = e
90 err = e
90 status = -1
91 status = -1
91 if util.safehasattr(req.ui, 'ferr'):
92 if util.safehasattr(req.ui, 'ferr'):
92 if err is not None and err.errno != errno.EPIPE:
93 if err is not None and err.errno != errno.EPIPE:
93 req.ui.ferr.write('abort: %s\n' %
94 req.ui.ferr.write('abort: %s\n' %
94 encoding.strtolocal(err.strerror))
95 encoding.strtolocal(err.strerror))
95 req.ui.ferr.flush()
96 req.ui.ferr.flush()
96 sys.exit(status & 255)
97 sys.exit(status & 255)
97
98
99 def _initstdio():
100 for fp in (sys.stdin, sys.stdout, sys.stderr):
101 util.setbinary(fp)
102
98 def _getsimilar(symbols, value):
103 def _getsimilar(symbols, value):
99 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
104 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
100 # The cutoff for similarity here is pretty arbitrary. It should
105 # The cutoff for similarity here is pretty arbitrary. It should
101 # probably be investigated and tweaked.
106 # probably be investigated and tweaked.
102 return [s for s in symbols if sim(s) > 0.6]
107 return [s for s in symbols if sim(s) > 0.6]
103
108
104 def _reportsimilar(write, similar):
109 def _reportsimilar(write, similar):
105 if len(similar) == 1:
110 if len(similar) == 1:
106 write(_("(did you mean %s?)\n") % similar[0])
111 write(_("(did you mean %s?)\n") % similar[0])
107 elif similar:
112 elif similar:
108 ss = ", ".join(sorted(similar))
113 ss = ", ".join(sorted(similar))
109 write(_("(did you mean one of %s?)\n") % ss)
114 write(_("(did you mean one of %s?)\n") % ss)
110
115
111 def _formatparse(write, inst):
116 def _formatparse(write, inst):
112 similar = []
117 similar = []
113 if isinstance(inst, error.UnknownIdentifier):
118 if isinstance(inst, error.UnknownIdentifier):
114 # make sure to check fileset first, as revset can invoke fileset
119 # make sure to check fileset first, as revset can invoke fileset
115 similar = _getsimilar(inst.symbols, inst.function)
120 similar = _getsimilar(inst.symbols, inst.function)
116 if len(inst.args) > 1:
121 if len(inst.args) > 1:
117 write(_("hg: parse error at %s: %s\n") %
122 write(_("hg: parse error at %s: %s\n") %
118 (inst.args[1], inst.args[0]))
123 (inst.args[1], inst.args[0]))
119 if (inst.args[0][0] == ' '):
124 if (inst.args[0][0] == ' '):
120 write(_("unexpected leading whitespace\n"))
125 write(_("unexpected leading whitespace\n"))
121 else:
126 else:
122 write(_("hg: parse error: %s\n") % inst.args[0])
127 write(_("hg: parse error: %s\n") % inst.args[0])
123 _reportsimilar(write, similar)
128 _reportsimilar(write, similar)
124 if inst.hint:
129 if inst.hint:
125 write(_("(%s)\n") % inst.hint)
130 write(_("(%s)\n") % inst.hint)
126
131
127 def _formatargs(args):
132 def _formatargs(args):
128 return ' '.join(util.shellquote(a) for a in args)
133 return ' '.join(util.shellquote(a) for a in args)
129
134
130 def dispatch(req):
135 def dispatch(req):
131 "run the command specified in req.args"
136 "run the command specified in req.args"
132 if req.ferr:
137 if req.ferr:
133 ferr = req.ferr
138 ferr = req.ferr
134 elif req.ui:
139 elif req.ui:
135 ferr = req.ui.ferr
140 ferr = req.ui.ferr
136 else:
141 else:
137 ferr = util.stderr
142 ferr = util.stderr
138
143
139 try:
144 try:
140 if not req.ui:
145 if not req.ui:
141 req.ui = uimod.ui.load()
146 req.ui = uimod.ui.load()
142 if '--traceback' in req.args:
147 if '--traceback' in req.args:
143 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
148 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
144
149
145 # set ui streams from the request
150 # set ui streams from the request
146 if req.fin:
151 if req.fin:
147 req.ui.fin = req.fin
152 req.ui.fin = req.fin
148 if req.fout:
153 if req.fout:
149 req.ui.fout = req.fout
154 req.ui.fout = req.fout
150 if req.ferr:
155 if req.ferr:
151 req.ui.ferr = req.ferr
156 req.ui.ferr = req.ferr
152 except error.Abort as inst:
157 except error.Abort as inst:
153 ferr.write(_("abort: %s\n") % inst)
158 ferr.write(_("abort: %s\n") % inst)
154 if inst.hint:
159 if inst.hint:
155 ferr.write(_("(%s)\n") % inst.hint)
160 ferr.write(_("(%s)\n") % inst.hint)
156 return -1
161 return -1
157 except error.ParseError as inst:
162 except error.ParseError as inst:
158 _formatparse(ferr.write, inst)
163 _formatparse(ferr.write, inst)
159 return -1
164 return -1
160
165
161 msg = _formatargs(req.args)
166 msg = _formatargs(req.args)
162 starttime = util.timer()
167 starttime = util.timer()
163 ret = None
168 ret = None
164 try:
169 try:
165 ret = _runcatch(req)
170 ret = _runcatch(req)
166 except error.ProgrammingError as inst:
171 except error.ProgrammingError as inst:
167 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
172 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
168 if inst.hint:
173 if inst.hint:
169 req.ui.warn(_('** (%s)\n') % inst.hint)
174 req.ui.warn(_('** (%s)\n') % inst.hint)
170 raise
175 raise
171 except KeyboardInterrupt as inst:
176 except KeyboardInterrupt as inst:
172 try:
177 try:
173 if isinstance(inst, error.SignalInterrupt):
178 if isinstance(inst, error.SignalInterrupt):
174 msg = _("killed!\n")
179 msg = _("killed!\n")
175 else:
180 else:
176 msg = _("interrupted!\n")
181 msg = _("interrupted!\n")
177 req.ui.warn(msg)
182 req.ui.warn(msg)
178 except error.SignalInterrupt:
183 except error.SignalInterrupt:
179 # maybe pager would quit without consuming all the output, and
184 # maybe pager would quit without consuming all the output, and
180 # SIGPIPE was raised. we cannot print anything in this case.
185 # SIGPIPE was raised. we cannot print anything in this case.
181 pass
186 pass
182 except IOError as inst:
187 except IOError as inst:
183 if inst.errno != errno.EPIPE:
188 if inst.errno != errno.EPIPE:
184 raise
189 raise
185 ret = -1
190 ret = -1
186 finally:
191 finally:
187 duration = util.timer() - starttime
192 duration = util.timer() - starttime
188 req.ui.flush()
193 req.ui.flush()
189 if req.ui.logblockedtimes:
194 if req.ui.logblockedtimes:
190 req.ui._blockedtimes['command_duration'] = duration * 1000
195 req.ui._blockedtimes['command_duration'] = duration * 1000
191 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
196 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
192 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
197 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
193 msg, ret or 0, duration)
198 msg, ret or 0, duration)
194 try:
199 try:
195 req._runexithandlers()
200 req._runexithandlers()
196 except: # exiting, so no re-raises
201 except: # exiting, so no re-raises
197 ret = ret or -1
202 ret = ret or -1
198 return ret
203 return ret
199
204
200 def _runcatch(req):
205 def _runcatch(req):
201 def catchterm(*args):
206 def catchterm(*args):
202 raise error.SignalInterrupt
207 raise error.SignalInterrupt
203
208
204 ui = req.ui
209 ui = req.ui
205 try:
210 try:
206 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
211 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
207 num = getattr(signal, name, None)
212 num = getattr(signal, name, None)
208 if num:
213 if num:
209 signal.signal(num, catchterm)
214 signal.signal(num, catchterm)
210 except ValueError:
215 except ValueError:
211 pass # happens if called in a thread
216 pass # happens if called in a thread
212
217
213 def _runcatchfunc():
218 def _runcatchfunc():
214 realcmd = None
219 realcmd = None
215 try:
220 try:
216 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
221 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
217 cmd = cmdargs[0]
222 cmd = cmdargs[0]
218 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
223 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
219 realcmd = aliases[0]
224 realcmd = aliases[0]
220 except (error.UnknownCommand, error.AmbiguousCommand,
225 except (error.UnknownCommand, error.AmbiguousCommand,
221 IndexError, getopt.GetoptError):
226 IndexError, getopt.GetoptError):
222 # Don't handle this here. We know the command is
227 # Don't handle this here. We know the command is
223 # invalid, but all we're worried about for now is that
228 # invalid, but all we're worried about for now is that
224 # it's not a command that server operators expect to
229 # it's not a command that server operators expect to
225 # be safe to offer to users in a sandbox.
230 # be safe to offer to users in a sandbox.
226 pass
231 pass
227 if realcmd == 'serve' and '--stdio' in cmdargs:
232 if realcmd == 'serve' and '--stdio' in cmdargs:
228 # We want to constrain 'hg serve --stdio' instances pretty
233 # We want to constrain 'hg serve --stdio' instances pretty
229 # closely, as many shared-ssh access tools want to grant
234 # closely, as many shared-ssh access tools want to grant
230 # access to run *only* 'hg -R $repo serve --stdio'. We
235 # access to run *only* 'hg -R $repo serve --stdio'. We
231 # restrict to exactly that set of arguments, and prohibit
236 # restrict to exactly that set of arguments, and prohibit
232 # any repo name that starts with '--' to prevent
237 # any repo name that starts with '--' to prevent
233 # shenanigans wherein a user does something like pass
238 # shenanigans wherein a user does something like pass
234 # --debugger or --config=ui.debugger=1 as a repo
239 # --debugger or --config=ui.debugger=1 as a repo
235 # name. This used to actually run the debugger.
240 # name. This used to actually run the debugger.
236 if (len(req.args) != 4 or
241 if (len(req.args) != 4 or
237 req.args[0] != '-R' or
242 req.args[0] != '-R' or
238 req.args[1].startswith('--') or
243 req.args[1].startswith('--') or
239 req.args[2] != 'serve' or
244 req.args[2] != 'serve' or
240 req.args[3] != '--stdio'):
245 req.args[3] != '--stdio'):
241 raise error.Abort(
246 raise error.Abort(
242 _('potentially unsafe serve --stdio invocation: %r') %
247 _('potentially unsafe serve --stdio invocation: %r') %
243 (req.args,))
248 (req.args,))
244
249
245 try:
250 try:
246 debugger = 'pdb'
251 debugger = 'pdb'
247 debugtrace = {
252 debugtrace = {
248 'pdb': pdb.set_trace
253 'pdb': pdb.set_trace
249 }
254 }
250 debugmortem = {
255 debugmortem = {
251 'pdb': pdb.post_mortem
256 'pdb': pdb.post_mortem
252 }
257 }
253
258
254 # read --config before doing anything else
259 # read --config before doing anything else
255 # (e.g. to change trust settings for reading .hg/hgrc)
260 # (e.g. to change trust settings for reading .hg/hgrc)
256 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
261 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
257
262
258 if req.repo:
263 if req.repo:
259 # copy configs that were passed on the cmdline (--config) to
264 # copy configs that were passed on the cmdline (--config) to
260 # the repo ui
265 # the repo ui
261 for sec, name, val in cfgs:
266 for sec, name, val in cfgs:
262 req.repo.ui.setconfig(sec, name, val, source='--config')
267 req.repo.ui.setconfig(sec, name, val, source='--config')
263
268
264 # developer config: ui.debugger
269 # developer config: ui.debugger
265 debugger = ui.config("ui", "debugger")
270 debugger = ui.config("ui", "debugger")
266 debugmod = pdb
271 debugmod = pdb
267 if not debugger or ui.plain():
272 if not debugger or ui.plain():
268 # if we are in HGPLAIN mode, then disable custom debugging
273 # if we are in HGPLAIN mode, then disable custom debugging
269 debugger = 'pdb'
274 debugger = 'pdb'
270 elif '--debugger' in req.args:
275 elif '--debugger' in req.args:
271 # This import can be slow for fancy debuggers, so only
276 # This import can be slow for fancy debuggers, so only
272 # do it when absolutely necessary, i.e. when actual
277 # do it when absolutely necessary, i.e. when actual
273 # debugging has been requested
278 # debugging has been requested
274 with demandimport.deactivated():
279 with demandimport.deactivated():
275 try:
280 try:
276 debugmod = __import__(debugger)
281 debugmod = __import__(debugger)
277 except ImportError:
282 except ImportError:
278 pass # Leave debugmod = pdb
283 pass # Leave debugmod = pdb
279
284
280 debugtrace[debugger] = debugmod.set_trace
285 debugtrace[debugger] = debugmod.set_trace
281 debugmortem[debugger] = debugmod.post_mortem
286 debugmortem[debugger] = debugmod.post_mortem
282
287
283 # enter the debugger before command execution
288 # enter the debugger before command execution
284 if '--debugger' in req.args:
289 if '--debugger' in req.args:
285 ui.warn(_("entering debugger - "
290 ui.warn(_("entering debugger - "
286 "type c to continue starting hg or h for help\n"))
291 "type c to continue starting hg or h for help\n"))
287
292
288 if (debugger != 'pdb' and
293 if (debugger != 'pdb' and
289 debugtrace[debugger] == debugtrace['pdb']):
294 debugtrace[debugger] == debugtrace['pdb']):
290 ui.warn(_("%s debugger specified "
295 ui.warn(_("%s debugger specified "
291 "but its module was not found\n") % debugger)
296 "but its module was not found\n") % debugger)
292 with demandimport.deactivated():
297 with demandimport.deactivated():
293 debugtrace[debugger]()
298 debugtrace[debugger]()
294 try:
299 try:
295 return _dispatch(req)
300 return _dispatch(req)
296 finally:
301 finally:
297 ui.flush()
302 ui.flush()
298 except: # re-raises
303 except: # re-raises
299 # enter the debugger when we hit an exception
304 # enter the debugger when we hit an exception
300 if '--debugger' in req.args:
305 if '--debugger' in req.args:
301 traceback.print_exc()
306 traceback.print_exc()
302 debugmortem[debugger](sys.exc_info()[2])
307 debugmortem[debugger](sys.exc_info()[2])
303 raise
308 raise
304
309
305 return _callcatch(ui, _runcatchfunc)
310 return _callcatch(ui, _runcatchfunc)
306
311
307 def _callcatch(ui, func):
312 def _callcatch(ui, func):
308 """like scmutil.callcatch but handles more high-level exceptions about
313 """like scmutil.callcatch but handles more high-level exceptions about
309 config parsing and commands. besides, use handlecommandexception to handle
314 config parsing and commands. besides, use handlecommandexception to handle
310 uncaught exceptions.
315 uncaught exceptions.
311 """
316 """
312 try:
317 try:
313 return scmutil.callcatch(ui, func)
318 return scmutil.callcatch(ui, func)
314 except error.AmbiguousCommand as inst:
319 except error.AmbiguousCommand as inst:
315 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
320 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
316 (inst.args[0], " ".join(inst.args[1])))
321 (inst.args[0], " ".join(inst.args[1])))
317 except error.CommandError as inst:
322 except error.CommandError as inst:
318 if inst.args[0]:
323 if inst.args[0]:
319 ui.pager('help')
324 ui.pager('help')
320 msgbytes = pycompat.bytestr(inst.args[1])
325 msgbytes = pycompat.bytestr(inst.args[1])
321 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
326 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
322 commands.help_(ui, inst.args[0], full=False, command=True)
327 commands.help_(ui, inst.args[0], full=False, command=True)
323 else:
328 else:
324 ui.pager('help')
329 ui.pager('help')
325 ui.warn(_("hg: %s\n") % inst.args[1])
330 ui.warn(_("hg: %s\n") % inst.args[1])
326 commands.help_(ui, 'shortlist')
331 commands.help_(ui, 'shortlist')
327 except error.ParseError as inst:
332 except error.ParseError as inst:
328 _formatparse(ui.warn, inst)
333 _formatparse(ui.warn, inst)
329 return -1
334 return -1
330 except error.UnknownCommand as inst:
335 except error.UnknownCommand as inst:
331 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
336 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
332 try:
337 try:
333 # check if the command is in a disabled extension
338 # check if the command is in a disabled extension
334 # (but don't check for extensions themselves)
339 # (but don't check for extensions themselves)
335 formatted = help.formattedhelp(ui, commands, inst.args[0],
340 formatted = help.formattedhelp(ui, commands, inst.args[0],
336 unknowncmd=True)
341 unknowncmd=True)
337 ui.warn(nocmdmsg)
342 ui.warn(nocmdmsg)
338 ui.write(formatted)
343 ui.write(formatted)
339 except (error.UnknownCommand, error.Abort):
344 except (error.UnknownCommand, error.Abort):
340 suggested = False
345 suggested = False
341 if len(inst.args) == 2:
346 if len(inst.args) == 2:
342 sim = _getsimilar(inst.args[1], inst.args[0])
347 sim = _getsimilar(inst.args[1], inst.args[0])
343 if sim:
348 if sim:
344 ui.warn(nocmdmsg)
349 ui.warn(nocmdmsg)
345 _reportsimilar(ui.warn, sim)
350 _reportsimilar(ui.warn, sim)
346 suggested = True
351 suggested = True
347 if not suggested:
352 if not suggested:
348 ui.pager('help')
353 ui.pager('help')
349 ui.warn(nocmdmsg)
354 ui.warn(nocmdmsg)
350 commands.help_(ui, 'shortlist')
355 commands.help_(ui, 'shortlist')
351 except IOError:
356 except IOError:
352 raise
357 raise
353 except KeyboardInterrupt:
358 except KeyboardInterrupt:
354 raise
359 raise
355 except: # probably re-raises
360 except: # probably re-raises
356 if not handlecommandexception(ui):
361 if not handlecommandexception(ui):
357 raise
362 raise
358
363
359 return -1
364 return -1
360
365
361 def aliasargs(fn, givenargs):
366 def aliasargs(fn, givenargs):
362 args = []
367 args = []
363 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
368 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
364 if not util.safehasattr(fn, '_origfunc'):
369 if not util.safehasattr(fn, '_origfunc'):
365 args = getattr(fn, 'args', args)
370 args = getattr(fn, 'args', args)
366 if args:
371 if args:
367 cmd = ' '.join(map(util.shellquote, args))
372 cmd = ' '.join(map(util.shellquote, args))
368
373
369 nums = []
374 nums = []
370 def replacer(m):
375 def replacer(m):
371 num = int(m.group(1)) - 1
376 num = int(m.group(1)) - 1
372 nums.append(num)
377 nums.append(num)
373 if num < len(givenargs):
378 if num < len(givenargs):
374 return givenargs[num]
379 return givenargs[num]
375 raise error.Abort(_('too few arguments for command alias'))
380 raise error.Abort(_('too few arguments for command alias'))
376 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
381 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
377 givenargs = [x for i, x in enumerate(givenargs)
382 givenargs = [x for i, x in enumerate(givenargs)
378 if i not in nums]
383 if i not in nums]
379 args = pycompat.shlexsplit(cmd)
384 args = pycompat.shlexsplit(cmd)
380 return args + givenargs
385 return args + givenargs
381
386
382 def aliasinterpolate(name, args, cmd):
387 def aliasinterpolate(name, args, cmd):
383 '''interpolate args into cmd for shell aliases
388 '''interpolate args into cmd for shell aliases
384
389
385 This also handles $0, $@ and "$@".
390 This also handles $0, $@ and "$@".
386 '''
391 '''
387 # util.interpolate can't deal with "$@" (with quotes) because it's only
392 # util.interpolate can't deal with "$@" (with quotes) because it's only
388 # built to match prefix + patterns.
393 # built to match prefix + patterns.
389 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
394 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
390 replacemap['$0'] = name
395 replacemap['$0'] = name
391 replacemap['$$'] = '$'
396 replacemap['$$'] = '$'
392 replacemap['$@'] = ' '.join(args)
397 replacemap['$@'] = ' '.join(args)
393 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
398 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
394 # parameters, separated out into words. Emulate the same behavior here by
399 # parameters, separated out into words. Emulate the same behavior here by
395 # quoting the arguments individually. POSIX shells will then typically
400 # quoting the arguments individually. POSIX shells will then typically
396 # tokenize each argument into exactly one word.
401 # tokenize each argument into exactly one word.
397 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
402 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
398 # escape '\$' for regex
403 # escape '\$' for regex
399 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
404 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
400 r = re.compile(regex)
405 r = re.compile(regex)
401 return r.sub(lambda x: replacemap[x.group()], cmd)
406 return r.sub(lambda x: replacemap[x.group()], cmd)
402
407
403 class cmdalias(object):
408 class cmdalias(object):
404 def __init__(self, name, definition, cmdtable, source):
409 def __init__(self, name, definition, cmdtable, source):
405 self.name = self.cmd = name
410 self.name = self.cmd = name
406 self.cmdname = ''
411 self.cmdname = ''
407 self.definition = definition
412 self.definition = definition
408 self.fn = None
413 self.fn = None
409 self.givenargs = []
414 self.givenargs = []
410 self.opts = []
415 self.opts = []
411 self.help = ''
416 self.help = ''
412 self.badalias = None
417 self.badalias = None
413 self.unknowncmd = False
418 self.unknowncmd = False
414 self.source = source
419 self.source = source
415
420
416 try:
421 try:
417 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
422 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
418 for alias, e in cmdtable.iteritems():
423 for alias, e in cmdtable.iteritems():
419 if e is entry:
424 if e is entry:
420 self.cmd = alias
425 self.cmd = alias
421 break
426 break
422 self.shadows = True
427 self.shadows = True
423 except error.UnknownCommand:
428 except error.UnknownCommand:
424 self.shadows = False
429 self.shadows = False
425
430
426 if not self.definition:
431 if not self.definition:
427 self.badalias = _("no definition for alias '%s'") % self.name
432 self.badalias = _("no definition for alias '%s'") % self.name
428 return
433 return
429
434
430 if self.definition.startswith('!'):
435 if self.definition.startswith('!'):
431 self.shell = True
436 self.shell = True
432 def fn(ui, *args):
437 def fn(ui, *args):
433 env = {'HG_ARGS': ' '.join((self.name,) + args)}
438 env = {'HG_ARGS': ' '.join((self.name,) + args)}
434 def _checkvar(m):
439 def _checkvar(m):
435 if m.groups()[0] == '$':
440 if m.groups()[0] == '$':
436 return m.group()
441 return m.group()
437 elif int(m.groups()[0]) <= len(args):
442 elif int(m.groups()[0]) <= len(args):
438 return m.group()
443 return m.group()
439 else:
444 else:
440 ui.debug("No argument found for substitution "
445 ui.debug("No argument found for substitution "
441 "of %i variable in alias '%s' definition."
446 "of %i variable in alias '%s' definition."
442 % (int(m.groups()[0]), self.name))
447 % (int(m.groups()[0]), self.name))
443 return ''
448 return ''
444 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
449 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
445 cmd = aliasinterpolate(self.name, args, cmd)
450 cmd = aliasinterpolate(self.name, args, cmd)
446 return ui.system(cmd, environ=env,
451 return ui.system(cmd, environ=env,
447 blockedtag='alias_%s' % self.name)
452 blockedtag='alias_%s' % self.name)
448 self.fn = fn
453 self.fn = fn
449 return
454 return
450
455
451 try:
456 try:
452 args = pycompat.shlexsplit(self.definition)
457 args = pycompat.shlexsplit(self.definition)
453 except ValueError as inst:
458 except ValueError as inst:
454 self.badalias = (_("error in definition for alias '%s': %s")
459 self.badalias = (_("error in definition for alias '%s': %s")
455 % (self.name, inst))
460 % (self.name, inst))
456 return
461 return
457 self.cmdname = cmd = args.pop(0)
462 self.cmdname = cmd = args.pop(0)
458 self.givenargs = args
463 self.givenargs = args
459
464
460 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
465 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
461 if _earlygetopt([invalidarg], args):
466 if _earlygetopt([invalidarg], args):
462 self.badalias = (_("error in definition for alias '%s': %s may "
467 self.badalias = (_("error in definition for alias '%s': %s may "
463 "only be given on the command line")
468 "only be given on the command line")
464 % (self.name, invalidarg))
469 % (self.name, invalidarg))
465 return
470 return
466
471
467 try:
472 try:
468 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
473 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
469 if len(tableentry) > 2:
474 if len(tableentry) > 2:
470 self.fn, self.opts, self.help = tableentry
475 self.fn, self.opts, self.help = tableentry
471 else:
476 else:
472 self.fn, self.opts = tableentry
477 self.fn, self.opts = tableentry
473
478
474 if self.help.startswith("hg " + cmd):
479 if self.help.startswith("hg " + cmd):
475 # drop prefix in old-style help lines so hg shows the alias
480 # drop prefix in old-style help lines so hg shows the alias
476 self.help = self.help[4 + len(cmd):]
481 self.help = self.help[4 + len(cmd):]
477 self.__doc__ = self.fn.__doc__
482 self.__doc__ = self.fn.__doc__
478
483
479 except error.UnknownCommand:
484 except error.UnknownCommand:
480 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
485 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
481 % (self.name, cmd))
486 % (self.name, cmd))
482 self.unknowncmd = True
487 self.unknowncmd = True
483 except error.AmbiguousCommand:
488 except error.AmbiguousCommand:
484 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
489 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
485 % (self.name, cmd))
490 % (self.name, cmd))
486
491
487 @property
492 @property
488 def args(self):
493 def args(self):
489 args = pycompat.maplist(util.expandpath, self.givenargs)
494 args = pycompat.maplist(util.expandpath, self.givenargs)
490 return aliasargs(self.fn, args)
495 return aliasargs(self.fn, args)
491
496
492 def __getattr__(self, name):
497 def __getattr__(self, name):
493 adefaults = {r'norepo': True,
498 adefaults = {r'norepo': True,
494 r'optionalrepo': False, r'inferrepo': False}
499 r'optionalrepo': False, r'inferrepo': False}
495 if name not in adefaults:
500 if name not in adefaults:
496 raise AttributeError(name)
501 raise AttributeError(name)
497 if self.badalias or util.safehasattr(self, 'shell'):
502 if self.badalias or util.safehasattr(self, 'shell'):
498 return adefaults[name]
503 return adefaults[name]
499 return getattr(self.fn, name)
504 return getattr(self.fn, name)
500
505
501 def __call__(self, ui, *args, **opts):
506 def __call__(self, ui, *args, **opts):
502 if self.badalias:
507 if self.badalias:
503 hint = None
508 hint = None
504 if self.unknowncmd:
509 if self.unknowncmd:
505 try:
510 try:
506 # check if the command is in a disabled extension
511 # check if the command is in a disabled extension
507 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
512 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
508 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
513 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
509 except error.UnknownCommand:
514 except error.UnknownCommand:
510 pass
515 pass
511 raise error.Abort(self.badalias, hint=hint)
516 raise error.Abort(self.badalias, hint=hint)
512 if self.shadows:
517 if self.shadows:
513 ui.debug("alias '%s' shadows command '%s'\n" %
518 ui.debug("alias '%s' shadows command '%s'\n" %
514 (self.name, self.cmdname))
519 (self.name, self.cmdname))
515
520
516 ui.log('commandalias', "alias '%s' expands to '%s'\n",
521 ui.log('commandalias', "alias '%s' expands to '%s'\n",
517 self.name, self.definition)
522 self.name, self.definition)
518 if util.safehasattr(self, 'shell'):
523 if util.safehasattr(self, 'shell'):
519 return self.fn(ui, *args, **opts)
524 return self.fn(ui, *args, **opts)
520 else:
525 else:
521 try:
526 try:
522 return util.checksignature(self.fn)(ui, *args, **opts)
527 return util.checksignature(self.fn)(ui, *args, **opts)
523 except error.SignatureError:
528 except error.SignatureError:
524 args = ' '.join([self.cmdname] + self.args)
529 args = ' '.join([self.cmdname] + self.args)
525 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
530 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
526 raise
531 raise
527
532
528 class lazyaliasentry(object):
533 class lazyaliasentry(object):
529 """like a typical command entry (func, opts, help), but is lazy"""
534 """like a typical command entry (func, opts, help), but is lazy"""
530
535
531 def __init__(self, name, definition, cmdtable, source):
536 def __init__(self, name, definition, cmdtable, source):
532 self.name = name
537 self.name = name
533 self.definition = definition
538 self.definition = definition
534 self.cmdtable = cmdtable.copy()
539 self.cmdtable = cmdtable.copy()
535 self.source = source
540 self.source = source
536
541
537 @util.propertycache
542 @util.propertycache
538 def _aliasdef(self):
543 def _aliasdef(self):
539 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
544 return cmdalias(self.name, self.definition, self.cmdtable, self.source)
540
545
541 def __getitem__(self, n):
546 def __getitem__(self, n):
542 aliasdef = self._aliasdef
547 aliasdef = self._aliasdef
543 if n == 0:
548 if n == 0:
544 return aliasdef
549 return aliasdef
545 elif n == 1:
550 elif n == 1:
546 return aliasdef.opts
551 return aliasdef.opts
547 elif n == 2:
552 elif n == 2:
548 return aliasdef.help
553 return aliasdef.help
549 else:
554 else:
550 raise IndexError
555 raise IndexError
551
556
552 def __iter__(self):
557 def __iter__(self):
553 for i in range(3):
558 for i in range(3):
554 yield self[i]
559 yield self[i]
555
560
556 def __len__(self):
561 def __len__(self):
557 return 3
562 return 3
558
563
559 def addaliases(ui, cmdtable):
564 def addaliases(ui, cmdtable):
560 # aliases are processed after extensions have been loaded, so they
565 # aliases are processed after extensions have been loaded, so they
561 # may use extension commands. Aliases can also use other alias definitions,
566 # may use extension commands. Aliases can also use other alias definitions,
562 # but only if they have been defined prior to the current definition.
567 # but only if they have been defined prior to the current definition.
563 for alias, definition in ui.configitems('alias'):
568 for alias, definition in ui.configitems('alias'):
564 try:
569 try:
565 if cmdtable[alias].definition == definition:
570 if cmdtable[alias].definition == definition:
566 continue
571 continue
567 except (KeyError, AttributeError):
572 except (KeyError, AttributeError):
568 # definition might not exist or it might not be a cmdalias
573 # definition might not exist or it might not be a cmdalias
569 pass
574 pass
570
575
571 source = ui.configsource('alias', alias)
576 source = ui.configsource('alias', alias)
572 entry = lazyaliasentry(alias, definition, cmdtable, source)
577 entry = lazyaliasentry(alias, definition, cmdtable, source)
573 cmdtable[alias] = entry
578 cmdtable[alias] = entry
574
579
575 def _parse(ui, args):
580 def _parse(ui, args):
576 options = {}
581 options = {}
577 cmdoptions = {}
582 cmdoptions = {}
578
583
579 try:
584 try:
580 args = fancyopts.fancyopts(args, commands.globalopts, options)
585 args = fancyopts.fancyopts(args, commands.globalopts, options)
581 except getopt.GetoptError as inst:
586 except getopt.GetoptError as inst:
582 raise error.CommandError(None, inst)
587 raise error.CommandError(None, inst)
583
588
584 if args:
589 if args:
585 cmd, args = args[0], args[1:]
590 cmd, args = args[0], args[1:]
586 aliases, entry = cmdutil.findcmd(cmd, commands.table,
591 aliases, entry = cmdutil.findcmd(cmd, commands.table,
587 ui.configbool("ui", "strict"))
592 ui.configbool("ui", "strict"))
588 cmd = aliases[0]
593 cmd = aliases[0]
589 args = aliasargs(entry[0], args)
594 args = aliasargs(entry[0], args)
590 defaults = ui.config("defaults", cmd)
595 defaults = ui.config("defaults", cmd)
591 if defaults:
596 if defaults:
592 args = pycompat.maplist(
597 args = pycompat.maplist(
593 util.expandpath, pycompat.shlexsplit(defaults)) + args
598 util.expandpath, pycompat.shlexsplit(defaults)) + args
594 c = list(entry[1])
599 c = list(entry[1])
595 else:
600 else:
596 cmd = None
601 cmd = None
597 c = []
602 c = []
598
603
599 # combine global options into local
604 # combine global options into local
600 for o in commands.globalopts:
605 for o in commands.globalopts:
601 c.append((o[0], o[1], options[o[1]], o[3]))
606 c.append((o[0], o[1], options[o[1]], o[3]))
602
607
603 try:
608 try:
604 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
609 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
605 except getopt.GetoptError as inst:
610 except getopt.GetoptError as inst:
606 raise error.CommandError(cmd, inst)
611 raise error.CommandError(cmd, inst)
607
612
608 # separate global options back out
613 # separate global options back out
609 for o in commands.globalopts:
614 for o in commands.globalopts:
610 n = o[1]
615 n = o[1]
611 options[n] = cmdoptions[n]
616 options[n] = cmdoptions[n]
612 del cmdoptions[n]
617 del cmdoptions[n]
613
618
614 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
619 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
615
620
616 def _parseconfig(ui, config):
621 def _parseconfig(ui, config):
617 """parse the --config options from the command line"""
622 """parse the --config options from the command line"""
618 configs = []
623 configs = []
619
624
620 for cfg in config:
625 for cfg in config:
621 try:
626 try:
622 name, value = [cfgelem.strip()
627 name, value = [cfgelem.strip()
623 for cfgelem in cfg.split('=', 1)]
628 for cfgelem in cfg.split('=', 1)]
624 section, name = name.split('.', 1)
629 section, name = name.split('.', 1)
625 if not section or not name:
630 if not section or not name:
626 raise IndexError
631 raise IndexError
627 ui.setconfig(section, name, value, '--config')
632 ui.setconfig(section, name, value, '--config')
628 configs.append((section, name, value))
633 configs.append((section, name, value))
629 except (IndexError, ValueError):
634 except (IndexError, ValueError):
630 raise error.Abort(_('malformed --config option: %r '
635 raise error.Abort(_('malformed --config option: %r '
631 '(use --config section.name=value)') % cfg)
636 '(use --config section.name=value)') % cfg)
632
637
633 return configs
638 return configs
634
639
635 def _earlygetopt(aliases, args):
640 def _earlygetopt(aliases, args):
636 """Return list of values for an option (or aliases).
641 """Return list of values for an option (or aliases).
637
642
638 The values are listed in the order they appear in args.
643 The values are listed in the order they appear in args.
639 The options and values are removed from args.
644 The options and values are removed from args.
640
645
641 >>> args = [b'x', b'--cwd', b'foo', b'y']
646 >>> args = [b'x', b'--cwd', b'foo', b'y']
642 >>> _earlygetopt([b'--cwd'], args), args
647 >>> _earlygetopt([b'--cwd'], args), args
643 (['foo'], ['x', 'y'])
648 (['foo'], ['x', 'y'])
644
649
645 >>> args = [b'x', b'--cwd=bar', b'y']
650 >>> args = [b'x', b'--cwd=bar', b'y']
646 >>> _earlygetopt([b'--cwd'], args), args
651 >>> _earlygetopt([b'--cwd'], args), args
647 (['bar'], ['x', 'y'])
652 (['bar'], ['x', 'y'])
648
653
649 >>> args = [b'x', b'-R', b'foo', b'y']
654 >>> args = [b'x', b'-R', b'foo', b'y']
650 >>> _earlygetopt([b'-R'], args), args
655 >>> _earlygetopt([b'-R'], args), args
651 (['foo'], ['x', 'y'])
656 (['foo'], ['x', 'y'])
652
657
653 >>> args = [b'x', b'-Rbar', b'y']
658 >>> args = [b'x', b'-Rbar', b'y']
654 >>> _earlygetopt([b'-R'], args), args
659 >>> _earlygetopt([b'-R'], args), args
655 (['bar'], ['x', 'y'])
660 (['bar'], ['x', 'y'])
656 """
661 """
657 try:
662 try:
658 argcount = args.index("--")
663 argcount = args.index("--")
659 except ValueError:
664 except ValueError:
660 argcount = len(args)
665 argcount = len(args)
661 shortopts = [opt for opt in aliases if len(opt) == 2]
666 shortopts = [opt for opt in aliases if len(opt) == 2]
662 values = []
667 values = []
663 pos = 0
668 pos = 0
664 while pos < argcount:
669 while pos < argcount:
665 fullarg = arg = args[pos]
670 fullarg = arg = args[pos]
666 equals = arg.find('=')
671 equals = arg.find('=')
667 if equals > -1:
672 if equals > -1:
668 arg = arg[:equals]
673 arg = arg[:equals]
669 if arg in aliases:
674 if arg in aliases:
670 del args[pos]
675 del args[pos]
671 if equals > -1:
676 if equals > -1:
672 values.append(fullarg[equals + 1:])
677 values.append(fullarg[equals + 1:])
673 argcount -= 1
678 argcount -= 1
674 else:
679 else:
675 if pos + 1 >= argcount:
680 if pos + 1 >= argcount:
676 # ignore and let getopt report an error if there is no value
681 # ignore and let getopt report an error if there is no value
677 break
682 break
678 values.append(args.pop(pos))
683 values.append(args.pop(pos))
679 argcount -= 2
684 argcount -= 2
680 elif arg[:2] in shortopts:
685 elif arg[:2] in shortopts:
681 # short option can have no following space, e.g. hg log -Rfoo
686 # short option can have no following space, e.g. hg log -Rfoo
682 values.append(args.pop(pos)[2:])
687 values.append(args.pop(pos)[2:])
683 argcount -= 1
688 argcount -= 1
684 else:
689 else:
685 pos += 1
690 pos += 1
686 return values
691 return values
687
692
688 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
693 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
689 # run pre-hook, and abort if it fails
694 # run pre-hook, and abort if it fails
690 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
695 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
691 pats=cmdpats, opts=cmdoptions)
696 pats=cmdpats, opts=cmdoptions)
692 try:
697 try:
693 ret = _runcommand(ui, options, cmd, d)
698 ret = _runcommand(ui, options, cmd, d)
694 # run post-hook, passing command result
699 # run post-hook, passing command result
695 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
700 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
696 result=ret, pats=cmdpats, opts=cmdoptions)
701 result=ret, pats=cmdpats, opts=cmdoptions)
697 except Exception:
702 except Exception:
698 # run failure hook and re-raise
703 # run failure hook and re-raise
699 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
704 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
700 pats=cmdpats, opts=cmdoptions)
705 pats=cmdpats, opts=cmdoptions)
701 raise
706 raise
702 return ret
707 return ret
703
708
704 def _getlocal(ui, rpath, wd=None):
709 def _getlocal(ui, rpath, wd=None):
705 """Return (path, local ui object) for the given target path.
710 """Return (path, local ui object) for the given target path.
706
711
707 Takes paths in [cwd]/.hg/hgrc into account."
712 Takes paths in [cwd]/.hg/hgrc into account."
708 """
713 """
709 if wd is None:
714 if wd is None:
710 try:
715 try:
711 wd = pycompat.getcwd()
716 wd = pycompat.getcwd()
712 except OSError as e:
717 except OSError as e:
713 raise error.Abort(_("error getting current working directory: %s") %
718 raise error.Abort(_("error getting current working directory: %s") %
714 encoding.strtolocal(e.strerror))
719 encoding.strtolocal(e.strerror))
715 path = cmdutil.findrepo(wd) or ""
720 path = cmdutil.findrepo(wd) or ""
716 if not path:
721 if not path:
717 lui = ui
722 lui = ui
718 else:
723 else:
719 lui = ui.copy()
724 lui = ui.copy()
720 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
725 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
721
726
722 if rpath and rpath[-1]:
727 if rpath and rpath[-1]:
723 path = lui.expandpath(rpath[-1])
728 path = lui.expandpath(rpath[-1])
724 lui = ui.copy()
729 lui = ui.copy()
725 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
730 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
726
731
727 return path, lui
732 return path, lui
728
733
729 def _checkshellalias(lui, ui, args):
734 def _checkshellalias(lui, ui, args):
730 """Return the function to run the shell alias, if it is required"""
735 """Return the function to run the shell alias, if it is required"""
731 options = {}
736 options = {}
732
737
733 try:
738 try:
734 args = fancyopts.fancyopts(args, commands.globalopts, options)
739 args = fancyopts.fancyopts(args, commands.globalopts, options)
735 except getopt.GetoptError:
740 except getopt.GetoptError:
736 return
741 return
737
742
738 if not args:
743 if not args:
739 return
744 return
740
745
741 cmdtable = commands.table
746 cmdtable = commands.table
742
747
743 cmd = args[0]
748 cmd = args[0]
744 try:
749 try:
745 strict = ui.configbool("ui", "strict")
750 strict = ui.configbool("ui", "strict")
746 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
751 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
747 except (error.AmbiguousCommand, error.UnknownCommand):
752 except (error.AmbiguousCommand, error.UnknownCommand):
748 return
753 return
749
754
750 cmd = aliases[0]
755 cmd = aliases[0]
751 fn = entry[0]
756 fn = entry[0]
752
757
753 if cmd and util.safehasattr(fn, 'shell'):
758 if cmd and util.safehasattr(fn, 'shell'):
754 d = lambda: fn(ui, *args[1:])
759 d = lambda: fn(ui, *args[1:])
755 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
760 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
756 [], {})
761 [], {})
757
762
758 def _dispatch(req):
763 def _dispatch(req):
759 args = req.args
764 args = req.args
760 ui = req.ui
765 ui = req.ui
761
766
762 # check for cwd
767 # check for cwd
763 cwd = _earlygetopt(['--cwd'], args)
768 cwd = _earlygetopt(['--cwd'], args)
764 if cwd:
769 if cwd:
765 os.chdir(cwd[-1])
770 os.chdir(cwd[-1])
766
771
767 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
772 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
768 path, lui = _getlocal(ui, rpath)
773 path, lui = _getlocal(ui, rpath)
769
774
770 uis = {ui, lui}
775 uis = {ui, lui}
771
776
772 if req.repo:
777 if req.repo:
773 uis.add(req.repo.ui)
778 uis.add(req.repo.ui)
774
779
775 if '--profile' in args:
780 if '--profile' in args:
776 for ui_ in uis:
781 for ui_ in uis:
777 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
782 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
778
783
779 profile = lui.configbool('profiling', 'enabled')
784 profile = lui.configbool('profiling', 'enabled')
780 with profiling.profile(lui, enabled=profile) as profiler:
785 with profiling.profile(lui, enabled=profile) as profiler:
781 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
786 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
782 # reposetup
787 # reposetup
783 extensions.loadall(lui)
788 extensions.loadall(lui)
784 # Propagate any changes to lui.__class__ by extensions
789 # Propagate any changes to lui.__class__ by extensions
785 ui.__class__ = lui.__class__
790 ui.__class__ = lui.__class__
786
791
787 # (uisetup and extsetup are handled in extensions.loadall)
792 # (uisetup and extsetup are handled in extensions.loadall)
788
793
789 # (reposetup is handled in hg.repository)
794 # (reposetup is handled in hg.repository)
790
795
791 addaliases(lui, commands.table)
796 addaliases(lui, commands.table)
792
797
793 # All aliases and commands are completely defined, now.
798 # All aliases and commands are completely defined, now.
794 # Check abbreviation/ambiguity of shell alias.
799 # Check abbreviation/ambiguity of shell alias.
795 shellaliasfn = _checkshellalias(lui, ui, args)
800 shellaliasfn = _checkshellalias(lui, ui, args)
796 if shellaliasfn:
801 if shellaliasfn:
797 return shellaliasfn()
802 return shellaliasfn()
798
803
799 # check for fallback encoding
804 # check for fallback encoding
800 fallback = lui.config('ui', 'fallbackencoding')
805 fallback = lui.config('ui', 'fallbackencoding')
801 if fallback:
806 if fallback:
802 encoding.fallbackencoding = fallback
807 encoding.fallbackencoding = fallback
803
808
804 fullargs = args
809 fullargs = args
805 cmd, func, args, options, cmdoptions = _parse(lui, args)
810 cmd, func, args, options, cmdoptions = _parse(lui, args)
806
811
807 if options["config"]:
812 if options["config"]:
808 raise error.Abort(_("option --config may not be abbreviated!"))
813 raise error.Abort(_("option --config may not be abbreviated!"))
809 if options["cwd"]:
814 if options["cwd"]:
810 raise error.Abort(_("option --cwd may not be abbreviated!"))
815 raise error.Abort(_("option --cwd may not be abbreviated!"))
811 if options["repository"]:
816 if options["repository"]:
812 raise error.Abort(_(
817 raise error.Abort(_(
813 "option -R has to be separated from other options (e.g. not "
818 "option -R has to be separated from other options (e.g. not "
814 "-qR) and --repository may only be abbreviated as --repo!"))
819 "-qR) and --repository may only be abbreviated as --repo!"))
815
820
816 if options["encoding"]:
821 if options["encoding"]:
817 encoding.encoding = options["encoding"]
822 encoding.encoding = options["encoding"]
818 if options["encodingmode"]:
823 if options["encodingmode"]:
819 encoding.encodingmode = options["encodingmode"]
824 encoding.encodingmode = options["encodingmode"]
820 if options["time"]:
825 if options["time"]:
821 def get_times():
826 def get_times():
822 t = os.times()
827 t = os.times()
823 if t[4] == 0.0:
828 if t[4] == 0.0:
824 # Windows leaves this as zero, so use time.clock()
829 # Windows leaves this as zero, so use time.clock()
825 t = (t[0], t[1], t[2], t[3], time.clock())
830 t = (t[0], t[1], t[2], t[3], time.clock())
826 return t
831 return t
827 s = get_times()
832 s = get_times()
828 def print_time():
833 def print_time():
829 t = get_times()
834 t = get_times()
830 ui.warn(
835 ui.warn(
831 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
836 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
832 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
837 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
833 ui.atexit(print_time)
838 ui.atexit(print_time)
834 if options["profile"]:
839 if options["profile"]:
835 profiler.start()
840 profiler.start()
836
841
837 if options['verbose'] or options['debug'] or options['quiet']:
842 if options['verbose'] or options['debug'] or options['quiet']:
838 for opt in ('verbose', 'debug', 'quiet'):
843 for opt in ('verbose', 'debug', 'quiet'):
839 val = str(bool(options[opt]))
844 val = str(bool(options[opt]))
840 if pycompat.ispy3:
845 if pycompat.ispy3:
841 val = val.encode('ascii')
846 val = val.encode('ascii')
842 for ui_ in uis:
847 for ui_ in uis:
843 ui_.setconfig('ui', opt, val, '--' + opt)
848 ui_.setconfig('ui', opt, val, '--' + opt)
844
849
845 if options['traceback']:
850 if options['traceback']:
846 for ui_ in uis:
851 for ui_ in uis:
847 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
852 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
848
853
849 if options['noninteractive']:
854 if options['noninteractive']:
850 for ui_ in uis:
855 for ui_ in uis:
851 ui_.setconfig('ui', 'interactive', 'off', '-y')
856 ui_.setconfig('ui', 'interactive', 'off', '-y')
852
857
853 if cmdoptions.get('insecure', False):
858 if cmdoptions.get('insecure', False):
854 for ui_ in uis:
859 for ui_ in uis:
855 ui_.insecureconnections = True
860 ui_.insecureconnections = True
856
861
857 # setup color handling before pager, because setting up pager
862 # setup color handling before pager, because setting up pager
858 # might cause incorrect console information
863 # might cause incorrect console information
859 coloropt = options['color']
864 coloropt = options['color']
860 for ui_ in uis:
865 for ui_ in uis:
861 if coloropt:
866 if coloropt:
862 ui_.setconfig('ui', 'color', coloropt, '--color')
867 ui_.setconfig('ui', 'color', coloropt, '--color')
863 color.setup(ui_)
868 color.setup(ui_)
864
869
865 if util.parsebool(options['pager']):
870 if util.parsebool(options['pager']):
866 # ui.pager() expects 'internal-always-' prefix in this case
871 # ui.pager() expects 'internal-always-' prefix in this case
867 ui.pager('internal-always-' + cmd)
872 ui.pager('internal-always-' + cmd)
868 elif options['pager'] != 'auto':
873 elif options['pager'] != 'auto':
869 ui.disablepager()
874 ui.disablepager()
870
875
871 if options['version']:
876 if options['version']:
872 return commands.version_(ui)
877 return commands.version_(ui)
873 if options['help']:
878 if options['help']:
874 return commands.help_(ui, cmd, command=cmd is not None)
879 return commands.help_(ui, cmd, command=cmd is not None)
875 elif not cmd:
880 elif not cmd:
876 return commands.help_(ui, 'shortlist')
881 return commands.help_(ui, 'shortlist')
877
882
878 repo = None
883 repo = None
879 cmdpats = args[:]
884 cmdpats = args[:]
880 if not func.norepo:
885 if not func.norepo:
881 # use the repo from the request only if we don't have -R
886 # use the repo from the request only if we don't have -R
882 if not rpath and not cwd:
887 if not rpath and not cwd:
883 repo = req.repo
888 repo = req.repo
884
889
885 if repo:
890 if repo:
886 # set the descriptors of the repo ui to those of ui
891 # set the descriptors of the repo ui to those of ui
887 repo.ui.fin = ui.fin
892 repo.ui.fin = ui.fin
888 repo.ui.fout = ui.fout
893 repo.ui.fout = ui.fout
889 repo.ui.ferr = ui.ferr
894 repo.ui.ferr = ui.ferr
890 else:
895 else:
891 try:
896 try:
892 repo = hg.repository(ui, path=path,
897 repo = hg.repository(ui, path=path,
893 presetupfuncs=req.prereposetups)
898 presetupfuncs=req.prereposetups)
894 if not repo.local():
899 if not repo.local():
895 raise error.Abort(_("repository '%s' is not local")
900 raise error.Abort(_("repository '%s' is not local")
896 % path)
901 % path)
897 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
902 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
898 'repo')
903 'repo')
899 except error.RequirementError:
904 except error.RequirementError:
900 raise
905 raise
901 except error.RepoError:
906 except error.RepoError:
902 if rpath and rpath[-1]: # invalid -R path
907 if rpath and rpath[-1]: # invalid -R path
903 raise
908 raise
904 if not func.optionalrepo:
909 if not func.optionalrepo:
905 if func.inferrepo and args and not path:
910 if func.inferrepo and args and not path:
906 # try to infer -R from command args
911 # try to infer -R from command args
907 repos = map(cmdutil.findrepo, args)
912 repos = map(cmdutil.findrepo, args)
908 guess = repos[0]
913 guess = repos[0]
909 if guess and repos.count(guess) == len(repos):
914 if guess and repos.count(guess) == len(repos):
910 req.args = ['--repository', guess] + fullargs
915 req.args = ['--repository', guess] + fullargs
911 return _dispatch(req)
916 return _dispatch(req)
912 if not path:
917 if not path:
913 raise error.RepoError(_("no repository found in"
918 raise error.RepoError(_("no repository found in"
914 " '%s' (.hg not found)")
919 " '%s' (.hg not found)")
915 % pycompat.getcwd())
920 % pycompat.getcwd())
916 raise
921 raise
917 if repo:
922 if repo:
918 ui = repo.ui
923 ui = repo.ui
919 if options['hidden']:
924 if options['hidden']:
920 repo = repo.unfiltered()
925 repo = repo.unfiltered()
921 args.insert(0, repo)
926 args.insert(0, repo)
922 elif rpath:
927 elif rpath:
923 ui.warn(_("warning: --repository ignored\n"))
928 ui.warn(_("warning: --repository ignored\n"))
924
929
925 msg = _formatargs(fullargs)
930 msg = _formatargs(fullargs)
926 ui.log("command", '%s\n', msg)
931 ui.log("command", '%s\n', msg)
927 strcmdopt = pycompat.strkwargs(cmdoptions)
932 strcmdopt = pycompat.strkwargs(cmdoptions)
928 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
933 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
929 try:
934 try:
930 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
935 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
931 cmdpats, cmdoptions)
936 cmdpats, cmdoptions)
932 finally:
937 finally:
933 if repo and repo != req.repo:
938 if repo and repo != req.repo:
934 repo.close()
939 repo.close()
935
940
936 def _runcommand(ui, options, cmd, cmdfunc):
941 def _runcommand(ui, options, cmd, cmdfunc):
937 """Run a command function, possibly with profiling enabled."""
942 """Run a command function, possibly with profiling enabled."""
938 try:
943 try:
939 return cmdfunc()
944 return cmdfunc()
940 except error.SignatureError:
945 except error.SignatureError:
941 raise error.CommandError(cmd, _('invalid arguments'))
946 raise error.CommandError(cmd, _('invalid arguments'))
942
947
943 def _exceptionwarning(ui):
948 def _exceptionwarning(ui):
944 """Produce a warning message for the current active exception"""
949 """Produce a warning message for the current active exception"""
945
950
946 # For compatibility checking, we discard the portion of the hg
951 # For compatibility checking, we discard the portion of the hg
947 # version after the + on the assumption that if a "normal
952 # version after the + on the assumption that if a "normal
948 # user" is running a build with a + in it the packager
953 # user" is running a build with a + in it the packager
949 # probably built from fairly close to a tag and anyone with a
954 # probably built from fairly close to a tag and anyone with a
950 # 'make local' copy of hg (where the version number can be out
955 # 'make local' copy of hg (where the version number can be out
951 # of date) will be clueful enough to notice the implausible
956 # of date) will be clueful enough to notice the implausible
952 # version number and try updating.
957 # version number and try updating.
953 ct = util.versiontuple(n=2)
958 ct = util.versiontuple(n=2)
954 worst = None, ct, ''
959 worst = None, ct, ''
955 if ui.config('ui', 'supportcontact') is None:
960 if ui.config('ui', 'supportcontact') is None:
956 for name, mod in extensions.extensions():
961 for name, mod in extensions.extensions():
957 testedwith = getattr(mod, 'testedwith', '')
962 testedwith = getattr(mod, 'testedwith', '')
958 if pycompat.ispy3 and isinstance(testedwith, str):
963 if pycompat.ispy3 and isinstance(testedwith, str):
959 testedwith = testedwith.encode(u'utf-8')
964 testedwith = testedwith.encode(u'utf-8')
960 report = getattr(mod, 'buglink', _('the extension author.'))
965 report = getattr(mod, 'buglink', _('the extension author.'))
961 if not testedwith.strip():
966 if not testedwith.strip():
962 # We found an untested extension. It's likely the culprit.
967 # We found an untested extension. It's likely the culprit.
963 worst = name, 'unknown', report
968 worst = name, 'unknown', report
964 break
969 break
965
970
966 # Never blame on extensions bundled with Mercurial.
971 # Never blame on extensions bundled with Mercurial.
967 if extensions.ismoduleinternal(mod):
972 if extensions.ismoduleinternal(mod):
968 continue
973 continue
969
974
970 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
975 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
971 if ct in tested:
976 if ct in tested:
972 continue
977 continue
973
978
974 lower = [t for t in tested if t < ct]
979 lower = [t for t in tested if t < ct]
975 nearest = max(lower or tested)
980 nearest = max(lower or tested)
976 if worst[0] is None or nearest < worst[1]:
981 if worst[0] is None or nearest < worst[1]:
977 worst = name, nearest, report
982 worst = name, nearest, report
978 if worst[0] is not None:
983 if worst[0] is not None:
979 name, testedwith, report = worst
984 name, testedwith, report = worst
980 if not isinstance(testedwith, (bytes, str)):
985 if not isinstance(testedwith, (bytes, str)):
981 testedwith = '.'.join([str(c) for c in testedwith])
986 testedwith = '.'.join([str(c) for c in testedwith])
982 warning = (_('** Unknown exception encountered with '
987 warning = (_('** Unknown exception encountered with '
983 'possibly-broken third-party extension %s\n'
988 'possibly-broken third-party extension %s\n'
984 '** which supports versions %s of Mercurial.\n'
989 '** which supports versions %s of Mercurial.\n'
985 '** Please disable %s and try your action again.\n'
990 '** Please disable %s and try your action again.\n'
986 '** If that fixes the bug please report it to %s\n')
991 '** If that fixes the bug please report it to %s\n')
987 % (name, testedwith, name, report))
992 % (name, testedwith, name, report))
988 else:
993 else:
989 bugtracker = ui.config('ui', 'supportcontact')
994 bugtracker = ui.config('ui', 'supportcontact')
990 if bugtracker is None:
995 if bugtracker is None:
991 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
996 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
992 warning = (_("** unknown exception encountered, "
997 warning = (_("** unknown exception encountered, "
993 "please report by visiting\n** ") + bugtracker + '\n')
998 "please report by visiting\n** ") + bugtracker + '\n')
994 if pycompat.ispy3:
999 if pycompat.ispy3:
995 sysversion = sys.version.encode(u'utf-8')
1000 sysversion = sys.version.encode(u'utf-8')
996 else:
1001 else:
997 sysversion = sys.version
1002 sysversion = sys.version
998 sysversion = sysversion.replace('\n', '')
1003 sysversion = sysversion.replace('\n', '')
999 warning += ((_("** Python %s\n") % sysversion) +
1004 warning += ((_("** Python %s\n") % sysversion) +
1000 (_("** Mercurial Distributed SCM (version %s)\n") %
1005 (_("** Mercurial Distributed SCM (version %s)\n") %
1001 util.version()) +
1006 util.version()) +
1002 (_("** Extensions loaded: %s\n") %
1007 (_("** Extensions loaded: %s\n") %
1003 ", ".join([x[0] for x in extensions.extensions()])))
1008 ", ".join([x[0] for x in extensions.extensions()])))
1004 return warning
1009 return warning
1005
1010
1006 def handlecommandexception(ui):
1011 def handlecommandexception(ui):
1007 """Produce a warning message for broken commands
1012 """Produce a warning message for broken commands
1008
1013
1009 Called when handling an exception; the exception is reraised if
1014 Called when handling an exception; the exception is reraised if
1010 this function returns False, ignored otherwise.
1015 this function returns False, ignored otherwise.
1011 """
1016 """
1012 warning = _exceptionwarning(ui)
1017 warning = _exceptionwarning(ui)
1013 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1018 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1014 ui.warn(warning)
1019 ui.warn(warning)
1015 return False # re-raise the exception
1020 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now