comparison mercurial/scmutil.py @ 14928:dca59d5be12d

scmutil: introduce filecache The idea is being able to associate a file with a property, and watch that file stat info for modifications when we decide it's important for it to be up-to-date. Once it changes, we recreate the object. On filesystems that can't uniquely identify a file, we always recreate. As a consequence, localrepo.invalidate() will become much less expensive in the case where nothing changed on-disk.
author Idan Kamara <idankk86@gmail.com>
date Sat, 09 Jul 2011 19:06:59 +0300
parents 6ed2a449cb5b
children 5523529bd1af
comparison
equal deleted inserted replaced
14927:2aa3e07b2f07 14928:dca59d5be12d
707 missings.sort() 707 missings.sort()
708 if missings: 708 if missings:
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