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