[PATCH 7 of 7] clone: only use stream when we understand the revlog format

Sune Foldager cryo at cyanite.org
Tue Sep 14 04:56:36 CDT 2010


# HG changeset patch
# User Sune Foldager <cryo at cyanite.org>
# Date 1283429388 -7200
# Node ID 467dca2391c68e7b418a6fbfc18099a3b3fea5fb
# Parent  6c95703db2ceff035bbcece54aa984da493e67f3
clone: only use stream when we understand the revlog format

This patch fixes issues with stream cloning in the presense of parentdelta,
lwcopy and similar additions that change the interpretation of the revlog
format, or the format itself.

Currently, the stream capability is sent like this:
stream=<version of changelog>

But the client doesn't check it; it only checks the changelog and it doesn't
capture the interpretation-changes and flag-changes in parentdelta and lwcopy.

This patch removes the 'stream' capability whenever we use a non-basic revlog
format, to prevent old clients from receiving incorrect data. Otherwise, a new
capability called 'streamreqs' is added instead. Instead of a revlog version,
it comes with a list of revlog-format relevant requirements, which are a subset
of the repository requirements, excluding things that are not relevant for
stream.

New clients use this to determine whether or not they can stream.

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -21,7 +21,8 @@
 
 class localrepository(repo.repository):
     capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey'))
-    supported = set('revlogv1 store fncache shared parentdelta lwcopy'.split())
+    supportedformats = set(('revlogv1', 'parentdelta', 'lwcopy'))
+    supported = supportedformats | set(('store', 'fncache', 'shared'))
 
     def __init__(self, baseui, path=None, create=0):
         repo.repository.__init__(self)
@@ -58,10 +59,6 @@
                     )
                 if self.ui.configbool('format', 'parentdelta', False):
                     requirements.append("parentdelta")
-                reqfile = self.opener("requires", "w")
-                for r in requirements:
-                    reqfile.write("%s\n" % r)
-                reqfile.close()
             else:
                 raise error.RepoError(_("repository %s not found") % path)
         elif create:
@@ -93,9 +90,7 @@
         self.sopener = self.store.opener
         self.sjoin = self.store.join
         self.opener.createmode = self.store.createmode
-        self.sopener.options = {"lwcopy": "lwcopy" in requirements}
-        if 'parentdelta' in requirements:
-            self.sopener.options['parentdelta'] = 1
+        self._setrequirements(requirements, writeback=create)
 
         # These two define the set of tags for this repository.  _tags
         # maps tag name to node; _tagtypes maps tag name to 'global' or
@@ -112,6 +107,19 @@
         self._datafilters = {}
         self._transref = self._lockref = self._wlockref = None
 
+    def _setrequirements(self, requirements, writeback=True):
+        self.requirements = requirements
+        self.sopener.options = {}
+        if 'parentdelta' in requirements:
+            self.sopener.options['parentdelta'] = 1
+        if 'lwcopy' in requirements:
+            self.sopener.options['lwcopy'] = 1
+        if writeback:
+            reqfile = self.opener("requires", "w")
+            for r in requirements:
+                reqfile.write("%s\n" % r)
+            reqfile.close()
+
     def _checknested(self, path):
         """Determine if path is a legal nested repository."""
         if not path.startswith(self.root):
@@ -149,7 +157,6 @@
                 parts.pop()
         return False
 
-
     @propertycache
     def changelog(self):
         c = changelog.changelog(self.sopener)
@@ -1778,7 +1785,7 @@
             return newheads - oldheads + 1
 
 
-    def stream_in(self, remote):
+    def stream_in(self, remote, requirements):
         fp = remote.stream_out()
         l = fp.readline()
         try:
@@ -1823,6 +1830,12 @@
         self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
                        (util.bytecount(total_bytes), elapsed,
                         util.bytecount(total_bytes / elapsed)))
+
+        # new requirements = old non-format requirements + new format-related
+        # requirements from the streamed-in repository
+        requirements.update(set(self.requirements) - self.supportedformats)
+        self._setrequirements(requirements)
+
         self.invalidate()
         return len(self.heads()) + 1
 
@@ -1841,8 +1854,17 @@
         # and format flags on "stream" capability, and use
         # uncompressed only if compatible.
 
-        if stream and not heads and remote.capable('stream'):
-            return self.stream_in(remote)
+        if stream and not heads:
+            # 'stream' means remote revlog format is revlogv1 only
+            if remote.capable('stream'):
+                return self.stream_in(remote, set(('revlogv1',)))
+            # otherwise, 'streamreqs' contains the remote revlog format
+            streamreqs = remote.capable('streamreqs')
+            if streamreqs:
+                streamreqs = set(streamreqs.split(','))
+                # if we support it, stream in and adjust our requirements
+                if not streamreqs - self.supportedformats:
+                    return self.stream_in(remote, streamreqs)
         return self.pull(remote, heads)
 
     def pushkey(self, namespace, key, old, new):
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -172,7 +172,13 @@
 def capabilities(repo, proto):
     caps = 'lookup changegroupsubset branchmap pushkey'.split()
     if _allowstream(repo.ui):
-        caps.append('stream=%d' % repo.changelog.version)
+        requiredformats = repo.requirements & repo.supportedformats
+        # if our local revlogs are just revlogv1, add 'stream' cap
+        if not requiredformats - set(('revlogv1',)):
+            caps.append('stream')
+        # otherwise, add 'streamcap' detailing our local revlog format
+        else:
+            caps.append('streamreqs=%s' % ','.join(requiredformats))
     caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
     return ' '.join(caps)
 


More information about the Mercurial-devel mailing list