[PATCH 6 of 8] ignore: switch to a dynamic filecache

Durham Goode durham at fb.com
Wed May 13 10:13:20 CDT 2015


# HG changeset patch
# User Durham Goode <durham at fb.com>
# Date 1431484250 25200
#      Tue May 12 19:30:50 2015 -0700
# Node ID f058e14998c947d86ccbdd17edaf2392b4190cce
# Parent  71a709c7d25b9e918538ddbdc36d3c5734b83e23
ignore: switch to a dynamic filecache

Previously the ignore matcher was cached behind a rootcache which only checked
if the repo .hgignore had changed. If any of the other ignore files had changed
it wouldn't actually update.

Let's add a dynamicfilecache class that dynamically defines what files should be
watched. This will allow us to watch all the ignore files, and in a future patch
it will enable us to watch sub-directory ignore files as well.

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -12,6 +12,7 @@ import os, stat, errno
 
 propertycache = util.propertycache
 filecache = scmutil.filecache
+dynamicfilecache = scmutil.dynamicfilecache
 _rangemask = 0x7fffffff
 
 dirstatetuple = parsers.dirstatetuple
@@ -141,7 +142,7 @@ class dirstate(object):
     def dirs(self):
         return self._dirs
 
-    @rootcache('.hgignore')
+    @dynamicfilecache()
     def _ignore(self):
         files = [self._join('.hgignore')]
         for name, path in self._ui.configitems("ui"):
@@ -149,7 +150,8 @@ class dirstate(object):
                 # we need to use os.path.join here rather than self._join
                 # because path is arbitrary and user-specified
                 files.append(os.path.join(self._rootdir, util.expandpath(path)))
-        return ignore.ignore(self._ui, self._root, files)
+        matcher, paths = ignore.ignore(self._ui, self._root, files)
+        return paths, matcher
 
     @propertycache
     def _slash(self):
diff --git a/mercurial/ignore.py b/mercurial/ignore.py
--- a/mercurial/ignore.py
+++ b/mercurial/ignore.py
@@ -74,14 +74,16 @@ def readpats(ui, root, files):
     '''return a dict mapping ignore-file-name to list-of-patterns'''
 
     pats = {}
+    paths = set()
     for f in files:
         if f in pats:
             continue
         skipwarning = f == files[0]
         fullpath = os.path.normpath(os.path.join(root, f))
+        paths.add(fullpath)
         pats[f] = readignorefile(ui, root, fullpath, skipwarning=skipwarning)
 
-    return [(f, pats[f]) for f in files if f in pats]
+    return [(f, pats[f]) for f in files if f in pats], paths
 
 def ignore(ui, root, files):
     '''return matcher covering patterns in 'files'.
@@ -103,13 +105,13 @@ def ignore(ui, root, files):
     glob:pattern   # non-rooted glob
     pattern        # pattern of the current default type'''
 
-    pats = readpats(ui, root, files)
+    pats, paths = readpats(ui, root, files)
 
     allpats = []
     for f, patlist in pats:
         allpats.extend(patlist)
     if not allpats:
-        return util.never
+        return util.never, paths
 
     try:
         ignorefunc = match.match(root, '', [], allpats)
@@ -121,4 +123,4 @@ def ignore(ui, root, files):
             except util.Abort, inst:
                 raise util.Abort('%s: %s' % (f, inst[0]))
 
-    return ignorefunc
+    return ignorefunc, paths
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1164,3 +1164,29 @@ class filecache(object):
             del obj.__dict__[self.name]
         except KeyError:
             raise AttributeError(self.name)
+
+class dynamicfilecache(filecache):
+    """A filecache whose file list is dynamically determined.
+
+    This operates just like a normal filecache, except it expects the returned
+    value to be of the form (paths, value).  The paths part represents which
+    files were used to compute the value and is used to check if the cached
+    value is invalid.
+
+    By default, all dynamically defined paths are expected to be absolute paths.
+    """
+    def join(self, obj, fname):
+        return fname
+
+    def __get__(self, obj, type=None):
+        paths, value = super(dynamicfilecache, self).__get__(obj, type=type)
+        if paths != self.paths:
+            self.paths = paths
+            entry = filecacheentry(paths, True)
+            entry.obj = (paths, value)
+            obj._filecache[self.name] = entry
+
+        return value
+
+    def __set__(self, obj, value):
+        super(dynamicfilecache, self).__set__(obj, (self.paths, value))


More information about the Mercurial-devel mailing list