##// END OF EJS Templates
support hooks written in python....
Vadim Gelfer -
r2155:ff255b41 default
parent child Browse files
Show More
@@ -131,11 +131,11 b' decode/encode::'
131 **.txt = tempfile: unix2dos -n INFILE OUTFILE
131 **.txt = tempfile: unix2dos -n INFILE OUTFILE
132
132
133 hooks::
133 hooks::
134 Commands that get automatically executed by various actions such as
134 Commands or Python functions that get automatically executed by
135 starting or finishing a commit. Multiple commands can be run for
135 various actions such as starting or finishing a commit. Multiple
136 the same action by appending a suffix to the action. Overriding a
136 hooks can be run for the same action by appending a suffix to the
137 site-wide hook can be done by changing its value or setting it to
137 action. Overriding a site-wide hook can be done by changing its
138 an empty string.
138 value or setting it to an empty string.
139
139
140 Example .hg/hgrc:
140 Example .hg/hgrc:
141
141
@@ -211,6 +211,21 b' hooks::'
211 the environment for backwards compatibility, but their use is
211 the environment for backwards compatibility, but their use is
212 deprecated, and they will be removed in a future release.
212 deprecated, and they will be removed in a future release.
213
213
214 The syntax for Python hooks is as follows:
215
216 hookname = python:modulename.submodule.callable
217
218 Python hooks are run within the Mercurial process. Each hook is
219 called with at least three keyword arguments: a ui object (keyword
220 "ui"), a repository object (keyword "repo"), and a "hooktype"
221 keyword that tells what kind of hook is used. Arguments listed as
222 environment variables above are passed as keyword arguments, with no
223 "HG_" prefix, and names in lower case.
224
225 A Python hook must return a "true" value to succeed. Returning a
226 "false" value or raising an exception is treated as failure of the
227 hook.
228
214 http_proxy::
229 http_proxy::
215 Used to access web-based Mercurial repositories through a HTTP
230 Used to access web-based Mercurial repositories through a HTTP
216 proxy.
231 proxy.
@@ -11,7 +11,8 b' from node import *'
11 from i18n import gettext as _
11 from i18n import gettext as _
12 from demandload import *
12 from demandload import *
13 demandload(globals(), "appendfile changegroup")
13 demandload(globals(), "appendfile changegroup")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui revlog")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
15 demandload(globals(), "revlog sys traceback")
15
16
16 class localrepository(object):
17 class localrepository(object):
17 def __del__(self):
18 def __del__(self):
@@ -71,7 +72,59 b' class localrepository(object):'
71 os.mkdir(self.join("data"))
72 os.mkdir(self.join("data"))
72
73
73 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
74 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
75
74 def hook(self, name, throw=False, **args):
76 def hook(self, name, throw=False, **args):
77 def callhook(hname, funcname):
78 '''call python hook. hook is callable object, looked up as
79 name in python module. if callable returns "true", hook
80 passes, else fails. if hook raises exception, treated as
81 hook failure. exception propagates if throw is "true".'''
82
83 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
84 d = funcname.rfind('.')
85 if d == -1:
86 raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
87 % (hname, funcname))
88 modname = funcname[:d]
89 try:
90 obj = __import__(modname)
91 except ImportError:
92 raise util.Abort(_('%s hook is invalid '
93 '(import of "%s" failed)') %
94 (hname, modname))
95 try:
96 for p in funcname.split('.')[1:]:
97 obj = getattr(obj, p)
98 except AttributeError, err:
99 raise util.Abort(_('%s hook is invalid '
100 '("%s" is not defined)') %
101 (hname, funcname))
102 if not callable(obj):
103 raise util.Abort(_('%s hook is invalid '
104 '("%s" is not callable)') %
105 (hname, funcname))
106 try:
107 r = obj(ui=ui, repo=repo, hooktype=name, **args)
108 except (KeyboardInterrupt, util.SignalInterrupt):
109 raise
110 except Exception, exc:
111 if isinstance(exc, util.Abort):
112 self.ui.warn(_('error: %s hook failed: %s\n') %
113 (hname, exc.args[0] % exc.args[1:]))
114 else:
115 self.ui.warn(_('error: %s hook raised an exception: '
116 '%s\n') % (hname, exc))
117 if throw:
118 raise
119 if "--traceback" in sys.argv[1:]:
120 traceback.print_exc()
121 return False
122 if not r:
123 if throw:
124 raise util.Abort(_('%s hook failed') % hname)
125 self.ui.warn(_('error: %s hook failed\n') % hname)
126 return r
127
75 def runhook(name, cmd):
128 def runhook(name, cmd):
76 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
129 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
77 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
130 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
@@ -90,7 +143,10 b' class localrepository(object):'
90 if hname.split(".", 1)[0] == name and cmd]
143 if hname.split(".", 1)[0] == name and cmd]
91 hooks.sort()
144 hooks.sort()
92 for hname, cmd in hooks:
145 for hname, cmd in hooks:
93 r = runhook(hname, cmd) and r
146 if cmd.startswith('python:'):
147 r = callhook(hname, cmd[7:].strip()) and r
148 else:
149 r = runhook(hname, cmd) and r
94 return r
150 return r
95
151
96 def tags(self):
152 def tags(self):
@@ -87,4 +87,93 b' hg undo'
87 echo 'preoutgoing.forbid = echo preoutgoing.forbid hook; exit 1' >> ../a/.hg/hgrc
87 echo 'preoutgoing.forbid = echo preoutgoing.forbid hook; exit 1' >> ../a/.hg/hgrc
88 hg pull ../a
88 hg pull ../a
89
89
90 cat > hooktests.py <<EOF
91 from mercurial import util
92
93 uncallable = 0
94
95 def printargs(args):
96 args.pop('ui', None)
97 args.pop('repo', None)
98 a = list(args.items())
99 a.sort()
100 print 'hook args:'
101 for k, v in a:
102 print ' ', k, v
103 return True
104
105 def passhook(**args):
106 printargs(args)
107 return True
108
109 def failhook(**args):
110 printargs(args)
111
112 class LocalException(Exception):
113 pass
114
115 def raisehook(**args):
116 raise LocalException('exception from hook')
117
118 def aborthook(**args):
119 raise util.Abort('raise abort from hook')
120
121 def brokenhook(**args):
122 return 1 + {}
123
124 class container:
125 unreachable = 1
126 EOF
127
128 echo '# test python hooks'
129 PYTHONPATH="$PWD:$PYTHONPATH"
130 export PYTHONPATH
131
132 echo '[hooks]' > ../a/.hg/hgrc
133 echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
134 hg pull ../a 2>&1 | grep 'raised an exception'
135
136 echo '[hooks]' > ../a/.hg/hgrc
137 echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
138 hg pull ../a 2>&1 | grep 'raised an exception'
139
140 echo '[hooks]' > ../a/.hg/hgrc
141 echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
142 hg pull ../a
143
144 echo '[hooks]' > ../a/.hg/hgrc
145 echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
146 hg pull ../a
147
148 echo '[hooks]' > ../a/.hg/hgrc
149 echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
150 hg pull ../a
151
152 echo '[hooks]' > ../a/.hg/hgrc
153 echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
154 hg pull ../a
155
156 echo '[hooks]' > ../a/.hg/hgrc
157 echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
158 hg pull ../a
159
160 echo '[hooks]' > ../a/.hg/hgrc
161 echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
162 hg pull ../a
163
164 echo '[hooks]' > ../a/.hg/hgrc
165 echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
166 hg pull ../a
167
168 echo '[hooks]' > ../a/.hg/hgrc
169 echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
170 hg pull ../a
171
172 echo '# make sure --traceback works'
173 echo '[hooks]' > .hg/hgrc
174 echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
175
176 echo a >> a
177 hg --traceback commit -A -m a 2>&1 | grep '^Traceback'
178
90 exit 0
179 exit 0
@@ -89,3 +89,43 b' preoutgoing.forbid hook'
89 pulling from ../a
89 pulling from ../a
90 searching for changes
90 searching for changes
91 abort: preoutgoing.forbid hook exited with status 1
91 abort: preoutgoing.forbid hook exited with status 1
92 # test python hooks
93 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
94 error: preoutgoing.raise hook raised an exception: exception from hook
95 pulling from ../a
96 searching for changes
97 error: preoutgoing.abort hook failed: raise abort from hook
98 abort: raise abort from hook
99 pulling from ../a
100 searching for changes
101 hook args:
102 hooktype preoutgoing
103 source pull
104 abort: preoutgoing.fail hook failed
105 pulling from ../a
106 searching for changes
107 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
108 pulling from ../a
109 searching for changes
110 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
111 pulling from ../a
112 searching for changes
113 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
114 pulling from ../a
115 searching for changes
116 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
117 pulling from ../a
118 searching for changes
119 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
120 pulling from ../a
121 searching for changes
122 hook args:
123 hooktype preoutgoing
124 source pull
125 adding changesets
126 adding manifests
127 adding file changes
128 added 1 changesets with 1 changes to 1 files
129 (run 'hg update' to get a working copy)
130 # make sure --traceback works
131 Traceback (most recent call last):
General Comments 0
You need to be logged in to leave comments. Login now