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