scmutil.filecache: support watching over multiple files

This commit is contained in:
Siddharth Agarwal 2013-11-16 13:29:39 -08:00
parent fceaf0623e
commit 25418f3155
3 changed files with 100 additions and 26 deletions

View File

@ -786,23 +786,26 @@ class filecacheentry(object):
entry.refresh()
class filecache(object):
'''A property like decorator that tracks a file under .hg/ for updates.
'''A property like decorator that tracks files under .hg/ for updates.
Records stat info when called in _filecache.
On subsequent calls, compares old stat info with new info, and recreates
the object when needed, updating the new stat info in _filecache.
On subsequent calls, compares old stat info with new info, and recreates the
object when any of the files changes, updating the new stat info in
_filecache.
Mercurial either atomic renames or appends for files under .hg,
so to ensure the cache is reliable we need the filesystem to be able
to tell us if a file has been replaced. If it can't, we fallback to
recreating the object on every call (essentially the same behaviour as
propertycache).'''
def __init__(self, path):
self.path = path
propertycache).
'''
def __init__(self, *paths):
self.paths = paths
def join(self, obj, fname):
"""Used to compute the runtime path of the cached file.
"""Used to compute the runtime path of a cached file.
Users should subclass filecache and provide their own version of this
function to call the appropriate join function on 'obj' (an instance
@ -827,11 +830,11 @@ class filecache(object):
if entry.changed():
entry.obj = self.func(obj)
else:
path = self.join(obj, self.path)
paths = [self.join(obj, path) for path in self.paths]
# We stat -before- creating the object so our cache doesn't lie if
# a writer modified between the time we read and stat
entry = filecachesubentry(path, True)
entry = filecacheentry(paths, True)
entry.obj = self.func(obj)
obj._filecache[self.name] = entry
@ -843,7 +846,8 @@ class filecache(object):
if self.name not in obj._filecache:
# we add an entry for the missing value because X in __dict__
# implies X in _filecache
ce = filecachesubentry(self.join(obj, self.path), False)
paths = [self.join(obj, path) for path in self.paths]
ce = filecacheentry(paths, False)
obj._filecache[self.name] = ce
else:
ce = obj._filecache[self.name]

View File

@ -18,7 +18,7 @@ class fakerepo(object):
def sjoin(self, p):
return p
@filecache('x')
@filecache('x', 'y')
def cached(self):
print 'creating'
return 'string from function'
@ -31,12 +31,12 @@ class fakerepo(object):
pass
def basic(repo):
print "* file doesn't exist"
print "* neither file exists"
# calls function
repo.cached
repo.invalidate()
print "* file still doesn't exist"
print "* neither file still exists"
# uses cache
repo.cached
@ -57,7 +57,7 @@ def basic(repo):
repo.cached
repo.invalidate()
print "* nothing changed with file x"
print "* nothing changed with either file"
# stats file again, reuses object
repo.cached
@ -72,6 +72,41 @@ def basic(repo):
print "* file x changed inode"
repo.cached
# create empty file y
f = open('y', 'w')
f.close()
repo.invalidate()
print "* empty file y created"
# should recreate the object
repo.cached
f = open('y', 'w')
f.write('A')
f.close()
repo.invalidate()
print "* file y changed size"
# should recreate the object
repo.cached
f = scmutil.opener('.')('y', 'w', atomictemp=True)
f.write('B')
f.close()
repo.invalidate()
print "* file y changed inode"
repo.cached
f = scmutil.opener('.')('x', 'w', atomictemp=True)
f.write('c')
f.close()
f = scmutil.opener('.')('y', 'w', atomictemp=True)
f.write('C')
f.close()
repo.invalidate()
print "* both files changed inode"
repo.cached
def fakeuncacheable():
def wrapcacheable(orig, *args, **kwargs):
return False
@ -83,10 +118,11 @@ def fakeuncacheable():
origcacheable = extensions.wrapfunction(util.cachestat, 'cacheable',
wrapcacheable)
try:
os.remove('x')
except OSError:
pass
for fn in ['x', 'y']:
try:
os.remove(fn)
except OSError:
pass
basic(fakerepo())
@ -110,9 +146,10 @@ def test_filecache_synced():
def setbeforeget(repo):
os.remove('x')
os.remove('y')
repo.cached = 'string set externally'
repo.invalidate()
print "* file x doesn't exist"
print "* neither file exists"
print repo.cached
repo.invalidate()
f = open('x', 'w')
@ -121,6 +158,18 @@ def setbeforeget(repo):
print "* file x created"
print repo.cached
repo.cached = 'string 2 set externally'
repo.invalidate()
print "* string set externally again"
print repo.cached
repo.invalidate()
f = open('y', 'w')
f.write('b')
f.close()
print "* file y created"
print repo.cached
print 'basic:'
print
basic(fakerepo())

View File

@ -1,30 +1,46 @@
basic:
* file doesn't exist
* neither file exists
creating
* file still doesn't exist
* neither file still exists
* empty file x created
creating
* file x changed size
creating
* nothing changed with file x
* nothing changed with either file
* file x changed inode
creating
* empty file y created
creating
* file y changed size
creating
* file y changed inode
creating
* both files changed inode
creating
fakeuncacheable:
* file doesn't exist
* neither file exists
creating
* file still doesn't exist
* neither file still exists
creating
* empty file x created
creating
* file x changed size
creating
* nothing changed with file x
* nothing changed with either file
creating
* file x changed inode
creating
* empty file y created
creating
* file y changed size
creating
* file y changed inode
creating
* both files changed inode
creating
repository tip rolled back to revision -1 (undo commit)
working directory now based on revision -1
repository tip rolled back to revision -1 (undo commit)
@ -32,8 +48,13 @@ working directory now based on revision -1
setbeforeget:
* file x doesn't exist
* neither file exists
string set externally
* file x created
creating
string from function
* string set externally again
string 2 set externally
* file y created
creating
string from function