|
@@ -0,0 +1,115 @@
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+"""Parsing of gitignore files."""
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+import fnmatch
|
|
|
+import posixpath
|
|
|
+
|
|
|
+
|
|
|
+def read_ignore_patterns(f):
|
|
|
+ """Read a git ignore file.
|
|
|
+
|
|
|
+ :param f: File-like object to read from
|
|
|
+ :return: List of patterns
|
|
|
+ """
|
|
|
+
|
|
|
+ for l in f:
|
|
|
+ l = l.rstrip(b"\n")
|
|
|
+
|
|
|
+
|
|
|
+ if not l:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if l[0:1] == b'#':
|
|
|
+
|
|
|
+ continue
|
|
|
+
|
|
|
+
|
|
|
+ while l.endswith(b' ') and not l.endswith(b'\\ '):
|
|
|
+ l = l[:-1]
|
|
|
+ l = l.replace(b'\\ ', b' ')
|
|
|
+
|
|
|
+ yield l
|
|
|
+
|
|
|
+
|
|
|
+def match_pattern(path, pattern):
|
|
|
+ """Match a gitignore-style pattern against a path.
|
|
|
+
|
|
|
+ :param path: Path to match
|
|
|
+ :param pattern: Pattern to match
|
|
|
+ :return: bool indicating whether the pattern matched
|
|
|
+ """
|
|
|
+ if b'/' not in pattern:
|
|
|
+ return fnmatch.fnmatch(posixpath.basename(path), pattern)
|
|
|
+ pattern = pattern.lstrip(b'/')
|
|
|
+ return fnmatch.fnmatch(path, pattern)
|
|
|
+
|
|
|
+
|
|
|
+class IgnoreFilter(object):
|
|
|
+
|
|
|
+ def __init__(self, patterns):
|
|
|
+ self._patterns = []
|
|
|
+ for pattern in patterns:
|
|
|
+ self.append_pattern(pattern)
|
|
|
+
|
|
|
+ def append_pattern(self, pattern):
|
|
|
+ """Add a pattern to the set."""
|
|
|
+ self._patterns.append(pattern)
|
|
|
+
|
|
|
+ def is_ignored(self, path):
|
|
|
+ """Check whether a path is ignored.
|
|
|
+
|
|
|
+ For directories, include a trailing slash.
|
|
|
+
|
|
|
+ :return: None if file is not mentioned, True if it is included, False
|
|
|
+ if it is explicitly excluded.
|
|
|
+ """
|
|
|
+ status = None
|
|
|
+ for pattern in self._patterns:
|
|
|
+ if pattern[0:1] == b'!':
|
|
|
+ if match_pattern(pattern[1:], path):
|
|
|
+
|
|
|
+ return False
|
|
|
+ else:
|
|
|
+ if pattern[0:1] == b'\\':
|
|
|
+ pattern = pattern[1:]
|
|
|
+ if match_pattern(pattern, path):
|
|
|
+ status = True
|
|
|
+ return status
|
|
|
+
|
|
|
+
|
|
|
+class IgnoreFilterStack(object):
|
|
|
+ """Check for ignore status in multiple filters."""
|
|
|
+
|
|
|
+ def __init__(self, filters):
|
|
|
+ self._filters = filters
|
|
|
+
|
|
|
+ def is_ignored(self, path):
|
|
|
+ """Check whether a path is explicitly included or excluded in ignores."""
|
|
|
+ status = None
|
|
|
+ for filter in self._filters:
|
|
|
+ status = filter.is_ignored(path)
|
|
|
+ if status is not None:
|
|
|
+ return status
|
|
|
+ return status
|