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