Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 05c1ada

Browse files
committed
Rewritten path matching and added unittests.
1 parent 13c0eae commit 05c1ada

2 files changed

Lines changed: 107 additions & 63 deletions

File tree

git-ftp-test.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env python
2+
# -*- coding:utf-8 -*-
3+
#
4+
import unittest
5+
6+
git_ftp = __import__('git-ftp', globals(), locals(), ['parse_ftpignore', 'is_ignored', 'split_pattern'], -1)
7+
parse_ftpignore = git_ftp.parse_ftpignore
8+
is_ignored = git_ftp.is_ignored
9+
split_pattern = git_ftp.split_pattern
10+
11+
class TestGitFtp(unittest.TestCase):
12+
13+
def test_parse_ftpignore(self):
14+
patterns = '''
15+
# comment and blank line
16+
17+
# negate patterns behaviour (not supported)
18+
!fileX.txt
19+
# directory match
20+
config/
21+
# shell glob (without /)
22+
*swp
23+
BasePresenter.php
24+
# with /
25+
css/*less
26+
# beginning of path
27+
/.htaccess
28+
'''
29+
self.assertEqual(parse_ftpignore(patterns.split("\n")),
30+
['!fileX.txt', 'config/', '*swp', 'BasePresenter.php', 'css/*less', '/.htaccess']
31+
)
32+
pass
33+
34+
def test_split_pattern(self):
35+
self.assertEqual(split_pattern('/foo/rand[/]om/dir/'), ['', 'foo\\Z(?ms)', 'rand[/]om\\Z(?ms)', 'dir\\Z(?ms)', '\\Z(?ms)'])
36+
self.assertEqual(split_pattern('/ano[/]her/bar/file[.-0]txt'), ['', 'ano[/]her\\Z(?ms)', 'bar\\Z(?ms)', 'file[.-0]txt\\Z(?ms)'])
37+
self.assertEqual(split_pattern('left[/right'), ['left\\[\\Z(?ms)', 'right\\Z(?ms)'])
38+
self.assertEqual(split_pattern('left[/notright]'), ['left[/notright]\\Z(?ms)'])
39+
pass
40+
41+
def test_is_ignored(self):
42+
self.assertTrue(is_ignored('/foo/bar/', 'bar/'), 'Ending slash matches only dir.')
43+
self.assertFalse(is_ignored('/foo/bar', 'bar/'), 'Ending slash matches only dir.')
44+
self.assertTrue(is_ignored('/foo/bar/baz', 'bar/'), 'Ending slash matches only dir and path underneath it.')
45+
46+
self.assertFalse(is_ignored('foo/bar', 'foo?*bar'), 'Slash must be matched explicitly.')
47+
48+
self.assertTrue(is_ignored('/foo/bar/', 'bar'))
49+
self.assertTrue(is_ignored('/foo/bar', 'bar'))
50+
self.assertTrue(is_ignored('/foo/bar/baz', 'bar'))
51+
52+
self.assertTrue(is_ignored('/foo/bar/file.txt', 'bar/*.txt'))
53+
self.assertFalse(is_ignored('/foo/bar/file.txt', '/*.txt'), 'Leading slash matches against root dir.')
54+
self.assertTrue(is_ignored('/file.txt', '/*.txt'), 'Leading slash matches against root dir.')
55+
56+
self.assertTrue(is_ignored('/foo/bar/output.o', 'bar/*.[oa]'), 'Character group.')
57+
self.assertFalse(is_ignored('/aaa/bbb/ccc', 'aaa/[!b]*'), 'Character ignore.')
58+
self.assertTrue(is_ignored('/aaa/bbb/ccc', '[a-z][a-c][!b-d]'), 'Character range.')
59+
pass
60+
61+
62+
if __name__ == '__main__':
63+
unittest.main()

git-ftp.py

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import optparse
4141
import logging
4242
import 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):
6465
class 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

130111
def 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

Comments
 (0)