Patchwork [2,of,5] push: catch and process PushkeyFailed error

login
register
mail settings
Submitter Pierre-Yves David
Date June 8, 2015, 5:29 p.m.
Message ID <b715c2156629ecf7622b.1433784556@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/9554/
State Accepted
Headers show

Comments

Pierre-Yves David - June 8, 2015, 5:29 p.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@fb.com>
# Date 1433547011 25200
#      Fri Jun 05 16:30:11 2015 -0700
# Node ID b715c2156629ecf7622bec3196ca8c35d7f173b4
# Parent  3dde3db78235e4aaa2e184e2dac73e9a8d27d74c
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. 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

Patch

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -115,10 +115,13 @@  class pushoperation(object):
         self.outobsmarkers = set()
         # outgoing bookmarks
         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):
         """future remote heads if the changeset push succeeds"""
         return self.outgoing.missingheads
@@ -621,20 +624,26 @@  def _pushbundle2(pushop):
     # do not push if nothing to push
     if bundler.nbparts <= 1:
         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)
 
 def _pushchangeset(pushop):
     """Make the actual push of changeset bundle to remote repo"""
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
@@ -722,18 +722,22 @@  Check abort from mandatory pushkey
 
   $ cat > mandatorypart.py << EOF
   > 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
   >     part = bundler.newpart('pushkey')
   >     part.addparam('namespace', enc('phases'))
   >     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]
   > pretxnchangegroup=
   > pretxnclose.failpush=
@@ -757,11 +761,11 @@  Check abort from mandatory pushkey
   do not push the key !
   pushkey-abort: prepushkey.failpush hook exited with status 1
   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
   searching for changes
   remote: adding changesets
@@ -794,18 +798,22 @@  Check abort from mandatory pushkey
 
   $ cat > mandatorypart.py << EOF
   > 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
   >     part = bundler.newpart('pushkey')
   >     part.addparam('namespace', enc('phases'))
   >     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]
   > prepushkey.failpush =
   > EOF
@@ -824,11 +832,11 @@  Check abort from mandatory pushkey
   Cleaning up the mess...
   rollback completed
   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
   searching for changes
   remote: adding changesets