diff mercurial/revset.py @ 45725:99b8b73eb622

revset: add diff(pattern) predicate for "grep --diff" I find this is useful in GUI log viewer since the tool only needs to support "log -rREV" command. This is basic implementation. Windowed search is not implemented since it wouldn't work pretty well with the smartset API. And filename matcher is not supported because the syntax isn't determined. My idea is to add handling of diff(pattern, file(..)) and diff(pattern, follow(..)), which will then be evolved to a full revset+matcher combinator support: x & diff(pattern, y & z) ===== y & z builds (revs(y) & revs(z), matcher(y) & matcher(z)) pair, and narrows the search space of diff() ==================== diff() returns matched (revs, matcher) pair ======================== revs and matcher will be combined respectively by &-operator, and the matcher will optionally be used to filter "hg log -p" output The predicate name "diff()" wouldn't be great, but grep() is already used. Another options I can think of are "grepdiff()" and "containsdiff()". Naming suggestions are welcome.
author Yuya Nishihara <yuya@tcha.org>
date Tue, 08 Sep 2020 18:16:24 +0900
parents b90d7e7f39db
children c00595736595
line wrap: on
line diff
--- 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):
     """