diff mercurial/changegroup.py @ 47759:d7515d29761d stable 5.9rc0

branching: merge default into stable This mark the start of the 5.9 freeze.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Wed, 21 Jul 2021 22:52:09 +0200
parents 3f00665bbea0
children 8843c9a8771b
line wrap: on
line diff
--- a/mercurial/changegroup.py	Fri Jul 09 00:25:14 2021 +0530
+++ b/mercurial/changegroup.py	Wed Jul 21 22:52:09 2021 +0200
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import collections
 import os
 import struct
 import weakref
@@ -15,7 +14,6 @@
 from .i18n import _
 from .node import (
     hex,
-    nullid,
     nullrev,
     short,
 )
@@ -34,10 +32,13 @@
 
 from .interfaces import repository
 from .revlogutils import sidedata as sidedatamod
+from .revlogutils import constants as revlog_constants
+from .utils import storageutil
 
 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
+_CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
 
 LFS_REQUIREMENT = b'lfs'
 
@@ -194,19 +195,20 @@
         else:
             deltabase = prevnode
         flags = 0
-        return node, p1, p2, deltabase, cs, flags
+        protocol_flags = 0
+        return node, p1, p2, deltabase, cs, flags, protocol_flags
 
     def deltachunk(self, prevnode):
+        # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata, proto_flags)
         l = self._chunklength()
         if not l:
             return {}
         headerdata = readexactly(self._stream, self.deltaheadersize)
         header = self.deltaheader.unpack(headerdata)
         delta = readexactly(self._stream, l - self.deltaheadersize)
-        node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
-        # cg4 forward-compat
-        sidedata = {}
-        return (node, p1, p2, cs, deltabase, delta, flags, sidedata)
+        header = self._deltaheader(header, prevnode)
+        node, p1, p2, deltabase, cs, flags, protocol_flags = header
+        return node, p1, p2, cs, deltabase, delta, flags, {}, protocol_flags
 
     def getchunks(self):
         """returns all the chunks contains in the bundle
@@ -293,8 +295,16 @@
 
         # Only useful if we're adding sidedata categories. If both peers have
         # the same categories, then we simply don't do anything.
-        if self.version == b'04' and srctype == b'pull':
-            sidedata_helpers = get_sidedata_helpers(
+        adding_sidedata = (
+            (
+                requirements.REVLOGV2_REQUIREMENT in repo.requirements
+                or requirements.CHANGELOGV2_REQUIREMENT in repo.requirements
+            )
+            and self.version == b'04'
+            and srctype == b'pull'
+        )
+        if adding_sidedata:
+            sidedata_helpers = sidedatamod.get_sidedata_helpers(
                 repo,
                 sidedata_categories or set(),
                 pull=True,
@@ -386,15 +396,16 @@
                 _(b'manifests'), unit=_(b'chunks'), total=changesets
             )
             on_manifest_rev = None
-            if sidedata_helpers and b'manifest' in sidedata_helpers[1]:
+            if sidedata_helpers:
+                if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
 
-                def on_manifest_rev(manifest, rev):
-                    range = touched_manifests.get(manifest)
-                    if not range:
-                        touched_manifests[manifest] = (rev, rev)
-                    else:
-                        assert rev == range[1] + 1
-                        touched_manifests[manifest] = (range[0], rev)
+                    def on_manifest_rev(manifest, rev):
+                        range = touched_manifests.get(manifest)
+                        if not range:
+                            touched_manifests[manifest] = (rev, rev)
+                        else:
+                            assert rev == range[1] + 1
+                            touched_manifests[manifest] = (range[0], rev)
 
             self._unpackmanifests(
                 repo,
@@ -417,15 +428,16 @@
                         needfiles.setdefault(f, set()).add(n)
 
             on_filelog_rev = None
-            if sidedata_helpers and b'filelog' in sidedata_helpers[1]:
+            if sidedata_helpers:
+                if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
 
-                def on_filelog_rev(filelog, rev):
-                    range = touched_filelogs.get(filelog)
-                    if not range:
-                        touched_filelogs[filelog] = (rev, rev)
-                    else:
-                        assert rev == range[1] + 1
-                        touched_filelogs[filelog] = (range[0], rev)
+                    def on_filelog_rev(filelog, rev):
+                        range = touched_filelogs.get(filelog)
+                        if not range:
+                            touched_filelogs[filelog] = (rev, rev)
+                        else:
+                            assert rev == range[1] + 1
+                            touched_filelogs[filelog] = (range[0], rev)
 
             # process the files
             repo.ui.status(_(b"adding file changes\n"))
@@ -440,12 +452,14 @@
             )
 
             if sidedata_helpers:
-                if b'changelog' in sidedata_helpers[1]:
-                    cl.rewrite_sidedata(sidedata_helpers, clstart, clend - 1)
+                if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
+                    cl.rewrite_sidedata(
+                        trp, sidedata_helpers, clstart, clend - 1
+                    )
                 for mf, (startrev, endrev) in touched_manifests.items():
-                    mf.rewrite_sidedata(sidedata_helpers, startrev, endrev)
+                    mf.rewrite_sidedata(trp, sidedata_helpers, startrev, endrev)
                 for fl, (startrev, endrev) in touched_filelogs.items():
-                    fl.rewrite_sidedata(sidedata_helpers, startrev, endrev)
+                    fl.rewrite_sidedata(trp, sidedata_helpers, startrev, endrev)
 
             # making sure the value exists
             tr.changes.setdefault(b'changegroup-count-changesets', 0)
@@ -570,8 +584,8 @@
         """
         chain = None
         for chunkdata in iter(lambda: self.deltachunk(chain), {}):
