hgext3rd/evolve/firstmergecache.py
author Pierre-Yves David <pierre-yves.david@octobus.net>
Thu, 13 Sep 2018 19:09:48 +0200
branchstable
changeset 4102 3895f6fcd35c
parent 4101 88000f1d2406
child 4109 d562316c548f
permissions -rw-r--r--
firstmergecache: update the variable tracking on-disk state after write Since we updated the on disk content, we should update that value. In practice the object will likely be discarded after the write, but there is nothing wrong in being more correct.

# Code dedicated to the cache of 'max(merge()) and ::X'
#
# These stable ranges are use for obsolescence markers discovery
#
# Copyright 2017 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
#
# 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

import array

from mercurial import (
    localrepo,
    node as nodemod,
)

from . import (
    compat,
    error,
    exthelper,
    genericcaches,
    utility,
)

filterparents = utility.filterparents

eh = exthelper.exthelper()

@eh.reposetup
def setupcache(ui, repo):

    class firstmergecacherepo(repo.__class__):

        @localrepo.unfilteredpropertycache
        def firstmergecache(self):
            cache = firstmergecache()
            cache.update(self)
            return cache

        @localrepo.unfilteredmethod
        def destroyed(self):
            if 'firstmergecach' in vars(self):
                self.firstmergecache.clear()
            super(firstmergecacherepo, self).destroyed()

        @localrepo.unfilteredmethod
        def updatecaches(self, tr=None, **kwargs):
            if utility.shouldwarmcache(self, tr):
                self.firstmergecache.update(self)
                self.firstmergecache.save(self)
            super(firstmergecacherepo, self).updatecaches(tr, **kwargs)

    repo.__class__ = firstmergecacherepo

class firstmergecache(genericcaches.changelogsourcebase):

    _filepath = 'evoext-firstmerge-00'
    _cachename = 'evo-ext-firstmerge'

    def __init__(self):
        super(firstmergecache, self).__init__()
        self._data = array.array('l')

    def get(self, rev):
        if len(self._data) <= rev:
            raise error.ProgrammingError('firstmergecache must be warmed before use')
        return self._data[rev]

    def _updatefrom(self, repo, data):
        """compute the rev of one revision, assert previous revision has an hot cache
        """
        cl = repo.unfiltered().changelog
        total = len(data)

        def progress(pos, rev):
            repo.ui.progress('updating firstmerge cache',
                             pos, 'rev %s' % rev, unit='revision', total=total)
        progress(0, '')
        for idx, rev in enumerate(data, 1):
            assert rev == len(self._data), (rev, len(self._data))
            self._data.append(self._firstmerge(cl, rev))
            if not (idx % 10000): # progress as a too high performance impact
                progress(idx, rev)
        progress(None, '')

    def _firstmerge(self, changelog, rev):
        cl = changelog
        ps = filterparents(cl.parentrevs(rev))
        if not ps:
            return nodemod.nullrev
        elif len(ps) == 1:
            # linear commit case
            return self.get(ps[0])
        else:
            return rev

    # cache internal logic

    def clear(self, reset=False):
        """invalidate the cache content

        if 'reset' is passed, we detected a strip and the cache will have to be
        recomputed.

        Subclasses MUST overide this method to actually affect the cache data.
        """
        super(firstmergecache, self).clear()
        self._data = array.array('l')

    # crude version of a cache, to show the kind of information we have to store

    def load(self, repo):
        """load data from disk"""
        assert repo.filtername is None

        cachevfs = compat.getcachevfs(repo)
        data = cachevfs.tryread(self._filepath)
        self._data = array.array('l')
        if not data:
            self._cachekey = self.emptykey
        else:
            headerdata = data[:self._cachekeysize]
            self._cachekey = self._deserializecachekey(headerdata)
            self._data.fromstring(data[self._cachekeysize:])
        self._ondiskkey = self._cachekey

    def save(self, repo):
        """save the data to disk

        Format is pretty simple, we serialise the cache key and then drop the
        bytearray.
        """
        if self._cachekey is None or self._cachekey == self._ondiskkey:
            return

        try:
            cachevfs = compat.getcachevfs(repo)
            cachefile = cachevfs(self._filepath, 'w', atomictemp=True)
            headerdata = self._serializecachekey()
            cachefile.write(headerdata)
            cachefile.write(self._data.tostring())
            cachefile.close()
            self._ondiskkey = self._cachekey
        except (IOError, OSError) as exc:
            repo.ui.debug('firstmergecache: could not write update %s\n' % exc)