view hgext/narrow/narrowrevlog.py @ 36082:adc8e1fe2f46

narrow: this code should assume REVIDX_FLAGS_ORDER exists Differential Revision: https://phab.mercurial-scm.org/D1977
author Augie Fackler <augie@google.com>
date Mon, 29 Jan 2018 18:13:56 -0500
parents a2a6e724d61a
children 9772ef9f6c04
line wrap: on
line source

# 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 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