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