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