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