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 |
|
134 | Commands or Python functions that get automatically executed by | |
135 |
starting or finishing a commit. Multiple |
|
135 | various actions such as starting or finishing a commit. Multiple | |
136 |
the same action by appending a suffix to the |
|
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 |
|
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 |
|
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