Patchwork [09,of,12,stable] bundle2: fix error raising during heads checking

login
register
mail settings
Submitter Pierre-Yves David
Date April 22, 2014, 8:10 p.m.
Message ID <8b5be208d6b83c89f8a5.1398197453@marginatus.alto.octopoid.net>
Download mbox | patch
Permalink /patch/4428/
State Accepted
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 1398131949 25200
#      Mon Apr 21 18:59:09 2014 -0700
# Branch stable
# Node ID 8b5be208d6b83c89f8a58ddf959bffaaab482c40
# Parent  db91dd50bd02279afe60da8dd3a33b299ff517da
bundle2: fix error raising during heads checking

If the heads on the server differ from the one reported seen by the client at
bundle time, we raise a PushRaced exception. However, the part raising the
exception was broken.

To fix it, we move the PushRaced class in the error module so it can be
accessible everywhere without import cycle.

A test is also added to prevent regression.

Patch

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -143,11 +143,11 @@  preserve.
 import util
 import struct
 import urllib
 import string
 
-import changegroup
+import changegroup, error
 from i18n import _
 
 _pack = struct.pack
 _unpack = struct.unpack
 
@@ -728,11 +728,11 @@  def handlechangegroup(op, inpart):
     while len(h) == 20:
         heads.append(h)
         h = inpart.read(20)
     assert not h
     if heads != op.repo.heads():
-        raise exchange.PushRaced()
+        raise error.PushRaced()
 
 @parthandler('b2x:output')
 def handleoutput(op, inpart):
     """forward output captured on the server to the client"""
     for line in inpart.read().splitlines():
diff --git a/mercurial/error.py b/mercurial/error.py
--- a/mercurial/error.py
+++ b/mercurial/error.py
@@ -92,5 +92,9 @@  class AmbiguousCommand(Exception):
 class SignalInterrupt(KeyboardInterrupt):
     """Exception raised on SIGTERM and SIGHUP."""
 
 class SignatureError(Exception):
     pass
+
+class PushRaced(RuntimeError):
+    """An exception raised during unbundling that indicate a push race"""
+
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -6,11 +6,11 @@ 
 # GNU General Public License version 2 or any later version.
 
 from i18n import _
 from node import hex, nullid
 import errno, urllib
-import util, scmutil, changegroup, base85
+import util, scmutil, changegroup, base85, error
 import discovery, phases, obsolete, bookmarks, bundle2
 
 def readbundle(ui, fh, fname, vfs=None):
     header = changegroup.readexactly(fh, 4)
 
@@ -706,13 +706,10 @@  def getbundle(repo, source, heads=None, 
 def _getbundleextrapart(bundler, repo, source, heads=None, common=None,
                         bundlecaps=None, **kwargs):
     """hook function to let extensions add parts to the requested bundle"""
     pass
 
-class PushRaced(RuntimeError):
-    """An exception raised during unbundling that indicate a push race"""
-
 def check_heads(repo, their_heads, context):
     """check if the heads of a repo have been modified
 
     Used by peer for unbundling.
     """
@@ -720,12 +717,12 @@  def check_heads(repo, their_heads, conte
     heads_hash = util.sha1(''.join(sorted(heads))).digest()
     if not (their_heads == ['force'] or their_heads == heads or
             their_heads == ['hashed', heads_hash]):
         # someone else committed/pushed/unbundled while we
         # were transferring data
-        raise PushRaced('repository changed while %s - '
-                        'please try again' % context)
+        raise error.PushRaced('repository changed while %s - '
+                              'please try again' % context)
 
 def unbundle(repo, cg, heads, source, url):
     """Apply a bundle to a repo.
 
     this function makes sure the repo is locked during the application and have
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -131,11 +131,11 @@  class localpeer(peer.peerrepository):
                 # This little dance should be dropped eventually when the API
                 # is finally improved.
                 stream = util.chunkbuffer(ret.getchunks())
                 ret = bundle2.unbundle20(self.ui, stream)
             return ret
-        except exchange.PushRaced, exc:
+        except error.PushRaced, exc:
             raise error.ResponseError(_('push failed:'), exc.message)
 
     def lock(self):
         return self._repo.lock()
 
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -824,7 +824,7 @@  def unbundle(repo, proto, heads):
                                                manargs, advargs))
             return streamres(bundler.getchunks())
         else:
             sys.stderr.write("abort: %s\n" % inst)
             return pushres(0)
-    except exchange.PushRaced, exc:
+    except error.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
@@ -13,10 +13,11 @@  Create an extension to test bundle2 API
   > from mercurial import util
   > from mercurial import bundle2
   > from mercurial import scmutil
   > from mercurial import discovery
   > from mercurial import changegroup
+  > from mercurial import error
   > cmdtable = {}
   > command = cmdutil.command(cmdtable)
   > 
   > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
   > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
@@ -57,10 +58,11 @@  Create an extension to test bundle2 API
   > @command('bundle2',
   >          [('', 'param', [], 'stream level parameter'),
   >           ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
   >           ('', 'parts', False, 'include some arbitrary parts to the bundle'),
   >           ('', 'reply', False, 'produce a reply bundle'),
+  >           ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
   >           ('r', 'rev', [], 'includes those changeset in the bundle'),],
   >          '[OUTPUTFILE]')
   > def cmdbundle2(ui, repo, path=None, **opts):
   >     """write a bundle2 container on standard ouput"""
   >     bundler = bundle2.bundle20(ui)
@@ -73,10 +75,14 @@  Create an extension to test bundle2 API
   > 
   >     if opts['reply']:
   >         capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
   >         bundler.addpart(bundle2.bundlepart('b2x:replycaps', data=capsstring))
   > 
+  >     if opts['pushrace']:
+  >         dummynode = '01234567890123456789'
+  >         bundler.addpart(bundle2.bundlepart('b2x:check:heads', data=dummynode))
+  > 
   >     revs = opts['rev']
   >     if 'rev' in opts:
   >         revs = scmutil.revrange(repo, opts['rev'])
   >         if revs:
   >             # very crude version of a changegroup part creation
@@ -130,10 +136,12 @@  Create an extension to test bundle2 API
   >             unbundler = bundle2.unbundle20(ui, sys.stdin)
   >             op = bundle2.processbundle(repo, unbundler, lambda: tr)
   >             tr.close()
   >         except KeyError, exc:
   >             raise util.Abort('missing support for %s' % exc)
+  >         except error.PushRaced, exc:
+  >             raise util.Abort('push race')
   >     finally:
   >         if tr is not None:
   >             tr.release()
   >         lock.release()
   >         remains = sys.stdin.read()
@@ -599,10 +607,19 @@  Unbundle the reply to get the output:
   remote: debugreply:     'ping-pong'
   remote: received ping request (id 6)
   remote: replying to ping request (id 6)
   0 unread bytes
 
+Test push race detection
+
+  $ hg bundle2 --pushrace ../part-race.hg2
+
+  $ hg unbundle2 < ../part-race.hg2
+  0 unread bytes
+  abort: push race
+  [255]
+
 Support for changegroup
 ===================================
 
   $ hg unbundle $TESTDIR/bundles/rebase.hg
   adding changesets