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