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