diff hgext/narrow/narrowrevlog.py @ 36079:a2a6e724d61a

narrow: import experimental extension from narrowhg revision cb51d673e9c5 Adjustments: * renamed src to hgext/narrow * marked extension experimental * added correct copyright header where it was missing * updated hgrc extension enable line in library.sh * renamed library.sh to narrow-library.sh * dropped all files from repo root as they're not interesting * dropped test-pyflakes.t, test-check-code.t and test-check-py3-compat.t * renamed remaining tests to all be test-narrow-* when they didn't already * fixed test-narrow-expanddirstate.t to refer to narrow and not narrowhg * fixed tests that wanted `update -C .` instead of `merge --abort` * corrected a two-space indent in narrowspec.py * added a missing _() in narrowcommands.py * fixed imports to pass the import checker * narrow only adds its --include and --exclude to clone if sparse isn't enabled to avoid breaking test-duplicateoptions.py. This is a kludge, and we'll need to come up with a better solution in the future. These were more or less the minimum to import something that would pass tests and not create a bunch of files we'll never use. Changes I intend to make as followups: * rework the test-narrow-*-tree.t tests to use the new testcases functionality in run-tests.py * remove lots of monkeypatches of core things Differential Revision: https://phab.mercurial-scm.org/D1974
author Augie Fackler <augie@google.com>
date Mon, 29 Jan 2018 16:19:33 -0500
parents
children adc8e1fe2f46
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/narrow/narrowrevlog.py	Mon Jan 29 16:19:33 2018 -0500
@@ -0,0 +1,163 @@
+# narrowrevlog.py - revlog storing irrelevant nodes as "ellipsis" nodes
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+   manifest,
+   revlog,
+   util,
+)
+
+ELLIPSIS_NODE_FLAG = 1 << 14
+revlog.REVIDX_KNOWN_FLAGS |= ELLIPSIS_NODE_FLAG
+if (util.safehasattr(revlog, 'REVIDX_FLAGS_ORDER') and
+    ELLIPSIS_NODE_FLAG not in revlog.REVIDX_FLAGS_ORDER):
+        revlog.REVIDX_FLAGS_ORDER.append(ELLIPSIS_NODE_FLAG)
+
+def readtransform(self, text):
+    return text, False
+
+def writetransform(self, text):
+    return text, False
+
+def rawtransform(self, text):
+    return False
+
+if util.safehasattr(revlog, 'addflagprocessor'):
+    revlog.addflagprocessor(ELLIPSIS_NODE_FLAG,
+                            (readtransform, writetransform, rawtransform))
+
+def setup():
+    # We just wanted to add the flag processor, which is done at module
+    # load time.
+    pass
+
+class excludeddir(manifest.treemanifest):
+    def __init__(self, dir, node):
+        super(excludeddir, self).__init__(dir)
+        self._node = node
+        # Add an empty file, which will be included by iterators and such,
+        # appearing as the directory itself (i.e. something like "dir/")
+        self._files[''] = node
+        self._flags[''] = 't'
+
+    # Manifests outside the narrowspec should never be modified, so avoid
+    # copying. This makes a noticeable difference when there are very many
+    # directories outside the narrowspec. Also, it makes sense for the copy to
+    # be of the same type as the original, which would not happen with the
+    # super type's copy().
+    def copy(self):
+        return self
+
+class excludeddirmanifestctx(manifest.treemanifestctx):
+    def __init__(self, dir, node):
+        self._dir = dir
+        self._node = node
+
+    def read(self):
+        return excludeddir(self._dir, self._node)
+
+    def write(self, *args):
+        raise AssertionError('Attempt to write manifest from excluded dir %s' %
+                             self._dir)
+
+class excludedmanifestrevlog(manifest.manifestrevlog):
+    def __init__(self, dir):
+        self._dir = dir
+
+    def __len__(self):
+        raise AssertionError('Attempt to get length of excluded dir %s' %
+                             self._dir)
+
+    def rev(self, node):
+        raise AssertionError('Attempt to get rev from excluded dir %s' %
+                             self._dir)
+
+    def linkrev(self, node):
+        raise AssertionError('Attempt to get linkrev from excluded dir %s' %
+                             self._dir)
+
+    def node(self, rev):
+        raise AssertionError('Attempt to get node from excluded dir %s' %
+                             self._dir)
+
+    def add(self, *args, **kwargs):
+        # We should never write entries in dirlogs outside the narrow clone.
+        # However, the method still gets called from writesubtree() in
+        # _addtree(), so we need to handle it. We should possibly make that
+        # avoid calling add() with a clean manifest (_dirty is always False
+        # in excludeddir instances).
+        pass
+
+def makenarrowmanifestrevlog(mfrevlog, repo):
+    if util.safehasattr(mfrevlog, '_narrowed'):
+        return
+
+    class narrowmanifestrevlog(mfrevlog.__class__):
+        # This function is called via debug{revlog,index,data}, but also during
+        # at least some push operations. This will be used to wrap/exclude the
+        # child directories when using treemanifests.
+        def dirlog(self, dir):
+            if dir and not dir.endswith('/'):
+                dir = dir + '/'
+            if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
+                return excludedmanifestrevlog(dir)
+            result = super(narrowmanifestrevlog, self).dirlog(dir)
+            makenarrowmanifestrevlog(result, repo)
+            return result
+
+    mfrevlog.__class__ = narrowmanifestrevlog
+    mfrevlog._narrowed = True
+
+def makenarrowmanifestlog(mfl, repo):
+    class narrowmanifestlog(mfl.__class__):
+        def get(self, dir, node, verify=True):
+            if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
+                return excludeddirmanifestctx(dir, node)
+            return super(narrowmanifestlog, self).get(dir, node, verify=verify)
+    mfl.__class__ = narrowmanifestlog
+
+def makenarrowfilelog(fl, narrowmatch):
+    class narrowfilelog(fl.__class__):
+        def renamed(self, node):
+            m = super(narrowfilelog, self).renamed(node)
+            if m and not narrowmatch(m[0]):
+                return None
+            return m
+
+        def size(self, rev):
+            # We take advantage of the fact that remotefilelog
+            # lacks a node() method to just skip the
+            # rename-checking logic when on remotefilelog. This
+            # might be incorrect on other non-revlog-based storage
+            # engines, but for now this seems to be fine.
+            if util.safehasattr(self, 'node'):
+                node = self.node(rev)
+                # Because renamed() is overridden above to
+                # sometimes return None even if there is metadata
+                # in the revlog, size can be incorrect for
+                # copies/renames, so we need to make sure we call
+                # the super class's implementation of renamed()
+                # for the purpose of size calculation.
+                if super(narrowfilelog, self).renamed(node):
+                    return len(self.read(node))
+            return super(narrowfilelog, self).size(rev)
+
+        def cmp(self, node, text):
+            different = super(narrowfilelog, self).cmp(node, text)
+            if different:
+                # Similar to size() above, if the file was copied from
+                # a file outside the narrowspec, the super class's
+                # would have returned True because we tricked it into
+                # thinking that the file was not renamed.
+                if super(narrowfilelog, self).renamed(node):
+                    t2 = self.read(node)
+                    return t2 != text
+            return different
+
+    fl.__class__ = narrowfilelog