##// END OF EJS Templates
dispatch: make cmdalias forward command attributes to function...
Yuya Nishihara -
r28621:d856e85a default
parent child Browse files
Show More
@@ -1,1056 +1,1055
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 atexit
10 import atexit
11 import difflib
11 import difflib
12 import errno
12 import errno
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import shlex
16 import shlex
17 import signal
17 import signal
18 import socket
18 import socket
19 import sys
19 import sys
20 import time
20 import time
21 import traceback
21 import traceback
22
22
23
23
24 from .i18n import _
24 from .i18n import _
25
25
26 from . import (
26 from . import (
27 cmdutil,
27 cmdutil,
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 fileset,
34 fileset,
35 hg,
35 hg,
36 hook,
36 hook,
37 revset,
37 revset,
38 templatekw,
38 templatekw,
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):
45 ferr=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 def run():
55 def run():
56 "run the command in sys.argv"
56 "run the command in sys.argv"
57 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
57 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
58
58
59 def _getsimilar(symbols, value):
59 def _getsimilar(symbols, value):
60 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
60 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
61 # The cutoff for similarity here is pretty arbitrary. It should
61 # The cutoff for similarity here is pretty arbitrary. It should
62 # probably be investigated and tweaked.
62 # probably be investigated and tweaked.
63 return [s for s in symbols if sim(s) > 0.6]
63 return [s for s in symbols if sim(s) > 0.6]
64
64
65 def _reportsimilar(write, similar):
65 def _reportsimilar(write, similar):
66 if len(similar) == 1:
66 if len(similar) == 1:
67 write(_("(did you mean %s?)\n") % similar[0])
67 write(_("(did you mean %s?)\n") % similar[0])
68 elif similar:
68 elif similar:
69 ss = ", ".join(sorted(similar))
69 ss = ", ".join(sorted(similar))
70 write(_("(did you mean one of %s?)\n") % ss)
70 write(_("(did you mean one of %s?)\n") % ss)
71
71
72 def _formatparse(write, inst):
72 def _formatparse(write, inst):
73 similar = []
73 similar = []
74 if isinstance(inst, error.UnknownIdentifier):
74 if isinstance(inst, error.UnknownIdentifier):
75 # make sure to check fileset first, as revset can invoke fileset
75 # make sure to check fileset first, as revset can invoke fileset
76 similar = _getsimilar(inst.symbols, inst.function)
76 similar = _getsimilar(inst.symbols, inst.function)
77 if len(inst.args) > 1:
77 if len(inst.args) > 1:
78 write(_("hg: parse error at %s: %s\n") %
78 write(_("hg: parse error at %s: %s\n") %
79 (inst.args[1], inst.args[0]))
79 (inst.args[1], inst.args[0]))
80 if (inst.args[0][0] == ' '):
80 if (inst.args[0][0] == ' '):
81 write(_("unexpected leading whitespace\n"))
81 write(_("unexpected leading whitespace\n"))
82 else:
82 else:
83 write(_("hg: parse error: %s\n") % inst.args[0])
83 write(_("hg: parse error: %s\n") % inst.args[0])
84 _reportsimilar(write, similar)
84 _reportsimilar(write, similar)
85 if inst.hint:
85 if inst.hint:
86 write(_("(%s)\n") % inst.hint)
86 write(_("(%s)\n") % inst.hint)
87
87
88 def dispatch(req):
88 def dispatch(req):
89 "run the command specified in req.args"
89 "run the command specified in req.args"
90 if req.ferr:
90 if req.ferr:
91 ferr = req.ferr
91 ferr = req.ferr
92 elif req.ui:
92 elif req.ui:
93 ferr = req.ui.ferr
93 ferr = req.ui.ferr
94 else:
94 else:
95 ferr = sys.stderr
95 ferr = sys.stderr
96
96
97 try:
97 try:
98 if not req.ui:
98 if not req.ui:
99 req.ui = uimod.ui()
99 req.ui = uimod.ui()
100 if '--traceback' in req.args:
100 if '--traceback' in req.args:
101 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
101 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
102
102
103 # set ui streams from the request
103 # set ui streams from the request
104 if req.fin:
104 if req.fin:
105 req.ui.fin = req.fin
105 req.ui.fin = req.fin
106 if req.fout:
106 if req.fout:
107 req.ui.fout = req.fout
107 req.ui.fout = req.fout
108 if req.ferr:
108 if req.ferr:
109 req.ui.ferr = req.ferr
109 req.ui.ferr = req.ferr
110 except error.Abort as inst:
110 except error.Abort as inst:
111 ferr.write(_("abort: %s\n") % inst)
111 ferr.write(_("abort: %s\n") % inst)
112 if inst.hint:
112 if inst.hint:
113 ferr.write(_("(%s)\n") % inst.hint)
113 ferr.write(_("(%s)\n") % inst.hint)
114 return -1
114 return -1
115 except error.ParseError as inst:
115 except error.ParseError as inst:
116 _formatparse(ferr.write, inst)
116 _formatparse(ferr.write, inst)
117 return -1
117 return -1
118
118
119 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
119 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
120 starttime = time.time()
120 starttime = time.time()
121 ret = None
121 ret = None
122 try:
122 try:
123 ret = _runcatch(req)
123 ret = _runcatch(req)
124 except KeyboardInterrupt:
124 except KeyboardInterrupt:
125 try:
125 try:
126 req.ui.warn(_("interrupted!\n"))
126 req.ui.warn(_("interrupted!\n"))
127 except IOError as inst:
127 except IOError as inst:
128 if inst.errno != errno.EPIPE:
128 if inst.errno != errno.EPIPE:
129 raise
129 raise
130 ret = -1
130 ret = -1
131 finally:
131 finally:
132 duration = time.time() - starttime
132 duration = time.time() - starttime
133 req.ui.flush()
133 req.ui.flush()
134 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
134 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
135 msg, ret or 0, duration)
135 msg, ret or 0, duration)
136 return ret
136 return ret
137
137
138 def _runcatch(req):
138 def _runcatch(req):
139 def catchterm(*args):
139 def catchterm(*args):
140 raise error.SignalInterrupt
140 raise error.SignalInterrupt
141
141
142 ui = req.ui
142 ui = req.ui
143 try:
143 try:
144 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
144 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
145 num = getattr(signal, name, None)
145 num = getattr(signal, name, None)
146 if num:
146 if num:
147 signal.signal(num, catchterm)
147 signal.signal(num, catchterm)
148 except ValueError:
148 except ValueError:
149 pass # happens if called in a thread
149 pass # happens if called in a thread
150
150
151 try:
151 try:
152 try:
152 try:
153 debugger = 'pdb'
153 debugger = 'pdb'
154 debugtrace = {
154 debugtrace = {
155 'pdb' : pdb.set_trace
155 'pdb' : pdb.set_trace
156 }
156 }
157 debugmortem = {
157 debugmortem = {
158 'pdb' : pdb.post_mortem
158 'pdb' : pdb.post_mortem
159 }
159 }
160
160
161 # read --config before doing anything else
161 # read --config before doing anything else
162 # (e.g. to change trust settings for reading .hg/hgrc)
162 # (e.g. to change trust settings for reading .hg/hgrc)
163 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
163 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
164
164
165 if req.repo:
165 if req.repo:
166 # copy configs that were passed on the cmdline (--config) to
166 # copy configs that were passed on the cmdline (--config) to
167 # the repo ui
167 # the repo ui
168 for sec, name, val in cfgs:
168 for sec, name, val in cfgs:
169 req.repo.ui.setconfig(sec, name, val, source='--config')
169 req.repo.ui.setconfig(sec, name, val, source='--config')
170
170
171 # developer config: ui.debugger
171 # developer config: ui.debugger
172 debugger = ui.config("ui", "debugger")
172 debugger = ui.config("ui", "debugger")
173 debugmod = pdb
173 debugmod = pdb
174 if not debugger or ui.plain():
174 if not debugger or ui.plain():
175 # if we are in HGPLAIN mode, then disable custom debugging
175 # if we are in HGPLAIN mode, then disable custom debugging
176 debugger = 'pdb'
176 debugger = 'pdb'
177 elif '--debugger' in req.args:
177 elif '--debugger' in req.args:
178 # This import can be slow for fancy debuggers, so only
178 # This import can be slow for fancy debuggers, so only
179 # do it when absolutely necessary, i.e. when actual
179 # do it when absolutely necessary, i.e. when actual
180 # debugging has been requested
180 # debugging has been requested
181 with demandimport.deactivated():
181 with demandimport.deactivated():
182 try:
182 try:
183 debugmod = __import__(debugger)
183 debugmod = __import__(debugger)
184 except ImportError:
184 except ImportError:
185 pass # Leave debugmod = pdb
185 pass # Leave debugmod = pdb
186
186
187 debugtrace[debugger] = debugmod.set_trace
187 debugtrace[debugger] = debugmod.set_trace
188 debugmortem[debugger] = debugmod.post_mortem
188 debugmortem[debugger] = debugmod.post_mortem
189
189
190 # enter the debugger before command execution
190 # enter the debugger before command execution
191 if '--debugger' in req.args:
191 if '--debugger' in req.args:
192 ui.warn(_("entering debugger - "
192 ui.warn(_("entering debugger - "
193 "type c to continue starting hg or h for help\n"))
193 "type c to continue starting hg or h for help\n"))
194
194
195 if (debugger != 'pdb' and
195 if (debugger != 'pdb' and
196 debugtrace[debugger] == debugtrace['pdb']):
196 debugtrace[debugger] == debugtrace['pdb']):
197 ui.warn(_("%s debugger specified "
197 ui.warn(_("%s debugger specified "
198 "but its module was not found\n") % debugger)
198 "but its module was not found\n") % debugger)
199 with demandimport.deactivated():
199 with demandimport.deactivated():
200 debugtrace[debugger]()
200 debugtrace[debugger]()
201 try:
201 try:
202 return _dispatch(req)
202 return _dispatch(req)
203 finally:
203 finally:
204 ui.flush()
204 ui.flush()
205 except: # re-raises
205 except: # re-raises
206 # enter the debugger when we hit an exception
206 # enter the debugger when we hit an exception
207 if '--debugger' in req.args:
207 if '--debugger' in req.args:
208 traceback.print_exc()
208 traceback.print_exc()
209 debugmortem[debugger](sys.exc_info()[2])
209 debugmortem[debugger](sys.exc_info()[2])
210 ui.traceback()
210 ui.traceback()
211 raise
211 raise
212
212
213 # Global exception handling, alphabetically
213 # Global exception handling, alphabetically
214 # Mercurial-specific first, followed by built-in and library exceptions
214 # Mercurial-specific first, followed by built-in and library exceptions
215 except error.AmbiguousCommand as inst:
215 except error.AmbiguousCommand as inst:
216 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
216 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
217 (inst.args[0], " ".join(inst.args[1])))
217 (inst.args[0], " ".join(inst.args[1])))
218 except error.ParseError as inst:
218 except error.ParseError as inst:
219 _formatparse(ui.warn, inst)
219 _formatparse(ui.warn, inst)
220 return -1
220 return -1
221 except error.LockHeld as inst:
221 except error.LockHeld as inst:
222 if inst.errno == errno.ETIMEDOUT:
222 if inst.errno == errno.ETIMEDOUT:
223 reason = _('timed out waiting for lock held by %s') % inst.locker
223 reason = _('timed out waiting for lock held by %s') % inst.locker
224 else:
224 else:
225 reason = _('lock held by %s') % inst.locker
225 reason = _('lock held by %s') % inst.locker
226 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
226 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
227 except error.LockUnavailable as inst:
227 except error.LockUnavailable as inst:
228 ui.warn(_("abort: could not lock %s: %s\n") %
228 ui.warn(_("abort: could not lock %s: %s\n") %
229 (inst.desc or inst.filename, inst.strerror))
229 (inst.desc or inst.filename, inst.strerror))
230 except error.CommandError as inst:
230 except error.CommandError as inst:
231 if inst.args[0]:
231 if inst.args[0]:
232 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
232 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
233 commands.help_(ui, inst.args[0], full=False, command=True)
233 commands.help_(ui, inst.args[0], full=False, command=True)
234 else:
234 else:
235 ui.warn(_("hg: %s\n") % inst.args[1])
235 ui.warn(_("hg: %s\n") % inst.args[1])
236 commands.help_(ui, 'shortlist')
236 commands.help_(ui, 'shortlist')
237 except error.OutOfBandError as inst:
237 except error.OutOfBandError as inst:
238 if inst.args:
238 if inst.args:
239 msg = _("abort: remote error:\n")
239 msg = _("abort: remote error:\n")
240 else:
240 else:
241 msg = _("abort: remote error\n")
241 msg = _("abort: remote error\n")
242 ui.warn(msg)
242 ui.warn(msg)
243 if inst.args:
243 if inst.args:
244 ui.warn(''.join(inst.args))
244 ui.warn(''.join(inst.args))
245 if inst.hint:
245 if inst.hint:
246 ui.warn('(%s)\n' % inst.hint)
246 ui.warn('(%s)\n' % inst.hint)
247 except error.RepoError as inst:
247 except error.RepoError as inst:
248 ui.warn(_("abort: %s!\n") % inst)
248 ui.warn(_("abort: %s!\n") % inst)
249 if inst.hint:
249 if inst.hint:
250 ui.warn(_("(%s)\n") % inst.hint)
250 ui.warn(_("(%s)\n") % inst.hint)
251 except error.ResponseError as inst:
251 except error.ResponseError as inst:
252 ui.warn(_("abort: %s") % inst.args[0])
252 ui.warn(_("abort: %s") % inst.args[0])
253 if not isinstance(inst.args[1], basestring):
253 if not isinstance(inst.args[1], basestring):
254 ui.warn(" %r\n" % (inst.args[1],))
254 ui.warn(" %r\n" % (inst.args[1],))
255 elif not inst.args[1]:
255 elif not inst.args[1]:
256 ui.warn(_(" empty string\n"))
256 ui.warn(_(" empty string\n"))
257 else:
257 else:
258 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
258 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
259 except error.CensoredNodeError as inst:
259 except error.CensoredNodeError as inst:
260 ui.warn(_("abort: file censored %s!\n") % inst)
260 ui.warn(_("abort: file censored %s!\n") % inst)
261 except error.RevlogError as inst:
261 except error.RevlogError as inst:
262 ui.warn(_("abort: %s!\n") % inst)
262 ui.warn(_("abort: %s!\n") % inst)
263 except error.SignalInterrupt:
263 except error.SignalInterrupt:
264 ui.warn(_("killed!\n"))
264 ui.warn(_("killed!\n"))
265 except error.UnknownCommand as inst:
265 except error.UnknownCommand as inst:
266 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
266 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
267 try:
267 try:
268 # check if the command is in a disabled extension
268 # check if the command is in a disabled extension
269 # (but don't check for extensions themselves)
269 # (but don't check for extensions themselves)
270 commands.help_(ui, inst.args[0], unknowncmd=True)
270 commands.help_(ui, inst.args[0], unknowncmd=True)
271 except (error.UnknownCommand, error.Abort):
271 except (error.UnknownCommand, error.Abort):
272 suggested = False
272 suggested = False
273 if len(inst.args) == 2:
273 if len(inst.args) == 2:
274 sim = _getsimilar(inst.args[1], inst.args[0])
274 sim = _getsimilar(inst.args[1], inst.args[0])
275 if sim:
275 if sim:
276 _reportsimilar(ui.warn, sim)
276 _reportsimilar(ui.warn, sim)
277 suggested = True
277 suggested = True
278 if not suggested:
278 if not suggested:
279 commands.help_(ui, 'shortlist')
279 commands.help_(ui, 'shortlist')
280 except error.InterventionRequired as inst:
280 except error.InterventionRequired as inst:
281 ui.warn("%s\n" % inst)
281 ui.warn("%s\n" % inst)
282 if inst.hint:
282 if inst.hint:
283 ui.warn(_("(%s)\n") % inst.hint)
283 ui.warn(_("(%s)\n") % inst.hint)
284 return 1
284 return 1
285 except error.Abort as inst:
285 except error.Abort as inst:
286 ui.warn(_("abort: %s\n") % inst)
286 ui.warn(_("abort: %s\n") % inst)
287 if inst.hint:
287 if inst.hint:
288 ui.warn(_("(%s)\n") % inst.hint)
288 ui.warn(_("(%s)\n") % inst.hint)
289 except ImportError as inst:
289 except ImportError as inst:
290 ui.warn(_("abort: %s!\n") % inst)
290 ui.warn(_("abort: %s!\n") % inst)
291 m = str(inst).split()[-1]
291 m = str(inst).split()[-1]
292 if m in "mpatch bdiff".split():
292 if m in "mpatch bdiff".split():
293 ui.warn(_("(did you forget to compile extensions?)\n"))
293 ui.warn(_("(did you forget to compile extensions?)\n"))
294 elif m in "zlib".split():
294 elif m in "zlib".split():
295 ui.warn(_("(is your Python install correct?)\n"))
295 ui.warn(_("(is your Python install correct?)\n"))
296 except IOError as inst:
296 except IOError as inst:
297 if util.safehasattr(inst, "code"):
297 if util.safehasattr(inst, "code"):
298 ui.warn(_("abort: %s\n") % inst)
298 ui.warn(_("abort: %s\n") % inst)
299 elif util.safehasattr(inst, "reason"):
299 elif util.safehasattr(inst, "reason"):
300 try: # usually it is in the form (errno, strerror)
300 try: # usually it is in the form (errno, strerror)
301 reason = inst.reason.args[1]
301 reason = inst.reason.args[1]
302 except (AttributeError, IndexError):
302 except (AttributeError, IndexError):
303 # it might be anything, for example a string
303 # it might be anything, for example a string
304 reason = inst.reason
304 reason = inst.reason
305 if isinstance(reason, unicode):
305 if isinstance(reason, unicode):
306 # SSLError of Python 2.7.9 contains a unicode
306 # SSLError of Python 2.7.9 contains a unicode
307 reason = reason.encode(encoding.encoding, 'replace')
307 reason = reason.encode(encoding.encoding, 'replace')
308 ui.warn(_("abort: error: %s\n") % reason)
308 ui.warn(_("abort: error: %s\n") % reason)
309 elif (util.safehasattr(inst, "args")
309 elif (util.safehasattr(inst, "args")
310 and inst.args and inst.args[0] == errno.EPIPE):
310 and inst.args and inst.args[0] == errno.EPIPE):
311 pass
311 pass
312 elif getattr(inst, "strerror", None):
312 elif getattr(inst, "strerror", None):
313 if getattr(inst, "filename", None):
313 if getattr(inst, "filename", None):
314 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
314 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
315 else:
315 else:
316 ui.warn(_("abort: %s\n") % inst.strerror)
316 ui.warn(_("abort: %s\n") % inst.strerror)
317 else:
317 else:
318 raise
318 raise
319 except OSError as inst:
319 except OSError as inst:
320 if getattr(inst, "filename", None) is not None:
320 if getattr(inst, "filename", None) is not None:
321 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
321 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
322 else:
322 else:
323 ui.warn(_("abort: %s\n") % inst.strerror)
323 ui.warn(_("abort: %s\n") % inst.strerror)
324 except KeyboardInterrupt:
324 except KeyboardInterrupt:
325 raise
325 raise
326 except MemoryError:
326 except MemoryError:
327 ui.warn(_("abort: out of memory\n"))
327 ui.warn(_("abort: out of memory\n"))
328 except SystemExit as inst:
328 except SystemExit as inst:
329 # Commands shouldn't sys.exit directly, but give a return code.
329 # Commands shouldn't sys.exit directly, but give a return code.
330 # Just in case catch this and and pass exit code to caller.
330 # Just in case catch this and and pass exit code to caller.
331 return inst.code
331 return inst.code
332 except socket.error as inst:
332 except socket.error as inst:
333 ui.warn(_("abort: %s\n") % inst.args[-1])
333 ui.warn(_("abort: %s\n") % inst.args[-1])
334 except: # re-raises
334 except: # re-raises
335 # For compatibility checking, we discard the portion of the hg
335 # For compatibility checking, we discard the portion of the hg
336 # version after the + on the assumption that if a "normal
336 # version after the + on the assumption that if a "normal
337 # user" is running a build with a + in it the packager
337 # user" is running a build with a + in it the packager
338 # probably built from fairly close to a tag and anyone with a
338 # probably built from fairly close to a tag and anyone with a
339 # 'make local' copy of hg (where the version number can be out
339 # 'make local' copy of hg (where the version number can be out
340 # of date) will be clueful enough to notice the implausible
340 # of date) will be clueful enough to notice the implausible
341 # version number and try updating.
341 # version number and try updating.
342 ct = util.versiontuple(n=2)
342 ct = util.versiontuple(n=2)
343 worst = None, ct, ''
343 worst = None, ct, ''
344 if ui.config('ui', 'supportcontact', None) is None:
344 if ui.config('ui', 'supportcontact', None) is None:
345 for name, mod in extensions.extensions():
345 for name, mod in extensions.extensions():
346 testedwith = getattr(mod, 'testedwith', '')
346 testedwith = getattr(mod, 'testedwith', '')
347 report = getattr(mod, 'buglink', _('the extension author.'))
347 report = getattr(mod, 'buglink', _('the extension author.'))
348 if not testedwith.strip():
348 if not testedwith.strip():
349 # We found an untested extension. It's likely the culprit.
349 # We found an untested extension. It's likely the culprit.
350 worst = name, 'unknown', report
350 worst = name, 'unknown', report
351 break
351 break
352
352
353 # Never blame on extensions bundled with Mercurial.
353 # Never blame on extensions bundled with Mercurial.
354 if testedwith == 'internal':
354 if testedwith == 'internal':
355 continue
355 continue
356
356
357 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
357 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
358 if ct in tested:
358 if ct in tested:
359 continue
359 continue
360
360
361 lower = [t for t in tested if t < ct]
361 lower = [t for t in tested if t < ct]
362 nearest = max(lower or tested)
362 nearest = max(lower or tested)
363 if worst[0] is None or nearest < worst[1]:
363 if worst[0] is None or nearest < worst[1]:
364 worst = name, nearest, report
364 worst = name, nearest, report
365 if worst[0] is not None:
365 if worst[0] is not None:
366 name, testedwith, report = worst
366 name, testedwith, report = worst
367 if not isinstance(testedwith, str):
367 if not isinstance(testedwith, str):
368 testedwith = '.'.join([str(c) for c in testedwith])
368 testedwith = '.'.join([str(c) for c in testedwith])
369 warning = (_('** Unknown exception encountered with '
369 warning = (_('** Unknown exception encountered with '
370 'possibly-broken third-party extension %s\n'
370 'possibly-broken third-party extension %s\n'
371 '** which supports versions %s of Mercurial.\n'
371 '** which supports versions %s of Mercurial.\n'
372 '** Please disable %s and try your action again.\n'
372 '** Please disable %s and try your action again.\n'
373 '** If that fixes the bug please report it to %s\n')
373 '** If that fixes the bug please report it to %s\n')
374 % (name, testedwith, name, report))
374 % (name, testedwith, name, report))
375 else:
375 else:
376 bugtracker = ui.config('ui', 'supportcontact', None)
376 bugtracker = ui.config('ui', 'supportcontact', None)
377 if bugtracker is None:
377 if bugtracker is None:
378 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
378 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
379 warning = (_("** unknown exception encountered, "
379 warning = (_("** unknown exception encountered, "
380 "please report by visiting\n** ") + bugtracker + '\n')
380 "please report by visiting\n** ") + bugtracker + '\n')
381 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
381 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
382 (_("** Mercurial Distributed SCM (version %s)\n") %
382 (_("** Mercurial Distributed SCM (version %s)\n") %
383 util.version()) +
383 util.version()) +
384 (_("** Extensions loaded: %s\n") %
384 (_("** Extensions loaded: %s\n") %
385 ", ".join([x[0] for x in extensions.extensions()])))
385 ", ".join([x[0] for x in extensions.extensions()])))
386 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
386 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
387 ui.warn(warning)
387 ui.warn(warning)
388 raise
388 raise
389
389
390 return -1
390 return -1
391
391
392 def aliasargs(fn, givenargs):
392 def aliasargs(fn, givenargs):
393 args = getattr(fn, 'args', [])
393 args = getattr(fn, 'args', [])
394 if args:
394 if args:
395 cmd = ' '.join(map(util.shellquote, args))
395 cmd = ' '.join(map(util.shellquote, args))
396
396
397 nums = []
397 nums = []
398 def replacer(m):
398 def replacer(m):
399 num = int(m.group(1)) - 1
399 num = int(m.group(1)) - 1
400 nums.append(num)
400 nums.append(num)
401 if num < len(givenargs):
401 if num < len(givenargs):
402 return givenargs[num]
402 return givenargs[num]
403 raise error.Abort(_('too few arguments for command alias'))
403 raise error.Abort(_('too few arguments for command alias'))
404 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
404 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
405 givenargs = [x for i, x in enumerate(givenargs)
405 givenargs = [x for i, x in enumerate(givenargs)
406 if i not in nums]
406 if i not in nums]
407 args = shlex.split(cmd)
407 args = shlex.split(cmd)
408 return args + givenargs
408 return args + givenargs
409
409
410 def aliasinterpolate(name, args, cmd):
410 def aliasinterpolate(name, args, cmd):
411 '''interpolate args into cmd for shell aliases
411 '''interpolate args into cmd for shell aliases
412
412
413 This also handles $0, $@ and "$@".
413 This also handles $0, $@ and "$@".
414 '''
414 '''
415 # util.interpolate can't deal with "$@" (with quotes) because it's only
415 # util.interpolate can't deal with "$@" (with quotes) because it's only
416 # built to match prefix + patterns.
416 # built to match prefix + patterns.
417 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
417 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
418 replacemap['$0'] = name
418 replacemap['$0'] = name
419 replacemap['$$'] = '$'
419 replacemap['$$'] = '$'
420 replacemap['$@'] = ' '.join(args)
420 replacemap['$@'] = ' '.join(args)
421 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
421 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
422 # parameters, separated out into words. Emulate the same behavior here by
422 # parameters, separated out into words. Emulate the same behavior here by
423 # quoting the arguments individually. POSIX shells will then typically
423 # quoting the arguments individually. POSIX shells will then typically
424 # tokenize each argument into exactly one word.
424 # tokenize each argument into exactly one word.
425 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
425 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
426 # escape '\$' for regex
426 # escape '\$' for regex
427 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
427 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
428 r = re.compile(regex)
428 r = re.compile(regex)
429 return r.sub(lambda x: replacemap[x.group()], cmd)
429 return r.sub(lambda x: replacemap[x.group()], cmd)
430
430
431 class cmdalias(object):
431 class cmdalias(object):
432 def __init__(self, name, definition, cmdtable):
432 def __init__(self, name, definition, cmdtable):
433 self.name = self.cmd = name
433 self.name = self.cmd = name
434 self.cmdname = ''
434 self.cmdname = ''
435 self.definition = definition
435 self.definition = definition
436 self.fn = None
436 self.fn = None
437 self.args = []
437 self.args = []
438 self.opts = []
438 self.opts = []
439 self.help = ''
439 self.help = ''
440 self.norepo = True
441 self.optionalrepo = False
442 self.inferrepo = False
443 self.badalias = None
440 self.badalias = None
444 self.unknowncmd = False
441 self.unknowncmd = False
445
442
446 try:
443 try:
447 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
444 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
448 for alias, e in cmdtable.iteritems():
445 for alias, e in cmdtable.iteritems():
449 if e is entry:
446 if e is entry:
450 self.cmd = alias
447 self.cmd = alias
451 break
448 break
452 self.shadows = True
449 self.shadows = True
453 except error.UnknownCommand:
450 except error.UnknownCommand:
454 self.shadows = False
451 self.shadows = False
455
452
456 if not self.definition:
453 if not self.definition:
457 self.badalias = _("no definition for alias '%s'") % self.name
454 self.badalias = _("no definition for alias '%s'") % self.name
458 return
455 return
459
456
460 if self.definition.startswith('!'):
457 if self.definition.startswith('!'):
461 self.shell = True
458 self.shell = True
462 def fn(ui, *args):
459 def fn(ui, *args):
463 env = {'HG_ARGS': ' '.join((self.name,) + args)}
460 env = {'HG_ARGS': ' '.join((self.name,) + args)}
464 def _checkvar(m):
461 def _checkvar(m):
465 if m.groups()[0] == '$':
462 if m.groups()[0] == '$':
466 return m.group()
463 return m.group()
467 elif int(m.groups()[0]) <= len(args):
464 elif int(m.groups()[0]) <= len(args):
468 return m.group()
465 return m.group()
469 else:
466 else:
470 ui.debug("No argument found for substitution "
467 ui.debug("No argument found for substitution "
471 "of %i variable in alias '%s' definition."
468 "of %i variable in alias '%s' definition."
472 % (int(m.groups()[0]), self.name))
469 % (int(m.groups()[0]), self.name))
473 return ''
470 return ''
474 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
471 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
475 cmd = aliasinterpolate(self.name, args, cmd)
472 cmd = aliasinterpolate(self.name, args, cmd)
476 return ui.system(cmd, environ=env)
473 return ui.system(cmd, environ=env)
477 self.fn = fn
474 self.fn = fn
478 return
475 return
479
476
480 try:
477 try:
481 args = shlex.split(self.definition)
478 args = shlex.split(self.definition)
482 except ValueError as inst:
479 except ValueError as inst:
483 self.badalias = (_("error in definition for alias '%s': %s")
480 self.badalias = (_("error in definition for alias '%s': %s")
484 % (self.name, inst))
481 % (self.name, inst))
485 return
482 return
486 self.cmdname = cmd = args.pop(0)
483 self.cmdname = cmd = args.pop(0)
487 args = map(util.expandpath, args)
484 args = map(util.expandpath, args)
488
485
489 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
486 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
490 if _earlygetopt([invalidarg], args):
487 if _earlygetopt([invalidarg], args):
491 self.badalias = (_("error in definition for alias '%s': %s may "
488 self.badalias = (_("error in definition for alias '%s': %s may "
492 "only be given on the command line")
489 "only be given on the command line")
493 % (self.name, invalidarg))
490 % (self.name, invalidarg))
494 return
491 return
495
492
496 try:
493 try:
497 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
494 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
498 if len(tableentry) > 2:
495 if len(tableentry) > 2:
499 self.fn, self.opts, self.help = tableentry
496 self.fn, self.opts, self.help = tableentry
500 else:
497 else:
501 self.fn, self.opts = tableentry
498 self.fn, self.opts = tableentry
502
499
503 self.args = aliasargs(self.fn, args)
500 self.args = aliasargs(self.fn, args)
504 if not self.fn.norepo:
505 self.norepo = False
506 if self.fn.optionalrepo:
507 self.optionalrepo = True
508 if self.fn.inferrepo:
509 self.inferrepo = True
510 if self.help.startswith("hg " + cmd):
501 if self.help.startswith("hg " + cmd):
511 # drop prefix in old-style help lines so hg shows the alias
502 # drop prefix in old-style help lines so hg shows the alias
512 self.help = self.help[4 + len(cmd):]
503 self.help = self.help[4 + len(cmd):]
513 self.__doc__ = self.fn.__doc__
504 self.__doc__ = self.fn.__doc__
514
505
515 except error.UnknownCommand:
506 except error.UnknownCommand:
516 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
507 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
517 % (self.name, cmd))
508 % (self.name, cmd))
518 self.unknowncmd = True
509 self.unknowncmd = True
519 except error.AmbiguousCommand:
510 except error.AmbiguousCommand:
520 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
511 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
521 % (self.name, cmd))
512 % (self.name, cmd))
522
513
514 def __getattr__(self, name):
515 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
516 if name not in adefaults:
517 raise AttributeError(name)
518 if self.badalias or util.safehasattr(self, 'shell'):
519 return adefaults[name]
520 return getattr(self.fn, name)
521
523 def __call__(self, ui, *args, **opts):
522 def __call__(self, ui, *args, **opts):
524 if self.badalias:
523 if self.badalias:
525 hint = None
524 hint = None
526 if self.unknowncmd:
525 if self.unknowncmd:
527 try:
526 try:
528 # check if the command is in a disabled extension
527 # check if the command is in a disabled extension
529 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
528 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
530 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
529 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
531 except error.UnknownCommand:
530 except error.UnknownCommand:
532 pass
531 pass
533 raise error.Abort(self.badalias, hint=hint)
532 raise error.Abort(self.badalias, hint=hint)
534 if self.shadows:
533 if self.shadows:
535 ui.debug("alias '%s' shadows command '%s'\n" %
534 ui.debug("alias '%s' shadows command '%s'\n" %
536 (self.name, self.cmdname))
535 (self.name, self.cmdname))
537
536
538 if util.safehasattr(self, 'shell'):
537 if util.safehasattr(self, 'shell'):
539 return self.fn(ui, *args, **opts)
538 return self.fn(ui, *args, **opts)
540 else:
539 else:
541 try:
540 try:
542 return util.checksignature(self.fn)(ui, *args, **opts)
541 return util.checksignature(self.fn)(ui, *args, **opts)
543 except error.SignatureError:
542 except error.SignatureError:
544 args = ' '.join([self.cmdname] + self.args)
543 args = ' '.join([self.cmdname] + self.args)
545 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
544 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
546 raise
545 raise
547
546
548 def addaliases(ui, cmdtable):
547 def addaliases(ui, cmdtable):
549 # aliases are processed after extensions have been loaded, so they
548 # aliases are processed after extensions have been loaded, so they
550 # may use extension commands. Aliases can also use other alias definitions,
549 # may use extension commands. Aliases can also use other alias definitions,
551 # but only if they have been defined prior to the current definition.
550 # but only if they have been defined prior to the current definition.
552 for alias, definition in ui.configitems('alias'):
551 for alias, definition in ui.configitems('alias'):
553 aliasdef = cmdalias(alias, definition, cmdtable)
552 aliasdef = cmdalias(alias, definition, cmdtable)
554
553
555 try:
554 try:
556 olddef = cmdtable[aliasdef.cmd][0]
555 olddef = cmdtable[aliasdef.cmd][0]
557 if olddef.definition == aliasdef.definition:
556 if olddef.definition == aliasdef.definition:
558 continue
557 continue
559 except (KeyError, AttributeError):
558 except (KeyError, AttributeError):
560 # definition might not exist or it might not be a cmdalias
559 # definition might not exist or it might not be a cmdalias
561 pass
560 pass
562
561
563 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
562 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
564
563
565 def _parse(ui, args):
564 def _parse(ui, args):
566 options = {}
565 options = {}
567 cmdoptions = {}
566 cmdoptions = {}
568
567
569 try:
568 try:
570 args = fancyopts.fancyopts(args, commands.globalopts, options)
569 args = fancyopts.fancyopts(args, commands.globalopts, options)
571 except fancyopts.getopt.GetoptError as inst:
570 except fancyopts.getopt.GetoptError as inst:
572 raise error.CommandError(None, inst)
571 raise error.CommandError(None, inst)
573
572
574 if args:
573 if args:
575 cmd, args = args[0], args[1:]
574 cmd, args = args[0], args[1:]
576 aliases, entry = cmdutil.findcmd(cmd, commands.table,
575 aliases, entry = cmdutil.findcmd(cmd, commands.table,
577 ui.configbool("ui", "strict"))
576 ui.configbool("ui", "strict"))
578 cmd = aliases[0]
577 cmd = aliases[0]
579 args = aliasargs(entry[0], args)
578 args = aliasargs(entry[0], args)
580 defaults = ui.config("defaults", cmd)
579 defaults = ui.config("defaults", cmd)
581 if defaults:
580 if defaults:
582 args = map(util.expandpath, shlex.split(defaults)) + args
581 args = map(util.expandpath, shlex.split(defaults)) + args
583 c = list(entry[1])
582 c = list(entry[1])
584 else:
583 else:
585 cmd = None
584 cmd = None
586 c = []
585 c = []
587
586
588 # combine global options into local
587 # combine global options into local
589 for o in commands.globalopts:
588 for o in commands.globalopts:
590 c.append((o[0], o[1], options[o[1]], o[3]))
589 c.append((o[0], o[1], options[o[1]], o[3]))
591
590
592 try:
591 try:
593 args = fancyopts.fancyopts(args, c, cmdoptions, True)
592 args = fancyopts.fancyopts(args, c, cmdoptions, True)
594 except fancyopts.getopt.GetoptError as inst:
593 except fancyopts.getopt.GetoptError as inst:
595 raise error.CommandError(cmd, inst)
594 raise error.CommandError(cmd, inst)
596
595
597 # separate global options back out
596 # separate global options back out
598 for o in commands.globalopts:
597 for o in commands.globalopts:
599 n = o[1]
598 n = o[1]
600 options[n] = cmdoptions[n]
599 options[n] = cmdoptions[n]
601 del cmdoptions[n]
600 del cmdoptions[n]
602
601
603 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
602 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
604
603
605 def _parseconfig(ui, config):
604 def _parseconfig(ui, config):
606 """parse the --config options from the command line"""
605 """parse the --config options from the command line"""
607 configs = []
606 configs = []
608
607
609 for cfg in config:
608 for cfg in config:
610 try:
609 try:
611 name, value = [cfgelem.strip()
610 name, value = [cfgelem.strip()
612 for cfgelem in cfg.split('=', 1)]
611 for cfgelem in cfg.split('=', 1)]
613 section, name = name.split('.', 1)
612 section, name = name.split('.', 1)
614 if not section or not name:
613 if not section or not name:
615 raise IndexError
614 raise IndexError
616 ui.setconfig(section, name, value, '--config')
615 ui.setconfig(section, name, value, '--config')
617 configs.append((section, name, value))
616 configs.append((section, name, value))
618 except (IndexError, ValueError):
617 except (IndexError, ValueError):
619 raise error.Abort(_('malformed --config option: %r '
618 raise error.Abort(_('malformed --config option: %r '
620 '(use --config section.name=value)') % cfg)
619 '(use --config section.name=value)') % cfg)
621
620
622 return configs
621 return configs
623
622
624 def _earlygetopt(aliases, args):
623 def _earlygetopt(aliases, args):
625 """Return list of values for an option (or aliases).
624 """Return list of values for an option (or aliases).
626
625
627 The values are listed in the order they appear in args.
626 The values are listed in the order they appear in args.
628 The options and values are removed from args.
627 The options and values are removed from args.
629
628
630 >>> args = ['x', '--cwd', 'foo', 'y']
629 >>> args = ['x', '--cwd', 'foo', 'y']
631 >>> _earlygetopt(['--cwd'], args), args
630 >>> _earlygetopt(['--cwd'], args), args
632 (['foo'], ['x', 'y'])
631 (['foo'], ['x', 'y'])
633
632
634 >>> args = ['x', '--cwd=bar', 'y']
633 >>> args = ['x', '--cwd=bar', 'y']
635 >>> _earlygetopt(['--cwd'], args), args
634 >>> _earlygetopt(['--cwd'], args), args
636 (['bar'], ['x', 'y'])
635 (['bar'], ['x', 'y'])
637
636
638 >>> args = ['x', '-R', 'foo', 'y']
637 >>> args = ['x', '-R', 'foo', 'y']
639 >>> _earlygetopt(['-R'], args), args
638 >>> _earlygetopt(['-R'], args), args
640 (['foo'], ['x', 'y'])
639 (['foo'], ['x', 'y'])
641
640
642 >>> args = ['x', '-Rbar', 'y']
641 >>> args = ['x', '-Rbar', 'y']
643 >>> _earlygetopt(['-R'], args), args
642 >>> _earlygetopt(['-R'], args), args
644 (['bar'], ['x', 'y'])
643 (['bar'], ['x', 'y'])
645 """
644 """
646 try:
645 try:
647 argcount = args.index("--")
646 argcount = args.index("--")
648 except ValueError:
647 except ValueError:
649 argcount = len(args)
648 argcount = len(args)
650 shortopts = [opt for opt in aliases if len(opt) == 2]
649 shortopts = [opt for opt in aliases if len(opt) == 2]
651 values = []
650 values = []
652 pos = 0
651 pos = 0
653 while pos < argcount:
652 while pos < argcount:
654 fullarg = arg = args[pos]
653 fullarg = arg = args[pos]
655 equals = arg.find('=')
654 equals = arg.find('=')
656 if equals > -1:
655 if equals > -1:
657 arg = arg[:equals]
656 arg = arg[:equals]
658 if arg in aliases:
657 if arg in aliases:
659 del args[pos]
658 del args[pos]
660 if equals > -1:
659 if equals > -1:
661 values.append(fullarg[equals + 1:])
660 values.append(fullarg[equals + 1:])
662 argcount -= 1
661 argcount -= 1
663 else:
662 else:
664 if pos + 1 >= argcount:
663 if pos + 1 >= argcount:
665 # ignore and let getopt report an error if there is no value
664 # ignore and let getopt report an error if there is no value
666 break
665 break
667 values.append(args.pop(pos))
666 values.append(args.pop(pos))
668 argcount -= 2
667 argcount -= 2
669 elif arg[:2] in shortopts:
668 elif arg[:2] in shortopts:
670 # short option can have no following space, e.g. hg log -Rfoo
669 # short option can have no following space, e.g. hg log -Rfoo
671 values.append(args.pop(pos)[2:])
670 values.append(args.pop(pos)[2:])
672 argcount -= 1
671 argcount -= 1
673 else:
672 else:
674 pos += 1
673 pos += 1
675 return values
674 return values
676
675
677 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
676 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
678 # run pre-hook, and abort if it fails
677 # run pre-hook, and abort if it fails
679 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
678 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
680 pats=cmdpats, opts=cmdoptions)
679 pats=cmdpats, opts=cmdoptions)
681 ret = _runcommand(ui, options, cmd, d)
680 ret = _runcommand(ui, options, cmd, d)
682 # run post-hook, passing command result
681 # run post-hook, passing command result
683 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
682 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
684 result=ret, pats=cmdpats, opts=cmdoptions)
683 result=ret, pats=cmdpats, opts=cmdoptions)
685 return ret
684 return ret
686
685
687 def _getlocal(ui, rpath, wd=None):
686 def _getlocal(ui, rpath, wd=None):
688 """Return (path, local ui object) for the given target path.
687 """Return (path, local ui object) for the given target path.
689
688
690 Takes paths in [cwd]/.hg/hgrc into account."
689 Takes paths in [cwd]/.hg/hgrc into account."
691 """
690 """
692 if wd is None:
691 if wd is None:
693 try:
692 try:
694 wd = os.getcwd()
693 wd = os.getcwd()
695 except OSError as e:
694 except OSError as e:
696 raise error.Abort(_("error getting current working directory: %s") %
695 raise error.Abort(_("error getting current working directory: %s") %
697 e.strerror)
696 e.strerror)
698 path = cmdutil.findrepo(wd) or ""
697 path = cmdutil.findrepo(wd) or ""
699 if not path:
698 if not path:
700 lui = ui
699 lui = ui
701 else:
700 else:
702 lui = ui.copy()
701 lui = ui.copy()
703 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
702 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
704
703
705 if rpath and rpath[-1]:
704 if rpath and rpath[-1]:
706 path = lui.expandpath(rpath[-1])
705 path = lui.expandpath(rpath[-1])
707 lui = ui.copy()
706 lui = ui.copy()
708 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
707 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
709
708
710 return path, lui
709 return path, lui
711
710
712 def _checkshellalias(lui, ui, args, precheck=True):
711 def _checkshellalias(lui, ui, args, precheck=True):
713 """Return the function to run the shell alias, if it is required
712 """Return the function to run the shell alias, if it is required
714
713
715 'precheck' is whether this function is invoked before adding
714 'precheck' is whether this function is invoked before adding
716 aliases or not.
715 aliases or not.
717 """
716 """
718 options = {}
717 options = {}
719
718
720 try:
719 try:
721 args = fancyopts.fancyopts(args, commands.globalopts, options)
720 args = fancyopts.fancyopts(args, commands.globalopts, options)
722 except fancyopts.getopt.GetoptError:
721 except fancyopts.getopt.GetoptError:
723 return
722 return
724
723
725 if not args:
724 if not args:
726 return
725 return
727
726
728 if precheck:
727 if precheck:
729 strict = True
728 strict = True
730 cmdtable = commands.table.copy()
729 cmdtable = commands.table.copy()
731 addaliases(lui, cmdtable)
730 addaliases(lui, cmdtable)
732 else:
731 else:
733 strict = False
732 strict = False
734 cmdtable = commands.table
733 cmdtable = commands.table
735
734
736 cmd = args[0]
735 cmd = args[0]
737 try:
736 try:
738 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
737 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
739 except (error.AmbiguousCommand, error.UnknownCommand):
738 except (error.AmbiguousCommand, error.UnknownCommand):
740 return
739 return
741
740
742 cmd = aliases[0]
741 cmd = aliases[0]
743 fn = entry[0]
742 fn = entry[0]
744
743
745 if cmd and util.safehasattr(fn, 'shell'):
744 if cmd and util.safehasattr(fn, 'shell'):
746 d = lambda: fn(ui, *args[1:])
745 d = lambda: fn(ui, *args[1:])
747 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
746 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
748 [], {})
747 [], {})
749
748
750 _loaded = set()
749 _loaded = set()
751
750
752 # list of (objname, loadermod, loadername) tuple:
751 # list of (objname, loadermod, loadername) tuple:
753 # - objname is the name of an object in extension module, from which
752 # - objname is the name of an object in extension module, from which
754 # extra information is loaded
753 # extra information is loaded
755 # - loadermod is the module where loader is placed
754 # - loadermod is the module where loader is placed
756 # - loadername is the name of the function, which takes (ui, extensionname,
755 # - loadername is the name of the function, which takes (ui, extensionname,
757 # extraobj) arguments
756 # extraobj) arguments
758 extraloaders = [
757 extraloaders = [
759 ('cmdtable', commands, 'loadcmdtable'),
758 ('cmdtable', commands, 'loadcmdtable'),
760 ('filesetpredicate', fileset, 'loadpredicate'),
759 ('filesetpredicate', fileset, 'loadpredicate'),
761 ('revsetpredicate', revset, 'loadpredicate'),
760 ('revsetpredicate', revset, 'loadpredicate'),
762 ('templatekeyword', templatekw, 'loadkeyword'),
761 ('templatekeyword', templatekw, 'loadkeyword'),
763 ]
762 ]
764
763
765 def _dispatch(req):
764 def _dispatch(req):
766 args = req.args
765 args = req.args
767 ui = req.ui
766 ui = req.ui
768
767
769 # check for cwd
768 # check for cwd
770 cwd = _earlygetopt(['--cwd'], args)
769 cwd = _earlygetopt(['--cwd'], args)
771 if cwd:
770 if cwd:
772 os.chdir(cwd[-1])
771 os.chdir(cwd[-1])
773
772
774 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
773 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
775 path, lui = _getlocal(ui, rpath)
774 path, lui = _getlocal(ui, rpath)
776
775
777 # Now that we're operating in the right directory/repository with
776 # Now that we're operating in the right directory/repository with
778 # the right config settings, check for shell aliases
777 # the right config settings, check for shell aliases
779 shellaliasfn = _checkshellalias(lui, ui, args)
778 shellaliasfn = _checkshellalias(lui, ui, args)
780 if shellaliasfn:
779 if shellaliasfn:
781 return shellaliasfn()
780 return shellaliasfn()
782
781
783 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
782 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
784 # reposetup. Programs like TortoiseHg will call _dispatch several
783 # reposetup. Programs like TortoiseHg will call _dispatch several
785 # times so we keep track of configured extensions in _loaded.
784 # times so we keep track of configured extensions in _loaded.
786 extensions.loadall(lui)
785 extensions.loadall(lui)
787 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
786 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
788 # Propagate any changes to lui.__class__ by extensions
787 # Propagate any changes to lui.__class__ by extensions
789 ui.__class__ = lui.__class__
788 ui.__class__ = lui.__class__
790
789
791 # (uisetup and extsetup are handled in extensions.loadall)
790 # (uisetup and extsetup are handled in extensions.loadall)
792
791
793 for name, module in exts:
792 for name, module in exts:
794 for objname, loadermod, loadername in extraloaders:
793 for objname, loadermod, loadername in extraloaders:
795 extraobj = getattr(module, objname, None)
794 extraobj = getattr(module, objname, None)
796 if extraobj is not None:
795 if extraobj is not None:
797 getattr(loadermod, loadername)(ui, name, extraobj)
796 getattr(loadermod, loadername)(ui, name, extraobj)
798 _loaded.add(name)
797 _loaded.add(name)
799
798
800 # (reposetup is handled in hg.repository)
799 # (reposetup is handled in hg.repository)
801
800
802 addaliases(lui, commands.table)
801 addaliases(lui, commands.table)
803
802
804 if not lui.configbool("ui", "strict"):
803 if not lui.configbool("ui", "strict"):
805 # All aliases and commands are completely defined, now.
804 # All aliases and commands are completely defined, now.
806 # Check abbreviation/ambiguity of shell alias again, because shell
805 # Check abbreviation/ambiguity of shell alias again, because shell
807 # alias may cause failure of "_parse" (see issue4355)
806 # alias may cause failure of "_parse" (see issue4355)
808 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
807 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
809 if shellaliasfn:
808 if shellaliasfn:
810 return shellaliasfn()
809 return shellaliasfn()
811
810
812 # check for fallback encoding
811 # check for fallback encoding
813 fallback = lui.config('ui', 'fallbackencoding')
812 fallback = lui.config('ui', 'fallbackencoding')
814 if fallback:
813 if fallback:
815 encoding.fallbackencoding = fallback
814 encoding.fallbackencoding = fallback
816
815
817 fullargs = args
816 fullargs = args
818 cmd, func, args, options, cmdoptions = _parse(lui, args)
817 cmd, func, args, options, cmdoptions = _parse(lui, args)
819
818
820 if options["config"]:
819 if options["config"]:
821 raise error.Abort(_("option --config may not be abbreviated!"))
820 raise error.Abort(_("option --config may not be abbreviated!"))
822 if options["cwd"]:
821 if options["cwd"]:
823 raise error.Abort(_("option --cwd may not be abbreviated!"))
822 raise error.Abort(_("option --cwd may not be abbreviated!"))
824 if options["repository"]:
823 if options["repository"]:
825 raise error.Abort(_(
824 raise error.Abort(_(
826 "option -R has to be separated from other options (e.g. not -qR) "
825 "option -R has to be separated from other options (e.g. not -qR) "
827 "and --repository may only be abbreviated as --repo!"))
826 "and --repository may only be abbreviated as --repo!"))
828
827
829 if options["encoding"]:
828 if options["encoding"]:
830 encoding.encoding = options["encoding"]
829 encoding.encoding = options["encoding"]
831 if options["encodingmode"]:
830 if options["encodingmode"]:
832 encoding.encodingmode = options["encodingmode"]
831 encoding.encodingmode = options["encodingmode"]
833 if options["time"]:
832 if options["time"]:
834 def get_times():
833 def get_times():
835 t = os.times()
834 t = os.times()
836 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
835 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
837 t = (t[0], t[1], t[2], t[3], time.clock())
836 t = (t[0], t[1], t[2], t[3], time.clock())
838 return t
837 return t
839 s = get_times()
838 s = get_times()
840 def print_time():
839 def print_time():
841 t = get_times()
840 t = get_times()
842 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
841 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
843 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
842 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
844 atexit.register(print_time)
843 atexit.register(print_time)
845
844
846 uis = set([ui, lui])
845 uis = set([ui, lui])
847
846
848 if req.repo:
847 if req.repo:
849 uis.add(req.repo.ui)
848 uis.add(req.repo.ui)
850
849
851 if options['verbose'] or options['debug'] or options['quiet']:
850 if options['verbose'] or options['debug'] or options['quiet']:
852 for opt in ('verbose', 'debug', 'quiet'):
851 for opt in ('verbose', 'debug', 'quiet'):
853 val = str(bool(options[opt]))
852 val = str(bool(options[opt]))
854 for ui_ in uis:
853 for ui_ in uis:
855 ui_.setconfig('ui', opt, val, '--' + opt)
854 ui_.setconfig('ui', opt, val, '--' + opt)
856
855
857 if options['traceback']:
856 if options['traceback']:
858 for ui_ in uis:
857 for ui_ in uis:
859 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
858 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
860
859
861 if options['noninteractive']:
860 if options['noninteractive']:
862 for ui_ in uis:
861 for ui_ in uis:
863 ui_.setconfig('ui', 'interactive', 'off', '-y')
862 ui_.setconfig('ui', 'interactive', 'off', '-y')
864
863
865 if cmdoptions.get('insecure', False):
864 if cmdoptions.get('insecure', False):
866 for ui_ in uis:
865 for ui_ in uis:
867 ui_.setconfig('web', 'cacerts', '!', '--insecure')
866 ui_.setconfig('web', 'cacerts', '!', '--insecure')
868
867
869 if options['version']:
868 if options['version']:
870 return commands.version_(ui)
869 return commands.version_(ui)
871 if options['help']:
870 if options['help']:
872 return commands.help_(ui, cmd, command=cmd is not None)
871 return commands.help_(ui, cmd, command=cmd is not None)
873 elif not cmd:
872 elif not cmd:
874 return commands.help_(ui, 'shortlist')
873 return commands.help_(ui, 'shortlist')
875
874
876 repo = None
875 repo = None
877 cmdpats = args[:]
876 cmdpats = args[:]
878 if not func.norepo:
877 if not func.norepo:
879 # use the repo from the request only if we don't have -R
878 # use the repo from the request only if we don't have -R
880 if not rpath and not cwd:
879 if not rpath and not cwd:
881 repo = req.repo
880 repo = req.repo
882
881
883 if repo:
882 if repo:
884 # set the descriptors of the repo ui to those of ui
883 # set the descriptors of the repo ui to those of ui
885 repo.ui.fin = ui.fin
884 repo.ui.fin = ui.fin
886 repo.ui.fout = ui.fout
885 repo.ui.fout = ui.fout
887 repo.ui.ferr = ui.ferr
886 repo.ui.ferr = ui.ferr
888 else:
887 else:
889 try:
888 try:
890 repo = hg.repository(ui, path=path)
889 repo = hg.repository(ui, path=path)
891 if not repo.local():
890 if not repo.local():
892 raise error.Abort(_("repository '%s' is not local") % path)
891 raise error.Abort(_("repository '%s' is not local") % path)
893 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
892 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
894 except error.RequirementError:
893 except error.RequirementError:
895 raise
894 raise
896 except error.RepoError:
895 except error.RepoError:
897 if rpath and rpath[-1]: # invalid -R path
896 if rpath and rpath[-1]: # invalid -R path
898 raise
897 raise
899 if not func.optionalrepo:
898 if not func.optionalrepo:
900 if func.inferrepo and args and not path:
899 if func.inferrepo and args and not path:
901 # try to infer -R from command args
900 # try to infer -R from command args
902 repos = map(cmdutil.findrepo, args)
901 repos = map(cmdutil.findrepo, args)
903 guess = repos[0]
902 guess = repos[0]
904 if guess and repos.count(guess) == len(repos):
903 if guess and repos.count(guess) == len(repos):
905 req.args = ['--repository', guess] + fullargs
904 req.args = ['--repository', guess] + fullargs
906 return _dispatch(req)
905 return _dispatch(req)
907 if not path:
906 if not path:
908 raise error.RepoError(_("no repository found in '%s'"
907 raise error.RepoError(_("no repository found in '%s'"
909 " (.hg not found)")
908 " (.hg not found)")
910 % os.getcwd())
909 % os.getcwd())
911 raise
910 raise
912 if repo:
911 if repo:
913 ui = repo.ui
912 ui = repo.ui
914 if options['hidden']:
913 if options['hidden']:
915 repo = repo.unfiltered()
914 repo = repo.unfiltered()
916 args.insert(0, repo)
915 args.insert(0, repo)
917 elif rpath:
916 elif rpath:
918 ui.warn(_("warning: --repository ignored\n"))
917 ui.warn(_("warning: --repository ignored\n"))
919
918
920 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
919 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
921 ui.log("command", '%s\n', msg)
920 ui.log("command", '%s\n', msg)
922 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
921 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
923 try:
922 try:
924 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
923 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
925 cmdpats, cmdoptions)
924 cmdpats, cmdoptions)
926 finally:
925 finally:
927 if repo and repo != req.repo:
926 if repo and repo != req.repo:
928 repo.close()
927 repo.close()
929
928
930 def lsprofile(ui, func, fp):
929 def lsprofile(ui, func, fp):
931 format = ui.config('profiling', 'format', default='text')
930 format = ui.config('profiling', 'format', default='text')
932 field = ui.config('profiling', 'sort', default='inlinetime')
931 field = ui.config('profiling', 'sort', default='inlinetime')
933 limit = ui.configint('profiling', 'limit', default=30)
932 limit = ui.configint('profiling', 'limit', default=30)
934 climit = ui.configint('profiling', 'nested', default=0)
933 climit = ui.configint('profiling', 'nested', default=0)
935
934
936 if format not in ['text', 'kcachegrind']:
935 if format not in ['text', 'kcachegrind']:
937 ui.warn(_("unrecognized profiling format '%s'"
936 ui.warn(_("unrecognized profiling format '%s'"
938 " - Ignored\n") % format)
937 " - Ignored\n") % format)
939 format = 'text'
938 format = 'text'
940
939
941 try:
940 try:
942 from . import lsprof
941 from . import lsprof
943 except ImportError:
942 except ImportError:
944 raise error.Abort(_(
943 raise error.Abort(_(
945 'lsprof not available - install from '
944 'lsprof not available - install from '
946 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
945 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
947 p = lsprof.Profiler()
946 p = lsprof.Profiler()
948 p.enable(subcalls=True)
947 p.enable(subcalls=True)
949 try:
948 try:
950 return func()
949 return func()
951 finally:
950 finally:
952 p.disable()
951 p.disable()
953
952
954 if format == 'kcachegrind':
953 if format == 'kcachegrind':
955 from . import lsprofcalltree
954 from . import lsprofcalltree
956 calltree = lsprofcalltree.KCacheGrind(p)
955 calltree = lsprofcalltree.KCacheGrind(p)
957 calltree.output(fp)
956 calltree.output(fp)
958 else:
957 else:
959 # format == 'text'
958 # format == 'text'
960 stats = lsprof.Stats(p.getstats())
959 stats = lsprof.Stats(p.getstats())
961 stats.sort(field)
960 stats.sort(field)
962 stats.pprint(limit=limit, file=fp, climit=climit)
961 stats.pprint(limit=limit, file=fp, climit=climit)
963
962
964 def flameprofile(ui, func, fp):
963 def flameprofile(ui, func, fp):
965 try:
964 try:
966 from flamegraph import flamegraph
965 from flamegraph import flamegraph
967 except ImportError:
966 except ImportError:
968 raise error.Abort(_(
967 raise error.Abort(_(
969 'flamegraph not available - install from '
968 'flamegraph not available - install from '
970 'https://github.com/evanhempel/python-flamegraph'))
969 'https://github.com/evanhempel/python-flamegraph'))
971 # developer config: profiling.freq
970 # developer config: profiling.freq
972 freq = ui.configint('profiling', 'freq', default=1000)
971 freq = ui.configint('profiling', 'freq', default=1000)
973 filter_ = None
972 filter_ = None
974 collapse_recursion = True
973 collapse_recursion = True
975 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
974 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
976 filter_, collapse_recursion)
975 filter_, collapse_recursion)
977 start_time = time.clock()
976 start_time = time.clock()
978 try:
977 try:
979 thread.start()
978 thread.start()
980 func()
979 func()
981 finally:
980 finally:
982 thread.stop()
981 thread.stop()
983 thread.join()
982 thread.join()
984 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
983 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
985 time.clock() - start_time, thread.num_frames(),
984 time.clock() - start_time, thread.num_frames(),
986 thread.num_frames(unique=True)))
985 thread.num_frames(unique=True)))
987
986
988
987
989 def statprofile(ui, func, fp):
988 def statprofile(ui, func, fp):
990 try:
989 try:
991 import statprof
990 import statprof
992 except ImportError:
991 except ImportError:
993 raise error.Abort(_(
992 raise error.Abort(_(
994 'statprof not available - install using "easy_install statprof"'))
993 'statprof not available - install using "easy_install statprof"'))
995
994
996 freq = ui.configint('profiling', 'freq', default=1000)
995 freq = ui.configint('profiling', 'freq', default=1000)
997 if freq > 0:
996 if freq > 0:
998 statprof.reset(freq)
997 statprof.reset(freq)
999 else:
998 else:
1000 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
999 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
1001
1000
1002 statprof.start()
1001 statprof.start()
1003 try:
1002 try:
1004 return func()
1003 return func()
1005 finally:
1004 finally:
1006 statprof.stop()
1005 statprof.stop()
1007 statprof.display(fp)
1006 statprof.display(fp)
1008
1007
1009 def _runcommand(ui, options, cmd, cmdfunc):
1008 def _runcommand(ui, options, cmd, cmdfunc):
1010 """Enables the profiler if applicable.
1009 """Enables the profiler if applicable.
1011
1010
1012 ``profiling.enabled`` - boolean config that enables or disables profiling
1011 ``profiling.enabled`` - boolean config that enables or disables profiling
1013 """
1012 """
1014 def checkargs():
1013 def checkargs():
1015 try:
1014 try:
1016 return cmdfunc()
1015 return cmdfunc()
1017 except error.SignatureError:
1016 except error.SignatureError:
1018 raise error.CommandError(cmd, _("invalid arguments"))
1017 raise error.CommandError(cmd, _("invalid arguments"))
1019
1018
1020 if options['profile'] or ui.configbool('profiling', 'enabled'):
1019 if options['profile'] or ui.configbool('profiling', 'enabled'):
1021 profiler = os.getenv('HGPROF')
1020 profiler = os.getenv('HGPROF')
1022 if profiler is None:
1021 if profiler is None:
1023 profiler = ui.config('profiling', 'type', default='ls')
1022 profiler = ui.config('profiling', 'type', default='ls')
1024 if profiler not in ('ls', 'stat', 'flame'):
1023 if profiler not in ('ls', 'stat', 'flame'):
1025 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
1024 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
1026 profiler = 'ls'
1025 profiler = 'ls'
1027
1026
1028 output = ui.config('profiling', 'output')
1027 output = ui.config('profiling', 'output')
1029
1028
1030 if output == 'blackbox':
1029 if output == 'blackbox':
1031 import StringIO
1030 import StringIO
1032 fp = StringIO.StringIO()
1031 fp = StringIO.StringIO()
1033 elif output:
1032 elif output:
1034 path = ui.expandpath(output)
1033 path = ui.expandpath(output)
1035 fp = open(path, 'wb')
1034 fp = open(path, 'wb')
1036 else:
1035 else:
1037 fp = sys.stderr
1036 fp = sys.stderr
1038
1037
1039 try:
1038 try:
1040 if profiler == 'ls':
1039 if profiler == 'ls':
1041 return lsprofile(ui, checkargs, fp)
1040 return lsprofile(ui, checkargs, fp)
1042 elif profiler == 'flame':
1041 elif profiler == 'flame':
1043 return flameprofile(ui, checkargs, fp)
1042 return flameprofile(ui, checkargs, fp)
1044 else:
1043 else:
1045 return statprofile(ui, checkargs, fp)
1044 return statprofile(ui, checkargs, fp)
1046 finally:
1045 finally:
1047 if output:
1046 if output:
1048 if output == 'blackbox':
1047 if output == 'blackbox':
1049 val = "Profile:\n%s" % fp.getvalue()
1048 val = "Profile:\n%s" % fp.getvalue()
1050 # ui.log treats the input as a format string,
1049 # ui.log treats the input as a format string,
1051 # so we need to escape any % signs.
1050 # so we need to escape any % signs.
1052 val = val.replace('%', '%%')
1051 val = val.replace('%', '%%')
1053 ui.log('profile', val)
1052 ui.log('profile', val)
1054 fp.close()
1053 fp.close()
1055 else:
1054 else:
1056 return checkargs()
1055 return checkargs()
General Comments 0
You need to be logged in to leave comments. Login now