[PATCH 8 of 9] grep: add support for inverting matches

Idan Kamara idankk86 at gmail.com
Sun Oct 14 15:54:25 CDT 2012


# HG changeset patch
# User Idan Kamara <idankk86 at gmail.com>
# Date 1350073302 -7200
# Node ID fc9b6a59bb64e8fff98671f2e6627f10bd889d0f
# Parent  726226e2c3947c4b1bc1e5b0cd8a75e28d5f2d27
grep: add support for inverting matches

Since we support multiline regexps the implementation is a lot more
complicated than it could have been. We aren't going over line by
line to find matches so we can't simply return those lines that don't
match any of the patterns.

Instead, we take the lines between i and j where i is the last line of the
previous match and j is the first line of the current match. Lastly, we take
all lines after the last line of the last match.

Unfortunately we can't use the common short option -v here since that's
taken by --verbose which can also actually change grep's output so we
can't even make an exception.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -2888,6 +2888,7 @@
     ('e', 'regexp', [],
      _('use this pattern to find matches (must be used if a pattern'
        ' starts with -), multiple patterns are or-ed'), _('PATTERN')),
+    ('', 'invert-match', None, _('invert the sense of matching')),
     ('r', 'rev', [],
      _('only search files changed within revision range'), _('REV')),
     ('u', 'user', None, _('list the author (long with -v)')),
@@ -2929,6 +2930,7 @@
     sep, eol = ':', '\n'
     if opts.get('print0'):
         sep = eol = '\0'
+    invert = opts.get('invert_match')
 
     getfile = util.lrucachefunc(repo.file)
 
@@ -2987,9 +2989,42 @@
     def grepbody(fn, rev, body):
         matches[rev].setdefault(fn, [])
         m = matches[rev][fn]
+        prevlend = 0
         for lnum, lstart, lend, cstart, cend, line in matchlines(body):
-            s = linestate(line, lnum, lstart, lend, cstart, cend)
-            m.append(s)
+            if invert:
+                if lstart - 1 - prevlend > 0:
+                    lookback = body[prevlend:lstart - 1]
+                    inverts = lookback.split('\n')
+                    invertedlnum = lnum - len(inverts)
+                    invertedlstart = invertedlend = lstart - 1
+                    for inv in inverts:
+                        invertedlstart -= len(inv)
+                        s = linestate(inv, invertedlnum, invertedlstart,
+                                      invertedlend, -1, -1)
+                        m.append(s)
+
+                        invertedlnum += 1
+                        invertedlend = invertedlstart - 2
+                        invertedlstart = invertedlend
+            else:
+                s = linestate(line, lnum, lstart, lend, cstart, cend)
+                m.append(s)
+            prevlend = lend
+        if invert and len(m) and lend != len(body):
+            lookahead = body[lend + 1:]
+            if lookahead:
+                inverts = lookahead.split('\n')
+                invertedlnum = lnum + 1
+                invertedlstart = invertedlend = lend + 1
+                for inv in inverts:
+                    invertedlend += len(inv)
+                    s = linestate(inv, invertedlnum, invertedlstart,
+                                  invertedlend, -1, -1)
+                    m.append(s)
+
+                    invertedlnum += 1
+                    invertedlstart = invertedlend + 1
+                    invertedlend = invertedlstart
 
     def difflinestates(a, b):
         sm = difflib.SequenceMatcher(None, a, b)
@@ -3054,9 +3089,17 @@
                 if not opts.get('text') and binary():
                     ui.write(" Binary file matches")
                 else:
-                    ui.write(before)
-                    ui.write(match, label='grep.match')
-                    ui.write(after)
+                    # We don't highlight anything on matches that were
+                    # inverted.
+                    if l.colstart == -1:
+                        ui.write(l.line)
+                    else:
+                        before = l.line[:l.colstart]
+                        match = l.line[l.colstart:l.colend]
+                        after = l.line[l.colend:]
+                        ui.write(before)
+                        ui.write(match, label='grep.match')
+                        ui.write(after)
             ui.write(eol)
             found = True
         return found
diff --git a/tests/test-debugcomplete.t b/tests/test-debugcomplete.t
--- a/tests/test-debugcomplete.t
+++ b/tests/test-debugcomplete.t
@@ -250,7 +250,7 @@
   debugwalk: include, exclude
   debugwireargs: three, four, five, ssh, remotecmd, insecure
   graft: rev, continue, edit, log, currentdate, currentuser, date, user, tool, dry-run
-  grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, no-filename, regexp, rev, user, date, include, exclude
+  grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, no-filename, regexp, invert-match, rev, user, date, include, exclude
   heads: rev, topo, active, closed, style, template
   help: extension, command, keyword
   identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure
diff --git a/tests/test-grep.t b/tests/test-grep.t
--- a/tests/test-grep.t
+++ b/tests/test-grep.t
@@ -39,6 +39,18 @@
   4:vaportight
   4:import/export
 
+invert
+  $ hg grep --invert-match port port
+  [1]
+  $ hg grep --invert-match -e exp -e vap -e imp port
+  [1]
+  $ hg grep -n --invert-match vapor port
+  port:4:1:export
+  port:4:3:import/export
+  port:4:4:
+  $ hg grep -n --invert-match 'ght\nimp' port
+  port:4:1:export
+
 simple with color
 
   $ hg --config extensions.color= grep --config color.mode=ansi \


More information about the Mercurial-devel mailing list