@@ -108,38 +108,68 @@ def exclude_paths(root, patterns, dockerfile=None):
108
108
109
109
exclude_patterns = list (set (patterns ) - set (exceptions ))
110
110
111
- all_paths = get_paths (root )
112
-
113
- # Remove all paths that are matched by any exclusion pattern
114
- paths = [
115
- p for p in all_paths
116
- if not any (match_path (p , pattern ) for pattern in exclude_patterns )
117
- ]
118
-
119
- # Add back the set of paths that are matched by any inclusion pattern.
120
- # Include parent dirs - if we add back 'foo/bar', add 'foo' as well
121
- for p in all_paths :
122
- if any (match_path (p , pattern ) for pattern in include_patterns ):
123
- components = p .split ('/' )
124
- paths += [
125
- '/' .join (components [:end ])
126
- for end in range (1 , len (components ) + 1 )
127
- ]
111
+ paths = get_paths (root , exclude_patterns , include_patterns ,
112
+ has_exceptions = len (exceptions ) > 0 )
128
113
129
114
return set (paths )
130
115
131
116
132
- def get_paths (root ):
117
+ def should_include (path , exclude_patterns , include_patterns ):
118
+ """
119
+ Given a path, a list of exclude patterns, and a list of inclusion patterns:
120
+
121
+ 1. Returns True if the path doesn't match any exclusion pattern
122
+ 2. Returns False if the path matches an exclusion pattern and doesn't match
123
+ an inclusion pattern
124
+ 3. Returns true if the path matches an exclusion pattern and matches an
125
+ inclusion pattern
126
+ """
127
+ for pattern in exclude_patterns :
128
+ if match_path (path , pattern ):
129
+ for pattern in include_patterns :
130
+ if match_path (path , pattern ):
131
+ return True
132
+ return False
133
+ return True
134
+
135
+
136
+ def get_paths (root , exclude_patterns , include_patterns , has_exceptions = False ):
133
137
paths = []
134
138
135
- for parent , dirs , files in os .walk (root , followlinks = False ):
139
+ for parent , dirs , files in os .walk (root , topdown = True , followlinks = False ):
136
140
parent = os .path .relpath (parent , root )
137
141
if parent == '.' :
138
142
parent = ''
143
+
144
+ # If exception rules exist, we can't skip recursing into ignored
145
+ # directories, as we need to look for exceptions in them.
146
+ #
147
+ # It may be possible to optimize this further for exception patterns
148
+ # that *couldn't* match within ignored directores.
149
+ #
150
+ # This matches the current docker logic (as of 2015-11-24):
151
+ # https://github.com/docker/docker/blob/37ba67bf636b34dc5c0c0265d62a089d0492088f/pkg/archive/archive.go#L555-L557
152
+
153
+ if not has_exceptions :
154
+
155
+ # Remove excluded patterns from the list of directories to traverse
156
+ # by mutating the dirs we're iterating over.
157
+ # This looks strange, but is considered the correct way to skip
158
+ # traversal. See https://docs.python.org/2/library/os.html#os.walk
159
+
160
+ dirs [:] = [d for d in dirs if
161
+ should_include (os .path .join (parent , d ),
162
+ exclude_patterns , include_patterns )]
163
+
139
164
for path in dirs :
140
- paths .append (os .path .join (parent , path ))
165
+ if should_include (os .path .join (parent , path ),
166
+ exclude_patterns , include_patterns ):
167
+ paths .append (os .path .join (parent , path ))
168
+
141
169
for path in files :
142
- paths .append (os .path .join (parent , path ))
170
+ if should_include (os .path .join (parent , path ),
171
+ exclude_patterns , include_patterns ):
172
+ paths .append (os .path .join (parent , path ))
143
173
144
174
return paths
145
175
0 commit comments