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