##// END OF EJS Templates
match: resolve 'set:' patterns first in _buildmatch()...
Yuya Nishihara -
r38598:ec0cee4c default
parent child Browse files
Show More
@@ -1,1048 +1,1048 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, print_function
8 from __future__ import absolute_import, print_function
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 encoding,
16 encoding,
17 error,
17 error,
18 pathutil,
18 pathutil,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 )
21 )
22 from .utils import (
22 from .utils import (
23 stringutil,
23 stringutil,
24 )
24 )
25
25
26 allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
26 allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
27 'listfile', 'listfile0', 'set', 'include', 'subinclude',
27 'listfile', 'listfile0', 'set', 'include', 'subinclude',
28 'rootfilesin')
28 'rootfilesin')
29 cwdrelativepatternkinds = ('relpath', 'glob')
29 cwdrelativepatternkinds = ('relpath', 'glob')
30
30
31 propertycache = util.propertycache
31 propertycache = util.propertycache
32
32
33 def _rematcher(regex):
33 def _rematcher(regex):
34 '''compile the regexp with the best available regexp engine and return a
34 '''compile the regexp with the best available regexp engine and return a
35 matcher function'''
35 matcher function'''
36 m = util.re.compile(regex)
36 m = util.re.compile(regex)
37 try:
37 try:
38 # slightly faster, provided by facebook's re2 bindings
38 # slightly faster, provided by facebook's re2 bindings
39 return m.test_match
39 return m.test_match
40 except AttributeError:
40 except AttributeError:
41 return m.match
41 return m.match
42
42
43 def _expandsets(kindpats, ctx, listsubrepos):
43 def _expandsets(kindpats, ctx, listsubrepos):
44 '''Returns the kindpats list with the 'set' patterns expanded.'''
44 '''Returns the kindpats list with the 'set' patterns expanded.'''
45 fset = set()
45 fset = set()
46 other = []
46 other = []
47
47
48 for kind, pat, source in kindpats:
48 for kind, pat, source in kindpats:
49 if kind == 'set':
49 if kind == 'set':
50 if not ctx:
50 if not ctx:
51 raise error.ProgrammingError("fileset expression with no "
51 raise error.ProgrammingError("fileset expression with no "
52 "context")
52 "context")
53 s = ctx.getfileset(pat)
53 s = ctx.getfileset(pat)
54 fset.update(s)
54 fset.update(s)
55
55
56 if listsubrepos:
56 if listsubrepos:
57 for subpath in ctx.substate:
57 for subpath in ctx.substate:
58 s = ctx.sub(subpath).getfileset(pat)
58 s = ctx.sub(subpath).getfileset(pat)
59 fset.update(subpath + '/' + f for f in s)
59 fset.update(subpath + '/' + f for f in s)
60
60
61 continue
61 continue
62 other.append((kind, pat, source))
62 other.append((kind, pat, source))
63 return fset, other
63 return fset, other
64
64
65 def _expandsubinclude(kindpats, root):
65 def _expandsubinclude(kindpats, root):
66 '''Returns the list of subinclude matcher args and the kindpats without the
66 '''Returns the list of subinclude matcher args and the kindpats without the
67 subincludes in it.'''
67 subincludes in it.'''
68 relmatchers = []
68 relmatchers = []
69 other = []
69 other = []
70
70
71 for kind, pat, source in kindpats:
71 for kind, pat, source in kindpats:
72 if kind == 'subinclude':
72 if kind == 'subinclude':
73 sourceroot = pathutil.dirname(util.normpath(source))
73 sourceroot = pathutil.dirname(util.normpath(source))
74 pat = util.pconvert(pat)
74 pat = util.pconvert(pat)
75 path = pathutil.join(sourceroot, pat)
75 path = pathutil.join(sourceroot, pat)
76
76
77 newroot = pathutil.dirname(path)
77 newroot = pathutil.dirname(path)
78 matcherargs = (newroot, '', [], ['include:%s' % path])
78 matcherargs = (newroot, '', [], ['include:%s' % path])
79
79
80 prefix = pathutil.canonpath(root, root, newroot)
80 prefix = pathutil.canonpath(root, root, newroot)
81 if prefix:
81 if prefix:
82 prefix += '/'
82 prefix += '/'
83 relmatchers.append((prefix, matcherargs))
83 relmatchers.append((prefix, matcherargs))
84 else:
84 else:
85 other.append((kind, pat, source))
85 other.append((kind, pat, source))
86
86
87 return relmatchers, other
87 return relmatchers, other
88
88
89 def _kindpatsalwaysmatch(kindpats):
89 def _kindpatsalwaysmatch(kindpats):
90 """"Checks whether the kindspats match everything, as e.g.
90 """"Checks whether the kindspats match everything, as e.g.
91 'relpath:.' does.
91 'relpath:.' does.
92 """
92 """
93 for kind, pat, source in kindpats:
93 for kind, pat, source in kindpats:
94 if pat != '' or kind not in ['relpath', 'glob']:
94 if pat != '' or kind not in ['relpath', 'glob']:
95 return False
95 return False
96 return True
96 return True
97
97
98 def match(root, cwd, patterns=None, include=None, exclude=None, default='glob',
98 def match(root, cwd, patterns=None, include=None, exclude=None, default='glob',
99 exact=False, auditor=None, ctx=None, listsubrepos=False, warn=None,
99 exact=False, auditor=None, ctx=None, listsubrepos=False, warn=None,
100 badfn=None, icasefs=False):
100 badfn=None, icasefs=False):
101 """build an object to match a set of file patterns
101 """build an object to match a set of file patterns
102
102
103 arguments:
103 arguments:
104 root - the canonical root of the tree you're matching against
104 root - the canonical root of the tree you're matching against
105 cwd - the current working directory, if relevant
105 cwd - the current working directory, if relevant
106 patterns - patterns to find
106 patterns - patterns to find
107 include - patterns to include (unless they are excluded)
107 include - patterns to include (unless they are excluded)
108 exclude - patterns to exclude (even if they are included)
108 exclude - patterns to exclude (even if they are included)
109 default - if a pattern in patterns has no explicit type, assume this one
109 default - if a pattern in patterns has no explicit type, assume this one
110 exact - patterns are actually filenames (include/exclude still apply)
110 exact - patterns are actually filenames (include/exclude still apply)
111 warn - optional function used for printing warnings
111 warn - optional function used for printing warnings
112 badfn - optional bad() callback for this matcher instead of the default
112 badfn - optional bad() callback for this matcher instead of the default
113 icasefs - make a matcher for wdir on case insensitive filesystems, which
113 icasefs - make a matcher for wdir on case insensitive filesystems, which
114 normalizes the given patterns to the case in the filesystem
114 normalizes the given patterns to the case in the filesystem
115
115
116 a pattern is one of:
116 a pattern is one of:
117 'glob:<glob>' - a glob relative to cwd
117 'glob:<glob>' - a glob relative to cwd
118 're:<regexp>' - a regular expression
118 're:<regexp>' - a regular expression
119 'path:<path>' - a path relative to repository root, which is matched
119 'path:<path>' - a path relative to repository root, which is matched
120 recursively
120 recursively
121 'rootfilesin:<path>' - a path relative to repository root, which is
121 'rootfilesin:<path>' - a path relative to repository root, which is
122 matched non-recursively (will not match subdirectories)
122 matched non-recursively (will not match subdirectories)
123 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
123 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
124 'relpath:<path>' - a path relative to cwd
124 'relpath:<path>' - a path relative to cwd
125 'relre:<regexp>' - a regexp that needn't match the start of a name
125 'relre:<regexp>' - a regexp that needn't match the start of a name
126 'set:<fileset>' - a fileset expression
126 'set:<fileset>' - a fileset expression
127 'include:<path>' - a file of patterns to read and include
127 'include:<path>' - a file of patterns to read and include
128 'subinclude:<path>' - a file of patterns to match against files under
128 'subinclude:<path>' - a file of patterns to match against files under
129 the same directory
129 the same directory
130 '<something>' - a pattern of the specified default type
130 '<something>' - a pattern of the specified default type
131 """
131 """
132 normalize = _donormalize
132 normalize = _donormalize
133 if icasefs:
133 if icasefs:
134 if exact:
134 if exact:
135 raise error.ProgrammingError("a case-insensitive exact matcher "
135 raise error.ProgrammingError("a case-insensitive exact matcher "
136 "doesn't make sense")
136 "doesn't make sense")
137 dirstate = ctx.repo().dirstate
137 dirstate = ctx.repo().dirstate
138 dsnormalize = dirstate.normalize
138 dsnormalize = dirstate.normalize
139
139
140 def normalize(patterns, default, root, cwd, auditor, warn):
140 def normalize(patterns, default, root, cwd, auditor, warn):
141 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
141 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
142 kindpats = []
142 kindpats = []
143 for kind, pats, source in kp:
143 for kind, pats, source in kp:
144 if kind not in ('re', 'relre'): # regex can't be normalized
144 if kind not in ('re', 'relre'): # regex can't be normalized
145 p = pats
145 p = pats
146 pats = dsnormalize(pats)
146 pats = dsnormalize(pats)
147
147
148 # Preserve the original to handle a case only rename.
148 # Preserve the original to handle a case only rename.
149 if p != pats and p in dirstate:
149 if p != pats and p in dirstate:
150 kindpats.append((kind, p, source))
150 kindpats.append((kind, p, source))
151
151
152 kindpats.append((kind, pats, source))
152 kindpats.append((kind, pats, source))
153 return kindpats
153 return kindpats
154
154
155 if exact:
155 if exact:
156 m = exactmatcher(root, cwd, patterns, badfn)
156 m = exactmatcher(root, cwd, patterns, badfn)
157 elif patterns:
157 elif patterns:
158 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
158 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
159 if _kindpatsalwaysmatch(kindpats):
159 if _kindpatsalwaysmatch(kindpats):
160 m = alwaysmatcher(root, cwd, badfn, relativeuipath=True)
160 m = alwaysmatcher(root, cwd, badfn, relativeuipath=True)
161 else:
161 else:
162 m = patternmatcher(root, cwd, kindpats, ctx=ctx,
162 m = patternmatcher(root, cwd, kindpats, ctx=ctx,
163 listsubrepos=listsubrepos, badfn=badfn)
163 listsubrepos=listsubrepos, badfn=badfn)
164 else:
164 else:
165 # It's a little strange that no patterns means to match everything.
165 # It's a little strange that no patterns means to match everything.
166 # Consider changing this to match nothing (probably using nevermatcher).
166 # Consider changing this to match nothing (probably using nevermatcher).
167 m = alwaysmatcher(root, cwd, badfn)
167 m = alwaysmatcher(root, cwd, badfn)
168
168
169 if include:
169 if include:
170 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
170 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
171 im = includematcher(root, cwd, kindpats, ctx=ctx,
171 im = includematcher(root, cwd, kindpats, ctx=ctx,
172 listsubrepos=listsubrepos, badfn=None)
172 listsubrepos=listsubrepos, badfn=None)
173 m = intersectmatchers(m, im)
173 m = intersectmatchers(m, im)
174 if exclude:
174 if exclude:
175 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
175 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
176 em = includematcher(root, cwd, kindpats, ctx=ctx,
176 em = includematcher(root, cwd, kindpats, ctx=ctx,
177 listsubrepos=listsubrepos, badfn=None)
177 listsubrepos=listsubrepos, badfn=None)
178 m = differencematcher(m, em)
178 m = differencematcher(m, em)
179 return m
179 return m
180
180
181 def exact(root, cwd, files, badfn=None):
181 def exact(root, cwd, files, badfn=None):
182 return exactmatcher(root, cwd, files, badfn=badfn)
182 return exactmatcher(root, cwd, files, badfn=badfn)
183
183
184 def always(root, cwd):
184 def always(root, cwd):
185 return alwaysmatcher(root, cwd)
185 return alwaysmatcher(root, cwd)
186
186
187 def never(root, cwd):
187 def never(root, cwd):
188 return nevermatcher(root, cwd)
188 return nevermatcher(root, cwd)
189
189
190 def badmatch(match, badfn):
190 def badmatch(match, badfn):
191 """Make a copy of the given matcher, replacing its bad method with the given
191 """Make a copy of the given matcher, replacing its bad method with the given
192 one.
192 one.
193 """
193 """
194 m = copy.copy(match)
194 m = copy.copy(match)
195 m.bad = badfn
195 m.bad = badfn
196 return m
196 return m
197
197
198 def _donormalize(patterns, default, root, cwd, auditor, warn):
198 def _donormalize(patterns, default, root, cwd, auditor, warn):
199 '''Convert 'kind:pat' from the patterns list to tuples with kind and
199 '''Convert 'kind:pat' from the patterns list to tuples with kind and
200 normalized and rooted patterns and with listfiles expanded.'''
200 normalized and rooted patterns and with listfiles expanded.'''
201 kindpats = []
201 kindpats = []
202 for kind, pat in [_patsplit(p, default) for p in patterns]:
202 for kind, pat in [_patsplit(p, default) for p in patterns]:
203 if kind in cwdrelativepatternkinds:
203 if kind in cwdrelativepatternkinds:
204 pat = pathutil.canonpath(root, cwd, pat, auditor)
204 pat = pathutil.canonpath(root, cwd, pat, auditor)
205 elif kind in ('relglob', 'path', 'rootfilesin'):
205 elif kind in ('relglob', 'path', 'rootfilesin'):
206 pat = util.normpath(pat)
206 pat = util.normpath(pat)
207 elif kind in ('listfile', 'listfile0'):
207 elif kind in ('listfile', 'listfile0'):
208 try:
208 try:
209 files = util.readfile(pat)
209 files = util.readfile(pat)
210 if kind == 'listfile0':
210 if kind == 'listfile0':
211 files = files.split('\0')
211 files = files.split('\0')
212 else:
212 else:
213 files = files.splitlines()
213 files = files.splitlines()
214 files = [f for f in files if f]
214 files = [f for f in files if f]
215 except EnvironmentError:
215 except EnvironmentError:
216 raise error.Abort(_("unable to read file list (%s)") % pat)
216 raise error.Abort(_("unable to read file list (%s)") % pat)
217 for k, p, source in _donormalize(files, default, root, cwd,
217 for k, p, source in _donormalize(files, default, root, cwd,
218 auditor, warn):
218 auditor, warn):
219 kindpats.append((k, p, pat))
219 kindpats.append((k, p, pat))
220 continue
220 continue
221 elif kind == 'include':
221 elif kind == 'include':
222 try:
222 try:
223 fullpath = os.path.join(root, util.localpath(pat))
223 fullpath = os.path.join(root, util.localpath(pat))
224 includepats = readpatternfile(fullpath, warn)
224 includepats = readpatternfile(fullpath, warn)
225 for k, p, source in _donormalize(includepats, default,
225 for k, p, source in _donormalize(includepats, default,
226 root, cwd, auditor, warn):
226 root, cwd, auditor, warn):
227 kindpats.append((k, p, source or pat))
227 kindpats.append((k, p, source or pat))
228 except error.Abort as inst:
228 except error.Abort as inst:
229 raise error.Abort('%s: %s' % (pat, inst[0]))
229 raise error.Abort('%s: %s' % (pat, inst[0]))
230 except IOError as inst:
230 except IOError as inst:
231 if warn:
231 if warn:
232 warn(_("skipping unreadable pattern file '%s': %s\n") %
232 warn(_("skipping unreadable pattern file '%s': %s\n") %
233 (pat, stringutil.forcebytestr(inst.strerror)))
233 (pat, stringutil.forcebytestr(inst.strerror)))
234 continue
234 continue
235 # else: re or relre - which cannot be normalized
235 # else: re or relre - which cannot be normalized
236 kindpats.append((kind, pat, ''))
236 kindpats.append((kind, pat, ''))
237 return kindpats
237 return kindpats
238
238
239 class basematcher(object):
239 class basematcher(object):
240
240
241 def __init__(self, root, cwd, badfn=None, relativeuipath=True):
241 def __init__(self, root, cwd, badfn=None, relativeuipath=True):
242 self._root = root
242 self._root = root
243 self._cwd = cwd
243 self._cwd = cwd
244 if badfn is not None:
244 if badfn is not None:
245 self.bad = badfn
245 self.bad = badfn
246 self._relativeuipath = relativeuipath
246 self._relativeuipath = relativeuipath
247
247
248 def __call__(self, fn):
248 def __call__(self, fn):
249 return self.matchfn(fn)
249 return self.matchfn(fn)
250 def __iter__(self):
250 def __iter__(self):
251 for f in self._files:
251 for f in self._files:
252 yield f
252 yield f
253 # Callbacks related to how the matcher is used by dirstate.walk.
253 # Callbacks related to how the matcher is used by dirstate.walk.
254 # Subscribers to these events must monkeypatch the matcher object.
254 # Subscribers to these events must monkeypatch the matcher object.
255 def bad(self, f, msg):
255 def bad(self, f, msg):
256 '''Callback from dirstate.walk for each explicit file that can't be
256 '''Callback from dirstate.walk for each explicit file that can't be
257 found/accessed, with an error message.'''
257 found/accessed, with an error message.'''
258
258
259 # If an explicitdir is set, it will be called when an explicitly listed
259 # If an explicitdir is set, it will be called when an explicitly listed
260 # directory is visited.
260 # directory is visited.
261 explicitdir = None
261 explicitdir = None
262
262
263 # If an traversedir is set, it will be called when a directory discovered
263 # If an traversedir is set, it will be called when a directory discovered
264 # by recursive traversal is visited.
264 # by recursive traversal is visited.
265 traversedir = None
265 traversedir = None
266
266
267 def abs(self, f):
267 def abs(self, f):
268 '''Convert a repo path back to path that is relative to the root of the
268 '''Convert a repo path back to path that is relative to the root of the
269 matcher.'''
269 matcher.'''
270 return f
270 return f
271
271
272 def rel(self, f):
272 def rel(self, f):
273 '''Convert repo path back to path that is relative to cwd of matcher.'''
273 '''Convert repo path back to path that is relative to cwd of matcher.'''
274 return util.pathto(self._root, self._cwd, f)
274 return util.pathto(self._root, self._cwd, f)
275
275
276 def uipath(self, f):
276 def uipath(self, f):
277 '''Convert repo path to a display path. If patterns or -I/-X were used
277 '''Convert repo path to a display path. If patterns or -I/-X were used
278 to create this matcher, the display path will be relative to cwd.
278 to create this matcher, the display path will be relative to cwd.
279 Otherwise it is relative to the root of the repo.'''
279 Otherwise it is relative to the root of the repo.'''
280 return (self._relativeuipath and self.rel(f)) or self.abs(f)
280 return (self._relativeuipath and self.rel(f)) or self.abs(f)
281
281
282 @propertycache
282 @propertycache
283 def _files(self):
283 def _files(self):
284 return []
284 return []
285
285
286 def files(self):
286 def files(self):
287 '''Explicitly listed files or patterns or roots:
287 '''Explicitly listed files or patterns or roots:
288 if no patterns or .always(): empty list,
288 if no patterns or .always(): empty list,
289 if exact: list exact files,
289 if exact: list exact files,
290 if not .anypats(): list all files and dirs,
290 if not .anypats(): list all files and dirs,
291 else: optimal roots'''
291 else: optimal roots'''
292 return self._files
292 return self._files
293
293
294 @propertycache
294 @propertycache
295 def _fileset(self):
295 def _fileset(self):
296 return set(self._files)
296 return set(self._files)
297
297
298 def exact(self, f):
298 def exact(self, f):
299 '''Returns True if f is in .files().'''
299 '''Returns True if f is in .files().'''
300 return f in self._fileset
300 return f in self._fileset
301
301
302 def matchfn(self, f):
302 def matchfn(self, f):
303 return False
303 return False
304
304
305 def visitdir(self, dir):
305 def visitdir(self, dir):
306 '''Decides whether a directory should be visited based on whether it
306 '''Decides whether a directory should be visited based on whether it
307 has potential matches in it or one of its subdirectories. This is
307 has potential matches in it or one of its subdirectories. This is
308 based on the match's primary, included, and excluded patterns.
308 based on the match's primary, included, and excluded patterns.
309
309
310 Returns the string 'all' if the given directory and all subdirectories
310 Returns the string 'all' if the given directory and all subdirectories
311 should be visited. Otherwise returns True or False indicating whether
311 should be visited. Otherwise returns True or False indicating whether
312 the given directory should be visited.
312 the given directory should be visited.
313 '''
313 '''
314 return True
314 return True
315
315
316 def always(self):
316 def always(self):
317 '''Matcher will match everything and .files() will be empty --
317 '''Matcher will match everything and .files() will be empty --
318 optimization might be possible.'''
318 optimization might be possible.'''
319 return False
319 return False
320
320
321 def isexact(self):
321 def isexact(self):
322 '''Matcher will match exactly the list of files in .files() --
322 '''Matcher will match exactly the list of files in .files() --
323 optimization might be possible.'''
323 optimization might be possible.'''
324 return False
324 return False
325
325
326 def prefix(self):
326 def prefix(self):
327 '''Matcher will match the paths in .files() recursively --
327 '''Matcher will match the paths in .files() recursively --
328 optimization might be possible.'''
328 optimization might be possible.'''
329 return False
329 return False
330
330
331 def anypats(self):
331 def anypats(self):
332 '''None of .always(), .isexact(), and .prefix() is true --
332 '''None of .always(), .isexact(), and .prefix() is true --
333 optimizations will be difficult.'''
333 optimizations will be difficult.'''
334 return not self.always() and not self.isexact() and not self.prefix()
334 return not self.always() and not self.isexact() and not self.prefix()
335
335
336 class alwaysmatcher(basematcher):
336 class alwaysmatcher(basematcher):
337 '''Matches everything.'''
337 '''Matches everything.'''
338
338
339 def __init__(self, root, cwd, badfn=None, relativeuipath=False):
339 def __init__(self, root, cwd, badfn=None, relativeuipath=False):
340 super(alwaysmatcher, self).__init__(root, cwd, badfn,
340 super(alwaysmatcher, self).__init__(root, cwd, badfn,
341 relativeuipath=relativeuipath)
341 relativeuipath=relativeuipath)
342
342
343 def always(self):
343 def always(self):
344 return True
344 return True
345
345
346 def matchfn(self, f):
346 def matchfn(self, f):
347 return True
347 return True
348
348
349 def visitdir(self, dir):
349 def visitdir(self, dir):
350 return 'all'
350 return 'all'
351
351
352 def __repr__(self):
352 def __repr__(self):
353 return r'<alwaysmatcher>'
353 return r'<alwaysmatcher>'
354
354
355 class nevermatcher(basematcher):
355 class nevermatcher(basematcher):
356 '''Matches nothing.'''
356 '''Matches nothing.'''
357
357
358 def __init__(self, root, cwd, badfn=None):
358 def __init__(self, root, cwd, badfn=None):
359 super(nevermatcher, self).__init__(root, cwd, badfn)
359 super(nevermatcher, self).__init__(root, cwd, badfn)
360
360
361 # It's a little weird to say that the nevermatcher is an exact matcher
361 # It's a little weird to say that the nevermatcher is an exact matcher
362 # or a prefix matcher, but it seems to make sense to let callers take
362 # or a prefix matcher, but it seems to make sense to let callers take
363 # fast paths based on either. There will be no exact matches, nor any
363 # fast paths based on either. There will be no exact matches, nor any
364 # prefixes (files() returns []), so fast paths iterating over them should
364 # prefixes (files() returns []), so fast paths iterating over them should
365 # be efficient (and correct).
365 # be efficient (and correct).
366 def isexact(self):
366 def isexact(self):
367 return True
367 return True
368
368
369 def prefix(self):
369 def prefix(self):
370 return True
370 return True
371
371
372 def visitdir(self, dir):
372 def visitdir(self, dir):
373 return False
373 return False
374
374
375 def __repr__(self):
375 def __repr__(self):
376 return r'<nevermatcher>'
376 return r'<nevermatcher>'
377
377
378 class predicatematcher(basematcher):
378 class predicatematcher(basematcher):
379 """A matcher adapter for a simple boolean function"""
379 """A matcher adapter for a simple boolean function"""
380
380
381 def __init__(self, root, cwd, predfn, predrepr=None, badfn=None):
381 def __init__(self, root, cwd, predfn, predrepr=None, badfn=None):
382 super(predicatematcher, self).__init__(root, cwd, badfn)
382 super(predicatematcher, self).__init__(root, cwd, badfn)
383 self.matchfn = predfn
383 self.matchfn = predfn
384 self._predrepr = predrepr
384 self._predrepr = predrepr
385
385
386 @encoding.strmethod
386 @encoding.strmethod
387 def __repr__(self):
387 def __repr__(self):
388 s = (stringutil.buildrepr(self._predrepr)
388 s = (stringutil.buildrepr(self._predrepr)
389 or pycompat.byterepr(self.matchfn))
389 or pycompat.byterepr(self.matchfn))
390 return '<predicatenmatcher pred=%s>' % s
390 return '<predicatenmatcher pred=%s>' % s
391
391
392 class patternmatcher(basematcher):
392 class patternmatcher(basematcher):
393
393
394 def __init__(self, root, cwd, kindpats, ctx=None, listsubrepos=False,
394 def __init__(self, root, cwd, kindpats, ctx=None, listsubrepos=False,
395 badfn=None):
395 badfn=None):
396 super(patternmatcher, self).__init__(root, cwd, badfn)
396 super(patternmatcher, self).__init__(root, cwd, badfn)
397
397
398 self._files = _explicitfiles(kindpats)
398 self._files = _explicitfiles(kindpats)
399 self._prefix = _prefix(kindpats)
399 self._prefix = _prefix(kindpats)
400 self._pats, self.matchfn = _buildmatch(ctx, kindpats, '$', listsubrepos,
400 self._pats, self.matchfn = _buildmatch(ctx, kindpats, '$', listsubrepos,
401 root)
401 root)
402
402
403 @propertycache
403 @propertycache
404 def _dirs(self):
404 def _dirs(self):
405 return set(util.dirs(self._fileset)) | {'.'}
405 return set(util.dirs(self._fileset)) | {'.'}
406
406
407 def visitdir(self, dir):
407 def visitdir(self, dir):
408 if self._prefix and dir in self._fileset:
408 if self._prefix and dir in self._fileset:
409 return 'all'
409 return 'all'
410 return ('.' in self._fileset or
410 return ('.' in self._fileset or
411 dir in self._fileset or
411 dir in self._fileset or
412 dir in self._dirs or
412 dir in self._dirs or
413 any(parentdir in self._fileset
413 any(parentdir in self._fileset
414 for parentdir in util.finddirs(dir)))
414 for parentdir in util.finddirs(dir)))
415
415
416 def prefix(self):
416 def prefix(self):
417 return self._prefix
417 return self._prefix
418
418
419 @encoding.strmethod
419 @encoding.strmethod
420 def __repr__(self):
420 def __repr__(self):
421 return ('<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats))
421 return ('<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats))
422
422
423 class includematcher(basematcher):
423 class includematcher(basematcher):
424
424
425 def __init__(self, root, cwd, kindpats, ctx=None, listsubrepos=False,
425 def __init__(self, root, cwd, kindpats, ctx=None, listsubrepos=False,
426 badfn=None):
426 badfn=None):
427 super(includematcher, self).__init__(root, cwd, badfn)
427 super(includematcher, self).__init__(root, cwd, badfn)
428
428
429 self._pats, self.matchfn = _buildmatch(ctx, kindpats, '(?:/|$)',
429 self._pats, self.matchfn = _buildmatch(ctx, kindpats, '(?:/|$)',
430 listsubrepos, root)
430 listsubrepos, root)
431 self._prefix = _prefix(kindpats)
431 self._prefix = _prefix(kindpats)
432 roots, dirs = _rootsanddirs(kindpats)
432 roots, dirs = _rootsanddirs(kindpats)
433 # roots are directories which are recursively included.
433 # roots are directories which are recursively included.
434 self._roots = set(roots)
434 self._roots = set(roots)
435 # dirs are directories which are non-recursively included.
435 # dirs are directories which are non-recursively included.
436 self._dirs = set(dirs)
436 self._dirs = set(dirs)
437
437
438 def visitdir(self, dir):
438 def visitdir(self, dir):
439 if self._prefix and dir in self._roots:
439 if self._prefix and dir in self._roots:
440 return 'all'
440 return 'all'
441 return ('.' in self._roots or
441 return ('.' in self._roots or
442 dir in self._roots or
442 dir in self._roots or
443 dir in self._dirs or
443 dir in self._dirs or
444 any(parentdir in self._roots
444 any(parentdir in self._roots
445 for parentdir in util.finddirs(dir)))
445 for parentdir in util.finddirs(dir)))
446
446
447 @encoding.strmethod
447 @encoding.strmethod
448 def __repr__(self):
448 def __repr__(self):
449 return ('<includematcher includes=%r>' % pycompat.bytestr(self._pats))
449 return ('<includematcher includes=%r>' % pycompat.bytestr(self._pats))
450
450
451 class exactmatcher(basematcher):
451 class exactmatcher(basematcher):
452 '''Matches the input files exactly. They are interpreted as paths, not
452 '''Matches the input files exactly. They are interpreted as paths, not
453 patterns (so no kind-prefixes).
453 patterns (so no kind-prefixes).
454 '''
454 '''
455
455
456 def __init__(self, root, cwd, files, badfn=None):
456 def __init__(self, root, cwd, files, badfn=None):
457 super(exactmatcher, self).__init__(root, cwd, badfn)
457 super(exactmatcher, self).__init__(root, cwd, badfn)
458
458
459 if isinstance(files, list):
459 if isinstance(files, list):
460 self._files = files
460 self._files = files
461 else:
461 else:
462 self._files = list(files)
462 self._files = list(files)
463
463
464 matchfn = basematcher.exact
464 matchfn = basematcher.exact
465
465
466 @propertycache
466 @propertycache
467 def _dirs(self):
467 def _dirs(self):
468 return set(util.dirs(self._fileset)) | {'.'}
468 return set(util.dirs(self._fileset)) | {'.'}
469
469
470 def visitdir(self, dir):
470 def visitdir(self, dir):
471 return dir in self._dirs
471 return dir in self._dirs
472
472
473 def isexact(self):
473 def isexact(self):
474 return True
474 return True
475
475
476 @encoding.strmethod
476 @encoding.strmethod
477 def __repr__(self):
477 def __repr__(self):
478 return ('<exactmatcher files=%r>' % self._files)
478 return ('<exactmatcher files=%r>' % self._files)
479
479
480 class differencematcher(basematcher):
480 class differencematcher(basematcher):
481 '''Composes two matchers by matching if the first matches and the second
481 '''Composes two matchers by matching if the first matches and the second
482 does not.
482 does not.
483
483
484 The second matcher's non-matching-attributes (root, cwd, bad, explicitdir,
484 The second matcher's non-matching-attributes (root, cwd, bad, explicitdir,
485 traversedir) are ignored.
485 traversedir) are ignored.
486 '''
486 '''
487 def __init__(self, m1, m2):
487 def __init__(self, m1, m2):
488 super(differencematcher, self).__init__(m1._root, m1._cwd)
488 super(differencematcher, self).__init__(m1._root, m1._cwd)
489 self._m1 = m1
489 self._m1 = m1
490 self._m2 = m2
490 self._m2 = m2
491 self.bad = m1.bad
491 self.bad = m1.bad
492 self.explicitdir = m1.explicitdir
492 self.explicitdir = m1.explicitdir
493 self.traversedir = m1.traversedir
493 self.traversedir = m1.traversedir
494
494
495 def matchfn(self, f):
495 def matchfn(self, f):
496 return self._m1(f) and not self._m2(f)
496 return self._m1(f) and not self._m2(f)
497
497
498 @propertycache
498 @propertycache
499 def _files(self):
499 def _files(self):
500 if self.isexact():
500 if self.isexact():
501 return [f for f in self._m1.files() if self(f)]
501 return [f for f in self._m1.files() if self(f)]
502 # If m1 is not an exact matcher, we can't easily figure out the set of
502 # If m1 is not an exact matcher, we can't easily figure out the set of
503 # files, because its files() are not always files. For example, if
503 # files, because its files() are not always files. For example, if
504 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
504 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
505 # want to remove "dir" from the set even though it would match m2,
505 # want to remove "dir" from the set even though it would match m2,
506 # because the "dir" in m1 may not be a file.
506 # because the "dir" in m1 may not be a file.
507 return self._m1.files()
507 return self._m1.files()
508
508
509 def visitdir(self, dir):
509 def visitdir(self, dir):
510 if self._m2.visitdir(dir) == 'all':
510 if self._m2.visitdir(dir) == 'all':
511 return False
511 return False
512 return bool(self._m1.visitdir(dir))
512 return bool(self._m1.visitdir(dir))
513
513
514 def isexact(self):
514 def isexact(self):
515 return self._m1.isexact()
515 return self._m1.isexact()
516
516
517 @encoding.strmethod
517 @encoding.strmethod
518 def __repr__(self):
518 def __repr__(self):
519 return ('<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2))
519 return ('<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2))
520
520
521 def intersectmatchers(m1, m2):
521 def intersectmatchers(m1, m2):
522 '''Composes two matchers by matching if both of them match.
522 '''Composes two matchers by matching if both of them match.
523
523
524 The second matcher's non-matching-attributes (root, cwd, bad, explicitdir,
524 The second matcher's non-matching-attributes (root, cwd, bad, explicitdir,
525 traversedir) are ignored.
525 traversedir) are ignored.
526 '''
526 '''
527 if m1 is None or m2 is None:
527 if m1 is None or m2 is None:
528 return m1 or m2
528 return m1 or m2
529 if m1.always():
529 if m1.always():
530 m = copy.copy(m2)
530 m = copy.copy(m2)
531 # TODO: Consider encapsulating these things in a class so there's only
531 # TODO: Consider encapsulating these things in a class so there's only
532 # one thing to copy from m1.
532 # one thing to copy from m1.
533 m.bad = m1.bad
533 m.bad = m1.bad
534 m.explicitdir = m1.explicitdir
534 m.explicitdir = m1.explicitdir
535 m.traversedir = m1.traversedir
535 m.traversedir = m1.traversedir
536 m.abs = m1.abs
536 m.abs = m1.abs
537 m.rel = m1.rel
537 m.rel = m1.rel
538 m._relativeuipath |= m1._relativeuipath
538 m._relativeuipath |= m1._relativeuipath
539 return m
539 return m
540 if m2.always():
540 if m2.always():
541 m = copy.copy(m1)
541 m = copy.copy(m1)
542 m._relativeuipath |= m2._relativeuipath
542 m._relativeuipath |= m2._relativeuipath
543 return m
543 return m
544 return intersectionmatcher(m1, m2)
544 return intersectionmatcher(m1, m2)
545
545
546 class intersectionmatcher(basematcher):
546 class intersectionmatcher(basematcher):
547 def __init__(self, m1, m2):
547 def __init__(self, m1, m2):
548 super(intersectionmatcher, self).__init__(m1._root, m1._cwd)
548 super(intersectionmatcher, self).__init__(m1._root, m1._cwd)
549 self._m1 = m1
549 self._m1 = m1
550 self._m2 = m2
550 self._m2 = m2
551 self.bad = m1.bad
551 self.bad = m1.bad
552 self.explicitdir = m1.explicitdir
552 self.explicitdir = m1.explicitdir
553 self.traversedir = m1.traversedir
553 self.traversedir = m1.traversedir
554
554
555 @propertycache
555 @propertycache
556 def _files(self):
556 def _files(self):
557 if self.isexact():
557 if self.isexact():
558 m1, m2 = self._m1, self._m2
558 m1, m2 = self._m1, self._m2
559 if not m1.isexact():
559 if not m1.isexact():
560 m1, m2 = m2, m1
560 m1, m2 = m2, m1
561 return [f for f in m1.files() if m2(f)]
561 return [f for f in m1.files() if m2(f)]
562 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
562 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
563 # the set of files, because their files() are not always files. For
563 # the set of files, because their files() are not always files. For
564 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
564 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
565 # "path:dir2", we don't want to remove "dir2" from the set.
565 # "path:dir2", we don't want to remove "dir2" from the set.
566 return self._m1.files() + self._m2.files()
566 return self._m1.files() + self._m2.files()
567
567
568 def matchfn(self, f):
568 def matchfn(self, f):
569 return self._m1(f) and self._m2(f)
569 return self._m1(f) and self._m2(f)
570
570
571 def visitdir(self, dir):
571 def visitdir(self, dir):
572 visit1 = self._m1.visitdir(dir)
572 visit1 = self._m1.visitdir(dir)
573 if visit1 == 'all':
573 if visit1 == 'all':
574 return self._m2.visitdir(dir)
574 return self._m2.visitdir(dir)
575 # bool() because visit1=True + visit2='all' should not be 'all'
575 # bool() because visit1=True + visit2='all' should not be 'all'
576 return bool(visit1 and self._m2.visitdir(dir))
576 return bool(visit1 and self._m2.visitdir(dir))
577
577
578 def always(self):
578 def always(self):
579 return self._m1.always() and self._m2.always()
579 return self._m1.always() and self._m2.always()
580
580
581 def isexact(self):
581 def isexact(self):
582 return self._m1.isexact() or self._m2.isexact()
582 return self._m1.isexact() or self._m2.isexact()
583
583
584 @encoding.strmethod
584 @encoding.strmethod
585 def __repr__(self):
585 def __repr__(self):
586 return ('<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2))
586 return ('<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2))
587
587
588 class subdirmatcher(basematcher):
588 class subdirmatcher(basematcher):
589 """Adapt a matcher to work on a subdirectory only.
589 """Adapt a matcher to work on a subdirectory only.
590
590
591 The paths are remapped to remove/insert the path as needed:
591 The paths are remapped to remove/insert the path as needed:
592
592
593 >>> from . import pycompat
593 >>> from . import pycompat
594 >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
594 >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
595 >>> m2 = subdirmatcher(b'sub', m1)
595 >>> m2 = subdirmatcher(b'sub', m1)
596 >>> bool(m2(b'a.txt'))
596 >>> bool(m2(b'a.txt'))
597 False
597 False
598 >>> bool(m2(b'b.txt'))
598 >>> bool(m2(b'b.txt'))
599 True
599 True
600 >>> bool(m2.matchfn(b'a.txt'))
600 >>> bool(m2.matchfn(b'a.txt'))
601 False
601 False
602 >>> bool(m2.matchfn(b'b.txt'))
602 >>> bool(m2.matchfn(b'b.txt'))
603 True
603 True
604 >>> m2.files()
604 >>> m2.files()
605 ['b.txt']
605 ['b.txt']
606 >>> m2.exact(b'b.txt')
606 >>> m2.exact(b'b.txt')
607 True
607 True
608 >>> util.pconvert(m2.rel(b'b.txt'))
608 >>> util.pconvert(m2.rel(b'b.txt'))
609 'sub/b.txt'
609 'sub/b.txt'
610 >>> def bad(f, msg):
610 >>> def bad(f, msg):
611 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
611 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
612 >>> m1.bad = bad
612 >>> m1.bad = bad
613 >>> m2.bad(b'x.txt', b'No such file')
613 >>> m2.bad(b'x.txt', b'No such file')
614 sub/x.txt: No such file
614 sub/x.txt: No such file
615 >>> m2.abs(b'c.txt')
615 >>> m2.abs(b'c.txt')
616 'sub/c.txt'
616 'sub/c.txt'
617 """
617 """
618
618
619 def __init__(self, path, matcher):
619 def __init__(self, path, matcher):
620 super(subdirmatcher, self).__init__(matcher._root, matcher._cwd)
620 super(subdirmatcher, self).__init__(matcher._root, matcher._cwd)
621 self._path = path
621 self._path = path
622 self._matcher = matcher
622 self._matcher = matcher
623 self._always = matcher.always()
623 self._always = matcher.always()
624
624
625 self._files = [f[len(path) + 1:] for f in matcher._files
625 self._files = [f[len(path) + 1:] for f in matcher._files
626 if f.startswith(path + "/")]
626 if f.startswith(path + "/")]
627
627
628 # If the parent repo had a path to this subrepo and the matcher is
628 # If the parent repo had a path to this subrepo and the matcher is
629 # a prefix matcher, this submatcher always matches.
629 # a prefix matcher, this submatcher always matches.
630 if matcher.prefix():
630 if matcher.prefix():
631 self._always = any(f == path for f in matcher._files)
631 self._always = any(f == path for f in matcher._files)
632
632
633 def bad(self, f, msg):
633 def bad(self, f, msg):
634 self._matcher.bad(self._path + "/" + f, msg)
634 self._matcher.bad(self._path + "/" + f, msg)
635
635
636 def abs(self, f):
636 def abs(self, f):
637 return self._matcher.abs(self._path + "/" + f)
637 return self._matcher.abs(self._path + "/" + f)
638
638
639 def rel(self, f):
639 def rel(self, f):
640 return self._matcher.rel(self._path + "/" + f)
640 return self._matcher.rel(self._path + "/" + f)
641
641
642 def uipath(self, f):
642 def uipath(self, f):
643 return self._matcher.uipath(self._path + "/" + f)
643 return self._matcher.uipath(self._path + "/" + f)
644
644
645 def matchfn(self, f):
645 def matchfn(self, f):
646 # Some information is lost in the superclass's constructor, so we
646 # Some information is lost in the superclass's constructor, so we
647 # can not accurately create the matching function for the subdirectory
647 # can not accurately create the matching function for the subdirectory
648 # from the inputs. Instead, we override matchfn() and visitdir() to
648 # from the inputs. Instead, we override matchfn() and visitdir() to
649 # call the original matcher with the subdirectory path prepended.
649 # call the original matcher with the subdirectory path prepended.
650 return self._matcher.matchfn(self._path + "/" + f)
650 return self._matcher.matchfn(self._path + "/" + f)
651
651
652 def visitdir(self, dir):
652 def visitdir(self, dir):
653 if dir == '.':
653 if dir == '.':
654 dir = self._path
654 dir = self._path
655 else:
655 else:
656 dir = self._path + "/" + dir
656 dir = self._path + "/" + dir
657 return self._matcher.visitdir(dir)
657 return self._matcher.visitdir(dir)
658
658
659 def always(self):
659 def always(self):
660 return self._always
660 return self._always
661
661
662 def prefix(self):
662 def prefix(self):
663 return self._matcher.prefix() and not self._always
663 return self._matcher.prefix() and not self._always
664
664
665 @encoding.strmethod
665 @encoding.strmethod
666 def __repr__(self):
666 def __repr__(self):
667 return ('<subdirmatcher path=%r, matcher=%r>' %
667 return ('<subdirmatcher path=%r, matcher=%r>' %
668 (self._path, self._matcher))
668 (self._path, self._matcher))
669
669
670 class unionmatcher(basematcher):
670 class unionmatcher(basematcher):
671 """A matcher that is the union of several matchers.
671 """A matcher that is the union of several matchers.
672
672
673 The non-matching-attributes (root, cwd, bad, explicitdir, traversedir) are
673 The non-matching-attributes (root, cwd, bad, explicitdir, traversedir) are
674 taken from the first matcher.
674 taken from the first matcher.
675 """
675 """
676
676
677 def __init__(self, matchers):
677 def __init__(self, matchers):
678 m1 = matchers[0]
678 m1 = matchers[0]
679 super(unionmatcher, self).__init__(m1._root, m1._cwd)
679 super(unionmatcher, self).__init__(m1._root, m1._cwd)
680 self.explicitdir = m1.explicitdir
680 self.explicitdir = m1.explicitdir
681 self.traversedir = m1.traversedir
681 self.traversedir = m1.traversedir
682 self._matchers = matchers
682 self._matchers = matchers
683
683
684 def matchfn(self, f):
684 def matchfn(self, f):
685 for match in self._matchers:
685 for match in self._matchers:
686 if match(f):
686 if match(f):
687 return True
687 return True
688 return False
688 return False
689
689
690 def visitdir(self, dir):
690 def visitdir(self, dir):
691 r = False
691 r = False
692 for m in self._matchers:
692 for m in self._matchers:
693 v = m.visitdir(dir)
693 v = m.visitdir(dir)
694 if v == 'all':
694 if v == 'all':
695 return v
695 return v
696 r |= v
696 r |= v
697 return r
697 return r
698
698
699 @encoding.strmethod
699 @encoding.strmethod
700 def __repr__(self):
700 def __repr__(self):
701 return ('<unionmatcher matchers=%r>' % self._matchers)
701 return ('<unionmatcher matchers=%r>' % self._matchers)
702
702
703 def patkind(pattern, default=None):
703 def patkind(pattern, default=None):
704 '''If pattern is 'kind:pat' with a known kind, return kind.'''
704 '''If pattern is 'kind:pat' with a known kind, return kind.'''
705 return _patsplit(pattern, default)[0]
705 return _patsplit(pattern, default)[0]
706
706
707 def _patsplit(pattern, default):
707 def _patsplit(pattern, default):
708 """Split a string into the optional pattern kind prefix and the actual
708 """Split a string into the optional pattern kind prefix and the actual
709 pattern."""
709 pattern."""
710 if ':' in pattern:
710 if ':' in pattern:
711 kind, pat = pattern.split(':', 1)
711 kind, pat = pattern.split(':', 1)
712 if kind in allpatternkinds:
712 if kind in allpatternkinds:
713 return kind, pat
713 return kind, pat
714 return default, pattern
714 return default, pattern
715
715
716 def _globre(pat):
716 def _globre(pat):
717 r'''Convert an extended glob string to a regexp string.
717 r'''Convert an extended glob string to a regexp string.
718
718
719 >>> from . import pycompat
719 >>> from . import pycompat
720 >>> def bprint(s):
720 >>> def bprint(s):
721 ... print(pycompat.sysstr(s))
721 ... print(pycompat.sysstr(s))
722 >>> bprint(_globre(br'?'))
722 >>> bprint(_globre(br'?'))
723 .
723 .
724 >>> bprint(_globre(br'*'))
724 >>> bprint(_globre(br'*'))
725 [^/]*
725 [^/]*
726 >>> bprint(_globre(br'**'))
726 >>> bprint(_globre(br'**'))
727 .*
727 .*
728 >>> bprint(_globre(br'**/a'))
728 >>> bprint(_globre(br'**/a'))
729 (?:.*/)?a
729 (?:.*/)?a
730 >>> bprint(_globre(br'a/**/b'))
730 >>> bprint(_globre(br'a/**/b'))
731 a/(?:.*/)?b
731 a/(?:.*/)?b
732 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
732 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
733 [a*?!^][\^b][^c]
733 [a*?!^][\^b][^c]
734 >>> bprint(_globre(br'{a,b}'))
734 >>> bprint(_globre(br'{a,b}'))
735 (?:a|b)
735 (?:a|b)
736 >>> bprint(_globre(br'.\*\?'))
736 >>> bprint(_globre(br'.\*\?'))
737 \.\*\?
737 \.\*\?
738 '''
738 '''
739 i, n = 0, len(pat)
739 i, n = 0, len(pat)
740 res = ''
740 res = ''
741 group = 0
741 group = 0
742 escape = util.stringutil.reescape
742 escape = util.stringutil.reescape
743 def peek():
743 def peek():
744 return i < n and pat[i:i + 1]
744 return i < n and pat[i:i + 1]
745 while i < n:
745 while i < n:
746 c = pat[i:i + 1]
746 c = pat[i:i + 1]
747 i += 1
747 i += 1
748 if c not in '*?[{},\\':
748 if c not in '*?[{},\\':
749 res += escape(c)
749 res += escape(c)
750 elif c == '*':
750 elif c == '*':
751 if peek() == '*':
751 if peek() == '*':
752 i += 1
752 i += 1
753 if peek() == '/':
753 if peek() == '/':
754 i += 1
754 i += 1
755 res += '(?:.*/)?'
755 res += '(?:.*/)?'
756 else:
756 else:
757 res += '.*'
757 res += '.*'
758 else:
758 else:
759 res += '[^/]*'
759 res += '[^/]*'
760 elif c == '?':
760 elif c == '?':
761 res += '.'
761 res += '.'
762 elif c == '[':
762 elif c == '[':
763 j = i
763 j = i
764 if j < n and pat[j:j + 1] in '!]':
764 if j < n and pat[j:j + 1] in '!]':
765 j += 1
765 j += 1
766 while j < n and pat[j:j + 1] != ']':
766 while j < n and pat[j:j + 1] != ']':
767 j += 1
767 j += 1
768 if j >= n:
768 if j >= n:
769 res += '\\['
769 res += '\\['
770 else:
770 else:
771 stuff = pat[i:j].replace('\\','\\\\')
771 stuff = pat[i:j].replace('\\','\\\\')
772 i = j + 1
772 i = j + 1
773 if stuff[0:1] == '!':
773 if stuff[0:1] == '!':
774 stuff = '^' + stuff[1:]
774 stuff = '^' + stuff[1:]
775 elif stuff[0:1] == '^':
775 elif stuff[0:1] == '^':
776 stuff = '\\' + stuff
776 stuff = '\\' + stuff
777 res = '%s[%s]' % (res, stuff)
777 res = '%s[%s]' % (res, stuff)
778 elif c == '{':
778 elif c == '{':
779 group += 1
779 group += 1
780 res += '(?:'
780 res += '(?:'
781 elif c == '}' and group:
781 elif c == '}' and group:
782 res += ')'
782 res += ')'
783 group -= 1
783 group -= 1
784 elif c == ',' and group:
784 elif c == ',' and group:
785 res += '|'
785 res += '|'
786 elif c == '\\':
786 elif c == '\\':
787 p = peek()
787 p = peek()
788 if p:
788 if p:
789 i += 1
789 i += 1
790 res += escape(p)
790 res += escape(p)
791 else:
791 else:
792 res += escape(c)
792 res += escape(c)
793 else:
793 else:
794 res += escape(c)
794 res += escape(c)
795 return res
795 return res
796
796
797 def _regex(kind, pat, globsuffix):
797 def _regex(kind, pat, globsuffix):
798 '''Convert a (normalized) pattern of any kind into a regular expression.
798 '''Convert a (normalized) pattern of any kind into a regular expression.
799 globsuffix is appended to the regexp of globs.'''
799 globsuffix is appended to the regexp of globs.'''
800 if not pat:
800 if not pat:
801 return ''
801 return ''
802 if kind == 're':
802 if kind == 're':
803 return pat
803 return pat
804 if kind in ('path', 'relpath'):
804 if kind in ('path', 'relpath'):
805 if pat == '.':
805 if pat == '.':
806 return ''
806 return ''
807 return util.stringutil.reescape(pat) + '(?:/|$)'
807 return util.stringutil.reescape(pat) + '(?:/|$)'
808 if kind == 'rootfilesin':
808 if kind == 'rootfilesin':
809 if pat == '.':
809 if pat == '.':
810 escaped = ''
810 escaped = ''
811 else:
811 else:
812 # Pattern is a directory name.
812 # Pattern is a directory name.
813 escaped = util.stringutil.reescape(pat) + '/'
813 escaped = util.stringutil.reescape(pat) + '/'
814 # Anything after the pattern must be a non-directory.
814 # Anything after the pattern must be a non-directory.
815 return escaped + '[^/]+$'
815 return escaped + '[^/]+$'
816 if kind == 'relglob':
816 if kind == 'relglob':
817 return '(?:|.*/)' + _globre(pat) + globsuffix
817 return '(?:|.*/)' + _globre(pat) + globsuffix
818 if kind == 'relre':
818 if kind == 'relre':
819 if pat.startswith('^'):
819 if pat.startswith('^'):
820 return pat
820 return pat
821 return '.*' + pat
821 return '.*' + pat
822 if kind == 'glob':
822 if kind == 'glob':
823 return _globre(pat) + globsuffix
823 return _globre(pat) + globsuffix
824 raise error.ProgrammingError('not a regex pattern: %s:%s' % (kind, pat))
824 raise error.ProgrammingError('not a regex pattern: %s:%s' % (kind, pat))
825
825
826 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
826 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
827 '''Return regexp string and a matcher function for kindpats.
827 '''Return regexp string and a matcher function for kindpats.
828 globsuffix is appended to the regexp of globs.'''
828 globsuffix is appended to the regexp of globs.'''
829 matchfuncs = []
829 matchfuncs = []
830
830
831 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
832 if fset:
833 matchfuncs.append(fset.__contains__)
834
831 subincludes, kindpats = _expandsubinclude(kindpats, root)
835 subincludes, kindpats = _expandsubinclude(kindpats, root)
832 if subincludes:
836 if subincludes:
833 submatchers = {}
837 submatchers = {}
834 def matchsubinclude(f):
838 def matchsubinclude(f):
835 for prefix, matcherargs in subincludes:
839 for prefix, matcherargs in subincludes:
836 if f.startswith(prefix):
840 if f.startswith(prefix):
837 mf = submatchers.get(prefix)
841 mf = submatchers.get(prefix)
838 if mf is None:
842 if mf is None:
839 mf = match(*matcherargs)
843 mf = match(*matcherargs)
840 submatchers[prefix] = mf
844 submatchers[prefix] = mf
841
845
842 if mf(f[len(prefix):]):
846 if mf(f[len(prefix):]):
843 return True
847 return True
844 return False
848 return False
845 matchfuncs.append(matchsubinclude)
849 matchfuncs.append(matchsubinclude)
846
850
847 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
848 if fset:
849 matchfuncs.append(fset.__contains__)
850
851 regex = ''
851 regex = ''
852 if kindpats:
852 if kindpats:
853 regex, mf = _buildregexmatch(kindpats, globsuffix)
853 regex, mf = _buildregexmatch(kindpats, globsuffix)
854 matchfuncs.append(mf)
854 matchfuncs.append(mf)
855
855
856 if len(matchfuncs) == 1:
856 if len(matchfuncs) == 1:
857 return regex, matchfuncs[0]
857 return regex, matchfuncs[0]
858 else:
858 else:
859 return regex, lambda f: any(mf(f) for mf in matchfuncs)
859 return regex, lambda f: any(mf(f) for mf in matchfuncs)
860
860
861 def _buildregexmatch(kindpats, globsuffix):
861 def _buildregexmatch(kindpats, globsuffix):
862 """Build a match function from a list of kinds and kindpats,
862 """Build a match function from a list of kinds and kindpats,
863 return regexp string and a matcher function."""
863 return regexp string and a matcher function."""
864 try:
864 try:
865 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
865 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
866 for (k, p, s) in kindpats])
866 for (k, p, s) in kindpats])
867 if len(regex) > 20000:
867 if len(regex) > 20000:
868 raise OverflowError
868 raise OverflowError
869 return regex, _rematcher(regex)
869 return regex, _rematcher(regex)
870 except OverflowError:
870 except OverflowError:
871 # We're using a Python with a tiny regex engine and we
871 # We're using a Python with a tiny regex engine and we
872 # made it explode, so we'll divide the pattern list in two
872 # made it explode, so we'll divide the pattern list in two
873 # until it works
873 # until it works
874 l = len(kindpats)
874 l = len(kindpats)
875 if l < 2:
875 if l < 2:
876 raise
876 raise
877 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
877 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
878 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
878 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
879 return regex, lambda s: a(s) or b(s)
879 return regex, lambda s: a(s) or b(s)
880 except re.error:
880 except re.error:
881 for k, p, s in kindpats:
881 for k, p, s in kindpats:
882 try:
882 try:
883 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
883 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
884 except re.error:
884 except re.error:
885 if s:
885 if s:
886 raise error.Abort(_("%s: invalid pattern (%s): %s") %
886 raise error.Abort(_("%s: invalid pattern (%s): %s") %
887 (s, k, p))
887 (s, k, p))
888 else:
888 else:
889 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
889 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
890 raise error.Abort(_("invalid pattern"))
890 raise error.Abort(_("invalid pattern"))
891
891
892 def _patternrootsanddirs(kindpats):
892 def _patternrootsanddirs(kindpats):
893 '''Returns roots and directories corresponding to each pattern.
893 '''Returns roots and directories corresponding to each pattern.
894
894
895 This calculates the roots and directories exactly matching the patterns and
895 This calculates the roots and directories exactly matching the patterns and
896 returns a tuple of (roots, dirs) for each. It does not return other
896 returns a tuple of (roots, dirs) for each. It does not return other
897 directories which may also need to be considered, like the parent
897 directories which may also need to be considered, like the parent
898 directories.
898 directories.
899 '''
899 '''
900 r = []
900 r = []
901 d = []
901 d = []
902 for kind, pat, source in kindpats:
902 for kind, pat, source in kindpats:
903 if kind == 'glob': # find the non-glob prefix
903 if kind == 'glob': # find the non-glob prefix
904 root = []
904 root = []
905 for p in pat.split('/'):
905 for p in pat.split('/'):
906 if '[' in p or '{' in p or '*' in p or '?' in p:
906 if '[' in p or '{' in p or '*' in p or '?' in p:
907 break
907 break
908 root.append(p)
908 root.append(p)
909 r.append('/'.join(root) or '.')
909 r.append('/'.join(root) or '.')
910 elif kind in ('relpath', 'path'):
910 elif kind in ('relpath', 'path'):
911 r.append(pat or '.')
911 r.append(pat or '.')
912 elif kind in ('rootfilesin',):
912 elif kind in ('rootfilesin',):
913 d.append(pat or '.')
913 d.append(pat or '.')
914 else: # relglob, re, relre
914 else: # relglob, re, relre
915 r.append('.')
915 r.append('.')
916 return r, d
916 return r, d
917
917
918 def _roots(kindpats):
918 def _roots(kindpats):
919 '''Returns root directories to match recursively from the given patterns.'''
919 '''Returns root directories to match recursively from the given patterns.'''
920 roots, dirs = _patternrootsanddirs(kindpats)
920 roots, dirs = _patternrootsanddirs(kindpats)
921 return roots
921 return roots
922
922
923 def _rootsanddirs(kindpats):
923 def _rootsanddirs(kindpats):
924 '''Returns roots and exact directories from patterns.
924 '''Returns roots and exact directories from patterns.
925
925
926 roots are directories to match recursively, whereas exact directories should
926 roots are directories to match recursively, whereas exact directories should
927 be matched non-recursively. The returned (roots, dirs) tuple will also
927 be matched non-recursively. The returned (roots, dirs) tuple will also
928 include directories that need to be implicitly considered as either, such as
928 include directories that need to be implicitly considered as either, such as
929 parent directories.
929 parent directories.
930
930
931 >>> _rootsanddirs(
931 >>> _rootsanddirs(
932 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
932 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
933 ... (b'glob', b'g*', b'')])
933 ... (b'glob', b'g*', b'')])
934 (['g/h', 'g/h', '.'], ['g', '.'])
934 (['g/h', 'g/h', '.'], ['g', '.'])
935 >>> _rootsanddirs(
935 >>> _rootsanddirs(
936 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
936 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
937 ([], ['g/h', '.', 'g', '.'])
937 ([], ['g/h', '.', 'g', '.'])
938 >>> _rootsanddirs(
938 >>> _rootsanddirs(
939 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
939 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
940 ... (b'path', b'', b'')])
940 ... (b'path', b'', b'')])
941 (['r', 'p/p', '.'], ['p', '.'])
941 (['r', 'p/p', '.'], ['p', '.'])
942 >>> _rootsanddirs(
942 >>> _rootsanddirs(
943 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
943 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
944 ... (b'relre', b'rr', b'')])
944 ... (b'relre', b'rr', b'')])
945 (['.', '.', '.'], ['.'])
945 (['.', '.', '.'], ['.'])
946 '''
946 '''
947 r, d = _patternrootsanddirs(kindpats)
947 r, d = _patternrootsanddirs(kindpats)
948
948
949 # Append the parents as non-recursive/exact directories, since they must be
949 # Append the parents as non-recursive/exact directories, since they must be
950 # scanned to get to either the roots or the other exact directories.
950 # scanned to get to either the roots or the other exact directories.
951 d.extend(util.dirs(d))
951 d.extend(util.dirs(d))
952 d.extend(util.dirs(r))
952 d.extend(util.dirs(r))
953 # util.dirs() does not include the root directory, so add it manually
953 # util.dirs() does not include the root directory, so add it manually
954 d.append('.')
954 d.append('.')
955
955
956 return r, d
956 return r, d
957
957
958 def _explicitfiles(kindpats):
958 def _explicitfiles(kindpats):
959 '''Returns the potential explicit filenames from the patterns.
959 '''Returns the potential explicit filenames from the patterns.
960
960
961 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
961 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
962 ['foo/bar']
962 ['foo/bar']
963 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
963 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
964 []
964 []
965 '''
965 '''
966 # Keep only the pattern kinds where one can specify filenames (vs only
966 # Keep only the pattern kinds where one can specify filenames (vs only
967 # directory names).
967 # directory names).
968 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
968 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
969 return _roots(filable)
969 return _roots(filable)
970
970
971 def _prefix(kindpats):
971 def _prefix(kindpats):
972 '''Whether all the patterns match a prefix (i.e. recursively)'''
972 '''Whether all the patterns match a prefix (i.e. recursively)'''
973 for kind, pat, source in kindpats:
973 for kind, pat, source in kindpats:
974 if kind not in ('path', 'relpath'):
974 if kind not in ('path', 'relpath'):
975 return False
975 return False
976 return True
976 return True
977
977
978 _commentre = None
978 _commentre = None
979
979
980 def readpatternfile(filepath, warn, sourceinfo=False):
980 def readpatternfile(filepath, warn, sourceinfo=False):
981 '''parse a pattern file, returning a list of
981 '''parse a pattern file, returning a list of
982 patterns. These patterns should be given to compile()
982 patterns. These patterns should be given to compile()
983 to be validated and converted into a match function.
983 to be validated and converted into a match function.
984
984
985 trailing white space is dropped.
985 trailing white space is dropped.
986 the escape character is backslash.
986 the escape character is backslash.
987 comments start with #.
987 comments start with #.
988 empty lines are skipped.
988 empty lines are skipped.
989
989
990 lines can be of the following formats:
990 lines can be of the following formats:
991
991
992 syntax: regexp # defaults following lines to non-rooted regexps
992 syntax: regexp # defaults following lines to non-rooted regexps
993 syntax: glob # defaults following lines to non-rooted globs
993 syntax: glob # defaults following lines to non-rooted globs
994 re:pattern # non-rooted regular expression
994 re:pattern # non-rooted regular expression
995 glob:pattern # non-rooted glob
995 glob:pattern # non-rooted glob
996 pattern # pattern of the current default type
996 pattern # pattern of the current default type
997
997
998 if sourceinfo is set, returns a list of tuples:
998 if sourceinfo is set, returns a list of tuples:
999 (pattern, lineno, originalline). This is useful to debug ignore patterns.
999 (pattern, lineno, originalline). This is useful to debug ignore patterns.
1000 '''
1000 '''
1001
1001
1002 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
1002 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
1003 'include': 'include', 'subinclude': 'subinclude'}
1003 'include': 'include', 'subinclude': 'subinclude'}
1004 syntax = 'relre:'
1004 syntax = 'relre:'
1005 patterns = []
1005 patterns = []
1006
1006
1007 fp = open(filepath, 'rb')
1007 fp = open(filepath, 'rb')
1008 for lineno, line in enumerate(util.iterfile(fp), start=1):
1008 for lineno, line in enumerate(util.iterfile(fp), start=1):
1009 if "#" in line:
1009 if "#" in line:
1010 global _commentre
1010 global _commentre
1011 if not _commentre:
1011 if not _commentre:
1012 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1012 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1013 # remove comments prefixed by an even number of escapes
1013 # remove comments prefixed by an even number of escapes
1014 m = _commentre.search(line)
1014 m = _commentre.search(line)
1015 if m:
1015 if m:
1016 line = line[:m.end(1)]
1016 line = line[:m.end(1)]
1017 # fixup properly escaped comments that survived the above
1017 # fixup properly escaped comments that survived the above
1018 line = line.replace("\\#", "#")
1018 line = line.replace("\\#", "#")
1019 line = line.rstrip()
1019 line = line.rstrip()
1020 if not line:
1020 if not line:
1021 continue
1021 continue
1022
1022
1023 if line.startswith('syntax:'):
1023 if line.startswith('syntax:'):
1024 s = line[7:].strip()
1024 s = line[7:].strip()
1025 try:
1025 try:
1026 syntax = syntaxes[s]
1026 syntax = syntaxes[s]
1027 except KeyError:
1027 except KeyError:
1028 if warn:
1028 if warn:
1029 warn(_("%s: ignoring invalid syntax '%s'\n") %
1029 warn(_("%s: ignoring invalid syntax '%s'\n") %
1030 (filepath, s))
1030 (filepath, s))
1031 continue
1031 continue
1032
1032
1033 linesyntax = syntax
1033 linesyntax = syntax
1034 for s, rels in syntaxes.iteritems():
1034 for s, rels in syntaxes.iteritems():
1035 if line.startswith(rels):
1035 if line.startswith(rels):
1036 linesyntax = rels
1036 linesyntax = rels
1037 line = line[len(rels):]
1037 line = line[len(rels):]
1038 break
1038 break
1039 elif line.startswith(s+':'):
1039 elif line.startswith(s+':'):
1040 linesyntax = rels
1040 linesyntax = rels
1041 line = line[len(s) + 1:]
1041 line = line[len(s) + 1:]
1042 break
1042 break
1043 if sourceinfo:
1043 if sourceinfo:
1044 patterns.append((linesyntax + line, lineno, line))
1044 patterns.append((linesyntax + line, lineno, line))
1045 else:
1045 else:
1046 patterns.append(linesyntax + line)
1046 patterns.append(linesyntax + line)
1047 fp.close()
1047 fp.close()
1048 return patterns
1048 return patterns
General Comments 0
You need to be logged in to leave comments. Login now