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