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