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