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: