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