Forráskód Böngészése

Add IgnoreFilterManager.

Jelmer Vernooij 7 éve
szülő
commit
450148eac0
2 módosított fájl, 122 hozzáadás és 6 törlés
  1. 88 0
      dulwich/ignore.py
  2. 34 6
      dulwich/tests/test_ignore.py

+ 88 - 0
dulwich/ignore.py

@@ -22,6 +22,7 @@
 For details for the matching rules, see https://git-scm.com/docs/gitignore
 """
 
+import os.path
 import re
 
 
@@ -156,6 +157,19 @@ class IgnoreFilter(object):
                     status = True
         return status
 
+    @classmethod
+    def from_path(cls, path):
+        with open(path, 'r') as f:
+            ret = cls(read_ignore_patterns(f))
+            ret._path = path
+            return ret
+
+    def __repr__(self):
+        if getattr(self, '_path', None) is None:
+            return "<%s>" % (type(self).__name__)
+        else:
+            return "%s.from_path(%r)" % (type(self).__name__, self._path)
+
 
 class IgnoreFilterStack(object):
     """Check for ignore status in multiple filters."""
@@ -176,3 +190,77 @@ class IgnoreFilterStack(object):
             if status is not None:
                 return status
         return status
+
+
+def default_user_ignore_filter_path(config):
+    """Return default user ignore filter path.
+
+    :param config: A Config object
+    :return: Path to a global ignore file
+    """
+    try:
+        return config.get(('core', ), 'excludesFile')
+    except KeyError:
+        pass
+
+    if os.environ.get('XDG_CONFIG_HOME', ''):
+        return os.path.join(os.environ['XDG_CONFIG_HOME'], 'git', 'ignore')
+    else:
+        return os.path.join(os.environ['HOME'], '.config', 'git', 'ignore')
+
+
+class IgnoreFilterManager(object):
+    """Ignore file manager."""
+
+    def __init__(self, top_path, global_filters):
+        self._path_filters = {}
+        self._top_path = top_path
+        self._global_filters = global_filters
+
+    def _load_path(self, path):
+        try:
+            return self._path_filters[path]
+        except KeyError:
+            pass
+
+        p = os.path.join(path, '.gitignore')
+        try:
+            self._path_filters[path] = IgnoreFilter.from_path(p)
+        except IOError:
+            self._path_filters[path] = None
+        return self._path_filters[path]
+
+    def is_ignored(self, path):
+        """Check whether a path is explicitly included or excluded in ignores.
+
+        :param path: Path to check
+        :return: None if the file is not mentioned, True if it is included,
+            False if it is explicitly excluded.
+        """
+        dirname = path
+        while dirname not in (self._top_path, '/'):
+            dirname = os.path.dirname(dirname)
+            ignore_filter = self._load_path(dirname)
+            if ignore_filter is not None:
+                relpath = os.path.relpath(path, dirname)
+                status = ignore_filter.is_ignored(relpath)
+                if status is not None:
+                    return status
+        for ignore_filter in self._global_filters:
+            relpath = os.path.relpath(path, dirname)
+            status = ignore_filter.is_ignored(relpath)
+            if status is not None:
+                return status
+        return None
+
+    @classmethod
+    def from_repo(cls, repo):
+        global_filters = []
+        for p in [
+                os.path.join(repo.controldir(), 'info', 'exclude'),
+                default_user_ignore_filter_path(repo.get_config_stack())]:
+            try:
+                global_filters.append(IgnoreFilter.from_path(p))
+            except IOError:
+                pass
+        return cls(repo.path, global_filters)

+ 34 - 6
dulwich/tests/test_ignore.py

@@ -21,16 +21,21 @@
 """Tests for ignore files."""
 
 from io import BytesIO
+import os
 import re
-import unittest
+import shutil
+import tempfile
+from dulwich.tests import TestCase
 
 from dulwich.ignore import (
     IgnoreFilter,
+    IgnoreFilterManager,
     IgnoreFilterStack,
     match_pattern,
     read_ignore_patterns,
     translate,
     )
+from dulwich.repo import Repo
 
 
 POSITIVE_MATCH_TESTS = [
@@ -74,7 +79,7 @@ TRANSLATE_TESTS = [
 ]
 
 
-class TranslateTests(unittest.TestCase):
+class TranslateTests(TestCase):
 
     def test_translate(self):
         for (pattern, regex) in TRANSLATE_TESTS:
@@ -88,7 +93,7 @@ class TranslateTests(unittest.TestCase):
                 (pattern, translate(pattern), regex))
 
 
-class ReadIgnorePatterns(unittest.TestCase):
+class ReadIgnorePatterns(TestCase):
 
     def test_read_file(self):
         f = BytesIO(b"""
@@ -109,7 +114,7 @@ with escaped trailing whitespace\
         ])
 
 
-class MatchPatternTests(unittest.TestCase):
+class MatchPatternTests(TestCase):
 
     def test_matches(self):
         for (path, pattern) in POSITIVE_MATCH_TESTS:
@@ -124,7 +129,7 @@ class MatchPatternTests(unittest.TestCase):
                 "path: %r, pattern: %r" % (path, pattern))
 
 
-class IgnoreFilterTests(unittest.TestCase):
+class IgnoreFilterTests(TestCase):
 
     def test_included(self):
         filter = IgnoreFilter([b'a.c', b'b.c'])
@@ -141,7 +146,7 @@ class IgnoreFilterTests(unittest.TestCase):
         self.assertTrue(filter.is_ignored(b'a.c'))
 
 
-class IgnoreFilterStackTests(unittest.TestCase):
+class IgnoreFilterStackTests(TestCase):
 
     def test_stack_first(self):
         filter1 = IgnoreFilter([b'[a].c', b'[b].c', b'![d].c'])
@@ -152,3 +157,26 @@ class IgnoreFilterStackTests(unittest.TestCase):
         self.assertIs(True, stack.is_ignored(b'c.c'))
         self.assertIs(False, stack.is_ignored(b'd.c'))
         self.assertIs(None, stack.is_ignored(b'e.c'))
+
+
+class IgnoreFilterManagerTests(TestCase):
+
+    def test_load_ignore(self):
+        tmp_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, tmp_dir)
+        repo = Repo.init(tmp_dir)
+        with open(os.path.join(repo.path, '.gitignore'), 'w') as f:
+            f.write('/foo/bar\n')
+        os.mkdir(os.path.join(repo.path, 'dir'))
+        with open(os.path.join(repo.path, 'dir', '.gitignore'), 'w') as f:
+            f.write('/blie\n')
+        with open(os.path.join(repo.path, 'dir', 'blie'), 'w') as f:
+            f.write('IGNORED')
+        with open(os.path.join(repo.controldir(), 'info', 'exclude'), 'w') as f:
+            f.write('/excluded\n')
+        m = IgnoreFilterManager.from_repo(repo)
+        self.assertTrue(m.is_ignored(os.path.join(repo.path, 'dir', 'blie')))
+        self.assertIs(None, m.is_ignored(os.path.join(repo.path, 'dir', 'bloe')))
+        self.assertIs(None, m.is_ignored(os.path.join(repo.path, 'dir')))
+        self.assertTrue(m.is_ignored(os.path.join(repo.path, 'foo', 'bar')))
+        self.assertTrue(m.is_ignored(os.path.join(repo.path, 'excluded')))