Patchwork [6,of,6] scmutil.filecache: support watching over multiple files

login
register
mail settings
Submitter Siddharth Agarwal
Date Nov. 16, 2013, 11:21 p.m.
Message ID <c3b07326434223e8957e.1384644072@dev1091.prn1.facebook.com>
Download mbox | patch
Permalink /patch/3001/
State Accepted
Commit b3684fd2ff1ab48bf90f183379b6054365736b4d
Headers show

Comments

Siddharth Agarwal - Nov. 16, 2013, 11:21 p.m.
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1384637379 28800
#      Sat Nov 16 13:29:39 2013 -0800
# Node ID c3b07326434223e8957ea6e332d80f4cf5caa84a
# Parent  c31031f519a1410a7ab92a0a0b8c263cf1e92161
scmutil.filecache: support watching over multiple files
Augie Fackler - Nov. 17, 2013, 10:19 p.m.
On Sat, Nov 16, 2013 at 03:21:12PM -0800, Siddharth Agarwal wrote:
> # HG changeset patch
> # User Siddharth Agarwal <sid0@fb.com>
> # Date 1384637379 28800
> #      Sat Nov 16 13:29:39 2013 -0800
> # Node ID c3b07326434223e8957ea6e332d80f4cf5caa84a
> # Parent  c31031f519a1410a7ab92a0a0b8c263cf1e92161
> scmutil.filecache: support watching over multiple files

Queueing these.

>
> diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
> --- a/mercurial/scmutil.py
> +++ b/mercurial/scmutil.py
> @@ -924,23 +924,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
> @@ -965,11 +968,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
> @@ -981,7 +984,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]
> 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 @@
>      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())
> 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 @@
>
>  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
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel

Patch

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -924,23 +924,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
@@ -965,11 +968,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
@@ -981,7 +984,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]
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 @@ 
     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())
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 @@ 
 
 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