##// END OF EJS Templates
match: extract base class for matchers...
Martin von Zweigbergk -
r32454:a04bc552 default
parent child Browse files
Show More
@@ -1,800 +1,819
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy
10 import copy
11 import os
11 import os
12 import re
12 import re
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 error,
16 error,
17 pathutil,
17 pathutil,
18 util,
18 util,
19 )
19 )
20
20
21 propertycache = util.propertycache
21 propertycache = util.propertycache
22
22
23 def _rematcher(regex):
23 def _rematcher(regex):
24 '''compile the regexp with the best available regexp engine and return a
24 '''compile the regexp with the best available regexp engine and return a
25 matcher function'''
25 matcher function'''
26 m = util.re.compile(regex)
26 m = util.re.compile(regex)
27 try:
27 try:
28 # slightly faster, provided by facebook's re2 bindings
28 # slightly faster, provided by facebook's re2 bindings
29 return m.test_match
29 return m.test_match
30 except AttributeError:
30 except AttributeError:
31 return m.match
31 return m.match
32
32
33 def _expandsets(kindpats, ctx, listsubrepos):
33 def _expandsets(kindpats, ctx, listsubrepos):
34 '''Returns the kindpats list with the 'set' patterns expanded.'''
34 '''Returns the kindpats list with the 'set' patterns expanded.'''
35 fset = set()
35 fset = set()
36 other = []
36 other = []
37
37
38 for kind, pat, source in kindpats:
38 for kind, pat, source in kindpats:
39 if kind == 'set':
39 if kind == 'set':
40 if not ctx:
40 if not ctx:
41 raise error.ProgrammingError("fileset expression with no "
41 raise error.ProgrammingError("fileset expression with no "
42 "context")
42 "context")
43 s = ctx.getfileset(pat)
43 s = ctx.getfileset(pat)
44 fset.update(s)
44 fset.update(s)
45
45
46 if listsubrepos:
46 if listsubrepos:
47 for subpath in ctx.substate:
47 for subpath in ctx.substate:
48 s = ctx.sub(subpath).getfileset(pat)
48 s = ctx.sub(subpath).getfileset(pat)
49 fset.update(subpath + '/' + f for f in s)
49 fset.update(subpath + '/' + f for f in s)
50
50
51 continue
51 continue
52 other.append((kind, pat, source))
52 other.append((kind, pat, source))
53 return fset, other
53 return fset, other
54
54
55 def _expandsubinclude(kindpats, root):
55 def _expandsubinclude(kindpats, root):
56 '''Returns the list of subinclude matcher args and the kindpats without the
56 '''Returns the list of subinclude matcher args and the kindpats without the
57 subincludes in it.'''
57 subincludes in it.'''
58 relmatchers = []
58 relmatchers = []
59 other = []
59 other = []
60
60
61 for kind, pat, source in kindpats:
61 for kind, pat, source in kindpats:
62 if kind == 'subinclude':
62 if kind == 'subinclude':
63 sourceroot = pathutil.dirname(util.normpath(source))
63 sourceroot = pathutil.dirname(util.normpath(source))
64 pat = util.pconvert(pat)
64 pat = util.pconvert(pat)
65 path = pathutil.join(sourceroot, pat)
65 path = pathutil.join(sourceroot, pat)
66
66
67 newroot = pathutil.dirname(path)
67 newroot = pathutil.dirname(path)
68 matcherargs = (newroot, '', [], ['include:%s' % path])
68 matcherargs = (newroot, '', [], ['include:%s' % path])
69
69
70 prefix = pathutil.canonpath(root, root, newroot)
70 prefix = pathutil.canonpath(root, root, newroot)
71 if prefix:
71 if prefix:
72 prefix += '/'
72 prefix += '/'
73 relmatchers.append((prefix, matcherargs))
73 relmatchers.append((prefix, matcherargs))
74 else:
74 else:
75 other.append((kind, pat, source))
75 other.append((kind, pat, source))
76
76
77 return relmatchers, other
77 return relmatchers, other
78
78
79 def _kindpatsalwaysmatch(kindpats):
79 def _kindpatsalwaysmatch(kindpats):
80 """"Checks whether the kindspats match everything, as e.g.
80 """"Checks whether the kindspats match everything, as e.g.
81 'relpath:.' does.
81 'relpath:.' does.
82 """
82 """
83 for kind, pat, source in kindpats:
83 for kind, pat, source in kindpats:
84 if pat != '' or kind not in ['relpath', 'glob']:
84 if pat != '' or kind not in ['relpath', 'glob']:
85 return False
85 return False
86 return True
86 return True
87
87
88 def match(root, cwd, patterns, include=None, exclude=None, default='glob',
88 def match(root, cwd, patterns, include=None, exclude=None, default='glob',
89 exact=False, auditor=None, ctx=None, listsubrepos=False, warn=None,
89 exact=False, auditor=None, ctx=None, listsubrepos=False, warn=None,
90 badfn=None, icasefs=False):
90 badfn=None, icasefs=False):
91 """build an object to match a set of file patterns
91 """build an object to match a set of file patterns
92
92
93 arguments:
93 arguments:
94 root - the canonical root of the tree you're matching against
94 root - the canonical root of the tree you're matching against
95 cwd - the current working directory, if relevant
95 cwd - the current working directory, if relevant
96 patterns - patterns to find
96 patterns - patterns to find
97 include - patterns to include (unless they are excluded)
97 include - patterns to include (unless they are excluded)
98 exclude - patterns to exclude (even if they are included)
98 exclude - patterns to exclude (even if they are included)
99 default - if a pattern in patterns has no explicit type, assume this one
99 default - if a pattern in patterns has no explicit type, assume this one
100 exact - patterns are actually filenames (include/exclude still apply)
100 exact - patterns are actually filenames (include/exclude still apply)
101 warn - optional function used for printing warnings
101 warn - optional function used for printing warnings
102 badfn - optional bad() callback for this matcher instead of the default
102 badfn - optional bad() callback for this matcher instead of the default
103 icasefs - make a matcher for wdir on case insensitive filesystems, which
103 icasefs - make a matcher for wdir on case insensitive filesystems, which
104 normalizes the given patterns to the case in the filesystem
104 normalizes the given patterns to the case in the filesystem
105
105
106 a pattern is one of:
106 a pattern is one of:
107 'glob:<glob>' - a glob relative to cwd
107 'glob:<glob>' - a glob relative to cwd
108 're:<regexp>' - a regular expression
108 're:<regexp>' - a regular expression
109 'path:<path>' - a path relative to repository root, which is matched
109 'path:<path>' - a path relative to repository root, which is matched
110 recursively
110 recursively
111 'rootfilesin:<path>' - a path relative to repository root, which is
111 'rootfilesin:<path>' - a path relative to repository root, which is
112 matched non-recursively (will not match subdirectories)
112 matched non-recursively (will not match subdirectories)
113 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
113 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
114 'relpath:<path>' - a path relative to cwd
114 'relpath:<path>' - a path relative to cwd
115 'relre:<regexp>' - a regexp that needn't match the start of a name
115 'relre:<regexp>' - a regexp that needn't match the start of a name
116 'set:<fileset>' - a fileset expression
116 'set:<fileset>' - a fileset expression
117 'include:<path>' - a file of patterns to read and include
117 'include:<path>' - a file of patterns to read and include
118 'subinclude:<path>' - a file of patterns to match against files under
118 'subinclude:<path>' - a file of patterns to match against files under
119 the same directory
119 the same directory
120 '<something>' - a pattern of the specified default type
120 '<something>' - a pattern of the specified default type
121 """
121 """
122 normalize = _donormalize
122 normalize = _donormalize
123 if icasefs:
123 if icasefs:
124 if exact:
124 if exact:
125 raise error.ProgrammingError("a case-insensitive exact matcher "
125 raise error.ProgrammingError("a case-insensitive exact matcher "
126 "doesn't make sense")
126 "doesn't make sense")
127 dirstate = ctx.repo().dirstate
127 dirstate = ctx.repo().dirstate
128 dsnormalize = dirstate.normalize
128 dsnormalize = dirstate.normalize
129
129
130 def normalize(patterns, default, root, cwd, auditor, warn):
130 def normalize(patterns, default, root, cwd, auditor, warn):
131 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
131 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
132 kindpats = []
132 kindpats = []
133 for kind, pats, source in kp:
133 for kind, pats, source in kp:
134 if kind not in ('re', 'relre'): # regex can't be normalized
134 if kind not in ('re', 'relre'): # regex can't be normalized
135 p = pats
135 p = pats
136 pats = dsnormalize(pats)
136 pats = dsnormalize(pats)
137
137
138 # Preserve the original to handle a case only rename.
138 # Preserve the original to handle a case only rename.
139 if p != pats and p in dirstate:
139 if p != pats and p in dirstate:
140 kindpats.append((kind, p, source))
140 kindpats.append((kind, p, source))
141
141
142 kindpats.append((kind, pats, source))
142 kindpats.append((kind, pats, source))
143 return kindpats
143 return kindpats
144
144
145 return matcher(root, cwd, normalize, patterns, include=include,
145 return matcher(root, cwd, normalize, patterns, include=include,
146 exclude=exclude, default=default, exact=exact,
146 exclude=exclude, default=default, exact=exact,
147 auditor=auditor, ctx=ctx, listsubrepos=listsubrepos,
147 auditor=auditor, ctx=ctx, listsubrepos=listsubrepos,
148 warn=warn, badfn=badfn)
148 warn=warn, badfn=badfn)
149
149
150 def exact(root, cwd, files, badfn=None):
150 def exact(root, cwd, files, badfn=None):
151 return match(root, cwd, files, exact=True, badfn=badfn)
151 return match(root, cwd, files, exact=True, badfn=badfn)
152
152
153 def always(root, cwd):
153 def always(root, cwd):
154 return match(root, cwd, [])
154 return match(root, cwd, [])
155
155
156 def badmatch(match, badfn):
156 def badmatch(match, badfn):
157 """Make a copy of the given matcher, replacing its bad method with the given
157 """Make a copy of the given matcher, replacing its bad method with the given
158 one.
158 one.
159 """
159 """
160 m = copy.copy(match)
160 m = copy.copy(match)
161 m.bad = badfn
161 m.bad = badfn
162 return m
162 return m
163
163
164 def _donormalize(patterns, default, root, cwd, auditor, warn):
164 def _donormalize(patterns, default, root, cwd, auditor, warn):
165 '''Convert 'kind:pat' from the patterns list to tuples with kind and
165 '''Convert 'kind:pat' from the patterns list to tuples with kind and
166 normalized and rooted patterns and with listfiles expanded.'''
166 normalized and rooted patterns and with listfiles expanded.'''
167 kindpats = []
167 kindpats = []
168 for kind, pat in [_patsplit(p, default) for p in patterns]:
168 for kind, pat in [_patsplit(p, default) for p in patterns]:
169 if kind in ('glob', 'relpath'):
169 if kind in ('glob', 'relpath'):
170 pat = pathutil.canonpath(root, cwd, pat, auditor)
170 pat = pathutil.canonpath(root, cwd, pat, auditor)
171 elif kind in ('relglob', 'path', 'rootfilesin'):
171 elif kind in ('relglob', 'path', 'rootfilesin'):
172 pat = util.normpath(pat)
172 pat = util.normpath(pat)
173 elif kind in ('listfile', 'listfile0'):
173 elif kind in ('listfile', 'listfile0'):
174 try:
174 try:
175 files = util.readfile(pat)
175 files = util.readfile(pat)
176 if kind == 'listfile0':
176 if kind == 'listfile0':
177 files = files.split('\0')
177 files = files.split('\0')
178 else:
178 else:
179 files = files.splitlines()
179 files = files.splitlines()
180 files = [f for f in files if f]
180 files = [f for f in files if f]
181 except EnvironmentError:
181 except EnvironmentError:
182 raise error.Abort(_("unable to read file list (%s)") % pat)
182 raise error.Abort(_("unable to read file list (%s)") % pat)
183 for k, p, source in _donormalize(files, default, root, cwd,
183 for k, p, source in _donormalize(files, default, root, cwd,
184 auditor, warn):
184 auditor, warn):
185 kindpats.append((k, p, pat))
185 kindpats.append((k, p, pat))
186 continue
186 continue
187 elif kind == 'include':
187 elif kind == 'include':
188 try:
188 try:
189 fullpath = os.path.join(root, util.localpath(pat))
189 fullpath = os.path.join(root, util.localpath(pat))
190 includepats = readpatternfile(fullpath, warn)
190 includepats = readpatternfile(fullpath, warn)
191 for k, p, source in _donormalize(includepats, default,
191 for k, p, source in _donormalize(includepats, default,
192 root, cwd, auditor, warn):
192 root, cwd, auditor, warn):
193 kindpats.append((k, p, source or pat))
193 kindpats.append((k, p, source or pat))
194 except error.Abort as inst:
194 except error.Abort as inst:
195 raise error.Abort('%s: %s' % (pat, inst[0]))
195 raise error.Abort('%s: %s' % (pat, inst[0]))
196 except IOError as inst:
196 except IOError as inst:
197 if warn:
197 if warn:
198 warn(_("skipping unreadable pattern file '%s': %s\n") %
198 warn(_("skipping unreadable pattern file '%s': %s\n") %
199 (pat, inst.strerror))
199 (pat, inst.strerror))
200 continue
200 continue
201 # else: re or relre - which cannot be normalized
201 # else: re or relre - which cannot be normalized
202 kindpats.append((kind, pat, ''))
202 kindpats.append((kind, pat, ''))
203 return kindpats
203 return kindpats
204
204
205 class matcher(object):
205 class basematcher(object):
206
207 def __init__(self, root, cwd, badfn=None):
208 self._root = root
209 self._cwd = cwd
210 if badfn is not None:
211 self.bad = badfn
212 self._files = [] # exact files and roots of patterns
213 self.matchfn = lambda f: False
214
215 def __call__(self, fn):
216 return self.matchfn(fn)
217 def __iter__(self):
218 for f in self._files:
219 yield f
220 # Callbacks related to how the matcher is used by dirstate.walk.
221 # Subscribers to these events must monkeypatch the matcher object.
222 def bad(self, f, msg):
223 '''Callback from dirstate.walk for each explicit file that can't be
224 found/accessed, with an error message.'''
225 pass
226
227 # If an explicitdir is set, it will be called when an explicitly listed
228 # directory is visited.
229 explicitdir = None
230
231 # If an traversedir is set, it will be called when a directory discovered
232 # by recursive traversal is visited.
233 traversedir = None
234
235 def abs(self, f):
236 '''Convert a repo path back to path that is relative to the root of the
237 matcher.'''
238 return f
239
240 def rel(self, f):
241 '''Convert repo path back to path that is relative to cwd of matcher.'''
242 return util.pathto(self._root, self._cwd, f)
243
244 def uipath(self, f):
245 '''Convert repo path to a display path. If patterns or -I/-X were used
246 to create this matcher, the display path will be relative to cwd.
247 Otherwise it is relative to the root of the repo.'''
248 return self.rel(f)
249
250 def files(self):
251 '''Explicitly listed files or patterns or roots:
252 if no patterns or .always(): empty list,
253 if exact: list exact files,
254 if not .anypats(): list all files and dirs,
255 else: optimal roots'''
256 return self._files
257
258 @propertycache
259 def _fileset(self):
260 return set(self._files)
261
262 def exact(self, f):
263 '''Returns True if f is in .files().'''
264 return f in self._fileset
265
266 def visitdir(self, dir):
267 '''Decides whether a directory should be visited based on whether it
268 has potential matches in it or one of its subdirectories. This is
269 based on the match's primary, included, and excluded patterns.
270
271 Returns the string 'all' if the given directory and all subdirectories
272 should be visited. Otherwise returns True or False indicating whether
273 the given directory should be visited.
274
275 This function's behavior is undefined if it has returned False for
276 one of the dir's parent directories.
277 '''
278 return False
279
280 def anypats(self):
281 '''Matcher uses patterns or include/exclude.'''
282 return False
283
284 def always(self):
285 '''Matcher will match everything and .files() will be empty
286 - optimization might be possible and necessary.'''
287 return False
288
289 def isexact(self):
290 return False
291
292 def prefix(self):
293 return not self.always() and not self.isexact() and not self.anypats()
294
295 class matcher(basematcher):
206
296
207 def __init__(self, root, cwd, normalize, patterns, include=None,
297 def __init__(self, root, cwd, normalize, patterns, include=None,
208 exclude=None, default='glob', exact=False, auditor=None,
298 exclude=None, default='glob', exact=False, auditor=None,
209 ctx=None, listsubrepos=False, warn=None, badfn=None):
299 ctx=None, listsubrepos=False, warn=None, badfn=None):
300 super(matcher, self).__init__(root, cwd, badfn)
210 if include is None:
301 if include is None:
211 include = []
302 include = []
212 if exclude is None:
303 if exclude is None:
213 exclude = []
304 exclude = []
214
305
215 self._root = root
216 self._cwd = cwd
217 self._files = [] # exact files and roots of patterns
218 self._anypats = bool(include or exclude)
306 self._anypats = bool(include or exclude)
219 self._always = False
307 self._always = False
220 self._pathrestricted = bool(include or exclude or patterns)
308 self._pathrestricted = bool(include or exclude or patterns)
221 self.patternspat = None
309 self.patternspat = None
222 self.includepat = None
310 self.includepat = None
223 self.excludepat = None
311 self.excludepat = None
224
312
225 # roots are directories which are recursively included/excluded.
313 # roots are directories which are recursively included/excluded.
226 self._includeroots = set()
314 self._includeroots = set()
227 self._excluderoots = set()
315 self._excluderoots = set()
228 # dirs are directories which are non-recursively included.
316 # dirs are directories which are non-recursively included.
229 self._includedirs = set()
317 self._includedirs = set()
230
318
231 if badfn is not None:
232 self.bad = badfn
233
234 matchfns = []
319 matchfns = []
235 if include:
320 if include:
236 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
321 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
237 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
322 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
238 listsubrepos, root)
323 listsubrepos, root)
239 roots, dirs = _rootsanddirs(kindpats)
324 roots, dirs = _rootsanddirs(kindpats)
240 self._includeroots.update(roots)
325 self._includeroots.update(roots)
241 self._includedirs.update(dirs)
326 self._includedirs.update(dirs)
242 matchfns.append(im)
327 matchfns.append(im)
243 if exclude:
328 if exclude:
244 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
329 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
245 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
330 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
246 listsubrepos, root)
331 listsubrepos, root)
247 if not _anypats(kindpats):
332 if not _anypats(kindpats):
248 # Only consider recursive excludes as such - if a non-recursive
333 # Only consider recursive excludes as such - if a non-recursive
249 # exclude is used, we must still recurse into the excluded
334 # exclude is used, we must still recurse into the excluded
250 # directory, at least to find subdirectories. In such a case,
335 # directory, at least to find subdirectories. In such a case,
251 # the regex still won't match the non-recursively-excluded
336 # the regex still won't match the non-recursively-excluded
252 # files.
337 # files.
253 self._excluderoots.update(_roots(kindpats))
338 self._excluderoots.update(_roots(kindpats))
254 matchfns.append(lambda f: not em(f))
339 matchfns.append(lambda f: not em(f))
255 if exact:
340 if exact:
256 if isinstance(patterns, list):
341 if isinstance(patterns, list):
257 self._files = patterns
342 self._files = patterns
258 else:
343 else:
259 self._files = list(patterns)
344 self._files = list(patterns)
260 matchfns.append(self.exact)
345 matchfns.append(self.exact)
261 elif patterns:
346 elif patterns:
262 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
347 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
263 if not _kindpatsalwaysmatch(kindpats):
348 if not _kindpatsalwaysmatch(kindpats):
264 self._files = _explicitfiles(kindpats)
349 self._files = _explicitfiles(kindpats)
265 self._anypats = self._anypats or _anypats(kindpats)
350 self._anypats = self._anypats or _anypats(kindpats)
266 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
351 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
267 listsubrepos, root)
352 listsubrepos, root)
268 matchfns.append(pm)
353 matchfns.append(pm)
269
354
270 if not matchfns:
355 if not matchfns:
271 m = util.always
356 m = util.always
272 self._always = True
357 self._always = True
273 elif len(matchfns) == 1:
358 elif len(matchfns) == 1:
274 m = matchfns[0]
359 m = matchfns[0]
275 else:
360 else:
276 def m(f):
361 def m(f):
277 for matchfn in matchfns:
362 for matchfn in matchfns:
278 if not matchfn(f):
363 if not matchfn(f):
279 return False
364 return False
280 return True
365 return True
281
366
282 self.matchfn = m
367 self.matchfn = m
283
368
284 def __call__(self, fn):
285 return self.matchfn(fn)
286 def __iter__(self):
287 for f in self._files:
288 yield f
289
290 # Callbacks related to how the matcher is used by dirstate.walk.
291 # Subscribers to these events must monkeypatch the matcher object.
292 def bad(self, f, msg):
293 '''Callback from dirstate.walk for each explicit file that can't be
294 found/accessed, with an error message.'''
295 pass
296
297 # If an explicitdir is set, it will be called when an explicitly listed
298 # directory is visited.
299 explicitdir = None
300
301 # If an traversedir is set, it will be called when a directory discovered
302 # by recursive traversal is visited.
303 traversedir = None
304
305 def abs(self, f):
306 '''Convert a repo path back to path that is relative to the root of the
307 matcher.'''
308 return f
309
310 def rel(self, f):
311 '''Convert repo path back to path that is relative to cwd of matcher.'''
312 return util.pathto(self._root, self._cwd, f)
313
314 def uipath(self, f):
369 def uipath(self, f):
315 '''Convert repo path to a display path. If patterns or -I/-X were used
316 to create this matcher, the display path will be relative to cwd.
317 Otherwise it is relative to the root of the repo.'''
318 return (self._pathrestricted and self.rel(f)) or self.abs(f)
370 return (self._pathrestricted and self.rel(f)) or self.abs(f)
319
371
320 def files(self):
321 '''Explicitly listed files or patterns or roots:
322 if no patterns or .always(): empty list,
323 if exact: list exact files,
324 if not .anypats(): list all files and dirs,
325 else: optimal roots'''
326 return self._files
327
328 @propertycache
329 def _fileset(self):
330 return set(self._files)
331
332 @propertycache
372 @propertycache
333 def _dirs(self):
373 def _dirs(self):
334 return set(util.dirs(self._fileset)) | {'.'}
374 return set(util.dirs(self._fileset)) | {'.'}
335
375
336 def visitdir(self, dir):
376 def visitdir(self, dir):
337 '''Decides whether a directory should be visited based on whether it
338 has potential matches in it or one of its subdirectories. This is
339 based on the match's primary, included, and excluded patterns.
340
341 Returns the string 'all' if the given directory and all subdirectories
342 should be visited. Otherwise returns True or False indicating whether
343 the given directory should be visited.
344
345 This function's behavior is undefined if it has returned False for
346 one of the dir's parent directories.
347 '''
348 if self.prefix() and dir in self._fileset:
377 if self.prefix() and dir in self._fileset:
349 return 'all'
378 return 'all'
350 if dir in self._excluderoots:
379 if dir in self._excluderoots:
351 return False
380 return False
352 if ((self._includeroots or self._includedirs) and
381 if ((self._includeroots or self._includedirs) and
353 '.' not in self._includeroots and
382 '.' not in self._includeroots and
354 dir not in self._includeroots and
383 dir not in self._includeroots and
355 dir not in self._includedirs and
384 dir not in self._includedirs and
356 not any(parent in self._includeroots
385 not any(parent in self._includeroots
357 for parent in util.finddirs(dir))):
386 for parent in util.finddirs(dir))):
358 return False
387 return False
359 return (not self._fileset or
388 return (not self._fileset or
360 '.' in self._fileset or
389 '.' in self._fileset or
361 dir in self._fileset or
390 dir in self._fileset or
362 dir in self._dirs or
391 dir in self._dirs or
363 any(parentdir in self._fileset
392 any(parentdir in self._fileset
364 for parentdir in util.finddirs(dir)))
393 for parentdir in util.finddirs(dir)))
365
394
366 def exact(self, f):
367 '''Returns True if f is in .files().'''
368 return f in self._fileset
369
370 def anypats(self):
395 def anypats(self):
371 '''Matcher uses patterns or include/exclude.'''
372 return self._anypats
396 return self._anypats
373
397
374 def always(self):
398 def always(self):
375 '''Matcher will match everything and .files() will be empty
376 - optimization might be possible and necessary.'''
377 return self._always
399 return self._always
378
400
379 def isexact(self):
401 def isexact(self):
380 return self.matchfn == self.exact
402 return self.matchfn == self.exact
381
403
382 def prefix(self):
383 return not self.always() and not self.isexact() and not self.anypats()
384
385 def __repr__(self):
404 def __repr__(self):
386 return ('<matcher files=%r, patterns=%r, includes=%r, excludes=%r>' %
405 return ('<matcher files=%r, patterns=%r, includes=%r, excludes=%r>' %
387 (self._files, self.patternspat, self.includepat,
406 (self._files, self.patternspat, self.includepat,
388 self.excludepat))
407 self.excludepat))
389
408
390 class subdirmatcher(matcher):
409 class subdirmatcher(matcher):
391 """Adapt a matcher to work on a subdirectory only.
410 """Adapt a matcher to work on a subdirectory only.
392
411
393 The paths are remapped to remove/insert the path as needed:
412 The paths are remapped to remove/insert the path as needed:
394
413
395 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
414 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
396 >>> m2 = subdirmatcher('sub', m1)
415 >>> m2 = subdirmatcher('sub', m1)
397 >>> bool(m2('a.txt'))
416 >>> bool(m2('a.txt'))
398 False
417 False
399 >>> bool(m2('b.txt'))
418 >>> bool(m2('b.txt'))
400 True
419 True
401 >>> bool(m2.matchfn('a.txt'))
420 >>> bool(m2.matchfn('a.txt'))
402 False
421 False
403 >>> bool(m2.matchfn('b.txt'))
422 >>> bool(m2.matchfn('b.txt'))
404 True
423 True
405 >>> m2.files()
424 >>> m2.files()
406 ['b.txt']
425 ['b.txt']
407 >>> m2.exact('b.txt')
426 >>> m2.exact('b.txt')
408 True
427 True
409 >>> util.pconvert(m2.rel('b.txt'))
428 >>> util.pconvert(m2.rel('b.txt'))
410 'sub/b.txt'
429 'sub/b.txt'
411 >>> def bad(f, msg):
430 >>> def bad(f, msg):
412 ... print "%s: %s" % (f, msg)
431 ... print "%s: %s" % (f, msg)
413 >>> m1.bad = bad
432 >>> m1.bad = bad
414 >>> m2.bad('x.txt', 'No such file')
433 >>> m2.bad('x.txt', 'No such file')
415 sub/x.txt: No such file
434 sub/x.txt: No such file
416 >>> m2.abs('c.txt')
435 >>> m2.abs('c.txt')
417 'sub/c.txt'
436 'sub/c.txt'
418 """
437 """
419
438
420 def __init__(self, path, matcher):
439 def __init__(self, path, matcher):
421 self._root = matcher._root
440 self._root = matcher._root
422 self._cwd = matcher._cwd
441 self._cwd = matcher._cwd
423 self._path = path
442 self._path = path
424 self._matcher = matcher
443 self._matcher = matcher
425 self._always = matcher._always
444 self._always = matcher._always
426
445
427 self._files = [f[len(path) + 1:] for f in matcher._files
446 self._files = [f[len(path) + 1:] for f in matcher._files
428 if f.startswith(path + "/")]
447 if f.startswith(path + "/")]
429
448
430 # If the parent repo had a path to this subrepo and the matcher is
449 # If the parent repo had a path to this subrepo and the matcher is
431 # a prefix matcher, this submatcher always matches.
450 # a prefix matcher, this submatcher always matches.
432 if matcher.prefix():
451 if matcher.prefix():
433 self._always = any(f == path for f in matcher._files)
452 self._always = any(f == path for f in matcher._files)
434
453
435 self._anypats = matcher._anypats
454 self._anypats = matcher._anypats
436 # Some information is lost in the superclass's constructor, so we
455 # Some information is lost in the superclass's constructor, so we
437 # can not accurately create the matching function for the subdirectory
456 # can not accurately create the matching function for the subdirectory
438 # from the inputs. Instead, we override matchfn() and visitdir() to
457 # from the inputs. Instead, we override matchfn() and visitdir() to
439 # call the original matcher with the subdirectory path prepended.
458 # call the original matcher with the subdirectory path prepended.
440 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
459 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
441
460
442 def bad(self, f, msg):
461 def bad(self, f, msg):
443 self._matcher.bad(self._path + "/" + f, msg)
462 self._matcher.bad(self._path + "/" + f, msg)
444
463
445 def abs(self, f):
464 def abs(self, f):
446 return self._matcher.abs(self._path + "/" + f)
465 return self._matcher.abs(self._path + "/" + f)
447
466
448 def rel(self, f):
467 def rel(self, f):
449 return self._matcher.rel(self._path + "/" + f)
468 return self._matcher.rel(self._path + "/" + f)
450
469
451 def uipath(self, f):
470 def uipath(self, f):
452 return self._matcher.uipath(self._path + "/" + f)
471 return self._matcher.uipath(self._path + "/" + f)
453
472
454 def visitdir(self, dir):
473 def visitdir(self, dir):
455 if dir == '.':
474 if dir == '.':
456 dir = self._path
475 dir = self._path
457 else:
476 else:
458 dir = self._path + "/" + dir
477 dir = self._path + "/" + dir
459 return self._matcher.visitdir(dir)
478 return self._matcher.visitdir(dir)
460
479
461 def patkind(pattern, default=None):
480 def patkind(pattern, default=None):
462 '''If pattern is 'kind:pat' with a known kind, return kind.'''
481 '''If pattern is 'kind:pat' with a known kind, return kind.'''
463 return _patsplit(pattern, default)[0]
482 return _patsplit(pattern, default)[0]
464
483
465 def _patsplit(pattern, default):
484 def _patsplit(pattern, default):
466 """Split a string into the optional pattern kind prefix and the actual
485 """Split a string into the optional pattern kind prefix and the actual
467 pattern."""
486 pattern."""
468 if ':' in pattern:
487 if ':' in pattern:
469 kind, pat = pattern.split(':', 1)
488 kind, pat = pattern.split(':', 1)
470 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
489 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
471 'listfile', 'listfile0', 'set', 'include', 'subinclude',
490 'listfile', 'listfile0', 'set', 'include', 'subinclude',
472 'rootfilesin'):
491 'rootfilesin'):
473 return kind, pat
492 return kind, pat
474 return default, pattern
493 return default, pattern
475
494
476 def _globre(pat):
495 def _globre(pat):
477 r'''Convert an extended glob string to a regexp string.
496 r'''Convert an extended glob string to a regexp string.
478
497
479 >>> print _globre(r'?')
498 >>> print _globre(r'?')
480 .
499 .
481 >>> print _globre(r'*')
500 >>> print _globre(r'*')
482 [^/]*
501 [^/]*
483 >>> print _globre(r'**')
502 >>> print _globre(r'**')
484 .*
503 .*
485 >>> print _globre(r'**/a')
504 >>> print _globre(r'**/a')
486 (?:.*/)?a
505 (?:.*/)?a
487 >>> print _globre(r'a/**/b')
506 >>> print _globre(r'a/**/b')
488 a\/(?:.*/)?b
507 a\/(?:.*/)?b
489 >>> print _globre(r'[a*?!^][^b][!c]')
508 >>> print _globre(r'[a*?!^][^b][!c]')
490 [a*?!^][\^b][^c]
509 [a*?!^][\^b][^c]
491 >>> print _globre(r'{a,b}')
510 >>> print _globre(r'{a,b}')
492 (?:a|b)
511 (?:a|b)
493 >>> print _globre(r'.\*\?')
512 >>> print _globre(r'.\*\?')
494 \.\*\?
513 \.\*\?
495 '''
514 '''
496 i, n = 0, len(pat)
515 i, n = 0, len(pat)
497 res = ''
516 res = ''
498 group = 0
517 group = 0
499 escape = util.re.escape
518 escape = util.re.escape
500 def peek():
519 def peek():
501 return i < n and pat[i:i + 1]
520 return i < n and pat[i:i + 1]
502 while i < n:
521 while i < n:
503 c = pat[i:i + 1]
522 c = pat[i:i + 1]
504 i += 1
523 i += 1
505 if c not in '*?[{},\\':
524 if c not in '*?[{},\\':
506 res += escape(c)
525 res += escape(c)
507 elif c == '*':
526 elif c == '*':
508 if peek() == '*':
527 if peek() == '*':
509 i += 1
528 i += 1
510 if peek() == '/':
529 if peek() == '/':
511 i += 1
530 i += 1
512 res += '(?:.*/)?'
531 res += '(?:.*/)?'
513 else:
532 else:
514 res += '.*'
533 res += '.*'
515 else:
534 else:
516 res += '[^/]*'
535 res += '[^/]*'
517 elif c == '?':
536 elif c == '?':
518 res += '.'
537 res += '.'
519 elif c == '[':
538 elif c == '[':
520 j = i
539 j = i
521 if j < n and pat[j:j + 1] in '!]':
540 if j < n and pat[j:j + 1] in '!]':
522 j += 1
541 j += 1
523 while j < n and pat[j:j + 1] != ']':
542 while j < n and pat[j:j + 1] != ']':
524 j += 1
543 j += 1
525 if j >= n:
544 if j >= n:
526 res += '\\['
545 res += '\\['
527 else:
546 else:
528 stuff = pat[i:j].replace('\\','\\\\')
547 stuff = pat[i:j].replace('\\','\\\\')
529 i = j + 1
548 i = j + 1
530 if stuff[0:1] == '!':
549 if stuff[0:1] == '!':
531 stuff = '^' + stuff[1:]
550 stuff = '^' + stuff[1:]
532 elif stuff[0:1] == '^':
551 elif stuff[0:1] == '^':
533 stuff = '\\' + stuff
552 stuff = '\\' + stuff
534 res = '%s[%s]' % (res, stuff)
553 res = '%s[%s]' % (res, stuff)
535 elif c == '{':
554 elif c == '{':
536 group += 1
555 group += 1
537 res += '(?:'
556 res += '(?:'
538 elif c == '}' and group:
557 elif c == '}' and group:
539 res += ')'
558 res += ')'
540 group -= 1
559 group -= 1
541 elif c == ',' and group:
560 elif c == ',' and group:
542 res += '|'
561 res += '|'
543 elif c == '\\':
562 elif c == '\\':
544 p = peek()
563 p = peek()
545 if p:
564 if p:
546 i += 1
565 i += 1
547 res += escape(p)
566 res += escape(p)
548 else:
567 else:
549 res += escape(c)
568 res += escape(c)
550 else:
569 else:
551 res += escape(c)
570 res += escape(c)
552 return res
571 return res
553
572
554 def _regex(kind, pat, globsuffix):
573 def _regex(kind, pat, globsuffix):
555 '''Convert a (normalized) pattern of any kind into a regular expression.
574 '''Convert a (normalized) pattern of any kind into a regular expression.
556 globsuffix is appended to the regexp of globs.'''
575 globsuffix is appended to the regexp of globs.'''
557 if not pat:
576 if not pat:
558 return ''
577 return ''
559 if kind == 're':
578 if kind == 're':
560 return pat
579 return pat
561 if kind == 'path':
580 if kind == 'path':
562 if pat == '.':
581 if pat == '.':
563 return ''
582 return ''
564 return '^' + util.re.escape(pat) + '(?:/|$)'
583 return '^' + util.re.escape(pat) + '(?:/|$)'
565 if kind == 'rootfilesin':
584 if kind == 'rootfilesin':
566 if pat == '.':
585 if pat == '.':
567 escaped = ''
586 escaped = ''
568 else:
587 else:
569 # Pattern is a directory name.
588 # Pattern is a directory name.
570 escaped = util.re.escape(pat) + '/'
589 escaped = util.re.escape(pat) + '/'
571 # Anything after the pattern must be a non-directory.
590 # Anything after the pattern must be a non-directory.
572 return '^' + escaped + '[^/]+$'
591 return '^' + escaped + '[^/]+$'
573 if kind == 'relglob':
592 if kind == 'relglob':
574 return '(?:|.*/)' + _globre(pat) + globsuffix
593 return '(?:|.*/)' + _globre(pat) + globsuffix
575 if kind == 'relpath':
594 if kind == 'relpath':
576 return util.re.escape(pat) + '(?:/|$)'
595 return util.re.escape(pat) + '(?:/|$)'
577 if kind == 'relre':
596 if kind == 'relre':
578 if pat.startswith('^'):
597 if pat.startswith('^'):
579 return pat
598 return pat
580 return '.*' + pat
599 return '.*' + pat
581 return _globre(pat) + globsuffix
600 return _globre(pat) + globsuffix
582
601
583 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
602 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
584 '''Return regexp string and a matcher function for kindpats.
603 '''Return regexp string and a matcher function for kindpats.
585 globsuffix is appended to the regexp of globs.'''
604 globsuffix is appended to the regexp of globs.'''
586 matchfuncs = []
605 matchfuncs = []
587
606
588 subincludes, kindpats = _expandsubinclude(kindpats, root)
607 subincludes, kindpats = _expandsubinclude(kindpats, root)
589 if subincludes:
608 if subincludes:
590 submatchers = {}
609 submatchers = {}
591 def matchsubinclude(f):
610 def matchsubinclude(f):
592 for prefix, matcherargs in subincludes:
611 for prefix, matcherargs in subincludes:
593 if f.startswith(prefix):
612 if f.startswith(prefix):
594 mf = submatchers.get(prefix)
613 mf = submatchers.get(prefix)
595 if mf is None:
614 if mf is None:
596 mf = match(*matcherargs)
615 mf = match(*matcherargs)
597 submatchers[prefix] = mf
616 submatchers[prefix] = mf
598
617
599 if mf(f[len(prefix):]):
618 if mf(f[len(prefix):]):
600 return True
619 return True
601 return False
620 return False
602 matchfuncs.append(matchsubinclude)
621 matchfuncs.append(matchsubinclude)
603
622
604 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
623 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
605 if fset:
624 if fset:
606 matchfuncs.append(fset.__contains__)
625 matchfuncs.append(fset.__contains__)
607
626
608 regex = ''
627 regex = ''
609 if kindpats:
628 if kindpats:
610 regex, mf = _buildregexmatch(kindpats, globsuffix)
629 regex, mf = _buildregexmatch(kindpats, globsuffix)
611 matchfuncs.append(mf)
630 matchfuncs.append(mf)
612
631
613 if len(matchfuncs) == 1:
632 if len(matchfuncs) == 1:
614 return regex, matchfuncs[0]
633 return regex, matchfuncs[0]
615 else:
634 else:
616 return regex, lambda f: any(mf(f) for mf in matchfuncs)
635 return regex, lambda f: any(mf(f) for mf in matchfuncs)
617
636
618 def _buildregexmatch(kindpats, globsuffix):
637 def _buildregexmatch(kindpats, globsuffix):
619 """Build a match function from a list of kinds and kindpats,
638 """Build a match function from a list of kinds and kindpats,
620 return regexp string and a matcher function."""
639 return regexp string and a matcher function."""
621 try:
640 try:
622 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
641 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
623 for (k, p, s) in kindpats])
642 for (k, p, s) in kindpats])
624 if len(regex) > 20000:
643 if len(regex) > 20000:
625 raise OverflowError
644 raise OverflowError
626 return regex, _rematcher(regex)
645 return regex, _rematcher(regex)
627 except OverflowError:
646 except OverflowError:
628 # We're using a Python with a tiny regex engine and we
647 # We're using a Python with a tiny regex engine and we
629 # made it explode, so we'll divide the pattern list in two
648 # made it explode, so we'll divide the pattern list in two
630 # until it works
649 # until it works
631 l = len(kindpats)
650 l = len(kindpats)
632 if l < 2:
651 if l < 2:
633 raise
652 raise
634 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
653 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
635 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
654 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
636 return regex, lambda s: a(s) or b(s)
655 return regex, lambda s: a(s) or b(s)
637 except re.error:
656 except re.error:
638 for k, p, s in kindpats:
657 for k, p, s in kindpats:
639 try:
658 try:
640 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
659 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
641 except re.error:
660 except re.error:
642 if s:
661 if s:
643 raise error.Abort(_("%s: invalid pattern (%s): %s") %
662 raise error.Abort(_("%s: invalid pattern (%s): %s") %
644 (s, k, p))
663 (s, k, p))
645 else:
664 else:
646 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
665 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
647 raise error.Abort(_("invalid pattern"))
666 raise error.Abort(_("invalid pattern"))
648
667
649 def _patternrootsanddirs(kindpats):
668 def _patternrootsanddirs(kindpats):
650 '''Returns roots and directories corresponding to each pattern.
669 '''Returns roots and directories corresponding to each pattern.
651
670
652 This calculates the roots and directories exactly matching the patterns and
671 This calculates the roots and directories exactly matching the patterns and
653 returns a tuple of (roots, dirs) for each. It does not return other
672 returns a tuple of (roots, dirs) for each. It does not return other
654 directories which may also need to be considered, like the parent
673 directories which may also need to be considered, like the parent
655 directories.
674 directories.
656 '''
675 '''
657 r = []
676 r = []
658 d = []
677 d = []
659 for kind, pat, source in kindpats:
678 for kind, pat, source in kindpats:
660 if kind == 'glob': # find the non-glob prefix
679 if kind == 'glob': # find the non-glob prefix
661 root = []
680 root = []
662 for p in pat.split('/'):
681 for p in pat.split('/'):
663 if '[' in p or '{' in p or '*' in p or '?' in p:
682 if '[' in p or '{' in p or '*' in p or '?' in p:
664 break
683 break
665 root.append(p)
684 root.append(p)
666 r.append('/'.join(root) or '.')
685 r.append('/'.join(root) or '.')
667 elif kind in ('relpath', 'path'):
686 elif kind in ('relpath', 'path'):
668 r.append(pat or '.')
687 r.append(pat or '.')
669 elif kind in ('rootfilesin',):
688 elif kind in ('rootfilesin',):
670 d.append(pat or '.')
689 d.append(pat or '.')
671 else: # relglob, re, relre
690 else: # relglob, re, relre
672 r.append('.')
691 r.append('.')
673 return r, d
692 return r, d
674
693
675 def _roots(kindpats):
694 def _roots(kindpats):
676 '''Returns root directories to match recursively from the given patterns.'''
695 '''Returns root directories to match recursively from the given patterns.'''
677 roots, dirs = _patternrootsanddirs(kindpats)
696 roots, dirs = _patternrootsanddirs(kindpats)
678 return roots
697 return roots
679
698
680 def _rootsanddirs(kindpats):
699 def _rootsanddirs(kindpats):
681 '''Returns roots and exact directories from patterns.
700 '''Returns roots and exact directories from patterns.
682
701
683 roots are directories to match recursively, whereas exact directories should
702 roots are directories to match recursively, whereas exact directories should
684 be matched non-recursively. The returned (roots, dirs) tuple will also
703 be matched non-recursively. The returned (roots, dirs) tuple will also
685 include directories that need to be implicitly considered as either, such as
704 include directories that need to be implicitly considered as either, such as
686 parent directories.
705 parent directories.
687
706
688 >>> _rootsanddirs(\
707 >>> _rootsanddirs(\
689 [('glob', 'g/h/*', ''), ('glob', 'g/h', ''), ('glob', 'g*', '')])
708 [('glob', 'g/h/*', ''), ('glob', 'g/h', ''), ('glob', 'g*', '')])
690 (['g/h', 'g/h', '.'], ['g', '.'])
709 (['g/h', 'g/h', '.'], ['g', '.'])
691 >>> _rootsanddirs(\
710 >>> _rootsanddirs(\
692 [('rootfilesin', 'g/h', ''), ('rootfilesin', '', '')])
711 [('rootfilesin', 'g/h', ''), ('rootfilesin', '', '')])
693 ([], ['g/h', '.', 'g', '.'])
712 ([], ['g/h', '.', 'g', '.'])
694 >>> _rootsanddirs(\
713 >>> _rootsanddirs(\
695 [('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
714 [('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
696 (['r', 'p/p', '.'], ['p', '.'])
715 (['r', 'p/p', '.'], ['p', '.'])
697 >>> _rootsanddirs(\
716 >>> _rootsanddirs(\
698 [('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
717 [('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
699 (['.', '.', '.'], ['.'])
718 (['.', '.', '.'], ['.'])
700 '''
719 '''
701 r, d = _patternrootsanddirs(kindpats)
720 r, d = _patternrootsanddirs(kindpats)
702
721
703 # Append the parents as non-recursive/exact directories, since they must be
722 # Append the parents as non-recursive/exact directories, since they must be
704 # scanned to get to either the roots or the other exact directories.
723 # scanned to get to either the roots or the other exact directories.
705 d.extend(util.dirs(d))
724 d.extend(util.dirs(d))
706 d.extend(util.dirs(r))
725 d.extend(util.dirs(r))
707 # util.dirs() does not include the root directory, so add it manually
726 # util.dirs() does not include the root directory, so add it manually
708 d.append('.')
727 d.append('.')
709
728
710 return r, d
729 return r, d
711
730
712 def _explicitfiles(kindpats):
731 def _explicitfiles(kindpats):
713 '''Returns the potential explicit filenames from the patterns.
732 '''Returns the potential explicit filenames from the patterns.
714
733
715 >>> _explicitfiles([('path', 'foo/bar', '')])
734 >>> _explicitfiles([('path', 'foo/bar', '')])
716 ['foo/bar']
735 ['foo/bar']
717 >>> _explicitfiles([('rootfilesin', 'foo/bar', '')])
736 >>> _explicitfiles([('rootfilesin', 'foo/bar', '')])
718 []
737 []
719 '''
738 '''
720 # Keep only the pattern kinds where one can specify filenames (vs only
739 # Keep only the pattern kinds where one can specify filenames (vs only
721 # directory names).
740 # directory names).
722 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
741 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
723 return _roots(filable)
742 return _roots(filable)
724
743
725 def _anypats(kindpats):
744 def _anypats(kindpats):
726 for kind, pat, source in kindpats:
745 for kind, pat, source in kindpats:
727 if kind in ('glob', 're', 'relglob', 'relre', 'set', 'rootfilesin'):
746 if kind in ('glob', 're', 'relglob', 'relre', 'set', 'rootfilesin'):
728 return True
747 return True
729
748
730 _commentre = None
749 _commentre = None
731
750
732 def readpatternfile(filepath, warn, sourceinfo=False):
751 def readpatternfile(filepath, warn, sourceinfo=False):
733 '''parse a pattern file, returning a list of
752 '''parse a pattern file, returning a list of
734 patterns. These patterns should be given to compile()
753 patterns. These patterns should be given to compile()
735 to be validated and converted into a match function.
754 to be validated and converted into a match function.
736
755
737 trailing white space is dropped.
756 trailing white space is dropped.
738 the escape character is backslash.
757 the escape character is backslash.
739 comments start with #.
758 comments start with #.
740 empty lines are skipped.
759 empty lines are skipped.
741
760
742 lines can be of the following formats:
761 lines can be of the following formats:
743
762
744 syntax: regexp # defaults following lines to non-rooted regexps
763 syntax: regexp # defaults following lines to non-rooted regexps
745 syntax: glob # defaults following lines to non-rooted globs
764 syntax: glob # defaults following lines to non-rooted globs
746 re:pattern # non-rooted regular expression
765 re:pattern # non-rooted regular expression
747 glob:pattern # non-rooted glob
766 glob:pattern # non-rooted glob
748 pattern # pattern of the current default type
767 pattern # pattern of the current default type
749
768
750 if sourceinfo is set, returns a list of tuples:
769 if sourceinfo is set, returns a list of tuples:
751 (pattern, lineno, originalline). This is useful to debug ignore patterns.
770 (pattern, lineno, originalline). This is useful to debug ignore patterns.
752 '''
771 '''
753
772
754 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
773 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
755 'include': 'include', 'subinclude': 'subinclude'}
774 'include': 'include', 'subinclude': 'subinclude'}
756 syntax = 'relre:'
775 syntax = 'relre:'
757 patterns = []
776 patterns = []
758
777
759 fp = open(filepath, 'rb')
778 fp = open(filepath, 'rb')
760 for lineno, line in enumerate(util.iterfile(fp), start=1):
779 for lineno, line in enumerate(util.iterfile(fp), start=1):
761 if "#" in line:
780 if "#" in line:
762 global _commentre
781 global _commentre
763 if not _commentre:
782 if not _commentre:
764 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
783 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
765 # remove comments prefixed by an even number of escapes
784 # remove comments prefixed by an even number of escapes
766 m = _commentre.search(line)
785 m = _commentre.search(line)
767 if m:
786 if m:
768 line = line[:m.end(1)]
787 line = line[:m.end(1)]
769 # fixup properly escaped comments that survived the above
788 # fixup properly escaped comments that survived the above
770 line = line.replace("\\#", "#")
789 line = line.replace("\\#", "#")
771 line = line.rstrip()
790 line = line.rstrip()
772 if not line:
791 if not line:
773 continue
792 continue
774
793
775 if line.startswith('syntax:'):
794 if line.startswith('syntax:'):
776 s = line[7:].strip()
795 s = line[7:].strip()
777 try:
796 try:
778 syntax = syntaxes[s]
797 syntax = syntaxes[s]
779 except KeyError:
798 except KeyError:
780 if warn:
799 if warn:
781 warn(_("%s: ignoring invalid syntax '%s'\n") %
800 warn(_("%s: ignoring invalid syntax '%s'\n") %
782 (filepath, s))
801 (filepath, s))
783 continue
802 continue
784
803
785 linesyntax = syntax
804 linesyntax = syntax
786 for s, rels in syntaxes.iteritems():
805 for s, rels in syntaxes.iteritems():
787 if line.startswith(rels):
806 if line.startswith(rels):
788 linesyntax = rels
807 linesyntax = rels
789 line = line[len(rels):]
808 line = line[len(rels):]
790 break
809 break
791 elif line.startswith(s+':'):
810 elif line.startswith(s+':'):
792 linesyntax = rels
811 linesyntax = rels
793 line = line[len(s) + 1:]
812 line = line[len(s) + 1:]
794 break
813 break
795 if sourceinfo:
814 if sourceinfo:
796 patterns.append((linesyntax + line, lineno, line))
815 patterns.append((linesyntax + line, lineno, line))
797 else:
816 else:
798 patterns.append(linesyntax + line)
817 patterns.append(linesyntax + line)
799 fp.close()
818 fp.close()
800 return patterns
819 return patterns
General Comments 0
You need to be logged in to leave comments. Login now