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