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