hgext/narrow/narrowrevlog.py
author Yuya Nishihara <yuya@tcha.org>
Sun, 04 Mar 2018 16:23:10 -0500
changeset 36734 b77ff4fbe9ad
parent 36097 66b4ffe90676
child 37270 6ff8bd691fb8
permissions -rw-r--r--
py3: work around comparison between int and None in tagmerge

# 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 (
   error,
   manifest,
   revlog,
   util,
)

def readtransform(self, text):
    return text, False

def writetransform(self, text):
    return text, False

def rawtransform(self, text):
    return False

revlog.addflagprocessor(revlog.REVIDX_ELLIPSIS,
                        (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):
    """Stand-in for a directory that is excluded from the repository.

    With narrowing active on a repository that uses treemanifests,
    some of the directory revlogs will be excluded from the resulting
    clone. This is a huge storage win for clients, but means we need
    some sort of pseudo-manifest to surface to internals so we can
    detect a merge conflict outside the narrowspec. That's what this
    class is: it stands in for a directory whose node is known, but
    whose contents are unknown.
    """
    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):
    """context wrapper for excludeddir - see that docstring for rationale"""
    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 error.ProgrammingError(
            'attempt to write manifest from excluded dir %s' % self._dir)

class excludedmanifestrevlog(manifest.manifestrevlog):
    """Stand-in for excluded treemanifest revlogs.

    When narrowing is active on a treemanifest repository, we'll have
    references to directories we can't see due to the revlog being
    skipped. This class exists to conform to the manifestrevlog
    interface for those directories and proactively prevent writes to
    outside the narrowspec.
    """

    def __init__(self, dir):
        self._dir = dir

    def __len__(self):
        raise error.ProgrammingError(
            'attempt to get length of excluded dir %s' % self._dir)

    def rev(self, node):
        raise error.ProgrammingError(
            'attempt to get rev from excluded dir %s' % self._dir)

    def linkrev(self, node):
        raise error.ProgrammingError(
            'attempt to get linkrev from excluded dir %s' % self._dir)

    def node(self, rev):
        raise error.ProgrammingError(
            '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, d):
            if d and not d.endswith('/'):
                d = d + '/'
            if not repo.narrowmatch().visitdir(d[:-1] or '.'):
                return excludedmanifestrevlog(d)
            result = super(narrowmanifestrevlog, self).dirlog(d)
            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):
            # Renames that come from outside the narrowspec are
            # problematic at least for git-diffs, because we lack the
            # base text for the rename. This logic was introduced in
            # 3cd72b1 of narrowhg (authored by martinvonz, reviewed by
            # adgar), but that revision doesn't have any additional
            # commentary on what problems we can encounter.
            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.
            #
            # TODO: when remotefilelog is in core, improve this to
            # explicitly look for remotefilelog instead of cheating
            # with a hasattr check.
            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