##// END OF EJS Templates
match: simplify brittle predicate construction...
Martin von Zweigbergk -
r22513:ca709785 default
parent child Browse files
Show More
@@ -1,416 +1,408 b''
1 # match.py - filename matching
1 # match.py - filename matching
2 #
2 #
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 import util, pathutil
9 import util, pathutil
10 from i18n import _
10 from i18n import _
11
11
12 def _rematcher(regex):
12 def _rematcher(regex):
13 '''compile the regexp with the best available regexp engine and return a
13 '''compile the regexp with the best available regexp engine and return a
14 matcher function'''
14 matcher function'''
15 m = util.re.compile(regex)
15 m = util.re.compile(regex)
16 try:
16 try:
17 # slightly faster, provided by facebook's re2 bindings
17 # slightly faster, provided by facebook's re2 bindings
18 return m.test_match
18 return m.test_match
19 except AttributeError:
19 except AttributeError:
20 return m.match
20 return m.match
21
21
22 def _expandsets(kindpats, ctx):
22 def _expandsets(kindpats, ctx):
23 '''Returns the kindpats list with the 'set' patterns expanded.'''
23 '''Returns the kindpats list with the 'set' patterns expanded.'''
24 fset = set()
24 fset = set()
25 other = []
25 other = []
26
26
27 for kind, pat in kindpats:
27 for kind, pat in kindpats:
28 if kind == 'set':
28 if kind == 'set':
29 if not ctx:
29 if not ctx:
30 raise util.Abort("fileset expression with no context")
30 raise util.Abort("fileset expression with no context")
31 s = ctx.getfileset(pat)
31 s = ctx.getfileset(pat)
32 fset.update(s)
32 fset.update(s)
33 continue
33 continue
34 other.append((kind, pat))
34 other.append((kind, pat))
35 return fset, other
35 return fset, other
36
36
37 class match(object):
37 class match(object):
38 def __init__(self, root, cwd, patterns, include=[], exclude=[],
38 def __init__(self, root, cwd, patterns, include=[], exclude=[],
39 default='glob', exact=False, auditor=None, ctx=None):
39 default='glob', exact=False, auditor=None, ctx=None):
40 """build an object to match a set of file patterns
40 """build an object to match a set of file patterns
41
41
42 arguments:
42 arguments:
43 root - the canonical root of the tree you're matching against
43 root - the canonical root of the tree you're matching against
44 cwd - the current working directory, if relevant
44 cwd - the current working directory, if relevant
45 patterns - patterns to find
45 patterns - patterns to find
46 include - patterns to include (unless they are excluded)
46 include - patterns to include (unless they are excluded)
47 exclude - patterns to exclude (even if they are included)
47 exclude - patterns to exclude (even if they are included)
48 default - if a pattern in patterns has no explicit type, assume this one
48 default - if a pattern in patterns has no explicit type, assume this one
49 exact - patterns are actually filenames (include/exclude still apply)
49 exact - patterns are actually filenames (include/exclude still apply)
50
50
51 a pattern is one of:
51 a pattern is one of:
52 'glob:<glob>' - a glob relative to cwd
52 'glob:<glob>' - a glob relative to cwd
53 're:<regexp>' - a regular expression
53 're:<regexp>' - a regular expression
54 'path:<path>' - a path relative to repository root
54 'path:<path>' - a path relative to repository root
55 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
55 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
56 'relpath:<path>' - a path relative to cwd
56 'relpath:<path>' - a path relative to cwd
57 'relre:<regexp>' - a regexp that needn't match the start of a name
57 'relre:<regexp>' - a regexp that needn't match the start of a name
58 'set:<fileset>' - a fileset expression
58 'set:<fileset>' - a fileset expression
59 '<something>' - a pattern of the specified default type
59 '<something>' - a pattern of the specified default type
60 """
60 """
61
61
62 self._root = root
62 self._root = root
63 self._cwd = cwd
63 self._cwd = cwd
64 self._files = [] # exact files and roots of patterns
64 self._files = [] # exact files and roots of patterns
65 self._anypats = bool(include or exclude)
65 self._anypats = bool(include or exclude)
66 self._ctx = ctx
66 self._ctx = ctx
67 self._always = False
67 self._always = False
68
68
69 matchfns = []
69 if include:
70 if include:
70 kindpats = _normalize(include, 'glob', root, cwd, auditor)
71 kindpats = _normalize(include, 'glob', root, cwd, auditor)
71 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)')
72 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)')
73 matchfns.append(im)
72 if exclude:
74 if exclude:
73 kindpats = _normalize(exclude, 'glob', root, cwd, auditor)
75 kindpats = _normalize(exclude, 'glob', root, cwd, auditor)
74 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)')
76 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)')
77 matchfns.append(lambda f: not em(f))
75 if exact:
78 if exact:
76 if isinstance(patterns, list):
79 if isinstance(patterns, list):
77 self._files = patterns
80 self._files = patterns
78 else:
81 else:
79 self._files = list(patterns)
82 self._files = list(patterns)
80 pm = self.exact
83 matchfns.append(self.exact)
81 elif patterns:
84 elif patterns:
82 kindpats = _normalize(patterns, default, root, cwd, auditor)
85 kindpats = _normalize(patterns, default, root, cwd, auditor)
83 self._files = _roots(kindpats)
86 self._files = _roots(kindpats)
84 self._anypats = self._anypats or _anypats(kindpats)
87 self._anypats = self._anypats or _anypats(kindpats)
85 self.patternspat, pm = _buildmatch(ctx, kindpats, '$')
88 self.patternspat, pm = _buildmatch(ctx, kindpats, '$')
89 matchfns.append(pm)
86
90
87 if patterns or exact:
91 if not matchfns:
88 if include:
92 m = util.always
89 if exclude:
93 self._always = True
90 m = lambda f: im(f) and not em(f) and pm(f)
94 elif len(matchfns) == 1:
91 else:
95 m = matchfns[0]
92 m = lambda f: im(f) and pm(f)
93 else:
94 if exclude:
95 m = lambda f: not em(f) and pm(f)
96 else:
97 m = pm
98 else:
96 else:
99 if include:
97 def m(f):
100 if exclude:
98 for matchfn in matchfns:
101 m = lambda f: im(f) and not em(f)
99 if not matchfn(f):
102 else:
100 return False
103 m = im
101 return True
104 else:
105 if exclude:
106 m = lambda f: not em(f)
107 else:
108 m = lambda f: True
109 self._always = True
110
102
111 self.matchfn = m
103 self.matchfn = m
112 self._fmap = set(self._files)
104 self._fmap = set(self._files)
113
105
114 def __call__(self, fn):
106 def __call__(self, fn):
115 return self.matchfn(fn)
107 return self.matchfn(fn)
116 def __iter__(self):
108 def __iter__(self):
117 for f in self._files:
109 for f in self._files:
118 yield f
110 yield f
119
111
120 # Callbacks related to how the matcher is used by dirstate.walk.
112 # Callbacks related to how the matcher is used by dirstate.walk.
121 # Subscribers to these events must monkeypatch the matcher object.
113 # Subscribers to these events must monkeypatch the matcher object.
122 def bad(self, f, msg):
114 def bad(self, f, msg):
123 '''Callback from dirstate.walk for each explicit file that can't be
115 '''Callback from dirstate.walk for each explicit file that can't be
124 found/accessed, with an error message.'''
116 found/accessed, with an error message.'''
125 pass
117 pass
126
118
127 # If an explicitdir is set, it will be called when an explicitly listed
119 # If an explicitdir is set, it will be called when an explicitly listed
128 # directory is visited.
120 # directory is visited.
129 explicitdir = None
121 explicitdir = None
130
122
131 # If an traversedir is set, it will be called when a directory discovered
123 # If an traversedir is set, it will be called when a directory discovered
132 # by recursive traversal is visited.
124 # by recursive traversal is visited.
133 traversedir = None
125 traversedir = None
134
126
135 def rel(self, f):
127 def rel(self, f):
136 '''Convert repo path back to path that is relative to cwd of matcher.'''
128 '''Convert repo path back to path that is relative to cwd of matcher.'''
137 return util.pathto(self._root, self._cwd, f)
129 return util.pathto(self._root, self._cwd, f)
138
130
139 def files(self):
131 def files(self):
140 '''Explicitly listed files or patterns or roots:
132 '''Explicitly listed files or patterns or roots:
141 if no patterns or .always(): empty list,
133 if no patterns or .always(): empty list,
142 if exact: list exact files,
134 if exact: list exact files,
143 if not .anypats(): list all files and dirs,
135 if not .anypats(): list all files and dirs,
144 else: optimal roots'''
136 else: optimal roots'''
145 return self._files
137 return self._files
146
138
147 def exact(self, f):
139 def exact(self, f):
148 '''Returns True if f is in .files().'''
140 '''Returns True if f is in .files().'''
149 return f in self._fmap
141 return f in self._fmap
150
142
151 def anypats(self):
143 def anypats(self):
152 '''Matcher uses patterns or include/exclude.'''
144 '''Matcher uses patterns or include/exclude.'''
153 return self._anypats
145 return self._anypats
154
146
155 def always(self):
147 def always(self):
156 '''Matcher will match everything and .files() will be empty
148 '''Matcher will match everything and .files() will be empty
157 - optimization might be possible and necessary.'''
149 - optimization might be possible and necessary.'''
158 return self._always
150 return self._always
159
151
160 class exact(match):
152 class exact(match):
161 def __init__(self, root, cwd, files):
153 def __init__(self, root, cwd, files):
162 match.__init__(self, root, cwd, files, exact=True)
154 match.__init__(self, root, cwd, files, exact=True)
163
155
164 class always(match):
156 class always(match):
165 def __init__(self, root, cwd):
157 def __init__(self, root, cwd):
166 match.__init__(self, root, cwd, [])
158 match.__init__(self, root, cwd, [])
167 self._always = True
159 self._always = True
168
160
169 class narrowmatcher(match):
161 class narrowmatcher(match):
170 """Adapt a matcher to work on a subdirectory only.
162 """Adapt a matcher to work on a subdirectory only.
171
163
172 The paths are remapped to remove/insert the path as needed:
164 The paths are remapped to remove/insert the path as needed:
173
165
174 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
166 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
175 >>> m2 = narrowmatcher('sub', m1)
167 >>> m2 = narrowmatcher('sub', m1)
176 >>> bool(m2('a.txt'))
168 >>> bool(m2('a.txt'))
177 False
169 False
178 >>> bool(m2('b.txt'))
170 >>> bool(m2('b.txt'))
179 True
171 True
180 >>> bool(m2.matchfn('a.txt'))
172 >>> bool(m2.matchfn('a.txt'))
181 False
173 False
182 >>> bool(m2.matchfn('b.txt'))
174 >>> bool(m2.matchfn('b.txt'))
183 True
175 True
184 >>> m2.files()
176 >>> m2.files()
185 ['b.txt']
177 ['b.txt']
186 >>> m2.exact('b.txt')
178 >>> m2.exact('b.txt')
187 True
179 True
188 >>> m2.rel('b.txt')
180 >>> m2.rel('b.txt')
189 'b.txt'
181 'b.txt'
190 >>> def bad(f, msg):
182 >>> def bad(f, msg):
191 ... print "%s: %s" % (f, msg)
183 ... print "%s: %s" % (f, msg)
192 >>> m1.bad = bad
184 >>> m1.bad = bad
193 >>> m2.bad('x.txt', 'No such file')
185 >>> m2.bad('x.txt', 'No such file')
194 sub/x.txt: No such file
186 sub/x.txt: No such file
195 """
187 """
196
188
197 def __init__(self, path, matcher):
189 def __init__(self, path, matcher):
198 self._root = matcher._root
190 self._root = matcher._root
199 self._cwd = matcher._cwd
191 self._cwd = matcher._cwd
200 self._path = path
192 self._path = path
201 self._matcher = matcher
193 self._matcher = matcher
202 self._always = matcher._always
194 self._always = matcher._always
203
195
204 self._files = [f[len(path) + 1:] for f in matcher._files
196 self._files = [f[len(path) + 1:] for f in matcher._files
205 if f.startswith(path + "/")]
197 if f.startswith(path + "/")]
206 self._anypats = matcher._anypats
198 self._anypats = matcher._anypats
207 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
199 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
208 self._fmap = set(self._files)
200 self._fmap = set(self._files)
209
201
210 def bad(self, f, msg):
202 def bad(self, f, msg):
211 self._matcher.bad(self._path + "/" + f, msg)
203 self._matcher.bad(self._path + "/" + f, msg)
212
204
213 def patkind(pattern, default=None):
205 def patkind(pattern, default=None):
214 '''If pattern is 'kind:pat' with a known kind, return kind.'''
206 '''If pattern is 'kind:pat' with a known kind, return kind.'''
215 return _patsplit(pattern, default)[0]
207 return _patsplit(pattern, default)[0]
216
208
217 def _patsplit(pattern, default):
209 def _patsplit(pattern, default):
218 """Split a string into the optional pattern kind prefix and the actual
210 """Split a string into the optional pattern kind prefix and the actual
219 pattern."""
211 pattern."""
220 if ':' in pattern:
212 if ':' in pattern:
221 kind, pat = pattern.split(':', 1)
213 kind, pat = pattern.split(':', 1)
222 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
214 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
223 'listfile', 'listfile0', 'set'):
215 'listfile', 'listfile0', 'set'):
224 return kind, pat
216 return kind, pat
225 return default, pattern
217 return default, pattern
226
218
227 def _globre(pat):
219 def _globre(pat):
228 r'''Convert an extended glob string to a regexp string.
220 r'''Convert an extended glob string to a regexp string.
229
221
230 >>> print _globre(r'?')
222 >>> print _globre(r'?')
231 .
223 .
232 >>> print _globre(r'*')
224 >>> print _globre(r'*')
233 [^/]*
225 [^/]*
234 >>> print _globre(r'**')
226 >>> print _globre(r'**')
235 .*
227 .*
236 >>> print _globre(r'**/a')
228 >>> print _globre(r'**/a')
237 (?:.*/)?a
229 (?:.*/)?a
238 >>> print _globre(r'a/**/b')
230 >>> print _globre(r'a/**/b')
239 a\/(?:.*/)?b
231 a\/(?:.*/)?b
240 >>> print _globre(r'[a*?!^][^b][!c]')
232 >>> print _globre(r'[a*?!^][^b][!c]')
241 [a*?!^][\^b][^c]
233 [a*?!^][\^b][^c]
242 >>> print _globre(r'{a,b}')
234 >>> print _globre(r'{a,b}')
243 (?:a|b)
235 (?:a|b)
244 >>> print _globre(r'.\*\?')
236 >>> print _globre(r'.\*\?')
245 \.\*\?
237 \.\*\?
246 '''
238 '''
247 i, n = 0, len(pat)
239 i, n = 0, len(pat)
248 res = ''
240 res = ''
249 group = 0
241 group = 0
250 escape = util.re.escape
242 escape = util.re.escape
251 def peek():
243 def peek():
252 return i < n and pat[i]
244 return i < n and pat[i]
253 while i < n:
245 while i < n:
254 c = pat[i]
246 c = pat[i]
255 i += 1
247 i += 1
256 if c not in '*?[{},\\':
248 if c not in '*?[{},\\':
257 res += escape(c)
249 res += escape(c)
258 elif c == '*':
250 elif c == '*':
259 if peek() == '*':
251 if peek() == '*':
260 i += 1
252 i += 1
261 if peek() == '/':
253 if peek() == '/':
262 i += 1
254 i += 1
263 res += '(?:.*/)?'
255 res += '(?:.*/)?'
264 else:
256 else:
265 res += '.*'
257 res += '.*'
266 else:
258 else:
267 res += '[^/]*'
259 res += '[^/]*'
268 elif c == '?':
260 elif c == '?':
269 res += '.'
261 res += '.'
270 elif c == '[':
262 elif c == '[':
271 j = i
263 j = i
272 if j < n and pat[j] in '!]':
264 if j < n and pat[j] in '!]':
273 j += 1
265 j += 1
274 while j < n and pat[j] != ']':
266 while j < n and pat[j] != ']':
275 j += 1
267 j += 1
276 if j >= n:
268 if j >= n:
277 res += '\\['
269 res += '\\['
278 else:
270 else:
279 stuff = pat[i:j].replace('\\','\\\\')
271 stuff = pat[i:j].replace('\\','\\\\')
280 i = j + 1
272 i = j + 1
281 if stuff[0] == '!':
273 if stuff[0] == '!':
282 stuff = '^' + stuff[1:]
274 stuff = '^' + stuff[1:]
283 elif stuff[0] == '^':
275 elif stuff[0] == '^':
284 stuff = '\\' + stuff
276 stuff = '\\' + stuff
285 res = '%s[%s]' % (res, stuff)
277 res = '%s[%s]' % (res, stuff)
286 elif c == '{':
278 elif c == '{':
287 group += 1
279 group += 1
288 res += '(?:'
280 res += '(?:'
289 elif c == '}' and group:
281 elif c == '}' and group:
290 res += ')'
282 res += ')'
291 group -= 1
283 group -= 1
292 elif c == ',' and group:
284 elif c == ',' and group:
293 res += '|'
285 res += '|'
294 elif c == '\\':
286 elif c == '\\':
295 p = peek()
287 p = peek()
296 if p:
288 if p:
297 i += 1
289 i += 1
298 res += escape(p)
290 res += escape(p)
299 else:
291 else:
300 res += escape(c)
292 res += escape(c)
301 else:
293 else:
302 res += escape(c)
294 res += escape(c)
303 return res
295 return res
304
296
305 def _regex(kind, pat, globsuffix):
297 def _regex(kind, pat, globsuffix):
306 '''Convert a (normalized) pattern of any kind into a regular expression.
298 '''Convert a (normalized) pattern of any kind into a regular expression.
307 globsuffix is appended to the regexp of globs.'''
299 globsuffix is appended to the regexp of globs.'''
308 if not pat:
300 if not pat:
309 return ''
301 return ''
310 if kind == 're':
302 if kind == 're':
311 return pat
303 return pat
312 if kind == 'path':
304 if kind == 'path':
313 return '^' + util.re.escape(pat) + '(?:/|$)'
305 return '^' + util.re.escape(pat) + '(?:/|$)'
314 if kind == 'relglob':
306 if kind == 'relglob':
315 return '(?:|.*/)' + _globre(pat) + globsuffix
307 return '(?:|.*/)' + _globre(pat) + globsuffix
316 if kind == 'relpath':
308 if kind == 'relpath':
317 return util.re.escape(pat) + '(?:/|$)'
309 return util.re.escape(pat) + '(?:/|$)'
318 if kind == 'relre':
310 if kind == 'relre':
319 if pat.startswith('^'):
311 if pat.startswith('^'):
320 return pat
312 return pat
321 return '.*' + pat
313 return '.*' + pat
322 return _globre(pat) + globsuffix
314 return _globre(pat) + globsuffix
323
315
324 def _buildmatch(ctx, kindpats, globsuffix):
316 def _buildmatch(ctx, kindpats, globsuffix):
325 '''Return regexp string and a matcher function for kindpats.
317 '''Return regexp string and a matcher function for kindpats.
326 globsuffix is appended to the regexp of globs.'''
318 globsuffix is appended to the regexp of globs.'''
327 fset, kindpats = _expandsets(kindpats, ctx)
319 fset, kindpats = _expandsets(kindpats, ctx)
328 if not kindpats:
320 if not kindpats:
329 return "", fset.__contains__
321 return "", fset.__contains__
330
322
331 regex, mf = _buildregexmatch(kindpats, globsuffix)
323 regex, mf = _buildregexmatch(kindpats, globsuffix)
332 if fset:
324 if fset:
333 return regex, lambda f: f in fset or mf(f)
325 return regex, lambda f: f in fset or mf(f)
334 return regex, mf
326 return regex, mf
335
327
336 def _buildregexmatch(kindpats, globsuffix):
328 def _buildregexmatch(kindpats, globsuffix):
337 """Build a match function from a list of kinds and kindpats,
329 """Build a match function from a list of kinds and kindpats,
338 return regexp string and a matcher function."""
330 return regexp string and a matcher function."""
339 try:
331 try:
340 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
332 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
341 for (k, p) in kindpats])
333 for (k, p) in kindpats])
342 if len(regex) > 20000:
334 if len(regex) > 20000:
343 raise OverflowError
335 raise OverflowError
344 return regex, _rematcher(regex)
336 return regex, _rematcher(regex)
345 except OverflowError:
337 except OverflowError:
346 # We're using a Python with a tiny regex engine and we
338 # We're using a Python with a tiny regex engine and we
347 # made it explode, so we'll divide the pattern list in two
339 # made it explode, so we'll divide the pattern list in two
348 # until it works
340 # until it works
349 l = len(kindpats)
341 l = len(kindpats)
350 if l < 2:
342 if l < 2:
351 raise
343 raise
352 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
344 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
353 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
345 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
354 return regex, lambda s: a(s) or b(s)
346 return regex, lambda s: a(s) or b(s)
355 except re.error:
347 except re.error:
356 for k, p in kindpats:
348 for k, p in kindpats:
357 try:
349 try:
358 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
350 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
359 except re.error:
351 except re.error:
360 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
352 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
361 raise util.Abort(_("invalid pattern"))
353 raise util.Abort(_("invalid pattern"))
362
354
363 def _normalize(patterns, default, root, cwd, auditor):
355 def _normalize(patterns, default, root, cwd, auditor):
364 '''Convert 'kind:pat' from the patterns list to tuples with kind and
356 '''Convert 'kind:pat' from the patterns list to tuples with kind and
365 normalized and rooted patterns and with listfiles expanded.'''
357 normalized and rooted patterns and with listfiles expanded.'''
366 kindpats = []
358 kindpats = []
367 for kind, pat in [_patsplit(p, default) for p in patterns]:
359 for kind, pat in [_patsplit(p, default) for p in patterns]:
368 if kind in ('glob', 'relpath'):
360 if kind in ('glob', 'relpath'):
369 pat = pathutil.canonpath(root, cwd, pat, auditor)
361 pat = pathutil.canonpath(root, cwd, pat, auditor)
370 elif kind in ('relglob', 'path'):
362 elif kind in ('relglob', 'path'):
371 pat = util.normpath(pat)
363 pat = util.normpath(pat)
372 elif kind in ('listfile', 'listfile0'):
364 elif kind in ('listfile', 'listfile0'):
373 try:
365 try:
374 files = util.readfile(pat)
366 files = util.readfile(pat)
375 if kind == 'listfile0':
367 if kind == 'listfile0':
376 files = files.split('\0')
368 files = files.split('\0')
377 else:
369 else:
378 files = files.splitlines()
370 files = files.splitlines()
379 files = [f for f in files if f]
371 files = [f for f in files if f]
380 except EnvironmentError:
372 except EnvironmentError:
381 raise util.Abort(_("unable to read file list (%s)") % pat)
373 raise util.Abort(_("unable to read file list (%s)") % pat)
382 kindpats += _normalize(files, default, root, cwd, auditor)
374 kindpats += _normalize(files, default, root, cwd, auditor)
383 continue
375 continue
384 # else: re or relre - which cannot be normalized
376 # else: re or relre - which cannot be normalized
385 kindpats.append((kind, pat))
377 kindpats.append((kind, pat))
386 return kindpats
378 return kindpats
387
379
388 def _roots(kindpats):
380 def _roots(kindpats):
389 '''return roots and exact explicitly listed files from patterns
381 '''return roots and exact explicitly listed files from patterns
390
382
391 >>> _roots([('glob', 'g/*'), ('glob', 'g'), ('glob', 'g*')])
383 >>> _roots([('glob', 'g/*'), ('glob', 'g'), ('glob', 'g*')])
392 ['g', 'g', '.']
384 ['g', 'g', '.']
393 >>> _roots([('relpath', 'r'), ('path', 'p/p'), ('path', '')])
385 >>> _roots([('relpath', 'r'), ('path', 'p/p'), ('path', '')])
394 ['r', 'p/p', '.']
386 ['r', 'p/p', '.']
395 >>> _roots([('relglob', 'rg*'), ('re', 're/'), ('relre', 'rr')])
387 >>> _roots([('relglob', 'rg*'), ('re', 're/'), ('relre', 'rr')])
396 ['.', '.', '.']
388 ['.', '.', '.']
397 '''
389 '''
398 r = []
390 r = []
399 for kind, pat in kindpats:
391 for kind, pat in kindpats:
400 if kind == 'glob': # find the non-glob prefix
392 if kind == 'glob': # find the non-glob prefix
401 root = []
393 root = []
402 for p in pat.split('/'):
394 for p in pat.split('/'):
403 if '[' in p or '{' in p or '*' in p or '?' in p:
395 if '[' in p or '{' in p or '*' in p or '?' in p:
404 break
396 break
405 root.append(p)
397 root.append(p)
406 r.append('/'.join(root) or '.')
398 r.append('/'.join(root) or '.')
407 elif kind in ('relpath', 'path'):
399 elif kind in ('relpath', 'path'):
408 r.append(pat or '.')
400 r.append(pat or '.')
409 else: # relglob, re, relre
401 else: # relglob, re, relre
410 r.append('.')
402 r.append('.')
411 return r
403 return r
412
404
413 def _anypats(kindpats):
405 def _anypats(kindpats):
414 for kind, pat in kindpats:
406 for kind, pat in kindpats:
415 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
407 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
416 return True
408 return True
General Comments 0
You need to be logged in to leave comments. Login now