revset: add diff(pattern) predicate for "grep --diff"
authorYuya Nishihara <yuya@tcha.org>
Tue, 08 Sep 2020 18:16:24 +0900
changeset 45725 99b8b73eb622
parent 45724 ac39a8a214b1
child 45726 d1072cba8aff
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.
mercurial/revset.py
tests/test-grep.t
--- 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):
     """
--- a/tests/test-grep.t	Mon Oct 05 20:40:39 2020 +0900
+++ b/tests/test-grep.t	Tue Sep 08 18:16:24 2020 +0900
@@ -21,6 +21,18 @@
   grep: invalid match pattern: nothing to repeat* (glob)
   [1]
 
+invalid revset syntax
+
+  $ hg log -r 'diff()'
+  hg: parse error: diff takes at least 1 argument
+  [255]
+  $ hg log -r 'diff(:)'
+  hg: parse error: diff requires a string pattern
+  [255]
+  $ hg log -r 'diff("re:**test**")'
+  hg: parse error: invalid regular expression: nothing to repeat* (glob)
+  [255]
+
 simple
 
   $ hg grep -r tip:0 '.*'
@@ -553,6 +565,18 @@
   color:2:-:orange
   color:1:+:orange
 
+revset predicate for "grep --diff"
+
+  $ hg log -qr 'diff("re:^bl...$")'
+  0:203191eb5e21
+  $ hg log -qr 'diff("orange")'
+  1:7c585a21e0d1
+  2:11bd8bc8d653
+  3:e0116d3829f8
+  $ hg log -qr '2:0 & diff("orange")'
+  2:11bd8bc8d653
+  1:7c585a21e0d1
+
 test substring match: '^' should only match at the beginning
 
   $ hg grep -r tip:0 '^.' --config extensions.color= --color debug