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