##// END OF EJS Templates
registrar: add templatekeyword to mark a function as template keyword (API)...
FUJIWARA Katsunori -
r28538:009f58f1 default
parent child Browse files
Show More
@@ -1,1054 +1,1056 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 templatekw,
38 ui as uimod,
39 ui as uimod,
39 util,
40 util,
40 )
41 )
41
42
42 class request(object):
43 class request(object):
43 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
44 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
44 ferr=None):
45 ferr=None):
45 self.args = args
46 self.args = args
46 self.ui = ui
47 self.ui = ui
47 self.repo = repo
48 self.repo = repo
48
49
49 # input/output/error streams
50 # input/output/error streams
50 self.fin = fin
51 self.fin = fin
51 self.fout = fout
52 self.fout = fout
52 self.ferr = ferr
53 self.ferr = ferr
53
54
54 def run():
55 def run():
55 "run the command in sys.argv"
56 "run the command in sys.argv"
56 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
57 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
57
58
58 def _getsimilar(symbols, value):
59 def _getsimilar(symbols, value):
59 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
60 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
60 # The cutoff for similarity here is pretty arbitrary. It should
61 # The cutoff for similarity here is pretty arbitrary. It should
61 # probably be investigated and tweaked.
62 # probably be investigated and tweaked.
62 return [s for s in symbols if sim(s) > 0.6]
63 return [s for s in symbols if sim(s) > 0.6]
63
64
64 def _reportsimilar(write, similar):
65 def _reportsimilar(write, similar):
65 if len(similar) == 1:
66 if len(similar) == 1:
66 write(_("(did you mean %s?)\n") % similar[0])
67 write(_("(did you mean %s?)\n") % similar[0])
67 elif similar:
68 elif similar:
68 ss = ", ".join(sorted(similar))
69 ss = ", ".join(sorted(similar))
69 write(_("(did you mean one of %s?)\n") % ss)
70 write(_("(did you mean one of %s?)\n") % ss)
70
71
71 def _formatparse(write, inst):
72 def _formatparse(write, inst):
72 similar = []
73 similar = []
73 if isinstance(inst, error.UnknownIdentifier):
74 if isinstance(inst, error.UnknownIdentifier):
74 # make sure to check fileset first, as revset can invoke fileset
75 # make sure to check fileset first, as revset can invoke fileset
75 similar = _getsimilar(inst.symbols, inst.function)
76 similar = _getsimilar(inst.symbols, inst.function)
76 if len(inst.args) > 1:
77 if len(inst.args) > 1:
77 write(_("hg: parse error at %s: %s\n") %
78 write(_("hg: parse error at %s: %s\n") %
78 (inst.args[1], inst.args[0]))
79 (inst.args[1], inst.args[0]))
79 if (inst.args[0][0] == ' '):
80 if (inst.args[0][0] == ' '):
80 write(_("unexpected leading whitespace\n"))
81 write(_("unexpected leading whitespace\n"))
81 else:
82 else:
82 write(_("hg: parse error: %s\n") % inst.args[0])
83 write(_("hg: parse error: %s\n") % inst.args[0])
83 _reportsimilar(write, similar)
84 _reportsimilar(write, similar)
84 if inst.hint:
85 if inst.hint:
85 write(_("(%s)\n") % inst.hint)
86 write(_("(%s)\n") % inst.hint)
86
87
87 def dispatch(req):
88 def dispatch(req):
88 "run the command specified in req.args"
89 "run the command specified in req.args"
89 if req.ferr:
90 if req.ferr:
90 ferr = req.ferr
91 ferr = req.ferr
91 elif req.ui:
92 elif req.ui:
92 ferr = req.ui.ferr
93 ferr = req.ui.ferr
93 else:
94 else:
94 ferr = sys.stderr
95 ferr = sys.stderr
95
96
96 try:
97 try:
97 if not req.ui:
98 if not req.ui:
98 req.ui = uimod.ui()
99 req.ui = uimod.ui()
99 if '--traceback' in req.args:
100 if '--traceback' in req.args:
100 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
101 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
101
102
102 # set ui streams from the request
103 # set ui streams from the request
103 if req.fin:
104 if req.fin:
104 req.ui.fin = req.fin
105 req.ui.fin = req.fin
105 if req.fout:
106 if req.fout:
106 req.ui.fout = req.fout
107 req.ui.fout = req.fout
107 if req.ferr:
108 if req.ferr:
108 req.ui.ferr = req.ferr
109 req.ui.ferr = req.ferr
109 except error.Abort as inst:
110 except error.Abort as inst:
110 ferr.write(_("abort: %s\n") % inst)
111 ferr.write(_("abort: %s\n") % inst)
111 if inst.hint:
112 if inst.hint:
112 ferr.write(_("(%s)\n") % inst.hint)
113 ferr.write(_("(%s)\n") % inst.hint)
113 return -1
114 return -1
114 except error.ParseError as inst:
115 except error.ParseError as inst:
115 _formatparse(ferr.write, inst)
116 _formatparse(ferr.write, inst)
116 return -1
117 return -1
117
118
118 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
119 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
119 starttime = time.time()
120 starttime = time.time()
120 ret = None
121 ret = None
121 try:
122 try:
122 ret = _runcatch(req)
123 ret = _runcatch(req)
123 except KeyboardInterrupt:
124 except KeyboardInterrupt:
124 try:
125 try:
125 req.ui.warn(_("interrupted!\n"))
126 req.ui.warn(_("interrupted!\n"))
126 except IOError as inst:
127 except IOError as inst:
127 if inst.errno != errno.EPIPE:
128 if inst.errno != errno.EPIPE:
128 raise
129 raise
129 ret = -1
130 ret = -1
130 finally:
131 finally:
131 duration = time.time() - starttime
132 duration = time.time() - starttime
132 req.ui.flush()
133 req.ui.flush()
133 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
134 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
134 msg, ret or 0, duration)
135 msg, ret or 0, duration)
135 return ret
136 return ret
136
137
137 def _runcatch(req):
138 def _runcatch(req):
138 def catchterm(*args):
139 def catchterm(*args):
139 raise error.SignalInterrupt
140 raise error.SignalInterrupt
140
141
141 ui = req.ui
142 ui = req.ui
142 try:
143 try:
143 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
144 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
144 num = getattr(signal, name, None)
145 num = getattr(signal, name, None)
145 if num:
146 if num:
146 signal.signal(num, catchterm)
147 signal.signal(num, catchterm)
147 except ValueError:
148 except ValueError:
148 pass # happens if called in a thread
149 pass # happens if called in a thread
149
150
150 try:
151 try:
151 try:
152 try:
152 debugger = 'pdb'
153 debugger = 'pdb'
153 debugtrace = {
154 debugtrace = {
154 'pdb' : pdb.set_trace
155 'pdb' : pdb.set_trace
155 }
156 }
156 debugmortem = {
157 debugmortem = {
157 'pdb' : pdb.post_mortem
158 'pdb' : pdb.post_mortem
158 }
159 }
159
160
160 # read --config before doing anything else
161 # read --config before doing anything else
161 # (e.g. to change trust settings for reading .hg/hgrc)
162 # (e.g. to change trust settings for reading .hg/hgrc)
162 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
163 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
163
164
164 if req.repo:
165 if req.repo:
165 # copy configs that were passed on the cmdline (--config) to
166 # copy configs that were passed on the cmdline (--config) to
166 # the repo ui
167 # the repo ui
167 for sec, name, val in cfgs:
168 for sec, name, val in cfgs:
168 req.repo.ui.setconfig(sec, name, val, source='--config')
169 req.repo.ui.setconfig(sec, name, val, source='--config')
169
170
170 # developer config: ui.debugger
171 # developer config: ui.debugger
171 debugger = ui.config("ui", "debugger")
172 debugger = ui.config("ui", "debugger")
172 debugmod = pdb
173 debugmod = pdb
173 if not debugger or ui.plain():
174 if not debugger or ui.plain():
174 # if we are in HGPLAIN mode, then disable custom debugging
175 # if we are in HGPLAIN mode, then disable custom debugging
175 debugger = 'pdb'
176 debugger = 'pdb'
176 elif '--debugger' in req.args:
177 elif '--debugger' in req.args:
177 # This import can be slow for fancy debuggers, so only
178 # This import can be slow for fancy debuggers, so only
178 # do it when absolutely necessary, i.e. when actual
179 # do it when absolutely necessary, i.e. when actual
179 # debugging has been requested
180 # debugging has been requested
180 with demandimport.deactivated():
181 with demandimport.deactivated():
181 try:
182 try:
182 debugmod = __import__(debugger)
183 debugmod = __import__(debugger)
183 except ImportError:
184 except ImportError:
184 pass # Leave debugmod = pdb
185 pass # Leave debugmod = pdb
185
186
186 debugtrace[debugger] = debugmod.set_trace
187 debugtrace[debugger] = debugmod.set_trace
187 debugmortem[debugger] = debugmod.post_mortem
188 debugmortem[debugger] = debugmod.post_mortem
188
189
189 # enter the debugger before command execution
190 # enter the debugger before command execution
190 if '--debugger' in req.args:
191 if '--debugger' in req.args:
191 ui.warn(_("entering debugger - "
192 ui.warn(_("entering debugger - "
192 "type c to continue starting hg or h for help\n"))
193 "type c to continue starting hg or h for help\n"))
193
194
194 if (debugger != 'pdb' and
195 if (debugger != 'pdb' and
195 debugtrace[debugger] == debugtrace['pdb']):
196 debugtrace[debugger] == debugtrace['pdb']):
196 ui.warn(_("%s debugger specified "
197 ui.warn(_("%s debugger specified "
197 "but its module was not found\n") % debugger)
198 "but its module was not found\n") % debugger)
198 with demandimport.deactivated():
199 with demandimport.deactivated():
199 debugtrace[debugger]()
200 debugtrace[debugger]()
200 try:
201 try:
201 return _dispatch(req)
202 return _dispatch(req)
202 finally:
203 finally:
203 ui.flush()
204 ui.flush()
204 except: # re-raises
205 except: # re-raises
205 # enter the debugger when we hit an exception
206 # enter the debugger when we hit an exception
206 if '--debugger' in req.args:
207 if '--debugger' in req.args:
207 traceback.print_exc()
208 traceback.print_exc()
208 debugmortem[debugger](sys.exc_info()[2])
209 debugmortem[debugger](sys.exc_info()[2])
209 ui.traceback()
210 ui.traceback()
210 raise
211 raise
211
212
212 # Global exception handling, alphabetically
213 # Global exception handling, alphabetically
213 # Mercurial-specific first, followed by built-in and library exceptions
214 # Mercurial-specific first, followed by built-in and library exceptions
214 except error.AmbiguousCommand as inst:
215 except error.AmbiguousCommand as inst:
215 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
216 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
216 (inst.args[0], " ".join(inst.args[1])))
217 (inst.args[0], " ".join(inst.args[1])))
217 except error.ParseError as inst:
218 except error.ParseError as inst:
218 _formatparse(ui.warn, inst)
219 _formatparse(ui.warn, inst)
219 return -1
220 return -1
220 except error.LockHeld as inst:
221 except error.LockHeld as inst:
221 if inst.errno == errno.ETIMEDOUT:
222 if inst.errno == errno.ETIMEDOUT:
222 reason = _('timed out waiting for lock held by %s') % inst.locker
223 reason = _('timed out waiting for lock held by %s') % inst.locker
223 else:
224 else:
224 reason = _('lock held by %s') % inst.locker
225 reason = _('lock held by %s') % inst.locker
225 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
226 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
226 except error.LockUnavailable as inst:
227 except error.LockUnavailable as inst:
227 ui.warn(_("abort: could not lock %s: %s\n") %
228 ui.warn(_("abort: could not lock %s: %s\n") %
228 (inst.desc or inst.filename, inst.strerror))
229 (inst.desc or inst.filename, inst.strerror))
229 except error.CommandError as inst:
230 except error.CommandError as inst:
230 if inst.args[0]:
231 if inst.args[0]:
231 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
232 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
232 commands.help_(ui, inst.args[0], full=False, command=True)
233 commands.help_(ui, inst.args[0], full=False, command=True)
233 else:
234 else:
234 ui.warn(_("hg: %s\n") % inst.args[1])
235 ui.warn(_("hg: %s\n") % inst.args[1])
235 commands.help_(ui, 'shortlist')
236 commands.help_(ui, 'shortlist')
236 except error.OutOfBandError as inst:
237 except error.OutOfBandError as inst:
237 if inst.args:
238 if inst.args:
238 msg = _("abort: remote error:\n")
239 msg = _("abort: remote error:\n")
239 else:
240 else:
240 msg = _("abort: remote error\n")
241 msg = _("abort: remote error\n")
241 ui.warn(msg)
242 ui.warn(msg)
242 if inst.args:
243 if inst.args:
243 ui.warn(''.join(inst.args))
244 ui.warn(''.join(inst.args))
244 if inst.hint:
245 if inst.hint:
245 ui.warn('(%s)\n' % inst.hint)
246 ui.warn('(%s)\n' % inst.hint)
246 except error.RepoError as inst:
247 except error.RepoError as inst:
247 ui.warn(_("abort: %s!\n") % inst)
248 ui.warn(_("abort: %s!\n") % inst)
248 if inst.hint:
249 if inst.hint:
249 ui.warn(_("(%s)\n") % inst.hint)
250 ui.warn(_("(%s)\n") % inst.hint)
250 except error.ResponseError as inst:
251 except error.ResponseError as inst:
251 ui.warn(_("abort: %s") % inst.args[0])
252 ui.warn(_("abort: %s") % inst.args[0])
252 if not isinstance(inst.args[1], basestring):
253 if not isinstance(inst.args[1], basestring):
253 ui.warn(" %r\n" % (inst.args[1],))
254 ui.warn(" %r\n" % (inst.args[1],))
254 elif not inst.args[1]:
255 elif not inst.args[1]:
255 ui.warn(_(" empty string\n"))
256 ui.warn(_(" empty string\n"))
256 else:
257 else:
257 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
258 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
258 except error.CensoredNodeError as inst:
259 except error.CensoredNodeError as inst:
259 ui.warn(_("abort: file censored %s!\n") % inst)
260 ui.warn(_("abort: file censored %s!\n") % inst)
260 except error.RevlogError as inst:
261 except error.RevlogError as inst:
261 ui.warn(_("abort: %s!\n") % inst)
262 ui.warn(_("abort: %s!\n") % inst)
262 except error.SignalInterrupt:
263 except error.SignalInterrupt:
263 ui.warn(_("killed!\n"))
264 ui.warn(_("killed!\n"))
264 except error.UnknownCommand as inst:
265 except error.UnknownCommand as inst:
265 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
266 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
266 try:
267 try:
267 # check if the command is in a disabled extension
268 # check if the command is in a disabled extension
268 # (but don't check for extensions themselves)
269 # (but don't check for extensions themselves)
269 commands.help_(ui, inst.args[0], unknowncmd=True)
270 commands.help_(ui, inst.args[0], unknowncmd=True)
270 except (error.UnknownCommand, error.Abort):
271 except (error.UnknownCommand, error.Abort):
271 suggested = False
272 suggested = False
272 if len(inst.args) == 2:
273 if len(inst.args) == 2:
273 sim = _getsimilar(inst.args[1], inst.args[0])
274 sim = _getsimilar(inst.args[1], inst.args[0])
274 if sim:
275 if sim:
275 _reportsimilar(ui.warn, sim)
276 _reportsimilar(ui.warn, sim)
276 suggested = True
277 suggested = True
277 if not suggested:
278 if not suggested:
278 commands.help_(ui, 'shortlist')
279 commands.help_(ui, 'shortlist')
279 except error.InterventionRequired as inst:
280 except error.InterventionRequired as inst:
280 ui.warn("%s\n" % inst)
281 ui.warn("%s\n" % inst)
281 if inst.hint:
282 if inst.hint:
282 ui.warn(_("(%s)\n") % inst.hint)
283 ui.warn(_("(%s)\n") % inst.hint)
283 return 1
284 return 1
284 except error.Abort as inst:
285 except error.Abort as inst:
285 ui.warn(_("abort: %s\n") % inst)
286 ui.warn(_("abort: %s\n") % inst)
286 if inst.hint:
287 if inst.hint:
287 ui.warn(_("(%s)\n") % inst.hint)
288 ui.warn(_("(%s)\n") % inst.hint)
288 except ImportError as inst:
289 except ImportError as inst:
289 ui.warn(_("abort: %s!\n") % inst)
290 ui.warn(_("abort: %s!\n") % inst)
290 m = str(inst).split()[-1]
291 m = str(inst).split()[-1]
291 if m in "mpatch bdiff".split():
292 if m in "mpatch bdiff".split():
292 ui.warn(_("(did you forget to compile extensions?)\n"))
293 ui.warn(_("(did you forget to compile extensions?)\n"))
293 elif m in "zlib".split():
294 elif m in "zlib".split():
294 ui.warn(_("(is your Python install correct?)\n"))
295 ui.warn(_("(is your Python install correct?)\n"))
295 except IOError as inst:
296 except IOError as inst:
296 if util.safehasattr(inst, "code"):
297 if util.safehasattr(inst, "code"):
297 ui.warn(_("abort: %s\n") % inst)
298 ui.warn(_("abort: %s\n") % inst)
298 elif util.safehasattr(inst, "reason"):
299 elif util.safehasattr(inst, "reason"):
299 try: # usually it is in the form (errno, strerror)
300 try: # usually it is in the form (errno, strerror)
300 reason = inst.reason.args[1]
301 reason = inst.reason.args[1]
301 except (AttributeError, IndexError):
302 except (AttributeError, IndexError):
302 # it might be anything, for example a string
303 # it might be anything, for example a string
303 reason = inst.reason
304 reason = inst.reason
304 if isinstance(reason, unicode):
305 if isinstance(reason, unicode):
305 # SSLError of Python 2.7.9 contains a unicode
306 # SSLError of Python 2.7.9 contains a unicode
306 reason = reason.encode(encoding.encoding, 'replace')
307 reason = reason.encode(encoding.encoding, 'replace')
307 ui.warn(_("abort: error: %s\n") % reason)
308 ui.warn(_("abort: error: %s\n") % reason)
308 elif (util.safehasattr(inst, "args")
309 elif (util.safehasattr(inst, "args")
309 and inst.args and inst.args[0] == errno.EPIPE):
310 and inst.args and inst.args[0] == errno.EPIPE):
310 pass
311 pass
311 elif getattr(inst, "strerror", None):
312 elif getattr(inst, "strerror", None):
312 if getattr(inst, "filename", None):
313 if getattr(inst, "filename", None):
313 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
314 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
314 else:
315 else:
315 ui.warn(_("abort: %s\n") % inst.strerror)
316 ui.warn(_("abort: %s\n") % inst.strerror)
316 else:
317 else:
317 raise
318 raise
318 except OSError as inst:
319 except OSError as inst:
319 if getattr(inst, "filename", None) is not None:
320 if getattr(inst, "filename", None) is not None:
320 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
321 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
321 else:
322 else:
322 ui.warn(_("abort: %s\n") % inst.strerror)
323 ui.warn(_("abort: %s\n") % inst.strerror)
323 except KeyboardInterrupt:
324 except KeyboardInterrupt:
324 raise
325 raise
325 except MemoryError:
326 except MemoryError:
326 ui.warn(_("abort: out of memory\n"))
327 ui.warn(_("abort: out of memory\n"))
327 except SystemExit as inst:
328 except SystemExit as inst:
328 # Commands shouldn't sys.exit directly, but give a return code.
329 # Commands shouldn't sys.exit directly, but give a return code.
329 # Just in case catch this and and pass exit code to caller.
330 # Just in case catch this and and pass exit code to caller.
330 return inst.code
331 return inst.code
331 except socket.error as inst:
332 except socket.error as inst:
332 ui.warn(_("abort: %s\n") % inst.args[-1])
333 ui.warn(_("abort: %s\n") % inst.args[-1])
333 except: # re-raises
334 except: # re-raises
334 # For compatibility checking, we discard the portion of the hg
335 # For compatibility checking, we discard the portion of the hg
335 # version after the + on the assumption that if a "normal
336 # version after the + on the assumption that if a "normal
336 # user" is running a build with a + in it the packager
337 # user" is running a build with a + in it the packager
337 # probably built from fairly close to a tag and anyone with a
338 # probably built from fairly close to a tag and anyone with a
338 # 'make local' copy of hg (where the version number can be out
339 # 'make local' copy of hg (where the version number can be out
339 # of date) will be clueful enough to notice the implausible
340 # of date) will be clueful enough to notice the implausible
340 # version number and try updating.
341 # version number and try updating.
341 ct = util.versiontuple(n=2)
342 ct = util.versiontuple(n=2)
342 worst = None, ct, ''
343 worst = None, ct, ''
343 if ui.config('ui', 'supportcontact', None) is None:
344 if ui.config('ui', 'supportcontact', None) is None:
344 for name, mod in extensions.extensions():
345 for name, mod in extensions.extensions():
345 testedwith = getattr(mod, 'testedwith', '')
346 testedwith = getattr(mod, 'testedwith', '')
346 report = getattr(mod, 'buglink', _('the extension author.'))
347 report = getattr(mod, 'buglink', _('the extension author.'))
347 if not testedwith.strip():
348 if not testedwith.strip():
348 # We found an untested extension. It's likely the culprit.
349 # We found an untested extension. It's likely the culprit.
349 worst = name, 'unknown', report
350 worst = name, 'unknown', report
350 break
351 break
351
352
352 # Never blame on extensions bundled with Mercurial.
353 # Never blame on extensions bundled with Mercurial.
353 if testedwith == 'internal':
354 if testedwith == 'internal':
354 continue
355 continue
355
356
356 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
357 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
357 if ct in tested:
358 if ct in tested:
358 continue
359 continue
359
360
360 lower = [t for t in tested if t < ct]
361 lower = [t for t in tested if t < ct]
361 nearest = max(lower or tested)
362 nearest = max(lower or tested)
362 if worst[0] is None or nearest < worst[1]:
363 if worst[0] is None or nearest < worst[1]:
363 worst = name, nearest, report
364 worst = name, nearest, report
364 if worst[0] is not None:
365 if worst[0] is not None:
365 name, testedwith, report = worst
366 name, testedwith, report = worst
366 if not isinstance(testedwith, str):
367 if not isinstance(testedwith, str):
367 testedwith = '.'.join([str(c) for c in testedwith])
368 testedwith = '.'.join([str(c) for c in testedwith])
368 warning = (_('** Unknown exception encountered with '
369 warning = (_('** Unknown exception encountered with '
369 'possibly-broken third-party extension %s\n'
370 'possibly-broken third-party extension %s\n'
370 '** which supports versions %s of Mercurial.\n'
371 '** which supports versions %s of Mercurial.\n'
371 '** Please disable %s and try your action again.\n'
372 '** Please disable %s and try your action again.\n'
372 '** If that fixes the bug please report it to %s\n')
373 '** If that fixes the bug please report it to %s\n')
373 % (name, testedwith, name, report))
374 % (name, testedwith, name, report))
374 else:
375 else:
375 bugtracker = ui.config('ui', 'supportcontact', None)
376 bugtracker = ui.config('ui', 'supportcontact', None)
376 if bugtracker is None:
377 if bugtracker is None:
377 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
378 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
378 warning = (_("** unknown exception encountered, "
379 warning = (_("** unknown exception encountered, "
379 "please report by visiting\n** ") + bugtracker + '\n')
380 "please report by visiting\n** ") + bugtracker + '\n')
380 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
381 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
381 (_("** Mercurial Distributed SCM (version %s)\n") %
382 (_("** Mercurial Distributed SCM (version %s)\n") %
382 util.version()) +
383 util.version()) +
383 (_("** Extensions loaded: %s\n") %
384 (_("** Extensions loaded: %s\n") %
384 ", ".join([x[0] for x in extensions.extensions()])))
385 ", ".join([x[0] for x in extensions.extensions()])))
385 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
386 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
386 ui.warn(warning)
387 ui.warn(warning)
387 raise
388 raise
388
389
389 return -1
390 return -1
390
391
391 def aliasargs(fn, givenargs):
392 def aliasargs(fn, givenargs):
392 args = getattr(fn, 'args', [])
393 args = getattr(fn, 'args', [])
393 if args:
394 if args:
394 cmd = ' '.join(map(util.shellquote, args))
395 cmd = ' '.join(map(util.shellquote, args))
395
396
396 nums = []
397 nums = []
397 def replacer(m):
398 def replacer(m):
398 num = int(m.group(1)) - 1
399 num = int(m.group(1)) - 1
399 nums.append(num)
400 nums.append(num)
400 if num < len(givenargs):
401 if num < len(givenargs):
401 return givenargs[num]
402 return givenargs[num]
402 raise error.Abort(_('too few arguments for command alias'))
403 raise error.Abort(_('too few arguments for command alias'))
403 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
404 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
404 givenargs = [x for i, x in enumerate(givenargs)
405 givenargs = [x for i, x in enumerate(givenargs)
405 if i not in nums]
406 if i not in nums]
406 args = shlex.split(cmd)
407 args = shlex.split(cmd)
407 return args + givenargs
408 return args + givenargs
408
409
409 def aliasinterpolate(name, args, cmd):
410 def aliasinterpolate(name, args, cmd):
410 '''interpolate args into cmd for shell aliases
411 '''interpolate args into cmd for shell aliases
411
412
412 This also handles $0, $@ and "$@".
413 This also handles $0, $@ and "$@".
413 '''
414 '''
414 # util.interpolate can't deal with "$@" (with quotes) because it's only
415 # util.interpolate can't deal with "$@" (with quotes) because it's only
415 # built to match prefix + patterns.
416 # built to match prefix + patterns.
416 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
417 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
417 replacemap['$0'] = name
418 replacemap['$0'] = name
418 replacemap['$$'] = '$'
419 replacemap['$$'] = '$'
419 replacemap['$@'] = ' '.join(args)
420 replacemap['$@'] = ' '.join(args)
420 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
421 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
421 # parameters, separated out into words. Emulate the same behavior here by
422 # parameters, separated out into words. Emulate the same behavior here by
422 # quoting the arguments individually. POSIX shells will then typically
423 # quoting the arguments individually. POSIX shells will then typically
423 # tokenize each argument into exactly one word.
424 # tokenize each argument into exactly one word.
424 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
425 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
425 # escape '\$' for regex
426 # escape '\$' for regex
426 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
427 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
427 r = re.compile(regex)
428 r = re.compile(regex)
428 return r.sub(lambda x: replacemap[x.group()], cmd)
429 return r.sub(lambda x: replacemap[x.group()], cmd)
429
430
430 class cmdalias(object):
431 class cmdalias(object):
431 def __init__(self, name, definition, cmdtable):
432 def __init__(self, name, definition, cmdtable):
432 self.name = self.cmd = name
433 self.name = self.cmd = name
433 self.cmdname = ''
434 self.cmdname = ''
434 self.definition = definition
435 self.definition = definition
435 self.fn = None
436 self.fn = None
436 self.args = []
437 self.args = []
437 self.opts = []
438 self.opts = []
438 self.help = ''
439 self.help = ''
439 self.norepo = True
440 self.norepo = True
440 self.optionalrepo = False
441 self.optionalrepo = False
441 self.inferrepo = False
442 self.inferrepo = False
442 self.badalias = None
443 self.badalias = None
443 self.unknowncmd = False
444 self.unknowncmd = False
444
445
445 try:
446 try:
446 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
447 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
447 for alias, e in cmdtable.iteritems():
448 for alias, e in cmdtable.iteritems():
448 if e is entry:
449 if e is entry:
449 self.cmd = alias
450 self.cmd = alias
450 break
451 break
451 self.shadows = True
452 self.shadows = True
452 except error.UnknownCommand:
453 except error.UnknownCommand:
453 self.shadows = False
454 self.shadows = False
454
455
455 if not self.definition:
456 if not self.definition:
456 self.badalias = _("no definition for alias '%s'") % self.name
457 self.badalias = _("no definition for alias '%s'") % self.name
457 return
458 return
458
459
459 if self.definition.startswith('!'):
460 if self.definition.startswith('!'):
460 self.shell = True
461 self.shell = True
461 def fn(ui, *args):
462 def fn(ui, *args):
462 env = {'HG_ARGS': ' '.join((self.name,) + args)}
463 env = {'HG_ARGS': ' '.join((self.name,) + args)}
463 def _checkvar(m):
464 def _checkvar(m):
464 if m.groups()[0] == '$':
465 if m.groups()[0] == '$':
465 return m.group()
466 return m.group()
466 elif int(m.groups()[0]) <= len(args):
467 elif int(m.groups()[0]) <= len(args):
467 return m.group()
468 return m.group()
468 else:
469 else:
469 ui.debug("No argument found for substitution "
470 ui.debug("No argument found for substitution "
470 "of %i variable in alias '%s' definition."
471 "of %i variable in alias '%s' definition."
471 % (int(m.groups()[0]), self.name))
472 % (int(m.groups()[0]), self.name))
472 return ''
473 return ''
473 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
474 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
474 cmd = aliasinterpolate(self.name, args, cmd)
475 cmd = aliasinterpolate(self.name, args, cmd)
475 return ui.system(cmd, environ=env)
476 return ui.system(cmd, environ=env)
476 self.fn = fn
477 self.fn = fn
477 return
478 return
478
479
479 try:
480 try:
480 args = shlex.split(self.definition)
481 args = shlex.split(self.definition)
481 except ValueError as inst:
482 except ValueError as inst:
482 self.badalias = (_("error in definition for alias '%s': %s")
483 self.badalias = (_("error in definition for alias '%s': %s")
483 % (self.name, inst))
484 % (self.name, inst))
484 return
485 return
485 self.cmdname = cmd = args.pop(0)
486 self.cmdname = cmd = args.pop(0)
486 args = map(util.expandpath, args)
487 args = map(util.expandpath, args)
487
488
488 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
489 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
489 if _earlygetopt([invalidarg], args):
490 if _earlygetopt([invalidarg], args):
490 self.badalias = (_("error in definition for alias '%s': %s may "
491 self.badalias = (_("error in definition for alias '%s': %s may "
491 "only be given on the command line")
492 "only be given on the command line")
492 % (self.name, invalidarg))
493 % (self.name, invalidarg))
493 return
494 return
494
495
495 try:
496 try:
496 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
497 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
497 if len(tableentry) > 2:
498 if len(tableentry) > 2:
498 self.fn, self.opts, self.help = tableentry
499 self.fn, self.opts, self.help = tableentry
499 else:
500 else:
500 self.fn, self.opts = tableentry
501 self.fn, self.opts = tableentry
501
502
502 self.args = aliasargs(self.fn, args)
503 self.args = aliasargs(self.fn, args)
503 if not self.fn.norepo:
504 if not self.fn.norepo:
504 self.norepo = False
505 self.norepo = False
505 if self.fn.optionalrepo:
506 if self.fn.optionalrepo:
506 self.optionalrepo = True
507 self.optionalrepo = True
507 if self.fn.inferrepo:
508 if self.fn.inferrepo:
508 self.inferrepo = True
509 self.inferrepo = True
509 if self.help.startswith("hg " + cmd):
510 if self.help.startswith("hg " + cmd):
510 # drop prefix in old-style help lines so hg shows the alias
511 # drop prefix in old-style help lines so hg shows the alias
511 self.help = self.help[4 + len(cmd):]
512 self.help = self.help[4 + len(cmd):]
512 self.__doc__ = self.fn.__doc__
513 self.__doc__ = self.fn.__doc__
513
514
514 except error.UnknownCommand:
515 except error.UnknownCommand:
515 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
516 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
516 % (self.name, cmd))
517 % (self.name, cmd))
517 self.unknowncmd = True
518 self.unknowncmd = True
518 except error.AmbiguousCommand:
519 except error.AmbiguousCommand:
519 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
520 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
520 % (self.name, cmd))
521 % (self.name, cmd))
521
522
522 def __call__(self, ui, *args, **opts):
523 def __call__(self, ui, *args, **opts):
523 if self.badalias:
524 if self.badalias:
524 hint = None
525 hint = None
525 if self.unknowncmd:
526 if self.unknowncmd:
526 try:
527 try:
527 # check if the command is in a disabled extension
528 # check if the command is in a disabled extension
528 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
529 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
529 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
530 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
530 except error.UnknownCommand:
531 except error.UnknownCommand:
531 pass
532 pass
532 raise error.Abort(self.badalias, hint=hint)
533 raise error.Abort(self.badalias, hint=hint)
533 if self.shadows:
534 if self.shadows:
534 ui.debug("alias '%s' shadows command '%s'\n" %
535 ui.debug("alias '%s' shadows command '%s'\n" %
535 (self.name, self.cmdname))
536 (self.name, self.cmdname))
536
537
537 if util.safehasattr(self, 'shell'):
538 if util.safehasattr(self, 'shell'):
538 return self.fn(ui, *args, **opts)
539 return self.fn(ui, *args, **opts)
539 else:
540 else:
540 try:
541 try:
541 return util.checksignature(self.fn)(ui, *args, **opts)
542 return util.checksignature(self.fn)(ui, *args, **opts)
542 except error.SignatureError:
543 except error.SignatureError:
543 args = ' '.join([self.cmdname] + self.args)
544 args = ' '.join([self.cmdname] + self.args)
544 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
545 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
545 raise
546 raise
546
547
547 def addaliases(ui, cmdtable):
548 def addaliases(ui, cmdtable):
548 # aliases are processed after extensions have been loaded, so they
549 # aliases are processed after extensions have been loaded, so they
549 # may use extension commands. Aliases can also use other alias definitions,
550 # may use extension commands. Aliases can also use other alias definitions,
550 # but only if they have been defined prior to the current definition.
551 # but only if they have been defined prior to the current definition.
551 for alias, definition in ui.configitems('alias'):
552 for alias, definition in ui.configitems('alias'):
552 aliasdef = cmdalias(alias, definition, cmdtable)
553 aliasdef = cmdalias(alias, definition, cmdtable)
553
554
554 try:
555 try:
555 olddef = cmdtable[aliasdef.cmd][0]
556 olddef = cmdtable[aliasdef.cmd][0]
556 if olddef.definition == aliasdef.definition:
557 if olddef.definition == aliasdef.definition:
557 continue
558 continue
558 except (KeyError, AttributeError):
559 except (KeyError, AttributeError):
559 # definition might not exist or it might not be a cmdalias
560 # definition might not exist or it might not be a cmdalias
560 pass
561 pass
561
562
562 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
563 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
563
564
564 def _parse(ui, args):
565 def _parse(ui, args):
565 options = {}
566 options = {}
566 cmdoptions = {}
567 cmdoptions = {}
567
568
568 try:
569 try:
569 args = fancyopts.fancyopts(args, commands.globalopts, options)
570 args = fancyopts.fancyopts(args, commands.globalopts, options)
570 except fancyopts.getopt.GetoptError as inst:
571 except fancyopts.getopt.GetoptError as inst:
571 raise error.CommandError(None, inst)
572 raise error.CommandError(None, inst)
572
573
573 if args:
574 if args:
574 cmd, args = args[0], args[1:]
575 cmd, args = args[0], args[1:]
575 aliases, entry = cmdutil.findcmd(cmd, commands.table,
576 aliases, entry = cmdutil.findcmd(cmd, commands.table,
576 ui.configbool("ui", "strict"))
577 ui.configbool("ui", "strict"))
577 cmd = aliases[0]
578 cmd = aliases[0]
578 args = aliasargs(entry[0], args)
579 args = aliasargs(entry[0], args)
579 defaults = ui.config("defaults", cmd)
580 defaults = ui.config("defaults", cmd)
580 if defaults:
581 if defaults:
581 args = map(util.expandpath, shlex.split(defaults)) + args
582 args = map(util.expandpath, shlex.split(defaults)) + args
582 c = list(entry[1])
583 c = list(entry[1])
583 else:
584 else:
584 cmd = None
585 cmd = None
585 c = []
586 c = []
586
587
587 # combine global options into local
588 # combine global options into local
588 for o in commands.globalopts:
589 for o in commands.globalopts:
589 c.append((o[0], o[1], options[o[1]], o[3]))
590 c.append((o[0], o[1], options[o[1]], o[3]))
590
591
591 try:
592 try:
592 args = fancyopts.fancyopts(args, c, cmdoptions, True)
593 args = fancyopts.fancyopts(args, c, cmdoptions, True)
593 except fancyopts.getopt.GetoptError as inst:
594 except fancyopts.getopt.GetoptError as inst:
594 raise error.CommandError(cmd, inst)
595 raise error.CommandError(cmd, inst)
595
596
596 # separate global options back out
597 # separate global options back out
597 for o in commands.globalopts:
598 for o in commands.globalopts:
598 n = o[1]
599 n = o[1]
599 options[n] = cmdoptions[n]
600 options[n] = cmdoptions[n]
600 del cmdoptions[n]
601 del cmdoptions[n]
601
602
602 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
603 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
603
604
604 def _parseconfig(ui, config):
605 def _parseconfig(ui, config):
605 """parse the --config options from the command line"""
606 """parse the --config options from the command line"""
606 configs = []
607 configs = []
607
608
608 for cfg in config:
609 for cfg in config:
609 try:
610 try:
610 name, value = [cfgelem.strip()
611 name, value = [cfgelem.strip()
611 for cfgelem in cfg.split('=', 1)]
612 for cfgelem in cfg.split('=', 1)]
612 section, name = name.split('.', 1)
613 section, name = name.split('.', 1)
613 if not section or not name:
614 if not section or not name:
614 raise IndexError
615 raise IndexError
615 ui.setconfig(section, name, value, '--config')
616 ui.setconfig(section, name, value, '--config')
616 configs.append((section, name, value))
617 configs.append((section, name, value))
617 except (IndexError, ValueError):
618 except (IndexError, ValueError):
618 raise error.Abort(_('malformed --config option: %r '
619 raise error.Abort(_('malformed --config option: %r '
619 '(use --config section.name=value)') % cfg)
620 '(use --config section.name=value)') % cfg)
620
621
621 return configs
622 return configs
622
623
623 def _earlygetopt(aliases, args):
624 def _earlygetopt(aliases, args):
624 """Return list of values for an option (or aliases).
625 """Return list of values for an option (or aliases).
625
626
626 The values are listed in the order they appear in args.
627 The values are listed in the order they appear in args.
627 The options and values are removed from args.
628 The options and values are removed from args.
628
629
629 >>> args = ['x', '--cwd', 'foo', 'y']
630 >>> args = ['x', '--cwd', 'foo', 'y']
630 >>> _earlygetopt(['--cwd'], args), args
631 >>> _earlygetopt(['--cwd'], args), args
631 (['foo'], ['x', 'y'])
632 (['foo'], ['x', 'y'])
632
633
633 >>> args = ['x', '--cwd=bar', 'y']
634 >>> args = ['x', '--cwd=bar', 'y']
634 >>> _earlygetopt(['--cwd'], args), args
635 >>> _earlygetopt(['--cwd'], args), args
635 (['bar'], ['x', 'y'])
636 (['bar'], ['x', 'y'])
636
637
637 >>> args = ['x', '-R', 'foo', 'y']
638 >>> args = ['x', '-R', 'foo', 'y']
638 >>> _earlygetopt(['-R'], args), args
639 >>> _earlygetopt(['-R'], args), args
639 (['foo'], ['x', 'y'])
640 (['foo'], ['x', 'y'])
640
641
641 >>> args = ['x', '-Rbar', 'y']
642 >>> args = ['x', '-Rbar', 'y']
642 >>> _earlygetopt(['-R'], args), args
643 >>> _earlygetopt(['-R'], args), args
643 (['bar'], ['x', 'y'])
644 (['bar'], ['x', 'y'])
644 """
645 """
645 try:
646 try:
646 argcount = args.index("--")
647 argcount = args.index("--")
647 except ValueError:
648 except ValueError:
648 argcount = len(args)
649 argcount = len(args)
649 shortopts = [opt for opt in aliases if len(opt) == 2]
650 shortopts = [opt for opt in aliases if len(opt) == 2]
650 values = []
651 values = []
651 pos = 0
652 pos = 0
652 while pos < argcount:
653 while pos < argcount:
653 fullarg = arg = args[pos]
654 fullarg = arg = args[pos]
654 equals = arg.find('=')
655 equals = arg.find('=')
655 if equals > -1:
656 if equals > -1:
656 arg = arg[:equals]
657 arg = arg[:equals]
657 if arg in aliases:
658 if arg in aliases:
658 del args[pos]
659 del args[pos]
659 if equals > -1:
660 if equals > -1:
660 values.append(fullarg[equals + 1:])
661 values.append(fullarg[equals + 1:])
661 argcount -= 1
662 argcount -= 1
662 else:
663 else:
663 if pos + 1 >= argcount:
664 if pos + 1 >= argcount:
664 # ignore and let getopt report an error if there is no value
665 # ignore and let getopt report an error if there is no value
665 break
666 break
666 values.append(args.pop(pos))
667 values.append(args.pop(pos))
667 argcount -= 2
668 argcount -= 2
668 elif arg[:2] in shortopts:
669 elif arg[:2] in shortopts:
669 # short option can have no following space, e.g. hg log -Rfoo
670 # short option can have no following space, e.g. hg log -Rfoo
670 values.append(args.pop(pos)[2:])
671 values.append(args.pop(pos)[2:])
671 argcount -= 1
672 argcount -= 1
672 else:
673 else:
673 pos += 1
674 pos += 1
674 return values
675 return values
675
676
676 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
677 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
677 # run pre-hook, and abort if it fails
678 # run pre-hook, and abort if it fails
678 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
679 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
679 pats=cmdpats, opts=cmdoptions)
680 pats=cmdpats, opts=cmdoptions)
680 ret = _runcommand(ui, options, cmd, d)
681 ret = _runcommand(ui, options, cmd, d)
681 # run post-hook, passing command result
682 # run post-hook, passing command result
682 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
683 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
683 result=ret, pats=cmdpats, opts=cmdoptions)
684 result=ret, pats=cmdpats, opts=cmdoptions)
684 return ret
685 return ret
685
686
686 def _getlocal(ui, rpath, wd=None):
687 def _getlocal(ui, rpath, wd=None):
687 """Return (path, local ui object) for the given target path.
688 """Return (path, local ui object) for the given target path.
688
689
689 Takes paths in [cwd]/.hg/hgrc into account."
690 Takes paths in [cwd]/.hg/hgrc into account."
690 """
691 """
691 if wd is None:
692 if wd is None:
692 try:
693 try:
693 wd = os.getcwd()
694 wd = os.getcwd()
694 except OSError as e:
695 except OSError as e:
695 raise error.Abort(_("error getting current working directory: %s") %
696 raise error.Abort(_("error getting current working directory: %s") %
696 e.strerror)
697 e.strerror)
697 path = cmdutil.findrepo(wd) or ""
698 path = cmdutil.findrepo(wd) or ""
698 if not path:
699 if not path:
699 lui = ui
700 lui = ui
700 else:
701 else:
701 lui = ui.copy()
702 lui = ui.copy()
702 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
703 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
703
704
704 if rpath and rpath[-1]:
705 if rpath and rpath[-1]:
705 path = lui.expandpath(rpath[-1])
706 path = lui.expandpath(rpath[-1])
706 lui = ui.copy()
707 lui = ui.copy()
707 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
708 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
708
709
709 return path, lui
710 return path, lui
710
711
711 def _checkshellalias(lui, ui, args, precheck=True):
712 def _checkshellalias(lui, ui, args, precheck=True):
712 """Return the function to run the shell alias, if it is required
713 """Return the function to run the shell alias, if it is required
713
714
714 'precheck' is whether this function is invoked before adding
715 'precheck' is whether this function is invoked before adding
715 aliases or not.
716 aliases or not.
716 """
717 """
717 options = {}
718 options = {}
718
719
719 try:
720 try:
720 args = fancyopts.fancyopts(args, commands.globalopts, options)
721 args = fancyopts.fancyopts(args, commands.globalopts, options)
721 except fancyopts.getopt.GetoptError:
722 except fancyopts.getopt.GetoptError:
722 return
723 return
723
724
724 if not args:
725 if not args:
725 return
726 return
726
727
727 if precheck:
728 if precheck:
728 strict = True
729 strict = True
729 cmdtable = commands.table.copy()
730 cmdtable = commands.table.copy()
730 addaliases(lui, cmdtable)
731 addaliases(lui, cmdtable)
731 else:
732 else:
732 strict = False
733 strict = False
733 cmdtable = commands.table
734 cmdtable = commands.table
734
735
735 cmd = args[0]
736 cmd = args[0]
736 try:
737 try:
737 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
738 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
738 except (error.AmbiguousCommand, error.UnknownCommand):
739 except (error.AmbiguousCommand, error.UnknownCommand):
739 return
740 return
740
741
741 cmd = aliases[0]
742 cmd = aliases[0]
742 fn = entry[0]
743 fn = entry[0]
743
744
744 if cmd and util.safehasattr(fn, 'shell'):
745 if cmd and util.safehasattr(fn, 'shell'):
745 d = lambda: fn(ui, *args[1:])
746 d = lambda: fn(ui, *args[1:])
746 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
747 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
747 [], {})
748 [], {})
748
749
749 _loaded = set()
750 _loaded = set()
750
751
751 # list of (objname, loadermod, loadername) tuple:
752 # list of (objname, loadermod, loadername) tuple:
752 # - objname is the name of an object in extension module, from which
753 # - objname is the name of an object in extension module, from which
753 # extra information is loaded
754 # extra information is loaded
754 # - loadermod is the module where loader is placed
755 # - loadermod is the module where loader is placed
755 # - loadername is the name of the function, which takes (ui, extensionname,
756 # - loadername is the name of the function, which takes (ui, extensionname,
756 # extraobj) arguments
757 # extraobj) arguments
757 extraloaders = [
758 extraloaders = [
758 ('cmdtable', commands, 'loadcmdtable'),
759 ('cmdtable', commands, 'loadcmdtable'),
759 ('filesetpredicate', fileset, 'loadpredicate'),
760 ('filesetpredicate', fileset, 'loadpredicate'),
760 ('revsetpredicate', revset, 'loadpredicate'),
761 ('revsetpredicate', revset, 'loadpredicate'),
762 ('templatekeyword', templatekw, 'loadkeyword'),
761 ]
763 ]
762
764
763 def _dispatch(req):
765 def _dispatch(req):
764 args = req.args
766 args = req.args
765 ui = req.ui
767 ui = req.ui
766
768
767 # check for cwd
769 # check for cwd
768 cwd = _earlygetopt(['--cwd'], args)
770 cwd = _earlygetopt(['--cwd'], args)
769 if cwd:
771 if cwd:
770 os.chdir(cwd[-1])
772 os.chdir(cwd[-1])
771
773
772 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
774 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
773 path, lui = _getlocal(ui, rpath)
775 path, lui = _getlocal(ui, rpath)
774
776
775 # Now that we're operating in the right directory/repository with
777 # Now that we're operating in the right directory/repository with
776 # the right config settings, check for shell aliases
778 # the right config settings, check for shell aliases
777 shellaliasfn = _checkshellalias(lui, ui, args)
779 shellaliasfn = _checkshellalias(lui, ui, args)
778 if shellaliasfn:
780 if shellaliasfn:
779 return shellaliasfn()
781 return shellaliasfn()
780
782
781 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
783 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
782 # reposetup. Programs like TortoiseHg will call _dispatch several
784 # reposetup. Programs like TortoiseHg will call _dispatch several
783 # times so we keep track of configured extensions in _loaded.
785 # times so we keep track of configured extensions in _loaded.
784 extensions.loadall(lui)
786 extensions.loadall(lui)
785 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
787 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
786 # Propagate any changes to lui.__class__ by extensions
788 # Propagate any changes to lui.__class__ by extensions
787 ui.__class__ = lui.__class__
789 ui.__class__ = lui.__class__
788
790
789 # (uisetup and extsetup are handled in extensions.loadall)
791 # (uisetup and extsetup are handled in extensions.loadall)
790
792
791 for name, module in exts:
793 for name, module in exts:
792 for objname, loadermod, loadername in extraloaders:
794 for objname, loadermod, loadername in extraloaders:
793 extraobj = getattr(module, objname, None)
795 extraobj = getattr(module, objname, None)
794 if extraobj is not None:
796 if extraobj is not None:
795 getattr(loadermod, loadername)(ui, name, extraobj)
797 getattr(loadermod, loadername)(ui, name, extraobj)
796 _loaded.add(name)
798 _loaded.add(name)
797
799
798 # (reposetup is handled in hg.repository)
800 # (reposetup is handled in hg.repository)
799
801
800 addaliases(lui, commands.table)
802 addaliases(lui, commands.table)
801
803
802 if not lui.configbool("ui", "strict"):
804 if not lui.configbool("ui", "strict"):
803 # All aliases and commands are completely defined, now.
805 # All aliases and commands are completely defined, now.
804 # Check abbreviation/ambiguity of shell alias again, because shell
806 # Check abbreviation/ambiguity of shell alias again, because shell
805 # alias may cause failure of "_parse" (see issue4355)
807 # alias may cause failure of "_parse" (see issue4355)
806 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
808 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
807 if shellaliasfn:
809 if shellaliasfn:
808 return shellaliasfn()
810 return shellaliasfn()
809
811
810 # check for fallback encoding
812 # check for fallback encoding
811 fallback = lui.config('ui', 'fallbackencoding')
813 fallback = lui.config('ui', 'fallbackencoding')
812 if fallback:
814 if fallback:
813 encoding.fallbackencoding = fallback
815 encoding.fallbackencoding = fallback
814
816
815 fullargs = args
817 fullargs = args
816 cmd, func, args, options, cmdoptions = _parse(lui, args)
818 cmd, func, args, options, cmdoptions = _parse(lui, args)
817
819
818 if options["config"]:
820 if options["config"]:
819 raise error.Abort(_("option --config may not be abbreviated!"))
821 raise error.Abort(_("option --config may not be abbreviated!"))
820 if options["cwd"]:
822 if options["cwd"]:
821 raise error.Abort(_("option --cwd may not be abbreviated!"))
823 raise error.Abort(_("option --cwd may not be abbreviated!"))
822 if options["repository"]:
824 if options["repository"]:
823 raise error.Abort(_(
825 raise error.Abort(_(
824 "option -R has to be separated from other options (e.g. not -qR) "
826 "option -R has to be separated from other options (e.g. not -qR) "
825 "and --repository may only be abbreviated as --repo!"))
827 "and --repository may only be abbreviated as --repo!"))
826
828
827 if options["encoding"]:
829 if options["encoding"]:
828 encoding.encoding = options["encoding"]
830 encoding.encoding = options["encoding"]
829 if options["encodingmode"]:
831 if options["encodingmode"]:
830 encoding.encodingmode = options["encodingmode"]
832 encoding.encodingmode = options["encodingmode"]
831 if options["time"]:
833 if options["time"]:
832 def get_times():
834 def get_times():
833 t = os.times()
835 t = os.times()
834 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
836 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
835 t = (t[0], t[1], t[2], t[3], time.clock())
837 t = (t[0], t[1], t[2], t[3], time.clock())
836 return t
838 return t
837 s = get_times()
839 s = get_times()
838 def print_time():
840 def print_time():
839 t = get_times()
841 t = get_times()
840 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
842 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
841 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
843 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
842 atexit.register(print_time)
844 atexit.register(print_time)
843
845
844 uis = set([ui, lui])
846 uis = set([ui, lui])
845
847
846 if req.repo:
848 if req.repo:
847 uis.add(req.repo.ui)
849 uis.add(req.repo.ui)
848
850
849 if options['verbose'] or options['debug'] or options['quiet']:
851 if options['verbose'] or options['debug'] or options['quiet']:
850 for opt in ('verbose', 'debug', 'quiet'):
852 for opt in ('verbose', 'debug', 'quiet'):
851 val = str(bool(options[opt]))
853 val = str(bool(options[opt]))
852 for ui_ in uis:
854 for ui_ in uis:
853 ui_.setconfig('ui', opt, val, '--' + opt)
855 ui_.setconfig('ui', opt, val, '--' + opt)
854
856
855 if options['traceback']:
857 if options['traceback']:
856 for ui_ in uis:
858 for ui_ in uis:
857 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
859 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
858
860
859 if options['noninteractive']:
861 if options['noninteractive']:
860 for ui_ in uis:
862 for ui_ in uis:
861 ui_.setconfig('ui', 'interactive', 'off', '-y')
863 ui_.setconfig('ui', 'interactive', 'off', '-y')
862
864
863 if cmdoptions.get('insecure', False):
865 if cmdoptions.get('insecure', False):
864 for ui_ in uis:
866 for ui_ in uis:
865 ui_.setconfig('web', 'cacerts', '!', '--insecure')
867 ui_.setconfig('web', 'cacerts', '!', '--insecure')
866
868
867 if options['version']:
869 if options['version']:
868 return commands.version_(ui)
870 return commands.version_(ui)
869 if options['help']:
871 if options['help']:
870 return commands.help_(ui, cmd, command=cmd is not None)
872 return commands.help_(ui, cmd, command=cmd is not None)
871 elif not cmd:
873 elif not cmd:
872 return commands.help_(ui, 'shortlist')
874 return commands.help_(ui, 'shortlist')
873
875
874 repo = None
876 repo = None
875 cmdpats = args[:]
877 cmdpats = args[:]
876 if not func.norepo:
878 if not func.norepo:
877 # use the repo from the request only if we don't have -R
879 # use the repo from the request only if we don't have -R
878 if not rpath and not cwd:
880 if not rpath and not cwd:
879 repo = req.repo
881 repo = req.repo
880
882
881 if repo:
883 if repo:
882 # set the descriptors of the repo ui to those of ui
884 # set the descriptors of the repo ui to those of ui
883 repo.ui.fin = ui.fin
885 repo.ui.fin = ui.fin
884 repo.ui.fout = ui.fout
886 repo.ui.fout = ui.fout
885 repo.ui.ferr = ui.ferr
887 repo.ui.ferr = ui.ferr
886 else:
888 else:
887 try:
889 try:
888 repo = hg.repository(ui, path=path)
890 repo = hg.repository(ui, path=path)
889 if not repo.local():
891 if not repo.local():
890 raise error.Abort(_("repository '%s' is not local") % path)
892 raise error.Abort(_("repository '%s' is not local") % path)
891 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
893 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
892 except error.RequirementError:
894 except error.RequirementError:
893 raise
895 raise
894 except error.RepoError:
896 except error.RepoError:
895 if rpath and rpath[-1]: # invalid -R path
897 if rpath and rpath[-1]: # invalid -R path
896 raise
898 raise
897 if not func.optionalrepo:
899 if not func.optionalrepo:
898 if func.inferrepo and args and not path:
900 if func.inferrepo and args and not path:
899 # try to infer -R from command args
901 # try to infer -R from command args
900 repos = map(cmdutil.findrepo, args)
902 repos = map(cmdutil.findrepo, args)
901 guess = repos[0]
903 guess = repos[0]
902 if guess and repos.count(guess) == len(repos):
904 if guess and repos.count(guess) == len(repos):
903 req.args = ['--repository', guess] + fullargs
905 req.args = ['--repository', guess] + fullargs
904 return _dispatch(req)
906 return _dispatch(req)
905 if not path:
907 if not path:
906 raise error.RepoError(_("no repository found in '%s'"
908 raise error.RepoError(_("no repository found in '%s'"
907 " (.hg not found)")
909 " (.hg not found)")
908 % os.getcwd())
910 % os.getcwd())
909 raise
911 raise
910 if repo:
912 if repo:
911 ui = repo.ui
913 ui = repo.ui
912 if options['hidden']:
914 if options['hidden']:
913 repo = repo.unfiltered()
915 repo = repo.unfiltered()
914 args.insert(0, repo)
916 args.insert(0, repo)
915 elif rpath:
917 elif rpath:
916 ui.warn(_("warning: --repository ignored\n"))
918 ui.warn(_("warning: --repository ignored\n"))
917
919
918 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
920 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
919 ui.log("command", '%s\n', msg)
921 ui.log("command", '%s\n', msg)
920 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
922 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
921 try:
923 try:
922 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
924 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
923 cmdpats, cmdoptions)
925 cmdpats, cmdoptions)
924 finally:
926 finally:
925 if repo and repo != req.repo:
927 if repo and repo != req.repo:
926 repo.close()
928 repo.close()
927
929
928 def lsprofile(ui, func, fp):
930 def lsprofile(ui, func, fp):
929 format = ui.config('profiling', 'format', default='text')
931 format = ui.config('profiling', 'format', default='text')
930 field = ui.config('profiling', 'sort', default='inlinetime')
932 field = ui.config('profiling', 'sort', default='inlinetime')
931 limit = ui.configint('profiling', 'limit', default=30)
933 limit = ui.configint('profiling', 'limit', default=30)
932 climit = ui.configint('profiling', 'nested', default=0)
934 climit = ui.configint('profiling', 'nested', default=0)
933
935
934 if format not in ['text', 'kcachegrind']:
936 if format not in ['text', 'kcachegrind']:
935 ui.warn(_("unrecognized profiling format '%s'"
937 ui.warn(_("unrecognized profiling format '%s'"
936 " - Ignored\n") % format)
938 " - Ignored\n") % format)
937 format = 'text'
939 format = 'text'
938
940
939 try:
941 try:
940 from . import lsprof
942 from . import lsprof
941 except ImportError:
943 except ImportError:
942 raise error.Abort(_(
944 raise error.Abort(_(
943 'lsprof not available - install from '
945 'lsprof not available - install from '
944 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
946 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
945 p = lsprof.Profiler()
947 p = lsprof.Profiler()
946 p.enable(subcalls=True)
948 p.enable(subcalls=True)
947 try:
949 try:
948 return func()
950 return func()
949 finally:
951 finally:
950 p.disable()
952 p.disable()
951
953
952 if format == 'kcachegrind':
954 if format == 'kcachegrind':
953 from . import lsprofcalltree
955 from . import lsprofcalltree
954 calltree = lsprofcalltree.KCacheGrind(p)
956 calltree = lsprofcalltree.KCacheGrind(p)
955 calltree.output(fp)
957 calltree.output(fp)
956 else:
958 else:
957 # format == 'text'
959 # format == 'text'
958 stats = lsprof.Stats(p.getstats())
960 stats = lsprof.Stats(p.getstats())
959 stats.sort(field)
961 stats.sort(field)
960 stats.pprint(limit=limit, file=fp, climit=climit)
962 stats.pprint(limit=limit, file=fp, climit=climit)
961
963
962 def flameprofile(ui, func, fp):
964 def flameprofile(ui, func, fp):
963 try:
965 try:
964 from flamegraph import flamegraph
966 from flamegraph import flamegraph
965 except ImportError:
967 except ImportError:
966 raise error.Abort(_(
968 raise error.Abort(_(
967 'flamegraph not available - install from '
969 'flamegraph not available - install from '
968 'https://github.com/evanhempel/python-flamegraph'))
970 'https://github.com/evanhempel/python-flamegraph'))
969 # developer config: profiling.freq
971 # developer config: profiling.freq
970 freq = ui.configint('profiling', 'freq', default=1000)
972 freq = ui.configint('profiling', 'freq', default=1000)
971 filter_ = None
973 filter_ = None
972 collapse_recursion = True
974 collapse_recursion = True
973 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
975 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
974 filter_, collapse_recursion)
976 filter_, collapse_recursion)
975 start_time = time.clock()
977 start_time = time.clock()
976 try:
978 try:
977 thread.start()
979 thread.start()
978 func()
980 func()
979 finally:
981 finally:
980 thread.stop()
982 thread.stop()
981 thread.join()
983 thread.join()
982 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
984 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
983 time.clock() - start_time, thread.num_frames(),
985 time.clock() - start_time, thread.num_frames(),
984 thread.num_frames(unique=True)))
986 thread.num_frames(unique=True)))
985
987
986
988
987 def statprofile(ui, func, fp):
989 def statprofile(ui, func, fp):
988 try:
990 try:
989 import statprof
991 import statprof
990 except ImportError:
992 except ImportError:
991 raise error.Abort(_(
993 raise error.Abort(_(
992 'statprof not available - install using "easy_install statprof"'))
994 'statprof not available - install using "easy_install statprof"'))
993
995
994 freq = ui.configint('profiling', 'freq', default=1000)
996 freq = ui.configint('profiling', 'freq', default=1000)
995 if freq > 0:
997 if freq > 0:
996 statprof.reset(freq)
998 statprof.reset(freq)
997 else:
999 else:
998 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
1000 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
999
1001
1000 statprof.start()
1002 statprof.start()
1001 try:
1003 try:
1002 return func()
1004 return func()
1003 finally:
1005 finally:
1004 statprof.stop()
1006 statprof.stop()
1005 statprof.display(fp)
1007 statprof.display(fp)
1006
1008
1007 def _runcommand(ui, options, cmd, cmdfunc):
1009 def _runcommand(ui, options, cmd, cmdfunc):
1008 """Enables the profiler if applicable.
1010 """Enables the profiler if applicable.
1009
1011
1010 ``profiling.enabled`` - boolean config that enables or disables profiling
1012 ``profiling.enabled`` - boolean config that enables or disables profiling
1011 """
1013 """
1012 def checkargs():
1014 def checkargs():
1013 try:
1015 try:
1014 return cmdfunc()
1016 return cmdfunc()
1015 except error.SignatureError:
1017 except error.SignatureError:
1016 raise error.CommandError(cmd, _("invalid arguments"))
1018 raise error.CommandError(cmd, _("invalid arguments"))
1017
1019
1018 if options['profile'] or ui.configbool('profiling', 'enabled'):
1020 if options['profile'] or ui.configbool('profiling', 'enabled'):
1019 profiler = os.getenv('HGPROF')
1021 profiler = os.getenv('HGPROF')
1020 if profiler is None:
1022 if profiler is None:
1021 profiler = ui.config('profiling', 'type', default='ls')
1023 profiler = ui.config('profiling', 'type', default='ls')
1022 if profiler not in ('ls', 'stat', 'flame'):
1024 if profiler not in ('ls', 'stat', 'flame'):
1023 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
1025 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
1024 profiler = 'ls'
1026 profiler = 'ls'
1025
1027
1026 output = ui.config('profiling', 'output')
1028 output = ui.config('profiling', 'output')
1027
1029
1028 if output == 'blackbox':
1030 if output == 'blackbox':
1029 import StringIO
1031 import StringIO
1030 fp = StringIO.StringIO()
1032 fp = StringIO.StringIO()
1031 elif output:
1033 elif output:
1032 path = ui.expandpath(output)
1034 path = ui.expandpath(output)
1033 fp = open(path, 'wb')
1035 fp = open(path, 'wb')
1034 else:
1036 else:
1035 fp = sys.stderr
1037 fp = sys.stderr
1036
1038
1037 try:
1039 try:
1038 if profiler == 'ls':
1040 if profiler == 'ls':
1039 return lsprofile(ui, checkargs, fp)
1041 return lsprofile(ui, checkargs, fp)
1040 elif profiler == 'flame':
1042 elif profiler == 'flame':
1041 return flameprofile(ui, checkargs, fp)
1043 return flameprofile(ui, checkargs, fp)
1042 else:
1044 else:
1043 return statprofile(ui, checkargs, fp)
1045 return statprofile(ui, checkargs, fp)
1044 finally:
1046 finally:
1045 if output:
1047 if output:
1046 if output == 'blackbox':
1048 if output == 'blackbox':
1047 val = "Profile:\n%s" % fp.getvalue()
1049 val = "Profile:\n%s" % fp.getvalue()
1048 # ui.log treats the input as a format string,
1050 # ui.log treats the input as a format string,
1049 # so we need to escape any % signs.
1051 # so we need to escape any % signs.
1050 val = val.replace('%', '%%')
1052 val = val.replace('%', '%%')
1051 ui.log('profile', val)
1053 ui.log('profile', val)
1052 fp.close()
1054 fp.close()
1053 else:
1055 else:
1054 return checkargs()
1056 return checkargs()
@@ -1,163 +1,193 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 . import (
10 from . import (
11 util,
11 util,
12 )
12 )
13
13
14 class _funcregistrarbase(object):
14 class _funcregistrarbase(object):
15 """Base of decorator to register a fuction for specific purpose
15 """Base of decorator to register a fuction for specific purpose
16
16
17 This decorator stores decorated functions into own dict 'table'.
17 This decorator stores decorated functions into own dict 'table'.
18
18
19 The least derived class can be defined by overriding 'formatdoc',
19 The least derived class can be defined by overriding 'formatdoc',
20 for example::
20 for example::
21
21
22 class keyword(_funcregistrarbase):
22 class keyword(_funcregistrarbase):
23 _docformat = ":%s: %s"
23 _docformat = ":%s: %s"
24
24
25 This should be used as below:
25 This should be used as below:
26
26
27 keyword = registrar.keyword()
27 keyword = registrar.keyword()
28
28
29 @keyword('bar')
29 @keyword('bar')
30 def barfunc(*args, **kwargs):
30 def barfunc(*args, **kwargs):
31 '''Explanation of bar keyword ....
31 '''Explanation of bar keyword ....
32 '''
32 '''
33 pass
33 pass
34
34
35 In this case:
35 In this case:
36
36
37 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
37 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
38 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
38 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
39 """
39 """
40 def __init__(self, table=None):
40 def __init__(self, table=None):
41 if table is None:
41 if table is None:
42 self._table = {}
42 self._table = {}
43 else:
43 else:
44 self._table = table
44 self._table = table
45
45
46 def __call__(self, decl, *args, **kwargs):
46 def __call__(self, decl, *args, **kwargs):
47 return lambda func: self._doregister(func, decl, *args, **kwargs)
47 return lambda func: self._doregister(func, decl, *args, **kwargs)
48
48
49 def _doregister(self, func, decl, *args, **kwargs):
49 def _doregister(self, func, decl, *args, **kwargs):
50 name = self._getname(decl)
50 name = self._getname(decl)
51
51
52 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
52 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
53 doc = func.__doc__.strip()
53 doc = func.__doc__.strip()
54 func._origdoc = doc
54 func._origdoc = doc
55 func.__doc__ = self._formatdoc(decl, doc)
55 func.__doc__ = self._formatdoc(decl, doc)
56
56
57 self._table[name] = func
57 self._table[name] = func
58 self._extrasetup(name, func, *args, **kwargs)
58 self._extrasetup(name, func, *args, **kwargs)
59
59
60 return func
60 return func
61
61
62 def _parsefuncdecl(self, decl):
62 def _parsefuncdecl(self, decl):
63 """Parse function declaration and return the name of function in it
63 """Parse function declaration and return the name of function in it
64 """
64 """
65 i = decl.find('(')
65 i = decl.find('(')
66 if i >= 0:
66 if i >= 0:
67 return decl[:i]
67 return decl[:i]
68 else:
68 else:
69 return decl
69 return decl
70
70
71 def _getname(self, decl):
71 def _getname(self, decl):
72 """Return the name of the registered function from decl
72 """Return the name of the registered function from decl
73
73
74 Derived class should override this, if it allows more
74 Derived class should override this, if it allows more
75 descriptive 'decl' string than just a name.
75 descriptive 'decl' string than just a name.
76 """
76 """
77 return decl
77 return decl
78
78
79 _docformat = None
79 _docformat = None
80
80
81 def _formatdoc(self, decl, doc):
81 def _formatdoc(self, decl, doc):
82 """Return formatted document of the registered function for help
82 """Return formatted document of the registered function for help
83
83
84 'doc' is '__doc__.strip()' of the registered function.
84 'doc' is '__doc__.strip()' of the registered function.
85 """
85 """
86 return self._docformat % (decl, doc)
86 return self._docformat % (decl, doc)
87
87
88 def _extrasetup(self, name, func):
88 def _extrasetup(self, name, func):
89 """Execute exra setup for registered function, if needed
89 """Execute exra setup for registered function, if needed
90 """
90 """
91 pass
91 pass
92
92
93 class revsetpredicate(_funcregistrarbase):
93 class revsetpredicate(_funcregistrarbase):
94 """Decorator to register revset predicate
94 """Decorator to register revset predicate
95
95
96 Usage::
96 Usage::
97
97
98 revsetpredicate = registrar.revsetpredicate()
98 revsetpredicate = registrar.revsetpredicate()
99
99
100 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
100 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
101 def mypredicatefunc(repo, subset, x):
101 def mypredicatefunc(repo, subset, x):
102 '''Explanation of this revset predicate ....
102 '''Explanation of this revset predicate ....
103 '''
103 '''
104 pass
104 pass
105
105
106 The first string argument is used also in online help.
106 The first string argument is used also in online help.
107
107
108 Optional argument 'safe' indicates whether a predicate is safe for
108 Optional argument 'safe' indicates whether a predicate is safe for
109 DoS attack (False by default).
109 DoS attack (False by default).
110
110
111 'revsetpredicate' instance in example above can be used to
111 'revsetpredicate' instance in example above can be used to
112 decorate multiple functions.
112 decorate multiple functions.
113
113
114 Decorated functions are registered automatically at loading
114 Decorated functions are registered automatically at loading
115 extension, if an instance named as 'revsetpredicate' is used for
115 extension, if an instance named as 'revsetpredicate' is used for
116 decorating in extension.
116 decorating in extension.
117
117
118 Otherwise, explicit 'revset.loadpredicate()' is needed.
118 Otherwise, explicit 'revset.loadpredicate()' is needed.
119 """
119 """
120 _getname = _funcregistrarbase._parsefuncdecl
120 _getname = _funcregistrarbase._parsefuncdecl
121 _docformat = "``%s``\n %s"
121 _docformat = "``%s``\n %s"
122
122
123 def _extrasetup(self, name, func, safe=False):
123 def _extrasetup(self, name, func, safe=False):
124 func._safe = safe
124 func._safe = safe
125
125
126 class filesetpredicate(_funcregistrarbase):
126 class filesetpredicate(_funcregistrarbase):
127 """Decorator to register fileset predicate
127 """Decorator to register fileset predicate
128
128
129 Usage::
129 Usage::
130
130
131 filesetpredicate = registrar.filesetpredicate()
131 filesetpredicate = registrar.filesetpredicate()
132
132
133 @filesetpredicate('mypredicate()')
133 @filesetpredicate('mypredicate()')
134 def mypredicatefunc(mctx, x):
134 def mypredicatefunc(mctx, x):
135 '''Explanation of this fileset predicate ....
135 '''Explanation of this fileset predicate ....
136 '''
136 '''
137 pass
137 pass
138
138
139 The first string argument is used also in online help.
139 The first string argument is used also in online help.
140
140
141 Optional argument 'callstatus' indicates whether a predicate
141 Optional argument 'callstatus' indicates whether a predicate
142 implies 'matchctx.status()' at runtime or not (False, by
142 implies 'matchctx.status()' at runtime or not (False, by
143 default).
143 default).
144
144
145 Optional argument 'callexisting' indicates whether a predicate
145 Optional argument 'callexisting' indicates whether a predicate
146 implies 'matchctx.existing()' at runtime or not (False, by
146 implies 'matchctx.existing()' at runtime or not (False, by
147 default).
147 default).
148
148
149 'filesetpredicate' instance in example above can be used to
149 'filesetpredicate' instance in example above can be used to
150 decorate multiple functions.
150 decorate multiple functions.
151
151
152 Decorated functions are registered automatically at loading
152 Decorated functions are registered automatically at loading
153 extension, if an instance named as 'filesetpredicate' is used for
153 extension, if an instance named as 'filesetpredicate' is used for
154 decorating in extension.
154 decorating in extension.
155
155
156 Otherwise, explicit 'fileset.loadpredicate()' is needed.
156 Otherwise, explicit 'fileset.loadpredicate()' is needed.
157 """
157 """
158 _getname = _funcregistrarbase._parsefuncdecl
158 _getname = _funcregistrarbase._parsefuncdecl
159 _docformat = "``%s``\n %s"
159 _docformat = "``%s``\n %s"
160
160
161 def _extrasetup(self, name, func, callstatus=False, callexisting=False):
161 def _extrasetup(self, name, func, callstatus=False, callexisting=False):
162 func._callstatus = callstatus
162 func._callstatus = callstatus
163 func._callexisting = callexisting
163 func._callexisting = callexisting
164
165 class _templateregistrarbase(_funcregistrarbase):
166 """Base of decorator to register functions as template specific one
167 """
168 _docformat = ":%s: %s"
169
170 class templatekeyword(_templateregistrarbase):
171 """Decorator to register template keyword
172
173 Usage::
174
175 templaetkeyword = registrar.templatekeyword()
176
177 @templatekeyword('mykeyword')
178 def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
179 '''Explanation of this template keyword ....
180 '''
181 pass
182
183 The first string argument is used also in online help.
184
185 'templatekeyword' instance in example above can be used to
186 decorate multiple functions.
187
188 Decorated functions are registered automatically at loading
189 extension, if an instance named as 'templatekeyword' is used for
190 decorating in extension.
191
192 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
193 """
@@ -1,578 +1,584 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 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
8 from __future__ import absolute_import
9
9
10 from .node import hex, nullid
10 from .node import hex, nullid
11 from . import (
11 from . import (
12 encoding,
12 encoding,
13 error,
13 error,
14 hbisect,
14 hbisect,
15 patch,
15 patch,
16 scmutil,
16 scmutil,
17 util,
17 util,
18 )
18 )
19
19
20 # This helper class allows us to handle both:
20 # This helper class allows us to handle both:
21 # "{files}" (legacy command-line-specific list hack) and
21 # "{files}" (legacy command-line-specific list hack) and
22 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
22 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
23 # and to access raw values:
23 # and to access raw values:
24 # "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
24 # "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
25 # "{get(extras, key)}"
25 # "{get(extras, key)}"
26
26
27 class _hybrid(object):
27 class _hybrid(object):
28 def __init__(self, gen, values, makemap, joinfmt=None):
28 def __init__(self, gen, values, makemap, joinfmt=None):
29 self.gen = gen
29 self.gen = gen
30 self.values = values
30 self.values = values
31 self._makemap = makemap
31 self._makemap = makemap
32 if joinfmt:
32 if joinfmt:
33 self.joinfmt = joinfmt
33 self.joinfmt = joinfmt
34 else:
34 else:
35 self.joinfmt = lambda x: x.values()[0]
35 self.joinfmt = lambda x: x.values()[0]
36 def __iter__(self):
36 def __iter__(self):
37 return self.gen
37 return self.gen
38 def itermaps(self):
38 def itermaps(self):
39 makemap = self._makemap
39 makemap = self._makemap
40 for x in self.values:
40 for x in self.values:
41 yield makemap(x)
41 yield makemap(x)
42 def __contains__(self, x):
42 def __contains__(self, x):
43 return x in self.values
43 return x in self.values
44 def __len__(self):
44 def __len__(self):
45 return len(self.values)
45 return len(self.values)
46 def __getattr__(self, name):
46 def __getattr__(self, name):
47 if name != 'get':
47 if name != 'get':
48 raise AttributeError(name)
48 raise AttributeError(name)
49 return getattr(self.values, name)
49 return getattr(self.values, name)
50
50
51 def showlist(name, values, plural=None, element=None, separator=' ', **args):
51 def showlist(name, values, plural=None, element=None, separator=' ', **args):
52 if not element:
52 if not element:
53 element = name
53 element = name
54 f = _showlist(name, values, plural, separator, **args)
54 f = _showlist(name, values, plural, separator, **args)
55 return _hybrid(f, values, lambda x: {element: x})
55 return _hybrid(f, values, lambda x: {element: x})
56
56
57 def _showlist(name, values, plural=None, separator=' ', **args):
57 def _showlist(name, values, plural=None, separator=' ', **args):
58 '''expand set of values.
58 '''expand set of values.
59 name is name of key in template map.
59 name is name of key in template map.
60 values is list of strings or dicts.
60 values is list of strings or dicts.
61 plural is plural of name, if not simply name + 's'.
61 plural is plural of name, if not simply name + 's'.
62 separator is used to join values as a string
62 separator is used to join values as a string
63
63
64 expansion works like this, given name 'foo'.
64 expansion works like this, given name 'foo'.
65
65
66 if values is empty, expand 'no_foos'.
66 if values is empty, expand 'no_foos'.
67
67
68 if 'foo' not in template map, return values as a string,
68 if 'foo' not in template map, return values as a string,
69 joined by 'separator'.
69 joined by 'separator'.
70
70
71 expand 'start_foos'.
71 expand 'start_foos'.
72
72
73 for each value, expand 'foo'. if 'last_foo' in template
73 for each value, expand 'foo'. if 'last_foo' in template
74 map, expand it instead of 'foo' for last key.
74 map, expand it instead of 'foo' for last key.
75
75
76 expand 'end_foos'.
76 expand 'end_foos'.
77 '''
77 '''
78 templ = args['templ']
78 templ = args['templ']
79 if plural:
79 if plural:
80 names = plural
80 names = plural
81 else: names = name + 's'
81 else: names = name + 's'
82 if not values:
82 if not values:
83 noname = 'no_' + names
83 noname = 'no_' + names
84 if noname in templ:
84 if noname in templ:
85 yield templ(noname, **args)
85 yield templ(noname, **args)
86 return
86 return
87 if name not in templ:
87 if name not in templ:
88 if isinstance(values[0], str):
88 if isinstance(values[0], str):
89 yield separator.join(values)
89 yield separator.join(values)
90 else:
90 else:
91 for v in values:
91 for v in values:
92 yield dict(v, **args)
92 yield dict(v, **args)
93 return
93 return
94 startname = 'start_' + names
94 startname = 'start_' + names
95 if startname in templ:
95 if startname in templ:
96 yield templ(startname, **args)
96 yield templ(startname, **args)
97 vargs = args.copy()
97 vargs = args.copy()
98 def one(v, tag=name):
98 def one(v, tag=name):
99 try:
99 try:
100 vargs.update(v)
100 vargs.update(v)
101 except (AttributeError, ValueError):
101 except (AttributeError, ValueError):
102 try:
102 try:
103 for a, b in v:
103 for a, b in v:
104 vargs[a] = b
104 vargs[a] = b
105 except ValueError:
105 except ValueError:
106 vargs[name] = v
106 vargs[name] = v
107 return templ(tag, **vargs)
107 return templ(tag, **vargs)
108 lastname = 'last_' + name
108 lastname = 'last_' + name
109 if lastname in templ:
109 if lastname in templ:
110 last = values.pop()
110 last = values.pop()
111 else:
111 else:
112 last = None
112 last = None
113 for v in values:
113 for v in values:
114 yield one(v)
114 yield one(v)
115 if last is not None:
115 if last is not None:
116 yield one(last, tag=lastname)
116 yield one(last, tag=lastname)
117 endname = 'end_' + names
117 endname = 'end_' + names
118 if endname in templ:
118 if endname in templ:
119 yield templ(endname, **args)
119 yield templ(endname, **args)
120
120
121 def getfiles(repo, ctx, revcache):
121 def getfiles(repo, ctx, revcache):
122 if 'files' not in revcache:
122 if 'files' not in revcache:
123 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
123 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
124 return revcache['files']
124 return revcache['files']
125
125
126 def getlatesttags(repo, ctx, cache, pattern=None):
126 def getlatesttags(repo, ctx, cache, pattern=None):
127 '''return date, distance and name for the latest tag of rev'''
127 '''return date, distance and name for the latest tag of rev'''
128
128
129 cachename = 'latesttags'
129 cachename = 'latesttags'
130 if pattern is not None:
130 if pattern is not None:
131 cachename += '-' + pattern
131 cachename += '-' + pattern
132 match = util.stringmatcher(pattern)[2]
132 match = util.stringmatcher(pattern)[2]
133 else:
133 else:
134 match = util.always
134 match = util.always
135
135
136 if cachename not in cache:
136 if cachename not in cache:
137 # Cache mapping from rev to a tuple with tag date, tag
137 # Cache mapping from rev to a tuple with tag date, tag
138 # distance and tag name
138 # distance and tag name
139 cache[cachename] = {-1: (0, 0, ['null'])}
139 cache[cachename] = {-1: (0, 0, ['null'])}
140 latesttags = cache[cachename]
140 latesttags = cache[cachename]
141
141
142 rev = ctx.rev()
142 rev = ctx.rev()
143 todo = [rev]
143 todo = [rev]
144 while todo:
144 while todo:
145 rev = todo.pop()
145 rev = todo.pop()
146 if rev in latesttags:
146 if rev in latesttags:
147 continue
147 continue
148 ctx = repo[rev]
148 ctx = repo[rev]
149 tags = [t for t in ctx.tags()
149 tags = [t for t in ctx.tags()
150 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
150 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
151 and match(t))]
151 and match(t))]
152 if tags:
152 if tags:
153 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
153 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
154 continue
154 continue
155 try:
155 try:
156 # The tuples are laid out so the right one can be found by
156 # The tuples are laid out so the right one can be found by
157 # comparison.
157 # comparison.
158 pdate, pdist, ptag = max(
158 pdate, pdist, ptag = max(
159 latesttags[p.rev()] for p in ctx.parents())
159 latesttags[p.rev()] for p in ctx.parents())
160 except KeyError:
160 except KeyError:
161 # Cache miss - recurse
161 # Cache miss - recurse
162 todo.append(rev)
162 todo.append(rev)
163 todo.extend(p.rev() for p in ctx.parents())
163 todo.extend(p.rev() for p in ctx.parents())
164 continue
164 continue
165 latesttags[rev] = pdate, pdist + 1, ptag
165 latesttags[rev] = pdate, pdist + 1, ptag
166 return latesttags[rev]
166 return latesttags[rev]
167
167
168 def getrenamedfn(repo, endrev=None):
168 def getrenamedfn(repo, endrev=None):
169 rcache = {}
169 rcache = {}
170 if endrev is None:
170 if endrev is None:
171 endrev = len(repo)
171 endrev = len(repo)
172
172
173 def getrenamed(fn, rev):
173 def getrenamed(fn, rev):
174 '''looks up all renames for a file (up to endrev) the first
174 '''looks up all renames for a file (up to endrev) the first
175 time the file is given. It indexes on the changerev and only
175 time the file is given. It indexes on the changerev and only
176 parses the manifest if linkrev != changerev.
176 parses the manifest if linkrev != changerev.
177 Returns rename info for fn at changerev rev.'''
177 Returns rename info for fn at changerev rev.'''
178 if fn not in rcache:
178 if fn not in rcache:
179 rcache[fn] = {}
179 rcache[fn] = {}
180 fl = repo.file(fn)
180 fl = repo.file(fn)
181 for i in fl:
181 for i in fl:
182 lr = fl.linkrev(i)
182 lr = fl.linkrev(i)
183 renamed = fl.renamed(fl.node(i))
183 renamed = fl.renamed(fl.node(i))
184 rcache[fn][lr] = renamed
184 rcache[fn][lr] = renamed
185 if lr >= endrev:
185 if lr >= endrev:
186 break
186 break
187 if rev in rcache[fn]:
187 if rev in rcache[fn]:
188 return rcache[fn][rev]
188 return rcache[fn][rev]
189
189
190 # If linkrev != rev (i.e. rev not found in rcache) fallback to
190 # If linkrev != rev (i.e. rev not found in rcache) fallback to
191 # filectx logic.
191 # filectx logic.
192 try:
192 try:
193 return repo[rev][fn].renamed()
193 return repo[rev][fn].renamed()
194 except error.LookupError:
194 except error.LookupError:
195 return None
195 return None
196
196
197 return getrenamed
197 return getrenamed
198
198
199
199
200 def showauthor(repo, ctx, templ, **args):
200 def showauthor(repo, ctx, templ, **args):
201 """:author: String. The unmodified author of the changeset."""
201 """:author: String. The unmodified author of the changeset."""
202 return ctx.user()
202 return ctx.user()
203
203
204 def showbisect(repo, ctx, templ, **args):
204 def showbisect(repo, ctx, templ, **args):
205 """:bisect: String. The changeset bisection status."""
205 """:bisect: String. The changeset bisection status."""
206 return hbisect.label(repo, ctx.node())
206 return hbisect.label(repo, ctx.node())
207
207
208 def showbranch(**args):
208 def showbranch(**args):
209 """:branch: String. The name of the branch on which the changeset was
209 """:branch: String. The name of the branch on which the changeset was
210 committed.
210 committed.
211 """
211 """
212 return args['ctx'].branch()
212 return args['ctx'].branch()
213
213
214 def showbranches(**args):
214 def showbranches(**args):
215 """:branches: List of strings. The name of the branch on which the
215 """:branches: List of strings. The name of the branch on which the
216 changeset was committed. Will be empty if the branch name was
216 changeset was committed. Will be empty if the branch name was
217 default. (DEPRECATED)
217 default. (DEPRECATED)
218 """
218 """
219 branch = args['ctx'].branch()
219 branch = args['ctx'].branch()
220 if branch != 'default':
220 if branch != 'default':
221 return showlist('branch', [branch], plural='branches', **args)
221 return showlist('branch', [branch], plural='branches', **args)
222 return showlist('branch', [], plural='branches', **args)
222 return showlist('branch', [], plural='branches', **args)
223
223
224 def showbookmarks(**args):
224 def showbookmarks(**args):
225 """:bookmarks: List of strings. Any bookmarks associated with the
225 """:bookmarks: List of strings. Any bookmarks associated with the
226 changeset. Also sets 'active', the name of the active bookmark.
226 changeset. Also sets 'active', the name of the active bookmark.
227 """
227 """
228 repo = args['ctx']._repo
228 repo = args['ctx']._repo
229 bookmarks = args['ctx'].bookmarks()
229 bookmarks = args['ctx'].bookmarks()
230 active = repo._activebookmark
230 active = repo._activebookmark
231 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
231 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
232 f = _showlist('bookmark', bookmarks, **args)
232 f = _showlist('bookmark', bookmarks, **args)
233 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
233 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
234
234
235 def showchildren(**args):
235 def showchildren(**args):
236 """:children: List of strings. The children of the changeset."""
236 """:children: List of strings. The children of the changeset."""
237 ctx = args['ctx']
237 ctx = args['ctx']
238 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
238 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
239 return showlist('children', childrevs, element='child', **args)
239 return showlist('children', childrevs, element='child', **args)
240
240
241 # Deprecated, but kept alive for help generation a purpose.
241 # Deprecated, but kept alive for help generation a purpose.
242 def showcurrentbookmark(**args):
242 def showcurrentbookmark(**args):
243 """:currentbookmark: String. The active bookmark, if it is
243 """:currentbookmark: String. The active bookmark, if it is
244 associated with the changeset (DEPRECATED)"""
244 associated with the changeset (DEPRECATED)"""
245 return showactivebookmark(**args)
245 return showactivebookmark(**args)
246
246
247 def showactivebookmark(**args):
247 def showactivebookmark(**args):
248 """:activebookmark: String. The active bookmark, if it is
248 """:activebookmark: String. The active bookmark, if it is
249 associated with the changeset"""
249 associated with the changeset"""
250 active = args['repo']._activebookmark
250 active = args['repo']._activebookmark
251 if active and active in args['ctx'].bookmarks():
251 if active and active in args['ctx'].bookmarks():
252 return active
252 return active
253 return ''
253 return ''
254
254
255 def showdate(repo, ctx, templ, **args):
255 def showdate(repo, ctx, templ, **args):
256 """:date: Date information. The date when the changeset was committed."""
256 """:date: Date information. The date when the changeset was committed."""
257 return ctx.date()
257 return ctx.date()
258
258
259 def showdescription(repo, ctx, templ, **args):
259 def showdescription(repo, ctx, templ, **args):
260 """:desc: String. The text of the changeset description."""
260 """:desc: String. The text of the changeset description."""
261 s = ctx.description()
261 s = ctx.description()
262 if isinstance(s, encoding.localstr):
262 if isinstance(s, encoding.localstr):
263 # try hard to preserve utf-8 bytes
263 # try hard to preserve utf-8 bytes
264 return encoding.tolocal(encoding.fromlocal(s).strip())
264 return encoding.tolocal(encoding.fromlocal(s).strip())
265 else:
265 else:
266 return s.strip()
266 return s.strip()
267
267
268 def showdiffstat(repo, ctx, templ, **args):
268 def showdiffstat(repo, ctx, templ, **args):
269 """:diffstat: String. Statistics of changes with the following format:
269 """:diffstat: String. Statistics of changes with the following format:
270 "modified files: +added/-removed lines"
270 "modified files: +added/-removed lines"
271 """
271 """
272 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
272 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
273 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
273 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
274 return '%s: +%s/-%s' % (len(stats), adds, removes)
274 return '%s: +%s/-%s' % (len(stats), adds, removes)
275
275
276 def showextras(**args):
276 def showextras(**args):
277 """:extras: List of dicts with key, value entries of the 'extras'
277 """:extras: List of dicts with key, value entries of the 'extras'
278 field of this changeset."""
278 field of this changeset."""
279 extras = args['ctx'].extra()
279 extras = args['ctx'].extra()
280 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
280 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
281 makemap = lambda k: {'key': k, 'value': extras[k]}
281 makemap = lambda k: {'key': k, 'value': extras[k]}
282 c = [makemap(k) for k in extras]
282 c = [makemap(k) for k in extras]
283 f = _showlist('extra', c, plural='extras', **args)
283 f = _showlist('extra', c, plural='extras', **args)
284 return _hybrid(f, extras, makemap,
284 return _hybrid(f, extras, makemap,
285 lambda x: '%s=%s' % (x['key'], x['value']))
285 lambda x: '%s=%s' % (x['key'], x['value']))
286
286
287 def showfileadds(**args):
287 def showfileadds(**args):
288 """:file_adds: List of strings. Files added by this changeset."""
288 """:file_adds: List of strings. Files added by this changeset."""
289 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
289 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
290 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
290 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
291 element='file', **args)
291 element='file', **args)
292
292
293 def showfilecopies(**args):
293 def showfilecopies(**args):
294 """:file_copies: List of strings. Files copied in this changeset with
294 """:file_copies: List of strings. Files copied in this changeset with
295 their sources.
295 their sources.
296 """
296 """
297 cache, ctx = args['cache'], args['ctx']
297 cache, ctx = args['cache'], args['ctx']
298 copies = args['revcache'].get('copies')
298 copies = args['revcache'].get('copies')
299 if copies is None:
299 if copies is None:
300 if 'getrenamed' not in cache:
300 if 'getrenamed' not in cache:
301 cache['getrenamed'] = getrenamedfn(args['repo'])
301 cache['getrenamed'] = getrenamedfn(args['repo'])
302 copies = []
302 copies = []
303 getrenamed = cache['getrenamed']
303 getrenamed = cache['getrenamed']
304 for fn in ctx.files():
304 for fn in ctx.files():
305 rename = getrenamed(fn, ctx.rev())
305 rename = getrenamed(fn, ctx.rev())
306 if rename:
306 if rename:
307 copies.append((fn, rename[0]))
307 copies.append((fn, rename[0]))
308
308
309 copies = util.sortdict(copies)
309 copies = util.sortdict(copies)
310 makemap = lambda k: {'name': k, 'source': copies[k]}
310 makemap = lambda k: {'name': k, 'source': copies[k]}
311 c = [makemap(k) for k in copies]
311 c = [makemap(k) for k in copies]
312 f = _showlist('file_copy', c, plural='file_copies', **args)
312 f = _showlist('file_copy', c, plural='file_copies', **args)
313 return _hybrid(f, copies, makemap,
313 return _hybrid(f, copies, makemap,
314 lambda x: '%s (%s)' % (x['name'], x['source']))
314 lambda x: '%s (%s)' % (x['name'], x['source']))
315
315
316 # showfilecopiesswitch() displays file copies only if copy records are
316 # showfilecopiesswitch() displays file copies only if copy records are
317 # provided before calling the templater, usually with a --copies
317 # provided before calling the templater, usually with a --copies
318 # command line switch.
318 # command line switch.
319 def showfilecopiesswitch(**args):
319 def showfilecopiesswitch(**args):
320 """:file_copies_switch: List of strings. Like "file_copies" but displayed
320 """:file_copies_switch: List of strings. Like "file_copies" but displayed
321 only if the --copied switch is set.
321 only if the --copied switch is set.
322 """
322 """
323 copies = args['revcache'].get('copies') or []
323 copies = args['revcache'].get('copies') or []
324 copies = util.sortdict(copies)
324 copies = util.sortdict(copies)
325 makemap = lambda k: {'name': k, 'source': copies[k]}
325 makemap = lambda k: {'name': k, 'source': copies[k]}
326 c = [makemap(k) for k in copies]
326 c = [makemap(k) for k in copies]
327 f = _showlist('file_copy', c, plural='file_copies', **args)
327 f = _showlist('file_copy', c, plural='file_copies', **args)
328 return _hybrid(f, copies, makemap,
328 return _hybrid(f, copies, makemap,
329 lambda x: '%s (%s)' % (x['name'], x['source']))
329 lambda x: '%s (%s)' % (x['name'], x['source']))
330
330
331 def showfiledels(**args):
331 def showfiledels(**args):
332 """:file_dels: List of strings. Files removed by this changeset."""
332 """:file_dels: List of strings. Files removed by this changeset."""
333 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
333 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
334 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
334 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
335 element='file', **args)
335 element='file', **args)
336
336
337 def showfilemods(**args):
337 def showfilemods(**args):
338 """:file_mods: List of strings. Files modified by this changeset."""
338 """:file_mods: List of strings. Files modified by this changeset."""
339 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
339 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
340 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
340 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
341 element='file', **args)
341 element='file', **args)
342
342
343 def showfiles(**args):
343 def showfiles(**args):
344 """:files: List of strings. All files modified, added, or removed by this
344 """:files: List of strings. All files modified, added, or removed by this
345 changeset.
345 changeset.
346 """
346 """
347 return showlist('file', args['ctx'].files(), **args)
347 return showlist('file', args['ctx'].files(), **args)
348
348
349 def showgraphnode(repo, ctx, **args):
349 def showgraphnode(repo, ctx, **args):
350 """:graphnode: String. The character representing the changeset node in
350 """:graphnode: String. The character representing the changeset node in
351 an ASCII revision graph"""
351 an ASCII revision graph"""
352 wpnodes = repo.dirstate.parents()
352 wpnodes = repo.dirstate.parents()
353 if wpnodes[1] == nullid:
353 if wpnodes[1] == nullid:
354 wpnodes = wpnodes[:1]
354 wpnodes = wpnodes[:1]
355 if ctx.node() in wpnodes:
355 if ctx.node() in wpnodes:
356 return '@'
356 return '@'
357 elif ctx.obsolete():
357 elif ctx.obsolete():
358 return 'x'
358 return 'x'
359 elif ctx.closesbranch():
359 elif ctx.closesbranch():
360 return '_'
360 return '_'
361 else:
361 else:
362 return 'o'
362 return 'o'
363
363
364 def showlatesttag(**args):
364 def showlatesttag(**args):
365 """:latesttag: List of strings. The global tags on the most recent globally
365 """:latesttag: List of strings. The global tags on the most recent globally
366 tagged ancestor of this changeset.
366 tagged ancestor of this changeset.
367 """
367 """
368 return showlatesttags(None, **args)
368 return showlatesttags(None, **args)
369
369
370 def showlatesttags(pattern, **args):
370 def showlatesttags(pattern, **args):
371 """helper method for the latesttag keyword and function"""
371 """helper method for the latesttag keyword and function"""
372 repo, ctx = args['repo'], args['ctx']
372 repo, ctx = args['repo'], args['ctx']
373 cache = args['cache']
373 cache = args['cache']
374 latesttags = getlatesttags(repo, ctx, cache, pattern)
374 latesttags = getlatesttags(repo, ctx, cache, pattern)
375
375
376 # latesttag[0] is an implementation detail for sorting csets on different
376 # latesttag[0] is an implementation detail for sorting csets on different
377 # branches in a stable manner- it is the date the tagged cset was created,
377 # branches in a stable manner- it is the date the tagged cset was created,
378 # not the date the tag was created. Therefore it isn't made visible here.
378 # not the date the tag was created. Therefore it isn't made visible here.
379 makemap = lambda v: {
379 makemap = lambda v: {
380 'changes': _showchangessincetag,
380 'changes': _showchangessincetag,
381 'distance': latesttags[1],
381 'distance': latesttags[1],
382 'latesttag': v, # BC with {latesttag % '{latesttag}'}
382 'latesttag': v, # BC with {latesttag % '{latesttag}'}
383 'tag': v
383 'tag': v
384 }
384 }
385
385
386 tags = latesttags[2]
386 tags = latesttags[2]
387 f = _showlist('latesttag', tags, separator=':', **args)
387 f = _showlist('latesttag', tags, separator=':', **args)
388 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
388 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
389
389
390 def showlatesttagdistance(repo, ctx, templ, cache, **args):
390 def showlatesttagdistance(repo, ctx, templ, cache, **args):
391 """:latesttagdistance: Integer. Longest path to the latest tag."""
391 """:latesttagdistance: Integer. Longest path to the latest tag."""
392 return getlatesttags(repo, ctx, cache)[1]
392 return getlatesttags(repo, ctx, cache)[1]
393
393
394 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
394 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
395 """:changessincelatesttag: Integer. All ancestors not in the latest tag."""
395 """:changessincelatesttag: Integer. All ancestors not in the latest tag."""
396 latesttag = getlatesttags(repo, ctx, cache)[2][0]
396 latesttag = getlatesttags(repo, ctx, cache)[2][0]
397
397
398 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
398 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
399
399
400 def _showchangessincetag(repo, ctx, **args):
400 def _showchangessincetag(repo, ctx, **args):
401 offset = 0
401 offset = 0
402 revs = [ctx.rev()]
402 revs = [ctx.rev()]
403 tag = args['tag']
403 tag = args['tag']
404
404
405 # The only() revset doesn't currently support wdir()
405 # The only() revset doesn't currently support wdir()
406 if ctx.rev() is None:
406 if ctx.rev() is None:
407 offset = 1
407 offset = 1
408 revs = [p.rev() for p in ctx.parents()]
408 revs = [p.rev() for p in ctx.parents()]
409
409
410 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
410 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
411
411
412 def showmanifest(**args):
412 def showmanifest(**args):
413 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
413 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
414 mnode = ctx.manifestnode()
414 mnode = ctx.manifestnode()
415 if mnode is None:
415 if mnode is None:
416 # just avoid crash, we might want to use the 'ff...' hash in future
416 # just avoid crash, we might want to use the 'ff...' hash in future
417 return
417 return
418 args = args.copy()
418 args = args.copy()
419 args.update({'rev': repo.manifest.rev(mnode), 'node': hex(mnode)})
419 args.update({'rev': repo.manifest.rev(mnode), 'node': hex(mnode)})
420 return templ('manifest', **args)
420 return templ('manifest', **args)
421
421
422 def shownames(namespace, **args):
422 def shownames(namespace, **args):
423 """helper method to generate a template keyword for a namespace"""
423 """helper method to generate a template keyword for a namespace"""
424 ctx = args['ctx']
424 ctx = args['ctx']
425 repo = ctx.repo()
425 repo = ctx.repo()
426 ns = repo.names[namespace]
426 ns = repo.names[namespace]
427 names = ns.names(repo, ctx.node())
427 names = ns.names(repo, ctx.node())
428 return showlist(ns.templatename, names, plural=namespace, **args)
428 return showlist(ns.templatename, names, plural=namespace, **args)
429
429
430 def shownamespaces(**args):
430 def shownamespaces(**args):
431 """:namespaces: Dict of lists. Names attached to this changeset per
431 """:namespaces: Dict of lists. Names attached to this changeset per
432 namespace."""
432 namespace."""
433 ctx = args['ctx']
433 ctx = args['ctx']
434 repo = ctx.repo()
434 repo = ctx.repo()
435 namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()),
435 namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()),
436 **args))
436 **args))
437 for k, ns in repo.names.iteritems())
437 for k, ns in repo.names.iteritems())
438 f = _showlist('namespace', list(namespaces), **args)
438 f = _showlist('namespace', list(namespaces), **args)
439 return _hybrid(f, namespaces,
439 return _hybrid(f, namespaces,
440 lambda k: {'namespace': k, 'names': namespaces[k]},
440 lambda k: {'namespace': k, 'names': namespaces[k]},
441 lambda x: x['namespace'])
441 lambda x: x['namespace'])
442
442
443 def shownode(repo, ctx, templ, **args):
443 def shownode(repo, ctx, templ, **args):
444 """:node: String. The changeset identification hash, as a 40 hexadecimal
444 """:node: String. The changeset identification hash, as a 40 hexadecimal
445 digit string.
445 digit string.
446 """
446 """
447 return ctx.hex()
447 return ctx.hex()
448
448
449 def showp1rev(repo, ctx, templ, **args):
449 def showp1rev(repo, ctx, templ, **args):
450 """:p1rev: Integer. The repository-local revision number of the changeset's
450 """:p1rev: Integer. The repository-local revision number of the changeset's
451 first parent, or -1 if the changeset has no parents."""
451 first parent, or -1 if the changeset has no parents."""
452 return ctx.p1().rev()
452 return ctx.p1().rev()
453
453
454 def showp2rev(repo, ctx, templ, **args):
454 def showp2rev(repo, ctx, templ, **args):
455 """:p2rev: Integer. The repository-local revision number of the changeset's
455 """:p2rev: Integer. The repository-local revision number of the changeset's
456 second parent, or -1 if the changeset has no second parent."""
456 second parent, or -1 if the changeset has no second parent."""
457 return ctx.p2().rev()
457 return ctx.p2().rev()
458
458
459 def showp1node(repo, ctx, templ, **args):
459 def showp1node(repo, ctx, templ, **args):
460 """:p1node: String. The identification hash of the changeset's first parent,
460 """:p1node: String. The identification hash of the changeset's first parent,
461 as a 40 digit hexadecimal string. If the changeset has no parents, all
461 as a 40 digit hexadecimal string. If the changeset has no parents, all
462 digits are 0."""
462 digits are 0."""
463 return ctx.p1().hex()
463 return ctx.p1().hex()
464
464
465 def showp2node(repo, ctx, templ, **args):
465 def showp2node(repo, ctx, templ, **args):
466 """:p2node: String. The identification hash of the changeset's second
466 """:p2node: String. The identification hash of the changeset's second
467 parent, as a 40 digit hexadecimal string. If the changeset has no second
467 parent, as a 40 digit hexadecimal string. If the changeset has no second
468 parent, all digits are 0."""
468 parent, all digits are 0."""
469 return ctx.p2().hex()
469 return ctx.p2().hex()
470
470
471 def showparents(**args):
471 def showparents(**args):
472 """:parents: List of strings. The parents of the changeset in "rev:node"
472 """:parents: List of strings. The parents of the changeset in "rev:node"
473 format. If the changeset has only one "natural" parent (the predecessor
473 format. If the changeset has only one "natural" parent (the predecessor
474 revision) nothing is shown."""
474 revision) nothing is shown."""
475 repo = args['repo']
475 repo = args['repo']
476 ctx = args['ctx']
476 ctx = args['ctx']
477 pctxs = scmutil.meaningfulparents(repo, ctx)
477 pctxs = scmutil.meaningfulparents(repo, ctx)
478 prevs = [str(p.rev()) for p in pctxs] # ifcontains() needs a list of str
478 prevs = [str(p.rev()) for p in pctxs] # ifcontains() needs a list of str
479 parents = [[('rev', p.rev()),
479 parents = [[('rev', p.rev()),
480 ('node', p.hex()),
480 ('node', p.hex()),
481 ('phase', p.phasestr())]
481 ('phase', p.phasestr())]
482 for p in pctxs]
482 for p in pctxs]
483 f = _showlist('parent', parents, **args)
483 f = _showlist('parent', parents, **args)
484 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}})
484 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}})
485
485
486 def showphase(repo, ctx, templ, **args):
486 def showphase(repo, ctx, templ, **args):
487 """:phase: String. The changeset phase name."""
487 """:phase: String. The changeset phase name."""
488 return ctx.phasestr()
488 return ctx.phasestr()
489
489
490 def showphaseidx(repo, ctx, templ, **args):
490 def showphaseidx(repo, ctx, templ, **args):
491 """:phaseidx: Integer. The changeset phase index."""
491 """:phaseidx: Integer. The changeset phase index."""
492 return ctx.phase()
492 return ctx.phase()
493
493
494 def showrev(repo, ctx, templ, **args):
494 def showrev(repo, ctx, templ, **args):
495 """:rev: Integer. The repository-local changeset revision number."""
495 """:rev: Integer. The repository-local changeset revision number."""
496 return scmutil.intrev(ctx.rev())
496 return scmutil.intrev(ctx.rev())
497
497
498 def showrevslist(name, revs, **args):
498 def showrevslist(name, revs, **args):
499 """helper to generate a list of revisions in which a mapped template will
499 """helper to generate a list of revisions in which a mapped template will
500 be evaluated"""
500 be evaluated"""
501 repo = args['ctx'].repo()
501 repo = args['ctx'].repo()
502 revs = [str(r) for r in revs] # ifcontains() needs a list of str
502 revs = [str(r) for r in revs] # ifcontains() needs a list of str
503 f = _showlist(name, revs, **args)
503 f = _showlist(name, revs, **args)
504 return _hybrid(f, revs,
504 return _hybrid(f, revs,
505 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}})
505 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}})
506
506
507 def showsubrepos(**args):
507 def showsubrepos(**args):
508 """:subrepos: List of strings. Updated subrepositories in the changeset."""
508 """:subrepos: List of strings. Updated subrepositories in the changeset."""
509 ctx = args['ctx']
509 ctx = args['ctx']
510 substate = ctx.substate
510 substate = ctx.substate
511 if not substate:
511 if not substate:
512 return showlist('subrepo', [], **args)
512 return showlist('subrepo', [], **args)
513 psubstate = ctx.parents()[0].substate or {}
513 psubstate = ctx.parents()[0].substate or {}
514 subrepos = []
514 subrepos = []
515 for sub in substate:
515 for sub in substate:
516 if sub not in psubstate or substate[sub] != psubstate[sub]:
516 if sub not in psubstate or substate[sub] != psubstate[sub]:
517 subrepos.append(sub) # modified or newly added in ctx
517 subrepos.append(sub) # modified or newly added in ctx
518 for sub in psubstate:
518 for sub in psubstate:
519 if sub not in substate:
519 if sub not in substate:
520 subrepos.append(sub) # removed in ctx
520 subrepos.append(sub) # removed in ctx
521 return showlist('subrepo', sorted(subrepos), **args)
521 return showlist('subrepo', sorted(subrepos), **args)
522
522
523 # don't remove "showtags" definition, even though namespaces will put
523 # don't remove "showtags" definition, even though namespaces will put
524 # a helper function for "tags" keyword into "keywords" map automatically,
524 # a helper function for "tags" keyword into "keywords" map automatically,
525 # because online help text is built without namespaces initialization
525 # because online help text is built without namespaces initialization
526 def showtags(**args):
526 def showtags(**args):
527 """:tags: List of strings. Any tags associated with the changeset."""
527 """:tags: List of strings. Any tags associated with the changeset."""
528 return shownames('tags', **args)
528 return shownames('tags', **args)
529
529
530 # keywords are callables like:
530 # keywords are callables like:
531 # fn(repo, ctx, templ, cache, revcache, **args)
531 # fn(repo, ctx, templ, cache, revcache, **args)
532 # with:
532 # with:
533 # repo - current repository instance
533 # repo - current repository instance
534 # ctx - the changectx being displayed
534 # ctx - the changectx being displayed
535 # templ - the templater instance
535 # templ - the templater instance
536 # cache - a cache dictionary for the whole templater run
536 # cache - a cache dictionary for the whole templater run
537 # revcache - a cache dictionary for the current revision
537 # revcache - a cache dictionary for the current revision
538 keywords = {
538 keywords = {
539 'activebookmark': showactivebookmark,
539 'activebookmark': showactivebookmark,
540 'author': showauthor,
540 'author': showauthor,
541 'bisect': showbisect,
541 'bisect': showbisect,
542 'branch': showbranch,
542 'branch': showbranch,
543 'branches': showbranches,
543 'branches': showbranches,
544 'bookmarks': showbookmarks,
544 'bookmarks': showbookmarks,
545 'changessincelatesttag': showchangessincelatesttag,
545 'changessincelatesttag': showchangessincelatesttag,
546 'children': showchildren,
546 'children': showchildren,
547 # currentbookmark is deprecated
547 # currentbookmark is deprecated
548 'currentbookmark': showcurrentbookmark,
548 'currentbookmark': showcurrentbookmark,
549 'date': showdate,
549 'date': showdate,
550 'desc': showdescription,
550 'desc': showdescription,
551 'diffstat': showdiffstat,
551 'diffstat': showdiffstat,
552 'extras': showextras,
552 'extras': showextras,
553 'file_adds': showfileadds,
553 'file_adds': showfileadds,
554 'file_copies': showfilecopies,
554 'file_copies': showfilecopies,
555 'file_copies_switch': showfilecopiesswitch,
555 'file_copies_switch': showfilecopiesswitch,
556 'file_dels': showfiledels,
556 'file_dels': showfiledels,
557 'file_mods': showfilemods,
557 'file_mods': showfilemods,
558 'files': showfiles,
558 'files': showfiles,
559 'graphnode': showgraphnode,
559 'graphnode': showgraphnode,
560 'latesttag': showlatesttag,
560 'latesttag': showlatesttag,
561 'latesttagdistance': showlatesttagdistance,
561 'latesttagdistance': showlatesttagdistance,
562 'manifest': showmanifest,
562 'manifest': showmanifest,
563 'namespaces': shownamespaces,
563 'namespaces': shownamespaces,
564 'node': shownode,
564 'node': shownode,
565 'p1rev': showp1rev,
565 'p1rev': showp1rev,
566 'p1node': showp1node,
566 'p1node': showp1node,
567 'p2rev': showp2rev,
567 'p2rev': showp2rev,
568 'p2node': showp2node,
568 'p2node': showp2node,
569 'parents': showparents,
569 'parents': showparents,
570 'phase': showphase,
570 'phase': showphase,
571 'phaseidx': showphaseidx,
571 'phaseidx': showphaseidx,
572 'rev': showrev,
572 'rev': showrev,
573 'subrepos': showsubrepos,
573 'subrepos': showsubrepos,
574 'tags': showtags,
574 'tags': showtags,
575 }
575 }
576
576
577 def loadkeyword(ui, extname, registrarobj):
578 """Load template keyword from specified registrarobj
579 """
580 for name, func in registrarobj._table.iteritems():
581 keywords[name] = func
582
577 # tell hggettext to extract docstrings from these functions:
583 # tell hggettext to extract docstrings from these functions:
578 i18nfunctions = keywords.values()
584 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now