##// END OF EJS Templates
hook: ignore EPIPE when flushing stdout/stderr...
Mitchell Plamann -
r46329:b3e8d8e4 default
parent child Browse files
Show More
@@ -1,340 +1,349 b''
1 # hook.py - hook support for mercurial
1 # hook.py - hook support for mercurial
2 #
2 #
3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 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
8 from __future__ import absolute_import
9
9
10 import contextlib
10 import contextlib
11 import errno
11 import os
12 import os
12 import sys
13 import sys
13
14
14 from .i18n import _
15 from .i18n import _
15 from .pycompat import getattr
16 from .pycompat import getattr
16 from . import (
17 from . import (
17 demandimport,
18 demandimport,
18 encoding,
19 encoding,
19 error,
20 error,
20 extensions,
21 extensions,
21 pycompat,
22 pycompat,
22 util,
23 util,
23 )
24 )
24 from .utils import (
25 from .utils import (
25 procutil,
26 procutil,
26 resourceutil,
27 resourceutil,
27 stringutil,
28 stringutil,
28 )
29 )
29
30
30
31
31 def pythonhook(ui, repo, htype, hname, funcname, args, throw):
32 def pythonhook(ui, repo, htype, hname, funcname, args, throw):
32 '''call python hook. hook is callable object, looked up as
33 '''call python hook. hook is callable object, looked up as
33 name in python module. if callable returns "true", hook
34 name in python module. if callable returns "true", hook
34 fails, else passes. if hook raises exception, treated as
35 fails, else passes. if hook raises exception, treated as
35 hook failure. exception propagates if throw is "true".
36 hook failure. exception propagates if throw is "true".
36
37
37 reason for "true" meaning "hook failed" is so that
38 reason for "true" meaning "hook failed" is so that
38 unmodified commands (e.g. mercurial.commands.update) can
39 unmodified commands (e.g. mercurial.commands.update) can
39 be run as hooks without wrappers to convert return values.'''
40 be run as hooks without wrappers to convert return values.'''
40
41
41 if callable(funcname):
42 if callable(funcname):
42 obj = funcname
43 obj = funcname
43 funcname = pycompat.sysbytes(obj.__module__ + "." + obj.__name__)
44 funcname = pycompat.sysbytes(obj.__module__ + "." + obj.__name__)
44 else:
45 else:
45 d = funcname.rfind(b'.')
46 d = funcname.rfind(b'.')
46 if d == -1:
47 if d == -1:
47 raise error.HookLoadError(
48 raise error.HookLoadError(
48 _(b'%s hook is invalid: "%s" not in a module')
49 _(b'%s hook is invalid: "%s" not in a module')
49 % (hname, funcname)
50 % (hname, funcname)
50 )
51 )
51 modname = funcname[:d]
52 modname = funcname[:d]
52 oldpaths = sys.path
53 oldpaths = sys.path
53 if resourceutil.mainfrozen():
54 if resourceutil.mainfrozen():
54 # binary installs require sys.path manipulation
55 # binary installs require sys.path manipulation
55 modpath, modfile = os.path.split(modname)
56 modpath, modfile = os.path.split(modname)
56 if modpath and modfile:
57 if modpath and modfile:
57 sys.path = sys.path[:] + [modpath]
58 sys.path = sys.path[:] + [modpath]
58 modname = modfile
59 modname = modfile
59 with demandimport.deactivated():
60 with demandimport.deactivated():
60 try:
61 try:
61 obj = __import__(pycompat.sysstr(modname))
62 obj = __import__(pycompat.sysstr(modname))
62 except (ImportError, SyntaxError):
63 except (ImportError, SyntaxError):
63 e1 = sys.exc_info()
64 e1 = sys.exc_info()
64 try:
65 try:
65 # extensions are loaded with hgext_ prefix
66 # extensions are loaded with hgext_ prefix
66 obj = __import__("hgext_%s" % pycompat.sysstr(modname))
67 obj = __import__("hgext_%s" % pycompat.sysstr(modname))
67 except (ImportError, SyntaxError):
68 except (ImportError, SyntaxError):
68 e2 = sys.exc_info()
69 e2 = sys.exc_info()
69 if ui.tracebackflag:
70 if ui.tracebackflag:
70 ui.warn(
71 ui.warn(
71 _(
72 _(
72 b'exception from first failed import '
73 b'exception from first failed import '
73 b'attempt:\n'
74 b'attempt:\n'
74 )
75 )
75 )
76 )
76 ui.traceback(e1)
77 ui.traceback(e1)
77 if ui.tracebackflag:
78 if ui.tracebackflag:
78 ui.warn(
79 ui.warn(
79 _(
80 _(
80 b'exception from second failed import '
81 b'exception from second failed import '
81 b'attempt:\n'
82 b'attempt:\n'
82 )
83 )
83 )
84 )
84 ui.traceback(e2)
85 ui.traceback(e2)
85
86
86 if not ui.tracebackflag:
87 if not ui.tracebackflag:
87 tracebackhint = _(
88 tracebackhint = _(
88 b'run with --traceback for stack trace'
89 b'run with --traceback for stack trace'
89 )
90 )
90 else:
91 else:
91 tracebackhint = None
92 tracebackhint = None
92 raise error.HookLoadError(
93 raise error.HookLoadError(
93 _(b'%s hook is invalid: import of "%s" failed')
94 _(b'%s hook is invalid: import of "%s" failed')
94 % (hname, modname),
95 % (hname, modname),
95 hint=tracebackhint,
96 hint=tracebackhint,
96 )
97 )
97 sys.path = oldpaths
98 sys.path = oldpaths
98 try:
99 try:
99 for p in funcname.split(b'.')[1:]:
100 for p in funcname.split(b'.')[1:]:
100 obj = getattr(obj, p)
101 obj = getattr(obj, p)
101 except AttributeError:
102 except AttributeError:
102 raise error.HookLoadError(
103 raise error.HookLoadError(
103 _(b'%s hook is invalid: "%s" is not defined')
104 _(b'%s hook is invalid: "%s" is not defined')
104 % (hname, funcname)
105 % (hname, funcname)
105 )
106 )
106 if not callable(obj):
107 if not callable(obj):
107 raise error.HookLoadError(
108 raise error.HookLoadError(
108 _(b'%s hook is invalid: "%s" is not callable')
109 _(b'%s hook is invalid: "%s" is not callable')
109 % (hname, funcname)
110 % (hname, funcname)
110 )
111 )
111
112
112 ui.note(_(b"calling hook %s: %s\n") % (hname, funcname))
113 ui.note(_(b"calling hook %s: %s\n") % (hname, funcname))
113 starttime = util.timer()
114 starttime = util.timer()
114
115
115 try:
116 try:
116 r = obj(ui=ui, repo=repo, hooktype=htype, **pycompat.strkwargs(args))
117 r = obj(ui=ui, repo=repo, hooktype=htype, **pycompat.strkwargs(args))
117 except Exception as exc:
118 except Exception as exc:
118 if isinstance(exc, error.Abort):
119 if isinstance(exc, error.Abort):
119 ui.warn(_(b'error: %s hook failed: %s\n') % (hname, exc.args[0]))
120 ui.warn(_(b'error: %s hook failed: %s\n') % (hname, exc.args[0]))
120 else:
121 else:
121 ui.warn(
122 ui.warn(
122 _(b'error: %s hook raised an exception: %s\n')
123 _(b'error: %s hook raised an exception: %s\n')
123 % (hname, stringutil.forcebytestr(exc))
124 % (hname, stringutil.forcebytestr(exc))
124 )
125 )
125 if throw:
126 if throw:
126 raise
127 raise
127 if not ui.tracebackflag:
128 if not ui.tracebackflag:
128 ui.warn(_(b'(run with --traceback for stack trace)\n'))
129 ui.warn(_(b'(run with --traceback for stack trace)\n'))
129 ui.traceback()
130 ui.traceback()
130 return True, True
131 return True, True
131 finally:
132 finally:
132 duration = util.timer() - starttime
133 duration = util.timer() - starttime
133 ui.log(
134 ui.log(
134 b'pythonhook',
135 b'pythonhook',
135 b'pythonhook-%s: %s finished in %0.2f seconds\n',
136 b'pythonhook-%s: %s finished in %0.2f seconds\n',
136 htype,
137 htype,
137 funcname,
138 funcname,
138 duration,
139 duration,
139 )
140 )
140 if r:
141 if r:
141 if throw:
142 if throw:
142 raise error.HookAbort(_(b'%s hook failed') % hname)
143 raise error.HookAbort(_(b'%s hook failed') % hname)
143 ui.warn(_(b'warning: %s hook failed\n') % hname)
144 ui.warn(_(b'warning: %s hook failed\n') % hname)
144 return r, False
145 return r, False
145
146
146
147
147 def _exthook(ui, repo, htype, name, cmd, args, throw):
148 def _exthook(ui, repo, htype, name, cmd, args, throw):
148 starttime = util.timer()
149 starttime = util.timer()
149 env = {}
150 env = {}
150
151
151 # make in-memory changes visible to external process
152 # make in-memory changes visible to external process
152 if repo is not None:
153 if repo is not None:
153 tr = repo.currenttransaction()
154 tr = repo.currenttransaction()
154 repo.dirstate.write(tr)
155 repo.dirstate.write(tr)
155 if tr and tr.writepending():
156 if tr and tr.writepending():
156 env[b'HG_PENDING'] = repo.root
157 env[b'HG_PENDING'] = repo.root
157 env[b'HG_HOOKTYPE'] = htype
158 env[b'HG_HOOKTYPE'] = htype
158 env[b'HG_HOOKNAME'] = name
159 env[b'HG_HOOKNAME'] = name
159
160
160 for k, v in pycompat.iteritems(args):
161 for k, v in pycompat.iteritems(args):
161 # transaction changes can accumulate MBs of data, so skip it
162 # transaction changes can accumulate MBs of data, so skip it
162 # for external hooks
163 # for external hooks
163 if k == b'changes':
164 if k == b'changes':
164 continue
165 continue
165 if callable(v):
166 if callable(v):
166 v = v()
167 v = v()
167 if isinstance(v, (dict, list)):
168 if isinstance(v, (dict, list)):
168 v = stringutil.pprint(v)
169 v = stringutil.pprint(v)
169 env[b'HG_' + k.upper()] = v
170 env[b'HG_' + k.upper()] = v
170
171
171 if ui.configbool(b'hooks', b'tonative.%s' % name, False):
172 if ui.configbool(b'hooks', b'tonative.%s' % name, False):
172 oldcmd = cmd
173 oldcmd = cmd
173 cmd = procutil.shelltonative(cmd, env)
174 cmd = procutil.shelltonative(cmd, env)
174 if cmd != oldcmd:
175 if cmd != oldcmd:
175 ui.note(_(b'converting hook "%s" to native\n') % name)
176 ui.note(_(b'converting hook "%s" to native\n') % name)
176
177
177 ui.note(_(b"running hook %s: %s\n") % (name, cmd))
178 ui.note(_(b"running hook %s: %s\n") % (name, cmd))
178
179
179 if repo:
180 if repo:
180 cwd = repo.root
181 cwd = repo.root
181 else:
182 else:
182 cwd = encoding.getcwd()
183 cwd = encoding.getcwd()
183 r = ui.system(cmd, environ=env, cwd=cwd, blockedtag=b'exthook-%s' % (name,))
184 r = ui.system(cmd, environ=env, cwd=cwd, blockedtag=b'exthook-%s' % (name,))
184
185
185 duration = util.timer() - starttime
186 duration = util.timer() - starttime
186 ui.log(
187 ui.log(
187 b'exthook',
188 b'exthook',
188 b'exthook-%s: %s finished in %0.2f seconds\n',
189 b'exthook-%s: %s finished in %0.2f seconds\n',
189 name,
190 name,
190 cmd,
191 cmd,
191 duration,
192 duration,
192 )
193 )
193 if r:
194 if r:
194 desc = procutil.explainexit(r)
195 desc = procutil.explainexit(r)
195 if throw:
196 if throw:
196 raise error.HookAbort(_(b'%s hook %s') % (name, desc))
197 raise error.HookAbort(_(b'%s hook %s') % (name, desc))
197 ui.warn(_(b'warning: %s hook %s\n') % (name, desc))
198 ui.warn(_(b'warning: %s hook %s\n') % (name, desc))
198 return r
199 return r
199
200
200
201
201 # represent an untrusted hook command
202 # represent an untrusted hook command
202 _fromuntrusted = object()
203 _fromuntrusted = object()
203
204
204
205
205 def _allhooks(ui):
206 def _allhooks(ui):
206 """return a list of (hook-id, cmd) pairs sorted by priority"""
207 """return a list of (hook-id, cmd) pairs sorted by priority"""
207 hooks = _hookitems(ui)
208 hooks = _hookitems(ui)
208 # Be careful in this section, propagating the real commands from untrusted
209 # Be careful in this section, propagating the real commands from untrusted
209 # sources would create a security vulnerability, make sure anything altered
210 # sources would create a security vulnerability, make sure anything altered
210 # in that section uses "_fromuntrusted" as its command.
211 # in that section uses "_fromuntrusted" as its command.
211 untrustedhooks = _hookitems(ui, _untrusted=True)
212 untrustedhooks = _hookitems(ui, _untrusted=True)
212 for name, value in untrustedhooks.items():
213 for name, value in untrustedhooks.items():
213 trustedvalue = hooks.get(name, ((), (), name, _fromuntrusted))
214 trustedvalue = hooks.get(name, ((), (), name, _fromuntrusted))
214 if value != trustedvalue:
215 if value != trustedvalue:
215 (lp, lo, lk, lv) = trustedvalue
216 (lp, lo, lk, lv) = trustedvalue
216 hooks[name] = (lp, lo, lk, _fromuntrusted)
217 hooks[name] = (lp, lo, lk, _fromuntrusted)
217 # (end of the security sensitive section)
218 # (end of the security sensitive section)
218 return [(k, v) for p, o, k, v in sorted(hooks.values())]
219 return [(k, v) for p, o, k, v in sorted(hooks.values())]
219
220
220
221
221 def _hookitems(ui, _untrusted=False):
222 def _hookitems(ui, _untrusted=False):
222 """return all hooks items ready to be sorted"""
223 """return all hooks items ready to be sorted"""
223 hooks = {}
224 hooks = {}
224 for name, cmd in ui.configitems(b'hooks', untrusted=_untrusted):
225 for name, cmd in ui.configitems(b'hooks', untrusted=_untrusted):
225 if name.startswith(b'priority.') or name.startswith(b'tonative.'):
226 if name.startswith(b'priority.') or name.startswith(b'tonative.'):
226 continue
227 continue
227
228
228 priority = ui.configint(b'hooks', b'priority.%s' % name, 0)
229 priority = ui.configint(b'hooks', b'priority.%s' % name, 0)
229 hooks[name] = ((-priority,), (len(hooks),), name, cmd)
230 hooks[name] = ((-priority,), (len(hooks),), name, cmd)
230 return hooks
231 return hooks
231
232
232
233
233 _redirect = False
234 _redirect = False
234
235
235
236
236 def redirect(state):
237 def redirect(state):
237 global _redirect
238 global _redirect
238 _redirect = state
239 _redirect = state
239
240
240
241
241 def hashook(ui, htype):
242 def hashook(ui, htype):
242 """return True if a hook is configured for 'htype'"""
243 """return True if a hook is configured for 'htype'"""
243 if not ui.callhooks:
244 if not ui.callhooks:
244 return False
245 return False
245 for hname, cmd in _allhooks(ui):
246 for hname, cmd in _allhooks(ui):
246 if hname.split(b'.')[0] == htype and cmd:
247 if hname.split(b'.')[0] == htype and cmd:
247 return True
248 return True
248 return False
249 return False
249
250
250
251
251 def hook(ui, repo, htype, throw=False, **args):
252 def hook(ui, repo, htype, throw=False, **args):
252 if not ui.callhooks:
253 if not ui.callhooks:
253 return False
254 return False
254
255
255 hooks = []
256 hooks = []
256 for hname, cmd in _allhooks(ui):
257 for hname, cmd in _allhooks(ui):
257 if hname.split(b'.')[0] == htype and cmd:
258 if hname.split(b'.')[0] == htype and cmd:
258 hooks.append((hname, cmd))
259 hooks.append((hname, cmd))
259
260
260 res = runhooks(ui, repo, htype, hooks, throw=throw, **args)
261 res = runhooks(ui, repo, htype, hooks, throw=throw, **args)
261 r = False
262 r = False
262 for hname, cmd in hooks:
263 for hname, cmd in hooks:
263 r = res[hname][0] or r
264 r = res[hname][0] or r
264 return r
265 return r
265
266
266
267
267 @contextlib.contextmanager
268 @contextlib.contextmanager
268 def redirect_stdio():
269 def redirect_stdio():
269 """Redirects stdout to stderr, if possible."""
270 """Redirects stdout to stderr, if possible."""
270
271
271 oldstdout = -1
272 oldstdout = -1
272 try:
273 try:
273 if _redirect:
274 if _redirect:
274 try:
275 try:
275 stdoutno = procutil.stdout.fileno()
276 stdoutno = procutil.stdout.fileno()
276 stderrno = procutil.stderr.fileno()
277 stderrno = procutil.stderr.fileno()
277 # temporarily redirect stdout to stderr, if possible
278 # temporarily redirect stdout to stderr, if possible
278 if stdoutno >= 0 and stderrno >= 0:
279 if stdoutno >= 0 and stderrno >= 0:
279 procutil.stdout.flush()
280 procutil.stdout.flush()
280 oldstdout = os.dup(stdoutno)
281 oldstdout = os.dup(stdoutno)
281 os.dup2(stderrno, stdoutno)
282 os.dup2(stderrno, stdoutno)
282 except (OSError, AttributeError):
283 except (OSError, AttributeError):
283 # files seem to be bogus, give up on redirecting (WSGI, etc)
284 # files seem to be bogus, give up on redirecting (WSGI, etc)
284 pass
285 pass
285
286
286 yield
287 yield
287
288
288 finally:
289 finally:
289 # The stderr is fully buffered on Windows when connected to a pipe.
290 # The stderr is fully buffered on Windows when connected to a pipe.
290 # A forcible flush is required to make small stderr data in the
291 # A forcible flush is required to make small stderr data in the
291 # remote side available to the client immediately.
292 # remote side available to the client immediately.
292 procutil.stderr.flush()
293 try:
294 procutil.stderr.flush()
295 except IOError as err:
296 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
297 raise error.StdioError(err)
293
298
294 if _redirect and oldstdout >= 0:
299 if _redirect and oldstdout >= 0:
295 procutil.stdout.flush() # write hook output to stderr fd
300 try:
301 procutil.stdout.flush() # write hook output to stderr fd
302 except IOError as err:
303 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
304 raise error.StdioError(err)
296 os.dup2(oldstdout, stdoutno)
305 os.dup2(oldstdout, stdoutno)
297 os.close(oldstdout)
306 os.close(oldstdout)
298
307
299
308
300 def runhooks(ui, repo, htype, hooks, throw=False, **args):
309 def runhooks(ui, repo, htype, hooks, throw=False, **args):
301 args = pycompat.byteskwargs(args)
310 args = pycompat.byteskwargs(args)
302 res = {}
311 res = {}
303
312
304 with redirect_stdio():
313 with redirect_stdio():
305 for hname, cmd in hooks:
314 for hname, cmd in hooks:
306 if cmd is _fromuntrusted:
315 if cmd is _fromuntrusted:
307 if throw:
316 if throw:
308 raise error.HookAbort(
317 raise error.HookAbort(
309 _(b'untrusted hook %s not executed') % hname,
318 _(b'untrusted hook %s not executed') % hname,
310 hint=_(b"see 'hg help config.trusted'"),
319 hint=_(b"see 'hg help config.trusted'"),
311 )
320 )
312 ui.warn(_(b'warning: untrusted hook %s not executed\n') % hname)
321 ui.warn(_(b'warning: untrusted hook %s not executed\n') % hname)
313 r = 1
322 r = 1
314 raised = False
323 raised = False
315 elif callable(cmd):
324 elif callable(cmd):
316 r, raised = pythonhook(ui, repo, htype, hname, cmd, args, throw)
325 r, raised = pythonhook(ui, repo, htype, hname, cmd, args, throw)
317 elif cmd.startswith(b'python:'):
326 elif cmd.startswith(b'python:'):
318 if cmd.count(b':') >= 2:
327 if cmd.count(b':') >= 2:
319 path, cmd = cmd[7:].rsplit(b':', 1)
328 path, cmd = cmd[7:].rsplit(b':', 1)
320 path = util.expandpath(path)
329 path = util.expandpath(path)
321 if repo:
330 if repo:
322 path = os.path.join(repo.root, path)
331 path = os.path.join(repo.root, path)
323 try:
332 try:
324 mod = extensions.loadpath(path, b'hghook.%s' % hname)
333 mod = extensions.loadpath(path, b'hghook.%s' % hname)
325 except Exception:
334 except Exception:
326 ui.write(_(b"loading %s hook failed:\n") % hname)
335 ui.write(_(b"loading %s hook failed:\n") % hname)
327 raise
336 raise
328 hookfn = getattr(mod, cmd)
337 hookfn = getattr(mod, cmd)
329 else:
338 else:
330 hookfn = cmd[7:].strip()
339 hookfn = cmd[7:].strip()
331 r, raised = pythonhook(
340 r, raised = pythonhook(
332 ui, repo, htype, hname, hookfn, args, throw
341 ui, repo, htype, hname, hookfn, args, throw
333 )
342 )
334 else:
343 else:
335 r = _exthook(ui, repo, htype, hname, cmd, args, throw)
344 r = _exthook(ui, repo, htype, hname, cmd, args, throw)
336 raised = False
345 raised = False
337
346
338 res[hname] = r, raised
347 res[hname] = r, raised
339
348
340 return res
349 return res
@@ -1,67 +1,67 b''
1 Test that, when an hg push is interrupted and the remote side recieves SIGPIPE,
1 Test that, when an hg push is interrupted and the remote side recieves SIGPIPE,
2 the remote hg is able to successfully roll back the transaction.
2 the remote hg is able to successfully roll back the transaction.
3
3
4 $ hg init -q remote
4 $ hg init -q remote
5 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" -q ssh://user@dummy/`pwd`/remote local
5 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" -q ssh://user@dummy/`pwd`/remote local
6
6
7 $ check_for_abandoned_transaction() {
7 $ check_for_abandoned_transaction() {
8 > [[ -f $TESTTMP/remote/.hg/store/journal ]] && echo "Abandoned transaction!"
8 > [[ -f $TESTTMP/remote/.hg/store/journal ]] && echo "Abandoned transaction!"
9 > }
9 > }
10
10
11 $ pidfile=`pwd`/pidfile
11 $ pidfile=`pwd`/pidfile
12 $ >$pidfile
12 $ >$pidfile
13
13
14 $ script() {
14 $ script() {
15 > cat >"$1"
15 > cat >"$1"
16 > chmod +x "$1"
16 > chmod +x "$1"
17 > }
17 > }
18
18
19 On the remote end, run hg, piping stdout and stderr through processes that we
19 On the remote end, run hg, piping stdout and stderr through processes that we
20 know the PIDs of. We will later kill these to simulate an ssh client
20 know the PIDs of. We will later kill these to simulate an ssh client
21 disconnecting.
21 disconnecting.
22
22
23 $ killable_pipe=`pwd`/killable_pipe.sh
23 $ killable_pipe=`pwd`/killable_pipe.sh
24 $ script $killable_pipe <<EOF
24 $ script $killable_pipe <<EOF
25 > #!/bin/bash
25 > #!/bin/bash
26 > echo \$\$ >> $pidfile
26 > echo \$\$ >> $pidfile
27 > exec cat
27 > exec cat
28 > EOF
28 > EOF
29
29
30 $ remotecmd=`pwd`/remotecmd.sh
30 $ remotecmd=`pwd`/remotecmd.sh
31 $ script $remotecmd <<EOF
31 $ script $remotecmd <<EOF
32 > #!/bin/bash
32 > #!/bin/bash
33 > hg "\$@" 1> >($killable_pipe) 2> >($killable_pipe >&2)
33 > hg "\$@" 1> >($killable_pipe) 2> >($killable_pipe >&2)
34 > EOF
34 > EOF
35
35
36 In the pretxnchangegroup hook, kill the PIDs recorded above to simulate ssh
36 In the pretxnchangegroup hook, kill the PIDs recorded above to simulate ssh
37 disconnecting. Then exit nonzero, to force a transaction rollback.
37 disconnecting. Then exit nonzero, to force a transaction rollback.
38
38
39 $ hook_script=`pwd`/pretxnchangegroup.sh
39 $ hook_script=`pwd`/pretxnchangegroup.sh
40 $ script $hook_script <<EOF
40 $ script $hook_script <<EOF
41 > #!/bin/bash
41 > #!/bin/bash
42 > for pid in \$(cat $pidfile) ; do
42 > for pid in \$(cat $pidfile) ; do
43 > kill \$pid
43 > kill \$pid
44 > while kill -0 \$pid 2>/dev/null ; do
44 > while kill -0 \$pid 2>/dev/null ; do
45 > sleep 0.1
45 > sleep 0.1
46 > done
46 > done
47 > done
47 > done
48 > exit 1
48 > exit 1
49 > EOF
49 > EOF
50
50
51 $ cat >remote/.hg/hgrc <<EOF
51 $ cat >remote/.hg/hgrc <<EOF
52 > [hooks]
52 > [hooks]
53 > pretxnchangegroup.break-things=$hook_script
53 > pretxnchangegroup.break-things=$hook_script
54 > EOF
54 > EOF
55
55
56 $ cd local
56 $ cd local
57 $ echo foo > foo ; hg commit -qAm "commit"
57 $ echo foo > foo ; hg commit -qAm "commit"
58 $ hg push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --remotecmd $remotecmd 2>&1 | grep -v $killable_pipe
58 $ hg push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --remotecmd $remotecmd 2>&1 | grep -v $killable_pipe
59 pushing to ssh://user@dummy/$TESTTMP/remote
59 pushing to ssh://user@dummy/$TESTTMP/remote
60 searching for changes
60 searching for changes
61 remote: adding changesets
61 remote: adding changesets
62 remote: adding manifests
62 remote: adding manifests
63 remote: adding file changes
63 remote: adding file changes
64 abort: stream ended unexpectedly (got 0 bytes, expected 4)
64 abort: stream ended unexpectedly (got 0 bytes, expected 4)
65
65
66 $ check_for_abandoned_transaction
66 $ check_for_abandoned_transaction
67 Abandoned transaction!
67 [1]
General Comments 0
You need to be logged in to leave comments. Login now