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