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 | ||
|
91 | basic(fakerepo()) | |
|
92 | ||
|
93 | print 'fakeuncacheable:' | |
|
94 | ||
|
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 | 709 | raise error.RequirementError(_("unknown repository format: " |
|
710 | 710 | "requires features '%s' (upgrade Mercurial)") % "', '".join(missings)) |
|
711 | 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