Mercurial > hg
comparison mercurial/match.py @ 32465:a83a7d27911e
match: handle excludes using new differencematcher
As I've said on earlier patches, I'm hoping to use more composition of
simpler matchers instead of the single complex matcher we currently
have. This extracts a first new matcher that composes two other
matchers. It matches if the first matcher matches but the second does
not. As such, we can use it for excludes, which this patch also
does. We'll remove the now-unncessary code for excludes in the next
patch.
author | Martin von Zweigbergk <martinvonz@google.com> |
---|---|
date | Tue, 16 May 2017 16:36:48 -0700 |
parents | 2e80a691e575 |
children | 12e241b2713c |
comparison
equal
deleted
inserted
replaced
32464:2e80a691e575 | 32465:a83a7d27911e |
---|---|
140 kindpats.append((kind, p, source)) | 140 kindpats.append((kind, p, source)) |
141 | 141 |
142 kindpats.append((kind, pats, source)) | 142 kindpats.append((kind, pats, source)) |
143 return kindpats | 143 return kindpats |
144 | 144 |
145 return matcher(root, cwd, normalize, patterns, include=include, | 145 m = matcher(root, cwd, normalize, patterns, include=include, exclude=None, |
146 exclude=exclude, default=default, exact=exact, | 146 default=default, exact=exact, auditor=auditor, ctx=ctx, |
147 auditor=auditor, ctx=ctx, listsubrepos=listsubrepos, | 147 listsubrepos=listsubrepos, warn=warn, badfn=badfn) |
148 warn=warn, badfn=badfn) | 148 if exclude: |
149 em = matcher(root, cwd, normalize, [], include=exclude, exclude=None, | |
150 default=default, exact=False, auditor=auditor, ctx=ctx, | |
151 listsubrepos=listsubrepos, warn=warn, badfn=None) | |
152 m = differencematcher(m, em) | |
153 return m | |
149 | 154 |
150 def exact(root, cwd, files, badfn=None): | 155 def exact(root, cwd, files, badfn=None): |
151 return match(root, cwd, files, exact=True, badfn=badfn) | 156 return match(root, cwd, files, exact=True, badfn=badfn) |
152 | 157 |
153 def always(root, cwd): | 158 def always(root, cwd): |
415 | 420 |
416 def __repr__(self): | 421 def __repr__(self): |
417 return ('<matcher files=%r, patterns=%r, includes=%r, excludes=%r>' % | 422 return ('<matcher files=%r, patterns=%r, includes=%r, excludes=%r>' % |
418 (self._files, self.patternspat, self.includepat, | 423 (self._files, self.patternspat, self.includepat, |
419 self.excludepat)) | 424 self.excludepat)) |
425 | |
426 class differencematcher(basematcher): | |
427 '''Composes two matchers by matching if the first matches and the second | |
428 does not. Well, almost... If the user provides a pattern like "-X foo foo", | |
429 Mercurial actually does match "foo" against that. That's because exact | |
430 matches are treated specially. So, since this differencematcher is used for | |
431 excludes, it needs to special-case exact matching. | |
432 | |
433 The second matcher's non-matching-attributes (root, cwd, bad, explicitdir, | |
434 traversedir) are ignored. | |
435 | |
436 TODO: If we want to keep the behavior described above for exact matches, we | |
437 should consider instead treating the above case something like this: | |
438 union(exact(foo), difference(pattern(foo), include(foo))) | |
439 ''' | |
440 def __init__(self, m1, m2): | |
441 super(differencematcher, self).__init__(m1._root, m1._cwd) | |
442 self._m1 = m1 | |
443 self._m2 = m2 | |
444 self.bad = m1.bad | |
445 self.explicitdir = m1.explicitdir | |
446 self.traversedir = m1.traversedir | |
447 | |
448 def matchfn(self, f): | |
449 return self._m1(f) and (not self._m2(f) or self._m1.exact(f)) | |
450 | |
451 @propertycache | |
452 def _files(self): | |
453 if self.isexact(): | |
454 return [f for f in self._m1.files() if self(f)] | |
455 # If m1 is not an exact matcher, we can't easily figure out the set of | |
456 # files, because its files() are not always files. For example, if | |
457 # m1 is "path:dir" and m2 is "rootfileins:.", we don't | |
458 # want to remove "dir" from the set even though it would match m2, | |
459 # because the "dir" in m1 may not be a file. | |
460 return self._m1.files() | |
461 | |
462 def visitdir(self, dir): | |
463 if self._m2.visitdir(dir) == 'all': | |
464 # There's a bug here: If m1 matches file 'dir/file' and m2 excludes | |
465 # 'dir' (recursively), we should still visit 'dir' due to the | |
466 # exception we have for exact matches. | |
467 return False | |
468 return bool(self._m1.visitdir(dir)) | |
469 | |
470 def isexact(self): | |
471 return self._m1.isexact() | |
472 | |
473 def anypats(self): | |
474 return self._m1.anypats() or self._m2.anypats() | |
475 | |
476 def prefix(self): | |
477 return not self.always() and not self.isexact() and not self.anypats() | |
478 | |
479 def __repr__(self): | |
480 return ('<differencematcher m1=%r, m2=%r>' % (self._m1, self._m2)) | |
420 | 481 |
421 class subdirmatcher(basematcher): | 482 class subdirmatcher(basematcher): |
422 """Adapt a matcher to work on a subdirectory only. | 483 """Adapt a matcher to work on a subdirectory only. |
423 | 484 |
424 The paths are remapped to remove/insert the path as needed: | 485 The paths are remapped to remove/insert the path as needed: |