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