scmutil.filecache: support watching over multiple files
authorSiddharth Agarwal <sid0@fb.com>
Sat, 16 Nov 2013 13:29:39 -0800
changeset 20045 b3684fd2ff1a
parent 20044 d38de18d187a
child 20046 6a03695fa72a
scmutil.filecache: support watching over multiple files
mercurial/scmutil.py
tests/test-filecache.py
tests/test-filecache.py.out
--- a/mercurial/scmutil.py	Sat Nov 16 13:24:26 2013 -0800
+++ b/mercurial/scmutil.py	Sat Nov 16 13:29:39 2013 -0800
@@ -786,23 +786,26 @@
             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 @@
             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 @@
         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]
--- a/tests/test-filecache.py	Sat Nov 16 13:24:26 2013 -0800
+++ b/tests/test-filecache.py	Sat Nov 16 13:29:39 2013 -0800
@@ -18,7 +18,7 @@
     def sjoin(self, p):
         return p
 
-    @filecache('x')
+    @filecache('x', 'y')
     def cached(self):
         print 'creating'
         return 'string from function'
@@ -31,12 +31,12 @@
                 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 @@
     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 @@
     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 @@
     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 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 @@
     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())
--- a/tests/test-filecache.py.out	Sat Nov 16 13:24:26 2013 -0800
+++ b/tests/test-filecache.py.out	Sat Nov 16 13:29:39 2013 -0800
@@ -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 @@
 
 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