# HG changeset patch # User Siddharth Agarwal # Date 2013-11-16 21:29:39 # Node ID b3684fd2ff1ab48bf90f183379b6054365736b4d # Parent d38de18d187aed6d5de246c86eed3d62d0e89de3 scmutil.filecache: support watching over multiple files diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py --- a/mercurial/scmutil.py +++ b/mercurial/scmutil.py @@ -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] diff --git a/tests/test-filecache.py b/tests/test-filecache.py --- a/tests/test-filecache.py +++ b/tests/test-filecache.py @@ -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()) diff --git a/tests/test-filecache.py.out b/tests/test-filecache.py.out --- a/tests/test-filecache.py.out +++ b/tests/test-filecache.py.out @@ -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 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