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