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.
--- 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