##// END OF EJS Templates
match: remove matchessubrepo method (API)...
Hannes Oldenburg -
r29803:dab7069f default
parent child Browse files
Show More
@@ -1,716 +1,712 b''
1 # match.py - filename matching
1 # match.py - filename matching
2 #
2 #
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy
10 import copy
11 import os
11 import os
12 import re
12 import re
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 error,
16 error,
17 pathutil,
17 pathutil,
18 util,
18 util,
19 )
19 )
20
20
21 propertycache = util.propertycache
21 propertycache = util.propertycache
22
22
23 def _rematcher(regex):
23 def _rematcher(regex):
24 '''compile the regexp with the best available regexp engine and return a
24 '''compile the regexp with the best available regexp engine and return a
25 matcher function'''
25 matcher function'''
26 m = util.re.compile(regex)
26 m = util.re.compile(regex)
27 try:
27 try:
28 # slightly faster, provided by facebook's re2 bindings
28 # slightly faster, provided by facebook's re2 bindings
29 return m.test_match
29 return m.test_match
30 except AttributeError:
30 except AttributeError:
31 return m.match
31 return m.match
32
32
33 def _expandsets(kindpats, ctx, listsubrepos):
33 def _expandsets(kindpats, ctx, listsubrepos):
34 '''Returns the kindpats list with the 'set' patterns expanded.'''
34 '''Returns the kindpats list with the 'set' patterns expanded.'''
35 fset = set()
35 fset = set()
36 other = []
36 other = []
37
37
38 for kind, pat, source in kindpats:
38 for kind, pat, source in kindpats:
39 if kind == 'set':
39 if kind == 'set':
40 if not ctx:
40 if not ctx:
41 raise error.Abort(_("fileset expression with no context"))
41 raise error.Abort(_("fileset expression with no context"))
42 s = ctx.getfileset(pat)
42 s = ctx.getfileset(pat)
43 fset.update(s)
43 fset.update(s)
44
44
45 if listsubrepos:
45 if listsubrepos:
46 for subpath in ctx.substate:
46 for subpath in ctx.substate:
47 s = ctx.sub(subpath).getfileset(pat)
47 s = ctx.sub(subpath).getfileset(pat)
48 fset.update(subpath + '/' + f for f in s)
48 fset.update(subpath + '/' + f for f in s)
49
49
50 continue
50 continue
51 other.append((kind, pat, source))
51 other.append((kind, pat, source))
52 return fset, other
52 return fset, other
53
53
54 def _expandsubinclude(kindpats, root):
54 def _expandsubinclude(kindpats, root):
55 '''Returns the list of subinclude matchers and the kindpats without the
55 '''Returns the list of subinclude matchers and the kindpats without the
56 subincludes in it.'''
56 subincludes in it.'''
57 relmatchers = []
57 relmatchers = []
58 other = []
58 other = []
59
59
60 for kind, pat, source in kindpats:
60 for kind, pat, source in kindpats:
61 if kind == 'subinclude':
61 if kind == 'subinclude':
62 sourceroot = pathutil.dirname(util.normpath(source))
62 sourceroot = pathutil.dirname(util.normpath(source))
63 pat = util.pconvert(pat)
63 pat = util.pconvert(pat)
64 path = pathutil.join(sourceroot, pat)
64 path = pathutil.join(sourceroot, pat)
65
65
66 newroot = pathutil.dirname(path)
66 newroot = pathutil.dirname(path)
67 relmatcher = match(newroot, '', [], ['include:%s' % path])
67 relmatcher = match(newroot, '', [], ['include:%s' % path])
68
68
69 prefix = pathutil.canonpath(root, root, newroot)
69 prefix = pathutil.canonpath(root, root, newroot)
70 if prefix:
70 if prefix:
71 prefix += '/'
71 prefix += '/'
72 relmatchers.append((prefix, relmatcher))
72 relmatchers.append((prefix, relmatcher))
73 else:
73 else:
74 other.append((kind, pat, source))
74 other.append((kind, pat, source))
75
75
76 return relmatchers, other
76 return relmatchers, other
77
77
78 def _kindpatsalwaysmatch(kindpats):
78 def _kindpatsalwaysmatch(kindpats):
79 """"Checks whether the kindspats match everything, as e.g.
79 """"Checks whether the kindspats match everything, as e.g.
80 'relpath:.' does.
80 'relpath:.' does.
81 """
81 """
82 for kind, pat, source in kindpats:
82 for kind, pat, source in kindpats:
83 if pat != '' or kind not in ['relpath', 'glob']:
83 if pat != '' or kind not in ['relpath', 'glob']:
84 return False
84 return False
85 return True
85 return True
86
86
87 class match(object):
87 class match(object):
88 def __init__(self, root, cwd, patterns, include=[], exclude=[],
88 def __init__(self, root, cwd, patterns, include=[], exclude=[],
89 default='glob', exact=False, auditor=None, ctx=None,
89 default='glob', exact=False, auditor=None, ctx=None,
90 listsubrepos=False, warn=None, badfn=None):
90 listsubrepos=False, warn=None, badfn=None):
91 """build an object to match a set of file patterns
91 """build an object to match a set of file patterns
92
92
93 arguments:
93 arguments:
94 root - the canonical root of the tree you're matching against
94 root - the canonical root of the tree you're matching against
95 cwd - the current working directory, if relevant
95 cwd - the current working directory, if relevant
96 patterns - patterns to find
96 patterns - patterns to find
97 include - patterns to include (unless they are excluded)
97 include - patterns to include (unless they are excluded)
98 exclude - patterns to exclude (even if they are included)
98 exclude - patterns to exclude (even if they are included)
99 default - if a pattern in patterns has no explicit type, assume this one
99 default - if a pattern in patterns has no explicit type, assume this one
100 exact - patterns are actually filenames (include/exclude still apply)
100 exact - patterns are actually filenames (include/exclude still apply)
101 warn - optional function used for printing warnings
101 warn - optional function used for printing warnings
102 badfn - optional bad() callback for this matcher instead of the default
102 badfn - optional bad() callback for this matcher instead of the default
103
103
104 a pattern is one of:
104 a pattern is one of:
105 'glob:<glob>' - a glob relative to cwd
105 'glob:<glob>' - a glob relative to cwd
106 're:<regexp>' - a regular expression
106 're:<regexp>' - a regular expression
107 'path:<path>' - a path relative to repository root
107 'path:<path>' - a path relative to repository root
108 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
108 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
109 'relpath:<path>' - a path relative to cwd
109 'relpath:<path>' - a path relative to cwd
110 'relre:<regexp>' - a regexp that needn't match the start of a name
110 'relre:<regexp>' - a regexp that needn't match the start of a name
111 'set:<fileset>' - a fileset expression
111 'set:<fileset>' - a fileset expression
112 'include:<path>' - a file of patterns to read and include
112 'include:<path>' - a file of patterns to read and include
113 'subinclude:<path>' - a file of patterns to match against files under
113 'subinclude:<path>' - a file of patterns to match against files under
114 the same directory
114 the same directory
115 '<something>' - a pattern of the specified default type
115 '<something>' - a pattern of the specified default type
116 """
116 """
117
117
118 self._root = root
118 self._root = root
119 self._cwd = cwd
119 self._cwd = cwd
120 self._files = [] # exact files and roots of patterns
120 self._files = [] # exact files and roots of patterns
121 self._anypats = bool(include or exclude)
121 self._anypats = bool(include or exclude)
122 self._always = False
122 self._always = False
123 self._pathrestricted = bool(include or exclude or patterns)
123 self._pathrestricted = bool(include or exclude or patterns)
124 self._warn = warn
124 self._warn = warn
125 self._includeroots = set()
125 self._includeroots = set()
126 self._includedirs = set(['.'])
126 self._includedirs = set(['.'])
127 self._excluderoots = set()
127 self._excluderoots = set()
128
128
129 if badfn is not None:
129 if badfn is not None:
130 self.bad = badfn
130 self.bad = badfn
131
131
132 matchfns = []
132 matchfns = []
133 if include:
133 if include:
134 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
134 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
135 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
135 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
136 listsubrepos, root)
136 listsubrepos, root)
137 self._includeroots.update(_roots(kindpats))
137 self._includeroots.update(_roots(kindpats))
138 self._includedirs.update(util.dirs(self._includeroots))
138 self._includedirs.update(util.dirs(self._includeroots))
139 matchfns.append(im)
139 matchfns.append(im)
140 if exclude:
140 if exclude:
141 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
141 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
142 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
142 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
143 listsubrepos, root)
143 listsubrepos, root)
144 if not _anypats(kindpats):
144 if not _anypats(kindpats):
145 self._excluderoots.update(_roots(kindpats))
145 self._excluderoots.update(_roots(kindpats))
146 matchfns.append(lambda f: not em(f))
146 matchfns.append(lambda f: not em(f))
147 if exact:
147 if exact:
148 if isinstance(patterns, list):
148 if isinstance(patterns, list):
149 self._files = patterns
149 self._files = patterns
150 else:
150 else:
151 self._files = list(patterns)
151 self._files = list(patterns)
152 matchfns.append(self.exact)
152 matchfns.append(self.exact)
153 elif patterns:
153 elif patterns:
154 kindpats = self._normalize(patterns, default, root, cwd, auditor)
154 kindpats = self._normalize(patterns, default, root, cwd, auditor)
155 if not _kindpatsalwaysmatch(kindpats):
155 if not _kindpatsalwaysmatch(kindpats):
156 self._files = _roots(kindpats)
156 self._files = _roots(kindpats)
157 self._anypats = self._anypats or _anypats(kindpats)
157 self._anypats = self._anypats or _anypats(kindpats)
158 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
158 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
159 listsubrepos, root)
159 listsubrepos, root)
160 matchfns.append(pm)
160 matchfns.append(pm)
161
161
162 if not matchfns:
162 if not matchfns:
163 m = util.always
163 m = util.always
164 self._always = True
164 self._always = True
165 elif len(matchfns) == 1:
165 elif len(matchfns) == 1:
166 m = matchfns[0]
166 m = matchfns[0]
167 else:
167 else:
168 def m(f):
168 def m(f):
169 for matchfn in matchfns:
169 for matchfn in matchfns:
170 if not matchfn(f):
170 if not matchfn(f):
171 return False
171 return False
172 return True
172 return True
173
173
174 self.matchfn = m
174 self.matchfn = m
175 self._fileroots = set(self._files)
175 self._fileroots = set(self._files)
176
176
177 def __call__(self, fn):
177 def __call__(self, fn):
178 return self.matchfn(fn)
178 return self.matchfn(fn)
179 def __iter__(self):
179 def __iter__(self):
180 for f in self._files:
180 for f in self._files:
181 yield f
181 yield f
182
182
183 # Callbacks related to how the matcher is used by dirstate.walk.
183 # Callbacks related to how the matcher is used by dirstate.walk.
184 # Subscribers to these events must monkeypatch the matcher object.
184 # Subscribers to these events must monkeypatch the matcher object.
185 def bad(self, f, msg):
185 def bad(self, f, msg):
186 '''Callback from dirstate.walk for each explicit file that can't be
186 '''Callback from dirstate.walk for each explicit file that can't be
187 found/accessed, with an error message.'''
187 found/accessed, with an error message.'''
188 pass
188 pass
189
189
190 # If an explicitdir is set, it will be called when an explicitly listed
190 # If an explicitdir is set, it will be called when an explicitly listed
191 # directory is visited.
191 # directory is visited.
192 explicitdir = None
192 explicitdir = None
193
193
194 # If an traversedir is set, it will be called when a directory discovered
194 # If an traversedir is set, it will be called when a directory discovered
195 # by recursive traversal is visited.
195 # by recursive traversal is visited.
196 traversedir = None
196 traversedir = None
197
197
198 def abs(self, f):
198 def abs(self, f):
199 '''Convert a repo path back to path that is relative to the root of the
199 '''Convert a repo path back to path that is relative to the root of the
200 matcher.'''
200 matcher.'''
201 return f
201 return f
202
202
203 def rel(self, f):
203 def rel(self, f):
204 '''Convert repo path back to path that is relative to cwd of matcher.'''
204 '''Convert repo path back to path that is relative to cwd of matcher.'''
205 return util.pathto(self._root, self._cwd, f)
205 return util.pathto(self._root, self._cwd, f)
206
206
207 def uipath(self, f):
207 def uipath(self, f):
208 '''Convert repo path to a display path. If patterns or -I/-X were used
208 '''Convert repo path to a display path. If patterns or -I/-X were used
209 to create this matcher, the display path will be relative to cwd.
209 to create this matcher, the display path will be relative to cwd.
210 Otherwise it is relative to the root of the repo.'''
210 Otherwise it is relative to the root of the repo.'''
211 return (self._pathrestricted and self.rel(f)) or self.abs(f)
211 return (self._pathrestricted and self.rel(f)) or self.abs(f)
212
212
213 def files(self):
213 def files(self):
214 '''Explicitly listed files or patterns or roots:
214 '''Explicitly listed files or patterns or roots:
215 if no patterns or .always(): empty list,
215 if no patterns or .always(): empty list,
216 if exact: list exact files,
216 if exact: list exact files,
217 if not .anypats(): list all files and dirs,
217 if not .anypats(): list all files and dirs,
218 else: optimal roots'''
218 else: optimal roots'''
219 return self._files
219 return self._files
220
220
221 @propertycache
221 @propertycache
222 def _dirs(self):
222 def _dirs(self):
223 return set(util.dirs(self._fileroots)) | set(['.'])
223 return set(util.dirs(self._fileroots)) | set(['.'])
224
224
225 def visitdir(self, dir):
225 def visitdir(self, dir):
226 '''Decides whether a directory should be visited based on whether it
226 '''Decides whether a directory should be visited based on whether it
227 has potential matches in it or one of its subdirectories. This is
227 has potential matches in it or one of its subdirectories. This is
228 based on the match's primary, included, and excluded patterns.
228 based on the match's primary, included, and excluded patterns.
229
229
230 Returns the string 'all' if the given directory and all subdirectories
230 Returns the string 'all' if the given directory and all subdirectories
231 should be visited. Otherwise returns True or False indicating whether
231 should be visited. Otherwise returns True or False indicating whether
232 the given directory should be visited.
232 the given directory should be visited.
233
233
234 This function's behavior is undefined if it has returned False for
234 This function's behavior is undefined if it has returned False for
235 one of the dir's parent directories.
235 one of the dir's parent directories.
236 '''
236 '''
237 if self.prefix() and dir in self._fileroots:
237 if self.prefix() and dir in self._fileroots:
238 return 'all'
238 return 'all'
239 if dir in self._excluderoots:
239 if dir in self._excluderoots:
240 return False
240 return False
241 if (self._includeroots and
241 if (self._includeroots and
242 '.' not in self._includeroots and
242 '.' not in self._includeroots and
243 dir not in self._includeroots and
243 dir not in self._includeroots and
244 dir not in self._includedirs and
244 dir not in self._includedirs and
245 not any(parent in self._includeroots
245 not any(parent in self._includeroots
246 for parent in util.finddirs(dir))):
246 for parent in util.finddirs(dir))):
247 return False
247 return False
248 return (not self._fileroots or
248 return (not self._fileroots or
249 '.' in self._fileroots or
249 '.' in self._fileroots or
250 dir in self._fileroots or
250 dir in self._fileroots or
251 dir in self._dirs or
251 dir in self._dirs or
252 any(parentdir in self._fileroots
252 any(parentdir in self._fileroots
253 for parentdir in util.finddirs(dir)))
253 for parentdir in util.finddirs(dir)))
254
254
255 def exact(self, f):
255 def exact(self, f):
256 '''Returns True if f is in .files().'''
256 '''Returns True if f is in .files().'''
257 return f in self._fileroots
257 return f in self._fileroots
258
258
259 def anypats(self):
259 def anypats(self):
260 '''Matcher uses patterns or include/exclude.'''
260 '''Matcher uses patterns or include/exclude.'''
261 return self._anypats
261 return self._anypats
262
262
263 def always(self):
263 def always(self):
264 '''Matcher will match everything and .files() will be empty
264 '''Matcher will match everything and .files() will be empty
265 - optimization might be possible and necessary.'''
265 - optimization might be possible and necessary.'''
266 return self._always
266 return self._always
267
267
268 def ispartial(self):
268 def ispartial(self):
269 '''True if the matcher won't always match.
269 '''True if the matcher won't always match.
270
270
271 Although it's just the inverse of _always in this implementation,
271 Although it's just the inverse of _always in this implementation,
272 an extension such as narrowhg might make it return something
272 an extension such as narrowhg might make it return something
273 slightly different.'''
273 slightly different.'''
274 return not self._always
274 return not self._always
275
275
276 def isexact(self):
276 def isexact(self):
277 return self.matchfn == self.exact
277 return self.matchfn == self.exact
278
278
279 def prefix(self):
279 def prefix(self):
280 return not self.always() and not self.isexact() and not self.anypats()
280 return not self.always() and not self.isexact() and not self.anypats()
281
281
282 def _normalize(self, patterns, default, root, cwd, auditor):
282 def _normalize(self, patterns, default, root, cwd, auditor):
283 '''Convert 'kind:pat' from the patterns list to tuples with kind and
283 '''Convert 'kind:pat' from the patterns list to tuples with kind and
284 normalized and rooted patterns and with listfiles expanded.'''
284 normalized and rooted patterns and with listfiles expanded.'''
285 kindpats = []
285 kindpats = []
286 for kind, pat in [_patsplit(p, default) for p in patterns]:
286 for kind, pat in [_patsplit(p, default) for p in patterns]:
287 if kind in ('glob', 'relpath'):
287 if kind in ('glob', 'relpath'):
288 pat = pathutil.canonpath(root, cwd, pat, auditor)
288 pat = pathutil.canonpath(root, cwd, pat, auditor)
289 elif kind in ('relglob', 'path'):
289 elif kind in ('relglob', 'path'):
290 pat = util.normpath(pat)
290 pat = util.normpath(pat)
291 elif kind in ('listfile', 'listfile0'):
291 elif kind in ('listfile', 'listfile0'):
292 try:
292 try:
293 files = util.readfile(pat)
293 files = util.readfile(pat)
294 if kind == 'listfile0':
294 if kind == 'listfile0':
295 files = files.split('\0')
295 files = files.split('\0')
296 else:
296 else:
297 files = files.splitlines()
297 files = files.splitlines()
298 files = [f for f in files if f]
298 files = [f for f in files if f]
299 except EnvironmentError:
299 except EnvironmentError:
300 raise error.Abort(_("unable to read file list (%s)") % pat)
300 raise error.Abort(_("unable to read file list (%s)") % pat)
301 for k, p, source in self._normalize(files, default, root, cwd,
301 for k, p, source in self._normalize(files, default, root, cwd,
302 auditor):
302 auditor):
303 kindpats.append((k, p, pat))
303 kindpats.append((k, p, pat))
304 continue
304 continue
305 elif kind == 'include':
305 elif kind == 'include':
306 try:
306 try:
307 fullpath = os.path.join(root, util.localpath(pat))
307 fullpath = os.path.join(root, util.localpath(pat))
308 includepats = readpatternfile(fullpath, self._warn)
308 includepats = readpatternfile(fullpath, self._warn)
309 for k, p, source in self._normalize(includepats, default,
309 for k, p, source in self._normalize(includepats, default,
310 root, cwd, auditor):
310 root, cwd, auditor):
311 kindpats.append((k, p, source or pat))
311 kindpats.append((k, p, source or pat))
312 except error.Abort as inst:
312 except error.Abort as inst:
313 raise error.Abort('%s: %s' % (pat, inst[0]))
313 raise error.Abort('%s: %s' % (pat, inst[0]))
314 except IOError as inst:
314 except IOError as inst:
315 if self._warn:
315 if self._warn:
316 self._warn(_("skipping unreadable pattern file "
316 self._warn(_("skipping unreadable pattern file "
317 "'%s': %s\n") % (pat, inst.strerror))
317 "'%s': %s\n") % (pat, inst.strerror))
318 continue
318 continue
319 # else: re or relre - which cannot be normalized
319 # else: re or relre - which cannot be normalized
320 kindpats.append((kind, pat, ''))
320 kindpats.append((kind, pat, ''))
321 return kindpats
321 return kindpats
322
322
323 def matchessubrepo(self, subpath):
324 return (self.exact(subpath)
325 or any(f.startswith(subpath + '/') for f in self.files()))
326
327 def exact(root, cwd, files, badfn=None):
323 def exact(root, cwd, files, badfn=None):
328 return match(root, cwd, files, exact=True, badfn=badfn)
324 return match(root, cwd, files, exact=True, badfn=badfn)
329
325
330 def always(root, cwd):
326 def always(root, cwd):
331 return match(root, cwd, [])
327 return match(root, cwd, [])
332
328
333 def badmatch(match, badfn):
329 def badmatch(match, badfn):
334 """Make a copy of the given matcher, replacing its bad method with the given
330 """Make a copy of the given matcher, replacing its bad method with the given
335 one.
331 one.
336 """
332 """
337 m = copy.copy(match)
333 m = copy.copy(match)
338 m.bad = badfn
334 m.bad = badfn
339 return m
335 return m
340
336
341 class subdirmatcher(match):
337 class subdirmatcher(match):
342 """Adapt a matcher to work on a subdirectory only.
338 """Adapt a matcher to work on a subdirectory only.
343
339
344 The paths are remapped to remove/insert the path as needed:
340 The paths are remapped to remove/insert the path as needed:
345
341
346 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
342 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
347 >>> m2 = subdirmatcher('sub', m1)
343 >>> m2 = subdirmatcher('sub', m1)
348 >>> bool(m2('a.txt'))
344 >>> bool(m2('a.txt'))
349 False
345 False
350 >>> bool(m2('b.txt'))
346 >>> bool(m2('b.txt'))
351 True
347 True
352 >>> bool(m2.matchfn('a.txt'))
348 >>> bool(m2.matchfn('a.txt'))
353 False
349 False
354 >>> bool(m2.matchfn('b.txt'))
350 >>> bool(m2.matchfn('b.txt'))
355 True
351 True
356 >>> m2.files()
352 >>> m2.files()
357 ['b.txt']
353 ['b.txt']
358 >>> m2.exact('b.txt')
354 >>> m2.exact('b.txt')
359 True
355 True
360 >>> util.pconvert(m2.rel('b.txt'))
356 >>> util.pconvert(m2.rel('b.txt'))
361 'sub/b.txt'
357 'sub/b.txt'
362 >>> def bad(f, msg):
358 >>> def bad(f, msg):
363 ... print "%s: %s" % (f, msg)
359 ... print "%s: %s" % (f, msg)
364 >>> m1.bad = bad
360 >>> m1.bad = bad
365 >>> m2.bad('x.txt', 'No such file')
361 >>> m2.bad('x.txt', 'No such file')
366 sub/x.txt: No such file
362 sub/x.txt: No such file
367 >>> m2.abs('c.txt')
363 >>> m2.abs('c.txt')
368 'sub/c.txt'
364 'sub/c.txt'
369 """
365 """
370
366
371 def __init__(self, path, matcher):
367 def __init__(self, path, matcher):
372 self._root = matcher._root
368 self._root = matcher._root
373 self._cwd = matcher._cwd
369 self._cwd = matcher._cwd
374 self._path = path
370 self._path = path
375 self._matcher = matcher
371 self._matcher = matcher
376 self._always = matcher._always
372 self._always = matcher._always
377 self._pathrestricted = matcher._pathrestricted
373 self._pathrestricted = matcher._pathrestricted
378
374
379 self._files = [f[len(path) + 1:] for f in matcher._files
375 self._files = [f[len(path) + 1:] for f in matcher._files
380 if f.startswith(path + "/")]
376 if f.startswith(path + "/")]
381
377
382 # If the parent repo had a path to this subrepo and no patterns are
378 # If the parent repo had a path to this subrepo and no patterns are
383 # specified, this submatcher always matches.
379 # specified, this submatcher always matches.
384 if not self._always and not matcher._anypats:
380 if not self._always and not matcher._anypats:
385 self._always = any(f == path for f in matcher._files)
381 self._always = any(f == path for f in matcher._files)
386
382
387 self._anypats = matcher._anypats
383 self._anypats = matcher._anypats
388 # Some information is lost in the superclass's constructor, so we
384 # Some information is lost in the superclass's constructor, so we
389 # can not accurately create the matching function for the subdirectory
385 # can not accurately create the matching function for the subdirectory
390 # from the inputs. Instead, we override matchfn() and visitdir() to
386 # from the inputs. Instead, we override matchfn() and visitdir() to
391 # call the original matcher with the subdirectory path prepended.
387 # call the original matcher with the subdirectory path prepended.
392 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
388 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
393 def visitdir(dir):
389 def visitdir(dir):
394 if dir == '.':
390 if dir == '.':
395 return matcher.visitdir(self._path)
391 return matcher.visitdir(self._path)
396 return matcher.visitdir(self._path + "/" + dir)
392 return matcher.visitdir(self._path + "/" + dir)
397 self.visitdir = visitdir
393 self.visitdir = visitdir
398 self._fileroots = set(self._files)
394 self._fileroots = set(self._files)
399
395
400 def abs(self, f):
396 def abs(self, f):
401 return self._matcher.abs(self._path + "/" + f)
397 return self._matcher.abs(self._path + "/" + f)
402
398
403 def bad(self, f, msg):
399 def bad(self, f, msg):
404 self._matcher.bad(self._path + "/" + f, msg)
400 self._matcher.bad(self._path + "/" + f, msg)
405
401
406 def rel(self, f):
402 def rel(self, f):
407 return self._matcher.rel(self._path + "/" + f)
403 return self._matcher.rel(self._path + "/" + f)
408
404
409 class icasefsmatcher(match):
405 class icasefsmatcher(match):
410 """A matcher for wdir on case insensitive filesystems, which normalizes the
406 """A matcher for wdir on case insensitive filesystems, which normalizes the
411 given patterns to the case in the filesystem.
407 given patterns to the case in the filesystem.
412 """
408 """
413
409
414 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
410 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
415 ctx, listsubrepos=False, badfn=None):
411 ctx, listsubrepos=False, badfn=None):
416 init = super(icasefsmatcher, self).__init__
412 init = super(icasefsmatcher, self).__init__
417 self._dirstate = ctx.repo().dirstate
413 self._dirstate = ctx.repo().dirstate
418 self._dsnormalize = self._dirstate.normalize
414 self._dsnormalize = self._dirstate.normalize
419
415
420 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
416 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
421 ctx=ctx, listsubrepos=listsubrepos, badfn=badfn)
417 ctx=ctx, listsubrepos=listsubrepos, badfn=badfn)
422
418
423 # m.exact(file) must be based off of the actual user input, otherwise
419 # m.exact(file) must be based off of the actual user input, otherwise
424 # inexact case matches are treated as exact, and not noted without -v.
420 # inexact case matches are treated as exact, and not noted without -v.
425 if self._files:
421 if self._files:
426 self._fileroots = set(_roots(self._kp))
422 self._fileroots = set(_roots(self._kp))
427
423
428 def _normalize(self, patterns, default, root, cwd, auditor):
424 def _normalize(self, patterns, default, root, cwd, auditor):
429 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
425 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
430 root, cwd, auditor)
426 root, cwd, auditor)
431 kindpats = []
427 kindpats = []
432 for kind, pats, source in self._kp:
428 for kind, pats, source in self._kp:
433 if kind not in ('re', 'relre'): # regex can't be normalized
429 if kind not in ('re', 'relre'): # regex can't be normalized
434 p = pats
430 p = pats
435 pats = self._dsnormalize(pats)
431 pats = self._dsnormalize(pats)
436
432
437 # Preserve the original to handle a case only rename.
433 # Preserve the original to handle a case only rename.
438 if p != pats and p in self._dirstate:
434 if p != pats and p in self._dirstate:
439 kindpats.append((kind, p, source))
435 kindpats.append((kind, p, source))
440
436
441 kindpats.append((kind, pats, source))
437 kindpats.append((kind, pats, source))
442 return kindpats
438 return kindpats
443
439
444 def patkind(pattern, default=None):
440 def patkind(pattern, default=None):
445 '''If pattern is 'kind:pat' with a known kind, return kind.'''
441 '''If pattern is 'kind:pat' with a known kind, return kind.'''
446 return _patsplit(pattern, default)[0]
442 return _patsplit(pattern, default)[0]
447
443
448 def _patsplit(pattern, default):
444 def _patsplit(pattern, default):
449 """Split a string into the optional pattern kind prefix and the actual
445 """Split a string into the optional pattern kind prefix and the actual
450 pattern."""
446 pattern."""
451 if ':' in pattern:
447 if ':' in pattern:
452 kind, pat = pattern.split(':', 1)
448 kind, pat = pattern.split(':', 1)
453 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
449 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
454 'listfile', 'listfile0', 'set', 'include', 'subinclude'):
450 'listfile', 'listfile0', 'set', 'include', 'subinclude'):
455 return kind, pat
451 return kind, pat
456 return default, pattern
452 return default, pattern
457
453
458 def _globre(pat):
454 def _globre(pat):
459 r'''Convert an extended glob string to a regexp string.
455 r'''Convert an extended glob string to a regexp string.
460
456
461 >>> print _globre(r'?')
457 >>> print _globre(r'?')
462 .
458 .
463 >>> print _globre(r'*')
459 >>> print _globre(r'*')
464 [^/]*
460 [^/]*
465 >>> print _globre(r'**')
461 >>> print _globre(r'**')
466 .*
462 .*
467 >>> print _globre(r'**/a')
463 >>> print _globre(r'**/a')
468 (?:.*/)?a
464 (?:.*/)?a
469 >>> print _globre(r'a/**/b')
465 >>> print _globre(r'a/**/b')
470 a\/(?:.*/)?b
466 a\/(?:.*/)?b
471 >>> print _globre(r'[a*?!^][^b][!c]')
467 >>> print _globre(r'[a*?!^][^b][!c]')
472 [a*?!^][\^b][^c]
468 [a*?!^][\^b][^c]
473 >>> print _globre(r'{a,b}')
469 >>> print _globre(r'{a,b}')
474 (?:a|b)
470 (?:a|b)
475 >>> print _globre(r'.\*\?')
471 >>> print _globre(r'.\*\?')
476 \.\*\?
472 \.\*\?
477 '''
473 '''
478 i, n = 0, len(pat)
474 i, n = 0, len(pat)
479 res = ''
475 res = ''
480 group = 0
476 group = 0
481 escape = util.re.escape
477 escape = util.re.escape
482 def peek():
478 def peek():
483 return i < n and pat[i]
479 return i < n and pat[i]
484 while i < n:
480 while i < n:
485 c = pat[i]
481 c = pat[i]
486 i += 1
482 i += 1
487 if c not in '*?[{},\\':
483 if c not in '*?[{},\\':
488 res += escape(c)
484 res += escape(c)
489 elif c == '*':
485 elif c == '*':
490 if peek() == '*':
486 if peek() == '*':
491 i += 1
487 i += 1
492 if peek() == '/':
488 if peek() == '/':
493 i += 1
489 i += 1
494 res += '(?:.*/)?'
490 res += '(?:.*/)?'
495 else:
491 else:
496 res += '.*'
492 res += '.*'
497 else:
493 else:
498 res += '[^/]*'
494 res += '[^/]*'
499 elif c == '?':
495 elif c == '?':
500 res += '.'
496 res += '.'
501 elif c == '[':
497 elif c == '[':
502 j = i
498 j = i
503 if j < n and pat[j] in '!]':
499 if j < n and pat[j] in '!]':
504 j += 1
500 j += 1
505 while j < n and pat[j] != ']':
501 while j < n and pat[j] != ']':
506 j += 1
502 j += 1
507 if j >= n:
503 if j >= n:
508 res += '\\['
504 res += '\\['
509 else:
505 else:
510 stuff = pat[i:j].replace('\\','\\\\')
506 stuff = pat[i:j].replace('\\','\\\\')
511 i = j + 1
507 i = j + 1
512 if stuff[0] == '!':
508 if stuff[0] == '!':
513 stuff = '^' + stuff[1:]
509 stuff = '^' + stuff[1:]
514 elif stuff[0] == '^':
510 elif stuff[0] == '^':
515 stuff = '\\' + stuff
511 stuff = '\\' + stuff
516 res = '%s[%s]' % (res, stuff)
512 res = '%s[%s]' % (res, stuff)
517 elif c == '{':
513 elif c == '{':
518 group += 1
514 group += 1
519 res += '(?:'
515 res += '(?:'
520 elif c == '}' and group:
516 elif c == '}' and group:
521 res += ')'
517 res += ')'
522 group -= 1
518 group -= 1
523 elif c == ',' and group:
519 elif c == ',' and group:
524 res += '|'
520 res += '|'
525 elif c == '\\':
521 elif c == '\\':
526 p = peek()
522 p = peek()
527 if p:
523 if p:
528 i += 1
524 i += 1
529 res += escape(p)
525 res += escape(p)
530 else:
526 else:
531 res += escape(c)
527 res += escape(c)
532 else:
528 else:
533 res += escape(c)
529 res += escape(c)
534 return res
530 return res
535
531
536 def _regex(kind, pat, globsuffix):
532 def _regex(kind, pat, globsuffix):
537 '''Convert a (normalized) pattern of any kind into a regular expression.
533 '''Convert a (normalized) pattern of any kind into a regular expression.
538 globsuffix is appended to the regexp of globs.'''
534 globsuffix is appended to the regexp of globs.'''
539 if not pat:
535 if not pat:
540 return ''
536 return ''
541 if kind == 're':
537 if kind == 're':
542 return pat
538 return pat
543 if kind == 'path':
539 if kind == 'path':
544 if pat == '.':
540 if pat == '.':
545 return ''
541 return ''
546 return '^' + util.re.escape(pat) + '(?:/|$)'
542 return '^' + util.re.escape(pat) + '(?:/|$)'
547 if kind == 'relglob':
543 if kind == 'relglob':
548 return '(?:|.*/)' + _globre(pat) + globsuffix
544 return '(?:|.*/)' + _globre(pat) + globsuffix
549 if kind == 'relpath':
545 if kind == 'relpath':
550 return util.re.escape(pat) + '(?:/|$)'
546 return util.re.escape(pat) + '(?:/|$)'
551 if kind == 'relre':
547 if kind == 'relre':
552 if pat.startswith('^'):
548 if pat.startswith('^'):
553 return pat
549 return pat
554 return '.*' + pat
550 return '.*' + pat
555 return _globre(pat) + globsuffix
551 return _globre(pat) + globsuffix
556
552
557 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
553 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
558 '''Return regexp string and a matcher function for kindpats.
554 '''Return regexp string and a matcher function for kindpats.
559 globsuffix is appended to the regexp of globs.'''
555 globsuffix is appended to the regexp of globs.'''
560 matchfuncs = []
556 matchfuncs = []
561
557
562 subincludes, kindpats = _expandsubinclude(kindpats, root)
558 subincludes, kindpats = _expandsubinclude(kindpats, root)
563 if subincludes:
559 if subincludes:
564 def matchsubinclude(f):
560 def matchsubinclude(f):
565 for prefix, mf in subincludes:
561 for prefix, mf in subincludes:
566 if f.startswith(prefix) and mf(f[len(prefix):]):
562 if f.startswith(prefix) and mf(f[len(prefix):]):
567 return True
563 return True
568 return False
564 return False
569 matchfuncs.append(matchsubinclude)
565 matchfuncs.append(matchsubinclude)
570
566
571 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
567 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
572 if fset:
568 if fset:
573 matchfuncs.append(fset.__contains__)
569 matchfuncs.append(fset.__contains__)
574
570
575 regex = ''
571 regex = ''
576 if kindpats:
572 if kindpats:
577 regex, mf = _buildregexmatch(kindpats, globsuffix)
573 regex, mf = _buildregexmatch(kindpats, globsuffix)
578 matchfuncs.append(mf)
574 matchfuncs.append(mf)
579
575
580 if len(matchfuncs) == 1:
576 if len(matchfuncs) == 1:
581 return regex, matchfuncs[0]
577 return regex, matchfuncs[0]
582 else:
578 else:
583 return regex, lambda f: any(mf(f) for mf in matchfuncs)
579 return regex, lambda f: any(mf(f) for mf in matchfuncs)
584
580
585 def _buildregexmatch(kindpats, globsuffix):
581 def _buildregexmatch(kindpats, globsuffix):
586 """Build a match function from a list of kinds and kindpats,
582 """Build a match function from a list of kinds and kindpats,
587 return regexp string and a matcher function."""
583 return regexp string and a matcher function."""
588 try:
584 try:
589 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
585 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
590 for (k, p, s) in kindpats])
586 for (k, p, s) in kindpats])
591 if len(regex) > 20000:
587 if len(regex) > 20000:
592 raise OverflowError
588 raise OverflowError
593 return regex, _rematcher(regex)
589 return regex, _rematcher(regex)
594 except OverflowError:
590 except OverflowError:
595 # We're using a Python with a tiny regex engine and we
591 # We're using a Python with a tiny regex engine and we
596 # made it explode, so we'll divide the pattern list in two
592 # made it explode, so we'll divide the pattern list in two
597 # until it works
593 # until it works
598 l = len(kindpats)
594 l = len(kindpats)
599 if l < 2:
595 if l < 2:
600 raise
596 raise
601 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
597 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
602 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
598 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
603 return regex, lambda s: a(s) or b(s)
599 return regex, lambda s: a(s) or b(s)
604 except re.error:
600 except re.error:
605 for k, p, s in kindpats:
601 for k, p, s in kindpats:
606 try:
602 try:
607 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
603 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
608 except re.error:
604 except re.error:
609 if s:
605 if s:
610 raise error.Abort(_("%s: invalid pattern (%s): %s") %
606 raise error.Abort(_("%s: invalid pattern (%s): %s") %
611 (s, k, p))
607 (s, k, p))
612 else:
608 else:
613 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
609 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
614 raise error.Abort(_("invalid pattern"))
610 raise error.Abort(_("invalid pattern"))
615
611
616 def _roots(kindpats):
612 def _roots(kindpats):
617 '''return roots and exact explicitly listed files from patterns
613 '''return roots and exact explicitly listed files from patterns
618
614
619 >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')])
615 >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')])
620 ['g', 'g', '.']
616 ['g', 'g', '.']
621 >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
617 >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
622 ['r', 'p/p', '.']
618 ['r', 'p/p', '.']
623 >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
619 >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
624 ['.', '.', '.']
620 ['.', '.', '.']
625 '''
621 '''
626 r = []
622 r = []
627 for kind, pat, source in kindpats:
623 for kind, pat, source in kindpats:
628 if kind == 'glob': # find the non-glob prefix
624 if kind == 'glob': # find the non-glob prefix
629 root = []
625 root = []
630 for p in pat.split('/'):
626 for p in pat.split('/'):
631 if '[' in p or '{' in p or '*' in p or '?' in p:
627 if '[' in p or '{' in p or '*' in p or '?' in p:
632 break
628 break
633 root.append(p)
629 root.append(p)
634 r.append('/'.join(root) or '.')
630 r.append('/'.join(root) or '.')
635 elif kind in ('relpath', 'path'):
631 elif kind in ('relpath', 'path'):
636 r.append(pat or '.')
632 r.append(pat or '.')
637 else: # relglob, re, relre
633 else: # relglob, re, relre
638 r.append('.')
634 r.append('.')
639 return r
635 return r
640
636
641 def _anypats(kindpats):
637 def _anypats(kindpats):
642 for kind, pat, source in kindpats:
638 for kind, pat, source in kindpats:
643 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
639 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
644 return True
640 return True
645
641
646 _commentre = None
642 _commentre = None
647
643
648 def readpatternfile(filepath, warn, sourceinfo=False):
644 def readpatternfile(filepath, warn, sourceinfo=False):
649 '''parse a pattern file, returning a list of
645 '''parse a pattern file, returning a list of
650 patterns. These patterns should be given to compile()
646 patterns. These patterns should be given to compile()
651 to be validated and converted into a match function.
647 to be validated and converted into a match function.
652
648
653 trailing white space is dropped.
649 trailing white space is dropped.
654 the escape character is backslash.
650 the escape character is backslash.
655 comments start with #.
651 comments start with #.
656 empty lines are skipped.
652 empty lines are skipped.
657
653
658 lines can be of the following formats:
654 lines can be of the following formats:
659
655
660 syntax: regexp # defaults following lines to non-rooted regexps
656 syntax: regexp # defaults following lines to non-rooted regexps
661 syntax: glob # defaults following lines to non-rooted globs
657 syntax: glob # defaults following lines to non-rooted globs
662 re:pattern # non-rooted regular expression
658 re:pattern # non-rooted regular expression
663 glob:pattern # non-rooted glob
659 glob:pattern # non-rooted glob
664 pattern # pattern of the current default type
660 pattern # pattern of the current default type
665
661
666 if sourceinfo is set, returns a list of tuples:
662 if sourceinfo is set, returns a list of tuples:
667 (pattern, lineno, originalline). This is useful to debug ignore patterns.
663 (pattern, lineno, originalline). This is useful to debug ignore patterns.
668 '''
664 '''
669
665
670 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
666 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
671 'include': 'include', 'subinclude': 'subinclude'}
667 'include': 'include', 'subinclude': 'subinclude'}
672 syntax = 'relre:'
668 syntax = 'relre:'
673 patterns = []
669 patterns = []
674
670
675 fp = open(filepath)
671 fp = open(filepath)
676 for lineno, line in enumerate(fp, start=1):
672 for lineno, line in enumerate(fp, start=1):
677 if "#" in line:
673 if "#" in line:
678 global _commentre
674 global _commentre
679 if not _commentre:
675 if not _commentre:
680 _commentre = util.re.compile(r'((?:^|[^\\])(?:\\\\)*)#.*')
676 _commentre = util.re.compile(r'((?:^|[^\\])(?:\\\\)*)#.*')
681 # remove comments prefixed by an even number of escapes
677 # remove comments prefixed by an even number of escapes
682 m = _commentre.search(line)
678 m = _commentre.search(line)
683 if m:
679 if m:
684 line = line[:m.end(1)]
680 line = line[:m.end(1)]
685 # fixup properly escaped comments that survived the above
681 # fixup properly escaped comments that survived the above
686 line = line.replace("\\#", "#")
682 line = line.replace("\\#", "#")
687 line = line.rstrip()
683 line = line.rstrip()
688 if not line:
684 if not line:
689 continue
685 continue
690
686
691 if line.startswith('syntax:'):
687 if line.startswith('syntax:'):
692 s = line[7:].strip()
688 s = line[7:].strip()
693 try:
689 try:
694 syntax = syntaxes[s]
690 syntax = syntaxes[s]
695 except KeyError:
691 except KeyError:
696 if warn:
692 if warn:
697 warn(_("%s: ignoring invalid syntax '%s'\n") %
693 warn(_("%s: ignoring invalid syntax '%s'\n") %
698 (filepath, s))
694 (filepath, s))
699 continue
695 continue
700
696
701 linesyntax = syntax
697 linesyntax = syntax
702 for s, rels in syntaxes.iteritems():
698 for s, rels in syntaxes.iteritems():
703 if line.startswith(rels):
699 if line.startswith(rels):
704 linesyntax = rels
700 linesyntax = rels
705 line = line[len(rels):]
701 line = line[len(rels):]
706 break
702 break
707 elif line.startswith(s+':'):
703 elif line.startswith(s+':'):
708 linesyntax = rels
704 linesyntax = rels
709 line = line[len(s) + 1:]
705 line = line[len(s) + 1:]
710 break
706 break
711 if sourceinfo:
707 if sourceinfo:
712 patterns.append((linesyntax + line, lineno, line))
708 patterns.append((linesyntax + line, lineno, line))
713 else:
709 else:
714 patterns.append(linesyntax + line)
710 patterns.append(linesyntax + line)
715 fp.close()
711 fp.close()
716 return patterns
712 return patterns
General Comments 0
You need to be logged in to leave comments. Login now