{"thread":{"id":"48776","subject":"[PATCH 0/2] grep.c: teach --only-matching to 'git-grep(1)'","startedAt":"2018-06-25T21:25:59Z","lastAt":"2018-07-09T20:36:21Z","messageCount":28,"participants":["Taylor Blau","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"350902","messageId":"cover.1529961706.git.me@ttaylorr.com","threadId":"48776","inReplyTo":null,"subject":"[PATCH 0/2] grep.c: teach --only-matching to 'git-grep(1)'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-06-25T21:25:51Z","receivedAt":"2018-06-25T21:25:59Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Hi,\n\nAttached is a resurrection of my previous topic [1] to add\n'--only-matching' (in the style of GNU grep) to 'git grep'.\n\nThe changes are described in detail in each of the attached patches.\n\nSimilar to the series to add --column to 'git grep', I have restarted\nthis thread in order to not clutter the old one. I rewrote the patches\nfrom scratch today, and have based them on tb/grep-colno, on top of\nwhich they should apply cleanly.\n\nThanks in advance for your kind review :-).\n\n\nThanks,\nTaylor\n\n[1]: https://public-inbox.org/git/cover.1525492696.git.me@ttaylorr.com/\n\nTaylor Blau (2):\n  grep.c: extract show_line_header()\n  grep.c: teach 'git grep --only-matching'\n\n builtin/grep.c  |  6 ++++\n grep.c          | 90 +++++++++++++++++++++++++++++++------------------\n grep.h          |  1 +\n t/t7810-grep.sh | 15 +++++++++\n 4 files changed, 79 insertions(+), 33 deletions(-)\n\n--\n2.18.0\n"},{"id":"350903","messageId":"62e19e5583e30117b6754be5de5582a07c9e78dc.1529961706.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"cover.1529961706.git.me@ttaylorr.com","subject":"[PATCH 1/2] grep.c: extract show_line_header()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-06-25T21:25:56Z","receivedAt":"2018-06-25T21:26:04Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The grep code invokes show_line() to display the contents of a matched\nor context line in its output. Part of this execution is to print a line\nheader that includes information such as the kind, the line- and\ncolumn-number and etc. of that match.\n\nTo prepare for the addition of an option to print only the matching\ncomponent(s) of a non-context line, we must prepare for the possibility\nthat a single line may contain multiple matching parts, and thus will\nneed multiple headers printed for a single line.\n\nExtracting show_line_header allows us to do just that. In the subsequent\ncommit, it will be used within the colorization loop to print out only\nthe matching parts of a line, optionally with LFs delimiting\nsub-matches.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n grep.c | 44 +++++++++++++++++++++++++-------------------\n 1 file changed, 25 insertions(+), 19 deletions(-)\n\ndiff --git a/grep.c b/grep.c\nindex 992673fe7e..4ff8a73043 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1410,26 +1410,9 @@ static int next_match(struct grep_opt *opt, char *bol, char *eol,\n \treturn hit;\n }\n\n-static void show_line(struct grep_opt *opt, char *bol, char *eol,\n-\t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n+static void show_line_header(struct grep_opt *opt, const char *name,\n+\t\t\t     unsigned lno, ssize_t cno, char sign)\n {\n-\tint rest = eol - bol;\n-\tconst char *match_color, *line_color = NULL;\n-\n-\tif (opt->file_break && opt->last_shown == 0) {\n-\t\tif (opt->show_hunk_mark)\n-\t\t\topt->output(opt, \"\\n\", 1);\n-\t} else if (opt->pre_context || opt->post_context || opt->funcbody) {\n-\t\tif (opt->last_shown == 0) {\n-\t\t\tif (opt->show_hunk_mark) {\n-\t\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n-\t\t\t\topt->output(opt, \"\\n\", 1);\n-\t\t\t}\n-\t\t} else if (lno > opt->last_shown + 1) {\n-\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n-\t\t\topt->output(opt, \"\\n\", 1);\n-\t\t}\n-\t}\n \tif (opt->heading && opt->last_shown == 0) {\n \t\toutput_color(opt, name, strlen(name), opt->color_filename);\n \t\topt->output(opt, \"\\n\", 1);\n@@ -1457,6 +1440,29 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\toutput_color(opt, buf, strlen(buf), opt->color_columnno);\n \t\toutput_sep(opt, sign);\n \t}\n+}\n+\n+static void show_line(struct grep_opt *opt, char *bol, char *eol,\n+\t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n+{\n+\tint rest = eol - bol;\n+\tconst char *match_color, *line_color = NULL;\n+\n+\tif (opt->file_break && opt->last_shown == 0) {\n+\t\tif (opt->show_hunk_mark)\n+\t\t\topt->output(opt, \"\\n\", 1);\n+\t} else if (opt->pre_context || opt->post_context || opt->funcbody) {\n+\t\tif (opt->last_shown == 0) {\n+\t\t\tif (opt->show_hunk_mark) {\n+\t\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n+\t\t\t}\n+\t\t} else if (lno > opt->last_shown + 1) {\n+\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n+\t\t\topt->output(opt, \"\\n\", 1);\n+\t\t}\n+\t}\n+\tshow_line_header(opt, name, lno, cno, sign);\n \tif (opt->color) {\n \t\tregmatch_t match;\n \t\tenum grep_context ctx = GREP_CONTEXT_BODY;\n--\n2.18.0\n\n"},{"id":"350904","messageId":"f156bd7c897359926ec407e805ecb0630a8b12da.1529961706.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"cover.1529961706.git.me@ttaylorr.com","subject":"[PATCH 2/2] grep.c: teach 'git grep --only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-06-25T21:26:00Z","receivedAt":"2018-06-25T21:26:07Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Teach 'git grep --only-matching', a new option to only print the\nmatching part(s) of a line.\n\nFor instance, a line containing the following (taken from README.md:27):\n\n  (`man gitcvs-migration` or `git help cvs-migration` if git is\n\nIs printed as follows:\n\n  $ git grep -no -e git -- README.md | grep \":27\"\n  README.md:27:7:git\n  README.md:27:16:git\n  README.md:27:38:git\n\nThe patch works mostly as one would expect, with the exception of a few\nconsiderations that are worth mentioning here.\n\nLike GNU grep, this patch ignores --only-matching when --invert (-v) is\ngiven. There is a sensible answer here, but parity with the behavior of\nother tools is preferred.\n\nBecause a line might contain more than one match, there are special\nconsiderations pertaining to when to print line headers, newlines, and\nhow to increment the match column offset. The line header and newlines\nare handled as a special case within the main loop to avoid polluting\nthe surrounding code with conditionals that have large blocks.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/grep.c  |  6 ++++++\n grep.c          | 48 +++++++++++++++++++++++++++++++++---------------\n grep.h          |  1 +\n t/t7810-grep.sh | 15 +++++++++++++++\n 4 files changed, 55 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 61bcaf6e58..228b83990f 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -843,6 +843,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL_F('z', \"null\", &opt.null_following_name,\n \t\t\t   N_(\"print NUL after filenames\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_BOOL('o', \"only-matching\", &opt.only_matching,\n+\t\t\tN_(\"show only matching parts of a line\")),\n \t\tOPT_BOOL('c', \"count\", &opt.count,\n \t\t\tN_(\"show the number of matches instead of matching lines\")),\n \t\tOPT__COLOR(&opt.color, N_(\"highlight matches\")),\n@@ -962,6 +964,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (!opt.pattern_list)\n \t\tdie(_(\"no pattern given.\"));\n\n+\t/* --only-matching has no effect with --invert. */\n+\tif (opt.invert)\n+\t\topt.only_matching = 0;\n+\n \t/*\n \t * We have to find \"--\" in a separate pass, because its presence\n \t * influences how we will parse arguments that come before it.\ndiff --git a/grep.c b/grep.c\nindex 4ff8a73043..48cca6723e 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -51,6 +51,7 @@ void init_grep_defaults(void)\n \tcolor_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);\n \tcolor_set(opt->color_selected, \"\");\n \tcolor_set(opt->color_sep, GIT_COLOR_CYAN);\n+\topt->only_matching = 0;\n \topt->color = -1;\n \topt->output = std_output;\n }\n@@ -158,6 +159,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)\n \topt->pattern_tail = &opt->pattern_list;\n \topt->header_tail = &opt->header_list;\n\n+\topt->only_matching = def->only_matching;\n \topt->color = def->color;\n \topt->extended_regexp_option = def->extended_regexp_option;\n \topt->pattern_type_option = def->pattern_type_option;\n@@ -1462,39 +1464,55 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t\topt->output(opt, \"\\n\", 1);\n \t\t}\n \t}\n-\tshow_line_header(opt, name, lno, cno, sign);\n-\tif (opt->color) {\n+\tif (!opt->only_matching) {\n+\t\t/*\n+\t\t * In case the line we're being called with contains more than\n+\t\t * one match, leave printing each header to the loop below.\n+\t\t */\n+\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t}\n+\tif (opt->color || opt->only_matching) {\n \t\tregmatch_t match;\n \t\tenum grep_context ctx = GREP_CONTEXT_BODY;\n \t\tint ch = *eol;\n \t\tint eflags = 0;\n\n-\t\tif (sign == ':')\n-\t\t\tmatch_color = opt->color_match_selected;\n-\t\telse\n-\t\t\tmatch_color = opt->color_match_context;\n-\t\tif (sign == ':')\n-\t\t\tline_color = opt->color_selected;\n-\t\telse if (sign == '-')\n-\t\t\tline_color = opt->color_context;\n-\t\telse if (sign == '=')\n-\t\t\tline_color = opt->color_function;\n+\t\tif (opt->color) {\n+\t\t\tif (sign == ':')\n+\t\t\t\tmatch_color = opt->color_match_selected;\n+\t\t\telse\n+\t\t\t\tmatch_color = opt->color_match_context;\n+\t\t\tif (sign == ':')\n+\t\t\t\tline_color = opt->color_selected;\n+\t\t\telse if (sign == '-')\n+\t\t\t\tline_color = opt->color_context;\n+\t\t\telse if (sign == '=')\n+\t\t\t\tline_color = opt->color_function;\n+\t\t}\n \t\t*eol = '\\0';\n \t\twhile (next_match(opt, bol, eol, ctx, &match, eflags)) {\n \t\t\tif (match.rm_so == match.rm_eo)\n \t\t\t\tbreak;\n\n-\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t\t\telse\n+\t\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n \t\t\toutput_color(opt, bol + match.rm_so,\n \t\t\t\t     match.rm_eo - match.rm_so, match_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n \t\t\tbol += match.rm_eo;\n+\t\t\tcno += match.rm_eo;\n \t\t\trest -= match.rm_eo;\n \t\t\teflags = REG_NOTBOL;\n \t\t}\n \t\t*eol = ch;\n \t}\n-\toutput_color(opt, bol, rest, line_color);\n-\topt->output(opt, \"\\n\", 1);\n+\tif (!opt->only_matching) {\n+\t\toutput_color(opt, bol, rest, line_color);\n+\t\topt->output(opt, \"\\n\", 1);\n+\t}\n }\n\n #ifndef NO_PTHREADS\ndiff --git a/grep.h b/grep.h\nindex 08a0b391c5..4d474d8ec4 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -150,6 +150,7 @@ struct grep_opt {\n \tint relative;\n \tint pathname;\n \tint null_following_name;\n+\tint only_matching;\n \tint color;\n \tint max_depth;\n \tint funcname;\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex 9312c8daf5..d8c232dbf4 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -262,6 +262,21 @@ do\n \t\tfi\n \t'\n\n+\ttest_expect_success \"grep $L (with --column, --only-matching)\" '\n+\t\t{\n+\t\t\techo ${HC}file:1:5:mmap\n+\t\t\techo ${HC}file:2:5:mmap\n+\t\t\techo ${HC}file:3:5:mmap\n+\t\t\techo ${HC}file:3:13:mmap\n+\t\t\techo ${HC}file:4:5:mmap\n+\t\t\techo ${HC}file:4:13:mmap\n+\t\t\techo ${HC}file:5:5:mmap\n+\t\t\techo ${HC}file:5:13:mmap\n+\t\t} >expected &&\n+\t\tgit grep --column -n -o -e mmap $H >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n \ttest_expect_success \"grep $L (t-1)\" '\n \t\techo \"${HC}t/t:1:test\" >expected &&\n \t\tgit grep -n -e test $H >actual &&\n--\n2.18.0\n"},{"id":"351118","messageId":"xmqqsh58gp3p.fsf@gitster-ct.c.googlers.com","threadId":"48776","inReplyTo":"f156bd7c897359926ec407e805ecb0630a8b12da.1529961706.git.me@ttaylorr.com","subject":"Re: [PATCH 2/2] grep.c: teach 'git grep --only-matching'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-06-27T16:40:10Z","receivedAt":"2018-06-27T16:40:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n> -\t\tif (sign == ':')\n> -\t\t\tmatch_color = opt->color_match_selected;\n> -\t\telse\n> -\t\t\tmatch_color = opt->color_match_context;\n> -\t\tif (sign == ':')\n> -\t\t\tline_color = opt->color_selected;\n> -\t\telse if (sign == '-')\n> -\t\t\tline_color = opt->color_context;\n> -\t\telse if (sign == '=')\n> -\t\t\tline_color = opt->color_function;\n> +\t\tif (opt->color) {\n> +\t\t\tif (sign == ':')\n> +\t\t\t\tmatch_color = opt->color_match_selected;\n> +\t\t\telse\n> +\t\t\t\tmatch_color = opt->color_match_context;\n> +\t\t\tif (sign == ':')\n> +\t\t\t\tline_color = opt->color_selected;\n> +\t\t\telse if (sign == '-')\n> +\t\t\t\tline_color = opt->color_context;\n> +\t\t\telse if (sign == '=')\n> +\t\t\t\tline_color = opt->color_function;\n> +\t\t}\n\nThe above change (specifically, the fact that this now is enclosed\nin \"if (opt->color) { ... }\") unfortunately leaves match_color\nundefined at this point in the control flow.  The next loop then\ncalls output_color() with an undefined match_color and tricks stupid\ncompiler to issue a warning and makes -Werror build to fail.\n\n>  \t\t*eol = '\\0';\n>  \t\twhile (next_match(opt, bol, eol, ctx, &match, eflags)) {\n>  \t\t\tif (match.rm_so == match.rm_eo)\n>  \t\t\t\tbreak;\n>\n> -\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n> +\t\t\tif (opt->only_matching)\n> +\t\t\t\tshow_line_header(opt, name, lno, cno, sign);\n> +\t\t\telse\n> +\t\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n>  \t\t\toutput_color(opt, bol + match.rm_so,\n>  \t\t\t\t     match.rm_eo - match.rm_so, match_color);\n\noutput_color() does check want_color(opt->color) before using its\nlast parameter, and want_color() gives false for opt->color that is\n0 (i.e. leaves match_color to be undefined), so in this case, the\ncompiler is worried too much, but still, we should work it around if\nit is easy to do so.\n\nJust initializing match_color where it is defined at the beginning of\nshow_line() should be sufficient, I think.\n\n"},{"id":"351123","messageId":"20180627171657.GA26628@syl.attlocal.net","threadId":"48776","inReplyTo":"xmqqsh58gp3p.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 2/2] grep.c: teach 'git grep --only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-06-27T17:16:57Z","receivedAt":"2018-06-27T17:17:04Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jun 27, 2018 at 09:40:10AM -0700, Junio C Hamano wrote:\n> Taylor Blau <me@ttaylorr.com> writes:\n>\n> > -\t\tif (sign == ':')\n> > -\t\t\tmatch_color = opt->color_match_selected;\n> > -\t\telse\n> > -\t\t\tmatch_color = opt->color_match_context;\n> > -\t\tif (sign == ':')\n> > -\t\t\tline_color = opt->color_selected;\n> > -\t\telse if (sign == '-')\n> > -\t\t\tline_color = opt->color_context;\n> > -\t\telse if (sign == '=')\n> > -\t\t\tline_color = opt->color_function;\n> > +\t\tif (opt->color) {\n> > +\t\t\tif (sign == ':')\n> > +\t\t\t\tmatch_color = opt->color_match_selected;\n> > +\t\t\telse\n> > +\t\t\t\tmatch_color = opt->color_match_context;\n> > +\t\t\tif (sign == ':')\n> > +\t\t\t\tline_color = opt->color_selected;\n> > +\t\t\telse if (sign == '-')\n> > +\t\t\t\tline_color = opt->color_context;\n> > +\t\t\telse if (sign == '=')\n> > +\t\t\t\tline_color = opt->color_function;\n> > +\t\t}\n>\n> The above change (specifically, the fact that this now is enclosed\n> in \"if (opt->color) { ... }\") unfortunately leaves match_color\n> undefined at this point in the control flow.  The next loop then\n> calls output_color() with an undefined match_color and tricks stupid\n> compiler to issue a warning and makes -Werror build to fail.\n\nRight, this is because we are using the `while (next_match(...))` loop\nfor non-colorized output, which is new. This seems like a definite\nproblem, and certainly causes the -Werror build to fail for me, too.\n\n> >  \t\t*eol = '\\0';\n> >  \t\twhile (next_match(opt, bol, eol, ctx, &match, eflags)) {\n> >  \t\t\tif (match.rm_so == match.rm_eo)\n> >  \t\t\t\tbreak;\n> >\n> > -\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n> > +\t\t\tif (opt->only_matching)\n> > +\t\t\t\tshow_line_header(opt, name, lno, cno, sign);\n> > +\t\t\telse\n> > +\t\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n> >  \t\t\toutput_color(opt, bol + match.rm_so,\n> >  \t\t\t\t     match.rm_eo - match.rm_so, match_color);\n>\n> output_color() does check want_color(opt->color) before using its\n> last parameter, and want_color() gives false for opt->color that is\n> 0 (i.e. leaves match_color to be undefined), so in this case, the\n> compiler is worried too much, but still, we should work it around if\n> it is easy to do so.\n>\n> Just initializing match_color where it is defined at the beginning of\n> show_line() should be sufficient, I think.\n\nI think that we could also use the following, and leave the `if\n(opt->color)` conditional where it is:\n\ndiff --git a/grep.c b/grep.c\nindex 48cca6723e..b985fb3ee0 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1448,7 +1448,7 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n {\n \tint rest = eol - bol;\n-\tconst char *match_color, *line_color = NULL;\n+\tconst char *match_color = NULL, *line_color = NULL;\n\n \tif (opt->file_break && opt->last_shown == 0) {\n \t\tif (opt->show_hunk_mark)\n\nFrom my reading, output_color() appears happy to treat a NULL color as\nmeaningless, and instead redirect the call to `opt->output` without the\npreceding colorization and the reset afterwords.\n\nWe could also move that `if (opt->color)` block up closer to where\nline_color and match_color are initialized, which might appear cleaner.\nI think the best time to do that would be in a preparatory patch in this\nseries.\n\nWhat do you think?\n\n\nThanks,\nTaylor\n"},{"id":"351163","messageId":"xmqq6024djf2.fsf@gitster-ct.c.googlers.com","threadId":"48776","inReplyTo":"20180627171657.GA26628@syl.attlocal.net","subject":"Re: [PATCH 2/2] grep.c: teach 'git grep --only-matching'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-06-27T21:11:13Z","receivedAt":"2018-06-27T21:11:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n>> Just initializing match_color where it is defined at the beginning of\n>> show_line() should be sufficient, I think.\n>\n> I think that we could also use the following, and leave the `if\n> (opt->color)` conditional where it is:\n>\n> diff --git a/grep.c b/grep.c\n> index 48cca6723e..b985fb3ee0 100644\n> --- a/grep.c\n> +++ b/grep.c\n> @@ -1448,7 +1448,7 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n>  \t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n>  {\n>  \tint rest = eol - bol;\n> -\tconst char *match_color, *line_color = NULL;\n> +\tconst char *match_color = NULL, *line_color = NULL;\n>\n>  \tif (opt->file_break && opt->last_shown == 0) {\n>  \t\tif (opt->show_hunk_mark)\n\nYou say \"we could also\", but the above is exactly what I suggested,\nso we seem to be on the same page, which is good ;-)\n\n"},{"id":"351165","messageId":"20180627212248.GA1282@syl.attlocal.net","threadId":"48776","inReplyTo":"xmqq6024djf2.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 2/2] grep.c: teach 'git grep --only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-06-27T21:22:48Z","receivedAt":"2018-06-27T21:22:55Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Jun 27, 2018 at 02:11:13PM -0700, Junio C Hamano wrote:\n> Taylor Blau <me@ttaylorr.com> writes:\n>\n> >> Just initializing match_color where it is defined at the beginning of\n> >> show_line() should be sufficient, I think.\n> >\n> > I think that we could also use the following, and leave the `if\n> > (opt->color)` conditional where it is:\n> >\n> > diff --git a/grep.c b/grep.c\n> > index 48cca6723e..b985fb3ee0 100644\n> > --- a/grep.c\n> > +++ b/grep.c\n> > @@ -1448,7 +1448,7 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n> >  \t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n> >  {\n> >  \tint rest = eol - bol;\n> > -\tconst char *match_color, *line_color = NULL;\n> > +\tconst char *match_color = NULL, *line_color = NULL;\n> >\n> >  \tif (opt->file_break && opt->last_shown == 0) {\n> >  \t\tif (opt->show_hunk_mark)\n>\n> You say \"we could also\", but the above is exactly what I suggested,\n> so we seem to be on the same page, which is good ;-)\n\n\nAh, sorry -- I misinterpreted your meaning \"initializing match_color\nwhere it is defined\" to mean bringing the large `if (opt->color)`\nconditional up, instead of just assigning to NULL.\n\nLet's do that and leave the `if (opt->color)` block where it is?\n\n\nThanks,\nTaylor\n"},{"id":"351253","messageId":"20180628183231.GA12657@sigill.intra.peff.net","threadId":"48776","inReplyTo":"f156bd7c897359926ec407e805ecb0630a8b12da.1529961706.git.me@ttaylorr.com","subject":"Re: [PATCH 2/2] grep.c: teach 'git grep --only-matching'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-06-28T18:32:31Z","receivedAt":"2018-06-28T18:32:36Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jun 25, 2018 at 04:26:00PM -0500, Taylor Blau wrote:\n\n> For instance, a line containing the following (taken from README.md:27):\n> \n>   (`man gitcvs-migration` or `git help cvs-migration` if git is\n> \n> Is printed as follows:\n> \n>   $ git grep -no -e git -- README.md | grep \":27\"\n>   README.md:27:7:git\n>   README.md:27:16:git\n>   README.md:27:38:git\n\nThis is with \"--column\", too, right?\n\n> Like GNU grep, this patch ignores --only-matching when --invert (-v) is\n> given. There is a sensible answer here, but parity with the behavior of\n> other tools is preferred.\n\nYeah, after all of our discussion about inverted matching and columns,\nI'm sure we could come up with _some_ answer. But I agree that what you\nhave here is quite sensible, and matching GNU grep seems like a good\nidea.\n\n> ---\n>  builtin/grep.c  |  6 ++++++\n>  grep.c          | 48 +++++++++++++++++++++++++++++++++---------------\n>  grep.h          |  1 +\n>  t/t7810-grep.sh | 15 +++++++++++++++\n>  4 files changed, 55 insertions(+), 15 deletions(-)\n\nThe patch itself looks pretty straightforward to me (especially with\n\"-w\"). I didn't hit the compiler warning that Junio did (I have gcc\n7.3.0). But I agree it's better to avoid even passing an uninitialized\nvariable to another function.\n\n-Peff\n"},{"id":"351555","messageId":"cover.1530562110.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"cover.1529961706.git.me@ttaylorr.com","subject":"[PATCH v2 0/2] teach --only-matching to 'git-grep(1)'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-02T20:08:54Z","receivedAt":"2018-07-02T20:09:01Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Hi,\n\nAttached is the second re-roll of my series to teach 'git grep\n--only-matching'. Since last time, not much has changed. The change I\ndid include is summarized below, and an inter-diff is attached as\nalways.\n\n  - Initialize both match_color and line_color to NULL, thereby\n    silencing a compiler warning where match_color was given to\n    opt->output_color uninitialized [1].\n\nThanks in advance for your review :-).\n\n\nThanks,\nTaylor\n\n[1]: https://public-inbox.org/git/xmqqsh58gp3p.fsf@gitster-ct.c.googlers.com/\n\nTaylor Blau (2):\n  grep.c: extract show_line_header()\n  grep.c: teach 'git grep --only-matching'\n\n builtin/grep.c  |  6 ++++\n grep.c          | 91 +++++++++++++++++++++++++++++++------------------\n grep.h          |  1 +\n t/t7810-grep.sh | 15 ++++++++\n 4 files changed, 80 insertions(+), 33 deletions(-)\n\nInter-diff (since v1):\n\ndiff --git a/grep.c b/grep.c\nindex 48cca6723e..49a744f96b 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1448,7 +1448,8 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n                      const char *name, unsigned lno, ssize_t cno, char sign)\n {\n        int rest = eol - bol;\n-       const char *match_color, *line_color = NULL;\n+       const char *match_color = NULL;\n+       const char *line_color = NULL;\n\n        if (opt->file_break && opt->last_shown == 0) {\n                if (opt->show_hunk_mark)\n\n--\n2.18.0\n"},{"id":"351556","messageId":"62e19e5583e30117b6754be5de5582a07c9e78dc.1530562110.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"cover.1530562110.git.me@ttaylorr.com","subject":"[PATCH v2 1/2] grep.c: extract show_line_header()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-02T20:08:58Z","receivedAt":"2018-07-02T20:09:06Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The grep code invokes show_line() to display the contents of a matched\nor context line in its output. Part of this execution is to print a line\nheader that includes information such as the kind, the line- and\ncolumn-number and etc. of that match.\n\nTo prepare for the addition of an option to print only the matching\ncomponent(s) of a non-context line, we must prepare for the possibility\nthat a single line may contain multiple matching parts, and thus will\nneed multiple headers printed for a single line.\n\nExtracting show_line_header allows us to do just that. In the subsequent\ncommit, it will be used within the colorization loop to print out only\nthe matching parts of a line, optionally with LFs delimiting\nsub-matches.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n grep.c | 44 +++++++++++++++++++++++++-------------------\n 1 file changed, 25 insertions(+), 19 deletions(-)\n\ndiff --git a/grep.c b/grep.c\nindex 992673fe7e..4ff8a73043 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1410,26 +1410,9 @@ static int next_match(struct grep_opt *opt, char *bol, char *eol,\n \treturn hit;\n }\n \n-static void show_line(struct grep_opt *opt, char *bol, char *eol,\n-\t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n+static void show_line_header(struct grep_opt *opt, const char *name,\n+\t\t\t     unsigned lno, ssize_t cno, char sign)\n {\n-\tint rest = eol - bol;\n-\tconst char *match_color, *line_color = NULL;\n-\n-\tif (opt->file_break && opt->last_shown == 0) {\n-\t\tif (opt->show_hunk_mark)\n-\t\t\topt->output(opt, \"\\n\", 1);\n-\t} else if (opt->pre_context || opt->post_context || opt->funcbody) {\n-\t\tif (opt->last_shown == 0) {\n-\t\t\tif (opt->show_hunk_mark) {\n-\t\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n-\t\t\t\topt->output(opt, \"\\n\", 1);\n-\t\t\t}\n-\t\t} else if (lno > opt->last_shown + 1) {\n-\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n-\t\t\topt->output(opt, \"\\n\", 1);\n-\t\t}\n-\t}\n \tif (opt->heading && opt->last_shown == 0) {\n \t\toutput_color(opt, name, strlen(name), opt->color_filename);\n \t\topt->output(opt, \"\\n\", 1);\n@@ -1457,6 +1440,29 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\toutput_color(opt, buf, strlen(buf), opt->color_columnno);\n \t\toutput_sep(opt, sign);\n \t}\n+}\n+\n+static void show_line(struct grep_opt *opt, char *bol, char *eol,\n+\t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n+{\n+\tint rest = eol - bol;\n+\tconst char *match_color, *line_color = NULL;\n+\n+\tif (opt->file_break && opt->last_shown == 0) {\n+\t\tif (opt->show_hunk_mark)\n+\t\t\topt->output(opt, \"\\n\", 1);\n+\t} else if (opt->pre_context || opt->post_context || opt->funcbody) {\n+\t\tif (opt->last_shown == 0) {\n+\t\t\tif (opt->show_hunk_mark) {\n+\t\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n+\t\t\t}\n+\t\t} else if (lno > opt->last_shown + 1) {\n+\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n+\t\t\topt->output(opt, \"\\n\", 1);\n+\t\t}\n+\t}\n+\tshow_line_header(opt, name, lno, cno, sign);\n \tif (opt->color) {\n \t\tregmatch_t match;\n \t\tenum grep_context ctx = GREP_CONTEXT_BODY;\n-- \n2.18.0\n\n"},{"id":"351557","messageId":"dbd762f164d1fb906d15a1d3077dcb305d7298d7.1530562110.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"cover.1530562110.git.me@ttaylorr.com","subject":"[PATCH v2 2/2] grep.c: teach 'git grep --only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-02T20:09:01Z","receivedAt":"2018-07-02T20:09:09Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Teach 'git grep --only-matching', a new option to only print the\nmatching part(s) of a line.\n\nFor instance, a line containing the following (taken from README.md:27):\n\n  (`man gitcvs-migration` or `git help cvs-migration` if git is\n\nIs printed as follows:\n\n  $ git grep -no -e git -- README.md | grep \":27\"\n  README.md:27:7:git\n  README.md:27:16:git\n  README.md:27:38:git\n\nThe patch works mostly as one would expect, with the exception of a few\nconsiderations that are worth mentioning here.\n\nLike GNU grep, this patch ignores --only-matching when --invert (-v) is\ngiven. There is a sensible answer here, but parity with the behavior of\nother tools is preferred.\n\nBecause a line might contain more than one match, there are special\nconsiderations pertaining to when to print line headers, newlines, and\nhow to increment the match column offset. The line header and newlines\nare handled as a special case within the main loop to avoid polluting\nthe surrounding code with conditionals that have large blocks.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n builtin/grep.c  |  6 ++++++\n grep.c          | 51 +++++++++++++++++++++++++++++++++----------------\n grep.h          |  1 +\n t/t7810-grep.sh | 15 +++++++++++++++\n 4 files changed, 57 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 61bcaf6e58..228b83990f 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -843,6 +843,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL_F('z', \"null\", &opt.null_following_name,\n \t\t\t   N_(\"print NUL after filenames\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_BOOL('o', \"only-matching\", &opt.only_matching,\n+\t\t\tN_(\"show only matching parts of a line\")),\n \t\tOPT_BOOL('c', \"count\", &opt.count,\n \t\t\tN_(\"show the number of matches instead of matching lines\")),\n \t\tOPT__COLOR(&opt.color, N_(\"highlight matches\")),\n@@ -962,6 +964,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (!opt.pattern_list)\n \t\tdie(_(\"no pattern given.\"));\n \n+\t/* --only-matching has no effect with --invert. */\n+\tif (opt.invert)\n+\t\topt.only_matching = 0;\n+\n \t/*\n \t * We have to find \"--\" in a separate pass, because its presence\n \t * influences how we will parse arguments that come before it.\ndiff --git a/grep.c b/grep.c\nindex 4ff8a73043..49a744f96b 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -51,6 +51,7 @@ void init_grep_defaults(void)\n \tcolor_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);\n \tcolor_set(opt->color_selected, \"\");\n \tcolor_set(opt->color_sep, GIT_COLOR_CYAN);\n+\topt->only_matching = 0;\n \topt->color = -1;\n \topt->output = std_output;\n }\n@@ -158,6 +159,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)\n \topt->pattern_tail = &opt->pattern_list;\n \topt->header_tail = &opt->header_list;\n \n+\topt->only_matching = def->only_matching;\n \topt->color = def->color;\n \topt->extended_regexp_option = def->extended_regexp_option;\n \topt->pattern_type_option = def->pattern_type_option;\n@@ -1446,7 +1448,8 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n {\n \tint rest = eol - bol;\n-\tconst char *match_color, *line_color = NULL;\n+\tconst char *match_color = NULL;\n+\tconst char *line_color = NULL;\n \n \tif (opt->file_break && opt->last_shown == 0) {\n \t\tif (opt->show_hunk_mark)\n@@ -1462,39 +1465,55 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t\topt->output(opt, \"\\n\", 1);\n \t\t}\n \t}\n-\tshow_line_header(opt, name, lno, cno, sign);\n-\tif (opt->color) {\n+\tif (!opt->only_matching) {\n+\t\t/*\n+\t\t * In case the line we're being called with contains more than\n+\t\t * one match, leave printing each header to the loop below.\n+\t\t */\n+\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t}\n+\tif (opt->color || opt->only_matching) {\n \t\tregmatch_t match;\n \t\tenum grep_context ctx = GREP_CONTEXT_BODY;\n \t\tint ch = *eol;\n \t\tint eflags = 0;\n \n-\t\tif (sign == ':')\n-\t\t\tmatch_color = opt->color_match_selected;\n-\t\telse\n-\t\t\tmatch_color = opt->color_match_context;\n-\t\tif (sign == ':')\n-\t\t\tline_color = opt->color_selected;\n-\t\telse if (sign == '-')\n-\t\t\tline_color = opt->color_context;\n-\t\telse if (sign == '=')\n-\t\t\tline_color = opt->color_function;\n+\t\tif (opt->color) {\n+\t\t\tif (sign == ':')\n+\t\t\t\tmatch_color = opt->color_match_selected;\n+\t\t\telse\n+\t\t\t\tmatch_color = opt->color_match_context;\n+\t\t\tif (sign == ':')\n+\t\t\t\tline_color = opt->color_selected;\n+\t\t\telse if (sign == '-')\n+\t\t\t\tline_color = opt->color_context;\n+\t\t\telse if (sign == '=')\n+\t\t\t\tline_color = opt->color_function;\n+\t\t}\n \t\t*eol = '\\0';\n \t\twhile (next_match(opt, bol, eol, ctx, &match, eflags)) {\n \t\t\tif (match.rm_so == match.rm_eo)\n \t\t\t\tbreak;\n \n-\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t\t\telse\n+\t\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n \t\t\toutput_color(opt, bol + match.rm_so,\n \t\t\t\t     match.rm_eo - match.rm_so, match_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n \t\t\tbol += match.rm_eo;\n+\t\t\tcno += match.rm_eo;\n \t\t\trest -= match.rm_eo;\n \t\t\teflags = REG_NOTBOL;\n \t\t}\n \t\t*eol = ch;\n \t}\n-\toutput_color(opt, bol, rest, line_color);\n-\topt->output(opt, \"\\n\", 1);\n+\tif (!opt->only_matching) {\n+\t\toutput_color(opt, bol, rest, line_color);\n+\t\topt->output(opt, \"\\n\", 1);\n+\t}\n }\n \n #ifndef NO_PTHREADS\ndiff --git a/grep.h b/grep.h\nindex 08a0b391c5..4d474d8ec4 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -150,6 +150,7 @@ struct grep_opt {\n \tint relative;\n \tint pathname;\n \tint null_following_name;\n+\tint only_matching;\n \tint color;\n \tint max_depth;\n \tint funcname;\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex 9312c8daf5..d8c232dbf4 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -262,6 +262,21 @@ do\n \t\tfi\n \t'\n \n+\ttest_expect_success \"grep $L (with --column, --only-matching)\" '\n+\t\t{\n+\t\t\techo ${HC}file:1:5:mmap\n+\t\t\techo ${HC}file:2:5:mmap\n+\t\t\techo ${HC}file:3:5:mmap\n+\t\t\techo ${HC}file:3:13:mmap\n+\t\t\techo ${HC}file:4:5:mmap\n+\t\t\techo ${HC}file:4:13:mmap\n+\t\t\techo ${HC}file:5:5:mmap\n+\t\t\techo ${HC}file:5:13:mmap\n+\t\t} >expected &&\n+\t\tgit grep --column -n -o -e mmap $H >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n \ttest_expect_success \"grep $L (t-1)\" '\n \t\techo \"${HC}t/t:1:test\" >expected &&\n \t\tgit grep -n -e test $H >actual &&\n-- \n2.18.0\n"},{"id":"351625","messageId":"20180703143729.GB23556@sigill.intra.peff.net","threadId":"48776","inReplyTo":"cover.1530562110.git.me@ttaylorr.com","subject":"Re: [PATCH v2 0/2] teach --only-matching to 'git-grep(1)'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-07-03T14:37:29Z","receivedAt":"2018-07-03T14:37:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 02, 2018 at 03:08:54PM -0500, Taylor Blau wrote:\n\n> Attached is the second re-roll of my series to teach 'git grep\n> --only-matching'. Since last time, not much has changed. The change I\n> did include is summarized below, and an inter-diff is attached as\n> always.\n> \n>   - Initialize both match_color and line_color to NULL, thereby\n>     silencing a compiler warning where match_color was given to\n>     opt->output_color uninitialized [1].\n\nThis iteration looks fine to me. I think we'd ideally do\ns/grep/& --column/ in the commit message of the second patch, but that's\nnot worth re-rolling.\n\n-Peff\n"},{"id":"351626","messageId":"20180703143820.GC23556@sigill.intra.peff.net","threadId":"48776","inReplyTo":"20180703143729.GB23556@sigill.intra.peff.net","subject":"Re: [PATCH v2 0/2] teach --only-matching to 'git-grep(1)'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-07-03T14:38:20Z","receivedAt":"2018-07-03T14:38:25Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 03, 2018 at 10:37:29AM -0400, Jeff King wrote:\n\n> On Mon, Jul 02, 2018 at 03:08:54PM -0500, Taylor Blau wrote:\n> \n> > Attached is the second re-roll of my series to teach 'git grep\n> > --only-matching'. Since last time, not much has changed. The change I\n> > did include is summarized below, and an inter-diff is attached as\n> > always.\n> > \n> >   - Initialize both match_color and line_color to NULL, thereby\n> >     silencing a compiler warning where match_color was given to\n> >     opt->output_color uninitialized [1].\n> \n> This iteration looks fine to me. I think we'd ideally do\n> s/grep/& --column/ in the commit message of the second patch, but that's\n> not worth re-rolling.\n\nIn the example it shows, I mean (a global s/ would not be good ;) ).\n\n-Peff\n"},{"id":"351652","messageId":"xmqq1sckoxk8.fsf@gitster-ct.c.googlers.com","threadId":"48776","inReplyTo":"20180703143729.GB23556@sigill.intra.peff.net","subject":"Re: [PATCH v2 0/2] teach --only-matching to 'git-grep(1)'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-03T20:48:23Z","receivedAt":"2018-07-03T20:48:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Mon, Jul 02, 2018 at 03:08:54PM -0500, Taylor Blau wrote:\n>\n>> Attached is the second re-roll of my series to teach 'git grep\n>> --only-matching'. Since last time, not much has changed. The change I\n>> did include is summarized below, and an inter-diff is attached as\n>> always.\n>> \n>>   - Initialize both match_color and line_color to NULL, thereby\n>>     silencing a compiler warning where match_color was given to\n>>     opt->output_color uninitialized [1].\n>\n> This iteration looks fine to me. I think we'd ideally do\n> s/grep/& --column/ in the commit message of the second patch, but that's\n> not worth re-rolling.\n\nI'm OK doing this myself while queuing:\n\n- $ git grep -no -e git -- README.md | grep \":27\"\n+ $ git grep -n --only-matching --column -e git -- README.md | grep \":27\"\n\nbut the lack of changes to Documentation/git-grep.txt makes the\ntopic worth rerolling anyway (I needed to look up what \"-no\" means\nthere, and then found there was no such option in the manpage ;-).\n\n"},{"id":"351661","messageId":"cover.1530654455.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"cover.1529961706.git.me@ttaylorr.com","subject":"[PATCH v3 0/2] grep.c: teach --only-matching to 'git-grep(1)'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-03T21:51:52Z","receivedAt":"2018-07-03T21:52:01Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Hi,\n\nAttached here is a third re-roll of my series to teach 'git grep\n--only-matching'. (I didn't mention it in the thread, but I _thought_\nthat twice would be enough, so I think Peff's advice about not counting\non anything being the final re-roll of something applies to my thoughts,\ntoo ;-) ).\n\nSince last time:\n\n  - The second patch has been amended to include the full invocation of\n    'git grep' (including `--column`, `--only-matching`, and\n    `--line-number`) [1].\n\n  - The second patch has been also amended to add the new option\n    (`--only-matching`) to Documentation/git-grep.txt per [2].\n\nAn inter-diff is available below, and thanks as always for your review\n:-).\n\nThanks,\nTaylor\n\n[1]: https://public-inbox.org/git/20180703143820.GC23556@sigill.intra.peff.net/\n[2]: https://public-inbox.org/git/xmqq1sckoxk8.fsf@gitster-ct.c.googlers.com/\n\nTaylor Blau (2):\n  grep.c: extract show_line_header()\n  grep.c: teach 'git grep --only-matching'\n\n Documentation/git-grep.txt |  6 ++-\n builtin/grep.c             |  6 +++\n grep.c                     | 91 ++++++++++++++++++++++++--------------\n grep.h                     |  1 +\n t/t7810-grep.sh            | 15 +++++++\n 5 files changed, 85 insertions(+), 34 deletions(-)\n\nInter-diff (since v2):\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex 0de3493b80..be13fc3253 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -17,7 +17,7 @@ SYNOPSIS\n \t   [-l | --files-with-matches] [-L | --files-without-match]\n \t   [(-O | --open-files-in-pager) [<pager>]]\n \t   [-z | --null]\n-\t   [-c | --count] [--all-match] [-q | --quiet]\n+\t   [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]\n \t   [--max-depth <depth>]\n \t   [--color[=<when>] | --no-color]\n \t   [--break] [--heading] [-p | --show-function]\n@@ -201,6 +201,10 @@ providing this option will cause it to die.\n \tOutput \\0 instead of the character that normally follows a\n \tfile name.\n\n+-o::\n+--only-matching::\n+  Output only the matching part of the lines.\n+\n -c::\n --count::\n \tInstead of showing every matched line, show the number of\n\n--\n2.18.0\n"},{"id":"351662","messageId":"62e19e5583e30117b6754be5de5582a07c9e78dc.1530654455.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"cover.1530654455.git.me@ttaylorr.com","subject":"[PATCH v3 1/2] grep.c: extract show_line_header()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-03T21:51:56Z","receivedAt":"2018-07-03T21:52:05Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"The grep code invokes show_line() to display the contents of a matched\nor context line in its output. Part of this execution is to print a line\nheader that includes information such as the kind, the line- and\ncolumn-number and etc. of that match.\n\nTo prepare for the addition of an option to print only the matching\ncomponent(s) of a non-context line, we must prepare for the possibility\nthat a single line may contain multiple matching parts, and thus will\nneed multiple headers printed for a single line.\n\nExtracting show_line_header allows us to do just that. In the subsequent\ncommit, it will be used within the colorization loop to print out only\nthe matching parts of a line, optionally with LFs delimiting\nsub-matches.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n grep.c | 44 +++++++++++++++++++++++++-------------------\n 1 file changed, 25 insertions(+), 19 deletions(-)\n\ndiff --git a/grep.c b/grep.c\nindex 992673fe7e..4ff8a73043 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1410,26 +1410,9 @@ static int next_match(struct grep_opt *opt, char *bol, char *eol,\n \treturn hit;\n }\n \n-static void show_line(struct grep_opt *opt, char *bol, char *eol,\n-\t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n+static void show_line_header(struct grep_opt *opt, const char *name,\n+\t\t\t     unsigned lno, ssize_t cno, char sign)\n {\n-\tint rest = eol - bol;\n-\tconst char *match_color, *line_color = NULL;\n-\n-\tif (opt->file_break && opt->last_shown == 0) {\n-\t\tif (opt->show_hunk_mark)\n-\t\t\topt->output(opt, \"\\n\", 1);\n-\t} else if (opt->pre_context || opt->post_context || opt->funcbody) {\n-\t\tif (opt->last_shown == 0) {\n-\t\t\tif (opt->show_hunk_mark) {\n-\t\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n-\t\t\t\topt->output(opt, \"\\n\", 1);\n-\t\t\t}\n-\t\t} else if (lno > opt->last_shown + 1) {\n-\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n-\t\t\topt->output(opt, \"\\n\", 1);\n-\t\t}\n-\t}\n \tif (opt->heading && opt->last_shown == 0) {\n \t\toutput_color(opt, name, strlen(name), opt->color_filename);\n \t\topt->output(opt, \"\\n\", 1);\n@@ -1457,6 +1440,29 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\toutput_color(opt, buf, strlen(buf), opt->color_columnno);\n \t\toutput_sep(opt, sign);\n \t}\n+}\n+\n+static void show_line(struct grep_opt *opt, char *bol, char *eol,\n+\t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n+{\n+\tint rest = eol - bol;\n+\tconst char *match_color, *line_color = NULL;\n+\n+\tif (opt->file_break && opt->last_shown == 0) {\n+\t\tif (opt->show_hunk_mark)\n+\t\t\topt->output(opt, \"\\n\", 1);\n+\t} else if (opt->pre_context || opt->post_context || opt->funcbody) {\n+\t\tif (opt->last_shown == 0) {\n+\t\t\tif (opt->show_hunk_mark) {\n+\t\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n+\t\t\t}\n+\t\t} else if (lno > opt->last_shown + 1) {\n+\t\t\toutput_color(opt, \"--\", 2, opt->color_sep);\n+\t\t\topt->output(opt, \"\\n\", 1);\n+\t\t}\n+\t}\n+\tshow_line_header(opt, name, lno, cno, sign);\n \tif (opt->color) {\n \t\tregmatch_t match;\n \t\tenum grep_context ctx = GREP_CONTEXT_BODY;\n-- \n2.18.0\n\n"},{"id":"351663","messageId":"702e1d2a26704c7c932ee4b96f32bff4c45e485e.1530654455.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"cover.1530654455.git.me@ttaylorr.com","subject":"[PATCH v3 2/2] grep.c: teach 'git grep --only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-03T21:52:02Z","receivedAt":"2018-07-03T21:52:16Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Teach 'git grep --only-matching', a new option to only print the\nmatching part(s) of a line.\n\nFor instance, a line containing the following (taken from README.md:27):\n\n  (`man gitcvs-migration` or `git help cvs-migration` if git is\n\nIs printed as follows:\n\n  $ git grep --line-number --column --only-matching -e git -- \\\n    README.md | grep \":27\"\n  README.md:27:7:git\n  README.md:27:16:git\n  README.md:27:38:git\n\nThe patch works mostly as one would expect, with the exception of a few\nconsiderations that are worth mentioning here.\n\nLike GNU grep, this patch ignores --only-matching when --invert (-v) is\ngiven. There is a sensible answer here, but parity with the behavior of\nother tools is preferred.\n\nBecause a line might contain more than one match, there are special\nconsiderations pertaining to when to print line headers, newlines, and\nhow to increment the match column offset. The line header and newlines\nare handled as a special case within the main loop to avoid polluting\nthe surrounding code with conditionals that have large blocks.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Documentation/git-grep.txt |  6 ++++-\n builtin/grep.c             |  6 +++++\n grep.c                     | 51 ++++++++++++++++++++++++++------------\n grep.h                     |  1 +\n t/t7810-grep.sh            | 15 +++++++++++\n 5 files changed, 62 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex 0de3493b80..be13fc3253 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -17,7 +17,7 @@ SYNOPSIS\n \t   [-l | --files-with-matches] [-L | --files-without-match]\n \t   [(-O | --open-files-in-pager) [<pager>]]\n \t   [-z | --null]\n-\t   [-c | --count] [--all-match] [-q | --quiet]\n+\t   [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]\n \t   [--max-depth <depth>]\n \t   [--color[=<when>] | --no-color]\n \t   [--break] [--heading] [-p | --show-function]\n@@ -201,6 +201,10 @@ providing this option will cause it to die.\n \tOutput \\0 instead of the character that normally follows a\n \tfile name.\n \n+-o::\n+--only-matching::\n+  Output only the matching part of the lines.\n+\n -c::\n --count::\n \tInstead of showing every matched line, show the number of\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 61bcaf6e58..228b83990f 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -843,6 +843,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL_F('z', \"null\", &opt.null_following_name,\n \t\t\t   N_(\"print NUL after filenames\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_BOOL('o', \"only-matching\", &opt.only_matching,\n+\t\t\tN_(\"show only matching parts of a line\")),\n \t\tOPT_BOOL('c', \"count\", &opt.count,\n \t\t\tN_(\"show the number of matches instead of matching lines\")),\n \t\tOPT__COLOR(&opt.color, N_(\"highlight matches\")),\n@@ -962,6 +964,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (!opt.pattern_list)\n \t\tdie(_(\"no pattern given.\"));\n \n+\t/* --only-matching has no effect with --invert. */\n+\tif (opt.invert)\n+\t\topt.only_matching = 0;\n+\n \t/*\n \t * We have to find \"--\" in a separate pass, because its presence\n \t * influences how we will parse arguments that come before it.\ndiff --git a/grep.c b/grep.c\nindex 4ff8a73043..49a744f96b 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -51,6 +51,7 @@ void init_grep_defaults(void)\n \tcolor_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);\n \tcolor_set(opt->color_selected, \"\");\n \tcolor_set(opt->color_sep, GIT_COLOR_CYAN);\n+\topt->only_matching = 0;\n \topt->color = -1;\n \topt->output = std_output;\n }\n@@ -158,6 +159,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)\n \topt->pattern_tail = &opt->pattern_list;\n \topt->header_tail = &opt->header_list;\n \n+\topt->only_matching = def->only_matching;\n \topt->color = def->color;\n \topt->extended_regexp_option = def->extended_regexp_option;\n \topt->pattern_type_option = def->pattern_type_option;\n@@ -1446,7 +1448,8 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n {\n \tint rest = eol - bol;\n-\tconst char *match_color, *line_color = NULL;\n+\tconst char *match_color = NULL;\n+\tconst char *line_color = NULL;\n \n \tif (opt->file_break && opt->last_shown == 0) {\n \t\tif (opt->show_hunk_mark)\n@@ -1462,39 +1465,55 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t\topt->output(opt, \"\\n\", 1);\n \t\t}\n \t}\n-\tshow_line_header(opt, name, lno, cno, sign);\n-\tif (opt->color) {\n+\tif (!opt->only_matching) {\n+\t\t/*\n+\t\t * In case the line we're being called with contains more than\n+\t\t * one match, leave printing each header to the loop below.\n+\t\t */\n+\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t}\n+\tif (opt->color || opt->only_matching) {\n \t\tregmatch_t match;\n \t\tenum grep_context ctx = GREP_CONTEXT_BODY;\n \t\tint ch = *eol;\n \t\tint eflags = 0;\n \n-\t\tif (sign == ':')\n-\t\t\tmatch_color = opt->color_match_selected;\n-\t\telse\n-\t\t\tmatch_color = opt->color_match_context;\n-\t\tif (sign == ':')\n-\t\t\tline_color = opt->color_selected;\n-\t\telse if (sign == '-')\n-\t\t\tline_color = opt->color_context;\n-\t\telse if (sign == '=')\n-\t\t\tline_color = opt->color_function;\n+\t\tif (opt->color) {\n+\t\t\tif (sign == ':')\n+\t\t\t\tmatch_color = opt->color_match_selected;\n+\t\t\telse\n+\t\t\t\tmatch_color = opt->color_match_context;\n+\t\t\tif (sign == ':')\n+\t\t\t\tline_color = opt->color_selected;\n+\t\t\telse if (sign == '-')\n+\t\t\t\tline_color = opt->color_context;\n+\t\t\telse if (sign == '=')\n+\t\t\t\tline_color = opt->color_function;\n+\t\t}\n \t\t*eol = '\\0';\n \t\twhile (next_match(opt, bol, eol, ctx, &match, eflags)) {\n \t\t\tif (match.rm_so == match.rm_eo)\n \t\t\t\tbreak;\n \n-\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t\t\telse\n+\t\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n \t\t\toutput_color(opt, bol + match.rm_so,\n \t\t\t\t     match.rm_eo - match.rm_so, match_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n \t\t\tbol += match.rm_eo;\n+\t\t\tcno += match.rm_eo;\n \t\t\trest -= match.rm_eo;\n \t\t\teflags = REG_NOTBOL;\n \t\t}\n \t\t*eol = ch;\n \t}\n-\toutput_color(opt, bol, rest, line_color);\n-\topt->output(opt, \"\\n\", 1);\n+\tif (!opt->only_matching) {\n+\t\toutput_color(opt, bol, rest, line_color);\n+\t\topt->output(opt, \"\\n\", 1);\n+\t}\n }\n \n #ifndef NO_PTHREADS\ndiff --git a/grep.h b/grep.h\nindex 08a0b391c5..4d474d8ec4 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -150,6 +150,7 @@ struct grep_opt {\n \tint relative;\n \tint pathname;\n \tint null_following_name;\n+\tint only_matching;\n \tint color;\n \tint max_depth;\n \tint funcname;\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex 9312c8daf5..d8c232dbf4 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -262,6 +262,21 @@ do\n \t\tfi\n \t'\n \n+\ttest_expect_success \"grep $L (with --column, --only-matching)\" '\n+\t\t{\n+\t\t\techo ${HC}file:1:5:mmap\n+\t\t\techo ${HC}file:2:5:mmap\n+\t\t\techo ${HC}file:3:5:mmap\n+\t\t\techo ${HC}file:3:13:mmap\n+\t\t\techo ${HC}file:4:5:mmap\n+\t\t\techo ${HC}file:4:13:mmap\n+\t\t\techo ${HC}file:5:5:mmap\n+\t\t\techo ${HC}file:5:13:mmap\n+\t\t} >expected &&\n+\t\tgit grep --column -n -o -e mmap $H >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n \ttest_expect_success \"grep $L (t-1)\" '\n \t\techo \"${HC}t/t:1:test\" >expected &&\n \t\tgit grep -n -e test $H >actual &&\n-- \n2.18.0\n"},{"id":"351694","messageId":"bf53ea90c9114d0c4e3cc2b1df05464bfeb6e84f.1530716005.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"702e1d2a26704c7c932ee4b96f32bff4c45e485e.1530654455.git.me@ttaylorr.com","subject":"[PATCH v2] grep.c: teach 'git grep --only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-04T14:53:30Z","receivedAt":"2018-07-04T14:53:37Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Teach 'git grep --only-matching', a new option to only print the\nmatching part(s) of a line.\n\nFor instance, a line containing the following (taken from README.md:27):\n\n  (`man gitcvs-migration` or `git help cvs-migration` if git is\n\nIs printed as follows:\n\n  $ git grep --line-number --column --only-matching -e git -- \\\n    README.md | grep \":27\"\n  README.md:27:7:git\n  README.md:27:16:git\n  README.md:27:38:git\n\nThe patch works mostly as one would expect, with the exception of a few\nconsiderations that are worth mentioning here.\n\nLike GNU grep, this patch ignores --only-matching when --invert (-v) is\ngiven. There is a sensible answer here, but parity with the behavior of\nother tools is preferred.\n\nBecause a line might contain more than one match, there are special\nconsiderations pertaining to when to print line headers, newlines, and\nhow to increment the match column offset. The line header and newlines\nare handled as a special case within the main loop to avoid polluting\nthe surrounding code with conditionals that have large blocks.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Documentation/git-grep.txt |  6 ++++-\n builtin/grep.c             |  6 +++++\n grep.c                     | 51 ++++++++++++++++++++++++++------------\n grep.h                     |  1 +\n t/t7810-grep.sh            | 15 +++++++++++\n 5 files changed, 62 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex 0de3493b80..078b4e3730 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -17,7 +17,7 @@ SYNOPSIS\n \t   [-l | --files-with-matches] [-L | --files-without-match]\n \t   [(-O | --open-files-in-pager) [<pager>]]\n \t   [-z | --null]\n-\t   [-c | --count] [--all-match] [-q | --quiet]\n+\t   [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]\n \t   [--max-depth <depth>]\n \t   [--color[=<when>] | --no-color]\n \t   [--break] [--heading] [-p | --show-function]\n@@ -201,6 +201,10 @@ providing this option will cause it to die.\n \tOutput \\0 instead of the character that normally follows a\n \tfile name.\n \n+-o::\n+--only-matching::\n+\tOutput only the matching part of the lines.\n+\n -c::\n --count::\n \tInstead of showing every matched line, show the number of\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 61bcaf6e58..228b83990f 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -843,6 +843,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL_F('z', \"null\", &opt.null_following_name,\n \t\t\t   N_(\"print NUL after filenames\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_BOOL('o', \"only-matching\", &opt.only_matching,\n+\t\t\tN_(\"show only matching parts of a line\")),\n \t\tOPT_BOOL('c', \"count\", &opt.count,\n \t\t\tN_(\"show the number of matches instead of matching lines\")),\n \t\tOPT__COLOR(&opt.color, N_(\"highlight matches\")),\n@@ -962,6 +964,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (!opt.pattern_list)\n \t\tdie(_(\"no pattern given.\"));\n \n+\t/* --only-matching has no effect with --invert. */\n+\tif (opt.invert)\n+\t\topt.only_matching = 0;\n+\n \t/*\n \t * We have to find \"--\" in a separate pass, because its presence\n \t * influences how we will parse arguments that come before it.\ndiff --git a/grep.c b/grep.c\nindex 4ff8a73043..49a744f96b 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -51,6 +51,7 @@ void init_grep_defaults(void)\n \tcolor_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);\n \tcolor_set(opt->color_selected, \"\");\n \tcolor_set(opt->color_sep, GIT_COLOR_CYAN);\n+\topt->only_matching = 0;\n \topt->color = -1;\n \topt->output = std_output;\n }\n@@ -158,6 +159,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)\n \topt->pattern_tail = &opt->pattern_list;\n \topt->header_tail = &opt->header_list;\n \n+\topt->only_matching = def->only_matching;\n \topt->color = def->color;\n \topt->extended_regexp_option = def->extended_regexp_option;\n \topt->pattern_type_option = def->pattern_type_option;\n@@ -1446,7 +1448,8 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n {\n \tint rest = eol - bol;\n-\tconst char *match_color, *line_color = NULL;\n+\tconst char *match_color = NULL;\n+\tconst char *line_color = NULL;\n \n \tif (opt->file_break && opt->last_shown == 0) {\n \t\tif (opt->show_hunk_mark)\n@@ -1462,39 +1465,55 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t\topt->output(opt, \"\\n\", 1);\n \t\t}\n \t}\n-\tshow_line_header(opt, name, lno, cno, sign);\n-\tif (opt->color) {\n+\tif (!opt->only_matching) {\n+\t\t/*\n+\t\t * In case the line we're being called with contains more than\n+\t\t * one match, leave printing each header to the loop below.\n+\t\t */\n+\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t}\n+\tif (opt->color || opt->only_matching) {\n \t\tregmatch_t match;\n \t\tenum grep_context ctx = GREP_CONTEXT_BODY;\n \t\tint ch = *eol;\n \t\tint eflags = 0;\n \n-\t\tif (sign == ':')\n-\t\t\tmatch_color = opt->color_match_selected;\n-\t\telse\n-\t\t\tmatch_color = opt->color_match_context;\n-\t\tif (sign == ':')\n-\t\t\tline_color = opt->color_selected;\n-\t\telse if (sign == '-')\n-\t\t\tline_color = opt->color_context;\n-\t\telse if (sign == '=')\n-\t\t\tline_color = opt->color_function;\n+\t\tif (opt->color) {\n+\t\t\tif (sign == ':')\n+\t\t\t\tmatch_color = opt->color_match_selected;\n+\t\t\telse\n+\t\t\t\tmatch_color = opt->color_match_context;\n+\t\t\tif (sign == ':')\n+\t\t\t\tline_color = opt->color_selected;\n+\t\t\telse if (sign == '-')\n+\t\t\t\tline_color = opt->color_context;\n+\t\t\telse if (sign == '=')\n+\t\t\t\tline_color = opt->color_function;\n+\t\t}\n \t\t*eol = '\\0';\n \t\twhile (next_match(opt, bol, eol, ctx, &match, eflags)) {\n \t\t\tif (match.rm_so == match.rm_eo)\n \t\t\t\tbreak;\n \n-\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t\t\telse\n+\t\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n \t\t\toutput_color(opt, bol + match.rm_so,\n \t\t\t\t     match.rm_eo - match.rm_so, match_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n \t\t\tbol += match.rm_eo;\n+\t\t\tcno += match.rm_eo;\n \t\t\trest -= match.rm_eo;\n \t\t\teflags = REG_NOTBOL;\n \t\t}\n \t\t*eol = ch;\n \t}\n-\toutput_color(opt, bol, rest, line_color);\n-\topt->output(opt, \"\\n\", 1);\n+\tif (!opt->only_matching) {\n+\t\toutput_color(opt, bol, rest, line_color);\n+\t\topt->output(opt, \"\\n\", 1);\n+\t}\n }\n \n #ifndef NO_PTHREADS\ndiff --git a/grep.h b/grep.h\nindex 08a0b391c5..4d474d8ec4 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -150,6 +150,7 @@ struct grep_opt {\n \tint relative;\n \tint pathname;\n \tint null_following_name;\n+\tint only_matching;\n \tint color;\n \tint max_depth;\n \tint funcname;\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex 9312c8daf5..d8c232dbf4 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -262,6 +262,21 @@ do\n \t\tfi\n \t'\n \n+\ttest_expect_success \"grep $L (with --column, --only-matching)\" '\n+\t\t{\n+\t\t\techo ${HC}file:1:5:mmap\n+\t\t\techo ${HC}file:2:5:mmap\n+\t\t\techo ${HC}file:3:5:mmap\n+\t\t\techo ${HC}file:3:13:mmap\n+\t\t\techo ${HC}file:4:5:mmap\n+\t\t\techo ${HC}file:4:13:mmap\n+\t\t\techo ${HC}file:5:5:mmap\n+\t\t\techo ${HC}file:5:13:mmap\n+\t\t} >expected &&\n+\t\tgit grep --column -n -o -e mmap $H >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n \ttest_expect_success \"grep $L (t-1)\" '\n \t\techo \"${HC}t/t:1:test\" >expected &&\n \t\tgit grep -n -e test $H >actual &&\n-- \n2.18.0\n"},{"id":"351695","messageId":"20180704145540.GA51949@syl.attlocal.net","threadId":"48776","inReplyTo":"bf53ea90c9114d0c4e3cc2b1df05464bfeb6e84f.1530716005.git.me@ttaylorr.com","subject":"Re: [PATCH v2] grep.c: teach 'git grep --only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-04T14:55:40Z","receivedAt":"2018-07-04T14:55:47Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Mon, Jun 25, 2018 at 02:59:07PM -0500, Taylor Blau wrote:\n> Teach 'git grep --only-matching', a new option to only print the\n> matching part(s) of a line.\n>\n> [ ... ]\n>\n> diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\n> index 0de3493b80..078b4e3730 100644\n> --- a/Documentation/git-grep.txt\n> +++ b/Documentation/git-grep.txt\n> @@ -17,7 +17,7 @@ SYNOPSIS\n>  \t   [-l | --files-with-matches] [-L | --files-without-match]\n>  \t   [(-O | --open-files-in-pager) [<pager>]]\n>  \t   [-z | --null]\n> -\t   [-c | --count] [--all-match] [-q | --quiet]\n> +\t   [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]\n>  \t   [--max-depth <depth>]\n>  \t   [--color[=<when>] | --no-color]\n>  \t   [--break] [--heading] [-p | --show-function]\n> @@ -201,6 +201,10 @@ providing this option will cause it to die.\n>  \tOutput \\0 instead of the character that normally follows a\n>  \tfile name.\n>\n> +-o::\n> +--only-matching::\n> +\tOutput only the matching part of the lines.\n\nJunio,\n\nMy apologies that I sent the previous patch with incorrect indentation on this\nline. Would you mind queueing this one instead?\n\n\nThanks,\nTaylor\n"},{"id":"351705","messageId":"20180705142110.GA10192@sigill.intra.peff.net","threadId":"48776","inReplyTo":"cover.1530654455.git.me@ttaylorr.com","subject":"Re: [PATCH v3 0/2] grep.c: teach --only-matching to 'git-grep(1)'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-07-05T14:21:11Z","receivedAt":"2018-07-05T14:21:16Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 03, 2018 at 04:51:52PM -0500, Taylor Blau wrote:\n\n> diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\n> index 0de3493b80..be13fc3253 100644\n> --- a/Documentation/git-grep.txt\n> +++ b/Documentation/git-grep.txt\n> @@ -17,7 +17,7 @@ SYNOPSIS\n>  \t   [-l | --files-with-matches] [-L | --files-without-match]\n>  \t   [(-O | --open-files-in-pager) [<pager>]]\n>  \t   [-z | --null]\n> -\t   [-c | --count] [--all-match] [-q | --quiet]\n> +\t   [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]\n>  \t   [--max-depth <depth>]\n>  \t   [--color[=<when>] | --no-color]\n>  \t   [--break] [--heading] [-p | --show-function]\n> @@ -201,6 +201,10 @@ providing this option will cause it to die.\n>  \tOutput \\0 instead of the character that normally follows a\n>  \tfile name.\n> \n> +-o::\n> +--only-matching::\n> +  Output only the matching part of the lines.\n> +\n\nPutting myself into the shoes of a naive reader, I have to wonder what\nhappens when there are multiple matching parts on the same line. I know\nthe answer from your commit message, but maybe that should be covered\nhere? Maybe:\n\n  Output only the matching part of the lines. If there are multiple\n  matching parts, each is output on a separate line.\n\n-Peff\n"},{"id":"351706","messageId":"20180705143401.GA87330@syl.attlocal.net","threadId":"48776","inReplyTo":"20180705142110.GA10192@sigill.intra.peff.net","subject":"Re: [PATCH v3 0/2] grep.c: teach --only-matching to 'git-grep(1)'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-05T14:34:01Z","receivedAt":"2018-07-05T14:34:08Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Jul 05, 2018 at 10:21:11AM -0400, Jeff King wrote:\n> On Tue, Jul 03, 2018 at 04:51:52PM -0500, Taylor Blau wrote:\n>\n> > diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\n> > index 0de3493b80..be13fc3253 100644\n> > --- a/Documentation/git-grep.txt\n> > +++ b/Documentation/git-grep.txt\n> > @@ -17,7 +17,7 @@ SYNOPSIS\n> >  \t   [-l | --files-with-matches] [-L | --files-without-match]\n> >  \t   [(-O | --open-files-in-pager) [<pager>]]\n> >  \t   [-z | --null]\n> > -\t   [-c | --count] [--all-match] [-q | --quiet]\n> > +\t   [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]\n> >  \t   [--max-depth <depth>]\n> >  \t   [--color[=<when>] | --no-color]\n> >  \t   [--break] [--heading] [-p | --show-function]\n> > @@ -201,6 +201,10 @@ providing this option will cause it to die.\n> >  \tOutput \\0 instead of the character that normally follows a\n> >  \tfile name.\n> >\n> > +-o::\n> > +--only-matching::\n> > +  Output only the matching part of the lines.\n> > +\n>\n> Putting myself into the shoes of a naive reader, I have to wonder what\n> happens when there are multiple matching parts on the same line. I know\n> the answer from your commit message, but maybe that should be covered\n> here? Maybe:\n>\n>   Output only the matching part of the lines. If there are multiple\n>   matching parts, each is output on a separate line.\n\nI think that this might be clear enough on its own, especially since\nthis is the same as BSD grep on my machine. I think that part_s_ of a\nline indicates that behavior, but perhaps not. On GNU grep, this is:\n\n  Print only the matched (non-empty) parts of a matching line, with each\n  such part on a separate output line.\n\nI'm happy to pick either and re-send this patch (2/2) again, if it\nwouldn't be too much to juggle. Otherwise, I can re-roll to v4.\n\n\nThanks,\nTaylor\n"},{"id":"351788","messageId":"xmqqpo00kz4r.fsf@gitster-ct.c.googlers.com","threadId":"48776","inReplyTo":"20180704145540.GA51949@syl.attlocal.net","subject":"Re: [PATCH v2] grep.c: teach 'git grep --only-matching'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-06T18:17:08Z","receivedAt":"2018-07-06T18:17:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n> On Mon, Jun 25, 2018 at 02:59:07PM -0500, Taylor Blau wrote:\n>> Teach 'git grep --only-matching', a new option to only print the\n>> matching part(s) of a line.\n>>\n>> [ ... ]\n>>\n>> diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\n>> index 0de3493b80..078b4e3730 100644\n>> --- a/Documentation/git-grep.txt\n>> +++ b/Documentation/git-grep.txt\n>> @@ -17,7 +17,7 @@ SYNOPSIS\n>>  \t   [-l | --files-with-matches] [-L | --files-without-match]\n>>  \t   [(-O | --open-files-in-pager) [<pager>]]\n>>  \t   [-z | --null]\n>> -\t   [-c | --count] [--all-match] [-q | --quiet]\n>> +\t   [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]\n>>  \t   [--max-depth <depth>]\n>>  \t   [--color[=<when>] | --no-color]\n>>  \t   [--break] [--heading] [-p | --show-function]\n>> @@ -201,6 +201,10 @@ providing this option will cause it to die.\n>>  \tOutput \\0 instead of the character that normally follows a\n>>  \tfile name.\n>>\n>> +-o::\n>> +--only-matching::\n>> +\tOutput only the matching part of the lines.\n>\n> Junio,\n>\n> My apologies that I sent the previous patch with incorrect indentation on this\n> line. Would you mind queueing this one instead?\n\nSurely, and thanks for telling me what difference to look for\nbetween the versions.  Will replace with the one with indent before\n\"Output only ...\".\n\n"},{"id":"351789","messageId":"xmqqlgaokyy5.fsf@gitster-ct.c.googlers.com","threadId":"48776","inReplyTo":"20180705143401.GA87330@syl.attlocal.net","subject":"Re: [PATCH v3 0/2] grep.c: teach --only-matching to 'git-grep(1)'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-06T18:21:06Z","receivedAt":"2018-07-06T18:21:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n> I think that this might be clear enough on its own, especially since\n> this is the same as BSD grep on my machine. I think that part_s_ of a\n> line indicates that behavior, but perhaps not. On GNU grep, this is:\n>\n>   Print only the matched (non-empty) parts of a matching line, with each\n>   such part on a separate output line.\n\nInteresting.  I wonder what \"git grep -o '^'\" would do ;-)\n\n> I'm happy to pick either and re-send this patch (2/2) again, if it\n> wouldn't be too much to juggle. Otherwise, I can re-roll to v4.\n\nPlease do not re-send a different version of a patch with the same\nv$n value.  Either re-send, otherwise re-roll, will give us v4, not\nv3.\n\nIn any case, I find that the GNU phrasing is the most clear among\nthe ones I've seen in this thread so far.\n\n"},{"id":"351804","messageId":"20180706201522.GA657@syl.attlocal.net","threadId":"48776","inReplyTo":"xmqqlgaokyy5.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 0/2] grep.c: teach --only-matching to 'git-grep(1)'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-06T20:15:22Z","receivedAt":"2018-07-06T20:15:28Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Jul 06, 2018 at 11:21:06AM -0700, Junio C Hamano wrote:\n> Taylor Blau <me@ttaylorr.com> writes:\n>\n> > I think that this might be clear enough on its own, especially since\n> > this is the same as BSD grep on my machine. I think that part_s_ of a\n> > line indicates that behavior, but perhaps not. On GNU grep, this is:\n> >\n> >   Print only the matched (non-empty) parts of a matching line, with each\n> >   such part on a separate output line.\n>\n> Interesting.  I wonder what \"git grep -o '^'\" would do ;-)\n\nThat invocation prints nothing, but on BSD grep it prints quite a few\nblank lines :-).\n\nI'm hesitant on sending a patch per the hunk of your reply below because\nof this. Should we mirror BSD grep's behavior exactly here? I suppose\nthat we could somehow, but it seems like we might be doing too much to\nsupport what appears to me to be an odd use-case.\n\n> > I'm happy to pick either and re-send this patch (2/2) again, if it\n> > wouldn't be too much to juggle. Otherwise, I can re-roll to v4.\n>\n> Please do not re-send a different version of a patch with the same\n> v$n value.  Either re-send, otherwise re-roll, will give us v4, not\n> v3.\n>\n> In any case, I find that the GNU phrasing is the most clear among\n> the ones I've seen in this thread so far.\n\nOK. I'm happy to re-send that patch with the GNU phrasing depending on\nwhat others think (and the above). I'll let this cook and collect some\nthoughts over the weekend.\n\n\nThanks,\nTaylor\n"},{"id":"351809","messageId":"20180706203339.GB4648@sigill.intra.peff.net","threadId":"48776","inReplyTo":"20180706201522.GA657@syl.attlocal.net","subject":"Re: [PATCH v3 0/2] grep.c: teach --only-matching to 'git-grep(1)'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-07-06T20:33:39Z","receivedAt":"2018-07-06T20:33:44Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Jul 06, 2018 at 03:15:22PM -0500, Taylor Blau wrote:\n\n> On Fri, Jul 06, 2018 at 11:21:06AM -0700, Junio C Hamano wrote:\n> > Taylor Blau <me@ttaylorr.com> writes:\n> >\n> > > I think that this might be clear enough on its own, especially since\n> > > this is the same as BSD grep on my machine. I think that part_s_ of a\n> > > line indicates that behavior, but perhaps not. On GNU grep, this is:\n> > >\n> > >   Print only the matched (non-empty) parts of a matching line, with each\n> > >   such part on a separate output line.\n> >\n> > Interesting.  I wonder what \"git grep -o '^'\" would do ;-)\n> \n> That invocation prints nothing, but on BSD grep it prints quite a few\n> blank lines :-).\n> \n> I'm hesitant on sending a patch per the hunk of your reply below because\n> of this. Should we mirror BSD grep's behavior exactly here? I suppose\n> that we could somehow, but it seems like we might be doing too much to\n> support what appears to me to be an odd use-case.\n\nIMHO the GNU behavior (omitting non-empty matches) makes more sense. And\nit's also what your patch already does. ;)\n\nAlthough amusingly \"git grep -o ^\" will still print a ton of \"Binary\nfile ... matches\". That _also_ matches what GNU grep does. I'm not sure\nif there's a saner behavior (it really has nothing to do with the funny\nempty match; any binary file with -o cannot show the normal text line).\n\n> > In any case, I find that the GNU phrasing is the most clear among\n> > the ones I've seen in this thread so far.\n> \n> OK. I'm happy to re-send that patch with the GNU phrasing depending on\n> what others think (and the above). I'll let this cook and collect some\n> thoughts over the weekend.\n\nFWIW, I like the GNU phrasing. I thought the \"non-empty\" part was not\nall that interesting, but after hearing that BSD behaves differently, it\nis probably worth mentioning.\n\nI think the actual behavior of your patch matches GNU grep, and does not\nneed changing.\n\n-Peff\n"},{"id":"351824","messageId":"xmqqbmbkhwdr.fsf@gitster-ct.c.googlers.com","threadId":"48776","inReplyTo":"20180706203339.GB4648@sigill.intra.peff.net","subject":"Re: [PATCH v3 0/2] grep.c: teach --only-matching to 'git-grep(1)'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-06T21:44:48Z","receivedAt":"2018-07-06T21:44:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> FWIW, I like the GNU phrasing. I thought the \"non-empty\" part was not\n> all that interesting, but after hearing that BSD behaves differently, it\n> is probably worth mentioning.\n>\n> I think the actual behavior of your patch matches GNU grep, and does not\n> need changing.\n\nGreat ;-)\n"},{"id":"352012","messageId":"17fb488dad653d6378af122c91805294e1a1e93a.1531168412.git.me@ttaylorr.com","threadId":"48776","inReplyTo":"20180705143401.GA87330@syl.attlocal.net","subject":"[PATCH v4] grep.c: teach 'git grep --only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-09T20:33:47Z","receivedAt":"2018-07-09T20:33:56Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Teach 'git grep --only-matching', a new option to only print the\nmatching part(s) of a line.\n\nFor instance, a line containing the following (taken from README.md:27):\n\n  (`man gitcvs-migration` or `git help cvs-migration` if git is\n\nIs printed as follows:\n\n  $ git grep --line-number --column --only-matching -e git -- \\\n    README.md | grep \":27\"\n  README.md:27:7:git\n  README.md:27:16:git\n  README.md:27:38:git\n\nThe patch works mostly as one would expect, with the exception of a few\nconsiderations that are worth mentioning here.\n\nLike GNU grep, this patch ignores --only-matching when --invert (-v) is\ngiven. There is a sensible answer here, but parity with the behavior of\nother tools is preferred.\n\nBecause a line might contain more than one match, there are special\nconsiderations pertaining to when to print line headers, newlines, and\nhow to increment the match column offset. The line header and newlines\nare handled as a special case within the main loop to avoid polluting\nthe surrounding code with conditionals that have large blocks.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Documentation/git-grep.txt |  7 +++++-\n builtin/grep.c             |  6 +++++\n grep.c                     | 51 ++++++++++++++++++++++++++------------\n grep.h                     |  1 +\n t/t7810-grep.sh            | 15 +++++++++++\n 5 files changed, 63 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex 0de3493b80..a3049af1a3 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -17,7 +17,7 @@ SYNOPSIS\n \t   [-l | --files-with-matches] [-L | --files-without-match]\n \t   [(-O | --open-files-in-pager) [<pager>]]\n \t   [-z | --null]\n-\t   [-c | --count] [--all-match] [-q | --quiet]\n+\t   [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]\n \t   [--max-depth <depth>]\n \t   [--color[=<when>] | --no-color]\n \t   [--break] [--heading] [-p | --show-function]\n@@ -201,6 +201,11 @@ providing this option will cause it to die.\n \tOutput \\0 instead of the character that normally follows a\n \tfile name.\n \n+-o::\n+--only-matching::\n+\tPrint only the matched (non-empty) parts of a matching line, with each such\n+\tpart on a separate output line.\n+\n -c::\n --count::\n \tInstead of showing every matched line, show the number of\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 61bcaf6e58..228b83990f 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -843,6 +843,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL_F('z', \"null\", &opt.null_following_name,\n \t\t\t   N_(\"print NUL after filenames\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_BOOL('o', \"only-matching\", &opt.only_matching,\n+\t\t\tN_(\"show only matching parts of a line\")),\n \t\tOPT_BOOL('c', \"count\", &opt.count,\n \t\t\tN_(\"show the number of matches instead of matching lines\")),\n \t\tOPT__COLOR(&opt.color, N_(\"highlight matches\")),\n@@ -962,6 +964,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (!opt.pattern_list)\n \t\tdie(_(\"no pattern given.\"));\n \n+\t/* --only-matching has no effect with --invert. */\n+\tif (opt.invert)\n+\t\topt.only_matching = 0;\n+\n \t/*\n \t * We have to find \"--\" in a separate pass, because its presence\n \t * influences how we will parse arguments that come before it.\ndiff --git a/grep.c b/grep.c\nindex 4ff8a73043..49a744f96b 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -51,6 +51,7 @@ void init_grep_defaults(void)\n \tcolor_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);\n \tcolor_set(opt->color_selected, \"\");\n \tcolor_set(opt->color_sep, GIT_COLOR_CYAN);\n+\topt->only_matching = 0;\n \topt->color = -1;\n \topt->output = std_output;\n }\n@@ -158,6 +159,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)\n \topt->pattern_tail = &opt->pattern_list;\n \topt->header_tail = &opt->header_list;\n \n+\topt->only_matching = def->only_matching;\n \topt->color = def->color;\n \topt->extended_regexp_option = def->extended_regexp_option;\n \topt->pattern_type_option = def->pattern_type_option;\n@@ -1446,7 +1448,8 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t      const char *name, unsigned lno, ssize_t cno, char sign)\n {\n \tint rest = eol - bol;\n-\tconst char *match_color, *line_color = NULL;\n+\tconst char *match_color = NULL;\n+\tconst char *line_color = NULL;\n \n \tif (opt->file_break && opt->last_shown == 0) {\n \t\tif (opt->show_hunk_mark)\n@@ -1462,39 +1465,55 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t\topt->output(opt, \"\\n\", 1);\n \t\t}\n \t}\n-\tshow_line_header(opt, name, lno, cno, sign);\n-\tif (opt->color) {\n+\tif (!opt->only_matching) {\n+\t\t/*\n+\t\t * In case the line we're being called with contains more than\n+\t\t * one match, leave printing each header to the loop below.\n+\t\t */\n+\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t}\n+\tif (opt->color || opt->only_matching) {\n \t\tregmatch_t match;\n \t\tenum grep_context ctx = GREP_CONTEXT_BODY;\n \t\tint ch = *eol;\n \t\tint eflags = 0;\n \n-\t\tif (sign == ':')\n-\t\t\tmatch_color = opt->color_match_selected;\n-\t\telse\n-\t\t\tmatch_color = opt->color_match_context;\n-\t\tif (sign == ':')\n-\t\t\tline_color = opt->color_selected;\n-\t\telse if (sign == '-')\n-\t\t\tline_color = opt->color_context;\n-\t\telse if (sign == '=')\n-\t\t\tline_color = opt->color_function;\n+\t\tif (opt->color) {\n+\t\t\tif (sign == ':')\n+\t\t\t\tmatch_color = opt->color_match_selected;\n+\t\t\telse\n+\t\t\t\tmatch_color = opt->color_match_context;\n+\t\t\tif (sign == ':')\n+\t\t\t\tline_color = opt->color_selected;\n+\t\t\telse if (sign == '-')\n+\t\t\t\tline_color = opt->color_context;\n+\t\t\telse if (sign == '=')\n+\t\t\t\tline_color = opt->color_function;\n+\t\t}\n \t\t*eol = '\\0';\n \t\twhile (next_match(opt, bol, eol, ctx, &match, eflags)) {\n \t\t\tif (match.rm_so == match.rm_eo)\n \t\t\t\tbreak;\n \n-\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\tshow_line_header(opt, name, lno, cno, sign);\n+\t\t\telse\n+\t\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n \t\t\toutput_color(opt, bol + match.rm_so,\n \t\t\t\t     match.rm_eo - match.rm_so, match_color);\n+\t\t\tif (opt->only_matching)\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n \t\t\tbol += match.rm_eo;\n+\t\t\tcno += match.rm_eo;\n \t\t\trest -= match.rm_eo;\n \t\t\teflags = REG_NOTBOL;\n \t\t}\n \t\t*eol = ch;\n \t}\n-\toutput_color(opt, bol, rest, line_color);\n-\topt->output(opt, \"\\n\", 1);\n+\tif (!opt->only_matching) {\n+\t\toutput_color(opt, bol, rest, line_color);\n+\t\topt->output(opt, \"\\n\", 1);\n+\t}\n }\n \n #ifndef NO_PTHREADS\ndiff --git a/grep.h b/grep.h\nindex 08a0b391c5..4d474d8ec4 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -150,6 +150,7 @@ struct grep_opt {\n \tint relative;\n \tint pathname;\n \tint null_following_name;\n+\tint only_matching;\n \tint color;\n \tint max_depth;\n \tint funcname;\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex 9312c8daf5..d8c232dbf4 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -262,6 +262,21 @@ do\n \t\tfi\n \t'\n \n+\ttest_expect_success \"grep $L (with --column, --only-matching)\" '\n+\t\t{\n+\t\t\techo ${HC}file:1:5:mmap\n+\t\t\techo ${HC}file:2:5:mmap\n+\t\t\techo ${HC}file:3:5:mmap\n+\t\t\techo ${HC}file:3:13:mmap\n+\t\t\techo ${HC}file:4:5:mmap\n+\t\t\techo ${HC}file:4:13:mmap\n+\t\t\techo ${HC}file:5:5:mmap\n+\t\t\techo ${HC}file:5:13:mmap\n+\t\t} >expected &&\n+\t\tgit grep --column -n -o -e mmap $H >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n \ttest_expect_success \"grep $L (t-1)\" '\n \t\techo \"${HC}t/t:1:test\" >expected &&\n \t\tgit grep -n -e test $H >actual &&\n-- \n2.18.0\n"},{"id":"352014","messageId":"20180709203614.GA83328@syl.attlocal.net","threadId":"48776","inReplyTo":"17fb488dad653d6378af122c91805294e1a1e93a.1531168412.git.me@ttaylorr.com","subject":"Re: [PATCH v4] grep.c: teach 'git grep --only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-07-09T20:36:14Z","receivedAt":"2018-07-09T20:36:21Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Mon, Jul 09, 2018 at 03:33:47PM -0500, Taylor Blau wrote:\n> [ ... ]\n> ---\n>  Documentation/git-grep.txt |  7 +++++-\n>  builtin/grep.c             |  6 +++++\n>  grep.c                     | 51 ++++++++++++++++++++++++++------------\n>  grep.h                     |  1 +\n>  t/t7810-grep.sh            | 15 +++++++++++\n>  5 files changed, 63 insertions(+), 17 deletions(-)\n>\n> diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\n> index 0de3493b80..a3049af1a3 100644\n> --- a/Documentation/git-grep.txt\n> +++ b/Documentation/git-grep.txt\n> @@ -17,7 +17,7 @@ SYNOPSIS\n>  \t   [-l | --files-with-matches] [-L | --files-without-match]\n>  \t   [(-O | --open-files-in-pager) [<pager>]]\n>  \t   [-z | --null]\n> -\t   [-c | --count] [--all-match] [-q | --quiet]\n> +\t   [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]\n>  \t   [--max-depth <depth>]\n>  \t   [--color[=<when>] | --no-color]\n>  \t   [--break] [--heading] [-p | --show-function]\n> @@ -201,6 +201,11 @@ providing this option will cause it to die.\n>  \tOutput \\0 instead of the character that normally follows a\n>  \tfile name.\n>\n> +-o::\n> +--only-matching::\n> +\tPrint only the matched (non-empty) parts of a matching line, with each such\n> +\tpart on a separate output line.\n\nOK, it seems as if the consensus is (1) take the description as-is from\nGNU grep, and (2) don't change the existing behavior of \"git grep -o\n'^'\".\n\nThis patch does both of those things, and can be queued as 2/2 in this\nseries.\n\nThanks, everybody :-).\n\n> --\n> 2.18.0\n\nThanks,\nTaylor\n"}]}