##// END OF EJS Templates
match: remove unused match.__iter__ implementation (API)...
Martin von Zweigbergk -
r42373:7edff8dd default
parent child Browse files
Show More
@@ -1,1479 +1,1476 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 itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 encoding,
17 encoding,
18 error,
18 error,
19 pathutil,
19 pathutil,
20 pycompat,
20 pycompat,
21 util,
21 util,
22 )
22 )
23 from .utils import (
23 from .utils import (
24 stringutil,
24 stringutil,
25 )
25 )
26
26
27 allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
27 allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
28 'rootglob',
28 'rootglob',
29 'listfile', 'listfile0', 'set', 'include', 'subinclude',
29 'listfile', 'listfile0', 'set', 'include', 'subinclude',
30 'rootfilesin')
30 'rootfilesin')
31 cwdrelativepatternkinds = ('relpath', 'glob')
31 cwdrelativepatternkinds = ('relpath', 'glob')
32
32
33 propertycache = util.propertycache
33 propertycache = util.propertycache
34
34
35 def _rematcher(regex):
35 def _rematcher(regex):
36 '''compile the regexp with the best available regexp engine and return a
36 '''compile the regexp with the best available regexp engine and return a
37 matcher function'''
37 matcher function'''
38 m = util.re.compile(regex)
38 m = util.re.compile(regex)
39 try:
39 try:
40 # slightly faster, provided by facebook's re2 bindings
40 # slightly faster, provided by facebook's re2 bindings
41 return m.test_match
41 return m.test_match
42 except AttributeError:
42 except AttributeError:
43 return m.match
43 return m.match
44
44
45 def _expandsets(kindpats, ctx=None, listsubrepos=False, badfn=None):
45 def _expandsets(kindpats, ctx=None, listsubrepos=False, badfn=None):
46 '''Returns the kindpats list with the 'set' patterns expanded to matchers'''
46 '''Returns the kindpats list with the 'set' patterns expanded to matchers'''
47 matchers = []
47 matchers = []
48 other = []
48 other = []
49
49
50 for kind, pat, source in kindpats:
50 for kind, pat, source in kindpats:
51 if kind == 'set':
51 if kind == 'set':
52 if ctx is None:
52 if ctx is None:
53 raise error.ProgrammingError("fileset expression with no "
53 raise error.ProgrammingError("fileset expression with no "
54 "context")
54 "context")
55 matchers.append(ctx.matchfileset(pat, badfn=badfn))
55 matchers.append(ctx.matchfileset(pat, badfn=badfn))
56
56
57 if listsubrepos:
57 if listsubrepos:
58 for subpath in ctx.substate:
58 for subpath in ctx.substate:
59 sm = ctx.sub(subpath).matchfileset(pat, badfn=badfn)
59 sm = ctx.sub(subpath).matchfileset(pat, badfn=badfn)
60 pm = prefixdirmatcher(subpath, sm, badfn=badfn)
60 pm = prefixdirmatcher(subpath, sm, badfn=badfn)
61 matchers.append(pm)
61 matchers.append(pm)
62
62
63 continue
63 continue
64 other.append((kind, pat, source))
64 other.append((kind, pat, source))
65 return matchers, other
65 return matchers, other
66
66
67 def _expandsubinclude(kindpats, root):
67 def _expandsubinclude(kindpats, root):
68 '''Returns the list of subinclude matcher args and the kindpats without the
68 '''Returns the list of subinclude matcher args and the kindpats without the
69 subincludes in it.'''
69 subincludes in it.'''
70 relmatchers = []
70 relmatchers = []
71 other = []
71 other = []
72
72
73 for kind, pat, source in kindpats:
73 for kind, pat, source in kindpats:
74 if kind == 'subinclude':
74 if kind == 'subinclude':
75 sourceroot = pathutil.dirname(util.normpath(source))
75 sourceroot = pathutil.dirname(util.normpath(source))
76 pat = util.pconvert(pat)
76 pat = util.pconvert(pat)
77 path = pathutil.join(sourceroot, pat)
77 path = pathutil.join(sourceroot, pat)
78
78
79 newroot = pathutil.dirname(path)
79 newroot = pathutil.dirname(path)
80 matcherargs = (newroot, '', [], ['include:%s' % path])
80 matcherargs = (newroot, '', [], ['include:%s' % path])
81
81
82 prefix = pathutil.canonpath(root, root, newroot)
82 prefix = pathutil.canonpath(root, root, newroot)
83 if prefix:
83 if prefix:
84 prefix += '/'
84 prefix += '/'
85 relmatchers.append((prefix, matcherargs))
85 relmatchers.append((prefix, matcherargs))
86 else:
86 else:
87 other.append((kind, pat, source))
87 other.append((kind, pat, source))
88
88
89 return relmatchers, other
89 return relmatchers, other
90
90
91 def _kindpatsalwaysmatch(kindpats):
91 def _kindpatsalwaysmatch(kindpats):
92 """"Checks whether the kindspats match everything, as e.g.
92 """"Checks whether the kindspats match everything, as e.g.
93 'relpath:.' does.
93 'relpath:.' does.
94 """
94 """
95 for kind, pat, source in kindpats:
95 for kind, pat, source in kindpats:
96 if pat != '' or kind not in ['relpath', 'glob']:
96 if pat != '' or kind not in ['relpath', 'glob']:
97 return False
97 return False
98 return True
98 return True
99
99
100 def _buildkindpatsmatcher(matchercls, root, kindpats, ctx=None,
100 def _buildkindpatsmatcher(matchercls, root, kindpats, ctx=None,
101 listsubrepos=False, badfn=None):
101 listsubrepos=False, badfn=None):
102 matchers = []
102 matchers = []
103 fms, kindpats = _expandsets(kindpats, ctx=ctx,
103 fms, kindpats = _expandsets(kindpats, ctx=ctx,
104 listsubrepos=listsubrepos, badfn=badfn)
104 listsubrepos=listsubrepos, badfn=badfn)
105 if kindpats:
105 if kindpats:
106 m = matchercls(root, kindpats, badfn=badfn)
106 m = matchercls(root, kindpats, badfn=badfn)
107 matchers.append(m)
107 matchers.append(m)
108 if fms:
108 if fms:
109 matchers.extend(fms)
109 matchers.extend(fms)
110 if not matchers:
110 if not matchers:
111 return nevermatcher(badfn=badfn)
111 return nevermatcher(badfn=badfn)
112 if len(matchers) == 1:
112 if len(matchers) == 1:
113 return matchers[0]
113 return matchers[0]
114 return unionmatcher(matchers)
114 return unionmatcher(matchers)
115
115
116 def match(root, cwd, patterns=None, include=None, exclude=None, default='glob',
116 def match(root, cwd, patterns=None, include=None, exclude=None, default='glob',
117 auditor=None, ctx=None, listsubrepos=False, warn=None,
117 auditor=None, ctx=None, listsubrepos=False, warn=None,
118 badfn=None, icasefs=False):
118 badfn=None, icasefs=False):
119 r"""build an object to match a set of file patterns
119 r"""build an object to match a set of file patterns
120
120
121 arguments:
121 arguments:
122 root - the canonical root of the tree you're matching against
122 root - the canonical root of the tree you're matching against
123 cwd - the current working directory, if relevant
123 cwd - the current working directory, if relevant
124 patterns - patterns to find
124 patterns - patterns to find
125 include - patterns to include (unless they are excluded)
125 include - patterns to include (unless they are excluded)
126 exclude - patterns to exclude (even if they are included)
126 exclude - patterns to exclude (even if they are included)
127 default - if a pattern in patterns has no explicit type, assume this one
127 default - if a pattern in patterns has no explicit type, assume this one
128 auditor - optional path auditor
128 auditor - optional path auditor
129 ctx - optional changecontext
129 ctx - optional changecontext
130 listsubrepos - if True, recurse into subrepositories
130 listsubrepos - if True, recurse into subrepositories
131 warn - optional function used for printing warnings
131 warn - optional function used for printing warnings
132 badfn - optional bad() callback for this matcher instead of the default
132 badfn - optional bad() callback for this matcher instead of the default
133 icasefs - make a matcher for wdir on case insensitive filesystems, which
133 icasefs - make a matcher for wdir on case insensitive filesystems, which
134 normalizes the given patterns to the case in the filesystem
134 normalizes the given patterns to the case in the filesystem
135
135
136 a pattern is one of:
136 a pattern is one of:
137 'glob:<glob>' - a glob relative to cwd
137 'glob:<glob>' - a glob relative to cwd
138 're:<regexp>' - a regular expression
138 're:<regexp>' - a regular expression
139 'path:<path>' - a path relative to repository root, which is matched
139 'path:<path>' - a path relative to repository root, which is matched
140 recursively
140 recursively
141 'rootfilesin:<path>' - a path relative to repository root, which is
141 'rootfilesin:<path>' - a path relative to repository root, which is
142 matched non-recursively (will not match subdirectories)
142 matched non-recursively (will not match subdirectories)
143 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
143 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
144 'relpath:<path>' - a path relative to cwd
144 'relpath:<path>' - a path relative to cwd
145 'relre:<regexp>' - a regexp that needn't match the start of a name
145 'relre:<regexp>' - a regexp that needn't match the start of a name
146 'set:<fileset>' - a fileset expression
146 'set:<fileset>' - a fileset expression
147 'include:<path>' - a file of patterns to read and include
147 'include:<path>' - a file of patterns to read and include
148 'subinclude:<path>' - a file of patterns to match against files under
148 'subinclude:<path>' - a file of patterns to match against files under
149 the same directory
149 the same directory
150 '<something>' - a pattern of the specified default type
150 '<something>' - a pattern of the specified default type
151
151
152 Usually a patternmatcher is returned:
152 Usually a patternmatcher is returned:
153 >>> match(b'foo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
153 >>> match(b'foo', b'.', [b're:.*\.c$', b'path:foo/a', b'*.py'])
154 <patternmatcher patterns='.*\\.c$|foo/a(?:/|$)|[^/]*\\.py$'>
154 <patternmatcher patterns='.*\\.c$|foo/a(?:/|$)|[^/]*\\.py$'>
155
155
156 Combining 'patterns' with 'include' (resp. 'exclude') gives an
156 Combining 'patterns' with 'include' (resp. 'exclude') gives an
157 intersectionmatcher (resp. a differencematcher):
157 intersectionmatcher (resp. a differencematcher):
158 >>> type(match(b'foo', b'.', [b're:.*\.c$'], include=[b'path:lib']))
158 >>> type(match(b'foo', b'.', [b're:.*\.c$'], include=[b'path:lib']))
159 <class 'mercurial.match.intersectionmatcher'>
159 <class 'mercurial.match.intersectionmatcher'>
160 >>> type(match(b'foo', b'.', [b're:.*\.c$'], exclude=[b'path:build']))
160 >>> type(match(b'foo', b'.', [b're:.*\.c$'], exclude=[b'path:build']))
161 <class 'mercurial.match.differencematcher'>
161 <class 'mercurial.match.differencematcher'>
162
162
163 Notice that, if 'patterns' is empty, an alwaysmatcher is returned:
163 Notice that, if 'patterns' is empty, an alwaysmatcher is returned:
164 >>> match(b'foo', b'.', [])
164 >>> match(b'foo', b'.', [])
165 <alwaysmatcher>
165 <alwaysmatcher>
166
166
167 The 'default' argument determines which kind of pattern is assumed if a
167 The 'default' argument determines which kind of pattern is assumed if a
168 pattern has no prefix:
168 pattern has no prefix:
169 >>> match(b'foo', b'.', [b'.*\.c$'], default=b're')
169 >>> match(b'foo', b'.', [b'.*\.c$'], default=b're')
170 <patternmatcher patterns='.*\\.c$'>
170 <patternmatcher patterns='.*\\.c$'>
171 >>> match(b'foo', b'.', [b'main.py'], default=b'relpath')
171 >>> match(b'foo', b'.', [b'main.py'], default=b'relpath')
172 <patternmatcher patterns='main\\.py(?:/|$)'>
172 <patternmatcher patterns='main\\.py(?:/|$)'>
173 >>> match(b'foo', b'.', [b'main.py'], default=b're')
173 >>> match(b'foo', b'.', [b'main.py'], default=b're')
174 <patternmatcher patterns='main.py'>
174 <patternmatcher patterns='main.py'>
175
175
176 The primary use of matchers is to check whether a value (usually a file
176 The primary use of matchers is to check whether a value (usually a file
177 name) matches againset one of the patterns given at initialization. There
177 name) matches againset one of the patterns given at initialization. There
178 are two ways of doing this check.
178 are two ways of doing this check.
179
179
180 >>> m = match(b'foo', b'', [b're:.*\.c$', b'relpath:a'])
180 >>> m = match(b'foo', b'', [b're:.*\.c$', b'relpath:a'])
181
181
182 1. Calling the matcher with a file name returns True if any pattern
182 1. Calling the matcher with a file name returns True if any pattern
183 matches that file name:
183 matches that file name:
184 >>> m(b'a')
184 >>> m(b'a')
185 True
185 True
186 >>> m(b'main.c')
186 >>> m(b'main.c')
187 True
187 True
188 >>> m(b'test.py')
188 >>> m(b'test.py')
189 False
189 False
190
190
191 2. Using the exact() method only returns True if the file name matches one
191 2. Using the exact() method only returns True if the file name matches one
192 of the exact patterns (i.e. not re: or glob: patterns):
192 of the exact patterns (i.e. not re: or glob: patterns):
193 >>> m.exact(b'a')
193 >>> m.exact(b'a')
194 True
194 True
195 >>> m.exact(b'main.c')
195 >>> m.exact(b'main.c')
196 False
196 False
197 """
197 """
198 normalize = _donormalize
198 normalize = _donormalize
199 if icasefs:
199 if icasefs:
200 dirstate = ctx.repo().dirstate
200 dirstate = ctx.repo().dirstate
201 dsnormalize = dirstate.normalize
201 dsnormalize = dirstate.normalize
202
202
203 def normalize(patterns, default, root, cwd, auditor, warn):
203 def normalize(patterns, default, root, cwd, auditor, warn):
204 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
204 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
205 kindpats = []
205 kindpats = []
206 for kind, pats, source in kp:
206 for kind, pats, source in kp:
207 if kind not in ('re', 'relre'): # regex can't be normalized
207 if kind not in ('re', 'relre'): # regex can't be normalized
208 p = pats
208 p = pats
209 pats = dsnormalize(pats)
209 pats = dsnormalize(pats)
210
210
211 # Preserve the original to handle a case only rename.
211 # Preserve the original to handle a case only rename.
212 if p != pats and p in dirstate:
212 if p != pats and p in dirstate:
213 kindpats.append((kind, p, source))
213 kindpats.append((kind, p, source))
214
214
215 kindpats.append((kind, pats, source))
215 kindpats.append((kind, pats, source))
216 return kindpats
216 return kindpats
217
217
218 if patterns:
218 if patterns:
219 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
219 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
220 if _kindpatsalwaysmatch(kindpats):
220 if _kindpatsalwaysmatch(kindpats):
221 m = alwaysmatcher(badfn)
221 m = alwaysmatcher(badfn)
222 else:
222 else:
223 m = _buildkindpatsmatcher(patternmatcher, root, kindpats, ctx=ctx,
223 m = _buildkindpatsmatcher(patternmatcher, root, kindpats, ctx=ctx,
224 listsubrepos=listsubrepos, badfn=badfn)
224 listsubrepos=listsubrepos, badfn=badfn)
225 else:
225 else:
226 # It's a little strange that no patterns means to match everything.
226 # It's a little strange that no patterns means to match everything.
227 # Consider changing this to match nothing (probably using nevermatcher).
227 # Consider changing this to match nothing (probably using nevermatcher).
228 m = alwaysmatcher(badfn)
228 m = alwaysmatcher(badfn)
229
229
230 if include:
230 if include:
231 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
231 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
232 im = _buildkindpatsmatcher(includematcher, root, kindpats, ctx=ctx,
232 im = _buildkindpatsmatcher(includematcher, root, kindpats, ctx=ctx,
233 listsubrepos=listsubrepos, badfn=None)
233 listsubrepos=listsubrepos, badfn=None)
234 m = intersectmatchers(m, im)
234 m = intersectmatchers(m, im)
235 if exclude:
235 if exclude:
236 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
236 kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
237 em = _buildkindpatsmatcher(includematcher, root, kindpats, ctx=ctx,
237 em = _buildkindpatsmatcher(includematcher, root, kindpats, ctx=ctx,
238 listsubrepos=listsubrepos, badfn=None)
238 listsubrepos=listsubrepos, badfn=None)
239 m = differencematcher(m, em)
239 m = differencematcher(m, em)
240 return m
240 return m
241
241
242 def exact(files, badfn=None):
242 def exact(files, badfn=None):
243 return exactmatcher(files, badfn=badfn)
243 return exactmatcher(files, badfn=badfn)
244
244
245 def always(badfn=None):
245 def always(badfn=None):
246 return alwaysmatcher(badfn)
246 return alwaysmatcher(badfn)
247
247
248 def never(badfn=None):
248 def never(badfn=None):
249 return nevermatcher(badfn)
249 return nevermatcher(badfn)
250
250
251 def badmatch(match, badfn):
251 def badmatch(match, badfn):
252 """Make a copy of the given matcher, replacing its bad method with the given
252 """Make a copy of the given matcher, replacing its bad method with the given
253 one.
253 one.
254 """
254 """
255 m = copy.copy(match)
255 m = copy.copy(match)
256 m.bad = badfn
256 m.bad = badfn
257 return m
257 return m
258
258
259 def _donormalize(patterns, default, root, cwd, auditor=None, warn=None):
259 def _donormalize(patterns, default, root, cwd, auditor=None, warn=None):
260 '''Convert 'kind:pat' from the patterns list to tuples with kind and
260 '''Convert 'kind:pat' from the patterns list to tuples with kind and
261 normalized and rooted patterns and with listfiles expanded.'''
261 normalized and rooted patterns and with listfiles expanded.'''
262 kindpats = []
262 kindpats = []
263 for kind, pat in [_patsplit(p, default) for p in patterns]:
263 for kind, pat in [_patsplit(p, default) for p in patterns]:
264 if kind in cwdrelativepatternkinds:
264 if kind in cwdrelativepatternkinds:
265 pat = pathutil.canonpath(root, cwd, pat, auditor=auditor)
265 pat = pathutil.canonpath(root, cwd, pat, auditor=auditor)
266 elif kind in ('relglob', 'path', 'rootfilesin', 'rootglob'):
266 elif kind in ('relglob', 'path', 'rootfilesin', 'rootglob'):
267 pat = util.normpath(pat)
267 pat = util.normpath(pat)
268 elif kind in ('listfile', 'listfile0'):
268 elif kind in ('listfile', 'listfile0'):
269 try:
269 try:
270 files = util.readfile(pat)
270 files = util.readfile(pat)
271 if kind == 'listfile0':
271 if kind == 'listfile0':
272 files = files.split('\0')
272 files = files.split('\0')
273 else:
273 else:
274 files = files.splitlines()
274 files = files.splitlines()
275 files = [f for f in files if f]
275 files = [f for f in files if f]
276 except EnvironmentError:
276 except EnvironmentError:
277 raise error.Abort(_("unable to read file list (%s)") % pat)
277 raise error.Abort(_("unable to read file list (%s)") % pat)
278 for k, p, source in _donormalize(files, default, root, cwd,
278 for k, p, source in _donormalize(files, default, root, cwd,
279 auditor, warn):
279 auditor, warn):
280 kindpats.append((k, p, pat))
280 kindpats.append((k, p, pat))
281 continue
281 continue
282 elif kind == 'include':
282 elif kind == 'include':
283 try:
283 try:
284 fullpath = os.path.join(root, util.localpath(pat))
284 fullpath = os.path.join(root, util.localpath(pat))
285 includepats = readpatternfile(fullpath, warn)
285 includepats = readpatternfile(fullpath, warn)
286 for k, p, source in _donormalize(includepats, default,
286 for k, p, source in _donormalize(includepats, default,
287 root, cwd, auditor, warn):
287 root, cwd, auditor, warn):
288 kindpats.append((k, p, source or pat))
288 kindpats.append((k, p, source or pat))
289 except error.Abort as inst:
289 except error.Abort as inst:
290 raise error.Abort('%s: %s' % (pat, inst[0]))
290 raise error.Abort('%s: %s' % (pat, inst[0]))
291 except IOError as inst:
291 except IOError as inst:
292 if warn:
292 if warn:
293 warn(_("skipping unreadable pattern file '%s': %s\n") %
293 warn(_("skipping unreadable pattern file '%s': %s\n") %
294 (pat, stringutil.forcebytestr(inst.strerror)))
294 (pat, stringutil.forcebytestr(inst.strerror)))
295 continue
295 continue
296 # else: re or relre - which cannot be normalized
296 # else: re or relre - which cannot be normalized
297 kindpats.append((kind, pat, ''))
297 kindpats.append((kind, pat, ''))
298 return kindpats
298 return kindpats
299
299
300 class basematcher(object):
300 class basematcher(object):
301
301
302 def __init__(self, badfn=None):
302 def __init__(self, badfn=None):
303 if badfn is not None:
303 if badfn is not None:
304 self.bad = badfn
304 self.bad = badfn
305
305
306 def __call__(self, fn):
306 def __call__(self, fn):
307 return self.matchfn(fn)
307 return self.matchfn(fn)
308 def __iter__(self):
309 for f in self._files:
310 yield f
311 # Callbacks related to how the matcher is used by dirstate.walk.
308 # Callbacks related to how the matcher is used by dirstate.walk.
312 # Subscribers to these events must monkeypatch the matcher object.
309 # Subscribers to these events must monkeypatch the matcher object.
313 def bad(self, f, msg):
310 def bad(self, f, msg):
314 '''Callback from dirstate.walk for each explicit file that can't be
311 '''Callback from dirstate.walk for each explicit file that can't be
315 found/accessed, with an error message.'''
312 found/accessed, with an error message.'''
316
313
317 # If an explicitdir is set, it will be called when an explicitly listed
314 # If an explicitdir is set, it will be called when an explicitly listed
318 # directory is visited.
315 # directory is visited.
319 explicitdir = None
316 explicitdir = None
320
317
321 # If an traversedir is set, it will be called when a directory discovered
318 # If an traversedir is set, it will be called when a directory discovered
322 # by recursive traversal is visited.
319 # by recursive traversal is visited.
323 traversedir = None
320 traversedir = None
324
321
325 @propertycache
322 @propertycache
326 def _files(self):
323 def _files(self):
327 return []
324 return []
328
325
329 def files(self):
326 def files(self):
330 '''Explicitly listed files or patterns or roots:
327 '''Explicitly listed files or patterns or roots:
331 if no patterns or .always(): empty list,
328 if no patterns or .always(): empty list,
332 if exact: list exact files,
329 if exact: list exact files,
333 if not .anypats(): list all files and dirs,
330 if not .anypats(): list all files and dirs,
334 else: optimal roots'''
331 else: optimal roots'''
335 return self._files
332 return self._files
336
333
337 @propertycache
334 @propertycache
338 def _fileset(self):
335 def _fileset(self):
339 return set(self._files)
336 return set(self._files)
340
337
341 def exact(self, f):
338 def exact(self, f):
342 '''Returns True if f is in .files().'''
339 '''Returns True if f is in .files().'''
343 return f in self._fileset
340 return f in self._fileset
344
341
345 def matchfn(self, f):
342 def matchfn(self, f):
346 return False
343 return False
347
344
348 def visitdir(self, dir):
345 def visitdir(self, dir):
349 '''Decides whether a directory should be visited based on whether it
346 '''Decides whether a directory should be visited based on whether it
350 has potential matches in it or one of its subdirectories. This is
347 has potential matches in it or one of its subdirectories. This is
351 based on the match's primary, included, and excluded patterns.
348 based on the match's primary, included, and excluded patterns.
352
349
353 Returns the string 'all' if the given directory and all subdirectories
350 Returns the string 'all' if the given directory and all subdirectories
354 should be visited. Otherwise returns True or False indicating whether
351 should be visited. Otherwise returns True or False indicating whether
355 the given directory should be visited.
352 the given directory should be visited.
356 '''
353 '''
357 return True
354 return True
358
355
359 def visitchildrenset(self, dir):
356 def visitchildrenset(self, dir):
360 '''Decides whether a directory should be visited based on whether it
357 '''Decides whether a directory should be visited based on whether it
361 has potential matches in it or one of its subdirectories, and
358 has potential matches in it or one of its subdirectories, and
362 potentially lists which subdirectories of that directory should be
359 potentially lists which subdirectories of that directory should be
363 visited. This is based on the match's primary, included, and excluded
360 visited. This is based on the match's primary, included, and excluded
364 patterns.
361 patterns.
365
362
366 This function is very similar to 'visitdir', and the following mapping
363 This function is very similar to 'visitdir', and the following mapping
367 can be applied:
364 can be applied:
368
365
369 visitdir | visitchildrenlist
366 visitdir | visitchildrenlist
370 ----------+-------------------
367 ----------+-------------------
371 False | set()
368 False | set()
372 'all' | 'all'
369 'all' | 'all'
373 True | 'this' OR non-empty set of subdirs -or files- to visit
370 True | 'this' OR non-empty set of subdirs -or files- to visit
374
371
375 Example:
372 Example:
376 Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
373 Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
377 the following values (assuming the implementation of visitchildrenset
374 the following values (assuming the implementation of visitchildrenset
378 is capable of recognizing this; some implementations are not).
375 is capable of recognizing this; some implementations are not).
379
376
380 '.' -> {'foo', 'qux'}
377 '.' -> {'foo', 'qux'}
381 'baz' -> set()
378 'baz' -> set()
382 'foo' -> {'bar'}
379 'foo' -> {'bar'}
383 # Ideally this would be 'all', but since the prefix nature of matchers
380 # Ideally this would be 'all', but since the prefix nature of matchers
384 # is applied to the entire matcher, we have to downgrade this to
381 # is applied to the entire matcher, we have to downgrade this to
385 # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
382 # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
386 # in.
383 # in.
387 'foo/bar' -> 'this'
384 'foo/bar' -> 'this'
388 'qux' -> 'this'
385 'qux' -> 'this'
389
386
390 Important:
387 Important:
391 Most matchers do not know if they're representing files or
388 Most matchers do not know if they're representing files or
392 directories. They see ['path:dir/f'] and don't know whether 'f' is a
389 directories. They see ['path:dir/f'] and don't know whether 'f' is a
393 file or a directory, so visitchildrenset('dir') for most matchers will
390 file or a directory, so visitchildrenset('dir') for most matchers will
394 return {'f'}, but if the matcher knows it's a file (like exactmatcher
391 return {'f'}, but if the matcher knows it's a file (like exactmatcher
395 does), it may return 'this'. Do not rely on the return being a set
392 does), it may return 'this'. Do not rely on the return being a set
396 indicating that there are no files in this dir to investigate (or
393 indicating that there are no files in this dir to investigate (or
397 equivalently that if there are files to investigate in 'dir' that it
394 equivalently that if there are files to investigate in 'dir' that it
398 will always return 'this').
395 will always return 'this').
399 '''
396 '''
400 return 'this'
397 return 'this'
401
398
402 def always(self):
399 def always(self):
403 '''Matcher will match everything and .files() will be empty --
400 '''Matcher will match everything and .files() will be empty --
404 optimization might be possible.'''
401 optimization might be possible.'''
405 return False
402 return False
406
403
407 def isexact(self):
404 def isexact(self):
408 '''Matcher will match exactly the list of files in .files() --
405 '''Matcher will match exactly the list of files in .files() --
409 optimization might be possible.'''
406 optimization might be possible.'''
410 return False
407 return False
411
408
412 def prefix(self):
409 def prefix(self):
413 '''Matcher will match the paths in .files() recursively --
410 '''Matcher will match the paths in .files() recursively --
414 optimization might be possible.'''
411 optimization might be possible.'''
415 return False
412 return False
416
413
417 def anypats(self):
414 def anypats(self):
418 '''None of .always(), .isexact(), and .prefix() is true --
415 '''None of .always(), .isexact(), and .prefix() is true --
419 optimizations will be difficult.'''
416 optimizations will be difficult.'''
420 return not self.always() and not self.isexact() and not self.prefix()
417 return not self.always() and not self.isexact() and not self.prefix()
421
418
422 class alwaysmatcher(basematcher):
419 class alwaysmatcher(basematcher):
423 '''Matches everything.'''
420 '''Matches everything.'''
424
421
425 def __init__(self, badfn=None):
422 def __init__(self, badfn=None):
426 super(alwaysmatcher, self).__init__(badfn)
423 super(alwaysmatcher, self).__init__(badfn)
427
424
428 def always(self):
425 def always(self):
429 return True
426 return True
430
427
431 def matchfn(self, f):
428 def matchfn(self, f):
432 return True
429 return True
433
430
434 def visitdir(self, dir):
431 def visitdir(self, dir):
435 return 'all'
432 return 'all'
436
433
437 def visitchildrenset(self, dir):
434 def visitchildrenset(self, dir):
438 return 'all'
435 return 'all'
439
436
440 def __repr__(self):
437 def __repr__(self):
441 return r'<alwaysmatcher>'
438 return r'<alwaysmatcher>'
442
439
443 class nevermatcher(basematcher):
440 class nevermatcher(basematcher):
444 '''Matches nothing.'''
441 '''Matches nothing.'''
445
442
446 def __init__(self, badfn=None):
443 def __init__(self, badfn=None):
447 super(nevermatcher, self).__init__(badfn)
444 super(nevermatcher, self).__init__(badfn)
448
445
449 # It's a little weird to say that the nevermatcher is an exact matcher
446 # It's a little weird to say that the nevermatcher is an exact matcher
450 # or a prefix matcher, but it seems to make sense to let callers take
447 # or a prefix matcher, but it seems to make sense to let callers take
451 # fast paths based on either. There will be no exact matches, nor any
448 # fast paths based on either. There will be no exact matches, nor any
452 # prefixes (files() returns []), so fast paths iterating over them should
449 # prefixes (files() returns []), so fast paths iterating over them should
453 # be efficient (and correct).
450 # be efficient (and correct).
454 def isexact(self):
451 def isexact(self):
455 return True
452 return True
456
453
457 def prefix(self):
454 def prefix(self):
458 return True
455 return True
459
456
460 def visitdir(self, dir):
457 def visitdir(self, dir):
461 return False
458 return False
462
459
463 def visitchildrenset(self, dir):
460 def visitchildrenset(self, dir):
464 return set()
461 return set()
465
462
466 def __repr__(self):
463 def __repr__(self):
467 return r'<nevermatcher>'
464 return r'<nevermatcher>'
468
465
469 class predicatematcher(basematcher):
466 class predicatematcher(basematcher):
470 """A matcher adapter for a simple boolean function"""
467 """A matcher adapter for a simple boolean function"""
471
468
472 def __init__(self, predfn, predrepr=None, badfn=None):
469 def __init__(self, predfn, predrepr=None, badfn=None):
473 super(predicatematcher, self).__init__(badfn)
470 super(predicatematcher, self).__init__(badfn)
474 self.matchfn = predfn
471 self.matchfn = predfn
475 self._predrepr = predrepr
472 self._predrepr = predrepr
476
473
477 @encoding.strmethod
474 @encoding.strmethod
478 def __repr__(self):
475 def __repr__(self):
479 s = (stringutil.buildrepr(self._predrepr)
476 s = (stringutil.buildrepr(self._predrepr)
480 or pycompat.byterepr(self.matchfn))
477 or pycompat.byterepr(self.matchfn))
481 return '<predicatenmatcher pred=%s>' % s
478 return '<predicatenmatcher pred=%s>' % s
482
479
483 class patternmatcher(basematcher):
480 class patternmatcher(basematcher):
484 """Matches a set of (kind, pat, source) against a 'root' directory.
481 """Matches a set of (kind, pat, source) against a 'root' directory.
485
482
486 >>> kindpats = [
483 >>> kindpats = [
487 ... (b're', br'.*\.c$', b''),
484 ... (b're', br'.*\.c$', b''),
488 ... (b'path', b'foo/a', b''),
485 ... (b'path', b'foo/a', b''),
489 ... (b'relpath', b'b', b''),
486 ... (b'relpath', b'b', b''),
490 ... (b'glob', b'*.h', b''),
487 ... (b'glob', b'*.h', b''),
491 ... ]
488 ... ]
492 >>> m = patternmatcher(b'foo', kindpats)
489 >>> m = patternmatcher(b'foo', kindpats)
493 >>> m(b'main.c') # matches re:.*\.c$
490 >>> m(b'main.c') # matches re:.*\.c$
494 True
491 True
495 >>> m(b'b.txt')
492 >>> m(b'b.txt')
496 False
493 False
497 >>> m(b'foo/a') # matches path:foo/a
494 >>> m(b'foo/a') # matches path:foo/a
498 True
495 True
499 >>> m(b'a') # does not match path:b, since 'root' is 'foo'
496 >>> m(b'a') # does not match path:b, since 'root' is 'foo'
500 False
497 False
501 >>> m(b'b') # matches relpath:b, since 'root' is 'foo'
498 >>> m(b'b') # matches relpath:b, since 'root' is 'foo'
502 True
499 True
503 >>> m(b'lib.h') # matches glob:*.h
500 >>> m(b'lib.h') # matches glob:*.h
504 True
501 True
505
502
506 >>> m.files()
503 >>> m.files()
507 ['.', 'foo/a', 'b', '.']
504 ['.', 'foo/a', 'b', '.']
508 >>> m.exact(b'foo/a')
505 >>> m.exact(b'foo/a')
509 True
506 True
510 >>> m.exact(b'b')
507 >>> m.exact(b'b')
511 True
508 True
512 >>> m.exact(b'lib.h') # exact matches are for (rel)path kinds
509 >>> m.exact(b'lib.h') # exact matches are for (rel)path kinds
513 False
510 False
514 """
511 """
515
512
516 def __init__(self, root, kindpats, badfn=None):
513 def __init__(self, root, kindpats, badfn=None):
517 super(patternmatcher, self).__init__(badfn)
514 super(patternmatcher, self).__init__(badfn)
518
515
519 self._files = _explicitfiles(kindpats)
516 self._files = _explicitfiles(kindpats)
520 self._prefix = _prefix(kindpats)
517 self._prefix = _prefix(kindpats)
521 self._pats, self.matchfn = _buildmatch(kindpats, '$', root)
518 self._pats, self.matchfn = _buildmatch(kindpats, '$', root)
522
519
523 @propertycache
520 @propertycache
524 def _dirs(self):
521 def _dirs(self):
525 return set(util.dirs(self._fileset)) | {'.'}
522 return set(util.dirs(self._fileset)) | {'.'}
526
523
527 def visitdir(self, dir):
524 def visitdir(self, dir):
528 if self._prefix and dir in self._fileset:
525 if self._prefix and dir in self._fileset:
529 return 'all'
526 return 'all'
530 return ('.' in self._fileset or
527 return ('.' in self._fileset or
531 dir in self._fileset or
528 dir in self._fileset or
532 dir in self._dirs or
529 dir in self._dirs or
533 any(parentdir in self._fileset
530 any(parentdir in self._fileset
534 for parentdir in util.finddirs(dir)))
531 for parentdir in util.finddirs(dir)))
535
532
536 def visitchildrenset(self, dir):
533 def visitchildrenset(self, dir):
537 ret = self.visitdir(dir)
534 ret = self.visitdir(dir)
538 if ret is True:
535 if ret is True:
539 return 'this'
536 return 'this'
540 elif not ret:
537 elif not ret:
541 return set()
538 return set()
542 assert ret == 'all'
539 assert ret == 'all'
543 return 'all'
540 return 'all'
544
541
545 def prefix(self):
542 def prefix(self):
546 return self._prefix
543 return self._prefix
547
544
548 @encoding.strmethod
545 @encoding.strmethod
549 def __repr__(self):
546 def __repr__(self):
550 return ('<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats))
547 return ('<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats))
551
548
552 # This is basically a reimplementation of util.dirs that stores the children
549 # This is basically a reimplementation of util.dirs that stores the children
553 # instead of just a count of them, plus a small optional optimization to avoid
550 # instead of just a count of them, plus a small optional optimization to avoid
554 # some directories we don't need.
551 # some directories we don't need.
555 class _dirchildren(object):
552 class _dirchildren(object):
556 def __init__(self, paths, onlyinclude=None):
553 def __init__(self, paths, onlyinclude=None):
557 self._dirs = {}
554 self._dirs = {}
558 self._onlyinclude = onlyinclude or []
555 self._onlyinclude = onlyinclude or []
559 addpath = self.addpath
556 addpath = self.addpath
560 for f in paths:
557 for f in paths:
561 addpath(f)
558 addpath(f)
562
559
563 def addpath(self, path):
560 def addpath(self, path):
564 if path == '.':
561 if path == '.':
565 return
562 return
566 dirs = self._dirs
563 dirs = self._dirs
567 findsplitdirs = _dirchildren._findsplitdirs
564 findsplitdirs = _dirchildren._findsplitdirs
568 for d, b in findsplitdirs(path):
565 for d, b in findsplitdirs(path):
569 if d not in self._onlyinclude:
566 if d not in self._onlyinclude:
570 continue
567 continue
571 dirs.setdefault(d, set()).add(b)
568 dirs.setdefault(d, set()).add(b)
572
569
573 @staticmethod
570 @staticmethod
574 def _findsplitdirs(path):
571 def _findsplitdirs(path):
575 # yields (dirname, basename) tuples, walking back to the root. This is
572 # yields (dirname, basename) tuples, walking back to the root. This is
576 # very similar to util.finddirs, except:
573 # very similar to util.finddirs, except:
577 # - produces a (dirname, basename) tuple, not just 'dirname'
574 # - produces a (dirname, basename) tuple, not just 'dirname'
578 # - includes root dir
575 # - includes root dir
579 # Unlike manifest._splittopdir, this does not suffix `dirname` with a
576 # Unlike manifest._splittopdir, this does not suffix `dirname` with a
580 # slash, and produces '.' for the root instead of ''.
577 # slash, and produces '.' for the root instead of ''.
581 oldpos = len(path)
578 oldpos = len(path)
582 pos = path.rfind('/')
579 pos = path.rfind('/')
583 while pos != -1:
580 while pos != -1:
584 yield path[:pos], path[pos + 1:oldpos]
581 yield path[:pos], path[pos + 1:oldpos]
585 oldpos = pos
582 oldpos = pos
586 pos = path.rfind('/', 0, pos)
583 pos = path.rfind('/', 0, pos)
587 yield '.', path[:oldpos]
584 yield '.', path[:oldpos]
588
585
589 def get(self, path):
586 def get(self, path):
590 return self._dirs.get(path, set())
587 return self._dirs.get(path, set())
591
588
592 class includematcher(basematcher):
589 class includematcher(basematcher):
593
590
594 def __init__(self, root, kindpats, badfn=None):
591 def __init__(self, root, kindpats, badfn=None):
595 super(includematcher, self).__init__(badfn)
592 super(includematcher, self).__init__(badfn)
596
593
597 self._pats, self.matchfn = _buildmatch(kindpats, '(?:/|$)', root)
594 self._pats, self.matchfn = _buildmatch(kindpats, '(?:/|$)', root)
598 self._prefix = _prefix(kindpats)
595 self._prefix = _prefix(kindpats)
599 roots, dirs, parents = _rootsdirsandparents(kindpats)
596 roots, dirs, parents = _rootsdirsandparents(kindpats)
600 # roots are directories which are recursively included.
597 # roots are directories which are recursively included.
601 self._roots = set(roots)
598 self._roots = set(roots)
602 # dirs are directories which are non-recursively included.
599 # dirs are directories which are non-recursively included.
603 self._dirs = set(dirs)
600 self._dirs = set(dirs)
604 # parents are directories which are non-recursively included because
601 # parents are directories which are non-recursively included because
605 # they are needed to get to items in _dirs or _roots.
602 # they are needed to get to items in _dirs or _roots.
606 self._parents = set(parents)
603 self._parents = set(parents)
607
604
608 def visitdir(self, dir):
605 def visitdir(self, dir):
609 if self._prefix and dir in self._roots:
606 if self._prefix and dir in self._roots:
610 return 'all'
607 return 'all'
611 return ('.' in self._roots or
608 return ('.' in self._roots or
612 dir in self._roots or
609 dir in self._roots or
613 dir in self._dirs or
610 dir in self._dirs or
614 dir in self._parents or
611 dir in self._parents or
615 any(parentdir in self._roots
612 any(parentdir in self._roots
616 for parentdir in util.finddirs(dir)))
613 for parentdir in util.finddirs(dir)))
617
614
618 @propertycache
615 @propertycache
619 def _allparentschildren(self):
616 def _allparentschildren(self):
620 # It may seem odd that we add dirs, roots, and parents, and then
617 # It may seem odd that we add dirs, roots, and parents, and then
621 # restrict to only parents. This is to catch the case of:
618 # restrict to only parents. This is to catch the case of:
622 # dirs = ['foo/bar']
619 # dirs = ['foo/bar']
623 # parents = ['foo']
620 # parents = ['foo']
624 # if we asked for the children of 'foo', but had only added
621 # if we asked for the children of 'foo', but had only added
625 # self._parents, we wouldn't be able to respond ['bar'].
622 # self._parents, we wouldn't be able to respond ['bar'].
626 return _dirchildren(
623 return _dirchildren(
627 itertools.chain(self._dirs, self._roots, self._parents),
624 itertools.chain(self._dirs, self._roots, self._parents),
628 onlyinclude=self._parents)
625 onlyinclude=self._parents)
629
626
630 def visitchildrenset(self, dir):
627 def visitchildrenset(self, dir):
631 if self._prefix and dir in self._roots:
628 if self._prefix and dir in self._roots:
632 return 'all'
629 return 'all'
633 # Note: this does *not* include the 'dir in self._parents' case from
630 # Note: this does *not* include the 'dir in self._parents' case from
634 # visitdir, that's handled below.
631 # visitdir, that's handled below.
635 if ('.' in self._roots or
632 if ('.' in self._roots or
636 dir in self._roots or
633 dir in self._roots or
637 dir in self._dirs or
634 dir in self._dirs or
638 any(parentdir in self._roots
635 any(parentdir in self._roots
639 for parentdir in util.finddirs(dir))):
636 for parentdir in util.finddirs(dir))):
640 return 'this'
637 return 'this'
641
638
642 if dir in self._parents:
639 if dir in self._parents:
643 return self._allparentschildren.get(dir) or set()
640 return self._allparentschildren.get(dir) or set()
644 return set()
641 return set()
645
642
646 @encoding.strmethod
643 @encoding.strmethod
647 def __repr__(self):
644 def __repr__(self):
648 return ('<includematcher includes=%r>' % pycompat.bytestr(self._pats))
645 return ('<includematcher includes=%r>' % pycompat.bytestr(self._pats))
649
646
650 class exactmatcher(basematcher):
647 class exactmatcher(basematcher):
651 r'''Matches the input files exactly. They are interpreted as paths, not
648 r'''Matches the input files exactly. They are interpreted as paths, not
652 patterns (so no kind-prefixes).
649 patterns (so no kind-prefixes).
653
650
654 >>> m = exactmatcher([b'a.txt', br're:.*\.c$'])
651 >>> m = exactmatcher([b'a.txt', br're:.*\.c$'])
655 >>> m(b'a.txt')
652 >>> m(b'a.txt')
656 True
653 True
657 >>> m(b'b.txt')
654 >>> m(b'b.txt')
658 False
655 False
659
656
660 Input files that would be matched are exactly those returned by .files()
657 Input files that would be matched are exactly those returned by .files()
661 >>> m.files()
658 >>> m.files()
662 ['a.txt', 're:.*\\.c$']
659 ['a.txt', 're:.*\\.c$']
663
660
664 So pattern 're:.*\.c$' is not considered as a regex, but as a file name
661 So pattern 're:.*\.c$' is not considered as a regex, but as a file name
665 >>> m(b'main.c')
662 >>> m(b'main.c')
666 False
663 False
667 >>> m(br're:.*\.c$')
664 >>> m(br're:.*\.c$')
668 True
665 True
669 '''
666 '''
670
667
671 def __init__(self, files, badfn=None):
668 def __init__(self, files, badfn=None):
672 super(exactmatcher, self).__init__(badfn)
669 super(exactmatcher, self).__init__(badfn)
673
670
674 if isinstance(files, list):
671 if isinstance(files, list):
675 self._files = files
672 self._files = files
676 else:
673 else:
677 self._files = list(files)
674 self._files = list(files)
678
675
679 matchfn = basematcher.exact
676 matchfn = basematcher.exact
680
677
681 @propertycache
678 @propertycache
682 def _dirs(self):
679 def _dirs(self):
683 return set(util.dirs(self._fileset)) | {'.'}
680 return set(util.dirs(self._fileset)) | {'.'}
684
681
685 def visitdir(self, dir):
682 def visitdir(self, dir):
686 return dir in self._dirs
683 return dir in self._dirs
687
684
688 def visitchildrenset(self, dir):
685 def visitchildrenset(self, dir):
689 if not self._fileset or dir not in self._dirs:
686 if not self._fileset or dir not in self._dirs:
690 return set()
687 return set()
691
688
692 candidates = self._fileset | self._dirs - {'.'}
689 candidates = self._fileset | self._dirs - {'.'}
693 if dir != '.':
690 if dir != '.':
694 d = dir + '/'
691 d = dir + '/'
695 candidates = set(c[len(d):] for c in candidates if
692 candidates = set(c[len(d):] for c in candidates if
696 c.startswith(d))
693 c.startswith(d))
697 # self._dirs includes all of the directories, recursively, so if
694 # self._dirs includes all of the directories, recursively, so if
698 # we're attempting to match foo/bar/baz.txt, it'll have '.', 'foo',
695 # we're attempting to match foo/bar/baz.txt, it'll have '.', 'foo',
699 # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
696 # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
700 # '/' in it, indicating a it's for a subdir-of-a-subdir; the
697 # '/' in it, indicating a it's for a subdir-of-a-subdir; the
701 # immediate subdir will be in there without a slash.
698 # immediate subdir will be in there without a slash.
702 ret = {c for c in candidates if '/' not in c}
699 ret = {c for c in candidates if '/' not in c}
703 # We really do not expect ret to be empty, since that would imply that
700 # We really do not expect ret to be empty, since that would imply that
704 # there's something in _dirs that didn't have a file in _fileset.
701 # there's something in _dirs that didn't have a file in _fileset.
705 assert ret
702 assert ret
706 return ret
703 return ret
707
704
708 def isexact(self):
705 def isexact(self):
709 return True
706 return True
710
707
711 @encoding.strmethod
708 @encoding.strmethod
712 def __repr__(self):
709 def __repr__(self):
713 return ('<exactmatcher files=%r>' % self._files)
710 return ('<exactmatcher files=%r>' % self._files)
714
711
715 class differencematcher(basematcher):
712 class differencematcher(basematcher):
716 '''Composes two matchers by matching if the first matches and the second
713 '''Composes two matchers by matching if the first matches and the second
717 does not.
714 does not.
718
715
719 The second matcher's non-matching-attributes (bad, explicitdir,
716 The second matcher's non-matching-attributes (bad, explicitdir,
720 traversedir) are ignored.
717 traversedir) are ignored.
721 '''
718 '''
722 def __init__(self, m1, m2):
719 def __init__(self, m1, m2):
723 super(differencematcher, self).__init__()
720 super(differencematcher, self).__init__()
724 self._m1 = m1
721 self._m1 = m1
725 self._m2 = m2
722 self._m2 = m2
726 self.bad = m1.bad
723 self.bad = m1.bad
727 self.explicitdir = m1.explicitdir
724 self.explicitdir = m1.explicitdir
728 self.traversedir = m1.traversedir
725 self.traversedir = m1.traversedir
729
726
730 def matchfn(self, f):
727 def matchfn(self, f):
731 return self._m1(f) and not self._m2(f)
728 return self._m1(f) and not self._m2(f)
732
729
733 @propertycache
730 @propertycache
734 def _files(self):
731 def _files(self):
735 if self.isexact():
732 if self.isexact():
736 return [f for f in self._m1.files() if self(f)]
733 return [f for f in self._m1.files() if self(f)]
737 # If m1 is not an exact matcher, we can't easily figure out the set of
734 # If m1 is not an exact matcher, we can't easily figure out the set of
738 # files, because its files() are not always files. For example, if
735 # files, because its files() are not always files. For example, if
739 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
736 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
740 # want to remove "dir" from the set even though it would match m2,
737 # want to remove "dir" from the set even though it would match m2,
741 # because the "dir" in m1 may not be a file.
738 # because the "dir" in m1 may not be a file.
742 return self._m1.files()
739 return self._m1.files()
743
740
744 def visitdir(self, dir):
741 def visitdir(self, dir):
745 if self._m2.visitdir(dir) == 'all':
742 if self._m2.visitdir(dir) == 'all':
746 return False
743 return False
747 elif not self._m2.visitdir(dir):
744 elif not self._m2.visitdir(dir):
748 # m2 does not match dir, we can return 'all' here if possible
745 # m2 does not match dir, we can return 'all' here if possible
749 return self._m1.visitdir(dir)
746 return self._m1.visitdir(dir)
750 return bool(self._m1.visitdir(dir))
747 return bool(self._m1.visitdir(dir))
751
748
752 def visitchildrenset(self, dir):
749 def visitchildrenset(self, dir):
753 m2_set = self._m2.visitchildrenset(dir)
750 m2_set = self._m2.visitchildrenset(dir)
754 if m2_set == 'all':
751 if m2_set == 'all':
755 return set()
752 return set()
756 m1_set = self._m1.visitchildrenset(dir)
753 m1_set = self._m1.visitchildrenset(dir)
757 # Possible values for m1: 'all', 'this', set(...), set()
754 # Possible values for m1: 'all', 'this', set(...), set()
758 # Possible values for m2: 'this', set(...), set()
755 # Possible values for m2: 'this', set(...), set()
759 # If m2 has nothing under here that we care about, return m1, even if
756 # If m2 has nothing under here that we care about, return m1, even if
760 # it's 'all'. This is a change in behavior from visitdir, which would
757 # it's 'all'. This is a change in behavior from visitdir, which would
761 # return True, not 'all', for some reason.
758 # return True, not 'all', for some reason.
762 if not m2_set:
759 if not m2_set:
763 return m1_set
760 return m1_set
764 if m1_set in ['all', 'this']:
761 if m1_set in ['all', 'this']:
765 # Never return 'all' here if m2_set is any kind of non-empty (either
762 # Never return 'all' here if m2_set is any kind of non-empty (either
766 # 'this' or set(foo)), since m2 might return set() for a
763 # 'this' or set(foo)), since m2 might return set() for a
767 # subdirectory.
764 # subdirectory.
768 return 'this'
765 return 'this'
769 # Possible values for m1: set(...), set()
766 # Possible values for m1: set(...), set()
770 # Possible values for m2: 'this', set(...)
767 # Possible values for m2: 'this', set(...)
771 # We ignore m2's set results. They're possibly incorrect:
768 # We ignore m2's set results. They're possibly incorrect:
772 # m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset('.'):
769 # m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset('.'):
773 # m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
770 # m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
774 # return set(), which is *not* correct, we still need to visit 'dir'!
771 # return set(), which is *not* correct, we still need to visit 'dir'!
775 return m1_set
772 return m1_set
776
773
777 def isexact(self):
774 def isexact(self):
778 return self._m1.isexact()
775 return self._m1.isexact()
779
776
780 @encoding.strmethod
777 @encoding.strmethod
781 def __repr__(self):
778 def __repr__(self):
782 return ('<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2))
779 return ('<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2))
783
780
784 def intersectmatchers(m1, m2):
781 def intersectmatchers(m1, m2):
785 '''Composes two matchers by matching if both of them match.
782 '''Composes two matchers by matching if both of them match.
786
783
787 The second matcher's non-matching-attributes (bad, explicitdir,
784 The second matcher's non-matching-attributes (bad, explicitdir,
788 traversedir) are ignored.
785 traversedir) are ignored.
789 '''
786 '''
790 if m1 is None or m2 is None:
787 if m1 is None or m2 is None:
791 return m1 or m2
788 return m1 or m2
792 if m1.always():
789 if m1.always():
793 m = copy.copy(m2)
790 m = copy.copy(m2)
794 # TODO: Consider encapsulating these things in a class so there's only
791 # TODO: Consider encapsulating these things in a class so there's only
795 # one thing to copy from m1.
792 # one thing to copy from m1.
796 m.bad = m1.bad
793 m.bad = m1.bad
797 m.explicitdir = m1.explicitdir
794 m.explicitdir = m1.explicitdir
798 m.traversedir = m1.traversedir
795 m.traversedir = m1.traversedir
799 return m
796 return m
800 if m2.always():
797 if m2.always():
801 m = copy.copy(m1)
798 m = copy.copy(m1)
802 return m
799 return m
803 return intersectionmatcher(m1, m2)
800 return intersectionmatcher(m1, m2)
804
801
805 class intersectionmatcher(basematcher):
802 class intersectionmatcher(basematcher):
806 def __init__(self, m1, m2):
803 def __init__(self, m1, m2):
807 super(intersectionmatcher, self).__init__()
804 super(intersectionmatcher, self).__init__()
808 self._m1 = m1
805 self._m1 = m1
809 self._m2 = m2
806 self._m2 = m2
810 self.bad = m1.bad
807 self.bad = m1.bad
811 self.explicitdir = m1.explicitdir
808 self.explicitdir = m1.explicitdir
812 self.traversedir = m1.traversedir
809 self.traversedir = m1.traversedir
813
810
814 @propertycache
811 @propertycache
815 def _files(self):
812 def _files(self):
816 if self.isexact():
813 if self.isexact():
817 m1, m2 = self._m1, self._m2
814 m1, m2 = self._m1, self._m2
818 if not m1.isexact():
815 if not m1.isexact():
819 m1, m2 = m2, m1
816 m1, m2 = m2, m1
820 return [f for f in m1.files() if m2(f)]
817 return [f for f in m1.files() if m2(f)]
821 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
818 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
822 # the set of files, because their files() are not always files. For
819 # the set of files, because their files() are not always files. For
823 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
820 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
824 # "path:dir2", we don't want to remove "dir2" from the set.
821 # "path:dir2", we don't want to remove "dir2" from the set.
825 return self._m1.files() + self._m2.files()
822 return self._m1.files() + self._m2.files()
826
823
827 def matchfn(self, f):
824 def matchfn(self, f):
828 return self._m1(f) and self._m2(f)
825 return self._m1(f) and self._m2(f)
829
826
830 def visitdir(self, dir):
827 def visitdir(self, dir):
831 visit1 = self._m1.visitdir(dir)
828 visit1 = self._m1.visitdir(dir)
832 if visit1 == 'all':
829 if visit1 == 'all':
833 return self._m2.visitdir(dir)
830 return self._m2.visitdir(dir)
834 # bool() because visit1=True + visit2='all' should not be 'all'
831 # bool() because visit1=True + visit2='all' should not be 'all'
835 return bool(visit1 and self._m2.visitdir(dir))
832 return bool(visit1 and self._m2.visitdir(dir))
836
833
837 def visitchildrenset(self, dir):
834 def visitchildrenset(self, dir):
838 m1_set = self._m1.visitchildrenset(dir)
835 m1_set = self._m1.visitchildrenset(dir)
839 if not m1_set:
836 if not m1_set:
840 return set()
837 return set()
841 m2_set = self._m2.visitchildrenset(dir)
838 m2_set = self._m2.visitchildrenset(dir)
842 if not m2_set:
839 if not m2_set:
843 return set()
840 return set()
844
841
845 if m1_set == 'all':
842 if m1_set == 'all':
846 return m2_set
843 return m2_set
847 elif m2_set == 'all':
844 elif m2_set == 'all':
848 return m1_set
845 return m1_set
849
846
850 if m1_set == 'this' or m2_set == 'this':
847 if m1_set == 'this' or m2_set == 'this':
851 return 'this'
848 return 'this'
852
849
853 assert isinstance(m1_set, set) and isinstance(m2_set, set)
850 assert isinstance(m1_set, set) and isinstance(m2_set, set)
854 return m1_set.intersection(m2_set)
851 return m1_set.intersection(m2_set)
855
852
856 def always(self):
853 def always(self):
857 return self._m1.always() and self._m2.always()
854 return self._m1.always() and self._m2.always()
858
855
859 def isexact(self):
856 def isexact(self):
860 return self._m1.isexact() or self._m2.isexact()
857 return self._m1.isexact() or self._m2.isexact()
861
858
862 @encoding.strmethod
859 @encoding.strmethod
863 def __repr__(self):
860 def __repr__(self):
864 return ('<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2))
861 return ('<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2))
865
862
866 class subdirmatcher(basematcher):
863 class subdirmatcher(basematcher):
867 """Adapt a matcher to work on a subdirectory only.
864 """Adapt a matcher to work on a subdirectory only.
868
865
869 The paths are remapped to remove/insert the path as needed:
866 The paths are remapped to remove/insert the path as needed:
870
867
871 >>> from . import pycompat
868 >>> from . import pycompat
872 >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
869 >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
873 >>> m2 = subdirmatcher(b'sub', m1)
870 >>> m2 = subdirmatcher(b'sub', m1)
874 >>> m2(b'a.txt')
871 >>> m2(b'a.txt')
875 False
872 False
876 >>> m2(b'b.txt')
873 >>> m2(b'b.txt')
877 True
874 True
878 >>> m2.matchfn(b'a.txt')
875 >>> m2.matchfn(b'a.txt')
879 False
876 False
880 >>> m2.matchfn(b'b.txt')
877 >>> m2.matchfn(b'b.txt')
881 True
878 True
882 >>> m2.files()
879 >>> m2.files()
883 ['b.txt']
880 ['b.txt']
884 >>> m2.exact(b'b.txt')
881 >>> m2.exact(b'b.txt')
885 True
882 True
886 >>> def bad(f, msg):
883 >>> def bad(f, msg):
887 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
884 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
888 >>> m1.bad = bad
885 >>> m1.bad = bad
889 >>> m2.bad(b'x.txt', b'No such file')
886 >>> m2.bad(b'x.txt', b'No such file')
890 sub/x.txt: No such file
887 sub/x.txt: No such file
891 """
888 """
892
889
893 def __init__(self, path, matcher):
890 def __init__(self, path, matcher):
894 super(subdirmatcher, self).__init__()
891 super(subdirmatcher, self).__init__()
895 self._path = path
892 self._path = path
896 self._matcher = matcher
893 self._matcher = matcher
897 self._always = matcher.always()
894 self._always = matcher.always()
898
895
899 self._files = [f[len(path) + 1:] for f in matcher._files
896 self._files = [f[len(path) + 1:] for f in matcher._files
900 if f.startswith(path + "/")]
897 if f.startswith(path + "/")]
901
898
902 # If the parent repo had a path to this subrepo and the matcher is
899 # If the parent repo had a path to this subrepo and the matcher is
903 # a prefix matcher, this submatcher always matches.
900 # a prefix matcher, this submatcher always matches.
904 if matcher.prefix():
901 if matcher.prefix():
905 self._always = any(f == path for f in matcher._files)
902 self._always = any(f == path for f in matcher._files)
906
903
907 def bad(self, f, msg):
904 def bad(self, f, msg):
908 self._matcher.bad(self._path + "/" + f, msg)
905 self._matcher.bad(self._path + "/" + f, msg)
909
906
910 def matchfn(self, f):
907 def matchfn(self, f):
911 # Some information is lost in the superclass's constructor, so we
908 # Some information is lost in the superclass's constructor, so we
912 # can not accurately create the matching function for the subdirectory
909 # can not accurately create the matching function for the subdirectory
913 # from the inputs. Instead, we override matchfn() and visitdir() to
910 # from the inputs. Instead, we override matchfn() and visitdir() to
914 # call the original matcher with the subdirectory path prepended.
911 # call the original matcher with the subdirectory path prepended.
915 return self._matcher.matchfn(self._path + "/" + f)
912 return self._matcher.matchfn(self._path + "/" + f)
916
913
917 def visitdir(self, dir):
914 def visitdir(self, dir):
918 if dir == '.':
915 if dir == '.':
919 dir = self._path
916 dir = self._path
920 else:
917 else:
921 dir = self._path + "/" + dir
918 dir = self._path + "/" + dir
922 return self._matcher.visitdir(dir)
919 return self._matcher.visitdir(dir)
923
920
924 def visitchildrenset(self, dir):
921 def visitchildrenset(self, dir):
925 if dir == '.':
922 if dir == '.':
926 dir = self._path
923 dir = self._path
927 else:
924 else:
928 dir = self._path + "/" + dir
925 dir = self._path + "/" + dir
929 return self._matcher.visitchildrenset(dir)
926 return self._matcher.visitchildrenset(dir)
930
927
931 def always(self):
928 def always(self):
932 return self._always
929 return self._always
933
930
934 def prefix(self):
931 def prefix(self):
935 return self._matcher.prefix() and not self._always
932 return self._matcher.prefix() and not self._always
936
933
937 @encoding.strmethod
934 @encoding.strmethod
938 def __repr__(self):
935 def __repr__(self):
939 return ('<subdirmatcher path=%r, matcher=%r>' %
936 return ('<subdirmatcher path=%r, matcher=%r>' %
940 (self._path, self._matcher))
937 (self._path, self._matcher))
941
938
942 class prefixdirmatcher(basematcher):
939 class prefixdirmatcher(basematcher):
943 """Adapt a matcher to work on a parent directory.
940 """Adapt a matcher to work on a parent directory.
944
941
945 The matcher's non-matching-attributes (bad, explicitdir, traversedir) are
942 The matcher's non-matching-attributes (bad, explicitdir, traversedir) are
946 ignored.
943 ignored.
947
944
948 The prefix path should usually be the relative path from the root of
945 The prefix path should usually be the relative path from the root of
949 this matcher to the root of the wrapped matcher.
946 this matcher to the root of the wrapped matcher.
950
947
951 >>> m1 = match(util.localpath(b'root/d/e'), b'f', [b'../a.txt', b'b.txt'])
948 >>> m1 = match(util.localpath(b'root/d/e'), b'f', [b'../a.txt', b'b.txt'])
952 >>> m2 = prefixdirmatcher(b'd/e', m1)
949 >>> m2 = prefixdirmatcher(b'd/e', m1)
953 >>> m2(b'a.txt')
950 >>> m2(b'a.txt')
954 False
951 False
955 >>> m2(b'd/e/a.txt')
952 >>> m2(b'd/e/a.txt')
956 True
953 True
957 >>> m2(b'd/e/b.txt')
954 >>> m2(b'd/e/b.txt')
958 False
955 False
959 >>> m2.files()
956 >>> m2.files()
960 ['d/e/a.txt', 'd/e/f/b.txt']
957 ['d/e/a.txt', 'd/e/f/b.txt']
961 >>> m2.exact(b'd/e/a.txt')
958 >>> m2.exact(b'd/e/a.txt')
962 True
959 True
963 >>> m2.visitdir(b'd')
960 >>> m2.visitdir(b'd')
964 True
961 True
965 >>> m2.visitdir(b'd/e')
962 >>> m2.visitdir(b'd/e')
966 True
963 True
967 >>> m2.visitdir(b'd/e/f')
964 >>> m2.visitdir(b'd/e/f')
968 True
965 True
969 >>> m2.visitdir(b'd/e/g')
966 >>> m2.visitdir(b'd/e/g')
970 False
967 False
971 >>> m2.visitdir(b'd/ef')
968 >>> m2.visitdir(b'd/ef')
972 False
969 False
973 """
970 """
974
971
975 def __init__(self, path, matcher, badfn=None):
972 def __init__(self, path, matcher, badfn=None):
976 super(prefixdirmatcher, self).__init__(badfn)
973 super(prefixdirmatcher, self).__init__(badfn)
977 if not path:
974 if not path:
978 raise error.ProgrammingError('prefix path must not be empty')
975 raise error.ProgrammingError('prefix path must not be empty')
979 self._path = path
976 self._path = path
980 self._pathprefix = path + '/'
977 self._pathprefix = path + '/'
981 self._matcher = matcher
978 self._matcher = matcher
982
979
983 @propertycache
980 @propertycache
984 def _files(self):
981 def _files(self):
985 return [self._pathprefix + f for f in self._matcher._files]
982 return [self._pathprefix + f for f in self._matcher._files]
986
983
987 def matchfn(self, f):
984 def matchfn(self, f):
988 if not f.startswith(self._pathprefix):
985 if not f.startswith(self._pathprefix):
989 return False
986 return False
990 return self._matcher.matchfn(f[len(self._pathprefix):])
987 return self._matcher.matchfn(f[len(self._pathprefix):])
991
988
992 @propertycache
989 @propertycache
993 def _pathdirs(self):
990 def _pathdirs(self):
994 return set(util.finddirs(self._path)) | {'.'}
991 return set(util.finddirs(self._path)) | {'.'}
995
992
996 def visitdir(self, dir):
993 def visitdir(self, dir):
997 if dir == self._path:
994 if dir == self._path:
998 return self._matcher.visitdir('.')
995 return self._matcher.visitdir('.')
999 if dir.startswith(self._pathprefix):
996 if dir.startswith(self._pathprefix):
1000 return self._matcher.visitdir(dir[len(self._pathprefix):])
997 return self._matcher.visitdir(dir[len(self._pathprefix):])
1001 return dir in self._pathdirs
998 return dir in self._pathdirs
1002
999
1003 def visitchildrenset(self, dir):
1000 def visitchildrenset(self, dir):
1004 if dir == self._path:
1001 if dir == self._path:
1005 return self._matcher.visitchildrenset('.')
1002 return self._matcher.visitchildrenset('.')
1006 if dir.startswith(self._pathprefix):
1003 if dir.startswith(self._pathprefix):
1007 return self._matcher.visitchildrenset(dir[len(self._pathprefix):])
1004 return self._matcher.visitchildrenset(dir[len(self._pathprefix):])
1008 if dir in self._pathdirs:
1005 if dir in self._pathdirs:
1009 return 'this'
1006 return 'this'
1010 return set()
1007 return set()
1011
1008
1012 def isexact(self):
1009 def isexact(self):
1013 return self._matcher.isexact()
1010 return self._matcher.isexact()
1014
1011
1015 def prefix(self):
1012 def prefix(self):
1016 return self._matcher.prefix()
1013 return self._matcher.prefix()
1017
1014
1018 @encoding.strmethod
1015 @encoding.strmethod
1019 def __repr__(self):
1016 def __repr__(self):
1020 return ('<prefixdirmatcher path=%r, matcher=%r>'
1017 return ('<prefixdirmatcher path=%r, matcher=%r>'
1021 % (pycompat.bytestr(self._path), self._matcher))
1018 % (pycompat.bytestr(self._path), self._matcher))
1022
1019
1023 class unionmatcher(basematcher):
1020 class unionmatcher(basematcher):
1024 """A matcher that is the union of several matchers.
1021 """A matcher that is the union of several matchers.
1025
1022
1026 The non-matching-attributes (bad, explicitdir, traversedir) are taken from
1023 The non-matching-attributes (bad, explicitdir, traversedir) are taken from
1027 the first matcher.
1024 the first matcher.
1028 """
1025 """
1029
1026
1030 def __init__(self, matchers):
1027 def __init__(self, matchers):
1031 m1 = matchers[0]
1028 m1 = matchers[0]
1032 super(unionmatcher, self).__init__()
1029 super(unionmatcher, self).__init__()
1033 self.explicitdir = m1.explicitdir
1030 self.explicitdir = m1.explicitdir
1034 self.traversedir = m1.traversedir
1031 self.traversedir = m1.traversedir
1035 self._matchers = matchers
1032 self._matchers = matchers
1036
1033
1037 def matchfn(self, f):
1034 def matchfn(self, f):
1038 for match in self._matchers:
1035 for match in self._matchers:
1039 if match(f):
1036 if match(f):
1040 return True
1037 return True
1041 return False
1038 return False
1042
1039
1043 def visitdir(self, dir):
1040 def visitdir(self, dir):
1044 r = False
1041 r = False
1045 for m in self._matchers:
1042 for m in self._matchers:
1046 v = m.visitdir(dir)
1043 v = m.visitdir(dir)
1047 if v == 'all':
1044 if v == 'all':
1048 return v
1045 return v
1049 r |= v
1046 r |= v
1050 return r
1047 return r
1051
1048
1052 def visitchildrenset(self, dir):
1049 def visitchildrenset(self, dir):
1053 r = set()
1050 r = set()
1054 this = False
1051 this = False
1055 for m in self._matchers:
1052 for m in self._matchers:
1056 v = m.visitchildrenset(dir)
1053 v = m.visitchildrenset(dir)
1057 if not v:
1054 if not v:
1058 continue
1055 continue
1059 if v == 'all':
1056 if v == 'all':
1060 return v
1057 return v
1061 if this or v == 'this':
1058 if this or v == 'this':
1062 this = True
1059 this = True
1063 # don't break, we might have an 'all' in here.
1060 # don't break, we might have an 'all' in here.
1064 continue
1061 continue
1065 assert isinstance(v, set)
1062 assert isinstance(v, set)
1066 r = r.union(v)
1063 r = r.union(v)
1067 if this:
1064 if this:
1068 return 'this'
1065 return 'this'
1069 return r
1066 return r
1070
1067
1071 @encoding.strmethod
1068 @encoding.strmethod
1072 def __repr__(self):
1069 def __repr__(self):
1073 return ('<unionmatcher matchers=%r>' % self._matchers)
1070 return ('<unionmatcher matchers=%r>' % self._matchers)
1074
1071
1075 def patkind(pattern, default=None):
1072 def patkind(pattern, default=None):
1076 '''If pattern is 'kind:pat' with a known kind, return kind.
1073 '''If pattern is 'kind:pat' with a known kind, return kind.
1077
1074
1078 >>> patkind(br're:.*\.c$')
1075 >>> patkind(br're:.*\.c$')
1079 're'
1076 're'
1080 >>> patkind(b'glob:*.c')
1077 >>> patkind(b'glob:*.c')
1081 'glob'
1078 'glob'
1082 >>> patkind(b'relpath:test.py')
1079 >>> patkind(b'relpath:test.py')
1083 'relpath'
1080 'relpath'
1084 >>> patkind(b'main.py')
1081 >>> patkind(b'main.py')
1085 >>> patkind(b'main.py', default=b're')
1082 >>> patkind(b'main.py', default=b're')
1086 're'
1083 're'
1087 '''
1084 '''
1088 return _patsplit(pattern, default)[0]
1085 return _patsplit(pattern, default)[0]
1089
1086
1090 def _patsplit(pattern, default):
1087 def _patsplit(pattern, default):
1091 """Split a string into the optional pattern kind prefix and the actual
1088 """Split a string into the optional pattern kind prefix and the actual
1092 pattern."""
1089 pattern."""
1093 if ':' in pattern:
1090 if ':' in pattern:
1094 kind, pat = pattern.split(':', 1)
1091 kind, pat = pattern.split(':', 1)
1095 if kind in allpatternkinds:
1092 if kind in allpatternkinds:
1096 return kind, pat
1093 return kind, pat
1097 return default, pattern
1094 return default, pattern
1098
1095
1099 def _globre(pat):
1096 def _globre(pat):
1100 r'''Convert an extended glob string to a regexp string.
1097 r'''Convert an extended glob string to a regexp string.
1101
1098
1102 >>> from . import pycompat
1099 >>> from . import pycompat
1103 >>> def bprint(s):
1100 >>> def bprint(s):
1104 ... print(pycompat.sysstr(s))
1101 ... print(pycompat.sysstr(s))
1105 >>> bprint(_globre(br'?'))
1102 >>> bprint(_globre(br'?'))
1106 .
1103 .
1107 >>> bprint(_globre(br'*'))
1104 >>> bprint(_globre(br'*'))
1108 [^/]*
1105 [^/]*
1109 >>> bprint(_globre(br'**'))
1106 >>> bprint(_globre(br'**'))
1110 .*
1107 .*
1111 >>> bprint(_globre(br'**/a'))
1108 >>> bprint(_globre(br'**/a'))
1112 (?:.*/)?a
1109 (?:.*/)?a
1113 >>> bprint(_globre(br'a/**/b'))
1110 >>> bprint(_globre(br'a/**/b'))
1114 a/(?:.*/)?b
1111 a/(?:.*/)?b
1115 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
1112 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
1116 [a*?!^][\^b][^c]
1113 [a*?!^][\^b][^c]
1117 >>> bprint(_globre(br'{a,b}'))
1114 >>> bprint(_globre(br'{a,b}'))
1118 (?:a|b)
1115 (?:a|b)
1119 >>> bprint(_globre(br'.\*\?'))
1116 >>> bprint(_globre(br'.\*\?'))
1120 \.\*\?
1117 \.\*\?
1121 '''
1118 '''
1122 i, n = 0, len(pat)
1119 i, n = 0, len(pat)
1123 res = ''
1120 res = ''
1124 group = 0
1121 group = 0
1125 escape = util.stringutil.regexbytesescapemap.get
1122 escape = util.stringutil.regexbytesescapemap.get
1126 def peek():
1123 def peek():
1127 return i < n and pat[i:i + 1]
1124 return i < n and pat[i:i + 1]
1128 while i < n:
1125 while i < n:
1129 c = pat[i:i + 1]
1126 c = pat[i:i + 1]
1130 i += 1
1127 i += 1
1131 if c not in '*?[{},\\':
1128 if c not in '*?[{},\\':
1132 res += escape(c, c)
1129 res += escape(c, c)
1133 elif c == '*':
1130 elif c == '*':
1134 if peek() == '*':
1131 if peek() == '*':
1135 i += 1
1132 i += 1
1136 if peek() == '/':
1133 if peek() == '/':
1137 i += 1
1134 i += 1
1138 res += '(?:.*/)?'
1135 res += '(?:.*/)?'
1139 else:
1136 else:
1140 res += '.*'
1137 res += '.*'
1141 else:
1138 else:
1142 res += '[^/]*'
1139 res += '[^/]*'
1143 elif c == '?':
1140 elif c == '?':
1144 res += '.'
1141 res += '.'
1145 elif c == '[':
1142 elif c == '[':
1146 j = i
1143 j = i
1147 if j < n and pat[j:j + 1] in '!]':
1144 if j < n and pat[j:j + 1] in '!]':
1148 j += 1
1145 j += 1
1149 while j < n and pat[j:j + 1] != ']':
1146 while j < n and pat[j:j + 1] != ']':
1150 j += 1
1147 j += 1
1151 if j >= n:
1148 if j >= n:
1152 res += '\\['
1149 res += '\\['
1153 else:
1150 else:
1154 stuff = pat[i:j].replace('\\','\\\\')
1151 stuff = pat[i:j].replace('\\','\\\\')
1155 i = j + 1
1152 i = j + 1
1156 if stuff[0:1] == '!':
1153 if stuff[0:1] == '!':
1157 stuff = '^' + stuff[1:]
1154 stuff = '^' + stuff[1:]
1158 elif stuff[0:1] == '^':
1155 elif stuff[0:1] == '^':
1159 stuff = '\\' + stuff
1156 stuff = '\\' + stuff
1160 res = '%s[%s]' % (res, stuff)
1157 res = '%s[%s]' % (res, stuff)
1161 elif c == '{':
1158 elif c == '{':
1162 group += 1
1159 group += 1
1163 res += '(?:'
1160 res += '(?:'
1164 elif c == '}' and group:
1161 elif c == '}' and group:
1165 res += ')'
1162 res += ')'
1166 group -= 1
1163 group -= 1
1167 elif c == ',' and group:
1164 elif c == ',' and group:
1168 res += '|'
1165 res += '|'
1169 elif c == '\\':
1166 elif c == '\\':
1170 p = peek()
1167 p = peek()
1171 if p:
1168 if p:
1172 i += 1
1169 i += 1
1173 res += escape(p, p)
1170 res += escape(p, p)
1174 else:
1171 else:
1175 res += escape(c, c)
1172 res += escape(c, c)
1176 else:
1173 else:
1177 res += escape(c, c)
1174 res += escape(c, c)
1178 return res
1175 return res
1179
1176
1180 def _regex(kind, pat, globsuffix):
1177 def _regex(kind, pat, globsuffix):
1181 '''Convert a (normalized) pattern of any kind into a regular expression.
1178 '''Convert a (normalized) pattern of any kind into a regular expression.
1182 globsuffix is appended to the regexp of globs.'''
1179 globsuffix is appended to the regexp of globs.'''
1183 if not pat:
1180 if not pat:
1184 return ''
1181 return ''
1185 if kind == 're':
1182 if kind == 're':
1186 return pat
1183 return pat
1187 if kind in ('path', 'relpath'):
1184 if kind in ('path', 'relpath'):
1188 if pat == '.':
1185 if pat == '.':
1189 return ''
1186 return ''
1190 return util.stringutil.reescape(pat) + '(?:/|$)'
1187 return util.stringutil.reescape(pat) + '(?:/|$)'
1191 if kind == 'rootfilesin':
1188 if kind == 'rootfilesin':
1192 if pat == '.':
1189 if pat == '.':
1193 escaped = ''
1190 escaped = ''
1194 else:
1191 else:
1195 # Pattern is a directory name.
1192 # Pattern is a directory name.
1196 escaped = util.stringutil.reescape(pat) + '/'
1193 escaped = util.stringutil.reescape(pat) + '/'
1197 # Anything after the pattern must be a non-directory.
1194 # Anything after the pattern must be a non-directory.
1198 return escaped + '[^/]+$'
1195 return escaped + '[^/]+$'
1199 if kind == 'relglob':
1196 if kind == 'relglob':
1200 return '(?:|.*/)' + _globre(pat) + globsuffix
1197 return '(?:|.*/)' + _globre(pat) + globsuffix
1201 if kind == 'relre':
1198 if kind == 'relre':
1202 if pat.startswith('^'):
1199 if pat.startswith('^'):
1203 return pat
1200 return pat
1204 return '.*' + pat
1201 return '.*' + pat
1205 if kind in ('glob', 'rootglob'):
1202 if kind in ('glob', 'rootglob'):
1206 return _globre(pat) + globsuffix
1203 return _globre(pat) + globsuffix
1207 raise error.ProgrammingError('not a regex pattern: %s:%s' % (kind, pat))
1204 raise error.ProgrammingError('not a regex pattern: %s:%s' % (kind, pat))
1208
1205
1209 def _buildmatch(kindpats, globsuffix, root):
1206 def _buildmatch(kindpats, globsuffix, root):
1210 '''Return regexp string and a matcher function for kindpats.
1207 '''Return regexp string and a matcher function for kindpats.
1211 globsuffix is appended to the regexp of globs.'''
1208 globsuffix is appended to the regexp of globs.'''
1212 matchfuncs = []
1209 matchfuncs = []
1213
1210
1214 subincludes, kindpats = _expandsubinclude(kindpats, root)
1211 subincludes, kindpats = _expandsubinclude(kindpats, root)
1215 if subincludes:
1212 if subincludes:
1216 submatchers = {}
1213 submatchers = {}
1217 def matchsubinclude(f):
1214 def matchsubinclude(f):
1218 for prefix, matcherargs in subincludes:
1215 for prefix, matcherargs in subincludes:
1219 if f.startswith(prefix):
1216 if f.startswith(prefix):
1220 mf = submatchers.get(prefix)
1217 mf = submatchers.get(prefix)
1221 if mf is None:
1218 if mf is None:
1222 mf = match(*matcherargs)
1219 mf = match(*matcherargs)
1223 submatchers[prefix] = mf
1220 submatchers[prefix] = mf
1224
1221
1225 if mf(f[len(prefix):]):
1222 if mf(f[len(prefix):]):
1226 return True
1223 return True
1227 return False
1224 return False
1228 matchfuncs.append(matchsubinclude)
1225 matchfuncs.append(matchsubinclude)
1229
1226
1230 regex = ''
1227 regex = ''
1231 if kindpats:
1228 if kindpats:
1232 if all(k == 'rootfilesin' for k, p, s in kindpats):
1229 if all(k == 'rootfilesin' for k, p, s in kindpats):
1233 dirs = {p for k, p, s in kindpats}
1230 dirs = {p for k, p, s in kindpats}
1234 def mf(f):
1231 def mf(f):
1235 i = f.rfind('/')
1232 i = f.rfind('/')
1236 if i >= 0:
1233 if i >= 0:
1237 dir = f[:i]
1234 dir = f[:i]
1238 else:
1235 else:
1239 dir = '.'
1236 dir = '.'
1240 return dir in dirs
1237 return dir in dirs
1241 regex = b'rootfilesin: %s' % stringutil.pprint(list(sorted(dirs)))
1238 regex = b'rootfilesin: %s' % stringutil.pprint(list(sorted(dirs)))
1242 matchfuncs.append(mf)
1239 matchfuncs.append(mf)
1243 else:
1240 else:
1244 regex, mf = _buildregexmatch(kindpats, globsuffix)
1241 regex, mf = _buildregexmatch(kindpats, globsuffix)
1245 matchfuncs.append(mf)
1242 matchfuncs.append(mf)
1246
1243
1247 if len(matchfuncs) == 1:
1244 if len(matchfuncs) == 1:
1248 return regex, matchfuncs[0]
1245 return regex, matchfuncs[0]
1249 else:
1246 else:
1250 return regex, lambda f: any(mf(f) for mf in matchfuncs)
1247 return regex, lambda f: any(mf(f) for mf in matchfuncs)
1251
1248
1252 MAX_RE_SIZE = 20000
1249 MAX_RE_SIZE = 20000
1253
1250
1254 def _joinregexes(regexps):
1251 def _joinregexes(regexps):
1255 """gather multiple regular expressions into a single one"""
1252 """gather multiple regular expressions into a single one"""
1256 return '|'.join(regexps)
1253 return '|'.join(regexps)
1257
1254
1258 def _buildregexmatch(kindpats, globsuffix):
1255 def _buildregexmatch(kindpats, globsuffix):
1259 """Build a match function from a list of kinds and kindpats,
1256 """Build a match function from a list of kinds and kindpats,
1260 return regexp string and a matcher function.
1257 return regexp string and a matcher function.
1261
1258
1262 Test too large input
1259 Test too large input
1263 >>> _buildregexmatch([
1260 >>> _buildregexmatch([
1264 ... (b'relglob', b'?' * MAX_RE_SIZE, b'')
1261 ... (b'relglob', b'?' * MAX_RE_SIZE, b'')
1265 ... ], b'$')
1262 ... ], b'$')
1266 Traceback (most recent call last):
1263 Traceback (most recent call last):
1267 ...
1264 ...
1268 Abort: matcher pattern is too long (20009 bytes)
1265 Abort: matcher pattern is too long (20009 bytes)
1269 """
1266 """
1270 try:
1267 try:
1271 allgroups = []
1268 allgroups = []
1272 regexps = [_regex(k, p, globsuffix) for (k, p, s) in kindpats]
1269 regexps = [_regex(k, p, globsuffix) for (k, p, s) in kindpats]
1273 fullregexp = _joinregexes(regexps)
1270 fullregexp = _joinregexes(regexps)
1274
1271
1275 startidx = 0
1272 startidx = 0
1276 groupsize = 0
1273 groupsize = 0
1277 for idx, r in enumerate(regexps):
1274 for idx, r in enumerate(regexps):
1278 piecesize = len(r)
1275 piecesize = len(r)
1279 if piecesize > MAX_RE_SIZE:
1276 if piecesize > MAX_RE_SIZE:
1280 msg = _("matcher pattern is too long (%d bytes)") % piecesize
1277 msg = _("matcher pattern is too long (%d bytes)") % piecesize
1281 raise error.Abort(msg)
1278 raise error.Abort(msg)
1282 elif (groupsize + piecesize) > MAX_RE_SIZE:
1279 elif (groupsize + piecesize) > MAX_RE_SIZE:
1283 group = regexps[startidx:idx]
1280 group = regexps[startidx:idx]
1284 allgroups.append(_joinregexes(group))
1281 allgroups.append(_joinregexes(group))
1285 startidx = idx
1282 startidx = idx
1286 groupsize = 0
1283 groupsize = 0
1287 groupsize += piecesize + 1
1284 groupsize += piecesize + 1
1288
1285
1289 if startidx == 0:
1286 if startidx == 0:
1290 matcher = _rematcher(fullregexp)
1287 matcher = _rematcher(fullregexp)
1291 func = lambda s: bool(matcher(s))
1288 func = lambda s: bool(matcher(s))
1292 else:
1289 else:
1293 group = regexps[startidx:]
1290 group = regexps[startidx:]
1294 allgroups.append(_joinregexes(group))
1291 allgroups.append(_joinregexes(group))
1295 allmatchers = [_rematcher(g) for g in allgroups]
1292 allmatchers = [_rematcher(g) for g in allgroups]
1296 func = lambda s: any(m(s) for m in allmatchers)
1293 func = lambda s: any(m(s) for m in allmatchers)
1297 return fullregexp, func
1294 return fullregexp, func
1298 except re.error:
1295 except re.error:
1299 for k, p, s in kindpats:
1296 for k, p, s in kindpats:
1300 try:
1297 try:
1301 _rematcher(_regex(k, p, globsuffix))
1298 _rematcher(_regex(k, p, globsuffix))
1302 except re.error:
1299 except re.error:
1303 if s:
1300 if s:
1304 raise error.Abort(_("%s: invalid pattern (%s): %s") %
1301 raise error.Abort(_("%s: invalid pattern (%s): %s") %
1305 (s, k, p))
1302 (s, k, p))
1306 else:
1303 else:
1307 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
1304 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
1308 raise error.Abort(_("invalid pattern"))
1305 raise error.Abort(_("invalid pattern"))
1309
1306
1310 def _patternrootsanddirs(kindpats):
1307 def _patternrootsanddirs(kindpats):
1311 '''Returns roots and directories corresponding to each pattern.
1308 '''Returns roots and directories corresponding to each pattern.
1312
1309
1313 This calculates the roots and directories exactly matching the patterns and
1310 This calculates the roots and directories exactly matching the patterns and
1314 returns a tuple of (roots, dirs) for each. It does not return other
1311 returns a tuple of (roots, dirs) for each. It does not return other
1315 directories which may also need to be considered, like the parent
1312 directories which may also need to be considered, like the parent
1316 directories.
1313 directories.
1317 '''
1314 '''
1318 r = []
1315 r = []
1319 d = []
1316 d = []
1320 for kind, pat, source in kindpats:
1317 for kind, pat, source in kindpats:
1321 if kind in ('glob', 'rootglob'): # find the non-glob prefix
1318 if kind in ('glob', 'rootglob'): # find the non-glob prefix
1322 root = []
1319 root = []
1323 for p in pat.split('/'):
1320 for p in pat.split('/'):
1324 if '[' in p or '{' in p or '*' in p or '?' in p:
1321 if '[' in p or '{' in p or '*' in p or '?' in p:
1325 break
1322 break
1326 root.append(p)
1323 root.append(p)
1327 r.append('/'.join(root) or '.')
1324 r.append('/'.join(root) or '.')
1328 elif kind in ('relpath', 'path'):
1325 elif kind in ('relpath', 'path'):
1329 r.append(pat or '.')
1326 r.append(pat or '.')
1330 elif kind in ('rootfilesin',):
1327 elif kind in ('rootfilesin',):
1331 d.append(pat or '.')
1328 d.append(pat or '.')
1332 else: # relglob, re, relre
1329 else: # relglob, re, relre
1333 r.append('.')
1330 r.append('.')
1334 return r, d
1331 return r, d
1335
1332
1336 def _roots(kindpats):
1333 def _roots(kindpats):
1337 '''Returns root directories to match recursively from the given patterns.'''
1334 '''Returns root directories to match recursively from the given patterns.'''
1338 roots, dirs = _patternrootsanddirs(kindpats)
1335 roots, dirs = _patternrootsanddirs(kindpats)
1339 return roots
1336 return roots
1340
1337
1341 def _rootsdirsandparents(kindpats):
1338 def _rootsdirsandparents(kindpats):
1342 '''Returns roots and exact directories from patterns.
1339 '''Returns roots and exact directories from patterns.
1343
1340
1344 `roots` are directories to match recursively, `dirs` should
1341 `roots` are directories to match recursively, `dirs` should
1345 be matched non-recursively, and `parents` are the implicitly required
1342 be matched non-recursively, and `parents` are the implicitly required
1346 directories to walk to items in either roots or dirs.
1343 directories to walk to items in either roots or dirs.
1347
1344
1348 Returns a tuple of (roots, dirs, parents).
1345 Returns a tuple of (roots, dirs, parents).
1349
1346
1350 >>> _rootsdirsandparents(
1347 >>> _rootsdirsandparents(
1351 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
1348 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
1352 ... (b'glob', b'g*', b'')])
1349 ... (b'glob', b'g*', b'')])
1353 (['g/h', 'g/h', '.'], [], ['g', '.'])
1350 (['g/h', 'g/h', '.'], [], ['g', '.'])
1354 >>> _rootsdirsandparents(
1351 >>> _rootsdirsandparents(
1355 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
1352 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
1356 ([], ['g/h', '.'], ['g', '.'])
1353 ([], ['g/h', '.'], ['g', '.'])
1357 >>> _rootsdirsandparents(
1354 >>> _rootsdirsandparents(
1358 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
1355 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
1359 ... (b'path', b'', b'')])
1356 ... (b'path', b'', b'')])
1360 (['r', 'p/p', '.'], [], ['p', '.'])
1357 (['r', 'p/p', '.'], [], ['p', '.'])
1361 >>> _rootsdirsandparents(
1358 >>> _rootsdirsandparents(
1362 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
1359 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
1363 ... (b'relre', b'rr', b'')])
1360 ... (b'relre', b'rr', b'')])
1364 (['.', '.', '.'], [], ['.'])
1361 (['.', '.', '.'], [], ['.'])
1365 '''
1362 '''
1366 r, d = _patternrootsanddirs(kindpats)
1363 r, d = _patternrootsanddirs(kindpats)
1367
1364
1368 p = []
1365 p = []
1369 # Append the parents as non-recursive/exact directories, since they must be
1366 # Append the parents as non-recursive/exact directories, since they must be
1370 # scanned to get to either the roots or the other exact directories.
1367 # scanned to get to either the roots or the other exact directories.
1371 p.extend(util.dirs(d))
1368 p.extend(util.dirs(d))
1372 p.extend(util.dirs(r))
1369 p.extend(util.dirs(r))
1373 # util.dirs() does not include the root directory, so add it manually
1370 # util.dirs() does not include the root directory, so add it manually
1374 p.append('.')
1371 p.append('.')
1375
1372
1376 # FIXME: all uses of this function convert these to sets, do so before
1373 # FIXME: all uses of this function convert these to sets, do so before
1377 # returning.
1374 # returning.
1378 # FIXME: all uses of this function do not need anything in 'roots' and
1375 # FIXME: all uses of this function do not need anything in 'roots' and
1379 # 'dirs' to also be in 'parents', consider removing them before returning.
1376 # 'dirs' to also be in 'parents', consider removing them before returning.
1380 return r, d, p
1377 return r, d, p
1381
1378
1382 def _explicitfiles(kindpats):
1379 def _explicitfiles(kindpats):
1383 '''Returns the potential explicit filenames from the patterns.
1380 '''Returns the potential explicit filenames from the patterns.
1384
1381
1385 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
1382 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
1386 ['foo/bar']
1383 ['foo/bar']
1387 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
1384 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
1388 []
1385 []
1389 '''
1386 '''
1390 # Keep only the pattern kinds where one can specify filenames (vs only
1387 # Keep only the pattern kinds where one can specify filenames (vs only
1391 # directory names).
1388 # directory names).
1392 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
1389 filable = [kp for kp in kindpats if kp[0] not in ('rootfilesin',)]
1393 return _roots(filable)
1390 return _roots(filable)
1394
1391
1395 def _prefix(kindpats):
1392 def _prefix(kindpats):
1396 '''Whether all the patterns match a prefix (i.e. recursively)'''
1393 '''Whether all the patterns match a prefix (i.e. recursively)'''
1397 for kind, pat, source in kindpats:
1394 for kind, pat, source in kindpats:
1398 if kind not in ('path', 'relpath'):
1395 if kind not in ('path', 'relpath'):
1399 return False
1396 return False
1400 return True
1397 return True
1401
1398
1402 _commentre = None
1399 _commentre = None
1403
1400
1404 def readpatternfile(filepath, warn, sourceinfo=False):
1401 def readpatternfile(filepath, warn, sourceinfo=False):
1405 '''parse a pattern file, returning a list of
1402 '''parse a pattern file, returning a list of
1406 patterns. These patterns should be given to compile()
1403 patterns. These patterns should be given to compile()
1407 to be validated and converted into a match function.
1404 to be validated and converted into a match function.
1408
1405
1409 trailing white space is dropped.
1406 trailing white space is dropped.
1410 the escape character is backslash.
1407 the escape character is backslash.
1411 comments start with #.
1408 comments start with #.
1412 empty lines are skipped.
1409 empty lines are skipped.
1413
1410
1414 lines can be of the following formats:
1411 lines can be of the following formats:
1415
1412
1416 syntax: regexp # defaults following lines to non-rooted regexps
1413 syntax: regexp # defaults following lines to non-rooted regexps
1417 syntax: glob # defaults following lines to non-rooted globs
1414 syntax: glob # defaults following lines to non-rooted globs
1418 re:pattern # non-rooted regular expression
1415 re:pattern # non-rooted regular expression
1419 glob:pattern # non-rooted glob
1416 glob:pattern # non-rooted glob
1420 rootglob:pat # rooted glob (same root as ^ in regexps)
1417 rootglob:pat # rooted glob (same root as ^ in regexps)
1421 pattern # pattern of the current default type
1418 pattern # pattern of the current default type
1422
1419
1423 if sourceinfo is set, returns a list of tuples:
1420 if sourceinfo is set, returns a list of tuples:
1424 (pattern, lineno, originalline). This is useful to debug ignore patterns.
1421 (pattern, lineno, originalline). This is useful to debug ignore patterns.
1425 '''
1422 '''
1426
1423
1427 syntaxes = {
1424 syntaxes = {
1428 're': 'relre:',
1425 're': 'relre:',
1429 'regexp': 'relre:',
1426 'regexp': 'relre:',
1430 'glob': 'relglob:',
1427 'glob': 'relglob:',
1431 'rootglob': 'rootglob:',
1428 'rootglob': 'rootglob:',
1432 'include': 'include',
1429 'include': 'include',
1433 'subinclude': 'subinclude',
1430 'subinclude': 'subinclude',
1434 }
1431 }
1435 syntax = 'relre:'
1432 syntax = 'relre:'
1436 patterns = []
1433 patterns = []
1437
1434
1438 fp = open(filepath, 'rb')
1435 fp = open(filepath, 'rb')
1439 for lineno, line in enumerate(util.iterfile(fp), start=1):
1436 for lineno, line in enumerate(util.iterfile(fp), start=1):
1440 if "#" in line:
1437 if "#" in line:
1441 global _commentre
1438 global _commentre
1442 if not _commentre:
1439 if not _commentre:
1443 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1440 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1444 # remove comments prefixed by an even number of escapes
1441 # remove comments prefixed by an even number of escapes
1445 m = _commentre.search(line)
1442 m = _commentre.search(line)
1446 if m:
1443 if m:
1447 line = line[:m.end(1)]
1444 line = line[:m.end(1)]
1448 # fixup properly escaped comments that survived the above
1445 # fixup properly escaped comments that survived the above
1449 line = line.replace("\\#", "#")
1446 line = line.replace("\\#", "#")
1450 line = line.rstrip()
1447 line = line.rstrip()
1451 if not line:
1448 if not line:
1452 continue
1449 continue
1453
1450
1454 if line.startswith('syntax:'):
1451 if line.startswith('syntax:'):
1455 s = line[7:].strip()
1452 s = line[7:].strip()
1456 try:
1453 try:
1457 syntax = syntaxes[s]
1454 syntax = syntaxes[s]
1458 except KeyError:
1455 except KeyError:
1459 if warn:
1456 if warn:
1460 warn(_("%s: ignoring invalid syntax '%s'\n") %
1457 warn(_("%s: ignoring invalid syntax '%s'\n") %
1461 (filepath, s))
1458 (filepath, s))
1462 continue
1459 continue
1463
1460
1464 linesyntax = syntax
1461 linesyntax = syntax
1465 for s, rels in syntaxes.iteritems():
1462 for s, rels in syntaxes.iteritems():
1466 if line.startswith(rels):
1463 if line.startswith(rels):
1467 linesyntax = rels
1464 linesyntax = rels
1468 line = line[len(rels):]
1465 line = line[len(rels):]
1469 break
1466 break
1470 elif line.startswith(s+':'):
1467 elif line.startswith(s+':'):
1471 linesyntax = rels
1468 linesyntax = rels
1472 line = line[len(s) + 1:]
1469 line = line[len(s) + 1:]
1473 break
1470 break
1474 if sourceinfo:
1471 if sourceinfo:
1475 patterns.append((linesyntax + line, lineno, line))
1472 patterns.append((linesyntax + line, lineno, line))
1476 else:
1473 else:
1477 patterns.append(linesyntax + line)
1474 patterns.append(linesyntax + line)
1478 fp.close()
1475 fp.close()
1479 return patterns
1476 return patterns
General Comments 0
You need to be logged in to leave comments. Login now