contrib/dirstatenonnormalcheck.py
author Boris Feld <boris.feld@octobus.net>
Tue, 11 Jul 2017 12:38:17 +0200
changeset 33436 9bb4decd43b0
parent 29100 3fd94f603190
child 34674 60927b19ed65
permissions -rw-r--r--
repovfs: add a ward to check if locks are properly taken When the appropriate developer warnings are enabled, We wrap 'repo.vfs.audit' to check for locks when accessing file in '.hg' for writing. Another changeset will add a 'ward' for the store vfs (svfs). This check system has caught a handful of locking issues that have been fixed in previous series (mostly in 4.0). I expect another batch to be caught in third party extensions. We introduce two real exceptions from extensions 'blackbox.log' (because a lot of read-only operations add entry to it), and 'last-email.txt' (because 'hg email' is currently a read only operation and there is value to keep it this way). In addition we are currently allowing bisect to operate outside of the lock because the current code is a bit hard to get properly locked for now. Multiple clean up have been made but there is still a couple of them to do and the freeze is coming.

# dirstatenonnormalcheck.py - extension to check the consistency of the
# dirstate's non-normal map
#
# For most operations on dirstate, this extensions checks that the nonnormalset
# contains the right entries.
# It compares the nonnormal file to a nonnormalset built from the map of all
# the files in the dirstate to check that they contain the same files.

from __future__ import absolute_import

from mercurial import (
    dirstate,
    extensions,
)

def nonnormalentries(dmap):
    """Compute nonnormal entries from dirstate's dmap"""
    res = set()
    for f, e in dmap.iteritems():
        if e[0] != 'n' or e[3] == -1:
            res.add(f)
    return res

def checkconsistency(ui, orig, dmap, _nonnormalset, label):
    """Compute nonnormalset from dmap, check that it matches _nonnormalset"""
    nonnormalcomputedmap = nonnormalentries(dmap)
    if _nonnormalset != nonnormalcomputedmap:
        ui.develwarn("%s call to %s\n" % (label, orig), config='dirstate')
        ui.develwarn("inconsistency in nonnormalset\n", config='dirstate')
        ui.develwarn("[nonnormalset] %s\n" % _nonnormalset, config='dirstate')
        ui.develwarn("[map] %s\n" % nonnormalcomputedmap, config='dirstate')

def _checkdirstate(orig, self, arg):
    """Check nonnormal set consistency before and after the call to orig"""
    checkconsistency(self._ui, orig, self._map, self._nonnormalset, "before")
    r = orig(self, arg)
    checkconsistency(self._ui, orig, self._map, self._nonnormalset, "after")
    return r

def extsetup(ui):
    """Wrap functions modifying dirstate to check nonnormalset consistency"""
    dirstatecl = dirstate.dirstate
    devel = ui.configbool('devel', 'all-warnings')
    paranoid = ui.configbool('experimental', 'nonnormalparanoidcheck')
    if devel:
        extensions.wrapfunction(dirstatecl, '_writedirstate', _checkdirstate)
        if paranoid:
            # We don't do all these checks when paranoid is disable as it would
            # make the extension run very slowly on large repos
            extensions.wrapfunction(dirstatecl, 'normallookup', _checkdirstate)
            extensions.wrapfunction(dirstatecl, 'otherparent', _checkdirstate)
            extensions.wrapfunction(dirstatecl, 'normal', _checkdirstate)
            extensions.wrapfunction(dirstatecl, 'write', _checkdirstate)
            extensions.wrapfunction(dirstatecl, 'add', _checkdirstate)
            extensions.wrapfunction(dirstatecl, 'remove', _checkdirstate)
            extensions.wrapfunction(dirstatecl, 'merge', _checkdirstate)
            extensions.wrapfunction(dirstatecl, 'drop', _checkdirstate)