# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 2015-06-05 23:30:11
# Node ID 8182163ae983a42a7f3de032bca7afd8574f6e16
# Parent  a5192774e9256b695ae4bc846c1e2c3947a62840

push: catch and process PushkeyFailed error

We add a way to register "pushkey failure callback" that will be used if the
push is aborted by a pushkey failure. A part generator adding mandatory pushkey
parts should register a failure callback for all of them. The callback will be
in charge of generating a meaningful abort if this part fails.

If no callback is registered, the error is propagated.

Catch PushkeyFailed error in exchange.

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -117,6 +117,9 @@ class pushoperation(object):
         self.outbookmarks = []
         # transaction manager
         self.trmanager = None
+        # map { pushkey partid -> callback handling failure}
+        # used to handle exception from mandatory pushkey part failure
+        self.pkfailcb = {}
 
     @util.propertycache
     def futureheads(self):
@@ -623,16 +626,22 @@ def _pushbundle2(pushop):
         return
     stream = util.chunkbuffer(bundler.getchunks())
     try:
-        reply = pushop.remote.unbundle(stream, ['force'], 'push')
-    except error.BundleValueError, exc:
-        raise util.Abort('missing support for %s' % exc)
-    try:
-        trgetter = None
-        if pushback:
-            trgetter = pushop.trmanager.transaction
-        op = bundle2.processbundle(pushop.repo, reply, trgetter)
-    except error.BundleValueError, exc:
-        raise util.Abort('missing support for %s' % exc)
+        try:
+            reply = pushop.remote.unbundle(stream, ['force'], 'push')
+        except error.BundleValueError, exc:
+            raise util.Abort('missing support for %s' % exc)
+        try:
+            trgetter = None
+            if pushback:
+                trgetter = pushop.trmanager.transaction
+            op = bundle2.processbundle(pushop.repo, reply, trgetter)
+        except error.BundleValueError, exc:
+            raise util.Abort('missing support for %s' % exc)
+    except error.PushkeyFailed, exc:
+        partid = int(exc.partid)
+        if partid not in pushop.pkfailcb:
+            raise
+        pushop.pkfailcb[partid](pushop, exc)
     for rephand in replyhandlers:
         rephand(op)
 
diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -724,6 +724,7 @@ Check abort from mandatory pushkey
   > from mercurial import exchange
   > from mercurial import pushkey
   > from mercurial import node
+  > from mercurial import error
   > @exchange.b2partsgenerator('failingpuskey')
   > def addfailingpushey(pushop, bundler):
   >     enc = pushkey.encode
@@ -732,6 +733,9 @@ Check abort from mandatory pushkey
   >     part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex()))
   >     part.addparam('old', enc(str(0))) # successful update
   >     part.addparam('new', enc(str(0)))
+  >     def fail(pushop, exc):
+  >         raise error.Abort('Correct phase push failed (because hooks)')
+  >     pushop.pkfailcb[part.id] = fail
   > EOF
   $ cat >> $HGRCPATH << EOF
   > [hooks]
@@ -759,7 +763,7 @@ Check abort from mandatory pushkey
   transaction abort!
   Cleaning up the mess...
   rollback completed
-  abort: failed to update value for "phases/cd010b8cd998f3981a5a8115f94f8da4ab506089"
+  abort: Correct phase push failed (because hooks)
   [255]
   $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
   pushing to ssh://user@dummy/other
@@ -796,6 +800,7 @@ Check abort from mandatory pushkey
   > from mercurial import exchange
   > from mercurial import pushkey
   > from mercurial import node
+  > from mercurial import error
   > @exchange.b2partsgenerator('failingpuskey')
   > def addfailingpushey(pushop, bundler):
   >     enc = pushkey.encode
@@ -804,6 +809,9 @@ Check abort from mandatory pushkey
   >     part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex()))
   >     part.addparam('old', enc(str(4))) # will fail
   >     part.addparam('new', enc(str(3)))
+  >     def fail(pushop, exc):
+  >         raise error.Abort('Clown phase push failed')
+  >     pushop.pkfailcb[part.id] = fail
   > EOF
   $ cat >> $HGRCPATH << EOF
   > [hooks]
@@ -826,7 +834,7 @@ Check abort from mandatory pushkey
   pushkey: lock state after "phases"
   lock:  free
   wlock: free
-  abort: failed to update value for "phases/cd010b8cd998f3981a5a8115f94f8da4ab506089"
+  abort: Clown phase push failed
   [255]
   $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
   pushing to ssh://user@dummy/other