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