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 <me@xxxxxxxxxxxx> --- 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