4040import optparse
4141import logging
4242import textwrap
43+ import fnmatch
4344
4445# Note about Tree.path/Blob.path: *real* Git trees and blobs don't
4546# actually provide path information, but the git-python bindings, as a
@@ -64,68 +65,48 @@ class FtpDataOldVersion(Exception):
6465class FtpSslNotSupported (Exception ):
6566 pass
6667
67- # Unfortunately Python's fnmatch don't support FNM_PATHNAME
68- # so we have to roll our own
69- class fnmatch2 :
70- _cache = {}
71-
72- @classmethod
73- def fnmatch (cls , name , pat ):
74- name = os .path .normcase (name )
75- pat = os .path .normcase (pat )
76- return cls .fnmatchcase (name , pat )
77-
78- @classmethod
79- def fnmatchcase (cls , name , pat ):
80- if not pat in cls ._cache :
81- res = cls .translate (pat )
82- cls ._cache [pat ] = re .compile (res )
83- return cls ._cache [pat ].search (name ) is not None
84-
85- @classmethod
86- def translate (cls , pat ):
87- '''
88- * matches everything
89- ? matches any single character
90- [seq] matches any character in seq
91- [!seq] matches any character not in seq
92-
93- Slash (/) won't be matched by any wildcard.
94- '''
95- i , n = 0 , len (pat )
96- res = ''
97-
98- if pat .startswith ('/' ):
99- res = res + '^/'
100- i = i + 1
101- while i < n :
102- c = pat [i ]
103- i = i + 1
104- if c == '*' :
105- res = res + '[^/]*'
106- elif c == '?' :
107- res = res + '[^/]'
108- elif c == '[' :
109- j = i
110- if j < n and pat [j ] == '!' :
111- j = j + 1
112- if j < n and pat [j ] == ']' :
113- j = j + 1
114- while j < n and pat [j ] != ']' :
115- j = j + 1
116- if j >= n :
117- res = res + '\\ ['
118- else :
119- stuff = pat [i :j ].replace ('\\ ' , '\\ \\ ' )
120- i = j + 1
121- if stuff [0 ] == '!' :
122- stuff = '^' + stuff [1 :]
123- elif stuff [0 ] == '^' :
124- stuff = '\\ ' + stuff
125- res = '%s[%s]' % (res , stuff )
126- else :
127- res = res + re .escape (c )
128- return res
68+ def split_pattern (path ):
69+ path = fnmatch .translate (path ).split ('\\ /' )
70+ for i ,p in enumerate (path [:- 1 ]):
71+ if p :
72+ path [i ] = p + '\\ Z(?ms)'
73+ return path
74+
75+
76+ def is_ignored (path , regex ):
77+ regex = split_pattern (os .path .normcase (regex ))
78+ path = os .path .normcase (path ).split ('/' )
79+
80+ regex_pos = path_pos = 0
81+ if regex [0 ] == '' : # leading slash - root dir must match
82+ if path [0 ] != '' or not re .match (regex [1 ], path [1 ]):
83+ return False
84+ regex_pos = path_pos = 2
85+
86+ if not regex_pos : # find beginning of regex
87+ for i ,p in enumerate (path ):
88+ if re .match (regex [0 ], p ):
89+ regex_pos = 1
90+ path_pos = i + 1
91+ break
92+ else :
93+ return False
94+
95+ if len (path [path_pos :]) < len (regex [regex_pos :]):
96+ return False
97+
98+ n = len (regex )
99+ for r in regex [regex_pos :]: # match the rest
100+ if regex_pos + 1 == n : # last item; if empty match anything
101+ if re .match (r , '' ):
102+ return True
103+
104+ if not re .match (r , path [path_pos ]):
105+ return False
106+ path_pos += 1
107+ regex_pos += 1
108+
109+ return True
129110
130111def main ():
131112 Git .git_binary = 'git' # Windows doesn't like env
@@ -420,7 +401,7 @@ def is_ignored_path(path, patterns, quiet = False):
420401 if is_special_file (path ):
421402 return True
422403 for pat in patterns :
423- if fnmatch2 . fnmatch (path , pat ):
404+ if is_ignored (path , pat ):
424405 return True
425406 return False
426407
0 commit comments