##// END OF EJS Templates
match: add source to kindpats list...
Durham Goode -
r25213:08a8e9da default
parent child Browse files
Show More
@@ -1,548 +1,554
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 import re
8 import re
9 import util, pathutil
9 import util, pathutil
10 from i18n import _
10 from i18n import _
11
11
12 propertycache = util.propertycache
12 propertycache = util.propertycache
13
13
14 def _rematcher(regex):
14 def _rematcher(regex):
15 '''compile the regexp with the best available regexp engine and return a
15 '''compile the regexp with the best available regexp engine and return a
16 matcher function'''
16 matcher function'''
17 m = util.re.compile(regex)
17 m = util.re.compile(regex)
18 try:
18 try:
19 # slightly faster, provided by facebook's re2 bindings
19 # slightly faster, provided by facebook's re2 bindings
20 return m.test_match
20 return m.test_match
21 except AttributeError:
21 except AttributeError:
22 return m.match
22 return m.match
23
23
24 def _expandsets(kindpats, ctx, listsubrepos):
24 def _expandsets(kindpats, ctx, listsubrepos):
25 '''Returns the kindpats list with the 'set' patterns expanded.'''
25 '''Returns the kindpats list with the 'set' patterns expanded.'''
26 fset = set()
26 fset = set()
27 other = []
27 other = []
28
28
29 for kind, pat in kindpats:
29 for kind, pat, source in kindpats:
30 if kind == 'set':
30 if kind == 'set':
31 if not ctx:
31 if not ctx:
32 raise util.Abort("fileset expression with no context")
32 raise util.Abort("fileset expression with no context")
33 s = ctx.getfileset(pat)
33 s = ctx.getfileset(pat)
34 fset.update(s)
34 fset.update(s)
35
35
36 if listsubrepos:
36 if listsubrepos:
37 for subpath in ctx.substate:
37 for subpath in ctx.substate:
38 s = ctx.sub(subpath).getfileset(pat)
38 s = ctx.sub(subpath).getfileset(pat)
39 fset.update(subpath + '/' + f for f in s)
39 fset.update(subpath + '/' + f for f in s)
40
40
41 continue
41 continue
42 other.append((kind, pat))
42 other.append((kind, pat, source))
43 return fset, other
43 return fset, other
44
44
45 def _kindpatsalwaysmatch(kindpats):
45 def _kindpatsalwaysmatch(kindpats):
46 """"Checks whether the kindspats match everything, as e.g.
46 """"Checks whether the kindspats match everything, as e.g.
47 'relpath:.' does.
47 'relpath:.' does.
48 """
48 """
49 for kind, pat in kindpats:
49 for kind, pat, source in kindpats:
50 if pat != '' or kind not in ['relpath', 'glob']:
50 if pat != '' or kind not in ['relpath', 'glob']:
51 return False
51 return False
52 return True
52 return True
53
53
54 class match(object):
54 class match(object):
55 def __init__(self, root, cwd, patterns, include=[], exclude=[],
55 def __init__(self, root, cwd, patterns, include=[], exclude=[],
56 default='glob', exact=False, auditor=None, ctx=None,
56 default='glob', exact=False, auditor=None, ctx=None,
57 listsubrepos=False):
57 listsubrepos=False):
58 """build an object to match a set of file patterns
58 """build an object to match a set of file patterns
59
59
60 arguments:
60 arguments:
61 root - the canonical root of the tree you're matching against
61 root - the canonical root of the tree you're matching against
62 cwd - the current working directory, if relevant
62 cwd - the current working directory, if relevant
63 patterns - patterns to find
63 patterns - patterns to find
64 include - patterns to include (unless they are excluded)
64 include - patterns to include (unless they are excluded)
65 exclude - patterns to exclude (even if they are included)
65 exclude - patterns to exclude (even if they are included)
66 default - if a pattern in patterns has no explicit type, assume this one
66 default - if a pattern in patterns has no explicit type, assume this one
67 exact - patterns are actually filenames (include/exclude still apply)
67 exact - patterns are actually filenames (include/exclude still apply)
68
68
69 a pattern is one of:
69 a pattern is one of:
70 'glob:<glob>' - a glob relative to cwd
70 'glob:<glob>' - a glob relative to cwd
71 're:<regexp>' - a regular expression
71 're:<regexp>' - a regular expression
72 'path:<path>' - a path relative to repository root
72 'path:<path>' - a path relative to repository root
73 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
73 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
74 'relpath:<path>' - a path relative to cwd
74 'relpath:<path>' - a path relative to cwd
75 'relre:<regexp>' - a regexp that needn't match the start of a name
75 'relre:<regexp>' - a regexp that needn't match the start of a name
76 'set:<fileset>' - a fileset expression
76 'set:<fileset>' - a fileset expression
77 '<something>' - a pattern of the specified default type
77 '<something>' - a pattern of the specified default type
78 """
78 """
79
79
80 self._root = root
80 self._root = root
81 self._cwd = cwd
81 self._cwd = cwd
82 self._files = [] # exact files and roots of patterns
82 self._files = [] # exact files and roots of patterns
83 self._anypats = bool(include or exclude)
83 self._anypats = bool(include or exclude)
84 self._always = False
84 self._always = False
85 self._pathrestricted = bool(include or exclude or patterns)
85 self._pathrestricted = bool(include or exclude or patterns)
86
86
87 matchfns = []
87 matchfns = []
88 if include:
88 if include:
89 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
89 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
90 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
90 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
91 listsubrepos)
91 listsubrepos)
92 matchfns.append(im)
92 matchfns.append(im)
93 if exclude:
93 if exclude:
94 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
94 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
95 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
95 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
96 listsubrepos)
96 listsubrepos)
97 matchfns.append(lambda f: not em(f))
97 matchfns.append(lambda f: not em(f))
98 if exact:
98 if exact:
99 if isinstance(patterns, list):
99 if isinstance(patterns, list):
100 self._files = patterns
100 self._files = patterns
101 else:
101 else:
102 self._files = list(patterns)
102 self._files = list(patterns)
103 matchfns.append(self.exact)
103 matchfns.append(self.exact)
104 elif patterns:
104 elif patterns:
105 kindpats = self._normalize(patterns, default, root, cwd, auditor)
105 kindpats = self._normalize(patterns, default, root, cwd, auditor)
106 if not _kindpatsalwaysmatch(kindpats):
106 if not _kindpatsalwaysmatch(kindpats):
107 self._files = _roots(kindpats)
107 self._files = _roots(kindpats)
108 self._anypats = self._anypats or _anypats(kindpats)
108 self._anypats = self._anypats or _anypats(kindpats)
109 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
109 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
110 listsubrepos)
110 listsubrepos)
111 matchfns.append(pm)
111 matchfns.append(pm)
112
112
113 if not matchfns:
113 if not matchfns:
114 m = util.always
114 m = util.always
115 self._always = True
115 self._always = True
116 elif len(matchfns) == 1:
116 elif len(matchfns) == 1:
117 m = matchfns[0]
117 m = matchfns[0]
118 else:
118 else:
119 def m(f):
119 def m(f):
120 for matchfn in matchfns:
120 for matchfn in matchfns:
121 if not matchfn(f):
121 if not matchfn(f):
122 return False
122 return False
123 return True
123 return True
124
124
125 self.matchfn = m
125 self.matchfn = m
126 self._fileroots = set(self._files)
126 self._fileroots = set(self._files)
127
127
128 def __call__(self, fn):
128 def __call__(self, fn):
129 return self.matchfn(fn)
129 return self.matchfn(fn)
130 def __iter__(self):
130 def __iter__(self):
131 for f in self._files:
131 for f in self._files:
132 yield f
132 yield f
133
133
134 # Callbacks related to how the matcher is used by dirstate.walk.
134 # Callbacks related to how the matcher is used by dirstate.walk.
135 # Subscribers to these events must monkeypatch the matcher object.
135 # Subscribers to these events must monkeypatch the matcher object.
136 def bad(self, f, msg):
136 def bad(self, f, msg):
137 '''Callback from dirstate.walk for each explicit file that can't be
137 '''Callback from dirstate.walk for each explicit file that can't be
138 found/accessed, with an error message.'''
138 found/accessed, with an error message.'''
139 pass
139 pass
140
140
141 # If an explicitdir is set, it will be called when an explicitly listed
141 # If an explicitdir is set, it will be called when an explicitly listed
142 # directory is visited.
142 # directory is visited.
143 explicitdir = None
143 explicitdir = None
144
144
145 # If an traversedir is set, it will be called when a directory discovered
145 # If an traversedir is set, it will be called when a directory discovered
146 # by recursive traversal is visited.
146 # by recursive traversal is visited.
147 traversedir = None
147 traversedir = None
148
148
149 def abs(self, f):
149 def abs(self, f):
150 '''Convert a repo path back to path that is relative to the root of the
150 '''Convert a repo path back to path that is relative to the root of the
151 matcher.'''
151 matcher.'''
152 return f
152 return f
153
153
154 def rel(self, f):
154 def rel(self, f):
155 '''Convert repo path back to path that is relative to cwd of matcher.'''
155 '''Convert repo path back to path that is relative to cwd of matcher.'''
156 return util.pathto(self._root, self._cwd, f)
156 return util.pathto(self._root, self._cwd, f)
157
157
158 def uipath(self, f):
158 def uipath(self, f):
159 '''Convert repo path to a display path. If patterns or -I/-X were used
159 '''Convert repo path to a display path. If patterns or -I/-X were used
160 to create this matcher, the display path will be relative to cwd.
160 to create this matcher, the display path will be relative to cwd.
161 Otherwise it is relative to the root of the repo.'''
161 Otherwise it is relative to the root of the repo.'''
162 return (self._pathrestricted and self.rel(f)) or self.abs(f)
162 return (self._pathrestricted and self.rel(f)) or self.abs(f)
163
163
164 def files(self):
164 def files(self):
165 '''Explicitly listed files or patterns or roots:
165 '''Explicitly listed files or patterns or roots:
166 if no patterns or .always(): empty list,
166 if no patterns or .always(): empty list,
167 if exact: list exact files,
167 if exact: list exact files,
168 if not .anypats(): list all files and dirs,
168 if not .anypats(): list all files and dirs,
169 else: optimal roots'''
169 else: optimal roots'''
170 return self._files
170 return self._files
171
171
172 @propertycache
172 @propertycache
173 def _dirs(self):
173 def _dirs(self):
174 return set(util.dirs(self._fileroots)) | set(['.'])
174 return set(util.dirs(self._fileroots)) | set(['.'])
175
175
176 def visitdir(self, dir):
176 def visitdir(self, dir):
177 return (not self._fileroots or '.' in self._fileroots or
177 return (not self._fileroots or '.' in self._fileroots or
178 dir in self._fileroots or dir in self._dirs or
178 dir in self._fileroots or dir in self._dirs or
179 any(parentdir in self._fileroots
179 any(parentdir in self._fileroots
180 for parentdir in util.finddirs(dir)))
180 for parentdir in util.finddirs(dir)))
181
181
182 def exact(self, f):
182 def exact(self, f):
183 '''Returns True if f is in .files().'''
183 '''Returns True if f is in .files().'''
184 return f in self._fileroots
184 return f in self._fileroots
185
185
186 def anypats(self):
186 def anypats(self):
187 '''Matcher uses patterns or include/exclude.'''
187 '''Matcher uses patterns or include/exclude.'''
188 return self._anypats
188 return self._anypats
189
189
190 def always(self):
190 def always(self):
191 '''Matcher will match everything and .files() will be empty
191 '''Matcher will match everything and .files() will be empty
192 - optimization might be possible and necessary.'''
192 - optimization might be possible and necessary.'''
193 return self._always
193 return self._always
194
194
195 def ispartial(self):
195 def ispartial(self):
196 '''True if the matcher won't always match.
196 '''True if the matcher won't always match.
197
197
198 Although it's just the inverse of _always in this implementation,
198 Although it's just the inverse of _always in this implementation,
199 an extenion such as narrowhg might make it return something
199 an extenion such as narrowhg might make it return something
200 slightly different.'''
200 slightly different.'''
201 return not self._always
201 return not self._always
202
202
203 def isexact(self):
203 def isexact(self):
204 return self.matchfn == self.exact
204 return self.matchfn == self.exact
205
205
206 def _normalize(self, patterns, default, root, cwd, auditor):
206 def _normalize(self, patterns, default, root, cwd, auditor):
207 '''Convert 'kind:pat' from the patterns list to tuples with kind and
207 '''Convert 'kind:pat' from the patterns list to tuples with kind and
208 normalized and rooted patterns and with listfiles expanded.'''
208 normalized and rooted patterns and with listfiles expanded.'''
209 kindpats = []
209 kindpats = []
210 for kind, pat in [_patsplit(p, default) for p in patterns]:
210 for kind, pat in [_patsplit(p, default) for p in patterns]:
211 if kind in ('glob', 'relpath'):
211 if kind in ('glob', 'relpath'):
212 pat = pathutil.canonpath(root, cwd, pat, auditor)
212 pat = pathutil.canonpath(root, cwd, pat, auditor)
213 elif kind in ('relglob', 'path'):
213 elif kind in ('relglob', 'path'):
214 pat = util.normpath(pat)
214 pat = util.normpath(pat)
215 elif kind in ('listfile', 'listfile0'):
215 elif kind in ('listfile', 'listfile0'):
216 try:
216 try:
217 files = util.readfile(pat)
217 files = util.readfile(pat)
218 if kind == 'listfile0':
218 if kind == 'listfile0':
219 files = files.split('\0')
219 files = files.split('\0')
220 else:
220 else:
221 files = files.splitlines()
221 files = files.splitlines()
222 files = [f for f in files if f]
222 files = [f for f in files if f]
223 except EnvironmentError:
223 except EnvironmentError:
224 raise util.Abort(_("unable to read file list (%s)") % pat)
224 raise util.Abort(_("unable to read file list (%s)") % pat)
225 kindpats += self._normalize(files, default, root, cwd, auditor)
225 for k, p, source in self._normalize(files, default, root, cwd,
226 auditor):
227 kindpats.append((k, p, pat))
226 continue
228 continue
227 # else: re or relre - which cannot be normalized
229 # else: re or relre - which cannot be normalized
228 kindpats.append((kind, pat))
230 kindpats.append((kind, pat, ''))
229 return kindpats
231 return kindpats
230
232
231 def exact(root, cwd, files):
233 def exact(root, cwd, files):
232 return match(root, cwd, files, exact=True)
234 return match(root, cwd, files, exact=True)
233
235
234 def always(root, cwd):
236 def always(root, cwd):
235 return match(root, cwd, [])
237 return match(root, cwd, [])
236
238
237 class narrowmatcher(match):
239 class narrowmatcher(match):
238 """Adapt a matcher to work on a subdirectory only.
240 """Adapt a matcher to work on a subdirectory only.
239
241
240 The paths are remapped to remove/insert the path as needed:
242 The paths are remapped to remove/insert the path as needed:
241
243
242 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
244 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
243 >>> m2 = narrowmatcher('sub', m1)
245 >>> m2 = narrowmatcher('sub', m1)
244 >>> bool(m2('a.txt'))
246 >>> bool(m2('a.txt'))
245 False
247 False
246 >>> bool(m2('b.txt'))
248 >>> bool(m2('b.txt'))
247 True
249 True
248 >>> bool(m2.matchfn('a.txt'))
250 >>> bool(m2.matchfn('a.txt'))
249 False
251 False
250 >>> bool(m2.matchfn('b.txt'))
252 >>> bool(m2.matchfn('b.txt'))
251 True
253 True
252 >>> m2.files()
254 >>> m2.files()
253 ['b.txt']
255 ['b.txt']
254 >>> m2.exact('b.txt')
256 >>> m2.exact('b.txt')
255 True
257 True
256 >>> util.pconvert(m2.rel('b.txt'))
258 >>> util.pconvert(m2.rel('b.txt'))
257 'sub/b.txt'
259 'sub/b.txt'
258 >>> def bad(f, msg):
260 >>> def bad(f, msg):
259 ... print "%s: %s" % (f, msg)
261 ... print "%s: %s" % (f, msg)
260 >>> m1.bad = bad
262 >>> m1.bad = bad
261 >>> m2.bad('x.txt', 'No such file')
263 >>> m2.bad('x.txt', 'No such file')
262 sub/x.txt: No such file
264 sub/x.txt: No such file
263 >>> m2.abs('c.txt')
265 >>> m2.abs('c.txt')
264 'sub/c.txt'
266 'sub/c.txt'
265 """
267 """
266
268
267 def __init__(self, path, matcher):
269 def __init__(self, path, matcher):
268 self._root = matcher._root
270 self._root = matcher._root
269 self._cwd = matcher._cwd
271 self._cwd = matcher._cwd
270 self._path = path
272 self._path = path
271 self._matcher = matcher
273 self._matcher = matcher
272 self._always = matcher._always
274 self._always = matcher._always
273 self._pathrestricted = matcher._pathrestricted
275 self._pathrestricted = matcher._pathrestricted
274
276
275 self._files = [f[len(path) + 1:] for f in matcher._files
277 self._files = [f[len(path) + 1:] for f in matcher._files
276 if f.startswith(path + "/")]
278 if f.startswith(path + "/")]
277
279
278 # If the parent repo had a path to this subrepo and no patterns are
280 # If the parent repo had a path to this subrepo and no patterns are
279 # specified, this submatcher always matches.
281 # specified, this submatcher always matches.
280 if not self._always and not matcher._anypats:
282 if not self._always and not matcher._anypats:
281 self._always = any(f == path for f in matcher._files)
283 self._always = any(f == path for f in matcher._files)
282
284
283 self._anypats = matcher._anypats
285 self._anypats = matcher._anypats
284 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
286 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
285 self._fileroots = set(self._files)
287 self._fileroots = set(self._files)
286
288
287 def abs(self, f):
289 def abs(self, f):
288 return self._matcher.abs(self._path + "/" + f)
290 return self._matcher.abs(self._path + "/" + f)
289
291
290 def bad(self, f, msg):
292 def bad(self, f, msg):
291 self._matcher.bad(self._path + "/" + f, msg)
293 self._matcher.bad(self._path + "/" + f, msg)
292
294
293 def rel(self, f):
295 def rel(self, f):
294 return self._matcher.rel(self._path + "/" + f)
296 return self._matcher.rel(self._path + "/" + f)
295
297
296 class icasefsmatcher(match):
298 class icasefsmatcher(match):
297 """A matcher for wdir on case insensitive filesystems, which normalizes the
299 """A matcher for wdir on case insensitive filesystems, which normalizes the
298 given patterns to the case in the filesystem.
300 given patterns to the case in the filesystem.
299 """
301 """
300
302
301 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
303 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
302 ctx, listsubrepos=False):
304 ctx, listsubrepos=False):
303 init = super(icasefsmatcher, self).__init__
305 init = super(icasefsmatcher, self).__init__
304 self._dsnormalize = ctx.repo().dirstate.normalize
306 self._dsnormalize = ctx.repo().dirstate.normalize
305
307
306 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
308 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
307 ctx=ctx, listsubrepos=listsubrepos)
309 ctx=ctx, listsubrepos=listsubrepos)
308
310
309 # m.exact(file) must be based off of the actual user input, otherwise
311 # m.exact(file) must be based off of the actual user input, otherwise
310 # inexact case matches are treated as exact, and not noted without -v.
312 # inexact case matches are treated as exact, and not noted without -v.
311 if self._files:
313 if self._files:
312 self._fileroots = set(_roots(self._kp))
314 self._fileroots = set(_roots(self._kp))
313
315
314 def _normalize(self, patterns, default, root, cwd, auditor):
316 def _normalize(self, patterns, default, root, cwd, auditor):
315 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
317 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
316 root, cwd, auditor)
318 root, cwd, auditor)
317 kindpats = []
319 kindpats = []
318 for kind, pats in self._kp:
320 for kind, pats, source in self._kp:
319 if kind not in ('re', 'relre'): # regex can't be normalized
321 if kind not in ('re', 'relre'): # regex can't be normalized
320 pats = self._dsnormalize(pats)
322 pats = self._dsnormalize(pats)
321 kindpats.append((kind, pats))
323 kindpats.append((kind, pats, source))
322 return kindpats
324 return kindpats
323
325
324 def patkind(pattern, default=None):
326 def patkind(pattern, default=None):
325 '''If pattern is 'kind:pat' with a known kind, return kind.'''
327 '''If pattern is 'kind:pat' with a known kind, return kind.'''
326 return _patsplit(pattern, default)[0]
328 return _patsplit(pattern, default)[0]
327
329
328 def _patsplit(pattern, default):
330 def _patsplit(pattern, default):
329 """Split a string into the optional pattern kind prefix and the actual
331 """Split a string into the optional pattern kind prefix and the actual
330 pattern."""
332 pattern."""
331 if ':' in pattern:
333 if ':' in pattern:
332 kind, pat = pattern.split(':', 1)
334 kind, pat = pattern.split(':', 1)
333 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
335 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
334 'listfile', 'listfile0', 'set'):
336 'listfile', 'listfile0', 'set'):
335 return kind, pat
337 return kind, pat
336 return default, pattern
338 return default, pattern
337
339
338 def _globre(pat):
340 def _globre(pat):
339 r'''Convert an extended glob string to a regexp string.
341 r'''Convert an extended glob string to a regexp string.
340
342
341 >>> print _globre(r'?')
343 >>> print _globre(r'?')
342 .
344 .
343 >>> print _globre(r'*')
345 >>> print _globre(r'*')
344 [^/]*
346 [^/]*
345 >>> print _globre(r'**')
347 >>> print _globre(r'**')
346 .*
348 .*
347 >>> print _globre(r'**/a')
349 >>> print _globre(r'**/a')
348 (?:.*/)?a
350 (?:.*/)?a
349 >>> print _globre(r'a/**/b')
351 >>> print _globre(r'a/**/b')
350 a\/(?:.*/)?b
352 a\/(?:.*/)?b
351 >>> print _globre(r'[a*?!^][^b][!c]')
353 >>> print _globre(r'[a*?!^][^b][!c]')
352 [a*?!^][\^b][^c]
354 [a*?!^][\^b][^c]
353 >>> print _globre(r'{a,b}')
355 >>> print _globre(r'{a,b}')
354 (?:a|b)
356 (?:a|b)
355 >>> print _globre(r'.\*\?')
357 >>> print _globre(r'.\*\?')
356 \.\*\?
358 \.\*\?
357 '''
359 '''
358 i, n = 0, len(pat)
360 i, n = 0, len(pat)
359 res = ''
361 res = ''
360 group = 0
362 group = 0
361 escape = util.re.escape
363 escape = util.re.escape
362 def peek():
364 def peek():
363 return i < n and pat[i]
365 return i < n and pat[i]
364 while i < n:
366 while i < n:
365 c = pat[i]
367 c = pat[i]
366 i += 1
368 i += 1
367 if c not in '*?[{},\\':
369 if c not in '*?[{},\\':
368 res += escape(c)
370 res += escape(c)
369 elif c == '*':
371 elif c == '*':
370 if peek() == '*':
372 if peek() == '*':
371 i += 1
373 i += 1
372 if peek() == '/':
374 if peek() == '/':
373 i += 1
375 i += 1
374 res += '(?:.*/)?'
376 res += '(?:.*/)?'
375 else:
377 else:
376 res += '.*'
378 res += '.*'
377 else:
379 else:
378 res += '[^/]*'
380 res += '[^/]*'
379 elif c == '?':
381 elif c == '?':
380 res += '.'
382 res += '.'
381 elif c == '[':
383 elif c == '[':
382 j = i
384 j = i
383 if j < n and pat[j] in '!]':
385 if j < n and pat[j] in '!]':
384 j += 1
386 j += 1
385 while j < n and pat[j] != ']':
387 while j < n and pat[j] != ']':
386 j += 1
388 j += 1
387 if j >= n:
389 if j >= n:
388 res += '\\['
390 res += '\\['
389 else:
391 else:
390 stuff = pat[i:j].replace('\\','\\\\')
392 stuff = pat[i:j].replace('\\','\\\\')
391 i = j + 1
393 i = j + 1
392 if stuff[0] == '!':
394 if stuff[0] == '!':
393 stuff = '^' + stuff[1:]
395 stuff = '^' + stuff[1:]
394 elif stuff[0] == '^':
396 elif stuff[0] == '^':
395 stuff = '\\' + stuff
397 stuff = '\\' + stuff
396 res = '%s[%s]' % (res, stuff)
398 res = '%s[%s]' % (res, stuff)
397 elif c == '{':
399 elif c == '{':
398 group += 1
400 group += 1
399 res += '(?:'
401 res += '(?:'
400 elif c == '}' and group:
402 elif c == '}' and group:
401 res += ')'
403 res += ')'
402 group -= 1
404 group -= 1
403 elif c == ',' and group:
405 elif c == ',' and group:
404 res += '|'
406 res += '|'
405 elif c == '\\':
407 elif c == '\\':
406 p = peek()
408 p = peek()
407 if p:
409 if p:
408 i += 1
410 i += 1
409 res += escape(p)
411 res += escape(p)
410 else:
412 else:
411 res += escape(c)
413 res += escape(c)
412 else:
414 else:
413 res += escape(c)
415 res += escape(c)
414 return res
416 return res
415
417
416 def _regex(kind, pat, globsuffix):
418 def _regex(kind, pat, globsuffix):
417 '''Convert a (normalized) pattern of any kind into a regular expression.
419 '''Convert a (normalized) pattern of any kind into a regular expression.
418 globsuffix is appended to the regexp of globs.'''
420 globsuffix is appended to the regexp of globs.'''
419 if not pat:
421 if not pat:
420 return ''
422 return ''
421 if kind == 're':
423 if kind == 're':
422 return pat
424 return pat
423 if kind == 'path':
425 if kind == 'path':
424 return '^' + util.re.escape(pat) + '(?:/|$)'
426 return '^' + util.re.escape(pat) + '(?:/|$)'
425 if kind == 'relglob':
427 if kind == 'relglob':
426 return '(?:|.*/)' + _globre(pat) + globsuffix
428 return '(?:|.*/)' + _globre(pat) + globsuffix
427 if kind == 'relpath':
429 if kind == 'relpath':
428 return util.re.escape(pat) + '(?:/|$)'
430 return util.re.escape(pat) + '(?:/|$)'
429 if kind == 'relre':
431 if kind == 'relre':
430 if pat.startswith('^'):
432 if pat.startswith('^'):
431 return pat
433 return pat
432 return '.*' + pat
434 return '.*' + pat
433 return _globre(pat) + globsuffix
435 return _globre(pat) + globsuffix
434
436
435 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos):
437 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos):
436 '''Return regexp string and a matcher function for kindpats.
438 '''Return regexp string and a matcher function for kindpats.
437 globsuffix is appended to the regexp of globs.'''
439 globsuffix is appended to the regexp of globs.'''
438 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
440 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
439 if not kindpats:
441 if not kindpats:
440 return "", fset.__contains__
442 return "", fset.__contains__
441
443
442 regex, mf = _buildregexmatch(kindpats, globsuffix)
444 regex, mf = _buildregexmatch(kindpats, globsuffix)
443 if fset:
445 if fset:
444 return regex, lambda f: f in fset or mf(f)
446 return regex, lambda f: f in fset or mf(f)
445 return regex, mf
447 return regex, mf
446
448
447 def _buildregexmatch(kindpats, globsuffix):
449 def _buildregexmatch(kindpats, globsuffix):
448 """Build a match function from a list of kinds and kindpats,
450 """Build a match function from a list of kinds and kindpats,
449 return regexp string and a matcher function."""
451 return regexp string and a matcher function."""
450 try:
452 try:
451 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
453 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
452 for (k, p) in kindpats])
454 for (k, p, s) in kindpats])
453 if len(regex) > 20000:
455 if len(regex) > 20000:
454 raise OverflowError
456 raise OverflowError
455 return regex, _rematcher(regex)
457 return regex, _rematcher(regex)
456 except OverflowError:
458 except OverflowError:
457 # We're using a Python with a tiny regex engine and we
459 # We're using a Python with a tiny regex engine and we
458 # made it explode, so we'll divide the pattern list in two
460 # made it explode, so we'll divide the pattern list in two
459 # until it works
461 # until it works
460 l = len(kindpats)
462 l = len(kindpats)
461 if l < 2:
463 if l < 2:
462 raise
464 raise
463 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
465 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
464 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
466 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
465 return regex, lambda s: a(s) or b(s)
467 return regex, lambda s: a(s) or b(s)
466 except re.error:
468 except re.error:
467 for k, p in kindpats:
469 for k, p, s in kindpats:
468 try:
470 try:
469 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
471 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
470 except re.error:
472 except re.error:
473 if s:
474 raise util.Abort(_("%s: invalid pattern (%s): %s") %
475 (s, k, p))
476 else:
471 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
477 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
472 raise util.Abort(_("invalid pattern"))
478 raise util.Abort(_("invalid pattern"))
473
479
474 def _roots(kindpats):
480 def _roots(kindpats):
475 '''return roots and exact explicitly listed files from patterns
481 '''return roots and exact explicitly listed files from patterns
476
482
477 >>> _roots([('glob', 'g/*'), ('glob', 'g'), ('glob', 'g*')])
483 >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')])
478 ['g', 'g', '.']
484 ['g', 'g', '.']
479 >>> _roots([('relpath', 'r'), ('path', 'p/p'), ('path', '')])
485 >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
480 ['r', 'p/p', '.']
486 ['r', 'p/p', '.']
481 >>> _roots([('relglob', 'rg*'), ('re', 're/'), ('relre', 'rr')])
487 >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
482 ['.', '.', '.']
488 ['.', '.', '.']
483 '''
489 '''
484 r = []
490 r = []
485 for kind, pat in kindpats:
491 for kind, pat, source in kindpats:
486 if kind == 'glob': # find the non-glob prefix
492 if kind == 'glob': # find the non-glob prefix
487 root = []
493 root = []
488 for p in pat.split('/'):
494 for p in pat.split('/'):
489 if '[' in p or '{' in p or '*' in p or '?' in p:
495 if '[' in p or '{' in p or '*' in p or '?' in p:
490 break
496 break
491 root.append(p)
497 root.append(p)
492 r.append('/'.join(root) or '.')
498 r.append('/'.join(root) or '.')
493 elif kind in ('relpath', 'path'):
499 elif kind in ('relpath', 'path'):
494 r.append(pat or '.')
500 r.append(pat or '.')
495 else: # relglob, re, relre
501 else: # relglob, re, relre
496 r.append('.')
502 r.append('.')
497 return r
503 return r
498
504
499 def _anypats(kindpats):
505 def _anypats(kindpats):
500 for kind, pat in kindpats:
506 for kind, pat, source in kindpats:
501 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
507 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
502 return True
508 return True
503
509
504 _commentre = None
510 _commentre = None
505
511
506 def readpatternfile(filepath, warn):
512 def readpatternfile(filepath, warn):
507 '''parse a pattern file, returning a list of
513 '''parse a pattern file, returning a list of
508 patterns. These patterns should be given to compile()
514 patterns. These patterns should be given to compile()
509 to be validated and converted into a match function.'''
515 to be validated and converted into a match function.'''
510 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
516 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
511 syntax = 'relre:'
517 syntax = 'relre:'
512 patterns = []
518 patterns = []
513
519
514 fp = open(filepath)
520 fp = open(filepath)
515 for line in fp:
521 for line in fp:
516 if "#" in line:
522 if "#" in line:
517 global _commentre
523 global _commentre
518 if not _commentre:
524 if not _commentre:
519 _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
525 _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
520 # remove comments prefixed by an even number of escapes
526 # remove comments prefixed by an even number of escapes
521 line = _commentre.sub(r'\1', line)
527 line = _commentre.sub(r'\1', line)
522 # fixup properly escaped comments that survived the above
528 # fixup properly escaped comments that survived the above
523 line = line.replace("\\#", "#")
529 line = line.replace("\\#", "#")
524 line = line.rstrip()
530 line = line.rstrip()
525 if not line:
531 if not line:
526 continue
532 continue
527
533
528 if line.startswith('syntax:'):
534 if line.startswith('syntax:'):
529 s = line[7:].strip()
535 s = line[7:].strip()
530 try:
536 try:
531 syntax = syntaxes[s]
537 syntax = syntaxes[s]
532 except KeyError:
538 except KeyError:
533 warn(_("%s: ignoring invalid syntax '%s'\n") % (filepath, s))
539 warn(_("%s: ignoring invalid syntax '%s'\n") % (filepath, s))
534 continue
540 continue
535
541
536 linesyntax = syntax
542 linesyntax = syntax
537 for s, rels in syntaxes.iteritems():
543 for s, rels in syntaxes.iteritems():
538 if line.startswith(rels):
544 if line.startswith(rels):
539 linesyntax = rels
545 linesyntax = rels
540 line = line[len(rels):]
546 line = line[len(rels):]
541 break
547 break
542 elif line.startswith(s+':'):
548 elif line.startswith(s+':'):
543 linesyntax = rels
549 linesyntax = rels
544 line = line[len(s) + 1:]
550 line = line[len(s) + 1:]
545 break
551 break
546 patterns.append(linesyntax + line)
552 patterns.append(linesyntax + line)
547 fp.close()
553 fp.close()
548 return patterns
554 return patterns
General Comments 0
You need to be logged in to leave comments. Login now