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