##// END OF EJS Templates
match: fix the "visitdir" method on "rootfilesin" matchers...
Arseniy Alekseyev -
r52462:b32c3146 stable
parent child Browse files
Show More
@@ -1,1703 +1,1706 b''
1 # match.py - filename matching
1 # match.py - filename matching
2 #
2 #
3 # Copyright 2008, 2009 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2008, 2009 Olivia Mackall <olivia@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
8
9 import bisect
9 import bisect
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('dirstate')
27 rustmod = policy.importrust('dirstate')
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'filepath',
33 b'filepath',
34 b'relglob',
34 b'relglob',
35 b'relpath',
35 b'relpath',
36 b'relre',
36 b'relre',
37 b'rootglob',
37 b'rootglob',
38 b'listfile',
38 b'listfile',
39 b'listfile0',
39 b'listfile0',
40 b'set',
40 b'set',
41 b'include',
41 b'include',
42 b'subinclude',
42 b'subinclude',
43 b'rootfilesin',
43 b'rootfilesin',
44 )
44 )
45 cwdrelativepatternkinds = (b'relpath', b'glob')
45 cwdrelativepatternkinds = (b'relpath', b'glob')
46
46
47 propertycache = util.propertycache
47 propertycache = util.propertycache
48
48
49
49
50 def _rematcher(regex):
50 def _rematcher(regex):
51 """compile the regexp with the best available regexp engine and return a
51 """compile the regexp with the best available regexp engine and return a
52 matcher function"""
52 matcher function"""
53 m = util.re.compile(regex)
53 m = util.re.compile(regex)
54 try:
54 try:
55 # slightly faster, provided by facebook's re2 bindings
55 # slightly faster, provided by facebook's re2 bindings
56 return m.test_match
56 return m.test_match
57 except AttributeError:
57 except AttributeError:
58 return m.match
58 return m.match
59
59
60
60
61 def _expandsets(cwd, kindpats, ctx=None, listsubrepos=False, badfn=None):
61 def _expandsets(cwd, kindpats, ctx=None, listsubrepos=False, badfn=None):
62 '''Returns the kindpats list with the 'set' patterns expanded to matchers'''
62 '''Returns the kindpats list with the 'set' patterns expanded to matchers'''
63 matchers = []
63 matchers = []
64 other = []
64 other = []
65
65
66 for kind, pat, source in kindpats:
66 for kind, pat, source in kindpats:
67 if kind == b'set':
67 if kind == b'set':
68 if ctx is None:
68 if ctx is None:
69 raise error.ProgrammingError(
69 raise error.ProgrammingError(
70 b"fileset expression with no context"
70 b"fileset expression with no context"
71 )
71 )
72 matchers.append(ctx.matchfileset(cwd, pat, badfn=badfn))
72 matchers.append(ctx.matchfileset(cwd, pat, badfn=badfn))
73
73
74 if listsubrepos:
74 if listsubrepos:
75 for subpath in ctx.substate:
75 for subpath in ctx.substate:
76 sm = ctx.sub(subpath).matchfileset(cwd, pat, badfn=badfn)
76 sm = ctx.sub(subpath).matchfileset(cwd, pat, badfn=badfn)
77 pm = prefixdirmatcher(subpath, sm, badfn=badfn)
77 pm = prefixdirmatcher(subpath, sm, badfn=badfn)
78 matchers.append(pm)
78 matchers.append(pm)
79
79
80 continue
80 continue
81 other.append((kind, pat, source))
81 other.append((kind, pat, source))
82 return matchers, other
82 return matchers, other
83
83
84
84
85 def _expandsubinclude(kindpats, root):
85 def _expandsubinclude(kindpats, root):
86 """Returns the list of subinclude matcher args and the kindpats without the
86 """Returns the list of subinclude matcher args and the kindpats without the
87 subincludes in it."""
87 subincludes in it."""
88 relmatchers = []
88 relmatchers = []
89 other = []
89 other = []
90
90
91 for kind, pat, source in kindpats:
91 for kind, pat, source in kindpats:
92 if kind == b'subinclude':
92 if kind == b'subinclude':
93 sourceroot = pathutil.dirname(util.normpath(source))
93 sourceroot = pathutil.dirname(util.normpath(source))
94 pat = util.pconvert(pat)
94 pat = util.pconvert(pat)
95 path = pathutil.join(sourceroot, pat)
95 path = pathutil.join(sourceroot, pat)
96
96
97 newroot = pathutil.dirname(path)
97 newroot = pathutil.dirname(path)
98 matcherargs = (newroot, b'', [], [b'include:%s' % path])
98 matcherargs = (newroot, b'', [], [b'include:%s' % path])
99
99
100 prefix = pathutil.canonpath(root, root, newroot)
100 prefix = pathutil.canonpath(root, root, newroot)
101 if prefix:
101 if prefix:
102 prefix += b'/'
102 prefix += b'/'
103 relmatchers.append((prefix, matcherargs))
103 relmatchers.append((prefix, matcherargs))
104 else:
104 else:
105 other.append((kind, pat, source))
105 other.append((kind, pat, source))
106
106
107 return relmatchers, other
107 return relmatchers, other
108
108
109
109
110 def _kindpatsalwaysmatch(kindpats):
110 def _kindpatsalwaysmatch(kindpats):
111 """Checks whether the kindspats match everything, as e.g.
111 """Checks whether the kindspats match everything, as e.g.
112 'relpath:.' does.
112 'relpath:.' does.
113 """
113 """
114 for kind, pat, source in kindpats:
114 for kind, pat, source in kindpats:
115 if pat != b'' or kind not in [b'relpath', b'glob']:
115 if pat != b'' or kind not in [b'relpath', b'glob']:
116 return False
116 return False
117 return True
117 return True
118
118
119
119
120 def _buildkindpatsmatcher(
120 def _buildkindpatsmatcher(
121 matchercls,
121 matchercls,
122 root,
122 root,
123 cwd,
123 cwd,
124 kindpats,
124 kindpats,
125 ctx=None,
125 ctx=None,
126 listsubrepos=False,
126 listsubrepos=False,
127 badfn=None,
127 badfn=None,
128 ):
128 ):
129 matchers = []
129 matchers = []
130 fms, kindpats = _expandsets(
130 fms, kindpats = _expandsets(
131 cwd,
131 cwd,
132 kindpats,
132 kindpats,
133 ctx=ctx,
133 ctx=ctx,
134 listsubrepos=listsubrepos,
134 listsubrepos=listsubrepos,
135 badfn=badfn,
135 badfn=badfn,
136 )
136 )
137 if kindpats:
137 if kindpats:
138 m = matchercls(root, kindpats, badfn=badfn)
138 m = matchercls(root, kindpats, badfn=badfn)
139 matchers.append(m)
139 matchers.append(m)
140 if fms:
140 if fms:
141 matchers.extend(fms)
141 matchers.extend(fms)
142 if not matchers:
142 if not matchers:
143 return nevermatcher(badfn=badfn)
143 return nevermatcher(badfn=badfn)
144 if len(matchers) == 1:
144 if len(matchers) == 1:
145 return matchers[0]
145 return matchers[0]
146 return unionmatcher(matchers)
146 return unionmatcher(matchers)
147
147
148
148
149 def match(
149 def match(
150 root,
150 root,
151 cwd,
151 cwd,
152 patterns=None,
152 patterns=None,
153 include=None,
153 include=None,
154 exclude=None,
154 exclude=None,
155 default=b'glob',
155 default=b'glob',
156 auditor=None,
156 auditor=None,
157 ctx=None,
157 ctx=None,
158 listsubrepos=False,
158 listsubrepos=False,
159 warn=None,
159 warn=None,
160 badfn=None,
160 badfn=None,
161 icasefs=False,
161 icasefs=False,
162 ):
162 ):
163 r"""build an object to match a set of file patterns
163 r"""build an object to match a set of file patterns
164
164
165 arguments:
165 arguments:
166 root - the canonical root of the tree you're matching against
166 root - the canonical root of the tree you're matching against
167 cwd - the current working directory, if relevant
167 cwd - the current working directory, if relevant
168 patterns - patterns to find
168 patterns - patterns to find
169 include - patterns to include (unless they are excluded)
169 include - patterns to include (unless they are excluded)
170 exclude - patterns to exclude (even if they are included)
170 exclude - patterns to exclude (even if they are included)
171 default - if a pattern in patterns has no explicit type, assume this one
171 default - if a pattern in patterns has no explicit type, assume this one
172 auditor - optional path auditor
172 auditor - optional path auditor
173 ctx - optional changecontext
173 ctx - optional changecontext
174 listsubrepos - if True, recurse into subrepositories
174 listsubrepos - if True, recurse into subrepositories
175 warn - optional function used for printing warnings
175 warn - optional function used for printing warnings
176 badfn - optional bad() callback for this matcher instead of the default
176 badfn - optional bad() callback for this matcher instead of the default
177 icasefs - make a matcher for wdir on case insensitive filesystems, which
177 icasefs - make a matcher for wdir on case insensitive filesystems, which
178 normalizes the given patterns to the case in the filesystem
178 normalizes the given patterns to the case in the filesystem
179
179
180 a pattern is one of:
180 a pattern is one of:
181 'glob:<glob>' - a glob relative to cwd
181 'glob:<glob>' - a glob relative to cwd
182 're:<regexp>' - a regular expression
182 're:<regexp>' - a regular expression
183 'path:<path>' - a path relative to repository root, which is matched
183 'path:<path>' - a path relative to repository root, which is matched
184 recursively
184 recursively
185 'filepath:<path>' - an exact path to a single file, relative to the
185 'filepath:<path>' - an exact path to a single file, relative to the
186 repository root
186 repository root
187 'rootfilesin:<path>' - a path relative to repository root, which is
187 'rootfilesin:<path>' - a path relative to repository root, which is
188 matched non-recursively (will not match subdirectories)
188 matched non-recursively (will not match subdirectories)
189 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
189 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
190 'relpath:<path>' - a path relative to cwd
190 'relpath:<path>' - a path relative to cwd
191 'relre:<regexp>' - a regexp that needn't match the start of a name
191 'relre:<regexp>' - a regexp that needn't match the start of a name
192 'set:<fileset>' - a fileset expression
192 'set:<fileset>' - a fileset expression
193 'include:<path>' - a file of patterns to read and include
193 'include:<path>' - a file of patterns to read and include
194 'subinclude:<path>' - a file of patterns to match against files under
194 'subinclude:<path>' - a file of patterns to match against files under
195 the same directory
195 the same directory
196 '<something>' - a pattern of the specified default type
196 '<something>' - a pattern of the specified default type
197
197
198 >>> def _match(root, *args, **kwargs):
198 >>> def _match(root, *args, **kwargs):
199 ... return match(util.localpath(root), *args, **kwargs)
199 ... return match(util.localpath(root), *args, **kwargs)
200
200
201 Usually a patternmatcher is returned:
201 Usually a patternmatcher is returned:
202 >>> _match(b'/foo', b'.', [br're:.*\.c$', b'path:foo/a', b'*.py'])
202 >>> _match(b'/foo', b'.', [br're:.*\.c$', b'path:foo/a', b'*.py'])
203 <patternmatcher patterns='[^/]*\\.py$|foo/a(?:/|$)|.*\\.c$'>
203 <patternmatcher patterns='[^/]*\\.py$|foo/a(?:/|$)|.*\\.c$'>
204
204
205 Combining 'patterns' with 'include' (resp. 'exclude') gives an
205 Combining 'patterns' with 'include' (resp. 'exclude') gives an
206 intersectionmatcher (resp. a differencematcher):
206 intersectionmatcher (resp. a differencematcher):
207 >>> type(_match(b'/foo', b'.', [br're:.*\.c$'], include=[b'path:lib']))
207 >>> type(_match(b'/foo', b'.', [br're:.*\.c$'], include=[b'path:lib']))
208 <class 'mercurial.match.intersectionmatcher'>
208 <class 'mercurial.match.intersectionmatcher'>
209 >>> type(_match(b'/foo', b'.', [br're:.*\.c$'], exclude=[b'path:build']))
209 >>> type(_match(b'/foo', b'.', [br're:.*\.c$'], exclude=[b'path:build']))
210 <class 'mercurial.match.differencematcher'>
210 <class 'mercurial.match.differencematcher'>
211
211
212 Notice that, if 'patterns' is empty, an alwaysmatcher is returned:
212 Notice that, if 'patterns' is empty, an alwaysmatcher is returned:
213 >>> _match(b'/foo', b'.', [])
213 >>> _match(b'/foo', b'.', [])
214 <alwaysmatcher>
214 <alwaysmatcher>
215
215
216 The 'default' argument determines which kind of pattern is assumed if a
216 The 'default' argument determines which kind of pattern is assumed if a
217 pattern has no prefix:
217 pattern has no prefix:
218 >>> _match(b'/foo', b'.', [br'.*\.c$'], default=b're')
218 >>> _match(b'/foo', b'.', [br'.*\.c$'], default=b're')
219 <patternmatcher patterns='.*\\.c$'>
219 <patternmatcher patterns='.*\\.c$'>
220 >>> _match(b'/foo', b'.', [b'main.py'], default=b'relpath')
220 >>> _match(b'/foo', b'.', [b'main.py'], default=b'relpath')
221 <patternmatcher patterns='main\\.py(?:/|$)'>
221 <patternmatcher patterns='main\\.py(?:/|$)'>
222 >>> _match(b'/foo', b'.', [b'main.py'], default=b're')
222 >>> _match(b'/foo', b'.', [b'main.py'], default=b're')
223 <patternmatcher patterns='main.py'>
223 <patternmatcher patterns='main.py'>
224
224
225 The primary use of matchers is to check whether a value (usually a file
225 The primary use of matchers is to check whether a value (usually a file
226 name) matches againset one of the patterns given at initialization. There
226 name) matches againset one of the patterns given at initialization. There
227 are two ways of doing this check.
227 are two ways of doing this check.
228
228
229 >>> m = _match(b'/foo', b'', [br're:.*\.c$', b'relpath:a'])
229 >>> m = _match(b'/foo', b'', [br're:.*\.c$', b'relpath:a'])
230
230
231 1. Calling the matcher with a file name returns True if any pattern
231 1. Calling the matcher with a file name returns True if any pattern
232 matches that file name:
232 matches that file name:
233 >>> m(b'a')
233 >>> m(b'a')
234 True
234 True
235 >>> m(b'main.c')
235 >>> m(b'main.c')
236 True
236 True
237 >>> m(b'test.py')
237 >>> m(b'test.py')
238 False
238 False
239
239
240 2. Using the exact() method only returns True if the file name matches one
240 2. Using the exact() method only returns True if the file name matches one
241 of the exact patterns (i.e. not re: or glob: patterns):
241 of the exact patterns (i.e. not re: or glob: patterns):
242 >>> m.exact(b'a')
242 >>> m.exact(b'a')
243 True
243 True
244 >>> m.exact(b'main.c')
244 >>> m.exact(b'main.c')
245 False
245 False
246 """
246 """
247 assert os.path.isabs(root)
247 assert os.path.isabs(root)
248 cwd = os.path.join(root, util.localpath(cwd))
248 cwd = os.path.join(root, util.localpath(cwd))
249 normalize = _donormalize
249 normalize = _donormalize
250 if icasefs:
250 if icasefs:
251 dirstate = ctx.repo().dirstate
251 dirstate = ctx.repo().dirstate
252 dsnormalize = dirstate.normalize
252 dsnormalize = dirstate.normalize
253
253
254 def normalize(patterns, default, root, cwd, auditor, warn):
254 def normalize(patterns, default, root, cwd, auditor, warn):
255 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
255 kp = _donormalize(patterns, default, root, cwd, auditor, warn)
256 kindpats = []
256 kindpats = []
257 for kind, pats, source in kp:
257 for kind, pats, source in kp:
258 if kind not in (b're', b'relre'): # regex can't be normalized
258 if kind not in (b're', b'relre'): # regex can't be normalized
259 p = pats
259 p = pats
260 pats = dsnormalize(pats)
260 pats = dsnormalize(pats)
261
261
262 # Preserve the original to handle a case only rename.
262 # Preserve the original to handle a case only rename.
263 if p != pats and p in dirstate:
263 if p != pats and p in dirstate:
264 kindpats.append((kind, p, source))
264 kindpats.append((kind, p, source))
265
265
266 kindpats.append((kind, pats, source))
266 kindpats.append((kind, pats, source))
267 return kindpats
267 return kindpats
268
268
269 if patterns:
269 if patterns:
270 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
270 kindpats = normalize(patterns, default, root, cwd, auditor, warn)
271 if _kindpatsalwaysmatch(kindpats):
271 if _kindpatsalwaysmatch(kindpats):
272 m = alwaysmatcher(badfn)
272 m = alwaysmatcher(badfn)
273 else:
273 else:
274 m = _buildkindpatsmatcher(
274 m = _buildkindpatsmatcher(
275 patternmatcher,
275 patternmatcher,
276 root,
276 root,
277 cwd,
277 cwd,
278 kindpats,
278 kindpats,
279 ctx=ctx,
279 ctx=ctx,
280 listsubrepos=listsubrepos,
280 listsubrepos=listsubrepos,
281 badfn=badfn,
281 badfn=badfn,
282 )
282 )
283 else:
283 else:
284 # It's a little strange that no patterns means to match everything.
284 # It's a little strange that no patterns means to match everything.
285 # Consider changing this to match nothing (probably using nevermatcher).
285 # Consider changing this to match nothing (probably using nevermatcher).
286 m = alwaysmatcher(badfn)
286 m = alwaysmatcher(badfn)
287
287
288 if include:
288 if include:
289 kindpats = normalize(include, b'glob', root, cwd, auditor, warn)
289 kindpats = normalize(include, b'glob', root, cwd, auditor, warn)
290 im = _buildkindpatsmatcher(
290 im = _buildkindpatsmatcher(
291 includematcher,
291 includematcher,
292 root,
292 root,
293 cwd,
293 cwd,
294 kindpats,
294 kindpats,
295 ctx=ctx,
295 ctx=ctx,
296 listsubrepos=listsubrepos,
296 listsubrepos=listsubrepos,
297 badfn=None,
297 badfn=None,
298 )
298 )
299 m = intersectmatchers(m, im)
299 m = intersectmatchers(m, im)
300 if exclude:
300 if exclude:
301 kindpats = normalize(exclude, b'glob', root, cwd, auditor, warn)
301 kindpats = normalize(exclude, b'glob', root, cwd, auditor, warn)
302 em = _buildkindpatsmatcher(
302 em = _buildkindpatsmatcher(
303 includematcher,
303 includematcher,
304 root,
304 root,
305 cwd,
305 cwd,
306 kindpats,
306 kindpats,
307 ctx=ctx,
307 ctx=ctx,
308 listsubrepos=listsubrepos,
308 listsubrepos=listsubrepos,
309 badfn=None,
309 badfn=None,
310 )
310 )
311 m = differencematcher(m, em)
311 m = differencematcher(m, em)
312 return m
312 return m
313
313
314
314
315 def exact(files, badfn=None):
315 def exact(files, badfn=None):
316 return exactmatcher(files, badfn=badfn)
316 return exactmatcher(files, badfn=badfn)
317
317
318
318
319 def always(badfn=None):
319 def always(badfn=None):
320 return alwaysmatcher(badfn)
320 return alwaysmatcher(badfn)
321
321
322
322
323 def never(badfn=None):
323 def never(badfn=None):
324 return nevermatcher(badfn)
324 return nevermatcher(badfn)
325
325
326
326
327 def badmatch(match, badfn):
327 def badmatch(match, badfn):
328 """Make a copy of the given matcher, replacing its bad method with the given
328 """Make a copy of the given matcher, replacing its bad method with the given
329 one.
329 one.
330 """
330 """
331 m = copy.copy(match)
331 m = copy.copy(match)
332 m.bad = badfn
332 m.bad = badfn
333 return m
333 return m
334
334
335
335
336 def _donormalize(patterns, default, root, cwd, auditor=None, warn=None):
336 def _donormalize(patterns, default, root, cwd, auditor=None, warn=None):
337 """Convert 'kind:pat' from the patterns list to tuples with kind and
337 """Convert 'kind:pat' from the patterns list to tuples with kind and
338 normalized and rooted patterns and with listfiles expanded."""
338 normalized and rooted patterns and with listfiles expanded."""
339 kindpats = []
339 kindpats = []
340 kinds_to_normalize = (
340 kinds_to_normalize = (
341 b'relglob',
341 b'relglob',
342 b'path',
342 b'path',
343 b'filepath',
343 b'filepath',
344 b'rootfilesin',
344 b'rootfilesin',
345 b'rootglob',
345 b'rootglob',
346 )
346 )
347
347
348 for kind, pat in [_patsplit(p, default) for p in patterns]:
348 for kind, pat in [_patsplit(p, default) for p in patterns]:
349 if kind in cwdrelativepatternkinds:
349 if kind in cwdrelativepatternkinds:
350 pat = pathutil.canonpath(root, cwd, pat, auditor=auditor)
350 pat = pathutil.canonpath(root, cwd, pat, auditor=auditor)
351 elif kind in kinds_to_normalize:
351 elif kind in kinds_to_normalize:
352 pat = util.normpath(pat)
352 pat = util.normpath(pat)
353 elif kind in (b'listfile', b'listfile0'):
353 elif kind in (b'listfile', b'listfile0'):
354 try:
354 try:
355 files = util.readfile(pat)
355 files = util.readfile(pat)
356 if kind == b'listfile0':
356 if kind == b'listfile0':
357 files = files.split(b'\0')
357 files = files.split(b'\0')
358 else:
358 else:
359 files = files.splitlines()
359 files = files.splitlines()
360 files = [f for f in files if f]
360 files = [f for f in files if f]
361 except EnvironmentError:
361 except EnvironmentError:
362 raise error.Abort(_(b"unable to read file list (%s)") % pat)
362 raise error.Abort(_(b"unable to read file list (%s)") % pat)
363 for k, p, source in _donormalize(
363 for k, p, source in _donormalize(
364 files, default, root, cwd, auditor, warn
364 files, default, root, cwd, auditor, warn
365 ):
365 ):
366 kindpats.append((k, p, pat))
366 kindpats.append((k, p, pat))
367 continue
367 continue
368 elif kind == b'include':
368 elif kind == b'include':
369 try:
369 try:
370 fullpath = os.path.join(root, util.localpath(pat))
370 fullpath = os.path.join(root, util.localpath(pat))
371 includepats = readpatternfile(fullpath, warn)
371 includepats = readpatternfile(fullpath, warn)
372 for k, p, source in _donormalize(
372 for k, p, source in _donormalize(
373 includepats, default, root, cwd, auditor, warn
373 includepats, default, root, cwd, auditor, warn
374 ):
374 ):
375 kindpats.append((k, p, source or pat))
375 kindpats.append((k, p, source or pat))
376 except error.Abort as inst:
376 except error.Abort as inst:
377 raise error.Abort(
377 raise error.Abort(
378 b'%s: %s'
378 b'%s: %s'
379 % (
379 % (
380 pat,
380 pat,
381 inst.message,
381 inst.message,
382 )
382 )
383 )
383 )
384 except IOError as inst:
384 except IOError as inst:
385 if warn:
385 if warn:
386 warn(
386 warn(
387 _(b"skipping unreadable pattern file '%s': %s\n")
387 _(b"skipping unreadable pattern file '%s': %s\n")
388 % (pat, stringutil.forcebytestr(inst.strerror))
388 % (pat, stringutil.forcebytestr(inst.strerror))
389 )
389 )
390 continue
390 continue
391 # else: re or relre - which cannot be normalized
391 # else: re or relre - which cannot be normalized
392 kindpats.append((kind, pat, b''))
392 kindpats.append((kind, pat, b''))
393 return kindpats
393 return kindpats
394
394
395
395
396 class basematcher:
396 class basematcher:
397 def __init__(self, badfn=None):
397 def __init__(self, badfn=None):
398 if badfn is not None:
398 if badfn is not None:
399 self.bad = badfn
399 self.bad = badfn
400
400
401 def __call__(self, fn):
401 def __call__(self, fn):
402 return self.matchfn(fn)
402 return self.matchfn(fn)
403
403
404 # Callbacks related to how the matcher is used by dirstate.walk.
404 # Callbacks related to how the matcher is used by dirstate.walk.
405 # Subscribers to these events must monkeypatch the matcher object.
405 # Subscribers to these events must monkeypatch the matcher object.
406 def bad(self, f, msg):
406 def bad(self, f, msg):
407 """Callback from dirstate.walk for each explicit file that can't be
407 """Callback from dirstate.walk for each explicit file that can't be
408 found/accessed, with an error message."""
408 found/accessed, with an error message."""
409
409
410 # If an traversedir is set, it will be called when a directory discovered
410 # If an traversedir is set, it will be called when a directory discovered
411 # by recursive traversal is visited.
411 # by recursive traversal is visited.
412 traversedir = None
412 traversedir = None
413
413
414 @propertycache
414 @propertycache
415 def _files(self):
415 def _files(self):
416 return []
416 return []
417
417
418 def files(self):
418 def files(self):
419 """Explicitly listed files or patterns or roots:
419 """Explicitly listed files or patterns or roots:
420 if no patterns or .always(): empty list,
420 if no patterns or .always(): empty list,
421 if exact: list exact files,
421 if exact: list exact files,
422 if not .anypats(): list all files and dirs,
422 if not .anypats(): list all files and dirs,
423 else: optimal roots"""
423 else: optimal roots"""
424 return self._files
424 return self._files
425
425
426 @propertycache
426 @propertycache
427 def _fileset(self):
427 def _fileset(self):
428 return set(self._files)
428 return set(self._files)
429
429
430 def exact(self, f):
430 def exact(self, f):
431 '''Returns True if f is in .files().'''
431 '''Returns True if f is in .files().'''
432 return f in self._fileset
432 return f in self._fileset
433
433
434 def matchfn(self, f):
434 def matchfn(self, f):
435 return False
435 return False
436
436
437 def visitdir(self, dir):
437 def visitdir(self, dir):
438 """Decides whether a directory should be visited based on whether it
438 """Decides whether a directory should be visited based on whether it
439 has potential matches in it or one of its subdirectories. This is
439 has potential matches in it or one of its subdirectories. This is
440 based on the match's primary, included, and excluded patterns.
440 based on the match's primary, included, and excluded patterns.
441
441
442 Returns the string 'all' if the given directory and all subdirectories
442 Returns the string 'all' if the given directory and all subdirectories
443 should be visited. Otherwise returns True or False indicating whether
443 should be visited. Otherwise returns True or False indicating whether
444 the given directory should be visited.
444 the given directory should be visited.
445 """
445 """
446 return True
446 return True
447
447
448 def visitchildrenset(self, dir):
448 def visitchildrenset(self, dir):
449 """Decides whether a directory should be visited based on whether it
449 """Decides whether a directory should be visited based on whether it
450 has potential matches in it or one of its subdirectories, and
450 has potential matches in it or one of its subdirectories, and
451 potentially lists which subdirectories of that directory should be
451 potentially lists which subdirectories of that directory should be
452 visited. This is based on the match's primary, included, and excluded
452 visited. This is based on the match's primary, included, and excluded
453 patterns.
453 patterns.
454
454
455 This function is very similar to 'visitdir', and the following mapping
455 This function is very similar to 'visitdir', and the following mapping
456 can be applied:
456 can be applied:
457
457
458 visitdir | visitchildrenlist
458 visitdir | visitchildrenlist
459 ----------+-------------------
459 ----------+-------------------
460 False | set()
460 False | set()
461 'all' | 'all'
461 'all' | 'all'
462 True | 'this' OR non-empty set of subdirs -or files- to visit
462 True | 'this' OR non-empty set of subdirs -or files- to visit
463
463
464 Example:
464 Example:
465 Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
465 Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
466 the following values (assuming the implementation of visitchildrenset
466 the following values (assuming the implementation of visitchildrenset
467 is capable of recognizing this; some implementations are not).
467 is capable of recognizing this; some implementations are not).
468
468
469 '' -> {'foo', 'qux'}
469 '' -> {'foo', 'qux'}
470 'baz' -> set()
470 'baz' -> set()
471 'foo' -> {'bar'}
471 'foo' -> {'bar'}
472 # Ideally this would be 'all', but since the prefix nature of matchers
472 # Ideally this would be 'all', but since the prefix nature of matchers
473 # is applied to the entire matcher, we have to downgrade this to
473 # is applied to the entire matcher, we have to downgrade this to
474 # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
474 # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
475 # in.
475 # in.
476 'foo/bar' -> 'this'
476 'foo/bar' -> 'this'
477 'qux' -> 'this'
477 'qux' -> 'this'
478
478
479 Important:
479 Important:
480 Most matchers do not know if they're representing files or
480 Most matchers do not know if they're representing files or
481 directories. They see ['path:dir/f'] and don't know whether 'f' is a
481 directories. They see ['path:dir/f'] and don't know whether 'f' is a
482 file or a directory, so visitchildrenset('dir') for most matchers will
482 file or a directory, so visitchildrenset('dir') for most matchers will
483 return {'f'}, but if the matcher knows it's a file (like exactmatcher
483 return {'f'}, but if the matcher knows it's a file (like exactmatcher
484 does), it may return 'this'. Do not rely on the return being a set
484 does), it may return 'this'. Do not rely on the return being a set
485 indicating that there are no files in this dir to investigate (or
485 indicating that there are no files in this dir to investigate (or
486 equivalently that if there are files to investigate in 'dir' that it
486 equivalently that if there are files to investigate in 'dir' that it
487 will always return 'this').
487 will always return 'this').
488 """
488 """
489 return b'this'
489 return b'this'
490
490
491 def always(self):
491 def always(self):
492 """Matcher will match everything and .files() will be empty --
492 """Matcher will match everything and .files() will be empty --
493 optimization might be possible."""
493 optimization might be possible."""
494 return False
494 return False
495
495
496 def isexact(self):
496 def isexact(self):
497 """Matcher will match exactly the list of files in .files() --
497 """Matcher will match exactly the list of files in .files() --
498 optimization might be possible."""
498 optimization might be possible."""
499 return False
499 return False
500
500
501 def prefix(self):
501 def prefix(self):
502 """Matcher will match the paths in .files() recursively --
502 """Matcher will match the paths in .files() recursively --
503 optimization might be possible."""
503 optimization might be possible."""
504 return False
504 return False
505
505
506 def anypats(self):
506 def anypats(self):
507 """None of .always(), .isexact(), and .prefix() is true --
507 """None of .always(), .isexact(), and .prefix() is true --
508 optimizations will be difficult."""
508 optimizations will be difficult."""
509 return not self.always() and not self.isexact() and not self.prefix()
509 return not self.always() and not self.isexact() and not self.prefix()
510
510
511
511
512 class alwaysmatcher(basematcher):
512 class alwaysmatcher(basematcher):
513 '''Matches everything.'''
513 '''Matches everything.'''
514
514
515 def __init__(self, badfn=None):
515 def __init__(self, badfn=None):
516 super(alwaysmatcher, self).__init__(badfn)
516 super(alwaysmatcher, self).__init__(badfn)
517
517
518 def always(self):
518 def always(self):
519 return True
519 return True
520
520
521 def matchfn(self, f):
521 def matchfn(self, f):
522 return True
522 return True
523
523
524 def visitdir(self, dir):
524 def visitdir(self, dir):
525 return b'all'
525 return b'all'
526
526
527 def visitchildrenset(self, dir):
527 def visitchildrenset(self, dir):
528 return b'all'
528 return b'all'
529
529
530 def __repr__(self):
530 def __repr__(self):
531 return r'<alwaysmatcher>'
531 return r'<alwaysmatcher>'
532
532
533
533
534 class nevermatcher(basematcher):
534 class nevermatcher(basematcher):
535 '''Matches nothing.'''
535 '''Matches nothing.'''
536
536
537 def __init__(self, badfn=None):
537 def __init__(self, badfn=None):
538 super(nevermatcher, self).__init__(badfn)
538 super(nevermatcher, self).__init__(badfn)
539
539
540 # It's a little weird to say that the nevermatcher is an exact matcher
540 # It's a little weird to say that the nevermatcher is an exact matcher
541 # or a prefix matcher, but it seems to make sense to let callers take
541 # or a prefix matcher, but it seems to make sense to let callers take
542 # fast paths based on either. There will be no exact matches, nor any
542 # fast paths based on either. There will be no exact matches, nor any
543 # prefixes (files() returns []), so fast paths iterating over them should
543 # prefixes (files() returns []), so fast paths iterating over them should
544 # be efficient (and correct).
544 # be efficient (and correct).
545 def isexact(self):
545 def isexact(self):
546 return True
546 return True
547
547
548 def prefix(self):
548 def prefix(self):
549 return True
549 return True
550
550
551 def visitdir(self, dir):
551 def visitdir(self, dir):
552 return False
552 return False
553
553
554 def visitchildrenset(self, dir):
554 def visitchildrenset(self, dir):
555 return set()
555 return set()
556
556
557 def __repr__(self):
557 def __repr__(self):
558 return r'<nevermatcher>'
558 return r'<nevermatcher>'
559
559
560
560
561 class predicatematcher(basematcher):
561 class predicatematcher(basematcher):
562 """A matcher adapter for a simple boolean function"""
562 """A matcher adapter for a simple boolean function"""
563
563
564 def __init__(self, predfn, predrepr=None, badfn=None):
564 def __init__(self, predfn, predrepr=None, badfn=None):
565 super(predicatematcher, self).__init__(badfn)
565 super(predicatematcher, self).__init__(badfn)
566 self.matchfn = predfn
566 self.matchfn = predfn
567 self._predrepr = predrepr
567 self._predrepr = predrepr
568
568
569 @encoding.strmethod
569 @encoding.strmethod
570 def __repr__(self):
570 def __repr__(self):
571 s = stringutil.buildrepr(self._predrepr) or pycompat.byterepr(
571 s = stringutil.buildrepr(self._predrepr) or pycompat.byterepr(
572 self.matchfn
572 self.matchfn
573 )
573 )
574 return b'<predicatenmatcher pred=%s>' % s
574 return b'<predicatenmatcher pred=%s>' % s
575
575
576
576
577 def path_or_parents_in_set(path, prefix_set):
577 def path_or_parents_in_set(path, prefix_set):
578 """Returns True if `path` (or any parent of `path`) is in `prefix_set`."""
578 """Returns True if `path` (or any parent of `path`) is in `prefix_set`."""
579 l = len(prefix_set)
579 l = len(prefix_set)
580 if l == 0:
580 if l == 0:
581 return False
581 return False
582 if path in prefix_set:
582 if path in prefix_set:
583 return True
583 return True
584 # If there's more than 5 paths in prefix_set, it's *probably* quicker to
584 # If there's more than 5 paths in prefix_set, it's *probably* quicker to
585 # "walk up" the directory hierarchy instead, with the assumption that most
585 # "walk up" the directory hierarchy instead, with the assumption that most
586 # directory hierarchies are relatively shallow and hash lookup is cheap.
586 # directory hierarchies are relatively shallow and hash lookup is cheap.
587 if l > 5:
587 if l > 5:
588 return any(
588 return any(
589 parentdir in prefix_set for parentdir in pathutil.finddirs(path)
589 parentdir in prefix_set for parentdir in pathutil.finddirs(path)
590 )
590 )
591
591
592 # FIXME: Ideally we'd never get to this point if this is the case - we'd
592 # FIXME: Ideally we'd never get to this point if this is the case - we'd
593 # recognize ourselves as an 'always' matcher and skip this.
593 # recognize ourselves as an 'always' matcher and skip this.
594 if b'' in prefix_set:
594 if b'' in prefix_set:
595 return True
595 return True
596
596
597 sl = ord(b'/')
597 sl = ord(b'/')
598
598
599 # We already checked that path isn't in prefix_set exactly, so
599 # We already checked that path isn't in prefix_set exactly, so
600 # `path[len(pf)] should never raise IndexError.
600 # `path[len(pf)] should never raise IndexError.
601 return any(path.startswith(pf) and path[len(pf)] == sl for pf in prefix_set)
601 return any(path.startswith(pf) and path[len(pf)] == sl for pf in prefix_set)
602
602
603
603
604 class patternmatcher(basematcher):
604 class patternmatcher(basematcher):
605 r"""Matches a set of (kind, pat, source) against a 'root' directory.
605 r"""Matches a set of (kind, pat, source) against a 'root' directory.
606
606
607 >>> kindpats = [
607 >>> kindpats = [
608 ... (b're', br'.*\.c$', b''),
608 ... (b're', br'.*\.c$', b''),
609 ... (b'path', b'foo/a', b''),
609 ... (b'path', b'foo/a', b''),
610 ... (b'relpath', b'b', b''),
610 ... (b'relpath', b'b', b''),
611 ... (b'glob', b'*.h', b''),
611 ... (b'glob', b'*.h', b''),
612 ... ]
612 ... ]
613 >>> m = patternmatcher(b'foo', kindpats)
613 >>> m = patternmatcher(b'foo', kindpats)
614 >>> m(b'main.c') # matches re:.*\.c$
614 >>> m(b'main.c') # matches re:.*\.c$
615 True
615 True
616 >>> m(b'b.txt')
616 >>> m(b'b.txt')
617 False
617 False
618 >>> m(b'foo/a') # matches path:foo/a
618 >>> m(b'foo/a') # matches path:foo/a
619 True
619 True
620 >>> m(b'a') # does not match path:b, since 'root' is 'foo'
620 >>> m(b'a') # does not match path:b, since 'root' is 'foo'
621 False
621 False
622 >>> m(b'b') # matches relpath:b, since 'root' is 'foo'
622 >>> m(b'b') # matches relpath:b, since 'root' is 'foo'
623 True
623 True
624 >>> m(b'lib.h') # matches glob:*.h
624 >>> m(b'lib.h') # matches glob:*.h
625 True
625 True
626
626
627 >>> m.files()
627 >>> m.files()
628 [b'', b'foo/a', b'', b'b']
628 [b'', b'foo/a', b'', b'b']
629 >>> m.exact(b'foo/a')
629 >>> m.exact(b'foo/a')
630 True
630 True
631 >>> m.exact(b'b')
631 >>> m.exact(b'b')
632 True
632 True
633 >>> m.exact(b'lib.h') # exact matches are for (rel)path kinds
633 >>> m.exact(b'lib.h') # exact matches are for (rel)path kinds
634 False
634 False
635 """
635 """
636
636
637 def __init__(self, root, kindpats, badfn=None):
637 def __init__(self, root, kindpats, badfn=None):
638 super(patternmatcher, self).__init__(badfn)
638 super(patternmatcher, self).__init__(badfn)
639 kindpats.sort()
639 kindpats.sort()
640
640
641 roots, dirs, parents = _rootsdirsandparents(kindpats)
641 self._files = _explicitfiles(kindpats)
642 self._files = _explicitfiles(kindpats)
643 self._dirs_explicit = set(dirs)
644 self._dirs = parents
642 self._prefix = _prefix(kindpats)
645 self._prefix = _prefix(kindpats)
643 self._pats, self._matchfn = _buildmatch(kindpats, b'$', root)
646 self._pats, self._matchfn = _buildmatch(kindpats, b'$', root)
644
647
645 def matchfn(self, fn):
648 def matchfn(self, fn):
646 if fn in self._fileset:
649 if fn in self._fileset:
647 return True
650 return True
648 return self._matchfn(fn)
651 return self._matchfn(fn)
649
652
650 @propertycache
651 def _dirs(self):
652 return set(pathutil.dirs(self._fileset))
653
654 def visitdir(self, dir):
653 def visitdir(self, dir):
655 if self._prefix and dir in self._fileset:
654 if self._prefix and dir in self._fileset:
656 return b'all'
655 return b'all'
657 return dir in self._dirs or path_or_parents_in_set(dir, self._fileset)
656 return (
657 dir in self._dirs
658 or path_or_parents_in_set(dir, self._fileset)
659 or path_or_parents_in_set(dir, self._dirs_explicit)
660 )
658
661
659 def visitchildrenset(self, dir):
662 def visitchildrenset(self, dir):
660 ret = self.visitdir(dir)
663 ret = self.visitdir(dir)
661 if ret is True:
664 if ret is True:
662 return b'this'
665 return b'this'
663 elif not ret:
666 elif not ret:
664 return set()
667 return set()
665 assert ret == b'all'
668 assert ret == b'all'
666 return b'all'
669 return b'all'
667
670
668 def prefix(self):
671 def prefix(self):
669 return self._prefix
672 return self._prefix
670
673
671 @encoding.strmethod
674 @encoding.strmethod
672 def __repr__(self):
675 def __repr__(self):
673 return b'<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats)
676 return b'<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats)
674
677
675
678
676 # This is basically a reimplementation of pathutil.dirs that stores the
679 # This is basically a reimplementation of pathutil.dirs that stores the
677 # children instead of just a count of them, plus a small optional optimization
680 # children instead of just a count of them, plus a small optional optimization
678 # to avoid some directories we don't need.
681 # to avoid some directories we don't need.
679 class _dirchildren:
682 class _dirchildren:
680 def __init__(self, paths, onlyinclude=None):
683 def __init__(self, paths, onlyinclude=None):
681 self._dirs = {}
684 self._dirs = {}
682 self._onlyinclude = onlyinclude or []
685 self._onlyinclude = onlyinclude or []
683 addpath = self.addpath
686 addpath = self.addpath
684 for f in paths:
687 for f in paths:
685 addpath(f)
688 addpath(f)
686
689
687 def addpath(self, path):
690 def addpath(self, path):
688 if path == b'':
691 if path == b'':
689 return
692 return
690 dirs = self._dirs
693 dirs = self._dirs
691 findsplitdirs = _dirchildren._findsplitdirs
694 findsplitdirs = _dirchildren._findsplitdirs
692 for d, b in findsplitdirs(path):
695 for d, b in findsplitdirs(path):
693 if d not in self._onlyinclude:
696 if d not in self._onlyinclude:
694 continue
697 continue
695 dirs.setdefault(d, set()).add(b)
698 dirs.setdefault(d, set()).add(b)
696
699
697 @staticmethod
700 @staticmethod
698 def _findsplitdirs(path):
701 def _findsplitdirs(path):
699 # yields (dirname, basename) tuples, walking back to the root. This is
702 # yields (dirname, basename) tuples, walking back to the root. This is
700 # very similar to pathutil.finddirs, except:
703 # very similar to pathutil.finddirs, except:
701 # - produces a (dirname, basename) tuple, not just 'dirname'
704 # - produces a (dirname, basename) tuple, not just 'dirname'
702 # Unlike manifest._splittopdir, this does not suffix `dirname` with a
705 # Unlike manifest._splittopdir, this does not suffix `dirname` with a
703 # slash.
706 # slash.
704 oldpos = len(path)
707 oldpos = len(path)
705 pos = path.rfind(b'/')
708 pos = path.rfind(b'/')
706 while pos != -1:
709 while pos != -1:
707 yield path[:pos], path[pos + 1 : oldpos]
710 yield path[:pos], path[pos + 1 : oldpos]
708 oldpos = pos
711 oldpos = pos
709 pos = path.rfind(b'/', 0, pos)
712 pos = path.rfind(b'/', 0, pos)
710 yield b'', path[:oldpos]
713 yield b'', path[:oldpos]
711
714
712 def get(self, path):
715 def get(self, path):
713 return self._dirs.get(path, set())
716 return self._dirs.get(path, set())
714
717
715
718
716 class includematcher(basematcher):
719 class includematcher(basematcher):
717 def __init__(self, root, kindpats, badfn=None):
720 def __init__(self, root, kindpats, badfn=None):
718 super(includematcher, self).__init__(badfn)
721 super(includematcher, self).__init__(badfn)
719 if rustmod is not None:
722 if rustmod is not None:
720 # We need to pass the patterns to Rust because they can contain
723 # We need to pass the patterns to Rust because they can contain
721 # patterns from the user interface
724 # patterns from the user interface
722 self._kindpats = kindpats
725 self._kindpats = kindpats
723 self._pats, self.matchfn = _buildmatch(kindpats, b'(?:/|$)', root)
726 self._pats, self.matchfn = _buildmatch(kindpats, b'(?:/|$)', root)
724 self._prefix = _prefix(kindpats)
727 self._prefix = _prefix(kindpats)
725 roots, dirs, parents = _rootsdirsandparents(kindpats)
728 roots, dirs, parents = _rootsdirsandparents(kindpats)
726 # roots are directories which are recursively included.
729 # roots are directories which are recursively included.
727 self._roots = set(roots)
730 self._roots = set(roots)
728 # dirs are directories which are non-recursively included.
731 # dirs are directories which are non-recursively included.
729 self._dirs = set(dirs)
732 self._dirs = set(dirs)
730 # parents are directories which are non-recursively included because
733 # parents are directories which are non-recursively included because
731 # they are needed to get to items in _dirs or _roots.
734 # they are needed to get to items in _dirs or _roots.
732 self._parents = parents
735 self._parents = parents
733
736
734 def visitdir(self, dir):
737 def visitdir(self, dir):
735 if self._prefix and dir in self._roots:
738 if self._prefix and dir in self._roots:
736 return b'all'
739 return b'all'
737 return (
740 return (
738 dir in self._dirs
741 dir in self._dirs
739 or dir in self._parents
742 or dir in self._parents
740 or path_or_parents_in_set(dir, self._roots)
743 or path_or_parents_in_set(dir, self._roots)
741 )
744 )
742
745
743 @propertycache
746 @propertycache
744 def _allparentschildren(self):
747 def _allparentschildren(self):
745 # It may seem odd that we add dirs, roots, and parents, and then
748 # It may seem odd that we add dirs, roots, and parents, and then
746 # restrict to only parents. This is to catch the case of:
749 # restrict to only parents. This is to catch the case of:
747 # dirs = ['foo/bar']
750 # dirs = ['foo/bar']
748 # parents = ['foo']
751 # parents = ['foo']
749 # if we asked for the children of 'foo', but had only added
752 # if we asked for the children of 'foo', but had only added
750 # self._parents, we wouldn't be able to respond ['bar'].
753 # self._parents, we wouldn't be able to respond ['bar'].
751 return _dirchildren(
754 return _dirchildren(
752 itertools.chain(self._dirs, self._roots, self._parents),
755 itertools.chain(self._dirs, self._roots, self._parents),
753 onlyinclude=self._parents,
756 onlyinclude=self._parents,
754 )
757 )
755
758
756 def visitchildrenset(self, dir):
759 def visitchildrenset(self, dir):
757 if self._prefix and dir in self._roots:
760 if self._prefix and dir in self._roots:
758 return b'all'
761 return b'all'
759 # Note: this does *not* include the 'dir in self._parents' case from
762 # Note: this does *not* include the 'dir in self._parents' case from
760 # visitdir, that's handled below.
763 # visitdir, that's handled below.
761 if (
764 if (
762 b'' in self._roots
765 b'' in self._roots
763 or dir in self._dirs
766 or dir in self._dirs
764 or path_or_parents_in_set(dir, self._roots)
767 or path_or_parents_in_set(dir, self._roots)
765 ):
768 ):
766 return b'this'
769 return b'this'
767
770
768 if dir in self._parents:
771 if dir in self._parents:
769 return self._allparentschildren.get(dir) or set()
772 return self._allparentschildren.get(dir) or set()
770 return set()
773 return set()
771
774
772 @encoding.strmethod
775 @encoding.strmethod
773 def __repr__(self):
776 def __repr__(self):
774 return b'<includematcher includes=%r>' % pycompat.bytestr(self._pats)
777 return b'<includematcher includes=%r>' % pycompat.bytestr(self._pats)
775
778
776
779
777 class exactmatcher(basematcher):
780 class exactmatcher(basematcher):
778 r"""Matches the input files exactly. They are interpreted as paths, not
781 r"""Matches the input files exactly. They are interpreted as paths, not
779 patterns (so no kind-prefixes).
782 patterns (so no kind-prefixes).
780
783
781 >>> m = exactmatcher([b'a.txt', br're:.*\.c$'])
784 >>> m = exactmatcher([b'a.txt', br're:.*\.c$'])
782 >>> m(b'a.txt')
785 >>> m(b'a.txt')
783 True
786 True
784 >>> m(b'b.txt')
787 >>> m(b'b.txt')
785 False
788 False
786
789
787 Input files that would be matched are exactly those returned by .files()
790 Input files that would be matched are exactly those returned by .files()
788 >>> m.files()
791 >>> m.files()
789 ['a.txt', 're:.*\\.c$']
792 ['a.txt', 're:.*\\.c$']
790
793
791 So pattern 're:.*\.c$' is not considered as a regex, but as a file name
794 So pattern 're:.*\.c$' is not considered as a regex, but as a file name
792 >>> m(b'main.c')
795 >>> m(b'main.c')
793 False
796 False
794 >>> m(br're:.*\.c$')
797 >>> m(br're:.*\.c$')
795 True
798 True
796 """
799 """
797
800
798 def __init__(self, files, badfn=None):
801 def __init__(self, files, badfn=None):
799 super(exactmatcher, self).__init__(badfn)
802 super(exactmatcher, self).__init__(badfn)
800
803
801 if isinstance(files, list):
804 if isinstance(files, list):
802 self._files = files
805 self._files = files
803 else:
806 else:
804 self._files = list(files)
807 self._files = list(files)
805
808
806 matchfn = basematcher.exact
809 matchfn = basematcher.exact
807
810
808 @propertycache
811 @propertycache
809 def _dirs(self):
812 def _dirs(self):
810 return set(pathutil.dirs(self._fileset))
813 return set(pathutil.dirs(self._fileset))
811
814
812 def visitdir(self, dir):
815 def visitdir(self, dir):
813 return dir in self._dirs
816 return dir in self._dirs
814
817
815 @propertycache
818 @propertycache
816 def _visitchildrenset_candidates(self):
819 def _visitchildrenset_candidates(self):
817 """A memoized set of candidates for visitchildrenset."""
820 """A memoized set of candidates for visitchildrenset."""
818 return self._fileset | self._dirs - {b''}
821 return self._fileset | self._dirs - {b''}
819
822
820 @propertycache
823 @propertycache
821 def _sorted_visitchildrenset_candidates(self):
824 def _sorted_visitchildrenset_candidates(self):
822 """A memoized sorted list of candidates for visitchildrenset."""
825 """A memoized sorted list of candidates for visitchildrenset."""
823 return sorted(self._visitchildrenset_candidates)
826 return sorted(self._visitchildrenset_candidates)
824
827
825 def visitchildrenset(self, dir):
828 def visitchildrenset(self, dir):
826 if not self._fileset or dir not in self._dirs:
829 if not self._fileset or dir not in self._dirs:
827 return set()
830 return set()
828
831
829 if dir == b'':
832 if dir == b'':
830 candidates = self._visitchildrenset_candidates
833 candidates = self._visitchildrenset_candidates
831 else:
834 else:
832 candidates = self._sorted_visitchildrenset_candidates
835 candidates = self._sorted_visitchildrenset_candidates
833 d = dir + b'/'
836 d = dir + b'/'
834 # Use bisect to find the first element potentially starting with d
837 # Use bisect to find the first element potentially starting with d
835 # (i.e. >= d). This should always find at least one element (we'll
838 # (i.e. >= d). This should always find at least one element (we'll
836 # assert later if this is not the case).
839 # assert later if this is not the case).
837 first = bisect.bisect_left(candidates, d)
840 first = bisect.bisect_left(candidates, d)
838 # We need a representation of the first element that is > d that
841 # We need a representation of the first element that is > d that
839 # does not start with d, so since we added a `/` on the end of dir,
842 # does not start with d, so since we added a `/` on the end of dir,
840 # we'll add whatever comes after slash (we could probably assume
843 # we'll add whatever comes after slash (we could probably assume
841 # that `0` is after `/`, but let's not) to the end of dir instead.
844 # that `0` is after `/`, but let's not) to the end of dir instead.
842 dnext = dir + encoding.strtolocal(chr(ord(b'/') + 1))
845 dnext = dir + encoding.strtolocal(chr(ord(b'/') + 1))
843 # Use bisect to find the first element >= d_next
846 # Use bisect to find the first element >= d_next
844 last = bisect.bisect_left(candidates, dnext, lo=first)
847 last = bisect.bisect_left(candidates, dnext, lo=first)
845 dlen = len(d)
848 dlen = len(d)
846 candidates = {c[dlen:] for c in candidates[first:last]}
849 candidates = {c[dlen:] for c in candidates[first:last]}
847 # self._dirs includes all of the directories, recursively, so if
850 # self._dirs includes all of the directories, recursively, so if
848 # we're attempting to match foo/bar/baz.txt, it'll have '', 'foo',
851 # we're attempting to match foo/bar/baz.txt, it'll have '', 'foo',
849 # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
852 # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
850 # '/' in it, indicating a it's for a subdir-of-a-subdir; the
853 # '/' in it, indicating a it's for a subdir-of-a-subdir; the
851 # immediate subdir will be in there without a slash.
854 # immediate subdir will be in there without a slash.
852 ret = {c for c in candidates if b'/' not in c}
855 ret = {c for c in candidates if b'/' not in c}
853 # We really do not expect ret to be empty, since that would imply that
856 # We really do not expect ret to be empty, since that would imply that
854 # there's something in _dirs that didn't have a file in _fileset.
857 # there's something in _dirs that didn't have a file in _fileset.
855 assert ret
858 assert ret
856 return ret
859 return ret
857
860
858 def isexact(self):
861 def isexact(self):
859 return True
862 return True
860
863
861 @encoding.strmethod
864 @encoding.strmethod
862 def __repr__(self):
865 def __repr__(self):
863 return b'<exactmatcher files=%r>' % self._files
866 return b'<exactmatcher files=%r>' % self._files
864
867
865
868
866 class differencematcher(basematcher):
869 class differencematcher(basematcher):
867 """Composes two matchers by matching if the first matches and the second
870 """Composes two matchers by matching if the first matches and the second
868 does not.
871 does not.
869
872
870 The second matcher's non-matching-attributes (bad, traversedir) are ignored.
873 The second matcher's non-matching-attributes (bad, traversedir) are ignored.
871 """
874 """
872
875
873 def __init__(self, m1, m2):
876 def __init__(self, m1, m2):
874 super(differencematcher, self).__init__()
877 super(differencematcher, self).__init__()
875 self._m1 = m1
878 self._m1 = m1
876 self._m2 = m2
879 self._m2 = m2
877 self.bad = m1.bad
880 self.bad = m1.bad
878 self.traversedir = m1.traversedir
881 self.traversedir = m1.traversedir
879
882
880 def matchfn(self, f):
883 def matchfn(self, f):
881 return self._m1(f) and not self._m2(f)
884 return self._m1(f) and not self._m2(f)
882
885
883 @propertycache
886 @propertycache
884 def _files(self):
887 def _files(self):
885 if self.isexact():
888 if self.isexact():
886 return [f for f in self._m1.files() if self(f)]
889 return [f for f in self._m1.files() if self(f)]
887 # If m1 is not an exact matcher, we can't easily figure out the set of
890 # If m1 is not an exact matcher, we can't easily figure out the set of
888 # files, because its files() are not always files. For example, if
891 # files, because its files() are not always files. For example, if
889 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
892 # m1 is "path:dir" and m2 is "rootfileins:.", we don't
890 # want to remove "dir" from the set even though it would match m2,
893 # want to remove "dir" from the set even though it would match m2,
891 # because the "dir" in m1 may not be a file.
894 # because the "dir" in m1 may not be a file.
892 return self._m1.files()
895 return self._m1.files()
893
896
894 def visitdir(self, dir):
897 def visitdir(self, dir):
895 if self._m2.visitdir(dir) == b'all':
898 if self._m2.visitdir(dir) == b'all':
896 return False
899 return False
897 elif not self._m2.visitdir(dir):
900 elif not self._m2.visitdir(dir):
898 # m2 does not match dir, we can return 'all' here if possible
901 # m2 does not match dir, we can return 'all' here if possible
899 return self._m1.visitdir(dir)
902 return self._m1.visitdir(dir)
900 return bool(self._m1.visitdir(dir))
903 return bool(self._m1.visitdir(dir))
901
904
902 def visitchildrenset(self, dir):
905 def visitchildrenset(self, dir):
903 m2_set = self._m2.visitchildrenset(dir)
906 m2_set = self._m2.visitchildrenset(dir)
904 if m2_set == b'all':
907 if m2_set == b'all':
905 return set()
908 return set()
906 m1_set = self._m1.visitchildrenset(dir)
909 m1_set = self._m1.visitchildrenset(dir)
907 # Possible values for m1: 'all', 'this', set(...), set()
910 # Possible values for m1: 'all', 'this', set(...), set()
908 # Possible values for m2: 'this', set(...), set()
911 # Possible values for m2: 'this', set(...), set()
909 # If m2 has nothing under here that we care about, return m1, even if
912 # If m2 has nothing under here that we care about, return m1, even if
910 # it's 'all'. This is a change in behavior from visitdir, which would
913 # it's 'all'. This is a change in behavior from visitdir, which would
911 # return True, not 'all', for some reason.
914 # return True, not 'all', for some reason.
912 if not m2_set:
915 if not m2_set:
913 return m1_set
916 return m1_set
914 if m1_set in [b'all', b'this']:
917 if m1_set in [b'all', b'this']:
915 # Never return 'all' here if m2_set is any kind of non-empty (either
918 # Never return 'all' here if m2_set is any kind of non-empty (either
916 # 'this' or set(foo)), since m2 might return set() for a
919 # 'this' or set(foo)), since m2 might return set() for a
917 # subdirectory.
920 # subdirectory.
918 return b'this'
921 return b'this'
919 # Possible values for m1: set(...), set()
922 # Possible values for m1: set(...), set()
920 # Possible values for m2: 'this', set(...)
923 # Possible values for m2: 'this', set(...)
921 # We ignore m2's set results. They're possibly incorrect:
924 # We ignore m2's set results. They're possibly incorrect:
922 # m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset(''):
925 # m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset(''):
923 # m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
926 # m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
924 # return set(), which is *not* correct, we still need to visit 'dir'!
927 # return set(), which is *not* correct, we still need to visit 'dir'!
925 return m1_set
928 return m1_set
926
929
927 def isexact(self):
930 def isexact(self):
928 return self._m1.isexact()
931 return self._m1.isexact()
929
932
930 @encoding.strmethod
933 @encoding.strmethod
931 def __repr__(self):
934 def __repr__(self):
932 return b'<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2)
935 return b'<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2)
933
936
934
937
935 def intersectmatchers(m1, m2):
938 def intersectmatchers(m1, m2):
936 """Composes two matchers by matching if both of them match.
939 """Composes two matchers by matching if both of them match.
937
940
938 The second matcher's non-matching-attributes (bad, traversedir) are ignored.
941 The second matcher's non-matching-attributes (bad, traversedir) are ignored.
939 """
942 """
940 if m1 is None or m2 is None:
943 if m1 is None or m2 is None:
941 return m1 or m2
944 return m1 or m2
942 if m1.always():
945 if m1.always():
943 m = copy.copy(m2)
946 m = copy.copy(m2)
944 # TODO: Consider encapsulating these things in a class so there's only
947 # TODO: Consider encapsulating these things in a class so there's only
945 # one thing to copy from m1.
948 # one thing to copy from m1.
946 m.bad = m1.bad
949 m.bad = m1.bad
947 m.traversedir = m1.traversedir
950 m.traversedir = m1.traversedir
948 return m
951 return m
949 if m2.always():
952 if m2.always():
950 m = copy.copy(m1)
953 m = copy.copy(m1)
951 return m
954 return m
952 return intersectionmatcher(m1, m2)
955 return intersectionmatcher(m1, m2)
953
956
954
957
955 class intersectionmatcher(basematcher):
958 class intersectionmatcher(basematcher):
956 def __init__(self, m1, m2):
959 def __init__(self, m1, m2):
957 super(intersectionmatcher, self).__init__()
960 super(intersectionmatcher, self).__init__()
958 self._m1 = m1
961 self._m1 = m1
959 self._m2 = m2
962 self._m2 = m2
960 self.bad = m1.bad
963 self.bad = m1.bad
961 self.traversedir = m1.traversedir
964 self.traversedir = m1.traversedir
962
965
963 @propertycache
966 @propertycache
964 def _files(self):
967 def _files(self):
965 if self.isexact():
968 if self.isexact():
966 m1, m2 = self._m1, self._m2
969 m1, m2 = self._m1, self._m2
967 if not m1.isexact():
970 if not m1.isexact():
968 m1, m2 = m2, m1
971 m1, m2 = m2, m1
969 return [f for f in m1.files() if m2(f)]
972 return [f for f in m1.files() if m2(f)]
970 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
973 # It neither m1 nor m2 is an exact matcher, we can't easily intersect
971 # the set of files, because their files() are not always files. For
974 # the set of files, because their files() are not always files. For
972 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
975 # example, if intersecting a matcher "-I glob:foo.txt" with matcher of
973 # "path:dir2", we don't want to remove "dir2" from the set.
976 # "path:dir2", we don't want to remove "dir2" from the set.
974 return self._m1.files() + self._m2.files()
977 return self._m1.files() + self._m2.files()
975
978
976 def matchfn(self, f):
979 def matchfn(self, f):
977 return self._m1(f) and self._m2(f)
980 return self._m1(f) and self._m2(f)
978
981
979 def visitdir(self, dir):
982 def visitdir(self, dir):
980 visit1 = self._m1.visitdir(dir)
983 visit1 = self._m1.visitdir(dir)
981 if visit1 == b'all':
984 if visit1 == b'all':
982 return self._m2.visitdir(dir)
985 return self._m2.visitdir(dir)
983 # bool() because visit1=True + visit2='all' should not be 'all'
986 # bool() because visit1=True + visit2='all' should not be 'all'
984 return bool(visit1 and self._m2.visitdir(dir))
987 return bool(visit1 and self._m2.visitdir(dir))
985
988
986 def visitchildrenset(self, dir):
989 def visitchildrenset(self, dir):
987 m1_set = self._m1.visitchildrenset(dir)
990 m1_set = self._m1.visitchildrenset(dir)
988 if not m1_set:
991 if not m1_set:
989 return set()
992 return set()
990 m2_set = self._m2.visitchildrenset(dir)
993 m2_set = self._m2.visitchildrenset(dir)
991 if not m2_set:
994 if not m2_set:
992 return set()
995 return set()
993
996
994 if m1_set == b'all':
997 if m1_set == b'all':
995 return m2_set
998 return m2_set
996 elif m2_set == b'all':
999 elif m2_set == b'all':
997 return m1_set
1000 return m1_set
998
1001
999 if m1_set == b'this' or m2_set == b'this':
1002 if m1_set == b'this' or m2_set == b'this':
1000 return b'this'
1003 return b'this'
1001
1004
1002 assert isinstance(m1_set, set) and isinstance(m2_set, set)
1005 assert isinstance(m1_set, set) and isinstance(m2_set, set)
1003 return m1_set.intersection(m2_set)
1006 return m1_set.intersection(m2_set)
1004
1007
1005 def always(self):
1008 def always(self):
1006 return self._m1.always() and self._m2.always()
1009 return self._m1.always() and self._m2.always()
1007
1010
1008 def isexact(self):
1011 def isexact(self):
1009 return self._m1.isexact() or self._m2.isexact()
1012 return self._m1.isexact() or self._m2.isexact()
1010
1013
1011 @encoding.strmethod
1014 @encoding.strmethod
1012 def __repr__(self):
1015 def __repr__(self):
1013 return b'<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2)
1016 return b'<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2)
1014
1017
1015
1018
1016 class subdirmatcher(basematcher):
1019 class subdirmatcher(basematcher):
1017 """Adapt a matcher to work on a subdirectory only.
1020 """Adapt a matcher to work on a subdirectory only.
1018
1021
1019 The paths are remapped to remove/insert the path as needed:
1022 The paths are remapped to remove/insert the path as needed:
1020
1023
1021 >>> from . import pycompat
1024 >>> from . import pycompat
1022 >>> m1 = match(util.localpath(b'/root'), b'', [b'a.txt', b'sub/b.txt'], auditor=lambda name: None)
1025 >>> m1 = match(util.localpath(b'/root'), b'', [b'a.txt', b'sub/b.txt'], auditor=lambda name: None)
1023 >>> m2 = subdirmatcher(b'sub', m1)
1026 >>> m2 = subdirmatcher(b'sub', m1)
1024 >>> m2(b'a.txt')
1027 >>> m2(b'a.txt')
1025 False
1028 False
1026 >>> m2(b'b.txt')
1029 >>> m2(b'b.txt')
1027 True
1030 True
1028 >>> m2.matchfn(b'a.txt')
1031 >>> m2.matchfn(b'a.txt')
1029 False
1032 False
1030 >>> m2.matchfn(b'b.txt')
1033 >>> m2.matchfn(b'b.txt')
1031 True
1034 True
1032 >>> m2.files()
1035 >>> m2.files()
1033 ['b.txt']
1036 ['b.txt']
1034 >>> m2.exact(b'b.txt')
1037 >>> m2.exact(b'b.txt')
1035 True
1038 True
1036 >>> def bad(f, msg):
1039 >>> def bad(f, msg):
1037 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
1040 ... print(pycompat.sysstr(b"%s: %s" % (f, msg)))
1038 >>> m1.bad = bad
1041 >>> m1.bad = bad
1039 >>> m2.bad(b'x.txt', b'No such file')
1042 >>> m2.bad(b'x.txt', b'No such file')
1040 sub/x.txt: No such file
1043 sub/x.txt: No such file
1041 """
1044 """
1042
1045
1043 def __init__(self, path, matcher):
1046 def __init__(self, path, matcher):
1044 super(subdirmatcher, self).__init__()
1047 super(subdirmatcher, self).__init__()
1045 self._path = path
1048 self._path = path
1046 self._matcher = matcher
1049 self._matcher = matcher
1047 self._always = matcher.always()
1050 self._always = matcher.always()
1048
1051
1049 self._files = [
1052 self._files = [
1050 f[len(path) + 1 :]
1053 f[len(path) + 1 :]
1051 for f in matcher._files
1054 for f in matcher._files
1052 if f.startswith(path + b"/")
1055 if f.startswith(path + b"/")
1053 ]
1056 ]
1054
1057
1055 # If the parent repo had a path to this subrepo and the matcher is
1058 # If the parent repo had a path to this subrepo and the matcher is
1056 # a prefix matcher, this submatcher always matches.
1059 # a prefix matcher, this submatcher always matches.
1057 if matcher.prefix():
1060 if matcher.prefix():
1058 self._always = any(f == path for f in matcher._files)
1061 self._always = any(f == path for f in matcher._files)
1059
1062
1060 def bad(self, f, msg):
1063 def bad(self, f, msg):
1061 self._matcher.bad(self._path + b"/" + f, msg)
1064 self._matcher.bad(self._path + b"/" + f, msg)
1062
1065
1063 def matchfn(self, f):
1066 def matchfn(self, f):
1064 # Some information is lost in the superclass's constructor, so we
1067 # Some information is lost in the superclass's constructor, so we
1065 # can not accurately create the matching function for the subdirectory
1068 # can not accurately create the matching function for the subdirectory
1066 # from the inputs. Instead, we override matchfn() and visitdir() to
1069 # from the inputs. Instead, we override matchfn() and visitdir() to
1067 # call the original matcher with the subdirectory path prepended.
1070 # call the original matcher with the subdirectory path prepended.
1068 return self._matcher.matchfn(self._path + b"/" + f)
1071 return self._matcher.matchfn(self._path + b"/" + f)
1069
1072
1070 def visitdir(self, dir):
1073 def visitdir(self, dir):
1071 if dir == b'':
1074 if dir == b'':
1072 dir = self._path
1075 dir = self._path
1073 else:
1076 else:
1074 dir = self._path + b"/" + dir
1077 dir = self._path + b"/" + dir
1075 return self._matcher.visitdir(dir)
1078 return self._matcher.visitdir(dir)
1076
1079
1077 def visitchildrenset(self, dir):
1080 def visitchildrenset(self, dir):
1078 if dir == b'':
1081 if dir == b'':
1079 dir = self._path
1082 dir = self._path
1080 else:
1083 else:
1081 dir = self._path + b"/" + dir
1084 dir = self._path + b"/" + dir
1082 return self._matcher.visitchildrenset(dir)
1085 return self._matcher.visitchildrenset(dir)
1083
1086
1084 def always(self):
1087 def always(self):
1085 return self._always
1088 return self._always
1086
1089
1087 def prefix(self):
1090 def prefix(self):
1088 return self._matcher.prefix() and not self._always
1091 return self._matcher.prefix() and not self._always
1089
1092
1090 @encoding.strmethod
1093 @encoding.strmethod
1091 def __repr__(self):
1094 def __repr__(self):
1092 return b'<subdirmatcher path=%r, matcher=%r>' % (
1095 return b'<subdirmatcher path=%r, matcher=%r>' % (
1093 self._path,
1096 self._path,
1094 self._matcher,
1097 self._matcher,
1095 )
1098 )
1096
1099
1097
1100
1098 class prefixdirmatcher(basematcher):
1101 class prefixdirmatcher(basematcher):
1099 """Adapt a matcher to work on a parent directory.
1102 """Adapt a matcher to work on a parent directory.
1100
1103
1101 The matcher's non-matching-attributes (bad, traversedir) are ignored.
1104 The matcher's non-matching-attributes (bad, traversedir) are ignored.
1102
1105
1103 The prefix path should usually be the relative path from the root of
1106 The prefix path should usually be the relative path from the root of
1104 this matcher to the root of the wrapped matcher.
1107 this matcher to the root of the wrapped matcher.
1105
1108
1106 >>> m1 = match(util.localpath(b'/root/d/e'), b'f', [b'../a.txt', b'b.txt'], auditor=lambda name: None)
1109 >>> m1 = match(util.localpath(b'/root/d/e'), b'f', [b'../a.txt', b'b.txt'], auditor=lambda name: None)
1107 >>> m2 = prefixdirmatcher(b'd/e', m1)
1110 >>> m2 = prefixdirmatcher(b'd/e', m1)
1108 >>> m2(b'a.txt')
1111 >>> m2(b'a.txt')
1109 False
1112 False
1110 >>> m2(b'd/e/a.txt')
1113 >>> m2(b'd/e/a.txt')
1111 True
1114 True
1112 >>> m2(b'd/e/b.txt')
1115 >>> m2(b'd/e/b.txt')
1113 False
1116 False
1114 >>> m2.files()
1117 >>> m2.files()
1115 ['d/e/a.txt', 'd/e/f/b.txt']
1118 ['d/e/a.txt', 'd/e/f/b.txt']
1116 >>> m2.exact(b'd/e/a.txt')
1119 >>> m2.exact(b'd/e/a.txt')
1117 True
1120 True
1118 >>> m2.visitdir(b'd')
1121 >>> m2.visitdir(b'd')
1119 True
1122 True
1120 >>> m2.visitdir(b'd/e')
1123 >>> m2.visitdir(b'd/e')
1121 True
1124 True
1122 >>> m2.visitdir(b'd/e/f')
1125 >>> m2.visitdir(b'd/e/f')
1123 True
1126 True
1124 >>> m2.visitdir(b'd/e/g')
1127 >>> m2.visitdir(b'd/e/g')
1125 False
1128 False
1126 >>> m2.visitdir(b'd/ef')
1129 >>> m2.visitdir(b'd/ef')
1127 False
1130 False
1128 """
1131 """
1129
1132
1130 def __init__(self, path, matcher, badfn=None):
1133 def __init__(self, path, matcher, badfn=None):
1131 super(prefixdirmatcher, self).__init__(badfn)
1134 super(prefixdirmatcher, self).__init__(badfn)
1132 if not path:
1135 if not path:
1133 raise error.ProgrammingError(b'prefix path must not be empty')
1136 raise error.ProgrammingError(b'prefix path must not be empty')
1134 self._path = path
1137 self._path = path
1135 self._pathprefix = path + b'/'
1138 self._pathprefix = path + b'/'
1136 self._matcher = matcher
1139 self._matcher = matcher
1137
1140
1138 @propertycache
1141 @propertycache
1139 def _files(self):
1142 def _files(self):
1140 return [self._pathprefix + f for f in self._matcher._files]
1143 return [self._pathprefix + f for f in self._matcher._files]
1141
1144
1142 def matchfn(self, f):
1145 def matchfn(self, f):
1143 if not f.startswith(self._pathprefix):
1146 if not f.startswith(self._pathprefix):
1144 return False
1147 return False
1145 return self._matcher.matchfn(f[len(self._pathprefix) :])
1148 return self._matcher.matchfn(f[len(self._pathprefix) :])
1146
1149
1147 @propertycache
1150 @propertycache
1148 def _pathdirs(self):
1151 def _pathdirs(self):
1149 return set(pathutil.finddirs(self._path))
1152 return set(pathutil.finddirs(self._path))
1150
1153
1151 def visitdir(self, dir):
1154 def visitdir(self, dir):
1152 if dir == self._path:
1155 if dir == self._path:
1153 return self._matcher.visitdir(b'')
1156 return self._matcher.visitdir(b'')
1154 if dir.startswith(self._pathprefix):
1157 if dir.startswith(self._pathprefix):
1155 return self._matcher.visitdir(dir[len(self._pathprefix) :])
1158 return self._matcher.visitdir(dir[len(self._pathprefix) :])
1156 return dir in self._pathdirs
1159 return dir in self._pathdirs
1157
1160
1158 def visitchildrenset(self, dir):
1161 def visitchildrenset(self, dir):
1159 if dir == self._path:
1162 if dir == self._path:
1160 return self._matcher.visitchildrenset(b'')
1163 return self._matcher.visitchildrenset(b'')
1161 if dir.startswith(self._pathprefix):
1164 if dir.startswith(self._pathprefix):
1162 return self._matcher.visitchildrenset(dir[len(self._pathprefix) :])
1165 return self._matcher.visitchildrenset(dir[len(self._pathprefix) :])
1163 if dir in self._pathdirs:
1166 if dir in self._pathdirs:
1164 return b'this'
1167 return b'this'
1165 return set()
1168 return set()
1166
1169
1167 def isexact(self):
1170 def isexact(self):
1168 return self._matcher.isexact()
1171 return self._matcher.isexact()
1169
1172
1170 def prefix(self):
1173 def prefix(self):
1171 return self._matcher.prefix()
1174 return self._matcher.prefix()
1172
1175
1173 @encoding.strmethod
1176 @encoding.strmethod
1174 def __repr__(self):
1177 def __repr__(self):
1175 return b'<prefixdirmatcher path=%r, matcher=%r>' % (
1178 return b'<prefixdirmatcher path=%r, matcher=%r>' % (
1176 pycompat.bytestr(self._path),
1179 pycompat.bytestr(self._path),
1177 self._matcher,
1180 self._matcher,
1178 )
1181 )
1179
1182
1180
1183
1181 class unionmatcher(basematcher):
1184 class unionmatcher(basematcher):
1182 """A matcher that is the union of several matchers.
1185 """A matcher that is the union of several matchers.
1183
1186
1184 The non-matching-attributes (bad, traversedir) are taken from the first
1187 The non-matching-attributes (bad, traversedir) are taken from the first
1185 matcher.
1188 matcher.
1186 """
1189 """
1187
1190
1188 def __init__(self, matchers):
1191 def __init__(self, matchers):
1189 m1 = matchers[0]
1192 m1 = matchers[0]
1190 super(unionmatcher, self).__init__()
1193 super(unionmatcher, self).__init__()
1191 self.traversedir = m1.traversedir
1194 self.traversedir = m1.traversedir
1192 self._matchers = matchers
1195 self._matchers = matchers
1193
1196
1194 def matchfn(self, f):
1197 def matchfn(self, f):
1195 for match in self._matchers:
1198 for match in self._matchers:
1196 if match(f):
1199 if match(f):
1197 return True
1200 return True
1198 return False
1201 return False
1199
1202
1200 def visitdir(self, dir):
1203 def visitdir(self, dir):
1201 r = False
1204 r = False
1202 for m in self._matchers:
1205 for m in self._matchers:
1203 v = m.visitdir(dir)
1206 v = m.visitdir(dir)
1204 if v == b'all':
1207 if v == b'all':
1205 return v
1208 return v
1206 r |= v
1209 r |= v
1207 return r
1210 return r
1208
1211
1209 def visitchildrenset(self, dir):
1212 def visitchildrenset(self, dir):
1210 r = set()
1213 r = set()
1211 this = False
1214 this = False
1212 for m in self._matchers:
1215 for m in self._matchers:
1213 v = m.visitchildrenset(dir)
1216 v = m.visitchildrenset(dir)
1214 if not v:
1217 if not v:
1215 continue
1218 continue
1216 if v == b'all':
1219 if v == b'all':
1217 return v
1220 return v
1218 if this or v == b'this':
1221 if this or v == b'this':
1219 this = True
1222 this = True
1220 # don't break, we might have an 'all' in here.
1223 # don't break, we might have an 'all' in here.
1221 continue
1224 continue
1222 assert isinstance(v, set)
1225 assert isinstance(v, set)
1223 r = r.union(v)
1226 r = r.union(v)
1224 if this:
1227 if this:
1225 return b'this'
1228 return b'this'
1226 return r
1229 return r
1227
1230
1228 @encoding.strmethod
1231 @encoding.strmethod
1229 def __repr__(self):
1232 def __repr__(self):
1230 return b'<unionmatcher matchers=%r>' % self._matchers
1233 return b'<unionmatcher matchers=%r>' % self._matchers
1231
1234
1232
1235
1233 def patkind(pattern, default=None):
1236 def patkind(pattern, default=None):
1234 r"""If pattern is 'kind:pat' with a known kind, return kind.
1237 r"""If pattern is 'kind:pat' with a known kind, return kind.
1235
1238
1236 >>> patkind(br're:.*\.c$')
1239 >>> patkind(br're:.*\.c$')
1237 're'
1240 're'
1238 >>> patkind(b'glob:*.c')
1241 >>> patkind(b'glob:*.c')
1239 'glob'
1242 'glob'
1240 >>> patkind(b'relpath:test.py')
1243 >>> patkind(b'relpath:test.py')
1241 'relpath'
1244 'relpath'
1242 >>> patkind(b'main.py')
1245 >>> patkind(b'main.py')
1243 >>> patkind(b'main.py', default=b're')
1246 >>> patkind(b'main.py', default=b're')
1244 're'
1247 're'
1245 """
1248 """
1246 return _patsplit(pattern, default)[0]
1249 return _patsplit(pattern, default)[0]
1247
1250
1248
1251
1249 def _patsplit(pattern, default):
1252 def _patsplit(pattern, default):
1250 """Split a string into the optional pattern kind prefix and the actual
1253 """Split a string into the optional pattern kind prefix and the actual
1251 pattern."""
1254 pattern."""
1252 if b':' in pattern:
1255 if b':' in pattern:
1253 kind, pat = pattern.split(b':', 1)
1256 kind, pat = pattern.split(b':', 1)
1254 if kind in allpatternkinds:
1257 if kind in allpatternkinds:
1255 return kind, pat
1258 return kind, pat
1256 return default, pattern
1259 return default, pattern
1257
1260
1258
1261
1259 def _globre(pat):
1262 def _globre(pat):
1260 r"""Convert an extended glob string to a regexp string.
1263 r"""Convert an extended glob string to a regexp string.
1261
1264
1262 >>> from . import pycompat
1265 >>> from . import pycompat
1263 >>> def bprint(s):
1266 >>> def bprint(s):
1264 ... print(pycompat.sysstr(s))
1267 ... print(pycompat.sysstr(s))
1265 >>> bprint(_globre(br'?'))
1268 >>> bprint(_globre(br'?'))
1266 .
1269 .
1267 >>> bprint(_globre(br'*'))
1270 >>> bprint(_globre(br'*'))
1268 [^/]*
1271 [^/]*
1269 >>> bprint(_globre(br'**'))
1272 >>> bprint(_globre(br'**'))
1270 .*
1273 .*
1271 >>> bprint(_globre(br'**/a'))
1274 >>> bprint(_globre(br'**/a'))
1272 (?:.*/)?a
1275 (?:.*/)?a
1273 >>> bprint(_globre(br'a/**/b'))
1276 >>> bprint(_globre(br'a/**/b'))
1274 a/(?:.*/)?b
1277 a/(?:.*/)?b
1275 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
1278 >>> bprint(_globre(br'[a*?!^][^b][!c]'))
1276 [a*?!^][\^b][^c]
1279 [a*?!^][\^b][^c]
1277 >>> bprint(_globre(br'{a,b}'))
1280 >>> bprint(_globre(br'{a,b}'))
1278 (?:a|b)
1281 (?:a|b)
1279 >>> bprint(_globre(br'.\*\?'))
1282 >>> bprint(_globre(br'.\*\?'))
1280 \.\*\?
1283 \.\*\?
1281 """
1284 """
1282 i, n = 0, len(pat)
1285 i, n = 0, len(pat)
1283 res = b''
1286 res = b''
1284 group = 0
1287 group = 0
1285 escape = util.stringutil.regexbytesescapemap.get
1288 escape = util.stringutil.regexbytesescapemap.get
1286
1289
1287 def peek():
1290 def peek():
1288 return i < n and pat[i : i + 1]
1291 return i < n and pat[i : i + 1]
1289
1292
1290 while i < n:
1293 while i < n:
1291 c = pat[i : i + 1]
1294 c = pat[i : i + 1]
1292 i += 1
1295 i += 1
1293 if c not in b'*?[{},\\':
1296 if c not in b'*?[{},\\':
1294 res += escape(c, c)
1297 res += escape(c, c)
1295 elif c == b'*':
1298 elif c == b'*':
1296 if peek() == b'*':
1299 if peek() == b'*':
1297 i += 1
1300 i += 1
1298 if peek() == b'/':
1301 if peek() == b'/':
1299 i += 1
1302 i += 1
1300 res += b'(?:.*/)?'
1303 res += b'(?:.*/)?'
1301 else:
1304 else:
1302 res += b'.*'
1305 res += b'.*'
1303 else:
1306 else:
1304 res += b'[^/]*'
1307 res += b'[^/]*'
1305 elif c == b'?':
1308 elif c == b'?':
1306 res += b'.'
1309 res += b'.'
1307 elif c == b'[':
1310 elif c == b'[':
1308 j = i
1311 j = i
1309 if j < n and pat[j : j + 1] in b'!]':
1312 if j < n and pat[j : j + 1] in b'!]':
1310 j += 1
1313 j += 1
1311 while j < n and pat[j : j + 1] != b']':
1314 while j < n and pat[j : j + 1] != b']':
1312 j += 1
1315 j += 1
1313 if j >= n:
1316 if j >= n:
1314 res += b'\\['
1317 res += b'\\['
1315 else:
1318 else:
1316 stuff = pat[i:j].replace(b'\\', b'\\\\')
1319 stuff = pat[i:j].replace(b'\\', b'\\\\')
1317 i = j + 1
1320 i = j + 1
1318 if stuff[0:1] == b'!':
1321 if stuff[0:1] == b'!':
1319 stuff = b'^' + stuff[1:]
1322 stuff = b'^' + stuff[1:]
1320 elif stuff[0:1] == b'^':
1323 elif stuff[0:1] == b'^':
1321 stuff = b'\\' + stuff
1324 stuff = b'\\' + stuff
1322 res = b'%s[%s]' % (res, stuff)
1325 res = b'%s[%s]' % (res, stuff)
1323 elif c == b'{':
1326 elif c == b'{':
1324 group += 1
1327 group += 1
1325 res += b'(?:'
1328 res += b'(?:'
1326 elif c == b'}' and group:
1329 elif c == b'}' and group:
1327 res += b')'
1330 res += b')'
1328 group -= 1
1331 group -= 1
1329 elif c == b',' and group:
1332 elif c == b',' and group:
1330 res += b'|'
1333 res += b'|'
1331 elif c == b'\\':
1334 elif c == b'\\':
1332 p = peek()
1335 p = peek()
1333 if p:
1336 if p:
1334 i += 1
1337 i += 1
1335 res += escape(p, p)
1338 res += escape(p, p)
1336 else:
1339 else:
1337 res += escape(c, c)
1340 res += escape(c, c)
1338 else:
1341 else:
1339 res += escape(c, c)
1342 res += escape(c, c)
1340 return res
1343 return res
1341
1344
1342
1345
1343 FLAG_RE = util.re.compile(br'^\(\?([aiLmsux]+)\)(.*)')
1346 FLAG_RE = util.re.compile(br'^\(\?([aiLmsux]+)\)(.*)')
1344
1347
1345
1348
1346 def _regex(kind, pat, globsuffix):
1349 def _regex(kind, pat, globsuffix):
1347 """Convert a (normalized) pattern of any kind into a
1350 """Convert a (normalized) pattern of any kind into a
1348 regular expression.
1351 regular expression.
1349 globsuffix is appended to the regexp of globs."""
1352 globsuffix is appended to the regexp of globs."""
1350 if not pat and kind in (b'glob', b'relpath'):
1353 if not pat and kind in (b'glob', b'relpath'):
1351 return b''
1354 return b''
1352 if kind == b're':
1355 if kind == b're':
1353 return pat
1356 return pat
1354 if kind == b'filepath':
1357 if kind == b'filepath':
1355 raise error.ProgrammingError(
1358 raise error.ProgrammingError(
1356 "'filepath:' patterns should not be converted to a regex"
1359 "'filepath:' patterns should not be converted to a regex"
1357 )
1360 )
1358 if kind in (b'path', b'relpath'):
1361 if kind in (b'path', b'relpath'):
1359 if pat == b'.':
1362 if pat == b'.':
1360 return b''
1363 return b''
1361 return util.stringutil.reescape(pat) + b'(?:/|$)'
1364 return util.stringutil.reescape(pat) + b'(?:/|$)'
1362 if kind == b'rootfilesin':
1365 if kind == b'rootfilesin':
1363 if pat == b'.':
1366 if pat == b'.':
1364 escaped = b''
1367 escaped = b''
1365 else:
1368 else:
1366 # Pattern is a directory name.
1369 # Pattern is a directory name.
1367 escaped = util.stringutil.reescape(pat) + b'/'
1370 escaped = util.stringutil.reescape(pat) + b'/'
1368 # Anything after the pattern must be a non-directory.
1371 # Anything after the pattern must be a non-directory.
1369 return escaped + b'[^/]+$'
1372 return escaped + b'[^/]+$'
1370 if kind == b'relglob':
1373 if kind == b'relglob':
1371 globre = _globre(pat)
1374 globre = _globre(pat)
1372 if globre.startswith(b'[^/]*'):
1375 if globre.startswith(b'[^/]*'):
1373 # When pat has the form *XYZ (common), make the returned regex more
1376 # When pat has the form *XYZ (common), make the returned regex more
1374 # legible by returning the regex for **XYZ instead of **/*XYZ.
1377 # legible by returning the regex for **XYZ instead of **/*XYZ.
1375 return b'.*' + globre[len(b'[^/]*') :] + globsuffix
1378 return b'.*' + globre[len(b'[^/]*') :] + globsuffix
1376 return b'(?:|.*/)' + globre + globsuffix
1379 return b'(?:|.*/)' + globre + globsuffix
1377 if kind == b'relre':
1380 if kind == b'relre':
1378 flag = None
1381 flag = None
1379 m = FLAG_RE.match(pat)
1382 m = FLAG_RE.match(pat)
1380 if m:
1383 if m:
1381 flag, pat = m.groups()
1384 flag, pat = m.groups()
1382 if not pat.startswith(b'^'):
1385 if not pat.startswith(b'^'):
1383 pat = b'.*' + pat
1386 pat = b'.*' + pat
1384 if flag is not None:
1387 if flag is not None:
1385 pat = br'(?%s:%s)' % (flag, pat)
1388 pat = br'(?%s:%s)' % (flag, pat)
1386 return pat
1389 return pat
1387 if kind in (b'glob', b'rootglob'):
1390 if kind in (b'glob', b'rootglob'):
1388 return _globre(pat) + globsuffix
1391 return _globre(pat) + globsuffix
1389 raise error.ProgrammingError(b'not a regex pattern: %s:%s' % (kind, pat))
1392 raise error.ProgrammingError(b'not a regex pattern: %s:%s' % (kind, pat))
1390
1393
1391
1394
1392 def _buildmatch(kindpats, globsuffix, root):
1395 def _buildmatch(kindpats, globsuffix, root):
1393 """Return regexp string and a matcher function for kindpats.
1396 """Return regexp string and a matcher function for kindpats.
1394 globsuffix is appended to the regexp of globs."""
1397 globsuffix is appended to the regexp of globs."""
1395 matchfuncs = []
1398 matchfuncs = []
1396
1399
1397 subincludes, kindpats = _expandsubinclude(kindpats, root)
1400 subincludes, kindpats = _expandsubinclude(kindpats, root)
1398 if subincludes:
1401 if subincludes:
1399 submatchers = {}
1402 submatchers = {}
1400
1403
1401 def matchsubinclude(f):
1404 def matchsubinclude(f):
1402 for prefix, matcherargs in subincludes:
1405 for prefix, matcherargs in subincludes:
1403 if f.startswith(prefix):
1406 if f.startswith(prefix):
1404 mf = submatchers.get(prefix)
1407 mf = submatchers.get(prefix)
1405 if mf is None:
1408 if mf is None:
1406 mf = match(*matcherargs)
1409 mf = match(*matcherargs)
1407 submatchers[prefix] = mf
1410 submatchers[prefix] = mf
1408
1411
1409 if mf(f[len(prefix) :]):
1412 if mf(f[len(prefix) :]):
1410 return True
1413 return True
1411 return False
1414 return False
1412
1415
1413 matchfuncs.append(matchsubinclude)
1416 matchfuncs.append(matchsubinclude)
1414
1417
1415 regex = b''
1418 regex = b''
1416 if kindpats:
1419 if kindpats:
1417 if all(k == b'rootfilesin' for k, p, s in kindpats):
1420 if all(k == b'rootfilesin' for k, p, s in kindpats):
1418 dirs = {p for k, p, s in kindpats}
1421 dirs = {p for k, p, s in kindpats}
1419
1422
1420 def mf(f):
1423 def mf(f):
1421 i = f.rfind(b'/')
1424 i = f.rfind(b'/')
1422 if i >= 0:
1425 if i >= 0:
1423 dir = f[:i]
1426 dir = f[:i]
1424 else:
1427 else:
1425 dir = b'.'
1428 dir = b'.'
1426 return dir in dirs
1429 return dir in dirs
1427
1430
1428 regex = b'rootfilesin: %s' % stringutil.pprint(list(sorted(dirs)))
1431 regex = b'rootfilesin: %s' % stringutil.pprint(list(sorted(dirs)))
1429 matchfuncs.append(mf)
1432 matchfuncs.append(mf)
1430 else:
1433 else:
1431 regex, mf = _buildregexmatch(kindpats, globsuffix)
1434 regex, mf = _buildregexmatch(kindpats, globsuffix)
1432 matchfuncs.append(mf)
1435 matchfuncs.append(mf)
1433
1436
1434 if len(matchfuncs) == 1:
1437 if len(matchfuncs) == 1:
1435 return regex, matchfuncs[0]
1438 return regex, matchfuncs[0]
1436 else:
1439 else:
1437 return regex, lambda f: any(mf(f) for mf in matchfuncs)
1440 return regex, lambda f: any(mf(f) for mf in matchfuncs)
1438
1441
1439
1442
1440 MAX_RE_SIZE = 20000
1443 MAX_RE_SIZE = 20000
1441
1444
1442
1445
1443 def _joinregexes(regexps):
1446 def _joinregexes(regexps):
1444 """gather multiple regular expressions into a single one"""
1447 """gather multiple regular expressions into a single one"""
1445 return b'|'.join(regexps)
1448 return b'|'.join(regexps)
1446
1449
1447
1450
1448 def _buildregexmatch(kindpats, globsuffix):
1451 def _buildregexmatch(kindpats, globsuffix):
1449 """Build a match function from a list of kinds and kindpats,
1452 """Build a match function from a list of kinds and kindpats,
1450 return regexp string and a matcher function.
1453 return regexp string and a matcher function.
1451
1454
1452 Test too large input
1455 Test too large input
1453 >>> _buildregexmatch([
1456 >>> _buildregexmatch([
1454 ... (b'relglob', b'?' * MAX_RE_SIZE, b'')
1457 ... (b'relglob', b'?' * MAX_RE_SIZE, b'')
1455 ... ], b'$')
1458 ... ], b'$')
1456 Traceback (most recent call last):
1459 Traceback (most recent call last):
1457 ...
1460 ...
1458 Abort: matcher pattern is too long (20009 bytes)
1461 Abort: matcher pattern is too long (20009 bytes)
1459 """
1462 """
1460 try:
1463 try:
1461 allgroups = []
1464 allgroups = []
1462 regexps = []
1465 regexps = []
1463 exact = set()
1466 exact = set()
1464 for (kind, pattern, _source) in kindpats:
1467 for kind, pattern, _source in kindpats:
1465 if kind == b'filepath':
1468 if kind == b'filepath':
1466 exact.add(pattern)
1469 exact.add(pattern)
1467 continue
1470 continue
1468 regexps.append(_regex(kind, pattern, globsuffix))
1471 regexps.append(_regex(kind, pattern, globsuffix))
1469
1472
1470 fullregexp = _joinregexes(regexps)
1473 fullregexp = _joinregexes(regexps)
1471
1474
1472 startidx = 0
1475 startidx = 0
1473 groupsize = 0
1476 groupsize = 0
1474 for idx, r in enumerate(regexps):
1477 for idx, r in enumerate(regexps):
1475 piecesize = len(r)
1478 piecesize = len(r)
1476 if piecesize > MAX_RE_SIZE:
1479 if piecesize > MAX_RE_SIZE:
1477 msg = _(b"matcher pattern is too long (%d bytes)") % piecesize
1480 msg = _(b"matcher pattern is too long (%d bytes)") % piecesize
1478 raise error.Abort(msg)
1481 raise error.Abort(msg)
1479 elif (groupsize + piecesize) > MAX_RE_SIZE:
1482 elif (groupsize + piecesize) > MAX_RE_SIZE:
1480 group = regexps[startidx:idx]
1483 group = regexps[startidx:idx]
1481 allgroups.append(_joinregexes(group))
1484 allgroups.append(_joinregexes(group))
1482 startidx = idx
1485 startidx = idx
1483 groupsize = 0
1486 groupsize = 0
1484 groupsize += piecesize + 1
1487 groupsize += piecesize + 1
1485
1488
1486 if startidx == 0:
1489 if startidx == 0:
1487 matcher = _rematcher(fullregexp)
1490 matcher = _rematcher(fullregexp)
1488 func = lambda s: bool(matcher(s))
1491 func = lambda s: bool(matcher(s))
1489 else:
1492 else:
1490 group = regexps[startidx:]
1493 group = regexps[startidx:]
1491 allgroups.append(_joinregexes(group))
1494 allgroups.append(_joinregexes(group))
1492 allmatchers = [_rematcher(g) for g in allgroups]
1495 allmatchers = [_rematcher(g) for g in allgroups]
1493 func = lambda s: any(m(s) for m in allmatchers)
1496 func = lambda s: any(m(s) for m in allmatchers)
1494
1497
1495 actualfunc = func
1498 actualfunc = func
1496 if exact:
1499 if exact:
1497 # An empty regex will always match, so only call the regex if
1500 # An empty regex will always match, so only call the regex if
1498 # there were any actual patterns to match.
1501 # there were any actual patterns to match.
1499 if not regexps:
1502 if not regexps:
1500 actualfunc = lambda s: s in exact
1503 actualfunc = lambda s: s in exact
1501 else:
1504 else:
1502 actualfunc = lambda s: s in exact or func(s)
1505 actualfunc = lambda s: s in exact or func(s)
1503 return fullregexp, actualfunc
1506 return fullregexp, actualfunc
1504 except re.error:
1507 except re.error:
1505 for k, p, s in kindpats:
1508 for k, p, s in kindpats:
1506 if k == b'filepath':
1509 if k == b'filepath':
1507 continue
1510 continue
1508 try:
1511 try:
1509 _rematcher(_regex(k, p, globsuffix))
1512 _rematcher(_regex(k, p, globsuffix))
1510 except re.error:
1513 except re.error:
1511 if s:
1514 if s:
1512 raise error.Abort(
1515 raise error.Abort(
1513 _(b"%s: invalid pattern (%s): %s") % (s, k, p)
1516 _(b"%s: invalid pattern (%s): %s") % (s, k, p)
1514 )
1517 )
1515 else:
1518 else:
1516 raise error.Abort(_(b"invalid pattern (%s): %s") % (k, p))
1519 raise error.Abort(_(b"invalid pattern (%s): %s") % (k, p))
1517 raise error.Abort(_(b"invalid pattern"))
1520 raise error.Abort(_(b"invalid pattern"))
1518
1521
1519
1522
1520 def _patternrootsanddirs(kindpats):
1523 def _patternrootsanddirs(kindpats):
1521 """Returns roots and directories corresponding to each pattern.
1524 """Returns roots and directories corresponding to each pattern.
1522
1525
1523 This calculates the roots and directories exactly matching the patterns and
1526 This calculates the roots and directories exactly matching the patterns and
1524 returns a tuple of (roots, dirs) for each. It does not return other
1527 returns a tuple of (roots, dirs) for each. It does not return other
1525 directories which may also need to be considered, like the parent
1528 directories which may also need to be considered, like the parent
1526 directories.
1529 directories.
1527 """
1530 """
1528 r = []
1531 r = []
1529 d = []
1532 d = []
1530 for kind, pat, source in kindpats:
1533 for kind, pat, source in kindpats:
1531 if kind in (b'glob', b'rootglob'): # find the non-glob prefix
1534 if kind in (b'glob', b'rootglob'): # find the non-glob prefix
1532 root = []
1535 root = []
1533 for p in pat.split(b'/'):
1536 for p in pat.split(b'/'):
1534 if b'[' in p or b'{' in p or b'*' in p or b'?' in p:
1537 if b'[' in p or b'{' in p or b'*' in p or b'?' in p:
1535 break
1538 break
1536 root.append(p)
1539 root.append(p)
1537 r.append(b'/'.join(root))
1540 r.append(b'/'.join(root))
1538 elif kind in (b'relpath', b'path', b'filepath'):
1541 elif kind in (b'relpath', b'path', b'filepath'):
1539 if pat == b'.':
1542 if pat == b'.':
1540 pat = b''
1543 pat = b''
1541 r.append(pat)
1544 r.append(pat)
1542 elif kind in (b'rootfilesin',):
1545 elif kind in (b'rootfilesin',):
1543 if pat == b'.':
1546 if pat == b'.':
1544 pat = b''
1547 pat = b''
1545 d.append(pat)
1548 d.append(pat)
1546 else: # relglob, re, relre
1549 else: # relglob, re, relre
1547 r.append(b'')
1550 r.append(b'')
1548 return r, d
1551 return r, d
1549
1552
1550
1553
1551 def _roots(kindpats):
1554 def _roots(kindpats):
1552 '''Returns root directories to match recursively from the given patterns.'''
1555 '''Returns root directories to match recursively from the given patterns.'''
1553 roots, dirs = _patternrootsanddirs(kindpats)
1556 roots, dirs = _patternrootsanddirs(kindpats)
1554 return roots
1557 return roots
1555
1558
1556
1559
1557 def _rootsdirsandparents(kindpats):
1560 def _rootsdirsandparents(kindpats):
1558 """Returns roots and exact directories from patterns.
1561 """Returns roots and exact directories from patterns.
1559
1562
1560 `roots` are directories to match recursively, `dirs` should
1563 `roots` are directories to match recursively, `dirs` should
1561 be matched non-recursively, and `parents` are the implicitly required
1564 be matched non-recursively, and `parents` are the implicitly required
1562 directories to walk to items in either roots or dirs.
1565 directories to walk to items in either roots or dirs.
1563
1566
1564 Returns a tuple of (roots, dirs, parents).
1567 Returns a tuple of (roots, dirs, parents).
1565
1568
1566 >>> r = _rootsdirsandparents(
1569 >>> r = _rootsdirsandparents(
1567 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
1570 ... [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
1568 ... (b'glob', b'g*', b'')])
1571 ... (b'glob', b'g*', b'')])
1569 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1572 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1570 (['g/h', 'g/h', ''], []) ['', 'g']
1573 (['g/h', 'g/h', ''], []) ['', 'g']
1571 >>> r = _rootsdirsandparents(
1574 >>> r = _rootsdirsandparents(
1572 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
1575 ... [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
1573 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1576 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1574 ([], ['g/h', '']) ['', 'g']
1577 ([], ['g/h', '']) ['', 'g']
1575 >>> r = _rootsdirsandparents(
1578 >>> r = _rootsdirsandparents(
1576 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
1579 ... [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
1577 ... (b'path', b'', b'')])
1580 ... (b'path', b'', b'')])
1578 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1581 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1579 (['r', 'p/p', ''], []) ['', 'p']
1582 (['r', 'p/p', ''], []) ['', 'p']
1580 >>> r = _rootsdirsandparents(
1583 >>> r = _rootsdirsandparents(
1581 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
1584 ... [(b'relglob', b'rg*', b''), (b're', b're/', b''),
1582 ... (b'relre', b'rr', b'')])
1585 ... (b'relre', b'rr', b'')])
1583 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1586 >>> print(r[0:2], sorted(r[2])) # the set has an unstable output
1584 (['', '', ''], []) ['']
1587 (['', '', ''], []) ['']
1585 """
1588 """
1586 r, d = _patternrootsanddirs(kindpats)
1589 r, d = _patternrootsanddirs(kindpats)
1587
1590
1588 p = set()
1591 p = set()
1589 # Add the parents as non-recursive/exact directories, since they must be
1592 # Add the parents as non-recursive/exact directories, since they must be
1590 # scanned to get to either the roots or the other exact directories.
1593 # scanned to get to either the roots or the other exact directories.
1591 p.update(pathutil.dirs(d))
1594 p.update(pathutil.dirs(d))
1592 p.update(pathutil.dirs(r))
1595 p.update(pathutil.dirs(r))
1593
1596
1594 # FIXME: all uses of this function convert these to sets, do so before
1597 # FIXME: all uses of this function convert these to sets, do so before
1595 # returning.
1598 # returning.
1596 # FIXME: all uses of this function do not need anything in 'roots' and
1599 # FIXME: all uses of this function do not need anything in 'roots' and
1597 # 'dirs' to also be in 'parents', consider removing them before returning.
1600 # 'dirs' to also be in 'parents', consider removing them before returning.
1598 return r, d, p
1601 return r, d, p
1599
1602
1600
1603
1601 def _explicitfiles(kindpats):
1604 def _explicitfiles(kindpats):
1602 """Returns the potential explicit filenames from the patterns.
1605 """Returns the potential explicit filenames from the patterns.
1603
1606
1604 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
1607 >>> _explicitfiles([(b'path', b'foo/bar', b'')])
1605 ['foo/bar']
1608 ['foo/bar']
1606 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
1609 >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
1607 []
1610 []
1608 """
1611 """
1609 # Keep only the pattern kinds where one can specify filenames (vs only
1612 # Keep only the pattern kinds where one can specify filenames (vs only
1610 # directory names).
1613 # directory names).
1611 filable = [kp for kp in kindpats if kp[0] not in (b'rootfilesin',)]
1614 filable = [kp for kp in kindpats if kp[0] not in (b'rootfilesin',)]
1612 return _roots(filable)
1615 return _roots(filable)
1613
1616
1614
1617
1615 def _prefix(kindpats):
1618 def _prefix(kindpats):
1616 '''Whether all the patterns match a prefix (i.e. recursively)'''
1619 '''Whether all the patterns match a prefix (i.e. recursively)'''
1617 for kind, pat, source in kindpats:
1620 for kind, pat, source in kindpats:
1618 if kind not in (b'path', b'relpath'):
1621 if kind not in (b'path', b'relpath'):
1619 return False
1622 return False
1620 return True
1623 return True
1621
1624
1622
1625
1623 _commentre = None
1626 _commentre = None
1624
1627
1625
1628
1626 def readpatternfile(filepath, warn, sourceinfo=False):
1629 def readpatternfile(filepath, warn, sourceinfo=False):
1627 """parse a pattern file, returning a list of
1630 """parse a pattern file, returning a list of
1628 patterns. These patterns should be given to compile()
1631 patterns. These patterns should be given to compile()
1629 to be validated and converted into a match function.
1632 to be validated and converted into a match function.
1630
1633
1631 trailing white space is dropped.
1634 trailing white space is dropped.
1632 the escape character is backslash.
1635 the escape character is backslash.
1633 comments start with #.
1636 comments start with #.
1634 empty lines are skipped.
1637 empty lines are skipped.
1635
1638
1636 lines can be of the following formats:
1639 lines can be of the following formats:
1637
1640
1638 syntax: regexp # defaults following lines to non-rooted regexps
1641 syntax: regexp # defaults following lines to non-rooted regexps
1639 syntax: glob # defaults following lines to non-rooted globs
1642 syntax: glob # defaults following lines to non-rooted globs
1640 re:pattern # non-rooted regular expression
1643 re:pattern # non-rooted regular expression
1641 glob:pattern # non-rooted glob
1644 glob:pattern # non-rooted glob
1642 rootglob:pat # rooted glob (same root as ^ in regexps)
1645 rootglob:pat # rooted glob (same root as ^ in regexps)
1643 pattern # pattern of the current default type
1646 pattern # pattern of the current default type
1644
1647
1645 if sourceinfo is set, returns a list of tuples:
1648 if sourceinfo is set, returns a list of tuples:
1646 (pattern, lineno, originalline).
1649 (pattern, lineno, originalline).
1647 This is useful to debug ignore patterns.
1650 This is useful to debug ignore patterns.
1648 """
1651 """
1649
1652
1650 syntaxes = {
1653 syntaxes = {
1651 b're': b'relre:',
1654 b're': b'relre:',
1652 b'regexp': b'relre:',
1655 b'regexp': b'relre:',
1653 b'glob': b'relglob:',
1656 b'glob': b'relglob:',
1654 b'rootglob': b'rootglob:',
1657 b'rootglob': b'rootglob:',
1655 b'include': b'include',
1658 b'include': b'include',
1656 b'subinclude': b'subinclude',
1659 b'subinclude': b'subinclude',
1657 }
1660 }
1658 syntax = b'relre:'
1661 syntax = b'relre:'
1659 patterns = []
1662 patterns = []
1660
1663
1661 fp = open(filepath, b'rb')
1664 fp = open(filepath, b'rb')
1662 for lineno, line in enumerate(fp, start=1):
1665 for lineno, line in enumerate(fp, start=1):
1663 if b"#" in line:
1666 if b"#" in line:
1664 global _commentre
1667 global _commentre
1665 if not _commentre:
1668 if not _commentre:
1666 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1669 _commentre = util.re.compile(br'((?:^|[^\\])(?:\\\\)*)#.*')
1667 # remove comments prefixed by an even number of escapes
1670 # remove comments prefixed by an even number of escapes
1668 m = _commentre.search(line)
1671 m = _commentre.search(line)
1669 if m:
1672 if m:
1670 line = line[: m.end(1)]
1673 line = line[: m.end(1)]
1671 # fixup properly escaped comments that survived the above
1674 # fixup properly escaped comments that survived the above
1672 line = line.replace(b"\\#", b"#")
1675 line = line.replace(b"\\#", b"#")
1673 line = line.rstrip()
1676 line = line.rstrip()
1674 if not line:
1677 if not line:
1675 continue
1678 continue
1676
1679
1677 if line.startswith(b'syntax:'):
1680 if line.startswith(b'syntax:'):
1678 s = line[7:].strip()
1681 s = line[7:].strip()
1679 try:
1682 try:
1680 syntax = syntaxes[s]
1683 syntax = syntaxes[s]
1681 except KeyError:
1684 except KeyError:
1682 if warn:
1685 if warn:
1683 warn(
1686 warn(
1684 _(b"%s: ignoring invalid syntax '%s'\n") % (filepath, s)
1687 _(b"%s: ignoring invalid syntax '%s'\n") % (filepath, s)
1685 )
1688 )
1686 continue
1689 continue
1687
1690
1688 linesyntax = syntax
1691 linesyntax = syntax
1689 for s, rels in syntaxes.items():
1692 for s, rels in syntaxes.items():
1690 if line.startswith(rels):
1693 if line.startswith(rels):
1691 linesyntax = rels
1694 linesyntax = rels
1692 line = line[len(rels) :]
1695 line = line[len(rels) :]
1693 break
1696 break
1694 elif line.startswith(s + b':'):
1697 elif line.startswith(s + b':'):
1695 linesyntax = rels
1698 linesyntax = rels
1696 line = line[len(s) + 1 :]
1699 line = line[len(s) + 1 :]
1697 break
1700 break
1698 if sourceinfo:
1701 if sourceinfo:
1699 patterns.append((linesyntax + line, lineno, line))
1702 patterns.append((linesyntax + line, lineno, line))
1700 else:
1703 else:
1701 patterns.append(linesyntax + line)
1704 patterns.append(linesyntax + line)
1702 fp.close()
1705 fp.close()
1703 return patterns
1706 return patterns
@@ -1,1007 +1,1009 b''
1 import unittest
1 import unittest
2
2
3 import silenttestrunner
3 import silenttestrunner
4
4
5 from mercurial import (
5 from mercurial import (
6 match as matchmod,
6 match as matchmod,
7 util,
7 util,
8 )
8 )
9
9
10
10
11 noop_auditor = lambda name: None
11 noop_auditor = lambda name: None
12
12
13
13
14 class BaseMatcherTests(unittest.TestCase):
14 class BaseMatcherTests(unittest.TestCase):
15 def testVisitdir(self):
15 def testVisitdir(self):
16 m = matchmod.basematcher()
16 m = matchmod.basematcher()
17 self.assertTrue(m.visitdir(b''))
17 self.assertTrue(m.visitdir(b''))
18 self.assertTrue(m.visitdir(b'dir'))
18 self.assertTrue(m.visitdir(b'dir'))
19
19
20 def testVisitchildrenset(self):
20 def testVisitchildrenset(self):
21 m = matchmod.basematcher()
21 m = matchmod.basematcher()
22 self.assertEqual(m.visitchildrenset(b''), b'this')
22 self.assertEqual(m.visitchildrenset(b''), b'this')
23 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
23 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
24
24
25
25
26 class AlwaysMatcherTests(unittest.TestCase):
26 class AlwaysMatcherTests(unittest.TestCase):
27 def testVisitdir(self):
27 def testVisitdir(self):
28 m = matchmod.alwaysmatcher()
28 m = matchmod.alwaysmatcher()
29 self.assertEqual(m.visitdir(b''), b'all')
29 self.assertEqual(m.visitdir(b''), b'all')
30 self.assertEqual(m.visitdir(b'dir'), b'all')
30 self.assertEqual(m.visitdir(b'dir'), b'all')
31
31
32 def testVisitchildrenset(self):
32 def testVisitchildrenset(self):
33 m = matchmod.alwaysmatcher()
33 m = matchmod.alwaysmatcher()
34 self.assertEqual(m.visitchildrenset(b''), b'all')
34 self.assertEqual(m.visitchildrenset(b''), b'all')
35 self.assertEqual(m.visitchildrenset(b'dir'), b'all')
35 self.assertEqual(m.visitchildrenset(b'dir'), b'all')
36
36
37
37
38 class NeverMatcherTests(unittest.TestCase):
38 class NeverMatcherTests(unittest.TestCase):
39 def testVisitdir(self):
39 def testVisitdir(self):
40 m = matchmod.nevermatcher()
40 m = matchmod.nevermatcher()
41 self.assertFalse(m.visitdir(b''))
41 self.assertFalse(m.visitdir(b''))
42 self.assertFalse(m.visitdir(b'dir'))
42 self.assertFalse(m.visitdir(b'dir'))
43
43
44 def testVisitchildrenset(self):
44 def testVisitchildrenset(self):
45 m = matchmod.nevermatcher()
45 m = matchmod.nevermatcher()
46 self.assertEqual(m.visitchildrenset(b''), set())
46 self.assertEqual(m.visitchildrenset(b''), set())
47 self.assertEqual(m.visitchildrenset(b'dir'), set())
47 self.assertEqual(m.visitchildrenset(b'dir'), set())
48
48
49
49
50 class PredicateMatcherTests(unittest.TestCase):
50 class PredicateMatcherTests(unittest.TestCase):
51 # predicatematcher does not currently define either of these methods, so
51 # predicatematcher does not currently define either of these methods, so
52 # this is equivalent to BaseMatcherTests.
52 # this is equivalent to BaseMatcherTests.
53
53
54 def testVisitdir(self):
54 def testVisitdir(self):
55 m = matchmod.predicatematcher(lambda *a: False)
55 m = matchmod.predicatematcher(lambda *a: False)
56 self.assertTrue(m.visitdir(b''))
56 self.assertTrue(m.visitdir(b''))
57 self.assertTrue(m.visitdir(b'dir'))
57 self.assertTrue(m.visitdir(b'dir'))
58
58
59 def testVisitchildrenset(self):
59 def testVisitchildrenset(self):
60 m = matchmod.predicatematcher(lambda *a: False)
60 m = matchmod.predicatematcher(lambda *a: False)
61 self.assertEqual(m.visitchildrenset(b''), b'this')
61 self.assertEqual(m.visitchildrenset(b''), b'this')
62 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
62 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
63
63
64
64
65 class PatternMatcherTests(unittest.TestCase):
65 class PatternMatcherTests(unittest.TestCase):
66 def testVisitdirPrefix(self):
66 def testVisitdirPrefix(self):
67 m = matchmod.match(
67 m = matchmod.match(
68 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
68 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
69 )
69 )
70 assert isinstance(m, matchmod.patternmatcher)
70 assert isinstance(m, matchmod.patternmatcher)
71 self.assertTrue(m.visitdir(b''))
71 self.assertTrue(m.visitdir(b''))
72 self.assertTrue(m.visitdir(b'dir'))
72 self.assertTrue(m.visitdir(b'dir'))
73 self.assertEqual(m.visitdir(b'dir/subdir'), b'all')
73 self.assertEqual(m.visitdir(b'dir/subdir'), b'all')
74 # OPT: This should probably be 'all' if its parent is?
74 # OPT: This should probably be 'all' if its parent is?
75 self.assertTrue(m.visitdir(b'dir/subdir/x'))
75 self.assertTrue(m.visitdir(b'dir/subdir/x'))
76 self.assertFalse(m.visitdir(b'folder'))
76 self.assertFalse(m.visitdir(b'folder'))
77
77
78 def testVisitchildrensetPrefix(self):
78 def testVisitchildrensetPrefix(self):
79 m = matchmod.match(
79 m = matchmod.match(
80 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
80 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
81 )
81 )
82 assert isinstance(m, matchmod.patternmatcher)
82 assert isinstance(m, matchmod.patternmatcher)
83 self.assertEqual(m.visitchildrenset(b''), b'this')
83 self.assertEqual(m.visitchildrenset(b''), b'this')
84 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
84 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
85 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'all')
85 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'all')
86 # OPT: This should probably be 'all' if its parent is?
86 # OPT: This should probably be 'all' if its parent is?
87 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
87 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
88 self.assertEqual(m.visitchildrenset(b'folder'), set())
88 self.assertEqual(m.visitchildrenset(b'folder'), set())
89
89
90 def testVisitdirRootfilesin(self):
90 def testVisitdirRootfilesin(self):
91 m = matchmod.match(
91 m = matchmod.match(
92 util.localpath(b'/repo'),
92 util.localpath(b'/repo'),
93 b'',
93 b'',
94 patterns=[b'rootfilesin:dir/subdir'],
94 patterns=[b'rootfilesin:dir/subdir'],
95 )
95 )
96 assert isinstance(m, matchmod.patternmatcher)
96 assert isinstance(m, matchmod.patternmatcher)
97 self.assertFalse(m.visitdir(b'dir/subdir/x'))
97 # OPT: we shouldn't visit [x] as a directory,
98 # but we should still visit it as a file.
99 # Unfortunately, `visitdir` is used for both.
100 self.assertTrue(m.visitdir(b'dir/subdir/x'))
98 self.assertFalse(m.visitdir(b'folder'))
101 self.assertFalse(m.visitdir(b'folder'))
99 # FIXME: These should probably be True.
102 self.assertTrue(m.visitdir(b''))
100 self.assertFalse(m.visitdir(b''))
103 self.assertTrue(m.visitdir(b'dir'))
101 self.assertFalse(m.visitdir(b'dir'))
104 self.assertTrue(m.visitdir(b'dir/subdir'))
102 self.assertFalse(m.visitdir(b'dir/subdir'))
103
105
104 def testVisitchildrensetRootfilesin(self):
106 def testVisitchildrensetRootfilesin(self):
105 m = matchmod.match(
107 m = matchmod.match(
106 util.localpath(b'/repo'),
108 util.localpath(b'/repo'),
107 b'',
109 b'',
108 patterns=[b'rootfilesin:dir/subdir'],
110 patterns=[b'rootfilesin:dir/subdir'],
109 )
111 )
110 assert isinstance(m, matchmod.patternmatcher)
112 assert isinstance(m, matchmod.patternmatcher)
111 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
113 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
112 self.assertEqual(m.visitchildrenset(b'folder'), set())
114 self.assertEqual(m.visitchildrenset(b'folder'), set())
113 # FIXME: These should probably be {'dir'}, {'subdir'} and 'this',
115 # OPT: These should probably be {'dir'}, {'subdir'} and 'this',
114 # respectively, or at least 'this' for all three.
116 # respectively
115 self.assertEqual(m.visitchildrenset(b''), set())
117 self.assertEqual(m.visitchildrenset(b''), b'this')
116 self.assertEqual(m.visitchildrenset(b'dir'), set())
118 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
117 self.assertEqual(m.visitchildrenset(b'dir/subdir'), set())
119 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
118
120
119 def testVisitdirGlob(self):
121 def testVisitdirGlob(self):
120 m = matchmod.match(
122 m = matchmod.match(
121 util.localpath(b'/repo'), b'', patterns=[b'glob:dir/z*']
123 util.localpath(b'/repo'), b'', patterns=[b'glob:dir/z*']
122 )
124 )
123 assert isinstance(m, matchmod.patternmatcher)
125 assert isinstance(m, matchmod.patternmatcher)
124 self.assertTrue(m.visitdir(b''))
126 self.assertTrue(m.visitdir(b''))
125 self.assertTrue(m.visitdir(b'dir'))
127 self.assertTrue(m.visitdir(b'dir'))
126 self.assertFalse(m.visitdir(b'folder'))
128 self.assertFalse(m.visitdir(b'folder'))
127 # OPT: these should probably be False.
129 # OPT: these should probably be False.
128 self.assertTrue(m.visitdir(b'dir/subdir'))
130 self.assertTrue(m.visitdir(b'dir/subdir'))
129 self.assertTrue(m.visitdir(b'dir/subdir/x'))
131 self.assertTrue(m.visitdir(b'dir/subdir/x'))
130
132
131 def testVisitchildrensetGlob(self):
133 def testVisitchildrensetGlob(self):
132 m = matchmod.match(
134 m = matchmod.match(
133 util.localpath(b'/repo'), b'', patterns=[b'glob:dir/z*']
135 util.localpath(b'/repo'), b'', patterns=[b'glob:dir/z*']
134 )
136 )
135 assert isinstance(m, matchmod.patternmatcher)
137 assert isinstance(m, matchmod.patternmatcher)
136 self.assertEqual(m.visitchildrenset(b''), b'this')
138 self.assertEqual(m.visitchildrenset(b''), b'this')
137 self.assertEqual(m.visitchildrenset(b'folder'), set())
139 self.assertEqual(m.visitchildrenset(b'folder'), set())
138 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
140 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
139 # OPT: these should probably be set().
141 # OPT: these should probably be set().
140 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
142 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
141 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
143 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
142
144
143 def testVisitdirFilepath(self):
145 def testVisitdirFilepath(self):
144 m = matchmod.match(
146 m = matchmod.match(
145 util.localpath(b'/repo'), b'', patterns=[b'filepath:dir/z']
147 util.localpath(b'/repo'), b'', patterns=[b'filepath:dir/z']
146 )
148 )
147 assert isinstance(m, matchmod.patternmatcher)
149 assert isinstance(m, matchmod.patternmatcher)
148 self.assertTrue(m.visitdir(b''))
150 self.assertTrue(m.visitdir(b''))
149 self.assertTrue(m.visitdir(b'dir'))
151 self.assertTrue(m.visitdir(b'dir'))
150 self.assertFalse(m.visitdir(b'folder'))
152 self.assertFalse(m.visitdir(b'folder'))
151 self.assertFalse(m.visitdir(b'dir/subdir'))
153 self.assertFalse(m.visitdir(b'dir/subdir'))
152 self.assertFalse(m.visitdir(b'dir/subdir/x'))
154 self.assertFalse(m.visitdir(b'dir/subdir/x'))
153
155
154 def testVisitchildrensetFilepath(self):
156 def testVisitchildrensetFilepath(self):
155 m = matchmod.match(
157 m = matchmod.match(
156 util.localpath(b'/repo'), b'', patterns=[b'filepath:dir/z']
158 util.localpath(b'/repo'), b'', patterns=[b'filepath:dir/z']
157 )
159 )
158 assert isinstance(m, matchmod.patternmatcher)
160 assert isinstance(m, matchmod.patternmatcher)
159 self.assertEqual(m.visitchildrenset(b''), b'this')
161 self.assertEqual(m.visitchildrenset(b''), b'this')
160 self.assertEqual(m.visitchildrenset(b'folder'), set())
162 self.assertEqual(m.visitchildrenset(b'folder'), set())
161 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
163 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
162 self.assertEqual(m.visitchildrenset(b'dir/subdir'), set())
164 self.assertEqual(m.visitchildrenset(b'dir/subdir'), set())
163 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
165 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
164
166
165
167
166 class IncludeMatcherTests(unittest.TestCase):
168 class IncludeMatcherTests(unittest.TestCase):
167 def testVisitdirPrefix(self):
169 def testVisitdirPrefix(self):
168 m = matchmod.match(
170 m = matchmod.match(
169 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
171 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
170 )
172 )
171 assert isinstance(m, matchmod.includematcher)
173 assert isinstance(m, matchmod.includematcher)
172 self.assertTrue(m.visitdir(b''))
174 self.assertTrue(m.visitdir(b''))
173 self.assertTrue(m.visitdir(b'dir'))
175 self.assertTrue(m.visitdir(b'dir'))
174 self.assertEqual(m.visitdir(b'dir/subdir'), b'all')
176 self.assertEqual(m.visitdir(b'dir/subdir'), b'all')
175 # OPT: This should probably be 'all' if its parent is?
177 # OPT: This should probably be 'all' if its parent is?
176 self.assertTrue(m.visitdir(b'dir/subdir/x'))
178 self.assertTrue(m.visitdir(b'dir/subdir/x'))
177 self.assertFalse(m.visitdir(b'folder'))
179 self.assertFalse(m.visitdir(b'folder'))
178
180
179 def testVisitchildrensetPrefix(self):
181 def testVisitchildrensetPrefix(self):
180 m = matchmod.match(
182 m = matchmod.match(
181 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
183 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
182 )
184 )
183 assert isinstance(m, matchmod.includematcher)
185 assert isinstance(m, matchmod.includematcher)
184 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
186 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
185 self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
187 self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
186 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'all')
188 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'all')
187 # OPT: This should probably be 'all' if its parent is?
189 # OPT: This should probably be 'all' if its parent is?
188 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
190 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
189 self.assertEqual(m.visitchildrenset(b'folder'), set())
191 self.assertEqual(m.visitchildrenset(b'folder'), set())
190
192
191 def testVisitdirRootfilesin(self):
193 def testVisitdirRootfilesin(self):
192 m = matchmod.match(
194 m = matchmod.match(
193 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir/subdir']
195 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir/subdir']
194 )
196 )
195 assert isinstance(m, matchmod.includematcher)
197 assert isinstance(m, matchmod.includematcher)
196 self.assertTrue(m.visitdir(b''))
198 self.assertTrue(m.visitdir(b''))
197 self.assertTrue(m.visitdir(b'dir'))
199 self.assertTrue(m.visitdir(b'dir'))
198 self.assertTrue(m.visitdir(b'dir/subdir'))
200 self.assertTrue(m.visitdir(b'dir/subdir'))
199 self.assertFalse(m.visitdir(b'dir/subdir/x'))
201 self.assertFalse(m.visitdir(b'dir/subdir/x'))
200 self.assertFalse(m.visitdir(b'folder'))
202 self.assertFalse(m.visitdir(b'folder'))
201
203
202 def testVisitchildrensetRootfilesin(self):
204 def testVisitchildrensetRootfilesin(self):
203 m = matchmod.match(
205 m = matchmod.match(
204 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir/subdir']
206 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir/subdir']
205 )
207 )
206 assert isinstance(m, matchmod.includematcher)
208 assert isinstance(m, matchmod.includematcher)
207 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
209 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
208 self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
210 self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
209 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
211 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
210 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
212 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
211 self.assertEqual(m.visitchildrenset(b'folder'), set())
213 self.assertEqual(m.visitchildrenset(b'folder'), set())
212
214
213 def testVisitdirGlob(self):
215 def testVisitdirGlob(self):
214 m = matchmod.match(
216 m = matchmod.match(
215 util.localpath(b'/repo'), b'', include=[b'glob:dir/z*']
217 util.localpath(b'/repo'), b'', include=[b'glob:dir/z*']
216 )
218 )
217 assert isinstance(m, matchmod.includematcher)
219 assert isinstance(m, matchmod.includematcher)
218 self.assertTrue(m.visitdir(b''))
220 self.assertTrue(m.visitdir(b''))
219 self.assertTrue(m.visitdir(b'dir'))
221 self.assertTrue(m.visitdir(b'dir'))
220 self.assertFalse(m.visitdir(b'folder'))
222 self.assertFalse(m.visitdir(b'folder'))
221 # OPT: these should probably be False.
223 # OPT: these should probably be False.
222 self.assertTrue(m.visitdir(b'dir/subdir'))
224 self.assertTrue(m.visitdir(b'dir/subdir'))
223 self.assertTrue(m.visitdir(b'dir/subdir/x'))
225 self.assertTrue(m.visitdir(b'dir/subdir/x'))
224
226
225 def testVisitchildrensetGlob(self):
227 def testVisitchildrensetGlob(self):
226 m = matchmod.match(
228 m = matchmod.match(
227 util.localpath(b'/repo'), b'', include=[b'glob:dir/z*']
229 util.localpath(b'/repo'), b'', include=[b'glob:dir/z*']
228 )
230 )
229 assert isinstance(m, matchmod.includematcher)
231 assert isinstance(m, matchmod.includematcher)
230 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
232 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
231 self.assertEqual(m.visitchildrenset(b'folder'), set())
233 self.assertEqual(m.visitchildrenset(b'folder'), set())
232 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
234 self.assertEqual(m.visitchildrenset(b'dir'), b'this')
233 # OPT: these should probably be set().
235 # OPT: these should probably be set().
234 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
236 self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
235 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
237 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
236
238
237 def testVisitdirFilepath(self):
239 def testVisitdirFilepath(self):
238 m = matchmod.match(
240 m = matchmod.match(
239 util.localpath(b'/repo'), b'', include=[b'filepath:dir/z']
241 util.localpath(b'/repo'), b'', include=[b'filepath:dir/z']
240 )
242 )
241 assert isinstance(m, matchmod.includematcher)
243 assert isinstance(m, matchmod.includematcher)
242 self.assertTrue(m.visitdir(b''))
244 self.assertTrue(m.visitdir(b''))
243 self.assertTrue(m.visitdir(b'dir'))
245 self.assertTrue(m.visitdir(b'dir'))
244 self.assertFalse(m.visitdir(b'folder'))
246 self.assertFalse(m.visitdir(b'folder'))
245 self.assertFalse(m.visitdir(b'dir/subdir'))
247 self.assertFalse(m.visitdir(b'dir/subdir'))
246 self.assertFalse(m.visitdir(b'dir/subdir/x'))
248 self.assertFalse(m.visitdir(b'dir/subdir/x'))
247
249
248 def testVisitchildrensetFilepath(self):
250 def testVisitchildrensetFilepath(self):
249 m = matchmod.match(
251 m = matchmod.match(
250 util.localpath(b'/repo'), b'', include=[b'filepath:dir/z']
252 util.localpath(b'/repo'), b'', include=[b'filepath:dir/z']
251 )
253 )
252 assert isinstance(m, matchmod.includematcher)
254 assert isinstance(m, matchmod.includematcher)
253 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
255 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
254 self.assertEqual(m.visitchildrenset(b'folder'), set())
256 self.assertEqual(m.visitchildrenset(b'folder'), set())
255 self.assertEqual(m.visitchildrenset(b'dir'), {b'z'})
257 self.assertEqual(m.visitchildrenset(b'dir'), {b'z'})
256 self.assertEqual(m.visitchildrenset(b'dir/subdir'), set())
258 self.assertEqual(m.visitchildrenset(b'dir/subdir'), set())
257 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
259 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
258
260
259
261
260 class ExactMatcherTests(unittest.TestCase):
262 class ExactMatcherTests(unittest.TestCase):
261 def testVisitdir(self):
263 def testVisitdir(self):
262 m = matchmod.exact(files=[b'dir/subdir/foo.txt'])
264 m = matchmod.exact(files=[b'dir/subdir/foo.txt'])
263 assert isinstance(m, matchmod.exactmatcher)
265 assert isinstance(m, matchmod.exactmatcher)
264 self.assertTrue(m.visitdir(b''))
266 self.assertTrue(m.visitdir(b''))
265 self.assertTrue(m.visitdir(b'dir'))
267 self.assertTrue(m.visitdir(b'dir'))
266 self.assertTrue(m.visitdir(b'dir/subdir'))
268 self.assertTrue(m.visitdir(b'dir/subdir'))
267 self.assertFalse(m.visitdir(b'dir/subdir/foo.txt'))
269 self.assertFalse(m.visitdir(b'dir/subdir/foo.txt'))
268 self.assertFalse(m.visitdir(b'dir/foo'))
270 self.assertFalse(m.visitdir(b'dir/foo'))
269 self.assertFalse(m.visitdir(b'dir/subdir/x'))
271 self.assertFalse(m.visitdir(b'dir/subdir/x'))
270 self.assertFalse(m.visitdir(b'folder'))
272 self.assertFalse(m.visitdir(b'folder'))
271
273
272 def testVisitchildrenset(self):
274 def testVisitchildrenset(self):
273 m = matchmod.exact(files=[b'dir/subdir/foo.txt'])
275 m = matchmod.exact(files=[b'dir/subdir/foo.txt'])
274 assert isinstance(m, matchmod.exactmatcher)
276 assert isinstance(m, matchmod.exactmatcher)
275 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
277 self.assertEqual(m.visitchildrenset(b''), {b'dir'})
276 self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
278 self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
277 self.assertEqual(m.visitchildrenset(b'dir/subdir'), {b'foo.txt'})
279 self.assertEqual(m.visitchildrenset(b'dir/subdir'), {b'foo.txt'})
278 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
280 self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
279 self.assertEqual(m.visitchildrenset(b'dir/subdir/foo.txt'), set())
281 self.assertEqual(m.visitchildrenset(b'dir/subdir/foo.txt'), set())
280 self.assertEqual(m.visitchildrenset(b'folder'), set())
282 self.assertEqual(m.visitchildrenset(b'folder'), set())
281
283
282 def testVisitchildrensetFilesAndDirs(self):
284 def testVisitchildrensetFilesAndDirs(self):
283 m = matchmod.exact(
285 m = matchmod.exact(
284 files=[
286 files=[
285 b'rootfile.txt',
287 b'rootfile.txt',
286 b'a/file1.txt',
288 b'a/file1.txt',
287 b'a/b/file2.txt',
289 b'a/b/file2.txt',
288 # no file in a/b/c
290 # no file in a/b/c
289 b'a/b/c/d/file4.txt',
291 b'a/b/c/d/file4.txt',
290 ]
292 ]
291 )
293 )
292 assert isinstance(m, matchmod.exactmatcher)
294 assert isinstance(m, matchmod.exactmatcher)
293 self.assertEqual(m.visitchildrenset(b''), {b'a', b'rootfile.txt'})
295 self.assertEqual(m.visitchildrenset(b''), {b'a', b'rootfile.txt'})
294 self.assertEqual(m.visitchildrenset(b'a'), {b'b', b'file1.txt'})
296 self.assertEqual(m.visitchildrenset(b'a'), {b'b', b'file1.txt'})
295 self.assertEqual(m.visitchildrenset(b'a/b'), {b'c', b'file2.txt'})
297 self.assertEqual(m.visitchildrenset(b'a/b'), {b'c', b'file2.txt'})
296 self.assertEqual(m.visitchildrenset(b'a/b/c'), {b'd'})
298 self.assertEqual(m.visitchildrenset(b'a/b/c'), {b'd'})
297 self.assertEqual(m.visitchildrenset(b'a/b/c/d'), {b'file4.txt'})
299 self.assertEqual(m.visitchildrenset(b'a/b/c/d'), {b'file4.txt'})
298 self.assertEqual(m.visitchildrenset(b'a/b/c/d/e'), set())
300 self.assertEqual(m.visitchildrenset(b'a/b/c/d/e'), set())
299 self.assertEqual(m.visitchildrenset(b'folder'), set())
301 self.assertEqual(m.visitchildrenset(b'folder'), set())
300
302
301
303
302 class DifferenceMatcherTests(unittest.TestCase):
304 class DifferenceMatcherTests(unittest.TestCase):
303 def testVisitdirM2always(self):
305 def testVisitdirM2always(self):
304 m1 = matchmod.alwaysmatcher()
306 m1 = matchmod.alwaysmatcher()
305 m2 = matchmod.alwaysmatcher()
307 m2 = matchmod.alwaysmatcher()
306 dm = matchmod.differencematcher(m1, m2)
308 dm = matchmod.differencematcher(m1, m2)
307 # dm should be equivalent to a nevermatcher.
309 # dm should be equivalent to a nevermatcher.
308 self.assertFalse(dm.visitdir(b''))
310 self.assertFalse(dm.visitdir(b''))
309 self.assertFalse(dm.visitdir(b'dir'))
311 self.assertFalse(dm.visitdir(b'dir'))
310 self.assertFalse(dm.visitdir(b'dir/subdir'))
312 self.assertFalse(dm.visitdir(b'dir/subdir'))
311 self.assertFalse(dm.visitdir(b'dir/subdir/z'))
313 self.assertFalse(dm.visitdir(b'dir/subdir/z'))
312 self.assertFalse(dm.visitdir(b'dir/foo'))
314 self.assertFalse(dm.visitdir(b'dir/foo'))
313 self.assertFalse(dm.visitdir(b'dir/subdir/x'))
315 self.assertFalse(dm.visitdir(b'dir/subdir/x'))
314 self.assertFalse(dm.visitdir(b'folder'))
316 self.assertFalse(dm.visitdir(b'folder'))
315
317
316 def testVisitchildrensetM2always(self):
318 def testVisitchildrensetM2always(self):
317 m1 = matchmod.alwaysmatcher()
319 m1 = matchmod.alwaysmatcher()
318 m2 = matchmod.alwaysmatcher()
320 m2 = matchmod.alwaysmatcher()
319 dm = matchmod.differencematcher(m1, m2)
321 dm = matchmod.differencematcher(m1, m2)
320 # dm should be equivalent to a nevermatcher.
322 # dm should be equivalent to a nevermatcher.
321 self.assertEqual(dm.visitchildrenset(b''), set())
323 self.assertEqual(dm.visitchildrenset(b''), set())
322 self.assertEqual(dm.visitchildrenset(b'dir'), set())
324 self.assertEqual(dm.visitchildrenset(b'dir'), set())
323 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), set())
325 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), set())
324 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), set())
326 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), set())
325 self.assertEqual(dm.visitchildrenset(b'dir/foo'), set())
327 self.assertEqual(dm.visitchildrenset(b'dir/foo'), set())
326 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), set())
328 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), set())
327 self.assertEqual(dm.visitchildrenset(b'folder'), set())
329 self.assertEqual(dm.visitchildrenset(b'folder'), set())
328
330
329 def testVisitdirM2never(self):
331 def testVisitdirM2never(self):
330 m1 = matchmod.alwaysmatcher()
332 m1 = matchmod.alwaysmatcher()
331 m2 = matchmod.nevermatcher()
333 m2 = matchmod.nevermatcher()
332 dm = matchmod.differencematcher(m1, m2)
334 dm = matchmod.differencematcher(m1, m2)
333 # dm should be equivalent to a alwaysmatcher.
335 # dm should be equivalent to a alwaysmatcher.
334 #
336 #
335 # We're testing Equal-to-True instead of just 'assertTrue' since
337 # We're testing Equal-to-True instead of just 'assertTrue' since
336 # assertTrue does NOT verify that it's a bool, just that it's truthy.
338 # assertTrue does NOT verify that it's a bool, just that it's truthy.
337 # While we may want to eventually make these return 'all', they should
339 # While we may want to eventually make these return 'all', they should
338 # not currently do so.
340 # not currently do so.
339 self.assertEqual(dm.visitdir(b''), b'all')
341 self.assertEqual(dm.visitdir(b''), b'all')
340 self.assertEqual(dm.visitdir(b'dir'), b'all')
342 self.assertEqual(dm.visitdir(b'dir'), b'all')
341 self.assertEqual(dm.visitdir(b'dir/subdir'), b'all')
343 self.assertEqual(dm.visitdir(b'dir/subdir'), b'all')
342 self.assertEqual(dm.visitdir(b'dir/subdir/z'), b'all')
344 self.assertEqual(dm.visitdir(b'dir/subdir/z'), b'all')
343 self.assertEqual(dm.visitdir(b'dir/foo'), b'all')
345 self.assertEqual(dm.visitdir(b'dir/foo'), b'all')
344 self.assertEqual(dm.visitdir(b'dir/subdir/x'), b'all')
346 self.assertEqual(dm.visitdir(b'dir/subdir/x'), b'all')
345 self.assertEqual(dm.visitdir(b'folder'), b'all')
347 self.assertEqual(dm.visitdir(b'folder'), b'all')
346
348
347 def testVisitchildrensetM2never(self):
349 def testVisitchildrensetM2never(self):
348 m1 = matchmod.alwaysmatcher()
350 m1 = matchmod.alwaysmatcher()
349 m2 = matchmod.nevermatcher()
351 m2 = matchmod.nevermatcher()
350 dm = matchmod.differencematcher(m1, m2)
352 dm = matchmod.differencematcher(m1, m2)
351 # dm should be equivalent to a alwaysmatcher.
353 # dm should be equivalent to a alwaysmatcher.
352 self.assertEqual(dm.visitchildrenset(b''), b'all')
354 self.assertEqual(dm.visitchildrenset(b''), b'all')
353 self.assertEqual(dm.visitchildrenset(b'dir'), b'all')
355 self.assertEqual(dm.visitchildrenset(b'dir'), b'all')
354 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), b'all')
356 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), b'all')
355 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'all')
357 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'all')
356 self.assertEqual(dm.visitchildrenset(b'dir/foo'), b'all')
358 self.assertEqual(dm.visitchildrenset(b'dir/foo'), b'all')
357 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'all')
359 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'all')
358 self.assertEqual(dm.visitchildrenset(b'folder'), b'all')
360 self.assertEqual(dm.visitchildrenset(b'folder'), b'all')
359
361
360 def testVisitdirM2SubdirPrefix(self):
362 def testVisitdirM2SubdirPrefix(self):
361 m1 = matchmod.alwaysmatcher()
363 m1 = matchmod.alwaysmatcher()
362 m2 = matchmod.match(
364 m2 = matchmod.match(
363 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
365 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
364 )
366 )
365 dm = matchmod.differencematcher(m1, m2)
367 dm = matchmod.differencematcher(m1, m2)
366 self.assertEqual(dm.visitdir(b''), True)
368 self.assertEqual(dm.visitdir(b''), True)
367 self.assertEqual(dm.visitdir(b'dir'), True)
369 self.assertEqual(dm.visitdir(b'dir'), True)
368 self.assertFalse(dm.visitdir(b'dir/subdir'))
370 self.assertFalse(dm.visitdir(b'dir/subdir'))
369 # OPT: We should probably return False for these; we don't because
371 # OPT: We should probably return False for these; we don't because
370 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
372 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
371 # an 'all' pattern, just True.
373 # an 'all' pattern, just True.
372 self.assertEqual(dm.visitdir(b'dir/subdir/z'), True)
374 self.assertEqual(dm.visitdir(b'dir/subdir/z'), True)
373 self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
375 self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
374 self.assertEqual(dm.visitdir(b'dir/foo'), b'all')
376 self.assertEqual(dm.visitdir(b'dir/foo'), b'all')
375 self.assertEqual(dm.visitdir(b'folder'), b'all')
377 self.assertEqual(dm.visitdir(b'folder'), b'all')
376
378
377 def testVisitchildrensetM2SubdirPrefix(self):
379 def testVisitchildrensetM2SubdirPrefix(self):
378 m1 = matchmod.alwaysmatcher()
380 m1 = matchmod.alwaysmatcher()
379 m2 = matchmod.match(
381 m2 = matchmod.match(
380 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
382 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
381 )
383 )
382 dm = matchmod.differencematcher(m1, m2)
384 dm = matchmod.differencematcher(m1, m2)
383 self.assertEqual(dm.visitchildrenset(b''), b'this')
385 self.assertEqual(dm.visitchildrenset(b''), b'this')
384 self.assertEqual(dm.visitchildrenset(b'dir'), b'this')
386 self.assertEqual(dm.visitchildrenset(b'dir'), b'this')
385 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), set())
387 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), set())
386 self.assertEqual(dm.visitchildrenset(b'dir/foo'), b'all')
388 self.assertEqual(dm.visitchildrenset(b'dir/foo'), b'all')
387 self.assertEqual(dm.visitchildrenset(b'folder'), b'all')
389 self.assertEqual(dm.visitchildrenset(b'folder'), b'all')
388 # OPT: We should probably return set() for these; we don't because
390 # OPT: We should probably return set() for these; we don't because
389 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
391 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
390 # an 'all' pattern, just 'this'.
392 # an 'all' pattern, just 'this'.
391 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'this')
393 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'this')
392 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'this')
394 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'this')
393
395
394 # We're using includematcher instead of patterns because it behaves slightly
396 # We're using includematcher instead of patterns because it behaves slightly
395 # better (giving narrower results) than patternmatcher.
397 # better (giving narrower results) than patternmatcher.
396 def testVisitdirIncludeInclude(self):
398 def testVisitdirIncludeInclude(self):
397 m1 = matchmod.match(
399 m1 = matchmod.match(
398 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
400 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
399 )
401 )
400 m2 = matchmod.match(
402 m2 = matchmod.match(
401 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
403 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
402 )
404 )
403 dm = matchmod.differencematcher(m1, m2)
405 dm = matchmod.differencematcher(m1, m2)
404 self.assertEqual(dm.visitdir(b''), True)
406 self.assertEqual(dm.visitdir(b''), True)
405 self.assertEqual(dm.visitdir(b'dir'), True)
407 self.assertEqual(dm.visitdir(b'dir'), True)
406 self.assertEqual(dm.visitdir(b'dir/subdir'), b'all')
408 self.assertEqual(dm.visitdir(b'dir/subdir'), b'all')
407 self.assertFalse(dm.visitdir(b'dir/foo'))
409 self.assertFalse(dm.visitdir(b'dir/foo'))
408 self.assertFalse(dm.visitdir(b'folder'))
410 self.assertFalse(dm.visitdir(b'folder'))
409 # OPT: We should probably return False for these; we don't because
411 # OPT: We should probably return False for these; we don't because
410 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
412 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
411 # an 'all' pattern, just True.
413 # an 'all' pattern, just True.
412 self.assertEqual(dm.visitdir(b'dir/subdir/z'), True)
414 self.assertEqual(dm.visitdir(b'dir/subdir/z'), True)
413 self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
415 self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
414
416
415 def testVisitchildrensetIncludeInclude(self):
417 def testVisitchildrensetIncludeInclude(self):
416 m1 = matchmod.match(
418 m1 = matchmod.match(
417 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
419 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
418 )
420 )
419 m2 = matchmod.match(
421 m2 = matchmod.match(
420 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
422 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
421 )
423 )
422 dm = matchmod.differencematcher(m1, m2)
424 dm = matchmod.differencematcher(m1, m2)
423 self.assertEqual(dm.visitchildrenset(b''), {b'dir'})
425 self.assertEqual(dm.visitchildrenset(b''), {b'dir'})
424 self.assertEqual(dm.visitchildrenset(b'dir'), {b'subdir'})
426 self.assertEqual(dm.visitchildrenset(b'dir'), {b'subdir'})
425 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), b'all')
427 self.assertEqual(dm.visitchildrenset(b'dir/subdir'), b'all')
426 self.assertEqual(dm.visitchildrenset(b'dir/foo'), set())
428 self.assertEqual(dm.visitchildrenset(b'dir/foo'), set())
427 self.assertEqual(dm.visitchildrenset(b'folder'), set())
429 self.assertEqual(dm.visitchildrenset(b'folder'), set())
428 # OPT: We should probably return set() for these; we don't because
430 # OPT: We should probably return set() for these; we don't because
429 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
431 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
430 # an 'all' pattern, just 'this'.
432 # an 'all' pattern, just 'this'.
431 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'this')
433 self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'this')
432 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'this')
434 self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'this')
433
435
434
436
435 class IntersectionMatcherTests(unittest.TestCase):
437 class IntersectionMatcherTests(unittest.TestCase):
436 def testVisitdirM2always(self):
438 def testVisitdirM2always(self):
437 m1 = matchmod.alwaysmatcher()
439 m1 = matchmod.alwaysmatcher()
438 m2 = matchmod.alwaysmatcher()
440 m2 = matchmod.alwaysmatcher()
439 im = matchmod.intersectmatchers(m1, m2)
441 im = matchmod.intersectmatchers(m1, m2)
440 # im should be equivalent to a alwaysmatcher.
442 # im should be equivalent to a alwaysmatcher.
441 self.assertEqual(im.visitdir(b''), b'all')
443 self.assertEqual(im.visitdir(b''), b'all')
442 self.assertEqual(im.visitdir(b'dir'), b'all')
444 self.assertEqual(im.visitdir(b'dir'), b'all')
443 self.assertEqual(im.visitdir(b'dir/subdir'), b'all')
445 self.assertEqual(im.visitdir(b'dir/subdir'), b'all')
444 self.assertEqual(im.visitdir(b'dir/subdir/z'), b'all')
446 self.assertEqual(im.visitdir(b'dir/subdir/z'), b'all')
445 self.assertEqual(im.visitdir(b'dir/foo'), b'all')
447 self.assertEqual(im.visitdir(b'dir/foo'), b'all')
446 self.assertEqual(im.visitdir(b'dir/subdir/x'), b'all')
448 self.assertEqual(im.visitdir(b'dir/subdir/x'), b'all')
447 self.assertEqual(im.visitdir(b'folder'), b'all')
449 self.assertEqual(im.visitdir(b'folder'), b'all')
448
450
449 def testVisitchildrensetM2always(self):
451 def testVisitchildrensetM2always(self):
450 m1 = matchmod.alwaysmatcher()
452 m1 = matchmod.alwaysmatcher()
451 m2 = matchmod.alwaysmatcher()
453 m2 = matchmod.alwaysmatcher()
452 im = matchmod.intersectmatchers(m1, m2)
454 im = matchmod.intersectmatchers(m1, m2)
453 # im should be equivalent to a alwaysmatcher.
455 # im should be equivalent to a alwaysmatcher.
454 self.assertEqual(im.visitchildrenset(b''), b'all')
456 self.assertEqual(im.visitchildrenset(b''), b'all')
455 self.assertEqual(im.visitchildrenset(b'dir'), b'all')
457 self.assertEqual(im.visitchildrenset(b'dir'), b'all')
456 self.assertEqual(im.visitchildrenset(b'dir/subdir'), b'all')
458 self.assertEqual(im.visitchildrenset(b'dir/subdir'), b'all')
457 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), b'all')
459 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), b'all')
458 self.assertEqual(im.visitchildrenset(b'dir/foo'), b'all')
460 self.assertEqual(im.visitchildrenset(b'dir/foo'), b'all')
459 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'all')
461 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'all')
460 self.assertEqual(im.visitchildrenset(b'folder'), b'all')
462 self.assertEqual(im.visitchildrenset(b'folder'), b'all')
461
463
462 def testVisitdirM2never(self):
464 def testVisitdirM2never(self):
463 m1 = matchmod.alwaysmatcher()
465 m1 = matchmod.alwaysmatcher()
464 m2 = matchmod.nevermatcher()
466 m2 = matchmod.nevermatcher()
465 im = matchmod.intersectmatchers(m1, m2)
467 im = matchmod.intersectmatchers(m1, m2)
466 # im should be equivalent to a nevermatcher.
468 # im should be equivalent to a nevermatcher.
467 self.assertFalse(im.visitdir(b''))
469 self.assertFalse(im.visitdir(b''))
468 self.assertFalse(im.visitdir(b'dir'))
470 self.assertFalse(im.visitdir(b'dir'))
469 self.assertFalse(im.visitdir(b'dir/subdir'))
471 self.assertFalse(im.visitdir(b'dir/subdir'))
470 self.assertFalse(im.visitdir(b'dir/subdir/z'))
472 self.assertFalse(im.visitdir(b'dir/subdir/z'))
471 self.assertFalse(im.visitdir(b'dir/foo'))
473 self.assertFalse(im.visitdir(b'dir/foo'))
472 self.assertFalse(im.visitdir(b'dir/subdir/x'))
474 self.assertFalse(im.visitdir(b'dir/subdir/x'))
473 self.assertFalse(im.visitdir(b'folder'))
475 self.assertFalse(im.visitdir(b'folder'))
474
476
475 def testVisitchildrensetM2never(self):
477 def testVisitchildrensetM2never(self):
476 m1 = matchmod.alwaysmatcher()
478 m1 = matchmod.alwaysmatcher()
477 m2 = matchmod.nevermatcher()
479 m2 = matchmod.nevermatcher()
478 im = matchmod.intersectmatchers(m1, m2)
480 im = matchmod.intersectmatchers(m1, m2)
479 # im should be equivalent to a nevermqtcher.
481 # im should be equivalent to a nevermqtcher.
480 self.assertEqual(im.visitchildrenset(b''), set())
482 self.assertEqual(im.visitchildrenset(b''), set())
481 self.assertEqual(im.visitchildrenset(b'dir'), set())
483 self.assertEqual(im.visitchildrenset(b'dir'), set())
482 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
484 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
483 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
485 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
484 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
486 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
485 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
487 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
486 self.assertEqual(im.visitchildrenset(b'folder'), set())
488 self.assertEqual(im.visitchildrenset(b'folder'), set())
487
489
488 def testVisitdirM2SubdirPrefix(self):
490 def testVisitdirM2SubdirPrefix(self):
489 m1 = matchmod.alwaysmatcher()
491 m1 = matchmod.alwaysmatcher()
490 m2 = matchmod.match(
492 m2 = matchmod.match(
491 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
493 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
492 )
494 )
493 im = matchmod.intersectmatchers(m1, m2)
495 im = matchmod.intersectmatchers(m1, m2)
494 self.assertEqual(im.visitdir(b''), True)
496 self.assertEqual(im.visitdir(b''), True)
495 self.assertEqual(im.visitdir(b'dir'), True)
497 self.assertEqual(im.visitdir(b'dir'), True)
496 self.assertEqual(im.visitdir(b'dir/subdir'), b'all')
498 self.assertEqual(im.visitdir(b'dir/subdir'), b'all')
497 self.assertFalse(im.visitdir(b'dir/foo'))
499 self.assertFalse(im.visitdir(b'dir/foo'))
498 self.assertFalse(im.visitdir(b'folder'))
500 self.assertFalse(im.visitdir(b'folder'))
499 # OPT: We should probably return 'all' for these; we don't because
501 # OPT: We should probably return 'all' for these; we don't because
500 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
502 # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
501 # an 'all' pattern, just True.
503 # an 'all' pattern, just True.
502 self.assertEqual(im.visitdir(b'dir/subdir/z'), True)
504 self.assertEqual(im.visitdir(b'dir/subdir/z'), True)
503 self.assertEqual(im.visitdir(b'dir/subdir/x'), True)
505 self.assertEqual(im.visitdir(b'dir/subdir/x'), True)
504
506
505 def testVisitchildrensetM2SubdirPrefix(self):
507 def testVisitchildrensetM2SubdirPrefix(self):
506 m1 = matchmod.alwaysmatcher()
508 m1 = matchmod.alwaysmatcher()
507 m2 = matchmod.match(
509 m2 = matchmod.match(
508 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
510 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
509 )
511 )
510 im = matchmod.intersectmatchers(m1, m2)
512 im = matchmod.intersectmatchers(m1, m2)
511 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
513 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
512 self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
514 self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
513 self.assertEqual(im.visitchildrenset(b'dir/subdir'), b'all')
515 self.assertEqual(im.visitchildrenset(b'dir/subdir'), b'all')
514 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
516 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
515 self.assertEqual(im.visitchildrenset(b'folder'), set())
517 self.assertEqual(im.visitchildrenset(b'folder'), set())
516 # OPT: We should probably return 'all' for these
518 # OPT: We should probably return 'all' for these
517 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), b'this')
519 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), b'this')
518 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'this')
520 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'this')
519
521
520 # We're using includematcher instead of patterns because it behaves slightly
522 # We're using includematcher instead of patterns because it behaves slightly
521 # better (giving narrower results) than patternmatcher.
523 # better (giving narrower results) than patternmatcher.
522 def testVisitdirIncludeInclude(self):
524 def testVisitdirIncludeInclude(self):
523 m1 = matchmod.match(
525 m1 = matchmod.match(
524 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
526 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
525 )
527 )
526 m2 = matchmod.match(
528 m2 = matchmod.match(
527 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
529 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
528 )
530 )
529 im = matchmod.intersectmatchers(m1, m2)
531 im = matchmod.intersectmatchers(m1, m2)
530 self.assertEqual(im.visitdir(b''), True)
532 self.assertEqual(im.visitdir(b''), True)
531 self.assertEqual(im.visitdir(b'dir'), True)
533 self.assertEqual(im.visitdir(b'dir'), True)
532 self.assertFalse(im.visitdir(b'dir/subdir'))
534 self.assertFalse(im.visitdir(b'dir/subdir'))
533 self.assertFalse(im.visitdir(b'dir/foo'))
535 self.assertFalse(im.visitdir(b'dir/foo'))
534 self.assertFalse(im.visitdir(b'folder'))
536 self.assertFalse(im.visitdir(b'folder'))
535 self.assertFalse(im.visitdir(b'dir/subdir/z'))
537 self.assertFalse(im.visitdir(b'dir/subdir/z'))
536 self.assertFalse(im.visitdir(b'dir/subdir/x'))
538 self.assertFalse(im.visitdir(b'dir/subdir/x'))
537
539
538 def testVisitchildrensetIncludeInclude(self):
540 def testVisitchildrensetIncludeInclude(self):
539 m1 = matchmod.match(
541 m1 = matchmod.match(
540 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
542 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
541 )
543 )
542 m2 = matchmod.match(
544 m2 = matchmod.match(
543 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
545 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
544 )
546 )
545 im = matchmod.intersectmatchers(m1, m2)
547 im = matchmod.intersectmatchers(m1, m2)
546 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
548 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
547 self.assertEqual(im.visitchildrenset(b'dir'), b'this')
549 self.assertEqual(im.visitchildrenset(b'dir'), b'this')
548 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
550 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
549 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
551 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
550 self.assertEqual(im.visitchildrenset(b'folder'), set())
552 self.assertEqual(im.visitchildrenset(b'folder'), set())
551 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
553 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
552 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
554 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
553
555
554 # We're using includematcher instead of patterns because it behaves slightly
556 # We're using includematcher instead of patterns because it behaves slightly
555 # better (giving narrower results) than patternmatcher.
557 # better (giving narrower results) than patternmatcher.
556 def testVisitdirIncludeInclude2(self):
558 def testVisitdirIncludeInclude2(self):
557 m1 = matchmod.match(
559 m1 = matchmod.match(
558 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
560 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
559 )
561 )
560 m2 = matchmod.match(
562 m2 = matchmod.match(
561 util.localpath(b'/repo'), b'', include=[b'path:folder']
563 util.localpath(b'/repo'), b'', include=[b'path:folder']
562 )
564 )
563 im = matchmod.intersectmatchers(m1, m2)
565 im = matchmod.intersectmatchers(m1, m2)
564 # FIXME: is True correct here?
566 # FIXME: is True correct here?
565 self.assertEqual(im.visitdir(b''), True)
567 self.assertEqual(im.visitdir(b''), True)
566 self.assertFalse(im.visitdir(b'dir'))
568 self.assertFalse(im.visitdir(b'dir'))
567 self.assertFalse(im.visitdir(b'dir/subdir'))
569 self.assertFalse(im.visitdir(b'dir/subdir'))
568 self.assertFalse(im.visitdir(b'dir/foo'))
570 self.assertFalse(im.visitdir(b'dir/foo'))
569 self.assertFalse(im.visitdir(b'folder'))
571 self.assertFalse(im.visitdir(b'folder'))
570 self.assertFalse(im.visitdir(b'dir/subdir/z'))
572 self.assertFalse(im.visitdir(b'dir/subdir/z'))
571 self.assertFalse(im.visitdir(b'dir/subdir/x'))
573 self.assertFalse(im.visitdir(b'dir/subdir/x'))
572
574
573 def testVisitchildrensetIncludeInclude2(self):
575 def testVisitchildrensetIncludeInclude2(self):
574 m1 = matchmod.match(
576 m1 = matchmod.match(
575 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
577 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
576 )
578 )
577 m2 = matchmod.match(
579 m2 = matchmod.match(
578 util.localpath(b'/repo'), b'', include=[b'path:folder']
580 util.localpath(b'/repo'), b'', include=[b'path:folder']
579 )
581 )
580 im = matchmod.intersectmatchers(m1, m2)
582 im = matchmod.intersectmatchers(m1, m2)
581 # FIXME: is set() correct here?
583 # FIXME: is set() correct here?
582 self.assertEqual(im.visitchildrenset(b''), set())
584 self.assertEqual(im.visitchildrenset(b''), set())
583 self.assertEqual(im.visitchildrenset(b'dir'), set())
585 self.assertEqual(im.visitchildrenset(b'dir'), set())
584 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
586 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
585 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
587 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
586 self.assertEqual(im.visitchildrenset(b'folder'), set())
588 self.assertEqual(im.visitchildrenset(b'folder'), set())
587 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
589 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
588 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
590 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
589
591
590 # We're using includematcher instead of patterns because it behaves slightly
592 # We're using includematcher instead of patterns because it behaves slightly
591 # better (giving narrower results) than patternmatcher.
593 # better (giving narrower results) than patternmatcher.
592 def testVisitdirIncludeInclude3(self):
594 def testVisitdirIncludeInclude3(self):
593 m1 = matchmod.match(
595 m1 = matchmod.match(
594 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
596 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
595 )
597 )
596 m2 = matchmod.match(
598 m2 = matchmod.match(
597 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
599 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
598 )
600 )
599 im = matchmod.intersectmatchers(m1, m2)
601 im = matchmod.intersectmatchers(m1, m2)
600 self.assertEqual(im.visitdir(b''), True)
602 self.assertEqual(im.visitdir(b''), True)
601 self.assertEqual(im.visitdir(b'dir'), True)
603 self.assertEqual(im.visitdir(b'dir'), True)
602 self.assertEqual(im.visitdir(b'dir/subdir'), True)
604 self.assertEqual(im.visitdir(b'dir/subdir'), True)
603 self.assertFalse(im.visitdir(b'dir/foo'))
605 self.assertFalse(im.visitdir(b'dir/foo'))
604 self.assertFalse(im.visitdir(b'folder'))
606 self.assertFalse(im.visitdir(b'folder'))
605 self.assertFalse(im.visitdir(b'dir/subdir/z'))
607 self.assertFalse(im.visitdir(b'dir/subdir/z'))
606 # OPT: this should probably be 'all' not True.
608 # OPT: this should probably be 'all' not True.
607 self.assertEqual(im.visitdir(b'dir/subdir/x'), True)
609 self.assertEqual(im.visitdir(b'dir/subdir/x'), True)
608
610
609 def testVisitchildrensetIncludeInclude3(self):
611 def testVisitchildrensetIncludeInclude3(self):
610 m1 = matchmod.match(
612 m1 = matchmod.match(
611 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
613 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
612 )
614 )
613 m2 = matchmod.match(
615 m2 = matchmod.match(
614 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
616 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
615 )
617 )
616 im = matchmod.intersectmatchers(m1, m2)
618 im = matchmod.intersectmatchers(m1, m2)
617 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
619 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
618 self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
620 self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
619 self.assertEqual(im.visitchildrenset(b'dir/subdir'), {b'x'})
621 self.assertEqual(im.visitchildrenset(b'dir/subdir'), {b'x'})
620 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
622 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
621 self.assertEqual(im.visitchildrenset(b'folder'), set())
623 self.assertEqual(im.visitchildrenset(b'folder'), set())
622 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
624 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
623 # OPT: this should probably be 'all' not 'this'.
625 # OPT: this should probably be 'all' not 'this'.
624 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'this')
626 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'this')
625
627
626 # We're using includematcher instead of patterns because it behaves slightly
628 # We're using includematcher instead of patterns because it behaves slightly
627 # better (giving narrower results) than patternmatcher.
629 # better (giving narrower results) than patternmatcher.
628 def testVisitdirIncludeInclude4(self):
630 def testVisitdirIncludeInclude4(self):
629 m1 = matchmod.match(
631 m1 = matchmod.match(
630 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
632 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
631 )
633 )
632 m2 = matchmod.match(
634 m2 = matchmod.match(
633 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/z']
635 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/z']
634 )
636 )
635 im = matchmod.intersectmatchers(m1, m2)
637 im = matchmod.intersectmatchers(m1, m2)
636 # OPT: these next three could probably be False as well.
638 # OPT: these next three could probably be False as well.
637 self.assertEqual(im.visitdir(b''), True)
639 self.assertEqual(im.visitdir(b''), True)
638 self.assertEqual(im.visitdir(b'dir'), True)
640 self.assertEqual(im.visitdir(b'dir'), True)
639 self.assertEqual(im.visitdir(b'dir/subdir'), True)
641 self.assertEqual(im.visitdir(b'dir/subdir'), True)
640 self.assertFalse(im.visitdir(b'dir/foo'))
642 self.assertFalse(im.visitdir(b'dir/foo'))
641 self.assertFalse(im.visitdir(b'folder'))
643 self.assertFalse(im.visitdir(b'folder'))
642 self.assertFalse(im.visitdir(b'dir/subdir/z'))
644 self.assertFalse(im.visitdir(b'dir/subdir/z'))
643 self.assertFalse(im.visitdir(b'dir/subdir/x'))
645 self.assertFalse(im.visitdir(b'dir/subdir/x'))
644
646
645 def testVisitchildrensetIncludeInclude4(self):
647 def testVisitchildrensetIncludeInclude4(self):
646 m1 = matchmod.match(
648 m1 = matchmod.match(
647 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
649 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
648 )
650 )
649 m2 = matchmod.match(
651 m2 = matchmod.match(
650 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/z']
652 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/z']
651 )
653 )
652 im = matchmod.intersectmatchers(m1, m2)
654 im = matchmod.intersectmatchers(m1, m2)
653 # OPT: these next two could probably be set() as well.
655 # OPT: these next two could probably be set() as well.
654 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
656 self.assertEqual(im.visitchildrenset(b''), {b'dir'})
655 self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
657 self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
656 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
658 self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
657 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
659 self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
658 self.assertEqual(im.visitchildrenset(b'folder'), set())
660 self.assertEqual(im.visitchildrenset(b'folder'), set())
659 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
661 self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
660 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
662 self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
661
663
662
664
663 class UnionMatcherTests(unittest.TestCase):
665 class UnionMatcherTests(unittest.TestCase):
664 def testVisitdirM2always(self):
666 def testVisitdirM2always(self):
665 m1 = matchmod.alwaysmatcher()
667 m1 = matchmod.alwaysmatcher()
666 m2 = matchmod.alwaysmatcher()
668 m2 = matchmod.alwaysmatcher()
667 um = matchmod.unionmatcher([m1, m2])
669 um = matchmod.unionmatcher([m1, m2])
668 # um should be equivalent to a alwaysmatcher.
670 # um should be equivalent to a alwaysmatcher.
669 self.assertEqual(um.visitdir(b''), b'all')
671 self.assertEqual(um.visitdir(b''), b'all')
670 self.assertEqual(um.visitdir(b'dir'), b'all')
672 self.assertEqual(um.visitdir(b'dir'), b'all')
671 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
673 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
672 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
674 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
673 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
675 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
674 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
676 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
675 self.assertEqual(um.visitdir(b'folder'), b'all')
677 self.assertEqual(um.visitdir(b'folder'), b'all')
676
678
677 def testVisitchildrensetM2always(self):
679 def testVisitchildrensetM2always(self):
678 m1 = matchmod.alwaysmatcher()
680 m1 = matchmod.alwaysmatcher()
679 m2 = matchmod.alwaysmatcher()
681 m2 = matchmod.alwaysmatcher()
680 um = matchmod.unionmatcher([m1, m2])
682 um = matchmod.unionmatcher([m1, m2])
681 # um should be equivalent to a alwaysmatcher.
683 # um should be equivalent to a alwaysmatcher.
682 self.assertEqual(um.visitchildrenset(b''), b'all')
684 self.assertEqual(um.visitchildrenset(b''), b'all')
683 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
685 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
684 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
686 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
685 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
687 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
686 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
688 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
687 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
689 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
688 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
690 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
689
691
690 def testVisitdirM1never(self):
692 def testVisitdirM1never(self):
691 m1 = matchmod.nevermatcher()
693 m1 = matchmod.nevermatcher()
692 m2 = matchmod.alwaysmatcher()
694 m2 = matchmod.alwaysmatcher()
693 um = matchmod.unionmatcher([m1, m2])
695 um = matchmod.unionmatcher([m1, m2])
694 # um should be equivalent to a alwaysmatcher.
696 # um should be equivalent to a alwaysmatcher.
695 self.assertEqual(um.visitdir(b''), b'all')
697 self.assertEqual(um.visitdir(b''), b'all')
696 self.assertEqual(um.visitdir(b'dir'), b'all')
698 self.assertEqual(um.visitdir(b'dir'), b'all')
697 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
699 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
698 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
700 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
699 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
701 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
700 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
702 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
701 self.assertEqual(um.visitdir(b'folder'), b'all')
703 self.assertEqual(um.visitdir(b'folder'), b'all')
702
704
703 def testVisitchildrensetM1never(self):
705 def testVisitchildrensetM1never(self):
704 m1 = matchmod.nevermatcher()
706 m1 = matchmod.nevermatcher()
705 m2 = matchmod.alwaysmatcher()
707 m2 = matchmod.alwaysmatcher()
706 um = matchmod.unionmatcher([m1, m2])
708 um = matchmod.unionmatcher([m1, m2])
707 # um should be equivalent to a alwaysmatcher.
709 # um should be equivalent to a alwaysmatcher.
708 self.assertEqual(um.visitchildrenset(b''), b'all')
710 self.assertEqual(um.visitchildrenset(b''), b'all')
709 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
711 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
710 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
712 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
711 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
713 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
712 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
714 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
713 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
715 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
714 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
716 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
715
717
716 def testVisitdirM2never(self):
718 def testVisitdirM2never(self):
717 m1 = matchmod.alwaysmatcher()
719 m1 = matchmod.alwaysmatcher()
718 m2 = matchmod.nevermatcher()
720 m2 = matchmod.nevermatcher()
719 um = matchmod.unionmatcher([m1, m2])
721 um = matchmod.unionmatcher([m1, m2])
720 # um should be equivalent to a alwaysmatcher.
722 # um should be equivalent to a alwaysmatcher.
721 self.assertEqual(um.visitdir(b''), b'all')
723 self.assertEqual(um.visitdir(b''), b'all')
722 self.assertEqual(um.visitdir(b'dir'), b'all')
724 self.assertEqual(um.visitdir(b'dir'), b'all')
723 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
725 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
724 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
726 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
725 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
727 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
726 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
728 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
727 self.assertEqual(um.visitdir(b'folder'), b'all')
729 self.assertEqual(um.visitdir(b'folder'), b'all')
728
730
729 def testVisitchildrensetM2never(self):
731 def testVisitchildrensetM2never(self):
730 m1 = matchmod.alwaysmatcher()
732 m1 = matchmod.alwaysmatcher()
731 m2 = matchmod.nevermatcher()
733 m2 = matchmod.nevermatcher()
732 um = matchmod.unionmatcher([m1, m2])
734 um = matchmod.unionmatcher([m1, m2])
733 # um should be equivalent to a alwaysmatcher.
735 # um should be equivalent to a alwaysmatcher.
734 self.assertEqual(um.visitchildrenset(b''), b'all')
736 self.assertEqual(um.visitchildrenset(b''), b'all')
735 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
737 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
736 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
738 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
737 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
739 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
738 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
740 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
739 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
741 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
740 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
742 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
741
743
742 def testVisitdirM2SubdirPrefix(self):
744 def testVisitdirM2SubdirPrefix(self):
743 m1 = matchmod.alwaysmatcher()
745 m1 = matchmod.alwaysmatcher()
744 m2 = matchmod.match(
746 m2 = matchmod.match(
745 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
747 util.localpath(b'/repo'), b'', patterns=[b'path:dir/subdir']
746 )
748 )
747 um = matchmod.unionmatcher([m1, m2])
749 um = matchmod.unionmatcher([m1, m2])
748 self.assertEqual(um.visitdir(b''), b'all')
750 self.assertEqual(um.visitdir(b''), b'all')
749 self.assertEqual(um.visitdir(b'dir'), b'all')
751 self.assertEqual(um.visitdir(b'dir'), b'all')
750 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
752 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
751 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
753 self.assertEqual(um.visitdir(b'dir/foo'), b'all')
752 self.assertEqual(um.visitdir(b'folder'), b'all')
754 self.assertEqual(um.visitdir(b'folder'), b'all')
753 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
755 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
754 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
756 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
755
757
756 def testVisitchildrensetM2SubdirPrefix(self):
758 def testVisitchildrensetM2SubdirPrefix(self):
757 m1 = matchmod.alwaysmatcher()
759 m1 = matchmod.alwaysmatcher()
758 m2 = matchmod.match(
760 m2 = matchmod.match(
759 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
761 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
760 )
762 )
761 um = matchmod.unionmatcher([m1, m2])
763 um = matchmod.unionmatcher([m1, m2])
762 self.assertEqual(um.visitchildrenset(b''), b'all')
764 self.assertEqual(um.visitchildrenset(b''), b'all')
763 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
765 self.assertEqual(um.visitchildrenset(b'dir'), b'all')
764 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
766 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
765 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
767 self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
766 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
768 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
767 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
769 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
768 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
770 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
769
771
770 # We're using includematcher instead of patterns because it behaves slightly
772 # We're using includematcher instead of patterns because it behaves slightly
771 # better (giving narrower results) than patternmatcher.
773 # better (giving narrower results) than patternmatcher.
772 def testVisitdirIncludeInclude(self):
774 def testVisitdirIncludeInclude(self):
773 m1 = matchmod.match(
775 m1 = matchmod.match(
774 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
776 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
775 )
777 )
776 m2 = matchmod.match(
778 m2 = matchmod.match(
777 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
779 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
778 )
780 )
779 um = matchmod.unionmatcher([m1, m2])
781 um = matchmod.unionmatcher([m1, m2])
780 self.assertEqual(um.visitdir(b''), True)
782 self.assertEqual(um.visitdir(b''), True)
781 self.assertEqual(um.visitdir(b'dir'), True)
783 self.assertEqual(um.visitdir(b'dir'), True)
782 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
784 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
783 self.assertFalse(um.visitdir(b'dir/foo'))
785 self.assertFalse(um.visitdir(b'dir/foo'))
784 self.assertFalse(um.visitdir(b'folder'))
786 self.assertFalse(um.visitdir(b'folder'))
785 # OPT: These two should probably be 'all' not True.
787 # OPT: These two should probably be 'all' not True.
786 self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
788 self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
787 self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
789 self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
788
790
789 def testVisitchildrensetIncludeInclude(self):
791 def testVisitchildrensetIncludeInclude(self):
790 m1 = matchmod.match(
792 m1 = matchmod.match(
791 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
793 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
792 )
794 )
793 m2 = matchmod.match(
795 m2 = matchmod.match(
794 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
796 util.localpath(b'/repo'), b'', include=[b'rootfilesin:dir']
795 )
797 )
796 um = matchmod.unionmatcher([m1, m2])
798 um = matchmod.unionmatcher([m1, m2])
797 self.assertEqual(um.visitchildrenset(b''), {b'dir'})
799 self.assertEqual(um.visitchildrenset(b''), {b'dir'})
798 self.assertEqual(um.visitchildrenset(b'dir'), b'this')
800 self.assertEqual(um.visitchildrenset(b'dir'), b'this')
799 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
801 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
800 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
802 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
801 self.assertEqual(um.visitchildrenset(b'folder'), set())
803 self.assertEqual(um.visitchildrenset(b'folder'), set())
802 # OPT: These next two could be 'all' instead of 'this'.
804 # OPT: These next two could be 'all' instead of 'this'.
803 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
805 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
804 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'this')
806 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'this')
805
807
806 # We're using includematcher instead of patterns because it behaves slightly
808 # We're using includematcher instead of patterns because it behaves slightly
807 # better (giving narrower results) than patternmatcher.
809 # better (giving narrower results) than patternmatcher.
808 def testVisitdirIncludeInclude2(self):
810 def testVisitdirIncludeInclude2(self):
809 m1 = matchmod.match(
811 m1 = matchmod.match(
810 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
812 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
811 )
813 )
812 m2 = matchmod.match(
814 m2 = matchmod.match(
813 util.localpath(b'/repo'), b'', include=[b'path:folder']
815 util.localpath(b'/repo'), b'', include=[b'path:folder']
814 )
816 )
815 um = matchmod.unionmatcher([m1, m2])
817 um = matchmod.unionmatcher([m1, m2])
816 self.assertEqual(um.visitdir(b''), True)
818 self.assertEqual(um.visitdir(b''), True)
817 self.assertEqual(um.visitdir(b'dir'), True)
819 self.assertEqual(um.visitdir(b'dir'), True)
818 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
820 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
819 self.assertFalse(um.visitdir(b'dir/foo'))
821 self.assertFalse(um.visitdir(b'dir/foo'))
820 self.assertEqual(um.visitdir(b'folder'), b'all')
822 self.assertEqual(um.visitdir(b'folder'), b'all')
821 # OPT: These should probably be 'all' not True.
823 # OPT: These should probably be 'all' not True.
822 self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
824 self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
823 self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
825 self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
824
826
825 def testVisitchildrensetIncludeInclude2(self):
827 def testVisitchildrensetIncludeInclude2(self):
826 m1 = matchmod.match(
828 m1 = matchmod.match(
827 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
829 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
828 )
830 )
829 m2 = matchmod.match(
831 m2 = matchmod.match(
830 util.localpath(b'/repo'), b'', include=[b'path:folder']
832 util.localpath(b'/repo'), b'', include=[b'path:folder']
831 )
833 )
832 um = matchmod.unionmatcher([m1, m2])
834 um = matchmod.unionmatcher([m1, m2])
833 self.assertEqual(um.visitchildrenset(b''), {b'folder', b'dir'})
835 self.assertEqual(um.visitchildrenset(b''), {b'folder', b'dir'})
834 self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
836 self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
835 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
837 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
836 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
838 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
837 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
839 self.assertEqual(um.visitchildrenset(b'folder'), b'all')
838 # OPT: These next two could be 'all' instead of 'this'.
840 # OPT: These next two could be 'all' instead of 'this'.
839 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
841 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
840 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'this')
842 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'this')
841
843
842 # We're using includematcher instead of patterns because it behaves slightly
844 # We're using includematcher instead of patterns because it behaves slightly
843 # better (giving narrower results) than patternmatcher.
845 # better (giving narrower results) than patternmatcher.
844 def testVisitdirIncludeInclude3(self):
846 def testVisitdirIncludeInclude3(self):
845 m1 = matchmod.match(
847 m1 = matchmod.match(
846 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
848 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
847 )
849 )
848 m2 = matchmod.match(
850 m2 = matchmod.match(
849 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
851 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
850 )
852 )
851 um = matchmod.unionmatcher([m1, m2])
853 um = matchmod.unionmatcher([m1, m2])
852 self.assertEqual(um.visitdir(b''), True)
854 self.assertEqual(um.visitdir(b''), True)
853 self.assertEqual(um.visitdir(b'dir'), True)
855 self.assertEqual(um.visitdir(b'dir'), True)
854 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
856 self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
855 self.assertFalse(um.visitdir(b'dir/foo'))
857 self.assertFalse(um.visitdir(b'dir/foo'))
856 self.assertFalse(um.visitdir(b'folder'))
858 self.assertFalse(um.visitdir(b'folder'))
857 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
859 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
858 # OPT: this should probably be 'all' not True.
860 # OPT: this should probably be 'all' not True.
859 self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
861 self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
860
862
861 def testVisitchildrensetIncludeInclude3(self):
863 def testVisitchildrensetIncludeInclude3(self):
862 m1 = matchmod.match(
864 m1 = matchmod.match(
863 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
865 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
864 )
866 )
865 m2 = matchmod.match(
867 m2 = matchmod.match(
866 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
868 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
867 )
869 )
868 um = matchmod.unionmatcher([m1, m2])
870 um = matchmod.unionmatcher([m1, m2])
869 self.assertEqual(um.visitchildrenset(b''), {b'dir'})
871 self.assertEqual(um.visitchildrenset(b''), {b'dir'})
870 self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
872 self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
871 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
873 self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
872 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
874 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
873 self.assertEqual(um.visitchildrenset(b'folder'), set())
875 self.assertEqual(um.visitchildrenset(b'folder'), set())
874 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
876 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
875 # OPT: this should probably be 'all' not 'this'.
877 # OPT: this should probably be 'all' not 'this'.
876 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
878 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
877
879
878 # We're using includematcher instead of patterns because it behaves slightly
880 # We're using includematcher instead of patterns because it behaves slightly
879 # better (giving narrower results) than patternmatcher.
881 # better (giving narrower results) than patternmatcher.
880 def testVisitdirIncludeInclude4(self):
882 def testVisitdirIncludeInclude4(self):
881 m1 = matchmod.match(
883 m1 = matchmod.match(
882 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
884 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
883 )
885 )
884 m2 = matchmod.match(
886 m2 = matchmod.match(
885 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/z']
887 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/z']
886 )
888 )
887 um = matchmod.unionmatcher([m1, m2])
889 um = matchmod.unionmatcher([m1, m2])
888 # OPT: these next three could probably be False as well.
890 # OPT: these next three could probably be False as well.
889 self.assertEqual(um.visitdir(b''), True)
891 self.assertEqual(um.visitdir(b''), True)
890 self.assertEqual(um.visitdir(b'dir'), True)
892 self.assertEqual(um.visitdir(b'dir'), True)
891 self.assertEqual(um.visitdir(b'dir/subdir'), True)
893 self.assertEqual(um.visitdir(b'dir/subdir'), True)
892 self.assertFalse(um.visitdir(b'dir/foo'))
894 self.assertFalse(um.visitdir(b'dir/foo'))
893 self.assertFalse(um.visitdir(b'folder'))
895 self.assertFalse(um.visitdir(b'folder'))
894 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
896 self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
895 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
897 self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
896
898
897 def testVisitchildrensetIncludeInclude4(self):
899 def testVisitchildrensetIncludeInclude4(self):
898 m1 = matchmod.match(
900 m1 = matchmod.match(
899 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
901 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/x']
900 )
902 )
901 m2 = matchmod.match(
903 m2 = matchmod.match(
902 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/z']
904 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir/z']
903 )
905 )
904 um = matchmod.unionmatcher([m1, m2])
906 um = matchmod.unionmatcher([m1, m2])
905 self.assertEqual(um.visitchildrenset(b''), {b'dir'})
907 self.assertEqual(um.visitchildrenset(b''), {b'dir'})
906 self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
908 self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
907 self.assertEqual(um.visitchildrenset(b'dir/subdir'), {b'x', b'z'})
909 self.assertEqual(um.visitchildrenset(b'dir/subdir'), {b'x', b'z'})
908 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
910 self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
909 self.assertEqual(um.visitchildrenset(b'folder'), set())
911 self.assertEqual(um.visitchildrenset(b'folder'), set())
910 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
912 self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
911 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
913 self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
912
914
913
915
914 class SubdirMatcherTests(unittest.TestCase):
916 class SubdirMatcherTests(unittest.TestCase):
915 def testVisitdir(self):
917 def testVisitdir(self):
916 m = matchmod.match(
918 m = matchmod.match(
917 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
919 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
918 )
920 )
919 sm = matchmod.subdirmatcher(b'dir', m)
921 sm = matchmod.subdirmatcher(b'dir', m)
920
922
921 self.assertEqual(sm.visitdir(b''), True)
923 self.assertEqual(sm.visitdir(b''), True)
922 self.assertEqual(sm.visitdir(b'subdir'), b'all')
924 self.assertEqual(sm.visitdir(b'subdir'), b'all')
923 # OPT: These next two should probably be 'all' not True.
925 # OPT: These next two should probably be 'all' not True.
924 self.assertEqual(sm.visitdir(b'subdir/x'), True)
926 self.assertEqual(sm.visitdir(b'subdir/x'), True)
925 self.assertEqual(sm.visitdir(b'subdir/z'), True)
927 self.assertEqual(sm.visitdir(b'subdir/z'), True)
926 self.assertFalse(sm.visitdir(b'foo'))
928 self.assertFalse(sm.visitdir(b'foo'))
927
929
928 def testVisitchildrenset(self):
930 def testVisitchildrenset(self):
929 m = matchmod.match(
931 m = matchmod.match(
930 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
932 util.localpath(b'/repo'), b'', include=[b'path:dir/subdir']
931 )
933 )
932 sm = matchmod.subdirmatcher(b'dir', m)
934 sm = matchmod.subdirmatcher(b'dir', m)
933
935
934 self.assertEqual(sm.visitchildrenset(b''), {b'subdir'})
936 self.assertEqual(sm.visitchildrenset(b''), {b'subdir'})
935 self.assertEqual(sm.visitchildrenset(b'subdir'), b'all')
937 self.assertEqual(sm.visitchildrenset(b'subdir'), b'all')
936 # OPT: These next two should probably be 'all' not 'this'.
938 # OPT: These next two should probably be 'all' not 'this'.
937 self.assertEqual(sm.visitchildrenset(b'subdir/x'), b'this')
939 self.assertEqual(sm.visitchildrenset(b'subdir/x'), b'this')
938 self.assertEqual(sm.visitchildrenset(b'subdir/z'), b'this')
940 self.assertEqual(sm.visitchildrenset(b'subdir/z'), b'this')
939 self.assertEqual(sm.visitchildrenset(b'foo'), set())
941 self.assertEqual(sm.visitchildrenset(b'foo'), set())
940
942
941
943
942 class PrefixdirMatcherTests(unittest.TestCase):
944 class PrefixdirMatcherTests(unittest.TestCase):
943 def testVisitdir(self):
945 def testVisitdir(self):
944 m = matchmod.match(
946 m = matchmod.match(
945 util.localpath(b'/root/d'),
947 util.localpath(b'/root/d'),
946 b'e/f',
948 b'e/f',
947 [b'../a.txt', b'b.txt'],
949 [b'../a.txt', b'b.txt'],
948 auditor=noop_auditor,
950 auditor=noop_auditor,
949 )
951 )
950 pm = matchmod.prefixdirmatcher(b'd', m)
952 pm = matchmod.prefixdirmatcher(b'd', m)
951
953
952 # `m` elides 'd' because it's part of the root, and the rest of the
954 # `m` elides 'd' because it's part of the root, and the rest of the
953 # patterns are relative.
955 # patterns are relative.
954 self.assertEqual(bool(m(b'a.txt')), False)
956 self.assertEqual(bool(m(b'a.txt')), False)
955 self.assertEqual(bool(m(b'b.txt')), False)
957 self.assertEqual(bool(m(b'b.txt')), False)
956 self.assertEqual(bool(m(b'e/a.txt')), True)
958 self.assertEqual(bool(m(b'e/a.txt')), True)
957 self.assertEqual(bool(m(b'e/b.txt')), False)
959 self.assertEqual(bool(m(b'e/b.txt')), False)
958 self.assertEqual(bool(m(b'e/f/b.txt')), True)
960 self.assertEqual(bool(m(b'e/f/b.txt')), True)
959
961
960 # The prefix matcher re-adds 'd' to the paths, so they need to be
962 # The prefix matcher re-adds 'd' to the paths, so they need to be
961 # specified when using the prefixdirmatcher.
963 # specified when using the prefixdirmatcher.
962 self.assertEqual(bool(pm(b'a.txt')), False)
964 self.assertEqual(bool(pm(b'a.txt')), False)
963 self.assertEqual(bool(pm(b'b.txt')), False)
965 self.assertEqual(bool(pm(b'b.txt')), False)
964 self.assertEqual(bool(pm(b'd/e/a.txt')), True)
966 self.assertEqual(bool(pm(b'd/e/a.txt')), True)
965 self.assertEqual(bool(pm(b'd/e/b.txt')), False)
967 self.assertEqual(bool(pm(b'd/e/b.txt')), False)
966 self.assertEqual(bool(pm(b'd/e/f/b.txt')), True)
968 self.assertEqual(bool(pm(b'd/e/f/b.txt')), True)
967
969
968 self.assertEqual(m.visitdir(b''), True)
970 self.assertEqual(m.visitdir(b''), True)
969 self.assertEqual(m.visitdir(b'e'), True)
971 self.assertEqual(m.visitdir(b'e'), True)
970 self.assertEqual(m.visitdir(b'e/f'), True)
972 self.assertEqual(m.visitdir(b'e/f'), True)
971 self.assertEqual(m.visitdir(b'e/f/g'), False)
973 self.assertEqual(m.visitdir(b'e/f/g'), False)
972
974
973 self.assertEqual(pm.visitdir(b''), True)
975 self.assertEqual(pm.visitdir(b''), True)
974 self.assertEqual(pm.visitdir(b'd'), True)
976 self.assertEqual(pm.visitdir(b'd'), True)
975 self.assertEqual(pm.visitdir(b'd/e'), True)
977 self.assertEqual(pm.visitdir(b'd/e'), True)
976 self.assertEqual(pm.visitdir(b'd/e/f'), True)
978 self.assertEqual(pm.visitdir(b'd/e/f'), True)
977 self.assertEqual(pm.visitdir(b'd/e/f/g'), False)
979 self.assertEqual(pm.visitdir(b'd/e/f/g'), False)
978
980
979 def testVisitchildrenset(self):
981 def testVisitchildrenset(self):
980 m = matchmod.match(
982 m = matchmod.match(
981 util.localpath(b'/root/d'),
983 util.localpath(b'/root/d'),
982 b'e/f',
984 b'e/f',
983 [b'../a.txt', b'b.txt'],
985 [b'../a.txt', b'b.txt'],
984 auditor=noop_auditor,
986 auditor=noop_auditor,
985 )
987 )
986 pm = matchmod.prefixdirmatcher(b'd', m)
988 pm = matchmod.prefixdirmatcher(b'd', m)
987
989
988 # OPT: visitchildrenset could possibly return {'e'} and {'f'} for these
990 # OPT: visitchildrenset could possibly return {'e'} and {'f'} for these
989 # next two, respectively; patternmatcher does not have this
991 # next two, respectively; patternmatcher does not have this
990 # optimization.
992 # optimization.
991 self.assertEqual(m.visitchildrenset(b''), b'this')
993 self.assertEqual(m.visitchildrenset(b''), b'this')
992 self.assertEqual(m.visitchildrenset(b'e'), b'this')
994 self.assertEqual(m.visitchildrenset(b'e'), b'this')
993 self.assertEqual(m.visitchildrenset(b'e/f'), b'this')
995 self.assertEqual(m.visitchildrenset(b'e/f'), b'this')
994 self.assertEqual(m.visitchildrenset(b'e/f/g'), set())
996 self.assertEqual(m.visitchildrenset(b'e/f/g'), set())
995
997
996 # OPT: visitchildrenset could possibly return {'d'}, {'e'}, and {'f'}
998 # OPT: visitchildrenset could possibly return {'d'}, {'e'}, and {'f'}
997 # for these next three, respectively; patternmatcher does not have this
999 # for these next three, respectively; patternmatcher does not have this
998 # optimization.
1000 # optimization.
999 self.assertEqual(pm.visitchildrenset(b''), b'this')
1001 self.assertEqual(pm.visitchildrenset(b''), b'this')
1000 self.assertEqual(pm.visitchildrenset(b'd'), b'this')
1002 self.assertEqual(pm.visitchildrenset(b'd'), b'this')
1001 self.assertEqual(pm.visitchildrenset(b'd/e'), b'this')
1003 self.assertEqual(pm.visitchildrenset(b'd/e'), b'this')
1002 self.assertEqual(pm.visitchildrenset(b'd/e/f'), b'this')
1004 self.assertEqual(pm.visitchildrenset(b'd/e/f'), b'this')
1003 self.assertEqual(pm.visitchildrenset(b'd/e/f/g'), set())
1005 self.assertEqual(pm.visitchildrenset(b'd/e/f/g'), set())
1004
1006
1005
1007
1006 if __name__ == '__main__':
1008 if __name__ == '__main__':
1007 silenttestrunner.main(__name__)
1009 silenttestrunner.main(__name__)
@@ -1,1053 +1,1054 b''
1 #testcases dirstate-v1 dirstate-v2
1 #testcases dirstate-v1 dirstate-v2
2
2
3 #if dirstate-v2
3 #if dirstate-v2
4 $ cat >> $HGRCPATH << EOF
4 $ cat >> $HGRCPATH << EOF
5 > [format]
5 > [format]
6 > use-dirstate-v2=1
6 > use-dirstate-v2=1
7 > [storage]
7 > [storage]
8 > dirstate-v2.slow-path=allow
8 > dirstate-v2.slow-path=allow
9 > EOF
9 > EOF
10 #endif
10 #endif
11
11
12 $ hg init repo1
12 $ hg init repo1
13 $ cd repo1
13 $ cd repo1
14 $ mkdir a b a/1 b/1 b/2
14 $ mkdir a b a/1 b/1 b/2
15 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
15 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
16
16
17 hg status in repo root:
17 hg status in repo root:
18
18
19 $ hg status
19 $ hg status
20 ? a/1/in_a_1
20 ? a/1/in_a_1
21 ? a/in_a
21 ? a/in_a
22 ? b/1/in_b_1
22 ? b/1/in_b_1
23 ? b/2/in_b_2
23 ? b/2/in_b_2
24 ? b/in_b
24 ? b/in_b
25 ? in_root
25 ? in_root
26
26
27 hg status . in repo root:
27 hg status . in repo root:
28
28
29 $ hg status .
29 $ hg status .
30 ? a/1/in_a_1
30 ? a/1/in_a_1
31 ? a/in_a
31 ? a/in_a
32 ? b/1/in_b_1
32 ? b/1/in_b_1
33 ? b/2/in_b_2
33 ? b/2/in_b_2
34 ? b/in_b
34 ? b/in_b
35 ? in_root
35 ? in_root
36
36
37 $ hg status --cwd a
37 $ hg status --cwd a
38 ? a/1/in_a_1
38 ? a/1/in_a_1
39 ? a/in_a
39 ? a/in_a
40 ? b/1/in_b_1
40 ? b/1/in_b_1
41 ? b/2/in_b_2
41 ? b/2/in_b_2
42 ? b/in_b
42 ? b/in_b
43 ? in_root
43 ? in_root
44 $ hg status --cwd a .
44 $ hg status --cwd a .
45 ? 1/in_a_1
45 ? 1/in_a_1
46 ? in_a
46 ? in_a
47 $ hg status --cwd a ..
47 $ hg status --cwd a ..
48 ? 1/in_a_1
48 ? 1/in_a_1
49 ? in_a
49 ? in_a
50 ? ../b/1/in_b_1
50 ? ../b/1/in_b_1
51 ? ../b/2/in_b_2
51 ? ../b/2/in_b_2
52 ? ../b/in_b
52 ? ../b/in_b
53 ? ../in_root
53 ? ../in_root
54
54
55 $ hg status --cwd b
55 $ hg status --cwd b
56 ? a/1/in_a_1
56 ? a/1/in_a_1
57 ? a/in_a
57 ? a/in_a
58 ? b/1/in_b_1
58 ? b/1/in_b_1
59 ? b/2/in_b_2
59 ? b/2/in_b_2
60 ? b/in_b
60 ? b/in_b
61 ? in_root
61 ? in_root
62 $ hg status --cwd b .
62 $ hg status --cwd b .
63 ? 1/in_b_1
63 ? 1/in_b_1
64 ? 2/in_b_2
64 ? 2/in_b_2
65 ? in_b
65 ? in_b
66 $ hg status --cwd b ..
66 $ hg status --cwd b ..
67 ? ../a/1/in_a_1
67 ? ../a/1/in_a_1
68 ? ../a/in_a
68 ? ../a/in_a
69 ? 1/in_b_1
69 ? 1/in_b_1
70 ? 2/in_b_2
70 ? 2/in_b_2
71 ? in_b
71 ? in_b
72 ? ../in_root
72 ? ../in_root
73
73
74 $ hg status --cwd a/1
74 $ hg status --cwd a/1
75 ? a/1/in_a_1
75 ? a/1/in_a_1
76 ? a/in_a
76 ? a/in_a
77 ? b/1/in_b_1
77 ? b/1/in_b_1
78 ? b/2/in_b_2
78 ? b/2/in_b_2
79 ? b/in_b
79 ? b/in_b
80 ? in_root
80 ? in_root
81 $ hg status --cwd a/1 .
81 $ hg status --cwd a/1 .
82 ? in_a_1
82 ? in_a_1
83 $ hg status --cwd a/1 ..
83 $ hg status --cwd a/1 ..
84 ? in_a_1
84 ? in_a_1
85 ? ../in_a
85 ? ../in_a
86
86
87 $ hg status --cwd b/1
87 $ hg status --cwd b/1
88 ? a/1/in_a_1
88 ? a/1/in_a_1
89 ? a/in_a
89 ? a/in_a
90 ? b/1/in_b_1
90 ? b/1/in_b_1
91 ? b/2/in_b_2
91 ? b/2/in_b_2
92 ? b/in_b
92 ? b/in_b
93 ? in_root
93 ? in_root
94 $ hg status --cwd b/1 .
94 $ hg status --cwd b/1 .
95 ? in_b_1
95 ? in_b_1
96 $ hg status --cwd b/1 ..
96 $ hg status --cwd b/1 ..
97 ? in_b_1
97 ? in_b_1
98 ? ../2/in_b_2
98 ? ../2/in_b_2
99 ? ../in_b
99 ? ../in_b
100
100
101 $ hg status --cwd b/2
101 $ hg status --cwd b/2
102 ? a/1/in_a_1
102 ? a/1/in_a_1
103 ? a/in_a
103 ? a/in_a
104 ? b/1/in_b_1
104 ? b/1/in_b_1
105 ? b/2/in_b_2
105 ? b/2/in_b_2
106 ? b/in_b
106 ? b/in_b
107 ? in_root
107 ? in_root
108 $ hg status --cwd b/2 .
108 $ hg status --cwd b/2 .
109 ? in_b_2
109 ? in_b_2
110 $ hg status --cwd b/2 ..
110 $ hg status --cwd b/2 ..
111 ? ../1/in_b_1
111 ? ../1/in_b_1
112 ? in_b_2
112 ? in_b_2
113 ? ../in_b
113 ? ../in_b
114
114
115 combining patterns with root and patterns without a root works
115 combining patterns with root and patterns without a root works
116
116
117 $ hg st a/in_a re:.*b$
117 $ hg st a/in_a re:.*b$
118 ? a/in_a
118 ? a/in_a
119 ? b/in_b
119 ? b/in_b
120
120
121 tweaking defaults works
121 tweaking defaults works
122 $ hg status --cwd a --config ui.tweakdefaults=yes
122 $ hg status --cwd a --config ui.tweakdefaults=yes
123 ? 1/in_a_1
123 ? 1/in_a_1
124 ? in_a
124 ? in_a
125 ? ../b/1/in_b_1
125 ? ../b/1/in_b_1
126 ? ../b/2/in_b_2
126 ? ../b/2/in_b_2
127 ? ../b/in_b
127 ? ../b/in_b
128 ? ../in_root
128 ? ../in_root
129 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
129 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
130 ? a/1/in_a_1 (glob)
130 ? a/1/in_a_1 (glob)
131 ? a/in_a (glob)
131 ? a/in_a (glob)
132 ? b/1/in_b_1 (glob)
132 ? b/1/in_b_1 (glob)
133 ? b/2/in_b_2 (glob)
133 ? b/2/in_b_2 (glob)
134 ? b/in_b (glob)
134 ? b/in_b (glob)
135 ? in_root
135 ? in_root
136 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
136 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
137 ? 1/in_a_1
137 ? 1/in_a_1
138 ? in_a
138 ? in_a
139 ? ../b/1/in_b_1
139 ? ../b/1/in_b_1
140 ? ../b/2/in_b_2
140 ? ../b/2/in_b_2
141 ? ../b/in_b
141 ? ../b/in_b
142 ? ../in_root (glob)
142 ? ../in_root (glob)
143
143
144 relative paths can be requested
144 relative paths can be requested
145
145
146 $ hg status --cwd a --config ui.relative-paths=yes
146 $ hg status --cwd a --config ui.relative-paths=yes
147 ? 1/in_a_1
147 ? 1/in_a_1
148 ? in_a
148 ? in_a
149 ? ../b/1/in_b_1
149 ? ../b/1/in_b_1
150 ? ../b/2/in_b_2
150 ? ../b/2/in_b_2
151 ? ../b/in_b
151 ? ../b/in_b
152 ? ../in_root
152 ? ../in_root
153
153
154 $ hg status --cwd a . --config ui.relative-paths=legacy
154 $ hg status --cwd a . --config ui.relative-paths=legacy
155 ? 1/in_a_1
155 ? 1/in_a_1
156 ? in_a
156 ? in_a
157 $ hg status --cwd a . --config ui.relative-paths=no
157 $ hg status --cwd a . --config ui.relative-paths=no
158 ? a/1/in_a_1
158 ? a/1/in_a_1
159 ? a/in_a
159 ? a/in_a
160
160
161 commands.status.relative overrides ui.relative-paths
161 commands.status.relative overrides ui.relative-paths
162
162
163 $ cat >> $HGRCPATH <<EOF
163 $ cat >> $HGRCPATH <<EOF
164 > [ui]
164 > [ui]
165 > relative-paths = False
165 > relative-paths = False
166 > [commands]
166 > [commands]
167 > status.relative = True
167 > status.relative = True
168 > EOF
168 > EOF
169 $ hg status --cwd a
169 $ hg status --cwd a
170 ? 1/in_a_1
170 ? 1/in_a_1
171 ? in_a
171 ? in_a
172 ? ../b/1/in_b_1
172 ? ../b/1/in_b_1
173 ? ../b/2/in_b_2
173 ? ../b/2/in_b_2
174 ? ../b/in_b
174 ? ../b/in_b
175 ? ../in_root
175 ? ../in_root
176 $ HGPLAIN=1 hg status --cwd a
176 $ HGPLAIN=1 hg status --cwd a
177 ? a/1/in_a_1 (glob)
177 ? a/1/in_a_1 (glob)
178 ? a/in_a (glob)
178 ? a/in_a (glob)
179 ? b/1/in_b_1 (glob)
179 ? b/1/in_b_1 (glob)
180 ? b/2/in_b_2 (glob)
180 ? b/2/in_b_2 (glob)
181 ? b/in_b (glob)
181 ? b/in_b (glob)
182 ? in_root
182 ? in_root
183
183
184 if relative paths are explicitly off, tweakdefaults doesn't change it
184 if relative paths are explicitly off, tweakdefaults doesn't change it
185 $ cat >> $HGRCPATH <<EOF
185 $ cat >> $HGRCPATH <<EOF
186 > [commands]
186 > [commands]
187 > status.relative = False
187 > status.relative = False
188 > EOF
188 > EOF
189 $ hg status --cwd a --config ui.tweakdefaults=yes
189 $ hg status --cwd a --config ui.tweakdefaults=yes
190 ? a/1/in_a_1
190 ? a/1/in_a_1
191 ? a/in_a
191 ? a/in_a
192 ? b/1/in_b_1
192 ? b/1/in_b_1
193 ? b/2/in_b_2
193 ? b/2/in_b_2
194 ? b/in_b
194 ? b/in_b
195 ? in_root
195 ? in_root
196
196
197 $ cd ..
197 $ cd ..
198
198
199 $ hg init repo2
199 $ hg init repo2
200 $ cd repo2
200 $ cd repo2
201 $ touch modified removed deleted ignored
201 $ touch modified removed deleted ignored
202 $ echo "^ignored$" > .hgignore
202 $ echo "^ignored$" > .hgignore
203 $ hg ci -A -m 'initial checkin'
203 $ hg ci -A -m 'initial checkin'
204 adding .hgignore
204 adding .hgignore
205 adding deleted
205 adding deleted
206 adding modified
206 adding modified
207 adding removed
207 adding removed
208 $ touch modified added unknown ignored
208 $ touch modified added unknown ignored
209 $ hg add added
209 $ hg add added
210 $ hg remove removed
210 $ hg remove removed
211 $ rm deleted
211 $ rm deleted
212
212
213 hg status:
213 hg status:
214
214
215 $ hg status
215 $ hg status
216 A added
216 A added
217 R removed
217 R removed
218 ! deleted
218 ! deleted
219 ? unknown
219 ? unknown
220
220
221 hg status -n:
221 hg status -n:
222 $ env RHG_ON_UNSUPPORTED=abort hg status -n
222 $ env RHG_ON_UNSUPPORTED=abort hg status -n
223 added
223 added
224 removed
224 removed
225 deleted
225 deleted
226 unknown
226 unknown
227
227
228 hg status modified added removed deleted unknown never-existed ignored:
228 hg status modified added removed deleted unknown never-existed ignored:
229
229
230 $ hg status modified added removed deleted unknown never-existed ignored
230 $ hg status modified added removed deleted unknown never-existed ignored
231 never-existed: * (glob)
231 never-existed: * (glob)
232 A added
232 A added
233 R removed
233 R removed
234 ! deleted
234 ! deleted
235 ? unknown
235 ? unknown
236
236
237 $ hg copy modified copied
237 $ hg copy modified copied
238
238
239 hg status -C:
239 hg status -C:
240
240
241 $ hg status -C
241 $ hg status -C
242 A added
242 A added
243 A copied
243 A copied
244 modified
244 modified
245 R removed
245 R removed
246 ! deleted
246 ! deleted
247 ? unknown
247 ? unknown
248
248
249 hg status -0:
249 hg status -0:
250
250
251 $ hg status -0 --config rhg.on-unsupported=abort
251 $ hg status -0 --config rhg.on-unsupported=abort
252 A added\x00A copied\x00R removed\x00! deleted\x00? unknown\x00 (no-eol) (esc)
252 A added\x00A copied\x00R removed\x00! deleted\x00? unknown\x00 (no-eol) (esc)
253
253
254 hg status -A:
254 hg status -A:
255
255
256 $ hg status -A
256 $ hg status -A
257 A added
257 A added
258 A copied
258 A copied
259 modified
259 modified
260 R removed
260 R removed
261 ! deleted
261 ! deleted
262 ? unknown
262 ? unknown
263 I ignored
263 I ignored
264 C .hgignore
264 C .hgignore
265 C modified
265 C modified
266
266
267 $ hg status -A -T '{status} {path} {node|shortest}\n'
267 $ hg status -A -T '{status} {path} {node|shortest}\n'
268 A added ffff
268 A added ffff
269 A copied ffff
269 A copied ffff
270 R removed ffff
270 R removed ffff
271 ! deleted ffff
271 ! deleted ffff
272 ? unknown ffff
272 ? unknown ffff
273 I ignored ffff
273 I ignored ffff
274 C .hgignore ffff
274 C .hgignore ffff
275 C modified ffff
275 C modified ffff
276
276
277 $ hg status -A -Tjson
277 $ hg status -A -Tjson
278 [
278 [
279 {
279 {
280 "itemtype": "file",
280 "itemtype": "file",
281 "path": "added",
281 "path": "added",
282 "status": "A"
282 "status": "A"
283 },
283 },
284 {
284 {
285 "itemtype": "file",
285 "itemtype": "file",
286 "path": "copied",
286 "path": "copied",
287 "source": "modified",
287 "source": "modified",
288 "status": "A"
288 "status": "A"
289 },
289 },
290 {
290 {
291 "itemtype": "file",
291 "itemtype": "file",
292 "path": "removed",
292 "path": "removed",
293 "status": "R"
293 "status": "R"
294 },
294 },
295 {
295 {
296 "itemtype": "file",
296 "itemtype": "file",
297 "path": "deleted",
297 "path": "deleted",
298 "status": "!"
298 "status": "!"
299 },
299 },
300 {
300 {
301 "itemtype": "file",
301 "itemtype": "file",
302 "path": "unknown",
302 "path": "unknown",
303 "status": "?"
303 "status": "?"
304 },
304 },
305 {
305 {
306 "itemtype": "file",
306 "itemtype": "file",
307 "path": "ignored",
307 "path": "ignored",
308 "status": "I"
308 "status": "I"
309 },
309 },
310 {
310 {
311 "itemtype": "file",
311 "itemtype": "file",
312 "path": ".hgignore",
312 "path": ".hgignore",
313 "status": "C"
313 "status": "C"
314 },
314 },
315 {
315 {
316 "itemtype": "file",
316 "itemtype": "file",
317 "path": "modified",
317 "path": "modified",
318 "status": "C"
318 "status": "C"
319 }
319 }
320 ]
320 ]
321
321
322 $ hg status -A -Tpickle > pickle
322 $ hg status -A -Tpickle > pickle
323 >>> import pickle
323 >>> import pickle
324 >>> from mercurial import util
324 >>> from mercurial import util
325 >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
325 >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
326 >>> for s, p in data: print("%s %s" % (s, p))
326 >>> for s, p in data: print("%s %s" % (s, p))
327 ! deleted
327 ! deleted
328 ? pickle
328 ? pickle
329 ? unknown
329 ? unknown
330 A added
330 A added
331 A copied
331 A copied
332 C .hgignore
332 C .hgignore
333 C modified
333 C modified
334 I ignored
334 I ignored
335 R removed
335 R removed
336 $ rm pickle
336 $ rm pickle
337
337
338 $ echo "^ignoreddir$" > .hgignore
338 $ echo "^ignoreddir$" > .hgignore
339 $ mkdir ignoreddir
339 $ mkdir ignoreddir
340 $ touch ignoreddir/file
340 $ touch ignoreddir/file
341
341
342 Test templater support:
342 Test templater support:
343
343
344 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
344 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
345 [M] .hgignore
345 [M] .hgignore
346 [A] added
346 [A] added
347 [A] modified -> copied
347 [A] modified -> copied
348 [R] removed
348 [R] removed
349 [!] deleted
349 [!] deleted
350 [?] ignored
350 [?] ignored
351 [?] unknown
351 [?] unknown
352 [I] ignoreddir/file
352 [I] ignoreddir/file
353 [C] modified
353 [C] modified
354 $ hg status -AT default
354 $ hg status -AT default
355 M .hgignore
355 M .hgignore
356 A added
356 A added
357 A copied
357 A copied
358 modified
358 modified
359 R removed
359 R removed
360 ! deleted
360 ! deleted
361 ? ignored
361 ? ignored
362 ? unknown
362 ? unknown
363 I ignoreddir/file
363 I ignoreddir/file
364 C modified
364 C modified
365 $ hg status -T compact
365 $ hg status -T compact
366 abort: "status" not in template map
366 abort: "status" not in template map
367 [255]
367 [255]
368
368
369 hg status ignoreddir/file:
369 hg status ignoreddir/file:
370
370
371 $ hg status ignoreddir/file
371 $ hg status ignoreddir/file
372
372
373 hg status -i ignoreddir/file:
373 hg status -i ignoreddir/file:
374
374
375 $ hg status -i ignoreddir/file
375 $ hg status -i ignoreddir/file
376 I ignoreddir/file
376 I ignoreddir/file
377 $ cd ..
377 $ cd ..
378
378
379 Check 'status -q' and some combinations
379 Check 'status -q' and some combinations
380
380
381 $ hg init repo3
381 $ hg init repo3
382 $ cd repo3
382 $ cd repo3
383 $ touch modified removed deleted ignored
383 $ touch modified removed deleted ignored
384 $ echo "^ignored$" > .hgignore
384 $ echo "^ignored$" > .hgignore
385 $ hg commit -A -m 'initial checkin'
385 $ hg commit -A -m 'initial checkin'
386 adding .hgignore
386 adding .hgignore
387 adding deleted
387 adding deleted
388 adding modified
388 adding modified
389 adding removed
389 adding removed
390 $ touch added unknown ignored
390 $ touch added unknown ignored
391 $ hg add added
391 $ hg add added
392 $ echo "test" >> modified
392 $ echo "test" >> modified
393 $ hg remove removed
393 $ hg remove removed
394 $ rm deleted
394 $ rm deleted
395 $ hg copy modified copied
395 $ hg copy modified copied
396
396
397 Specify working directory revision explicitly, that should be the same as
397 Specify working directory revision explicitly, that should be the same as
398 "hg status"
398 "hg status"
399
399
400 $ hg status --change "wdir()"
400 $ hg status --change "wdir()"
401 M modified
401 M modified
402 A added
402 A added
403 A copied
403 A copied
404 R removed
404 R removed
405 ! deleted
405 ! deleted
406 ? unknown
406 ? unknown
407
407
408 Run status with 2 different flags.
408 Run status with 2 different flags.
409 Check if result is the same or different.
409 Check if result is the same or different.
410 If result is not as expected, raise error
410 If result is not as expected, raise error
411
411
412 $ assert() {
412 $ assert() {
413 > hg status $1 > ../a
413 > hg status $1 > ../a
414 > hg status $2 > ../b
414 > hg status $2 > ../b
415 > if diff ../a ../b > /dev/null; then
415 > if diff ../a ../b > /dev/null; then
416 > out=0
416 > out=0
417 > else
417 > else
418 > out=1
418 > out=1
419 > fi
419 > fi
420 > if [ $3 -eq 0 ]; then
420 > if [ $3 -eq 0 ]; then
421 > df="same"
421 > df="same"
422 > else
422 > else
423 > df="different"
423 > df="different"
424 > fi
424 > fi
425 > if [ $out -ne $3 ]; then
425 > if [ $out -ne $3 ]; then
426 > echo "Error on $1 and $2, should be $df."
426 > echo "Error on $1 and $2, should be $df."
427 > fi
427 > fi
428 > }
428 > }
429
429
430 Assert flag1 flag2 [0-same | 1-different]
430 Assert flag1 flag2 [0-same | 1-different]
431
431
432 $ assert "-q" "-mard" 0
432 $ assert "-q" "-mard" 0
433 $ assert "-A" "-marduicC" 0
433 $ assert "-A" "-marduicC" 0
434 $ assert "-qA" "-mardcC" 0
434 $ assert "-qA" "-mardcC" 0
435 $ assert "-qAui" "-A" 0
435 $ assert "-qAui" "-A" 0
436 $ assert "-qAu" "-marducC" 0
436 $ assert "-qAu" "-marducC" 0
437 $ assert "-qAi" "-mardicC" 0
437 $ assert "-qAi" "-mardicC" 0
438 $ assert "-qu" "-u" 0
438 $ assert "-qu" "-u" 0
439 $ assert "-q" "-u" 1
439 $ assert "-q" "-u" 1
440 $ assert "-m" "-a" 1
440 $ assert "-m" "-a" 1
441 $ assert "-r" "-d" 1
441 $ assert "-r" "-d" 1
442 $ cd ..
442 $ cd ..
443
443
444 $ hg init repo4
444 $ hg init repo4
445 $ cd repo4
445 $ cd repo4
446 $ touch modified removed deleted
446 $ touch modified removed deleted
447 $ hg ci -q -A -m 'initial checkin'
447 $ hg ci -q -A -m 'initial checkin'
448 $ touch added unknown
448 $ touch added unknown
449 $ hg add added
449 $ hg add added
450 $ hg remove removed
450 $ hg remove removed
451 $ rm deleted
451 $ rm deleted
452 $ echo x > modified
452 $ echo x > modified
453 $ hg copy modified copied
453 $ hg copy modified copied
454 $ hg ci -m 'test checkin' -d "1000001 0"
454 $ hg ci -m 'test checkin' -d "1000001 0"
455 $ rm *
455 $ rm *
456 $ touch unrelated
456 $ touch unrelated
457 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
457 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
458
458
459 hg status --change 1:
459 hg status --change 1:
460
460
461 $ hg status --change 1
461 $ hg status --change 1
462 M modified
462 M modified
463 A added
463 A added
464 A copied
464 A copied
465 R removed
465 R removed
466
466
467 hg status --change 1 unrelated:
467 hg status --change 1 unrelated:
468
468
469 $ hg status --change 1 unrelated
469 $ hg status --change 1 unrelated
470
470
471 hg status -C --change 1 added modified copied removed deleted:
471 hg status -C --change 1 added modified copied removed deleted:
472
472
473 $ hg status -C --change 1 added modified copied removed deleted
473 $ hg status -C --change 1 added modified copied removed deleted
474 M modified
474 M modified
475 A added
475 A added
476 A copied
476 A copied
477 modified
477 modified
478 R removed
478 R removed
479
479
480 hg status -A --change 1 and revset:
480 hg status -A --change 1 and revset:
481
481
482 $ hg status -A --change '1|1'
482 $ hg status -A --change '1|1'
483 M modified
483 M modified
484 A added
484 A added
485 A copied
485 A copied
486 modified
486 modified
487 R removed
487 R removed
488 C deleted
488 C deleted
489
489
490 $ cd ..
490 $ cd ..
491
491
492 hg status with --rev and reverted changes:
492 hg status with --rev and reverted changes:
493
493
494 $ hg init reverted-changes-repo
494 $ hg init reverted-changes-repo
495 $ cd reverted-changes-repo
495 $ cd reverted-changes-repo
496 $ echo a > file
496 $ echo a > file
497 $ hg add file
497 $ hg add file
498 $ hg ci -m a
498 $ hg ci -m a
499 $ echo b > file
499 $ echo b > file
500 $ hg ci -m b
500 $ hg ci -m b
501
501
502 reverted file should appear clean
502 reverted file should appear clean
503
503
504 $ hg revert -r 0 .
504 $ hg revert -r 0 .
505 reverting file
505 reverting file
506 $ hg status -A --rev 0
506 $ hg status -A --rev 0
507 C file
507 C file
508
508
509 #if execbit
509 #if execbit
510 reverted file with changed flag should appear modified
510 reverted file with changed flag should appear modified
511
511
512 $ chmod +x file
512 $ chmod +x file
513 $ hg status -A --rev 0
513 $ hg status -A --rev 0
514 M file
514 M file
515
515
516 $ hg revert -r 0 .
516 $ hg revert -r 0 .
517 reverting file
517 reverting file
518
518
519 reverted and committed file with changed flag should appear modified
519 reverted and committed file with changed flag should appear modified
520
520
521 $ hg co -C .
521 $ hg co -C .
522 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
522 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
523 $ chmod +x file
523 $ chmod +x file
524 $ hg ci -m 'change flag'
524 $ hg ci -m 'change flag'
525 $ hg status -A --rev 1 --rev 2
525 $ hg status -A --rev 1 --rev 2
526 M file
526 M file
527 $ hg diff -r 1 -r 2
527 $ hg diff -r 1 -r 2
528
528
529 #endif
529 #endif
530
530
531 $ cd ..
531 $ cd ..
532
532
533 hg status of binary file starting with '\1\n', a separator for metadata:
533 hg status of binary file starting with '\1\n', a separator for metadata:
534
534
535 $ hg init repo5
535 $ hg init repo5
536 $ cd repo5
536 $ cd repo5
537 >>> open("010a", r"wb").write(b"\1\nfoo") and None
537 >>> open("010a", r"wb").write(b"\1\nfoo") and None
538 $ hg ci -q -A -m 'initial checkin'
538 $ hg ci -q -A -m 'initial checkin'
539 $ hg status -A
539 $ hg status -A
540 C 010a
540 C 010a
541
541
542 >>> open("010a", r"wb").write(b"\1\nbar") and None
542 >>> open("010a", r"wb").write(b"\1\nbar") and None
543 $ hg status -A
543 $ hg status -A
544 M 010a
544 M 010a
545 $ hg ci -q -m 'modify 010a'
545 $ hg ci -q -m 'modify 010a'
546 $ hg status -A --rev 0:1
546 $ hg status -A --rev 0:1
547 M 010a
547 M 010a
548
548
549 $ touch empty
549 $ touch empty
550 $ hg ci -q -A -m 'add another file'
550 $ hg ci -q -A -m 'add another file'
551 $ hg status -A --rev 1:2 010a
551 $ hg status -A --rev 1:2 010a
552 C 010a
552 C 010a
553
553
554 $ cd ..
554 $ cd ..
555
555
556 test "hg status" with "directory pattern" which matches against files
556 test "hg status" with "directory pattern" which matches against files
557 only known on target revision.
557 only known on target revision.
558
558
559 $ hg init repo6
559 $ hg init repo6
560 $ cd repo6
560 $ cd repo6
561
561
562 $ echo a > a.txt
562 $ echo a > a.txt
563 $ hg add a.txt
563 $ hg add a.txt
564 $ hg commit -m '#0'
564 $ hg commit -m '#0'
565 $ mkdir -p 1/2/3/4/5
565 $ mkdir -p 1/2/3/4/5
566 $ echo b > 1/2/3/4/5/b.txt
566 $ echo b > 1/2/3/4/5/b.txt
567 $ hg add 1/2/3/4/5/b.txt
567 $ hg add 1/2/3/4/5/b.txt
568 $ hg commit -m '#1'
568 $ hg commit -m '#1'
569
569
570 $ hg update -C 0 > /dev/null
570 $ hg update -C 0 > /dev/null
571 $ hg status -A
571 $ hg status -A
572 C a.txt
572 C a.txt
573
573
574 the directory matching against specified pattern should be removed,
574 the directory matching against specified pattern should be removed,
575 because directory existence prevents 'dirstate.walk()' from showing
575 because directory existence prevents 'dirstate.walk()' from showing
576 warning message about such pattern.
576 warning message about such pattern.
577
577
578 $ test ! -d 1
578 $ test ! -d 1
579 $ hg status -A --rev 1 1/2/3/4/5/b.txt
579 $ hg status -A --rev 1 1/2/3/4/5/b.txt
580 R 1/2/3/4/5/b.txt
580 R 1/2/3/4/5/b.txt
581 $ hg status -A --rev 1 1/2/3/4/5
581 $ hg status -A --rev 1 1/2/3/4/5
582 R 1/2/3/4/5/b.txt
582 R 1/2/3/4/5/b.txt
583 $ hg status -A --rev 1 1/2/3
583 $ hg status -A --rev 1 1/2/3
584 R 1/2/3/4/5/b.txt
584 R 1/2/3/4/5/b.txt
585 $ hg status -A --rev 1 1
585 $ hg status -A --rev 1 1
586 R 1/2/3/4/5/b.txt
586 R 1/2/3/4/5/b.txt
587
587
588 $ hg status --config ui.formatdebug=True --rev 1 1
588 $ hg status --config ui.formatdebug=True --rev 1 1
589 status = [
589 status = [
590 {
590 {
591 'itemtype': 'file',
591 'itemtype': 'file',
592 'path': '1/2/3/4/5/b.txt',
592 'path': '1/2/3/4/5/b.txt',
593 'status': 'R'
593 'status': 'R'
594 },
594 },
595 ]
595 ]
596
596
597 #if windows
597 #if windows
598 $ hg --config ui.slash=false status -A --rev 1 1
598 $ hg --config ui.slash=false status -A --rev 1 1
599 R 1\2\3\4\5\b.txt
599 R 1\2\3\4\5\b.txt
600 #endif
600 #endif
601
601
602 $ cd ..
602 $ cd ..
603
603
604 Status after move overwriting a file (issue4458)
604 Status after move overwriting a file (issue4458)
605 =================================================
605 =================================================
606
606
607
607
608 $ hg init issue4458
608 $ hg init issue4458
609 $ cd issue4458
609 $ cd issue4458
610 $ echo a > a
610 $ echo a > a
611 $ echo b > b
611 $ echo b > b
612 $ hg commit -Am base
612 $ hg commit -Am base
613 adding a
613 adding a
614 adding b
614 adding b
615
615
616
616
617 with --force
617 with --force
618
618
619 $ hg mv b --force a
619 $ hg mv b --force a
620 $ hg st --copies
620 $ hg st --copies
621 M a
621 M a
622 b
622 b
623 R b
623 R b
624 $ hg revert --all
624 $ hg revert --all
625 reverting a
625 reverting a
626 undeleting b
626 undeleting b
627 $ rm *.orig
627 $ rm *.orig
628
628
629 without force
629 without force
630
630
631 $ hg rm a
631 $ hg rm a
632 $ hg st --copies
632 $ hg st --copies
633 R a
633 R a
634 $ hg mv b a
634 $ hg mv b a
635 $ hg st --copies
635 $ hg st --copies
636 M a
636 M a
637 b
637 b
638 R b
638 R b
639
639
640 using ui.statuscopies setting
640 using ui.statuscopies setting
641 $ hg st --config ui.statuscopies=true
641 $ hg st --config ui.statuscopies=true
642 M a
642 M a
643 b
643 b
644 R b
644 R b
645 $ hg st --config ui.statuscopies=true --no-copies
645 $ hg st --config ui.statuscopies=true --no-copies
646 M a
646 M a
647 R b
647 R b
648 $ hg st --config ui.statuscopies=false
648 $ hg st --config ui.statuscopies=false
649 M a
649 M a
650 R b
650 R b
651 $ hg st --config ui.statuscopies=false --copies
651 $ hg st --config ui.statuscopies=false --copies
652 M a
652 M a
653 b
653 b
654 R b
654 R b
655 $ hg st --config ui.tweakdefaults=yes
655 $ hg st --config ui.tweakdefaults=yes
656 M a
656 M a
657 b
657 b
658 R b
658 R b
659
659
660 using log status template (issue5155)
660 using log status template (issue5155)
661 $ hg log -Tstatus -r 'wdir()' -C
661 $ hg log -Tstatus -r 'wdir()' -C
662 changeset: 2147483647:ffffffffffff
662 changeset: 2147483647:ffffffffffff
663 parent: 0:8c55c58b4c0e
663 parent: 0:8c55c58b4c0e
664 user: test
664 user: test
665 date: * (glob)
665 date: * (glob)
666 files:
666 files:
667 M a
667 M a
668 b
668 b
669 R b
669 R b
670
670
671 $ hg log -GTstatus -r 'wdir()' -C
671 $ hg log -GTstatus -r 'wdir()' -C
672 o changeset: 2147483647:ffffffffffff
672 o changeset: 2147483647:ffffffffffff
673 | parent: 0:8c55c58b4c0e
673 | parent: 0:8c55c58b4c0e
674 ~ user: test
674 ~ user: test
675 date: * (glob)
675 date: * (glob)
676 files:
676 files:
677 M a
677 M a
678 b
678 b
679 R b
679 R b
680
680
681
681
682 Other "bug" highlight, the revision status does not report the copy information.
682 Other "bug" highlight, the revision status does not report the copy information.
683 This is buggy behavior.
683 This is buggy behavior.
684
684
685 $ hg commit -m 'blah'
685 $ hg commit -m 'blah'
686 $ hg st --copies --change .
686 $ hg st --copies --change .
687 M a
687 M a
688 R b
688 R b
689
689
690 using log status template, the copy information is displayed correctly.
690 using log status template, the copy information is displayed correctly.
691 $ hg log -Tstatus -r. -C
691 $ hg log -Tstatus -r. -C
692 changeset: 1:6685fde43d21
692 changeset: 1:6685fde43d21
693 tag: tip
693 tag: tip
694 user: test
694 user: test
695 date: * (glob)
695 date: * (glob)
696 summary: blah
696 summary: blah
697 files:
697 files:
698 M a
698 M a
699 b
699 b
700 R b
700 R b
701
701
702
702
703 $ cd ..
703 $ cd ..
704
704
705 Make sure .hg doesn't show up even as a symlink
705 Make sure .hg doesn't show up even as a symlink
706
706
707 $ hg init repo0
707 $ hg init repo0
708 $ mkdir symlink-repo0
708 $ mkdir symlink-repo0
709 $ cd symlink-repo0
709 $ cd symlink-repo0
710 $ ln -s ../repo0/.hg
710 $ ln -s ../repo0/.hg
711 $ hg status
711 $ hg status
712
712
713 If the size hasn’t changed but mtime has, status needs to read the contents
713 If the size hasn’t changed but mtime has, status needs to read the contents
714 of the file to check whether it has changed
714 of the file to check whether it has changed
715
715
716 $ echo 1 > a
716 $ echo 1 > a
717 $ echo 1 > b
717 $ echo 1 > b
718 $ touch -t 200102030000 a b
718 $ touch -t 200102030000 a b
719 $ hg commit -Aqm '#0'
719 $ hg commit -Aqm '#0'
720 $ echo 2 > a
720 $ echo 2 > a
721 $ touch -t 200102040000 a b
721 $ touch -t 200102040000 a b
722 $ hg status
722 $ hg status
723 M a
723 M a
724
724
725 Asking specifically for the status of a deleted/removed file
725 Asking specifically for the status of a deleted/removed file
726
726
727 $ rm a
727 $ rm a
728 $ rm b
728 $ rm b
729 $ hg status a
729 $ hg status a
730 ! a
730 ! a
731 $ hg rm a
731 $ hg rm a
732 $ hg rm b
732 $ hg rm b
733 $ hg status a
733 $ hg status a
734 R a
734 R a
735 $ hg commit -qm '#1'
735 $ hg commit -qm '#1'
736 $ hg status a
736 $ hg status a
737 a: $ENOENT$
737 a: $ENOENT$
738
738
739 Check using include flag with pattern when status does not need to traverse
739 Check using include flag with pattern when status does not need to traverse
740 the working directory (issue6483)
740 the working directory (issue6483)
741
741
742 $ cd ..
742 $ cd ..
743 $ hg init issue6483
743 $ hg init issue6483
744 $ cd issue6483
744 $ cd issue6483
745 $ touch a.py b.rs
745 $ touch a.py b.rs
746 $ hg add a.py b.rs
746 $ hg add a.py b.rs
747 $ hg st -aI "*.py"
747 $ hg st -aI "*.py"
748 A a.py
748 A a.py
749
749
750 Also check exclude pattern
750 Also check exclude pattern
751
751
752 $ hg st -aX "*.rs"
752 $ hg st -aX "*.rs"
753 A a.py
753 A a.py
754
754
755 issue6335
755 issue6335
756 When a directory containing a tracked file gets symlinked, as of 5.8
756 When a directory containing a tracked file gets symlinked, as of 5.8
757 `hg st` only gives the correct answer about clean (or deleted) files
757 `hg st` only gives the correct answer about clean (or deleted) files
758 if also listing unknowns.
758 if also listing unknowns.
759 The tree-based dirstate and status algorithm fix this:
759 The tree-based dirstate and status algorithm fix this:
760
760
761 #if symlink no-dirstate-v1 rust
761 #if symlink no-dirstate-v1 rust
762
762
763 $ cd ..
763 $ cd ..
764 $ hg init issue6335
764 $ hg init issue6335
765 $ cd issue6335
765 $ cd issue6335
766 $ mkdir foo
766 $ mkdir foo
767 $ touch foo/a
767 $ touch foo/a
768 $ hg ci -Ama
768 $ hg ci -Ama
769 adding foo/a
769 adding foo/a
770 $ mv foo bar
770 $ mv foo bar
771 $ ln -s bar foo
771 $ ln -s bar foo
772 $ hg status
772 $ hg status
773 ! foo/a
773 ! foo/a
774 ? bar/a
774 ? bar/a
775 ? foo
775 ? foo
776
776
777 $ hg status -c # incorrect output without the Rust implementation
777 $ hg status -c # incorrect output without the Rust implementation
778 $ hg status -cu
778 $ hg status -cu
779 ? bar/a
779 ? bar/a
780 ? foo
780 ? foo
781 $ hg status -d # incorrect output without the Rust implementation
781 $ hg status -d # incorrect output without the Rust implementation
782 ! foo/a
782 ! foo/a
783 $ hg status -du
783 $ hg status -du
784 ! foo/a
784 ! foo/a
785 ? bar/a
785 ? bar/a
786 ? foo
786 ? foo
787
787
788 #endif
788 #endif
789
789
790
790
791 Create a repo with files in each possible status
791 Create a repo with files in each possible status
792
792
793 $ cd ..
793 $ cd ..
794 $ hg init repo7
794 $ hg init repo7
795 $ cd repo7
795 $ cd repo7
796 $ mkdir subdir
796 $ mkdir subdir
797 $ touch clean modified deleted removed
797 $ touch clean modified deleted removed
798 $ touch subdir/clean subdir/modified subdir/deleted subdir/removed
798 $ touch subdir/clean subdir/modified subdir/deleted subdir/removed
799 $ echo ignored > .hgignore
799 $ echo ignored > .hgignore
800 $ hg ci -Aqm '#0'
800 $ hg ci -Aqm '#0'
801 $ echo 1 > modified
801 $ echo 1 > modified
802 $ echo 1 > subdir/modified
802 $ echo 1 > subdir/modified
803 $ rm deleted
803 $ rm deleted
804 $ rm subdir/deleted
804 $ rm subdir/deleted
805 $ hg rm removed
805 $ hg rm removed
806 $ hg rm subdir/removed
806 $ hg rm subdir/removed
807 $ touch unknown ignored
807 $ touch unknown ignored
808 $ touch subdir/unknown subdir/ignored
808 $ touch subdir/unknown subdir/ignored
809
809
810 Check the output
810 Check the output
811
811
812 $ hg status
812 $ hg status
813 M modified
813 M modified
814 M subdir/modified
814 M subdir/modified
815 R removed
815 R removed
816 R subdir/removed
816 R subdir/removed
817 ! deleted
817 ! deleted
818 ! subdir/deleted
818 ! subdir/deleted
819 ? subdir/unknown
819 ? subdir/unknown
820 ? unknown
820 ? unknown
821
821
822 $ hg status -mard
822 $ hg status -mard
823 M modified
823 M modified
824 M subdir/modified
824 M subdir/modified
825 R removed
825 R removed
826 R subdir/removed
826 R subdir/removed
827 ! deleted
827 ! deleted
828 ! subdir/deleted
828 ! subdir/deleted
829
829
830 $ hg status -A
830 $ hg status -A
831 M modified
831 M modified
832 M subdir/modified
832 M subdir/modified
833 R removed
833 R removed
834 R subdir/removed
834 R subdir/removed
835 ! deleted
835 ! deleted
836 ! subdir/deleted
836 ! subdir/deleted
837 ? subdir/unknown
837 ? subdir/unknown
838 ? unknown
838 ? unknown
839 I ignored
839 I ignored
840 I subdir/ignored
840 I subdir/ignored
841 C .hgignore
841 C .hgignore
842 C clean
842 C clean
843 C subdir/clean
843 C subdir/clean
844
844
845 Test various matchers interatction with dirstate code:
845 Test various matchers interatction with dirstate code:
846
846
847 $ hg status path:subdir
847 $ hg status path:subdir
848 M subdir/modified
848 M subdir/modified
849 R subdir/removed
849 R subdir/removed
850 ! subdir/deleted
850 ! subdir/deleted
851 ? subdir/unknown
851 ? subdir/unknown
852
852
853 $ hg status 'glob:subdir/*'
853 $ hg status 'glob:subdir/*'
854 M subdir/modified
854 M subdir/modified
855 R subdir/removed
855 R subdir/removed
856 ! subdir/deleted
856 ! subdir/deleted
857 ? subdir/unknown
857 ? subdir/unknown
858
858
859 FIXME: it's a bug (both in rhg and in Python) that the status below is wrong,
859 FIXME: it's a bug (both in rhg and in Python) that the status below is wrong,
860 in rhg it's empty, in Python it's missing the unknown file:
860 in rhg it's empty, in Python it's missing the unknown file:
861
861
862 $ hg status rootfilesin:subdir
862 $ hg status rootfilesin:subdir
863 M subdir/modified (no-rhg !)
863 M subdir/modified (no-rhg !)
864 R subdir/removed (no-rhg !)
864 R subdir/removed (no-rhg !)
865 ! subdir/deleted (no-rhg !)
865 ! subdir/deleted (no-rhg !)
866 ? subdir/unknown (no-rhg !)
866
867
867 Note: `hg status some-name` creates a patternmatcher which is not supported
868 Note: `hg status some-name` creates a patternmatcher which is not supported
868 yet by the Rust implementation of status, but includematcher is supported.
869 yet by the Rust implementation of status, but includematcher is supported.
869 --include is used below for that reason
870 --include is used below for that reason
870
871
871 #if unix-permissions
872 #if unix-permissions
872
873
873 Not having permission to read a directory that contains tracked files makes
874 Not having permission to read a directory that contains tracked files makes
874 status emit a warning then behave as if the directory was empty or removed
875 status emit a warning then behave as if the directory was empty or removed
875 entirely:
876 entirely:
876
877
877 $ chmod 0 subdir
878 $ chmod 0 subdir
878 $ hg status --include subdir
879 $ hg status --include subdir
879 subdir: $EACCES$
880 subdir: $EACCES$
880 R subdir/removed
881 R subdir/removed
881 ! subdir/clean
882 ! subdir/clean
882 ! subdir/deleted
883 ! subdir/deleted
883 ! subdir/modified
884 ! subdir/modified
884 $ chmod 755 subdir
885 $ chmod 755 subdir
885
886
886 #endif
887 #endif
887
888
888 Remove a directory that contains tracked files
889 Remove a directory that contains tracked files
889
890
890 $ rm -r subdir
891 $ rm -r subdir
891 $ hg status --include subdir
892 $ hg status --include subdir
892 R subdir/removed
893 R subdir/removed
893 ! subdir/clean
894 ! subdir/clean
894 ! subdir/deleted
895 ! subdir/deleted
895 ! subdir/modified
896 ! subdir/modified
896
897
897 … and replace it by a file
898 … and replace it by a file
898
899
899 $ touch subdir
900 $ touch subdir
900 $ hg status --include subdir
901 $ hg status --include subdir
901 R subdir/removed
902 R subdir/removed
902 ! subdir/clean
903 ! subdir/clean
903 ! subdir/deleted
904 ! subdir/deleted
904 ! subdir/modified
905 ! subdir/modified
905 ? subdir
906 ? subdir
906
907
907 Replaced a deleted or removed file with a directory
908 Replaced a deleted or removed file with a directory
908
909
909 $ mkdir deleted removed
910 $ mkdir deleted removed
910 $ touch deleted/1 removed/1
911 $ touch deleted/1 removed/1
911 $ hg status --include deleted --include removed
912 $ hg status --include deleted --include removed
912 R removed
913 R removed
913 ! deleted
914 ! deleted
914 ? deleted/1
915 ? deleted/1
915 ? removed/1
916 ? removed/1
916 $ hg add removed/1
917 $ hg add removed/1
917 $ hg status --include deleted --include removed
918 $ hg status --include deleted --include removed
918 A removed/1
919 A removed/1
919 R removed
920 R removed
920 ! deleted
921 ! deleted
921 ? deleted/1
922 ? deleted/1
922
923
923 Deeply nested files in an ignored directory are still listed on request
924 Deeply nested files in an ignored directory are still listed on request
924
925
925 $ echo ignored-dir >> .hgignore
926 $ echo ignored-dir >> .hgignore
926 $ mkdir ignored-dir
927 $ mkdir ignored-dir
927 $ mkdir ignored-dir/subdir
928 $ mkdir ignored-dir/subdir
928 $ touch ignored-dir/subdir/1
929 $ touch ignored-dir/subdir/1
929 $ hg status --ignored
930 $ hg status --ignored
930 I ignored
931 I ignored
931 I ignored-dir/subdir/1
932 I ignored-dir/subdir/1
932
933
933 Check using include flag while listing ignored composes correctly (issue6514)
934 Check using include flag while listing ignored composes correctly (issue6514)
934
935
935 $ cd ..
936 $ cd ..
936 $ hg init issue6514
937 $ hg init issue6514
937 $ cd issue6514
938 $ cd issue6514
938 $ mkdir ignored-folder
939 $ mkdir ignored-folder
939 $ touch A.hs B.hs C.hs ignored-folder/other.txt ignored-folder/ctest.hs
940 $ touch A.hs B.hs C.hs ignored-folder/other.txt ignored-folder/ctest.hs
940 $ cat >.hgignore <<EOF
941 $ cat >.hgignore <<EOF
941 > A.hs
942 > A.hs
942 > B.hs
943 > B.hs
943 > ignored-folder/
944 > ignored-folder/
944 > EOF
945 > EOF
945 $ hg st -i -I 're:.*\.hs$'
946 $ hg st -i -I 're:.*\.hs$'
946 I A.hs
947 I A.hs
947 I B.hs
948 I B.hs
948 I ignored-folder/ctest.hs
949 I ignored-folder/ctest.hs
949
950
950 #if rust dirstate-v2
951 #if rust dirstate-v2
951
952
952 Check read_dir caching
953 Check read_dir caching
953
954
954 $ cd ..
955 $ cd ..
955 $ hg init repo8
956 $ hg init repo8
956 $ cd repo8
957 $ cd repo8
957 $ mkdir subdir
958 $ mkdir subdir
958 $ touch subdir/a subdir/b
959 $ touch subdir/a subdir/b
959 $ hg ci -Aqm '#0'
960 $ hg ci -Aqm '#0'
960
961
961 The cached mtime is initially unset
962 The cached mtime is initially unset
962
963
963 $ hg debugdirstate --all --no-dates | grep '^ '
964 $ hg debugdirstate --all --no-dates | grep '^ '
964 0 -1 unset subdir
965 0 -1 unset subdir
965
966
966 It is still not set when there are unknown files
967 It is still not set when there are unknown files
967
968
968 $ touch subdir/unknown
969 $ touch subdir/unknown
969 $ hg status
970 $ hg status
970 ? subdir/unknown
971 ? subdir/unknown
971 $ hg debugdirstate --all --no-dates | grep '^ '
972 $ hg debugdirstate --all --no-dates | grep '^ '
972 0 -1 unset subdir
973 0 -1 unset subdir
973
974
974 Now the directory is eligible for caching, so its mtime is saved in the dirstate
975 Now the directory is eligible for caching, so its mtime is saved in the dirstate
975
976
976 $ rm subdir/unknown
977 $ rm subdir/unknown
977 $ sleep 0.1 # ensure the kernel’s internal clock for mtimes has ticked
978 $ sleep 0.1 # ensure the kernel’s internal clock for mtimes has ticked
978 $ hg status
979 $ hg status
979 $ hg debugdirstate --all --no-dates | grep '^ '
980 $ hg debugdirstate --all --no-dates | grep '^ '
980 0 -1 set subdir
981 0 -1 set subdir
981
982
982 This time the command should be ever so slightly faster since it does not need `read_dir("subdir")`
983 This time the command should be ever so slightly faster since it does not need `read_dir("subdir")`
983
984
984 $ hg status
985 $ hg status
985
986
986 Creating a new file changes the directory’s mtime, invalidating the cache
987 Creating a new file changes the directory’s mtime, invalidating the cache
987
988
988 $ touch subdir/unknown
989 $ touch subdir/unknown
989 $ hg status
990 $ hg status
990 ? subdir/unknown
991 ? subdir/unknown
991
992
992 $ rm subdir/unknown
993 $ rm subdir/unknown
993 $ hg status
994 $ hg status
994
995
995 Removing a node from the dirstate resets the cache for its parent directory
996 Removing a node from the dirstate resets the cache for its parent directory
996
997
997 $ hg forget subdir/a
998 $ hg forget subdir/a
998 $ hg debugdirstate --all --no-dates | grep '^ '
999 $ hg debugdirstate --all --no-dates | grep '^ '
999 0 -1 set subdir
1000 0 -1 set subdir
1000 $ hg ci -qm '#1'
1001 $ hg ci -qm '#1'
1001 $ hg debugdirstate --all --no-dates | grep '^ '
1002 $ hg debugdirstate --all --no-dates | grep '^ '
1002 0 -1 unset subdir
1003 0 -1 unset subdir
1003 $ hg status
1004 $ hg status
1004 ? subdir/a
1005 ? subdir/a
1005
1006
1006 Changing the hgignore rules makes us recompute the status (and rewrite the dirstate).
1007 Changing the hgignore rules makes us recompute the status (and rewrite the dirstate).
1007
1008
1008 $ rm subdir/a
1009 $ rm subdir/a
1009 $ mkdir another-subdir
1010 $ mkdir another-subdir
1010 $ touch another-subdir/something-else
1011 $ touch another-subdir/something-else
1011
1012
1012 $ cat > "$TESTTMP"/extra-hgignore <<EOF
1013 $ cat > "$TESTTMP"/extra-hgignore <<EOF
1013 > something-else
1014 > something-else
1014 > EOF
1015 > EOF
1015
1016
1016 $ hg status --config ui.ignore.global="$TESTTMP"/extra-hgignore
1017 $ hg status --config ui.ignore.global="$TESTTMP"/extra-hgignore
1017 $ hg debugdirstate --all --no-dates | grep '^ '
1018 $ hg debugdirstate --all --no-dates | grep '^ '
1018 0 -1 set subdir
1019 0 -1 set subdir
1019
1020
1020 $ hg status
1021 $ hg status
1021 ? another-subdir/something-else
1022 ? another-subdir/something-else
1022
1023
1023 One invocation of status is enough to populate the cache even if it's invalidated
1024 One invocation of status is enough to populate the cache even if it's invalidated
1024 in the same run.
1025 in the same run.
1025
1026
1026 $ hg debugdirstate --all --no-dates | grep '^ '
1027 $ hg debugdirstate --all --no-dates | grep '^ '
1027 0 -1 set subdir
1028 0 -1 set subdir
1028
1029
1029 #endif
1030 #endif
1030
1031
1031
1032
1032 Test copy source formatting.
1033 Test copy source formatting.
1033 $ cd ..
1034 $ cd ..
1034 $ hg init copy-source-repo
1035 $ hg init copy-source-repo
1035 $ cd copy-source-repo
1036 $ cd copy-source-repo
1036 $ mkdir -p foo/bar
1037 $ mkdir -p foo/bar
1037 $ cd foo/bar
1038 $ cd foo/bar
1038 $ touch file
1039 $ touch file
1039 $ hg addremove
1040 $ hg addremove
1040 adding foo/bar/file
1041 adding foo/bar/file
1041 $ hg commit -m 'add file'
1042 $ hg commit -m 'add file'
1042 $ hg mv file copy
1043 $ hg mv file copy
1043
1044
1044 Copy source respects relative path setting.
1045 Copy source respects relative path setting.
1045 $ hg st --config ui.statuscopies=true --config commands.status.relative=true
1046 $ hg st --config ui.statuscopies=true --config commands.status.relative=true
1046 A copy
1047 A copy
1047 file
1048 file
1048 R file
1049 R file
1049
1050
1050 Copy source is not shown when --no-status is passed.
1051 Copy source is not shown when --no-status is passed.
1051 $ hg st --config ui.statuscopies=true --no-status
1052 $ hg st --config ui.statuscopies=true --no-status
1052 foo/bar/copy
1053 foo/bar/copy
1053 foo/bar/file
1054 foo/bar/file
General Comments 0
You need to be logged in to leave comments. Login now