-            # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata)
-            yield chunkdata
+            # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata, proto_flags)
+            yield chunkdata[:8]
             chain = chunkdata[0]
 
 
@@ -590,7 +604,8 @@
     def _deltaheader(self, headertuple, prevnode):
         node, p1, p2, deltabase, cs = headertuple
         flags = 0
-        return node, p1, p2, deltabase, cs, flags
+        protocol_flags = 0
+        return node, p1, p2, deltabase, cs, flags, protocol_flags
 
 
 class cg3unpacker(cg2unpacker):
@@ -608,7 +623,8 @@
 
     def _deltaheader(self, headertuple, prevnode):
         node, p1, p2, deltabase, cs, flags = headertuple
-        return node, p1, p2, deltabase, cs, flags
+        protocol_flags = 0
+        return node, p1, p2, deltabase, cs, flags, protocol_flags
 
     def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
         super(cg3unpacker, self)._unpackmanifests(
@@ -631,21 +647,48 @@
     cg4 streams add support for exchanging sidedata.
     """
 
+    deltaheader = _CHANGEGROUPV4_DELTA_HEADER
+    deltaheadersize = deltaheader.size
     version = b'04'
 
+    def _deltaheader(self, headertuple, prevnode):
+        protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
+        return node, p1, p2, deltabase, cs, flags, protocol_flags
+
     def deltachunk(self, prevnode):
         res = super(cg4unpacker, self).deltachunk(prevnode)
         if not res:
             return res
 
-        (node, p1, p2, cs, deltabase, delta, flags, _sidedata) = res
+        (
+            node,
+            p1,
+            p2,
+            cs,
+            deltabase,
+            delta,
+            flags,
+            sidedata,
+            protocol_flags,
+        ) = res
+        assert not sidedata
 
-        sidedata_raw = getchunk(self._stream)
         sidedata = {}
-        if len(sidedata_raw) > 0:
+        if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
+            sidedata_raw = getchunk(self._stream)
             sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
 
-        return node, p1, p2, cs, deltabase, delta, flags, sidedata
+        return (
+            node,
+            p1,
+            p2,
+            cs,
+            deltabase,
+            delta,
+            flags,
+            sidedata,
+            protocol_flags,
+        )
 
 
 class headerlessfixup(object):
@@ -673,7 +716,7 @@
 
     if delta.delta is not None:
         prefix, data = b'', delta.delta
-    elif delta.basenode == nullid:
+    elif delta.basenode == repo.nullid:
         data = delta.revision
         prefix = mdiff.trivialdiffheader(len(data))
     else:
@@ -688,10 +731,10 @@
         yield prefix
     yield data
 
-    sidedata = delta.sidedata
-    if sidedata is not None:
+    if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
         # Need a separate chunk for sidedata to be able to differentiate
         # "raw delta" length and sidedata length
+        sidedata = delta.sidedata
         yield chunkheader(len(sidedata))
         yield sidedata
 
@@ -787,9 +830,15 @@
                         return i
                 # We failed to resolve a parent for this node, so
                 # we crash the changegroup construction.
+                if util.safehasattr(store, 'target'):
+                    target = store.display_id
+                else:
+                    # some revlog not actually a revlog
+                    target = store._revlog.display_id
+
                 raise error.Abort(
                     b"unable to resolve parent while packing '%s' %r"
-                    b' for changeset %r' % (store.indexfile, rev, clrev)
+                    b' for changeset %r' % (target, rev, clrev)
                 )
 
         return nullrev
@@ -828,7 +877,8 @@
     If topic is not None, progress detail will be generated using this
     topic name (e.g. changesets, manifests, etc).
 
-    See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
+    See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
+    `sidedata_helpers`.
     """
     if not nodes:
         return
@@ -1056,7 +1106,9 @@
                 # TODO a better approach would be for the strip bundle to
                 # correctly advertise its sidedata categories directly.
                 remote_sidedata = repo._wanted_sidedata
-            sidedata_helpers = get_sidedata_helpers(repo, remote_sidedata)
+            sidedata_helpers = sidedatamod.get_sidedata_helpers(
+                repo, remote_sidedata
+            )
 
         clstate, deltas = self._generatechangelog(
             cl,
@@ -1194,7 +1246,8 @@
         if generate is False, the state will be fully populated and no chunk
         stream will be yielded
 
-        See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
+        See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
+        `sidedata_helpers`.
         """
         clrevorder = {}
         manifests = {}
@@ -1299,7 +1352,8 @@
         `source` is unused here, but is used by extensions like remotefilelog to
         change what is sent based in pulls vs pushes, etc.
 
-        See `storageutil.emitrevisions` for the doc on `sidedata_helpers`.
+        See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
+        `sidedata_helpers`.
         """
         repo = self._repo
         mfl = repo.manifestlog
@@ -1633,11 +1687,18 @@
     fullnodes=None,
     remote_sidedata=None,
 ):
-    # Same header func as cg3. Sidedata is in a separate chunk from the delta to
-    # differenciate "raw delta" and sidedata.
-    builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
-        d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
-    )
+    # Sidedata is in a separate chunk from the delta to differentiate
+    # "raw delta" and sidedata.
+    def builddeltaheader(d):
+        return _CHANGEGROUPV4_DELTA_HEADER.pack(
+            d.protocol_flags,
+            d.node,
+            d.p1node,
+            d.p2node,
+            d.basenode,
+            d.linknode,
+            d.flags,
+        )
 
     return cgpacker(
         repo,
@@ -1682,11 +1743,15 @@
         #
         # (or even to push subset of history)
         needv03 = True
-    has_revlogv2 = requirements.REVLOGV2_REQUIREMENT in repo.requirements
-    if not has_revlogv2:
-        versions.discard(b'04')
     if not needv03:
         versions.discard(b'03')
+    want_v4 = (
+        repo.ui.configbool(b'experimental', b'changegroup4')
+        or requirements.REVLOGV2_REQUIREMENT in repo.requirements
+        or requirements.CHANGELOGV2_REQUIREMENT in repo.requirements
+    )
+    if not want_v4:
+        versions.discard(b'04')
     return versions
 
 
@@ -1913,25 +1978,3 @@
                 )
 
     return revisions, files
-
-
-def get_sidedata_helpers(repo, remote_sd_categories, pull=False):
-    # Computers for computing sidedata on-the-fly
-    sd_computers = collections.defaultdict(list)
-    # Computers for categories to remove from sidedata
-    sd_removers = collections.defaultdict(list)
-
-    to_generate = remote_sd_categories - repo._wanted_sidedata
-    to_remove = repo._wanted_sidedata - remote_sd_categories
-    if pull:
-        to_generate, to_remove = to_remove, to_generate
-
-    for revlog_kind, computers in repo._sidedata_computers.items():
-        for category, computer in computers.items():
-            if category in to_generate:
-                sd_computers[revlog_kind].append(computer)
-            if category in to_remove:
-                sd_removers[revlog_kind].append(computer)
-
-    sidedata_helpers = (repo, sd_computers, sd_removers)
-    return sidedata_helpers