bundle2: gracefully handle abort during unbundle
Clients expect a bundle2 reply to their bundle2 submission. So we
catch the Abort error and turn it into a bundle2 containing a part
transporting the exception data. The unbundling of this reply will
raise the error again.
--- a/mercurial/bundle2.py Tue Apr 22 11:22:41 2014 -0700
+++ b/mercurial/bundle2.py Mon Apr 21 15:48:52 2014 -0700
@@ -743,3 +743,9 @@
if op.reply is None:
op.reply = bundle20(op.ui, caps)
+@parthandler('b2x:error:abort')
+def handlereplycaps(op, inpart):
+ """Used to transmit abort error over the wire"""
+ manargs = dict(inpart.mandatoryparams)
+ advargs = dict(inpart.advisoryparams)
+ raise util.Abort(manargs['message'], hint=advargs.get('hint'))
--- a/mercurial/wireproto.py Tue Apr 22 11:22:41 2014 -0700
+++ b/mercurial/wireproto.py Mon Apr 21 15:48:52 2014 -0700
@@ -808,7 +808,17 @@
# We did not change it to minimise code change.
# This need to be moved to something proper.
# Feel free to do it.
- sys.stderr.write("abort: %s\n" % inst)
- return pushres(0)
+ if getattr(inst, 'duringunbundle2', False):
+ bundler = bundle2.bundle20(repo.ui)
+ manargs = [('message', str(inst))]
+ advargs = []
+ if inst.hint is not None:
+ advargs.append(('hint', inst.hint))
+ bundler.addpart(bundle2.bundlepart('B2X:ERROR:ABORT',
+ manargs, advargs))
+ return streamres(bundler.getchunks())
+ else:
+ sys.stderr.write("abort: %s\n" % inst)
+ return pushres(0)
except exchange.PushRaced, exc:
return pusherr(str(exc))
--- a/tests/test-bundle2.t Tue Apr 22 11:22:41 2014 -0700
+++ b/tests/test-bundle2.t Mon Apr 21 15:48:52 2014 -0700
@@ -883,3 +883,80 @@
date: Sat Apr 30 15:24:48 2011 +0200
summary: A
+
+Error Handling
+==============
+
+Check that errors are properly returned to the client during push.
+
+Setting up
+
+ $ cat > failpush.py << EOF
+ > """A small extension that makes push fails when using bundle2
+ >
+ > used to test error handling in bundle2
+ > """
+ >
+ > from mercurial import util
+ > from mercurial import bundle2
+ > from mercurial import exchange
+ > from mercurial import extensions
+ >
+ > def _pushbundle2failpart(orig, pushop, bundler):
+ > extradata = orig(pushop, bundler)
+ > part = bundle2.bundlepart('test:abort')
+ > bundler.addpart(part)
+ > return extradata
+ >
+ > @bundle2.parthandler("test:abort")
+ > def handleabort(op, part):
+ > raise util.Abort('Abandon ship!', hint="don't panic")
+ >
+ > def uisetup(ui):
+ > extensions.wrapfunction(exchange, '_pushbundle2extraparts', _pushbundle2failpart)
+ >
+ > EOF
+
+ $ cd main
+ $ hg up tip
+ 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ echo 'I' > I
+ $ hg add I
+ $ hg ci -m 'I'
+ $ hg id
+ e7ec4e813ba6 tip
+ $ cd ..
+
+ $ cat << EOF >> $HGRCPATH
+ > [extensions]
+ > failpush=$TESTTMP/failpush.py
+ > EOF
+
+ $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+ $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
+ $ cat other.pid >> $DAEMON_PIDS
+
+Doing the actual push: Abort error
+
+ $ hg -R main push other -r e7ec4e813ba6
+ pushing to other
+ searching for changes
+ abort: Abandon ship!
+ (don't panic)
+ [255]
+
+ $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
+ pushing to ssh://user@dummy/other
+ searching for changes
+ abort: Abandon ship!
+ (don't panic)
+ [255]
+
+ $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
+ pushing to http://localhost:$HGPORT2/
+ searching for changes
+ abort: Abandon ship!
+ (don't panic)
+ [255]
+
+