Merge lp:~aaron-whitehouse/duplicity/trailing_slash_match_dirs into lp:~duplicity-team/duplicity/0.7-series

Proposed by Aaron Whitehouse
Status: Merged
Merged at revision: 1112
Proposed branch: lp:~aaron-whitehouse/duplicity/trailing_slash_match_dirs
Merge into: lp:~duplicity-team/duplicity/0.7-series
Diff against target: 218 lines (+103/-20)
4 files modified
duplicity/selection.py (+33/-7)
po/duplicity.pot (+1/-1)
testing/functional/test_selection.py (+51/-0)
testing/unit/test_selection.py (+18/-12)
To merge this branch: bzr merge lp:~aaron-whitehouse/duplicity/trailing_slash_match_dirs
Reviewer Review Type Date Requested Status
duplicity-team Pending
Review via email: mp+266527@code.launchpad.net

Description of the change

Made globs with trailing slashes only match directories, not files, fixing Bug #1479545.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'duplicity/selection.py'
2--- duplicity/selection.py 2015-07-29 08:35:52 +0000
3+++ duplicity/selection.py 2015-07-31 09:21:59 +0000
4@@ -473,24 +473,39 @@
5
6 """
7 # Internal. Used by glob_get_sf and unit tests.
8+ match_only_dirs = False
9+
10+ if filename != "/" and filename[-1] == "/":
11+ match_only_dirs = True
12+ # Remove trailing / from directory name (unless that is the entire
13+ # string)
14+ filename = filename[:-1]
15+
16 if not filename.startswith(self.prefix):
17 raise FilePrefixError(filename)
18 index = tuple(filter(lambda x: x,
19 filename[len(self.prefix):].split("/")))
20- return self.glob_get_tuple_sf(index, include)
21+ return self.glob_get_tuple_sf(index, include, match_only_dirs)
22
23- def glob_get_tuple_sf(self, tuple, include):
24+ def glob_get_tuple_sf(self, tuple, include, match_only_dirs=False):
25 """Return selection function based on tuple"""
26 # Internal. Used by glob_get_filename_sf.
27+
28 def include_sel_func(path):
29- if (path.index == tuple[:len(path.index)] or
30+ if match_only_dirs and not path.isdir():
31+ # If the glob ended with a /, only match directories
32+ return None
33+ elif (path.index == tuple[:len(path.index)] or
34 path.index[:len(tuple)] == tuple):
35 return 1 # /foo/bar implicitly matches /foo, vice-versa
36 else:
37 return None
38
39 def exclude_sel_func(path):
40- if path.index[:len(tuple)] == tuple:
41+ if match_only_dirs and not path.isdir():
42+ # If the glob ended with a /, only match directories
43+ return None
44+ elif path.index[:len(tuple)] == tuple:
45 return 0 # /foo excludes /foo/bar, not vice-versa
46 else:
47 return None
48@@ -519,8 +534,13 @@
49
50 """
51 # Internal. Used by glob_get_sf and unit tests.
52+
53+ match_only_dirs = False
54+
55 if glob_str != "/" and glob_str[-1] == "/":
56- # Remove trailing / from directory name (unless that is the entire string)
57+ match_only_dirs = True
58+ # Remove trailing / from directory name (unless that is the entire
59+ # string)
60 glob_str = glob_str[:-1]
61
62 if glob_str.lower().startswith("ignorecase:"):
63@@ -539,7 +559,10 @@
64 "|".join(self.glob_get_prefix_res(glob_str)))
65
66 def include_sel_func(path):
67- if glob_comp_re.match(path.name):
68+ if match_only_dirs and not path.isdir():
69+ # If the glob ended with a /, only match directories
70+ return None
71+ elif glob_comp_re.match(path.name):
72 return 1
73 elif scan_comp_re.match(path.name):
74 return 2
75@@ -547,7 +570,10 @@
76 return None
77
78 def exclude_sel_func(path):
79- if glob_comp_re.match(path.name):
80+ if match_only_dirs and not path.isdir():
81+ # If the glob ended with a /, only match directories
82+ return None
83+ elif glob_comp_re.match(path.name):
84 return 0
85 else:
86 return None
87
88=== modified file 'po/duplicity.pot'
89--- po/duplicity.pot 2015-07-29 10:50:28 +0000
90+++ po/duplicity.pot 2015-07-31 09:21:59 +0000
91@@ -8,7 +8,7 @@
92 msgstr ""
93 "Project-Id-Version: PACKAGE VERSION\n"
94 "Report-Msgid-Bugs-To: Kenneth Loafman <kenneth@loafman.com>\n"
95-"POT-Creation-Date: 2015-07-29 11:39+0100\n"
96+"POT-Creation-Date: 2015-07-31 09:14+0100\n"
97 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
98 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
99 "Language-Team: LANGUAGE <LL@li.org>\n"
100
101=== modified file 'testing/functional/test_selection.py'
102--- testing/functional/test_selection.py 2015-07-28 17:48:40 +0000
103+++ testing/functional/test_selection.py 2015-07-31 09:21:59 +0000
104@@ -874,5 +874,56 @@
105 restored = self.directory_tree_to_list_of_lists(restore_dir)
106 self.assertEqual(restored, self.expected_restored_tree)
107
108+
109+class TestTrailingSlash(IncludeExcludeFunctionalTest):
110+ """ This tests the behaviour of globbing strings with a trailing slash"""
111+ # See Bug #1479545 (https://bugs.launchpad.net/duplicity/+bug/1479545)
112+
113+ def test_no_trailing_slash(self):
114+ """ Test that including 1.py works as expected"""
115+ self.backup("full", "testfiles/select2",
116+ options=["--include", "testfiles/select2/1.py",
117+ "--exclude", "**"])
118+ self.restore()
119+ restore_dir = 'testfiles/restore_out'
120+ restored = self.directory_tree_to_list_of_lists(restore_dir)
121+ self.assertEqual(restored, [['1.py']])
122+
123+ def test_trailing_slash(self):
124+ """ Test that globs with a trailing slash only match directories"""
125+ # ToDo: Bug #1479545
126+ # (https://bugs.launchpad.net/duplicity/+bug/1479545)
127+ self.backup("full", "testfiles/select2",
128+ options=["--include", "testfiles/select2/1.py/",
129+ "--exclude", "**"])
130+ self.restore()
131+ restore_dir = 'testfiles/restore_out'
132+ restored = self.directory_tree_to_list_of_lists(restore_dir)
133+ self.assertEqual(restored, [])
134+
135+ def test_include_files_not_subdirectories(self):
136+ """ Test that a trailing slash glob followed by a * glob only matches
137+ files and not subdirectories"""
138+ self.backup("full", "testfiles/select2",
139+ options=["--exclude", "testfiles/select2/*/",
140+ "--include", "testfiles/select2/*",
141+ "--exclude", "**"])
142+ self.restore()
143+ restore_dir = 'testfiles/restore_out'
144+ restored = self.directory_tree_to_list_of_lists(restore_dir)
145+ self.assertEqual(restored, [['1.doc', '1.py']])
146+
147+ def test_include_subdirectories_not_files(self):
148+ """ Test that a trailing slash glob only matches directories"""
149+ self.backup("full", "testfiles/select2",
150+ options=["--include", "testfiles/select2/1/1sub1/**/",
151+ "--exclude", "testfiles/select2/1/1sub1/**",
152+ "--exclude", "**"])
153+ self.restore()
154+ restore_dir = 'testfiles/restore_out'
155+ restored = self.directory_tree_to_list_of_lists(restore_dir)
156+ self.assertEqual(restored, [['1'], ['1sub1'],
157+ ['1sub1sub1', '1sub1sub2', '1sub1sub3']])
158+
159 if __name__ == "__main__":
160 unittest.main()
161
162=== modified file 'testing/unit/test_selection.py'
163--- testing/unit/test_selection.py 2015-07-29 10:50:28 +0000
164+++ testing/unit/test_selection.py 2015-07-31 09:21:59 +0000
165@@ -23,10 +23,12 @@
166 import types
167 import StringIO
168 import unittest
169+import duplicity.path
170
171 from duplicity.selection import * # @UnusedWildImport
172 from duplicity.lazy import * # @UnusedWildImport
173 from . import UnitTestCase
174+from mock import patch
175
176
177 class MatchingTest(UnitTestCase):
178@@ -57,24 +59,28 @@
179 self.assertRaises(FilePrefixError, self.Select.glob_get_normal_sf, "foo", 1)
180
181 sf2 = self.Select.glob_get_sf("testfiles/select/usr/local/bin/", 1)
182- assert sf2(self.makeext("usr")) == 1
183- assert sf2(self.makeext("usr/local")) == 1
184- assert sf2(self.makeext("usr/local/bin")) == 1
185- assert sf2(self.makeext("usr/local/doc")) is None
186- assert sf2(self.makeext("usr/local/bin/gzip")) == 1
187- assert sf2(self.makeext("usr/local/bingzip")) is None
188+
189+ with patch('duplicity.path.ROPath.isdir', return_value=True):
190+ assert sf2(self.makeext("usr")) == 1
191+ assert sf2(self.makeext("usr/local")) == 1
192+ assert sf2(self.makeext("usr/local/bin")) == 1
193+ assert sf2(self.makeext("usr/local/doc")) is None
194+ assert sf2(self.makeext("usr/local/bin/gzip")) == 1
195+ assert sf2(self.makeext("usr/local/bingzip")) is None
196
197 def test_tuple_exclude(self):
198 """Test exclude selection function made from a regular filename"""
199 self.assertRaises(FilePrefixError, self.Select.glob_get_normal_sf, "foo", 0)
200
201 sf2 = self.Select.glob_get_sf("testfiles/select/usr/local/bin/", 0)
202- assert sf2(self.makeext("usr")) is None
203- assert sf2(self.makeext("usr/local")) is None
204- assert sf2(self.makeext("usr/local/bin")) == 0
205- assert sf2(self.makeext("usr/local/doc")) is None
206- assert sf2(self.makeext("usr/local/bin/gzip")) == 0
207- assert sf2(self.makeext("usr/local/bingzip")) is None
208+
209+ with patch('duplicity.path.ROPath.isdir', return_value=True):
210+ assert sf2(self.makeext("usr")) is None
211+ assert sf2(self.makeext("usr/local")) is None
212+ assert sf2(self.makeext("usr/local/bin")) == 0
213+ assert sf2(self.makeext("usr/local/doc")) is None
214+ assert sf2(self.makeext("usr/local/bin/gzip")) == 0
215+ assert sf2(self.makeext("usr/local/bingzip")) is None
216
217 def test_glob_star_include(self):
218 """Test a few globbing patterns, including **"""

Subscribers

People subscribed via source and target branches