##// END OF EJS Templates
scmutil: introduce filecache...
Idan Kamara -
r14928:dca59d5b default
parent child Browse files
Show More
@@ -0,0 +1,95 b''
1 import sys, os, subprocess
2
3 try:
4 subprocess.check_call(['%s/hghave' % os.environ['TESTDIR'], 'cacheable'])
5 except subprocess.CalledProcessError:
6 sys.exit(80)
7
8 from mercurial import util, scmutil, extensions
9
10 filecache = scmutil.filecache
11
12 class fakerepo(object):
13 def __init__(self):
14 self._filecache = {}
15
16 def join(self, p):
17 return p
18
19 def sjoin(self, p):
20 return p
21
22 @filecache('x')
23 def cached(self):
24 print 'creating'
25
26 def invalidate(self):
27 for k in self._filecache:
28 try:
29 delattr(self, k)
30 except AttributeError:
31 pass
32
33 def basic(repo):
34 # file doesn't exist, calls function
35 repo.cached
36
37 repo.invalidate()
38 # file still doesn't exist, uses cache
39 repo.cached
40
41 # create empty file
42 f = open('x', 'w')
43 f.close()
44 repo.invalidate()
45 # should recreate the object
46 repo.cached
47
48 f = open('x', 'w')
49 f.write('a')
50 f.close()
51 repo.invalidate()
52 # should recreate the object
53 repo.cached
54
55 repo.invalidate()
56 # stats file again, nothing changed, reuses object
57 repo.cached
58
59 # atomic replace file, size doesn't change
60 # hopefully st_mtime doesn't change as well so this doesn't use the cache
61 # because of inode change
62 f = scmutil.opener('.')('x', 'w', atomictemp=True)
63 f.write('b')
64 f.rename()
65
66 repo.invalidate()
67 repo.cached
68
69 def fakeuncacheable():
70 def wrapcacheable(orig, *args, **kwargs):
71 return False
72
73 def wrapinit(orig, *args, **kwargs):
74 pass
75
76 originit = extensions.wrapfunction(util.cachestat, '__init__', wrapinit)
77 origcacheable = extensions.wrapfunction(util.cachestat, 'cacheable', wrapcacheable)
78
79 try:
80 os.remove('x')
81 except:
82 pass
83
84 basic(fakerepo())
85
86 util.cachestat.cacheable = origcacheable
87 util.cachestat.__init__ = originit
88
89 print 'basic:'
90 print
91 basic(fakerepo())
92 print
93 print 'fakeuncacheable:'
94 print
95 fakeuncacheable()
@@ -0,0 +1,15 b''
1 basic:
2
3 creating
4 creating
5 creating
6 creating
7
8 fakeuncacheable:
9
10 creating
11 creating
12 creating
13 creating
14 creating
15 creating
@@ -709,3 +709,95 b' def readrequires(opener, supported):'
709 raise error.RequirementError(_("unknown repository format: "
709 raise error.RequirementError(_("unknown repository format: "
710 "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
710 "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
711 return requirements
711 return requirements
712
713 class filecacheentry(object):
714 def __init__(self, path):
715 self.path = path
716 self.cachestat = filecacheentry.stat(self.path)
717
718 if self.cachestat:
719 self._cacheable = self.cachestat.cacheable()
720 else:
721 # None means we don't know yet
722 self._cacheable = None
723
724 def refresh(self):
725 if self.cacheable():
726 self.cachestat = filecacheentry.stat(self.path)
727
728 def cacheable(self):
729 if self._cacheable is not None:
730 return self._cacheable
731
732 # we don't know yet, assume it is for now
733 return True
734
735 def changed(self):
736 # no point in going further if we can't cache it
737 if not self.cacheable():
738 return True
739
740 newstat = filecacheentry.stat(self.path)
741
742 # we may not know if it's cacheable yet, check again now
743 if newstat and self._cacheable is None:
744 self._cacheable = newstat.cacheable()
745
746 # check again
747 if not self._cacheable:
748 return True
749
750 if self.cachestat != newstat:
751 self.cachestat = newstat
752 return True
753 else:
754 return False
755
756 @staticmethod
757 def stat(path):
758 try:
759 return util.cachestat(path)
760 except OSError, e:
761 if e.errno != errno.ENOENT:
762 raise
763
764 class filecache(object):
765 '''A property like decorator that tracks a file under .hg/ for updates.
766
767 Records stat info when called in _filecache.
768
769 On subsequent calls, compares old stat info with new info, and recreates
770 the object when needed, updating the new stat info in _filecache.
771
772 Mercurial either atomic renames or appends for files under .hg,
773 so to ensure the cache is reliable we need the filesystem to be able
774 to tell us if a file has been replaced. If it can't, we fallback to
775 recreating the object on every call (essentially the same behaviour as
776 propertycache).'''
777 def __init__(self, path, instore=False):
778 self.path = path
779 self.instore = instore
780
781 def __call__(self, func):
782 self.func = func
783 self.name = func.__name__
784 return self
785
786 def __get__(self, obj, type=None):
787 entry = obj._filecache.get(self.name)
788
789 if entry:
790 if entry.changed():
791 entry.obj = self.func(obj)
792 else:
793 path = self.instore and obj.sjoin(self.path) or obj.join(self.path)
794
795 # We stat -before- creating the object so our cache doesn't lie if
796 # a writer modified between the time we read and stat
797 entry = filecacheentry(path)
798 entry.obj = self.func(obj)
799
800 obj._filecache[self.name] = entry
801
802 setattr(obj, self.name, entry.obj)
803 return entry.obj
General Comments 0
You need to be logged in to leave comments. Login now