Patchwork [02,of,12,stable] bundle2: gracefully handle abort during unbundle

login
register
mail settings
Submitter Pierre-Yves David
Date April 22, 2014, 8:10 p.m.
Message ID <32afe109f32541e1a56b.1398197446@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/4421/
State Accepted
Commit 952af771bc17e95039e066412aa3e270dbc5dc26
Headers show

Comments

Pierre-Yves David - April 22, 2014, 8:10 p.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1398120532 25200
#      Mon Apr 21 15:48:52 2014 -0700
# Branch stable
# Node ID 32afe109f32541e1a56b15706bdf033defc39f15
# Parent  be776cb27517486130180fa9d7abdd9f20fdff84
bundle2: gracefully handle abort during unbundle

Clients expect a bundle2 reply to their bundle2 submission. So we catch the
Abort error and turn into a bundle2 containing a part transporting the exception
data. The unbundling of this reply will raise the error again.

Patch

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -741,5 +741,11 @@  def handlereplycaps(op, inpart):
     The payload contains the capabilities information for the reply"""
     caps = decodecaps(inpart.read())
     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'))
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -806,9 +806,19 @@  def unbundle(repo, proto, heads):
     except util.Abort, inst:
         # The old code we moved used sys.stderr directly.
         # 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))
diff --git a/tests/test-bundle2.t b/tests/test-bundle2.t
--- a/tests/test-bundle2.t
+++ b/tests/test-bundle2.t
@@ -881,5 +881,82 @@  Check final content.
   o  changeset:   0:cd010b8cd998
      user:        Nicolas Dumazet <nicdumz.commits@gmail.com>
      date:        Sat Apr 30 15:24:48 2011 +0200
      summary:     A
   
+
+Error Handling
+==============
+
+Checks 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]
+
+