mercurial/revset.py
changeset 45725 99b8b73eb622
parent 45701 b90d7e7f39db
child 45750 c00595736595
--- a/mercurial/revset.py	Mon Oct 05 20:40:39 2020 +0900
+++ b/mercurial/revset.py	Tue Sep 08 18:16:24 2020 +0900
@@ -17,6 +17,7 @@
     diffutil,
     encoding,
     error,
+    grep as grepmod,
     hbisect,
     match as matchmod,
     node,
@@ -993,6 +994,45 @@
     )
 
 
+@predicate(b'diff(pattern)', weight=110)
+def diff(repo, subset, x):
+    """Search revision differences for when the pattern was added or removed.
+
+    The pattern may be a substring literal or a regular expression. See
+    :hg:`help revisions.patterns`.
+    """
+    args = getargsdict(x, b'diff', b'pattern')
+    if b'pattern' not in args:
+        # i18n: "diff" is a keyword
+        raise error.ParseError(_(b'diff takes at least 1 argument'))
+
+    pattern = getstring(args[b'pattern'], _(b'diff requires a string pattern'))
+    regexp = stringutil.substringregexp(pattern, re.M)
+
+    # TODO: add support for file pattern and --follow. For example,
+    # diff(pattern[, set]) where set may be file(pattern) or follow(pattern),
+    # and we'll eventually add a support for narrowing files by revset?
+    fmatch = matchmod.always()
+
+    def makefilematcher(ctx):
+        return fmatch
+
+    # TODO: search in a windowed way
+    searcher = grepmod.grepsearcher(repo.ui, repo, regexp, diff=True)
+
+    def testdiff(rev):
+        # consume the generator to discard revfiles/matches cache
+        found = False
+        for fn, ctx, pstates, states in searcher.searchfiles(
+            baseset([rev]), makefilematcher
+        ):
+            if next(grepmod.difflinestates(pstates, states), None):
+                found = True
+        return found
+
+    return subset.filter(testdiff, condrepr=(b'<diff %r>', pattern))
+
+
 @predicate(b'contentdivergent()', safe=True)
 def contentdivergent(repo, subset, x):
     """