Teach GNU grep(1)'s '-o' ('--only-matching') to 'git-grep'. This option
prints only the matching components of each line. It writes multiple
lines if more than one match exists on a given line.

For example:

  $ git grep -on --column --heading git -- README.md | head -3
  README.md
  15:56:git
  18:20:git

By using show_line_header(), 'git grep --only-matching' correctly
respects the '--header' option:

  $ git grep -on --column --heading git -- README.md | head -4
  README.md
  15:56:git
  18:20:git
  19:16:git

Signed-off-by: Taylor Blau <m...@ttaylorr.com>
---
 Documentation/git-grep.txt |  6 +++++-
 builtin/grep.c             |  1 +
 grep.c                     | 23 ++++++++++++++++++++---
 grep.h                     |  1 +
 t/t7810-grep.sh            | 33 +++++++++++++++++++++++++++++++++
 5 files changed, 60 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index d451cd8883..9754923041 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -20,7 +20,7 @@ SYNOPSIS
           [-c | --count] [--all-match] [-q | --quiet]
           [--max-depth <depth>]
           [--color[=<when>] | --no-color]
-          [--break] [--heading] [-p | --show-function]
+          [--break] [--heading] [-o | --only-matching] [-p | --show-function]
           [-A <post-context>] [-B <pre-context>] [-C <context>]
           [-W | --function-context]
           [--threads <num>]
@@ -221,6 +221,10 @@ providing this option will cause it to die.
        Show the filename above the matches in that file instead of
        at the start of each shown line.
 
+--o::
+--only-matching::
+       Show only the matching part of the lines.
+
 -p::
 --show-function::
        Show the preceding line that contains the function name of
diff --git a/builtin/grep.c b/builtin/grep.c
index 5c83f17759..5028bf96cf 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -851,6 +851,7 @@ int cmd_grep(int argc, const char **argv, const char 
*prefix)
                        N_("print empty line between matches from different 
files")),
                OPT_BOOL(0, "heading", &opt.heading,
                        N_("show filename only once above matches from same 
file")),
+               OPT_BOOL('o', "only-matching", &opt.only_matching, N_("show 
only matches")),
                OPT_GROUP(""),
                OPT_CALLBACK('C', "context", &opt, N_("n"),
                        N_("show <n> context lines before and after matches"),
diff --git a/grep.c b/grep.c
index 89dd719e4d..da3f8e6266 100644
--- a/grep.c
+++ b/grep.c
@@ -1422,11 +1422,13 @@ static void show_line(struct grep_opt *opt, char *bol, 
char *eol,
                }
        }
        show_line_header(opt, name, lno, cno, sign);
-       if (opt->color) {
+       if (opt->color || opt->only_matching) {
                regmatch_t match;
                enum grep_context ctx = GREP_CONTEXT_BODY;
                int ch = *eol;
                int eflags = 0;
+               int first = 1;
+               int offset = 1;
 
                if (sign == ':')
                        match_color = opt->color_match_selected;
@@ -1443,16 +1445,31 @@ static void show_line(struct grep_opt *opt, char *bol, 
char *eol,
                        if (match.rm_so == match.rm_eo)
                                break;
 
-                       output_color(opt, bol, match.rm_so, line_color);
+                       if (!opt->only_matching)
+                               output_color(opt, bol, match.rm_so, line_color);
+                       else if (!first) {
+                               /*
+                                * We are given --only-matching, and this is not
+                                * the first match on a line. Reprint the
+                                * newline and header before showing another
+                                * match.
+                                */
+                               opt->output(opt, "\n", 1);
+                               show_line_header(opt, name, lno,
+                                       offset+match.rm_so, sign);
+                       }
                        output_color(opt, bol + match.rm_so,
                                     match.rm_eo - match.rm_so, match_color);
+                       offset += match.rm_eo;
                        bol += match.rm_eo;
                        rest -= match.rm_eo;
                        eflags = REG_NOTBOL;
+                       first = 0;
                }
                *eol = ch;
        }
-       output_color(opt, bol, rest, line_color);
+       if (!opt->only_matching)
+               output_color(opt, bol, rest, line_color);
        opt->output(opt, "\n", 1);
 }
 
diff --git a/grep.h b/grep.h
index 08a0b391c5..24c1460100 100644
--- a/grep.h
+++ b/grep.h
@@ -126,6 +126,7 @@ struct grep_opt {
        const char *prefix;
        int prefix_length;
        regex_t regexp;
+       int only_matching;
        int linenum;
        int columnnum;
        int invert;
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index a03c3416e7..ef7f4ce725 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -1420,6 +1420,39 @@ test_expect_success 'grep --heading' '
        test_cmp expected actual
 '
 
+cat >expected <<EOF
+file:1:5:mmap
+file:2:5:mmap
+file:3:5:mmap
+file:3:14:mmap
+file:4:5:mmap
+file:4:14:mmap
+file:5:5:mmap
+file:5:14:mmap
+EOF
+
+test_expect_success 'grep --only-matching' '
+       git grep --only-matching --line-number --column mmap file >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<EOF
+file
+1:5:mmap
+2:5:mmap
+3:5:mmap
+3:14:mmap
+4:5:mmap
+4:14:mmap
+5:5:mmap
+5:14:mmap
+EOF
+
+test_expect_success 'grep --only-matching --heading' '
+       git grep --only-matching --heading --line-number --column mmap file 
>actual &&
+       test_cmp expected actual
+'
+
 cat >expected <<EOF
 <BOLD;GREEN>hello.c<RESET>
 4:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)
-- 
2.17.0

Reply via email to