From c707ded332375a5b51c1acf9b7ce1c29cbd4536d Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Tue, 3 Jul 2018 16:51:56 -0500 Subject: [PATCH 1/2] grep.c: extract show_line_header() The grep code invokes show_line() to display the contents of a matched or context line in its output. Part of this execution is to print a line header that includes information such as the kind, the line- and column-number and etc. of that match. To prepare for the addition of an option to print only the matching component(s) of a non-context line, we must prepare for the possibility that a single line may contain multiple matching parts, and thus will need multiple headers printed for a single line. Extracting show_line_header allows us to do just that. In the subsequent commit, it will be used within the colorization loop to print out only the matching parts of a line, optionally with LFs delimiting sub-matches. Signed-off-by: Taylor Blau Signed-off-by: Junio C Hamano --- grep.c | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/grep.c b/grep.c index 992673fe7e..4ff8a73043 100644 --- a/grep.c +++ b/grep.c @@ -1410,26 +1410,9 @@ static int next_match(struct grep_opt *opt, char *bol, char *eol, return hit; } -static void show_line(struct grep_opt *opt, char *bol, char *eol, - const char *name, unsigned lno, ssize_t cno, char sign) +static void show_line_header(struct grep_opt *opt, const char *name, + unsigned lno, ssize_t cno, char sign) { - int rest = eol - bol; - const char *match_color, *line_color = NULL; - - if (opt->file_break && opt->last_shown == 0) { - if (opt->show_hunk_mark) - opt->output(opt, "\n", 1); - } else if (opt->pre_context || opt->post_context || opt->funcbody) { - if (opt->last_shown == 0) { - if (opt->show_hunk_mark) { - output_color(opt, "--", 2, opt->color_sep); - opt->output(opt, "\n", 1); - } - } else if (lno > opt->last_shown + 1) { - output_color(opt, "--", 2, opt->color_sep); - opt->output(opt, "\n", 1); - } - } if (opt->heading && opt->last_shown == 0) { output_color(opt, name, strlen(name), opt->color_filename); opt->output(opt, "\n", 1); @@ -1457,6 +1440,29 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, output_color(opt, buf, strlen(buf), opt->color_columnno); output_sep(opt, sign); } +} + +static void show_line(struct grep_opt *opt, char *bol, char *eol, + const char *name, unsigned lno, ssize_t cno, char sign) +{ + int rest = eol - bol; + const char *match_color, *line_color = NULL; + + if (opt->file_break && opt->last_shown == 0) { + if (opt->show_hunk_mark) + opt->output(opt, "\n", 1); + } else if (opt->pre_context || opt->post_context || opt->funcbody) { + if (opt->last_shown == 0) { + if (opt->show_hunk_mark) { + output_color(opt, "--", 2, opt->color_sep); + opt->output(opt, "\n", 1); + } + } else if (lno > opt->last_shown + 1) { + output_color(opt, "--", 2, opt->color_sep); + opt->output(opt, "\n", 1); + } + } + show_line_header(opt, name, lno, cno, sign); if (opt->color) { regmatch_t match; enum grep_context ctx = GREP_CONTEXT_BODY; From 9d8db06eb4a5d5577db559a0059a377914c1ce5a Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 9 Jul 2018 15:33:47 -0500 Subject: [PATCH 2/2] grep.c: teach 'git grep --only-matching' Teach 'git grep --only-matching', a new option to only print the matching part(s) of a line. For instance, a line containing the following (taken from README.md:27): (`man gitcvs-migration` or `git help cvs-migration` if git is Is printed as follows: $ git grep --line-number --column --only-matching -e git -- \ README.md | grep ":27" README.md:27:7:git README.md:27:16:git README.md:27:38:git The patch works mostly as one would expect, with the exception of a few considerations that are worth mentioning here. Like GNU grep, this patch ignores --only-matching when --invert (-v) is given. There is a sensible answer here, but parity with the behavior of other tools is preferred. Because a line might contain more than one match, there are special considerations pertaining to when to print line headers, newlines, and how to increment the match column offset. The line header and newlines are handled as a special case within the main loop to avoid polluting the surrounding code with conditionals that have large blocks. Signed-off-by: Taylor Blau Signed-off-by: Junio C Hamano --- Documentation/git-grep.txt | 7 +++++- builtin/grep.c | 6 +++++ grep.c | 51 ++++++++++++++++++++++++++------------ grep.h | 1 + t/t7810-grep.sh | 15 +++++++++++ 5 files changed, 63 insertions(+), 17 deletions(-) diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 0de3493b80..a3049af1a3 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -17,7 +17,7 @@ SYNOPSIS [-l | --files-with-matches] [-L | --files-without-match] [(-O | --open-files-in-pager) []] [-z | --null] - [-c | --count] [--all-match] [-q | --quiet] + [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet] [--max-depth ] [--color[=] | --no-color] [--break] [--heading] [-p | --show-function] @@ -201,6 +201,11 @@ providing this option will cause it to die. Output \0 instead of the character that normally follows a file name. +-o:: +--only-matching:: + Print only the matched (non-empty) parts of a matching line, with each such + part on a separate output line. + -c:: --count:: Instead of showing every matched line, show the number of diff --git a/builtin/grep.c b/builtin/grep.c index 61bcaf6e58..228b83990f 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -843,6 +843,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOL_F('z', "null", &opt.null_following_name, N_("print NUL after filenames"), PARSE_OPT_NOCOMPLETE), + OPT_BOOL('o', "only-matching", &opt.only_matching, + N_("show only matching parts of a line")), OPT_BOOL('c', "count", &opt.count, N_("show the number of matches instead of matching lines")), OPT__COLOR(&opt.color, N_("highlight matches")), @@ -962,6 +964,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!opt.pattern_list) die(_("no pattern given.")); + /* --only-matching has no effect with --invert. */ + if (opt.invert) + opt.only_matching = 0; + /* * We have to find "--" in a separate pass, because its presence * influences how we will parse arguments that come before it. diff --git a/grep.c b/grep.c index 4ff8a73043..49a744f96b 100644 --- a/grep.c +++ b/grep.c @@ -51,6 +51,7 @@ void init_grep_defaults(void) color_set(opt->color_match_selected, GIT_COLOR_BOLD_RED); color_set(opt->color_selected, ""); color_set(opt->color_sep, GIT_COLOR_CYAN); + opt->only_matching = 0; opt->color = -1; opt->output = std_output; } @@ -158,6 +159,7 @@ void grep_init(struct grep_opt *opt, const char *prefix) opt->pattern_tail = &opt->pattern_list; opt->header_tail = &opt->header_list; + opt->only_matching = def->only_matching; opt->color = def->color; opt->extended_regexp_option = def->extended_regexp_option; opt->pattern_type_option = def->pattern_type_option; @@ -1446,7 +1448,8 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, const char *name, unsigned lno, ssize_t cno, char sign) { int rest = eol - bol; - const char *match_color, *line_color = NULL; + const char *match_color = NULL; + const char *line_color = NULL; if (opt->file_break && opt->last_shown == 0) { if (opt->show_hunk_mark) @@ -1462,39 +1465,55 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, opt->output(opt, "\n", 1); } } - show_line_header(opt, name, lno, cno, sign); - if (opt->color) { + if (!opt->only_matching) { + /* + * In case the line we're being called with contains more than + * one match, leave printing each header to the loop below. + */ + show_line_header(opt, name, lno, cno, sign); + } + if (opt->color || opt->only_matching) { regmatch_t match; enum grep_context ctx = GREP_CONTEXT_BODY; int ch = *eol; int eflags = 0; - if (sign == ':') - match_color = opt->color_match_selected; - else - match_color = opt->color_match_context; - if (sign == ':') - line_color = opt->color_selected; - else if (sign == '-') - line_color = opt->color_context; - else if (sign == '=') - line_color = opt->color_function; + if (opt->color) { + if (sign == ':') + match_color = opt->color_match_selected; + else + match_color = opt->color_match_context; + if (sign == ':') + line_color = opt->color_selected; + else if (sign == '-') + line_color = opt->color_context; + else if (sign == '=') + line_color = opt->color_function; + } *eol = '\0'; while (next_match(opt, bol, eol, ctx, &match, eflags)) { if (match.rm_so == match.rm_eo) break; - output_color(opt, bol, match.rm_so, line_color); + if (opt->only_matching) + show_line_header(opt, name, lno, cno, sign); + else + output_color(opt, bol, match.rm_so, line_color); output_color(opt, bol + match.rm_so, match.rm_eo - match.rm_so, match_color); + if (opt->only_matching) + opt->output(opt, "\n", 1); bol += match.rm_eo; + cno += match.rm_eo; rest -= match.rm_eo; eflags = REG_NOTBOL; } *eol = ch; } - output_color(opt, bol, rest, line_color); - opt->output(opt, "\n", 1); + if (!opt->only_matching) { + output_color(opt, bol, rest, line_color); + opt->output(opt, "\n", 1); + } } #ifndef NO_PTHREADS diff --git a/grep.h b/grep.h index 08a0b391c5..4d474d8ec4 100644 --- a/grep.h +++ b/grep.h @@ -150,6 +150,7 @@ struct grep_opt { int relative; int pathname; int null_following_name; + int only_matching; int color; int max_depth; int funcname; diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 9312c8daf5..d8c232dbf4 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -262,6 +262,21 @@ do fi ' + test_expect_success "grep $L (with --column, --only-matching)" ' + { + echo ${HC}file:1:5:mmap + echo ${HC}file:2:5:mmap + echo ${HC}file:3:5:mmap + echo ${HC}file:3:13:mmap + echo ${HC}file:4:5:mmap + echo ${HC}file:4:13:mmap + echo ${HC}file:5:5:mmap + echo ${HC}file:5:13:mmap + } >expected && + git grep --column -n -o -e mmap $H >actual && + test_cmp expected actual + ' + test_expect_success "grep $L (t-1)" ' echo "${HC}t/t:1:test" >expected && git grep -n -e test $H >actual &&