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