##// END OF EJS Templates
match: make basematcher._files a @propertycache...
Martin von Zweigbergk -
r32455:9f781f43 default
parent child Browse files
Show More
@@ -1,819 +1,822
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 basematcher(object):
205 class basematcher(object):
206
206
207 def __init__(self, root, cwd, badfn=None):
207 def __init__(self, root, cwd, badfn=None):
208 self._root = root
208 self._root = root
209 self._cwd = cwd
209 self._cwd = cwd
210 if badfn is not None:
210 if badfn is not None:
211 self.bad = badfn
211 self.bad = badfn
212 self._files = [] # exact files and roots of patterns
213 self.matchfn = lambda f: False
212 self.matchfn = lambda f: False
214
213
215 def __call__(self, fn):
214 def __call__(self, fn):
216 return self.matchfn(fn)
215 return self.matchfn(fn)
217 def __iter__(self):
216 def __iter__(self):
218 for f in self._files:
217 for f in self._files:
219 yield f
218 yield f
220 # Callbacks related to how the matcher is used by dirstate.walk.
219 # Callbacks related to how the matcher is used by dirstate.walk.
221 # Subscribers to these events must monkeypatch the matcher object.
220 # Subscribers to these events must monkeypatch the matcher object.
222 def bad(self, f, msg):
221 def bad(self, f, msg):
223 '''Callback from dirstate.walk for each explicit file that can't be
222 '''Callback from dirstate.walk for each explicit file that can't be
224 found/accessed, with an error message.'''
223 found/accessed, with an error message.'''
225 pass
224 pass
226
225
227 # If an explicitdir is set, it will be called when an explicitly listed
226 # If an explicitdir is set, it will be called when an explicitly listed
228 # directory is visited.
227 # directory is visited.
229 explicitdir = None
228 explicitdir = None
230
229
231 # If an traversedir is set, it will be called when a directory discovered
230 # If an traversedir is set, it will be called when a directory discovered
232 # by recursive traversal is visited.
231 # by recursive traversal is visited.
233 traversedir = None
232 traversedir = None
234
233
235 def abs(self, f):
234 def abs(self, f):
236 '''Convert a repo path back to path that is relative to the root of the
235 '''Convert a repo path back to path that is relative to the root of the
237 matcher.'''
236 matcher.'''
238 return f
237 return f
239
238
240 def rel(self, f):
239 def rel(self, f):
241 '''Convert repo path back to path that is relative to cwd of matcher.'''
240 '''Convert repo path back to path that is relative to cwd of matcher.'''
242 return util.pathto(self._root, self._cwd, f)
241 return util.pathto(self._root, self._cwd, f)
243
242
244 def uipath(self, f):
243 def uipath(self, f):
245 '''Convert repo path to a display path. If patterns or -I/-X were used
244 '''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.
245 to create this matcher, the display path will be relative to cwd.
247 Otherwise it is relative to the root of the repo.'''
246 Otherwise it is relative to the root of the repo.'''
248 return self.rel(f)
247 return self.rel(f)
249
248
249 @propertycache
250 def _files(self):
251 return []
252
250 def files(self):
253 def files(self):
251 '''Explicitly listed files or patterns or roots:
254 '''Explicitly listed files or patterns or roots:
252 if no patterns or .always(): empty list,
255 if no patterns or .always(): empty list,
253 if exact: list exact files,
256 if exact: list exact files,
254 if not .anypats(): list all files and dirs,
257 if not .anypats(): list all files and dirs,
255 else: optimal roots'''
258 else: optimal roots'''
256 return self._files
259 return self._files
257
260
258 @propertycache
261 @propertycache
259 def _fileset(self):
262 def _fileset(self):
260 return set(self._files)
263 return set(self._files)
261
264
262 def exact(self, f):
265 def exact(self, f):
263 '''Returns True if f is in .files().'''
266 '''Returns True if f is in .files().'''
264 return f in self._fileset
267 return f in self._fileset
265
268
266 def visitdir(self, dir):
269 def visitdir(self, dir):
267 '''Decides whether a directory should be visited based on whether it
270 '''Decides whether a directory should be visited based on whether it
268 has potential matches in it or one of its subdirectories. This is
271 has potential matches in it or one of its subdirectories. This is
269 based on the match's primary, included, and excluded patterns.
272 based on the match's primary, included, and excluded patterns.
270
273
271 Returns the string 'all' if the given directory and all subdirectories
274 Returns the string 'all' if the given directory and all subdirectories
272 should be visited. Otherwise returns True or False indicating whether
275 should be visited. Otherwise returns True or False indicating whether
273 the given directory should be visited.
276 the given directory should be visited.
274
277
275 This function's behavior is undefined if it has returned False for
278 This function's behavior is undefined if it has returned False for
276 one of the dir's parent directories.
279 one of the dir's parent directories.
277 '''
280 '''
278 return False
281 return False
279
282
280 def anypats(self):
283 def anypats(self):
281 '''Matcher uses patterns or include/exclude.'''
284 '''Matcher uses patterns or include/exclude.'''
282 return False
285 return False
283
286
284 def always(self):
287 def always(self):
285 '''Matcher will match everything and .files() will be empty
288 '''Matcher will match everything and .files() will be empty
286 - optimization might be possible and necessary.'''
289 - optimization might be possible and necessary.'''
287 return False
290 return False
288
291
289 def isexact(self):
292 def isexact(self):
290 return False
293 return False
291
294
292 def prefix(self):
295 def prefix(self):
293 return not self.always() and not self.isexact() and not self.anypats()
296 return not self.always() and not self.isexact() and not self.anypats()
294
297
295 class matcher(basematcher):
298 class matcher(basematcher):
296
299
297 def __init__(self, root, cwd, normalize, patterns, include=None,
300 def __init__(self, root, cwd, normalize, patterns, include=None,
298 exclude=None, default='glob', exact=False, auditor=None,
301 exclude=None, default='glob', exact=False, auditor=None,
299 ctx=None, listsubrepos=False, warn=None, badfn=None):
302 ctx=None, listsubrepos=False, warn=None, badfn=None):
300 super(matcher, self).__init__(root, cwd, badfn)
303 super(matcher, self).__init__(root, cwd, badfn)
301 if include is None:
304 if include is None:
302 include = []
305 include = []
303 if exclude is None:
306 if exclude is None:
304 exclude = []
307 exclude = []
305
308
306 self._anypats = bool(include or exclude)
309 self._anypats = bool(include or exclude)
307 self._always = False
310 self._always = False
308 self._pathrestricted = bool(include or exclude or patterns)
311 self._pathrestricted = bool(include or exclude or patterns)
309 self.patternspat = None
312 self.patternspat = None
310 self.includepat = None
313 self.includepat = None
311 self.excludepat = None
314 self.excludepat = None
312
315
313 # roots are directories which are recursively included/excluded.
316 # roots are directories which are recursively included/excluded.
314 self._includeroots = set()
317 self._includeroots = set()
315 self._excluderoots = set()
318 self._excluderoots = set()
316 # dirs are directories which are non-recursively included.
319 # dirs are directories which are non-recursively included.
317 self._includedirs = set()
320 self._includedirs = set()
318
321
319 matchfns = []
322 matchfns = []
320 if include:
323 if include:
321 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
324 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
322 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
325 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
323 listsubrepos, root)
326 listsubrepos, root)
324 roots, dirs = _rootsanddirs(kindpats)
327 roots, dirs = _rootsanddirs(kindpats)
325 self._includeroots.update(roots)
328 self._includeroots.update(roots)
326 self._includedirs.update(dirs)
329 self._includedirs.update(dirs)
327 matchfns.append(im)
330 matchfns.append(im)
328 if exclude:
331 if exclude:
329 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
332 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
330 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
333 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
331 listsubrepos, root)
334 listsubrepos, root)
332 if not _anypats(kindpats):
335 if not _anypats(kindpats):
333 # Only consider recursive excludes as such - if a non-recursive
336 # Only consider recursive excludes as such - if a non-recursive
334 # exclude is used, we must still recurse into the excluded
337 # exclude is used, we must still recurse into the excluded
335 # directory, at least to find subdirectories. In such a case,
338 # directory, at least to find subdirectories. In such a case,
336 # the regex still won't match the non-recursively-excluded
339 # the regex still won't match the non-recursively-excluded
337 # files.
340 # files.
338 self._excluderoots.update(_roots(kindpats))
341 self._excluderoots.update(_roots(kindpats))
339 matchfns.append(lambda f: not em(f))
342 matchfns.append(lambda f: not em(f))
340 if exact:
343 if exact:
341 if isinstance(patterns, list):
344 if isinstance(patterns, list):
342 self._files = patterns
345 self._files = patterns
343 else:
346 else:
344 self._files = list(patterns)
347 self._files = list(patterns)
345 matchfns.append(self.exact)
348 matchfns.append(self.exact)
346 elif patterns:
349 elif patterns:
347 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
350 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
348 if not _kindpatsalwaysmatch(kindpats):
351 if not _kindpatsalwaysmatch(kindpats):
349 self._files = _explicitfiles(kindpats)
352 self._files = _explicitfiles(kindpats)
350 self._anypats = self._anypats or _anypats(kindpats)
353 self._anypats = self._anypats or _anypats(kindpats)
351 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
354 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
352 listsubrepos, root)
355 listsubrepos, root)
353 matchfns.append(pm)
356 matchfns.append(pm)
354
357
355 if not matchfns:
358 if not matchfns:
356 m = util.always
359 m = util.always
357 self._always = True
360 self._always = True
358 elif len(matchfns) == 1:
361 elif len(matchfns) == 1:
359 m = matchfns[0]
362 m = matchfns[0]
360 else:
363 else:
361 def m(f):
364 def m(f):
362 for matchfn in matchfns:
365 for matchfn in matchfns:
363 if not matchfn(f):
366 if not matchfn(f):
364 return False
367 return False
365 return True
368 return True
366
369
367 self.matchfn = m
370 self.matchfn = m
368
371
369 def uipath(self, f):
372 def uipath(self, f):
370 return (self._pathrestricted and self.rel(f)) or self.abs(f)
373 return (self._pathrestricted and self.rel(f)) or self.abs(f)
371
374
372 @propertycache
375 @propertycache
373 def _dirs(self):
376 def _dirs(self):
374 return set(util.dirs(self._fileset)) | {'.'}
377 return set(util.dirs(self._fileset)) | {'.'}
375
378
376 def visitdir(self, dir):
379 def visitdir(self, dir):
377 if self.prefix() and dir in self._fileset:
380 if self.prefix() and dir in self._fileset:
378 return 'all'
381 return 'all'
379 if dir in self._excluderoots:
382 if dir in self._excluderoots:
380 return False
383 return False
381 if ((self._includeroots or self._includedirs) and
384 if ((self._includeroots or self._includedirs) and
382 '.' not in self._includeroots and
385 '.' not in self._includeroots and
383 dir not in self._includeroots and
386 dir not in self._includeroots and
384 dir not in self._includedirs and
387 dir not in self._includedirs and
385 not any(parent in self._includeroots
388 not any(parent in self._includeroots
386 for parent in util.finddirs(dir))):
389 for parent in util.finddirs(dir))):
387 return False
390 return False
388 return (not self._fileset or
391 return (not self._fileset or
389 '.' in self._fileset or
392 '.' in self._fileset or
390 dir in self._fileset or
393 dir in self._fileset or
391 dir in self._dirs or
394 dir in self._dirs or
392 any(parentdir in self._fileset
395 any(parentdir in self._fileset
393 for parentdir in util.finddirs(dir)))
396 for parentdir in util.finddirs(dir)))
394
397
395 def anypats(self):
398 def anypats(self):
396 return self._anypats
399 return self._anypats
397
400
398 def always(self):
401 def always(self):
399 return self._always
402 return self._always
400
403
401 def isexact(self):
404 def isexact(self):
402 return self.matchfn == self.exact
405 return self.matchfn == self.exact
403
406
404 def __repr__(self):
407 def __repr__(self):
405 return ('<matcher files=%r, patterns=%r, includes=%r, excludes=%r>' %
408 return ('<matcher files=%r, patterns=%r, includes=%r, excludes=%r>' %
406 (self._files, self.patternspat, self.includepat,
409 (self._files, self.patternspat, self.includepat,
407 self.excludepat))
410 self.excludepat))
408
411
409 class subdirmatcher(matcher):
412 class subdirmatcher(matcher):
410 """Adapt a matcher to work on a subdirectory only.
413 """Adapt a matcher to work on a subdirectory only.
411
414
412 The paths are remapped to remove/insert the path as needed:
415 The paths are remapped to remove/insert the path as needed:
413
416
414 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
417 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
415 >>> m2 = subdirmatcher('sub', m1)
418 >>> m2 = subdirmatcher('sub', m1)
416 >>> bool(m2('a.txt'))
419 >>> bool(m2('a.txt'))
417 False
420 False
418 >>> bool(m2('b.txt'))
421 >>> bool(m2('b.txt'))
419 True
422 True
420 >>> bool(m2.matchfn('a.txt'))
423 >>> bool(m2.matchfn('a.txt'))
421 False
424 False
422 >>> bool(m2.matchfn('b.txt'))
425 >>> bool(m2.matchfn('b.txt'))
423 True
426 True
424 >>> m2.files()
427 >>> m2.files()
425 ['b.txt']
428 ['b.txt']
426 >>> m2.exact('b.txt')
429 >>> m2.exact('b.txt')
427 True
430 True
428 >>> util.pconvert(m2.rel('b.txt'))
431 >>> util.pconvert(m2.rel('b.txt'))
429 'sub/b.txt'
432 'sub/b.txt'
430 >>> def bad(f, msg):
433 >>> def bad(f, msg):
431 ... print "%s: %s" % (f, msg)
434 ... print "%s: %s" % (f, msg)
432 >>> m1.bad = bad
435 >>> m1.bad = bad
433 >>> m2.bad('x.txt', 'No such file')
436 >>> m2.bad('x.txt', 'No such file')
434 sub/x.txt: No such file
437 sub/x.txt: No such file
435 >>> m2.abs('c.txt')
438 >>> m2.abs('c.txt')
436 'sub/c.txt'
439 'sub/c.txt'
437 """
440 """
438
441
439 def __init__(self, path, matcher):
442 def __init__(self, path, matcher):
440 self._root = matcher._root
443 self._root = matcher._root
441 self._cwd = matcher._cwd
444 self._cwd = matcher._cwd
442 self._path = path
445 self._path = path
443 self._matcher = matcher
446 self._matcher = matcher
444 self._always = matcher._always
447 self._always = matcher._always
445
448
446 self._files = [f[len(path) + 1:] for f in matcher._files
449 self._files = [f[len(path) + 1:] for f in matcher._files
447 if f.startswith(path + "/")]
450 if f.startswith(path + "/")]
448
451
449 # If the parent repo had a path to this subrepo and the matcher is
452 # If the parent repo had a path to this subrepo and the matcher is
450 # a prefix matcher, this submatcher always matches.
453 # a prefix matcher, this submatcher always matches.
451 if matcher.prefix():
454 if matcher.prefix():
452 self._always = any(f == path for f in matcher._files)
455 self._always = any(f == path for f in matcher._files)
453
456
454 self._anypats = matcher._anypats
457 self._anypats = matcher._anypats
455 # Some information is lost in the superclass's constructor, so we
458 # Some information is lost in the superclass's constructor, so we
456 # can not accurately create the matching function for the subdirectory
459 # can not accurately create the matching function for the subdirectory
457 # from the inputs. Instead, we override matchfn() and visitdir() to
460 # from the inputs. Instead, we override matchfn() and visitdir() to
458 # call the original matcher with the subdirectory path prepended.
461 # call the original matcher with the subdirectory path prepended.
459 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
462 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
460
463
461 def bad(self, f, msg):
464 def bad(self, f, msg):
462 self._matcher.bad(self._path + "/" + f, msg)
465 self._matcher.bad(self._path + "/" + f, msg)
463
466
464 def abs(self, f):
467 def abs(self, f):
465 return self._matcher.abs(self._path + "/" + f)
468 return self._matcher.abs(self._path + "/" + f)
466
469
467 def rel(self, f):
470 def rel(self, f):
468 return self._matcher.rel(self._path + "/" + f)
471 return self._matcher.rel(self._path + "/" + f)
469
472
470 def uipath(self, f):
473 def uipath(self, f):
471 return self._matcher.uipath(self._path + "/" + f)
474 return self._matcher.uipath(self._path + "/" + f)
472
475
473 def visitdir(self, dir):
476 def visitdir(self, dir):
474 if dir == '.':
477 if dir == '.':
475 dir = self._path
478 dir = self._path
476 else:
479 else:
477 dir = self._path + "/" + dir
480 dir = self._path + "/" + dir
478 return self._matcher.visitdir(dir)
481 return self._matcher.visitdir(dir)
479
482
480 def patkind(pattern, default=None):
483 def patkind(pattern, default=None):
481 '''If pattern is 'kind:pat' with a known kind, return kind.'''
484 '''If pattern is 'kind:pat' with a known kind, return kind.'''
482 return _patsplit(pattern, default)[0]
485 return _patsplit(pattern, default)[0]
483
486
484 def _patsplit(pattern, default):
487 def _patsplit(pattern, default):
485 """Split a string into the optional pattern kind prefix and the actual
488 """Split a string into the optional pattern kind prefix and the actual
486 pattern."""
489 pattern."""
487 if ':' in pattern:
490 if ':' in pattern:
488 kind, pat = pattern.split(':', 1)
491 kind, pat = pattern.split(':', 1)
489 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
492 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
490 'listfile', 'listfile0', 'set', 'include', 'subinclude',
493 'listfile', 'listfile0', 'set', 'include', 'subinclude',
491 'rootfilesin'):
494 'rootfilesin'):
492 return kind, pat
495 return kind, pat
493 return default, pattern
496 return default, pattern
494
497
495 def _globre(pat):
498 def _globre(pat):
496 r'''Convert an extended glob string to a regexp string.
499 r'''Convert an extended glob string to a regexp string.
497
500
498 >>> print _globre(r'?')
501 >>> print _globre(r'?')
499 .
502 .
500 >>> print _globre(r'*')
503 >>> print _globre(r'*')
501 [^/]*
504 [^/]*
502 >>> print _globre(r'**')
505 >>> print _globre(r'**')
503 .*
506 .*
504 >>> print _globre(r'**/a')
507 >>> print _globre(r'**/a')
505 (?:.*/)?a
508 (?:.*/)?a
506 >>> print _globre(r'a/**/b')
509 >>> print _globre(r'a/**/b')
507 a\/(?:.*/)?b
510 a\/(?:.*/)?b
508 >>> print _globre(r'[a*?!^][^b][!c]')
511 >>> print _globre(r'[a*?!^][^b][!c]')
509 [a*?!^][\^b][^c]
512 [a*?!^][\^b][^c]
510 >>> print _globre(r'{a,b}')
513 >>> print _globre(r'{a,b}')
511 (?:a|b)
514 (?:a|b)
512 >>> print _globre(r'.\*\?')
515 >>> print _globre(r'.\*\?')
513 \.\*\?
516 \.\*\?
514 '''
517 '''
515 i, n = 0, len(pat)
518 i, n = 0, len(pat)
516 res = ''
519 res = ''
517 group = 0
520 group = 0
518 escape = util.re.escape
521 escape = util.re.escape
519 def peek():
522 def peek():
520 return i < n and pat[i:i + 1]
523 return i < n and pat[i:i + 1]
521 while i < n:
524 while i < n:
522 c = pat[i:i + 1]
525 c = pat[i:i + 1]
523 i += 1
526 i += 1
524 if c not in '*?[{},\\':
527 if c not in '*?[{},\\':
525 res += escape(c)
528 res += escape(c)
526 elif c == '*':
529 elif c == '*':
527 if peek() == '*':
530 if peek() == '*':
528 i += 1
531 i += 1
529 if peek() == '/':
532 if peek() == '/':
530 i += 1
533 i += 1
531 res += '(?:.*/)?'
534 res += '(?:.*/)?'
532 else:
535 else:
533 res += '.*'
536 res += '.*'
534 else:
537 else:
535 res += '[^/]*'
538 res += '[^/]*'
536 elif c == '?':
539 elif c == '?':
537 res += '.'
540 res += '.'
538 elif c == '[':
541 elif c == '[':
539 j = i
542 j = i
540 if j < n and pat[j:j + 1] in '!]':
543 if j < n and pat[j:j + 1] in '!]':
541 j += 1
544 j += 1
542 while j < n and pat[j:j + 1] != ']':
545 while j < n and pat[j:j + 1] != ']':
543 j += 1
546 j += 1
544 if j >= n:
547 if j >= n:
545 res += '\\['
548 res += '\\['
546 else:
549 else:
547 stuff = pat[i:j].replace('\\','\\\\')
550 stuff = pat[i:j].replace('\\','\\\\')
548 i = j + 1
551 i = j + 1
549 if stuff[0:1] == '!':
552 if stuff[0:1] == '!':
550 stuff = '^' + stuff[1:]
553 stuff = '^' + stuff[1:]
551 elif stuff[0:1] == '^':
554 elif stuff[0:1] == '^':
552 stuff = '\\' + stuff
555 stuff = '\\' + stuff
553 res = '%s[%s]' % (res, stuff)
556 res = '%s[%s]' % (res, stuff)
554 elif c == '{':
557 elif c == '{':
555 group += 1
558 group += 1
556 res += '(?:'
559 res += '(?:'
557 elif c == '}' and group:
560 elif c == '}' and group:
558 res += ')'
561 res += ')'
559 group -= 1
562 group -= 1
560 elif c == ',' and group:
563 elif c == ',' and group:
561 res += '|'
564 res += '|'
562 elif c == '\\':
565 elif c == '\\':
563 p = peek()
566 p = peek()
564 if p:
567 if p:
565 i += 1
568 i += 1
566 res += escape(p)
569 res += escape(p)
567 else:
570 else:
568 res += escape(c)
571 res += escape(c)
569 else:
572 else:
570 res += escape(c)
573 res += escape(c)
571 return res
574 return res
572
575
573 def _regex(kind, pat, globsuffix):
576 def _regex(kind, pat, globsuffix):
574 '''Convert a (normalized) pattern of any kind into a regular expression.
577 '''Convert a (normalized) pattern of any kind into a regular expression.
575 globsuffix is appended to the regexp of globs.'''
578 globsuffix is appended to the regexp of globs.'''
576 if not pat:
579 if not pat:
577 return ''
580 return ''
578 if kind == 're':
581 if kind == 're':
579 return pat
582 return pat
580 if kind == 'path':
583 if kind == 'path':
581 if pat == '.':
584 if pat == '.':
582 return ''
585 return ''
583 return '^' + util.re.escape(pat) + '(?:/|$)'
586 return '^' + util.re.escape(pat) + '(?:/|$)'
584 if kind == 'rootfilesin':
587 if kind == 'rootfilesin':
585 if pat == '.':
588 if pat == '.':
586 escaped = ''
589 escaped = ''
587 else:
590 else:
588 # Pattern is a directory name.
591 # Pattern is a directory name.
589 escaped = util.re.escape(pat) + '/'
592 escaped = util.re.escape(pat) + '/'
590 # Anything after the pattern must be a non-directory.
593 # Anything after the pattern must be a non-directory.
591 return '^' + escaped + '[^/]+$'
594 return '^' + escaped + '[^/]+$'
592 if kind == 'relglob':
595 if kind == 'relglob':
593 return '(?:|.*/)' + _globre(pat) + globsuffix
596 return '(?:|.*/)' + _globre(pat) + globsuffix
594 if kind == 'relpath':
597 if kind == 'relpath':
595 return util.re.escape(pat) + '(?:/|$)'
598 return util.re.escape(pat) + '(?:/|$)'
596 if kind == 'relre':
599 if kind == 'relre':
597 if pat.startswith('^'):
600 if pat.startswith('^'):
598 return pat
601 return pat
599 return '.*' + pat
602 return '.*' + pat
600 return _globre(pat) + globsuffix
603 return _globre(pat) + globsuffix
601
604
602 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
605 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
603 '''Return regexp string and a matcher function for kindpats.
606 '''Return regexp string and a matcher function for kindpats.
604 globsuffix is appended to the regexp of globs.'''
607 globsuffix is appended to the regexp of globs.'''
605 matchfuncs = []
608 matchfuncs = []
606
609
607 subincludes, kindpats = _expandsubinclude(kindpats, root)
610 subincludes, kindpats = _expandsubinclude(kindpats, root)
608 if subincludes:
611 if subincludes:
609 submatchers = {}
612 submatchers = {}
610 def matchsubinclude(f):
613 def matchsubinclude(f):
611 for prefix, matcherargs in subincludes:
614 for prefix, matcherargs in subincludes:
612 if f.startswith(prefix):
615 if f.startswith(prefix):
613 mf = submatchers.get(prefix)
616 mf = submatchers.get(prefix)
614 if mf is None:
617 if mf is None:
615 mf = match(*matcherargs)
618 mf = match(*matcherargs)
616 submatchers[prefix] = mf
619 submatchers[prefix] = mf
617
620
618 if mf(f[len(prefix):]):
621 if mf(f[len(prefix):]):
619 return True
622 return True
620 return False
623 return False
621 matchfuncs.append(matchsubinclude)
624 matchfuncs.append(matchsubinclude)
622
625
623 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
626 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
624 if fset:
627 if fset:
625 matchfuncs.append(fset.__contains__)
628 matchfuncs.append(fset.__contains__)
626
629
627 regex = ''
630 regex = ''
628 if kindpats:
631 if kindpats:
629 regex, mf = _buildregexmatch(kindpats, globsuffix)
632 regex, mf = _buildregexmatch(kindpats, globsuffix)
630 matchfuncs.append(mf)
633 matchfuncs.append(mf)
631
634
632 if len(matchfuncs) == 1:
635 if len(matchfuncs) == 1:
633 return regex, matchfuncs[0]
636 return regex, matchfuncs[0]
634 else:
637 else:
635 return regex, lambda f: any(mf(f) for mf in matchfuncs)
638 return regex, lambda f: any(mf(f) for mf in matchfuncs)
636
639
637 def _buildregexmatch(kindpats, globsuffix):
640 def _buildregexmatch(kindpats, globsuffix):
638 """Build a match function from a list of kinds and kindpats,
641 """Build a match function from a list of kinds and kindpats,
639 return regexp string and a matcher function."""
642 return regexp string and a matcher function."""
640 try:
643 try:
641 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
644 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
642 for (k, p, s) in kindpats])
645 for (k, p, s) in kindpats])
643 if len(regex) > 20000:
646 if len(regex) > 20000:
644 raise OverflowError
647 raise OverflowError
645 return regex, _rematcher(regex)
648 return regex, _rematcher(regex)
646 except OverflowError:
649 except OverflowError:
647 # We're using a Python with a tiny regex engine and we
650 # We're using a Python with a tiny regex engine and we
648 # made it explode, so we'll divide the pattern list in two
651 # made it explode, so we'll divide the pattern list in two
649 # until it works
652 # until it works
650 l = len(kindpats)
653 l = len(kindpats)
651 if l < 2:
654 if l < 2:
652 raise
655 raise
653 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
656 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
654 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
657 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
655 return regex, lambda s: a(s) or b(s)
658 return regex, lambda s: a(s) or b(s)
656 except re.error:
659 except re.error:
657 for k, p, s in kindpats:
660 for k, p, s in kindpats:
658 try:
661 try:
659 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
662 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
660 except re.error:
663 except re.error:
661 if s:
664 if s:
662 raise error.Abort(_("%s: invalid pattern (%s): %s") %
665 raise error.Abort(_("%s: invalid pattern (%s): %s") %
663 (s, k, p))
666 (s, k, p))
664 else:
667 else:
665 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
668 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
666 raise error.Abort(_("invalid pattern"))
669 raise error.Abort(_("invalid pattern"))
667
670
668 def _patternrootsanddirs(kindpats):
671 def _patternrootsanddirs(kindpats):
669 '''Returns roots and directories corresponding to each pattern.
672 '''Returns roots and directories corresponding to each pattern.
670
673
671 This calculates the roots and directories exactly matching the patterns and
674 This calculates the roots and directories exactly matching the patterns and
672 returns a tuple of (roots, dirs) for each. It does not return other
675 returns a tuple of (roots, dirs) for each. It does not return other
673 directories which may also need to be considered, like the parent
676 directories which may also need to be considered, like the parent
674 directories.
677 directories.
675 '''
678 '''
676 r = []
679 r = []
677 d = []
680 d = []
678 for kind, pat, source in kindpats:
681 for kind, pat, source in kindpats:
679 if kind == 'glob': # find the non-glob prefix
682 if kind == 'glob': # find the non-glob prefix
680 root = []
683 root = []
681 for p in pat.split('/'):
684 for p in pat.split('/'):
682 if '[' in p or '{' in p or '*' in p or '?' in p:
685 if '[' in p or '{' in p or '*' in p or '?' in p:
683 break
686 break
684 root.append(p)
687 root.append(p)
685 r.append('/'.join(root) or '.')
688 r.append('/'.join(root) or '.')
686 elif kind in ('relpath', 'path'):
689 elif kind in ('relpath', 'path'):
687 r.append(pat or '.')
690 r.append(pat or '.')
688 elif kind in ('rootfilesin',):
691 elif kind in ('rootfilesin',):
689 d.append(pat or '.')
692 d.append(pat or '.')
690 else: # relglob, re, relre
693 else: # relglob, re, relre
691 r.append('.')
694 r.append('.')
692 return r, d
695 return r, d
693
696
694 def _roots(kindpats):
697 def _roots(kindpats):
695 '''Returns root directories to match recursively from the given patterns.'''
698 '''Returns root directories to match recursively from the given patterns.'''
696 roots, dirs = _patternrootsanddirs(kindpats)
699 roots, dirs = _patternrootsanddirs(kindpats)
697 return roots
700 return roots
698
701
699 def _rootsanddirs(kindpats):
702 def _rootsanddirs(kindpats):
700 '''Returns roots and exact directories from patterns.
703 '''Returns roots and exact directories from patterns.
701
704
702 roots are directories to match recursively, whereas exact directories should
705 roots are directories to match recursively, whereas exact directories should
703 be matched non-recursively. The returned (roots, dirs) tuple will also
706 be matched non-recursively. The returned (roots, dirs) tuple will also
704 include directories that need to be implicitly considered as either, such as
707 include directories that need to be implicitly considered as either, such as
705 parent directories.
708 parent directories.
706
709
707 >>> _rootsanddirs(\
710 >>> _rootsanddirs(\
708 [('glob', 'g/h/*', ''), ('glob', 'g/h', ''), ('glob', 'g*', '')])
711 [('glob', 'g/h/*', ''), ('glob', 'g/h', ''), ('glob', 'g*', '')])
709 (['g/h', 'g/h', '.'], ['g', '.'])
712 (['g/h', 'g/h', '.'], ['g', '.'])
710 >>> _rootsanddirs(\
713 >>> _rootsanddirs(\
711 [('rootfilesin', 'g/h', ''), ('rootfilesin', '', '')])
714 [('rootfilesin', 'g/h', ''), ('rootfilesin', '', '')])
712 ([], ['g/h', '.', 'g', '.'])
715 ([], ['g/h', '.', 'g', '.'])
713 >>> _rootsanddirs(\
716 >>> _rootsanddirs(\
714 [('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
717 [('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
715 (['r', 'p/p', '.'], ['p', '.'])
718 (['r', 'p/p', '.'], ['p', '.'])
716 >>> _rootsanddirs(\
719 >>> _rootsanddirs(\
717 [('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
720 [('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
718 (['.', '.', '.'], ['.'])
721 (['.', '.', '.'], ['.'])
719 '''
722 '''
720 r, d = _patternrootsanddirs(kindpats)
723 r, d = _patternrootsanddirs(kindpats)
721
724
722 # Append the parents as non-recursive/exact directories, since they must be
725 # Append the parents as non-recursive/exact directories, since they must be
723 # scanned to get to either the roots or the other exact directories.
726 # scanned to get to either the roots or the other exact directories.
724 d.extend(util.dirs(d))
727 d.extend(util.dirs(d))
725 d.extend(util.dirs(r))
728 d.extend(util.dirs(r))
726 # util.dirs() does not include the root directory, so add it manually
729 # util.dirs() does not include the root directory, so add it manually
727 d.append('.')
730 d.append('.')
728
731
729 return r, d
732 return r, d
730
733
731 def _explicitfiles(kindpats):
734 def _explicitfiles(kindpats):
732 '''Returns the potential explicit filenames from the patterns.
735 '''Returns the potential explicit filenames from the patterns.
733
736
734 >>> _explicitfiles([('path', 'foo/bar', '')])
737 >>> _explicitfiles([('path', 'foo/bar', '')])
735 ['foo/bar']
738 ['foo/bar']
736 >>> _explicitfiles([('rootfilesin', 'foo/bar', '')])
739 >>> _explicitfiles([('rootfilesin', 'foo/bar', '')])
737 []
740 []
738 '''
741 '''
739 # Keep only the pattern kinds where one can specify filenames (vs only
742 # Keep only the pattern kinds where one can specify filenames (vs only
740 # directory names).
743 # directory names).
741 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
744 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
742 return _roots(filable)
745 return _roots(filable)
743
746
744 def _anypats(kindpats):
747 def _anypats(kindpats):
745 for kind, pat, source in kindpats:
748 for kind, pat, source in kindpats:
746 if kind in ('glob', 're', 'relglob', 'relre', 'set', 'rootfilesin'):
749 if kind in ('glob', 're', 'relglob', 'relre', 'set', 'rootfilesin'):
747 return True
750 return True
748
751
749 _commentre = None
752 _commentre = None
750
753
751 def readpatternfile(filepath, warn, sourceinfo=False):
754 def readpatternfile(filepath, warn, sourceinfo=False):
752 '''parse a pattern file, returning a list of
755 '''parse a pattern file, returning a list of
753 patterns. These patterns should be given to compile()
756 patterns. These patterns should be given to compile()
754 to be validated and converted into a match function.
757 to be validated and converted into a match function.
755
758
756 trailing white space is dropped.
759 trailing white space is dropped.
757 the escape character is backslash.
760 the escape character is backslash.
758 comments start with #.
761 comments start with #.
759 empty lines are skipped.
762 empty lines are skipped.
760
763
761 lines can be of the following formats:
764 lines can be of the following formats:
762
765
763 syntax: regexp # defaults following lines to non-rooted regexps
766 syntax: regexp # defaults following lines to non-rooted regexps
764 syntax: glob # defaults following lines to non-rooted globs
767 syntax: glob # defaults following lines to non-rooted globs
765 re:pattern # non-rooted regular expression
768 re:pattern # non-rooted regular expression
766 glob:pattern # non-rooted glob
769 glob:pattern # non-rooted glob
767 pattern # pattern of the current default type
770 pattern # pattern of the current default type
768
771
769 if sourceinfo is set, returns a list of tuples:
772 if sourceinfo is set, returns a list of tuples:
770 (pattern, lineno, originalline). This is useful to debug ignore patterns.
773 (pattern, lineno, originalline). This is useful to debug ignore patterns.
771 '''
774 '''
772
775
773 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
776 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
774 'include': 'include', 'subinclude': 'subinclude'}
777 'include': 'include', 'subinclude': 'subinclude'}
775 syntax = 'relre:'
778 syntax = 'relre:'
776 patterns = []
779 patterns = []
777
780
778 fp = open(filepath, 'rb')
781 fp = open(filepath, 'rb')
779 for lineno, line in enumerate(util.iterfile(fp), start=1):
782 for lineno, line in enumerate(util.iterfile(fp), start=1):
780 if "#" in line:
783 if "#" in line:
781 global _commentre
784 global _commentre
782 if not _commentre:
785 if not _commentre:
783 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
786 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
784 # remove comments prefixed by an even number of escapes
787 # remove comments prefixed by an even number of escapes
785 m = _commentre.search(line)
788 m = _commentre.search(line)
786 if m:
789 if m:
787 line = line[:m.end(1)]
790 line = line[:m.end(1)]
788 # fixup properly escaped comments that survived the above
791 # fixup properly escaped comments that survived the above
789 line = line.replace("\\#", "#")
792 line = line.replace("\\#", "#")
790 line = line.rstrip()
793 line = line.rstrip()
791 if not line:
794 if not line:
792 continue
795 continue
793
796
794 if line.startswith('syntax:'):
797 if line.startswith('syntax:'):
795 s = line[7:].strip()
798 s = line[7:].strip()
796 try:
799 try:
797 syntax = syntaxes[s]
800 syntax = syntaxes[s]
798 except KeyError:
801 except KeyError:
799 if warn:
802 if warn:
800 warn(_("%s: ignoring invalid syntax '%s'\n") %
803 warn(_("%s: ignoring invalid syntax '%s'\n") %
801 (filepath, s))
804 (filepath, s))
802 continue
805 continue
803
806
804 linesyntax = syntax
807 linesyntax = syntax
805 for s, rels in syntaxes.iteritems():
808 for s, rels in syntaxes.iteritems():
806 if line.startswith(rels):
809 if line.startswith(rels):
807 linesyntax = rels
810 linesyntax = rels
808 line = line[len(rels):]
811 line = line[len(rels):]
809 break
812 break
810 elif line.startswith(s+':'):
813 elif line.startswith(s+':'):
811 linesyntax = rels
814 linesyntax = rels
812 line = line[len(s) + 1:]
815 line = line[len(s) + 1:]
813 break
816 break
814 if sourceinfo:
817 if sourceinfo:
815 patterns.append((linesyntax + line, lineno, line))
818 patterns.append((linesyntax + line, lineno, line))
816 else:
819 else:
817 patterns.append(linesyntax + line)
820 patterns.append(linesyntax + line)
818 fp.close()
821 fp.close()
819 return patterns
822 return patterns
General Comments 0
You need to be logged in to leave comments. Login now