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