Patchwork [09,of,12] filecache: create an entry in _filecache when __set__ is called for a missing one

login
register
mail settings
Submitter Idan Kamara
Date Dec. 17, 2012, 3:35 p.m.
Message ID <5046f500886a30970f1b.1355758534@idan>
Download mbox | patch
Permalink /patch/163/
State Superseded, archived
Commit f36375576ed570f55e5132670a598e3f2676f2b5
Headers show

Comments

Idan Kamara - Dec. 17, 2012, 3:35 p.m.
# HG changeset patch
# User Idan Kamara <idankk86 at gmail.com>
# Date 1355750745 -7200
# Branch stable
# Node ID 5046f500886a30970f1b7e40db9a372b51055748
# Parent  569e314c159920cdaba86d75374e1b6eddb11284
filecache: create an entry in _filecache when __set__ is called for a missing one

Preserve the invariant that if P is a filecached property on X then
P in X.__dict__ => P in X._filecache.

Previously, it was possible for a filecached property to become out of sync
with the filesystem if it was set before getting it first, since the initial
filecacheentry was created in __get__.

Old behaviour:

  repo.prop = x
  repo.invalidate() # prop has no entry in _filecache, it's not removed from __dict__
  repo.prop         # returns x like before without checking with the filesystem

New:

  repo.prop = x     # an empty entry is created in _filecache
  repo.invalidate() # prop is removed from __dict__
  repo.prop         # recreates prop

Patch

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -934,6 +934,7 @@ 
     def __get__(self, obj, type=None):
         # do we need to check if the file changed?
         if self.name in obj.__dict__:
+            assert self.name in obj._filecache, self.name
             return obj.__dict__[self.name]
 
         entry = obj._filecache.get(self.name)
@@ -955,8 +956,15 @@ 
         return entry.obj
 
     def __set__(self, obj, value):
-        if self.name in obj._filecache:
-            obj._filecache[self.name].obj = value # update cached copy
+        if self.name not in obj._filecache:
+            # we add an entry for the missing value because X in __dict__
+            # implies X in _filecache
+            ce = filecacheentry(self.join(obj, self.path), False)
+            obj._filecache[self.name] = ce
+        else:
+            ce = obj._filecache[self.name]
+
+        ce.obj = value # update cached copy
         obj.__dict__[self.name] = value # update copy returned by obj.x
 
     def __delete__(self, obj):
diff --git a/tests/test-filecache.py b/tests/test-filecache.py
--- a/tests/test-filecache.py
+++ b/tests/test-filecache.py
@@ -101,6 +101,17 @@ 
     # it
     repo.commit('.')
 
+def setbeforeget(repo):
+    os.remove('x')
+    repo.cached = 0
+    repo.invalidate()
+    print repo.cached
+    repo.invalidate()
+    f = open('x', 'w')
+    f.write('a')
+    f.close()
+    print repo.cached
+
 print 'basic:'
 print
 basic(fakerepo())
@@ -109,3 +120,7 @@ 
 print
 fakeuncacheable()
 test_filecache_synced()
+print
+print 'setbeforeget:'
+print
+setbeforeget(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
@@ -17,3 +17,9 @@ 
 working directory now based on revision -1
 repository tip rolled back to revision -1 (undo commit)
 working directory now based on revision -1
+
+setbeforeget:
+
+0
+creating
+None