{"thread":{"id":"48412","subject":"[PATCH 0/2] builtin/grep.c: teach '-o', '--only-matching'","startedAt":"2018-05-05T04:03:16Z","lastAt":"2018-05-12T03:21:18Z","messageCount":14,"participants":["Taylor Blau","Ævar Arnfjörð Bjarmason","Eric Sunshine","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"346704","messageId":"cover.1525492696.git.me@ttaylorr.com","threadId":"48412","inReplyTo":null,"subject":"[PATCH 0/2] builtin/grep.c: teach '-o', '--only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-05-05T04:03:08Z","receivedAt":"2018-05-05T04:03:16Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Hi,\n\nAttached is a series to teach 'git-grep(1)' how to respond to\n'--only-matching' (a-la GNU grep(1)'s --only-matching, including an '-o'\nsynonym) to only print the matching component(s) of a line. It is based\non v4 of tb/grep-colno, which was sent in [1].\n\nThis was suggested to me by Ævar in [2].\n\nThis change was fairly straightforward, as Ævar suggests in [3], with\nthe only complication being that we must print a line header multiple\ntimes when there are >1 matches per-line. This requirement pushes the\nimplementation towards the extraction of a show_line_header() function,\nand some minor changes in show_line() to reflect.\n\nThank you in advance for your review.\n\n\nThanks,\nTaylor\n\n[1]: https://public-inbox.org/git/cover.1525488108.git.me@ttaylorr.com\n[2]: https://public-inbox.org/git/874lk2e4he.fsf@evledraar.gmail.com\n[3]: https://public-inbox.org/git/87in9ucsbb.fsf@evledraar.gmail.com\n\nTaylor Blau (2):\n  grep.c: extract show_line_header()\n  builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'\n\n Documentation/git-grep.txt |  6 +++-\n builtin/grep.c             |  1 +\n grep.c                     | 67 +++++++++++++++++++++++++-------------\n grep.h                     |  1 +\n t/t7810-grep.sh            | 33 +++++++++++++++++++\n 5 files changed, 85 insertions(+), 23 deletions(-)\n\n--\n2.17.0\n"},{"id":"346705","messageId":"9dc758e3517681691e0809d9e9bcca03fbdad1cc.1525492696.git.me@ttaylorr.com","threadId":"48412","inReplyTo":"cover.1525492696.git.me@ttaylorr.com","subject":"[PATCH 1/2] grep.c: extract show_line_header()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-05-05T04:03:11Z","receivedAt":"2018-05-05T04:03:19Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Teach 'git-grep(1)' how to print a line header multiple times from\nshow_line() in preparation for it learning '--only-matching'.\n\nshow_line_header() comprises of the code in show_line() executed in\norder to produce a line header. It is a one-for-one extraction of the\nexisting implementation.\n\nFor now, show_line_header() provides no benefit over the change before\nthis patch. The following patch will call conditionally call\nshow_line_header() multiple times per invocation to show_line(), which\nis the desired benefit of this change.\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 37bb39a4a8..89dd719e4d 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1369,26 +1369,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, unsigned cno, char sign)\n+static void show_line_header(struct grep_opt *opt, const char *name,\n+                             unsigned lno, unsigned 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@@ -1416,6 +1399,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, unsigned 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.17.0\n\n"},{"id":"346706","messageId":"c8b527c5de3b0e5422d2c1afb91d454d1e46fff4.1525492696.git.me@ttaylorr.com","threadId":"48412","inReplyTo":"cover.1525492696.git.me@ttaylorr.com","subject":"[PATCH 2/2] builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-05-05T04:03:14Z","receivedAt":"2018-05-05T04:03:24Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Teach GNU grep(1)'s '-o' ('--only-matching') to 'git-grep'. This option\nprints only the matching components of each line. It writes multiple\nlines if more than one match exists on a given line.\n\nFor example:\n\n  $ git grep -on --column --heading git -- README.md | head -3\n  README.md\n  15:56:git\n  18:20:git\n\nBy using show_line_header(), 'git grep --only-matching' correctly\nrespects the '--header' option:\n\n  $ git grep -on --column --heading git -- README.md | head -4\n  README.md\n  15:56:git\n  18:20:git\n  19:16:git\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Documentation/git-grep.txt |  6 +++++-\n builtin/grep.c             |  1 +\n grep.c                     | 23 ++++++++++++++++++++---\n grep.h                     |  1 +\n t/t7810-grep.sh            | 33 +++++++++++++++++++++++++++++++++\n 5 files changed, 60 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex d451cd8883..9754923041 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -20,7 +20,7 @@ SYNOPSIS\n \t   [-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+\t   [--break] [--heading] [-o | --only-matching] [-p | --show-function]\n \t   [-A <post-context>] [-B <pre-context>] [-C <context>]\n \t   [-W | --function-context]\n \t   [--threads <num>]\n@@ -221,6 +221,10 @@ providing this option will cause it to die.\n \tShow the filename above the matches in that file instead of\n \tat the start of each shown line.\n \n+--o::\n+--only-matching::\n+\tShow only the matching part of the lines.\n+\n -p::\n --show-function::\n \tShow the preceding line that contains the function name of\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 5c83f17759..5028bf96cf 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -851,6 +851,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"print empty line between matches from different files\")),\n \t\tOPT_BOOL(0, \"heading\", &opt.heading,\n \t\t\tN_(\"show filename only once above matches from same file\")),\n+\t\tOPT_BOOL('o', \"only-matching\", &opt.only_matching, N_(\"show only matches\")),\n \t\tOPT_GROUP(\"\"),\n \t\tOPT_CALLBACK('C', \"context\", &opt, N_(\"n\"),\n \t\t\tN_(\"show <n> context lines before and after matches\"),\ndiff --git a/grep.c b/grep.c\nindex 89dd719e4d..da3f8e6266 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1422,11 +1422,13 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\t}\n \t}\n \tshow_line_header(opt, name, lno, cno, sign);\n-\tif (opt->color) {\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+\t\tint first = 1;\n+\t\tint offset = 1;\n \n \t\tif (sign == ':')\n \t\t\tmatch_color = opt->color_match_selected;\n@@ -1443,16 +1445,31 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\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\toutput_color(opt, bol, match.rm_so, line_color);\n+\t\t\telse if (!first) {\n+\t\t\t\t/*\n+\t\t\t\t * We are given --only-matching, and this is not\n+\t\t\t\t * the first match on a line. Reprint the\n+\t\t\t\t * newline and header before showing another\n+\t\t\t\t * match.\n+\t\t\t\t */\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n+\t\t\t\tshow_line_header(opt, name, lno,\n+\t\t\t\t\toffset+match.rm_so, sign);\n+\t\t\t}\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\toffset += match.rm_eo;\n \t\t\tbol += match.rm_eo;\n \t\t\trest -= match.rm_eo;\n \t\t\teflags = REG_NOTBOL;\n+\t\t\tfirst = 0;\n \t\t}\n \t\t*eol = ch;\n \t}\n-\toutput_color(opt, bol, rest, line_color);\n+\tif (!opt->only_matching)\n+\t\toutput_color(opt, bol, rest, line_color);\n \topt->output(opt, \"\\n\", 1);\n }\n \ndiff --git a/grep.h b/grep.h\nindex 08a0b391c5..24c1460100 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -126,6 +126,7 @@ struct grep_opt {\n \tconst char *prefix;\n \tint prefix_length;\n \tregex_t regexp;\n+\tint only_matching;\n \tint linenum;\n \tint columnnum;\n \tint invert;\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex a03c3416e7..ef7f4ce725 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -1420,6 +1420,39 @@ test_expect_success 'grep --heading' '\n \ttest_cmp expected actual\n '\n \n+cat >expected <<EOF\n+file:1:5:mmap\n+file:2:5:mmap\n+file:3:5:mmap\n+file:3:14:mmap\n+file:4:5:mmap\n+file:4:14:mmap\n+file:5:5:mmap\n+file:5:14:mmap\n+EOF\n+\n+test_expect_success 'grep --only-matching' '\n+\tgit grep --only-matching --line-number --column mmap file >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF\n+file\n+1:5:mmap\n+2:5:mmap\n+3:5:mmap\n+3:14:mmap\n+4:5:mmap\n+4:14:mmap\n+5:5:mmap\n+5:14:mmap\n+EOF\n+\n+test_expect_success 'grep --only-matching --heading' '\n+\tgit grep --only-matching --heading --line-number --column mmap file >actual &&\n+\ttest_cmp expected actual\n+'\n+\n cat >expected <<EOF\n <BOLD;GREEN>hello.c<RESET>\n 4:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)\n-- \n2.17.0\n"},{"id":"346708","messageId":"87fu36y4u0.fsf@evledraar.gmail.com","threadId":"48412","inReplyTo":"c8b527c5de3b0e5422d2c1afb91d454d1e46fff4.1525492696.git.me@ttaylorr.com","subject":"Re: [PATCH 2/2] builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-05-05T06:49:43Z","receivedAt":"2018-05-05T06:49:49Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, May 05 2018, Taylor Blau wrote:\n\n> +--o::\n> +--only-matching::\n> +\tShow only the matching part of the lines.\n> +\n\nMakes sense to steal GNU grep's description here:\n\n    Print only the matched (non-empty) parts of a matching line, with\n    each such part on a separate output line.\n\n> +\t\t\tif (!opt->only_matching)\n> +\t\t\t\toutput_color(opt, bol, match.rm_so, line_color);\n\nThis should also have braces, see \"When there are multiple arms to a\nconditional\" in Documentation/CodingGuidelines.\n\n\n>  '\n>\n> +cat >expected <<EOF\n> +file:1:5:mmap\n> +file:2:5:mmap\n> +file:3:5:mmap\n> +file:3:14:mmap\n> +file:4:5:mmap\n> +file:4:14:mmap\n> +file:5:5:mmap\n> +file:5:14:mmap\n> +EOF\n\nThis should be set up as part of the test itself, see e.g. my c8b2cec09e\n(\"branch: add test for -m renaming multiple config sections\",\n2017-06-18) for how to do that.\n\n> +test_expect_success 'grep --only-matching' '\n> +\tgit grep --only-matching --line-number --column mmap file >actual &&\n> +\ttest_cmp expected actual\n> +'\n> +\n> +cat >expected <<EOF\n> +file\n> +1:5:mmap\n> +2:5:mmap\n> +3:5:mmap\n> +3:14:mmap\n> +4:5:mmap\n> +4:14:mmap\n> +5:5:mmap\n> +5:14:mmap\n> +EOF\n> +\n> +test_expect_success 'grep --only-matching --heading' '\n> +\tgit grep --only-matching --heading --line-number --column mmap file >actual &&\n> +\ttest_cmp expected actual\n> +'\n> +\n>  cat >expected <<EOF\n>  <BOLD;GREEN>hello.c<RESET>\n>  4:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)\n\nWe should test this a lot more, I think a good way to do that would be\nto extend this series by first importing GNU grep's -o tests, see\nhttp://git.savannah.gnu.org/cgit/grep.git/tree/tests/foad1 they are\nlicense-compatible. Then change the grep_test() function to call git\ngrep instead.\n\nIt should also be tested with the various grep.patternType options to\nmake sure it works with basic, extended, perl, fixed etc.\n"},{"id":"346710","messageId":"CAPig+cRbBZ+QTqGiW_wQ9E-gROA-Wtevp1vcRqmJ5YQJ8tYEVQ@mail.gmail.com","threadId":"48412","inReplyTo":"9dc758e3517681691e0809d9e9bcca03fbdad1cc.1525492696.git.me@ttaylorr.com","subject":"Re: [PATCH 1/2] grep.c: extract show_line_header()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-05-05T07:30:51Z","receivedAt":"2018-05-05T07:30:55Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, May 5, 2018 at 12:03 AM, Taylor Blau <me@ttaylorr.com> wrote:\n> Teach 'git-grep(1)' how to print a line header multiple times from\n> show_line() in preparation for it learning '--only-matching'.\n>\n> show_line_header() comprises of the code in show_line() executed in\n\ns/of//\n\n> order to produce a line header. It is a one-for-one extraction of the\n> existing implementation.\n>\n> For now, show_line_header() provides no benefit over the change before\n> this patch. The following patch will call conditionally call\n\ns/call conditionally call/conditionally call/\n\n> show_line_header() multiple times per invocation to show_line(), which\n> is the desired benefit of this change.\n>\n> Signed-off-by: Taylor Blau <me@ttaylorr.com>\n"},{"id":"346711","messageId":"CAPig+cSRJWW4-7vj6wK8aOfNB20bqUCSOOySjdPci1r5Vb83Uw@mail.gmail.com","threadId":"48412","inReplyTo":"c8b527c5de3b0e5422d2c1afb91d454d1e46fff4.1525492696.git.me@ttaylorr.com","subject":"Re: [PATCH 2/2] builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-05-05T07:36:12Z","receivedAt":"2018-05-05T07:36:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, May 5, 2018 at 12:03 AM, Taylor Blau <me@ttaylorr.com> wrote:\n> Teach GNU grep(1)'s '-o' ('--only-matching') to 'git-grep'. This option\n> prints only the matching components of each line. It writes multiple\n> lines if more than one match exists on a given line.\n>\n> For example:\n>\n>   $ git grep -on --column --heading git -- README.md | head -3\n>   README.md\n>   15:56:git\n>   18:20:git\n>\n> By using show_line_header(), 'git grep --only-matching' correctly\n> respects the '--header' option:\n\nWhat is the '--header' option? I don't see it used in any example.\n\n>   $ git grep -on --column --heading git -- README.md | head -4\n>   README.md\n>   15:56:git\n>   18:20:git\n>   19:16:git\n\nHow does this example differ from the earlier example (other than\nshowing 4 lines of output rather than 3)?\n\n> Signed-off-by: Taylor Blau <me@ttaylorr.com>\n"},{"id":"346937","messageId":"20180508002438.GA6763@syl.local","threadId":"48412","inReplyTo":"CAPig+cRbBZ+QTqGiW_wQ9E-gROA-Wtevp1vcRqmJ5YQJ8tYEVQ@mail.gmail.com","subject":"Re: [PATCH 1/2] grep.c: extract show_line_header()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-05-08T00:24:38Z","receivedAt":"2018-05-08T00:24:44Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Sat, May 05, 2018 at 03:30:51AM -0400, Eric Sunshine wrote:\n> On Sat, May 5, 2018 at 12:03 AM, Taylor Blau <me@ttaylorr.com> wrote:\n> > Teach 'git-grep(1)' how to print a line header multiple times from\n> > show_line() in preparation for it learning '--only-matching'.\n> >\n> > show_line_header() comprises of the code in show_line() executed in\n>\n> s/of//\n\nAck -- thanks for pointing that out :-).\n\n> > order to produce a line header. It is a one-for-one extraction of the\n> > existing implementation.\n> >\n> > For now, show_line_header() provides no benefit over the change before\n> > this patch. The following patch will call conditionally call\n>\n> s/call conditionally call/conditionally call/\n\nDouble ack. Thanks again :-).\n\n\nThanks,\nTaylor\n"},{"id":"346938","messageId":"20180508002748.GB6763@syl.local","threadId":"48412","inReplyTo":"CAPig+cSRJWW4-7vj6wK8aOfNB20bqUCSOOySjdPci1r5Vb83Uw@mail.gmail.com","subject":"Re: [PATCH 2/2] builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-05-08T00:27:48Z","receivedAt":"2018-05-08T00:27:54Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Sat, May 05, 2018 at 03:36:12AM -0400, Eric Sunshine wrote:\n> On Sat, May 5, 2018 at 12:03 AM, Taylor Blau <me@ttaylorr.com> wrote:\n> > Teach GNU grep(1)'s '-o' ('--only-matching') to 'git-grep'. This option\n> > prints only the matching components of each line. It writes multiple\n> > lines if more than one match exists on a given line.\n> >\n> > For example:\n> >\n> >   $ git grep -on --column --heading git -- README.md | head -3\n> >   README.md\n> >   15:56:git\n> >   18:20:git\n> >\n> > By using show_line_header(), 'git grep --only-matching' correctly\n> > respects the '--header' option:\n>\n> What is the '--header' option? I don't see it used in any example.\n\nI think '--header' is a typo for '--heading', which is used in the\nfollowing example.\n\n> >   $ git grep -on --column --heading git -- README.md | head -4\n> >   README.md\n> >   15:56:git\n> >   18:20:git\n> >   19:16:git\n>\n> How does this example differ from the earlier example (other than\n> showing 4 lines of output rather than 3)?\n\nAck. I clipped from my terminal what I meant to be the seocnd\nexample, and pasted it in for both examples. They are meant to be as\nfollows:\n\n  1. 'git grep' without heading, showing the full line prefix, and\n  2. 'git grep' with heading, showing the file heading with '--heading'.\n\nThe later has '| head -n4' on the end to include 3+1 lines (3 matches, 1\nheading) whereas the former has '| head -n3' to include 3 lines (3\nmatches, no heading).\n\nI have updated my patch locally to reflect this.\n\n\nThanks,\nTaylor\n"},{"id":"346998","messageId":"20180508172517.GA934@sigill.intra.peff.net","threadId":"48412","inReplyTo":"87fu36y4u0.fsf@evledraar.gmail.com","subject":"Re: [PATCH 2/2] builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-08T17:25:17Z","receivedAt":"2018-05-08T17:25:22Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, May 05, 2018 at 08:49:43AM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> > +test_expect_success 'grep --only-matching --heading' '\n> > +\tgit grep --only-matching --heading --line-number --column mmap file >actual &&\n> > +\ttest_cmp expected actual\n> > +'\n> > +\n> >  cat >expected <<EOF\n> >  <BOLD;GREEN>hello.c<RESET>\n> >  4:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)\n> \n> We should test this a lot more, I think a good way to do that would be\n> to extend this series by first importing GNU grep's -o tests, see\n> http://git.savannah.gnu.org/cgit/grep.git/tree/tests/foad1 they are\n> license-compatible. Then change the grep_test() function to call git\n> grep instead.\n\nI'm trying to figure out what GNU grep's tests are actually checking\nthat we don't have. I see:\n\n - they check that \"-i\" returns the actual found string in its original\n   case. This seems like a subset of finding a non-trivial regex. I.e.,\n   \"foo.*\" should find \"foobar\". We probably should have a test like\n   that.\n\n - they test multiple hits on the same line, which seems like an\n   important and easy-to-screw-up case; we do that, too.\n\n - they test everything other thing with and without \"-i\" because those\n   are apparently separate code paths in GNU grep, but I don't think\n   that applies to us.\n\n - they test each case with \"-b\", but we don't have that (we do test\n   with \"--column\", which is good)\n\n - they test with \"-n\", which we do here (we don't test without, but\n   that seems like an unlikely bug, knowing how it is implemented)\n\n - they test with -H, but that is already the default for git-grep\n\n - they test with context (-C3) for us. It looks like GNU grep omits\n   context lines with \"-o\", but we show a bunch of blank lines. This is\n   I guess a bug (though it seems kind of an odd combination to specify\n   in the first place)\n\nSo I think it probably makes sense to just pick through the list I just\nwrote and write our own tests than to try to import GNU grep's specific\ntests (and there's a ton of other unrelated tests in that file that may\nor may not even run with git-grep).\n\n> It should also be tested with the various grep.patternType options to\n> make sure it works with basic, extended, perl, fixed etc.\n\nHmm, this code is all external to the actual matching. So unless one of\nthose is totally screwing up the regmatch_t return, I'm not sure that's\naccomplishing much (and if one of them is, it's totally broken for\ncoloring, \"-c\", and maybe other features).\n\nWe've usually taken a pretty white-box approach to our testing, covering\nthings that seem likely to go wrong given the general problem space and\nour implementation. But maybe I'm missing something in particular that\nyou think might be tricky.\n\n-Peff\n"},{"id":"347167","messageId":"20180510020010.GA5375@syl.local","threadId":"48412","inReplyTo":"20180508172517.GA934@sigill.intra.peff.net","subject":"Re: [PATCH 2/2] builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-05-10T02:00:10Z","receivedAt":"2018-05-10T02:00:17Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, May 08, 2018 at 01:25:17PM -0400, Jeff King wrote:\n> On Sat, May 05, 2018 at 08:49:43AM +0200, Ævar Arnfjörð Bjarmason wrote:\n>\n> > > +test_expect_success 'grep --only-matching --heading' '\n> > > +\tgit grep --only-matching --heading --line-number --column mmap file >actual &&\n> > > +\ttest_cmp expected actual\n> > > +'\n> > > +\n> > >  cat >expected <<EOF\n> > >  <BOLD;GREEN>hello.c<RESET>\n> > >  4:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)\n> >\n> > We should test this a lot more, I think a good way to do that would be\n> > to extend this series by first importing GNU grep's -o tests, see\n> > http://git.savannah.gnu.org/cgit/grep.git/tree/tests/foad1 they are\n> > license-compatible. Then change the grep_test() function to call git\n> > grep instead.\n>\n> I'm trying to figure out what GNU grep's tests are actually checking\n> that we don't have. I see:\n>\n>  - they check that \"-i\" returns the actual found string in its original\n>    case. This seems like a subset of finding a non-trivial regex. I.e.,\n>    \"foo.*\" should find \"foobar\". We probably should have a test like\n>    that.\n>\n>  - they test multiple hits on the same line, which seems like an\n>    important and easy-to-screw-up case; we do that, too.\n\nAgree.\n\n>  - they test everything other thing with and without \"-i\" because those\n>    are apparently separate code paths in GNU grep, but I don't think\n>    that applies to us.\n>\n>  - they test each case with \"-b\", but we don't have that (we do test\n>    with \"--column\", which is good)\n\nRight, I think that we can ignore these groups.\n\n>  - they test with \"-n\", which we do here (we don't test without, but\n>    that seems like an unlikely bug, knowing how it is implemented)\n\nFair, let's leave that as is.\n\n>  - they test with -H, but that is already the default for git-grep\n\nGood, we can ignore this one.\n\n>  - they test with context (-C3) for us. It looks like GNU grep omits\n>    context lines with \"-o\", but we show a bunch of blank lines. This is\n>    I guess a bug (though it seems kind of an odd combination to specify\n>    in the first place)\n\nI'm torn on what to do here. Currently, with -C3, I get a bunch of lines\nlike:\n\n  <file>-\n\nWhich I think is technically _right_, but I agree that it is certainly\nan odd combination of things to give to 'git grep'. I think that we\ncould either:\n\n  1. Continue outputting blank lines,\n  2. Ignore '-C<n>' with '-o', or\n  3. die() when given this combination.\n\nI think that (1) is the most \"correct\" (for some definition), so I'm\ninclined to adopt that approach. I suppose that (2) is closer to what\nGNU grep offers, and that is the point of this series, so perhaps it\nmakes sense to pick that instead.\n\nI'll go with (2) for now, but I would appreciate others' thoughts before\nsending a subsequent re-roll of this series.\n\n> So I think it probably makes sense to just pick through the list I just\n> wrote and write our own tests than to try to import GNU grep's specific\n> tests (and there's a ton of other unrelated tests in that file that may\n> or may not even run with git-grep).\n\nI agree. Since some of these cases are already covered, and some don't\nhave analogues, I think that it is most sensible to go through the above\nand add _those_, instead of porting the whole test suite over from GNU.\n\n> > It should also be tested with the various grep.patternType options to\n> > make sure it works with basic, extended, perl, fixed etc.\n>\n> Hmm, this code is all external to the actual matching. So unless one of\n> those is totally screwing up the regmatch_t return, I'm not sure that's\n> accomplishing much (and if one of them is, it's totally broken for\n> coloring, \"-c\", and maybe other features).\n>\n> We've usually taken a pretty white-box approach to our testing, covering\n> things that seem likely to go wrong given the general problem space and\n> our implementation. But maybe I'm missing something in particular that\n> you think might be tricky.\n>\n> -Peff\n\nThanks,\nTaylor\n"},{"id":"347181","messageId":"20180510064014.GA31779@sigill.intra.peff.net","threadId":"48412","inReplyTo":"20180510020010.GA5375@syl.local","subject":"Re: [PATCH 2/2] builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-05-10T06:40:14Z","receivedAt":"2018-05-10T06:40:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, May 09, 2018 at 07:00:10PM -0700, Taylor Blau wrote:\n\n> >  - they test with context (-C3) for us. It looks like GNU grep omits\n> >    context lines with \"-o\", but we show a bunch of blank lines. This is\n> >    I guess a bug (though it seems kind of an odd combination to specify\n> >    in the first place)\n> \n> I'm torn on what to do here. Currently, with -C3, I get a bunch of lines\n> like:\n> \n>   <file>-\n> \n> Which I think is technically _right_, but I agree that it is certainly\n> an odd combination of things to give to 'git grep'. I think that we\n> could either:\n> \n>   1. Continue outputting blank lines,\n>   2. Ignore '-C<n>' with '-o', or\n>   3. die() when given this combination.\n> \n> I think that (1) is the most \"correct\" (for some definition), so I'm\n> inclined to adopt that approach. I suppose that (2) is closer to what\n> GNU grep offers, and that is the point of this series, so perhaps it\n> makes sense to pick that instead.\n> \n> I'll go with (2) for now, but I would appreciate others' thoughts before\n> sending a subsequent re-roll of this series.\n\nWe talked about this off-list, so I want to summarize here for the\narchive.\n\nIt turns out that the GNU grep behavior isn't universal.  Here's what I\nget:\n\n  $ grep -o -C3 the README.md\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n  --\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n  the\n\nSo that's not _quite_ ignoring -C. It's still showing the \"--\", which\nindicates that the first chunk are all matches within 6 lines of each\nother (so their context is melded into a single hunk), but it omits the\nlines entirely. Which at least seems like it could be _plausibly_\nuseful.\n\nBSD grep seems to actually show the context lines. Which is more\ninformation, but if you want to actually see the context, why are you\nusing \"-o\" in the first place?\n\nSo my general feeling is that disallowing the combination is probably\nfine, because it's a vaguely crazy thing to ask for. But that GNU grep's\nbehavior is the most likely to actually be useful. The BSD behavior\nseems more like \"this is what we happen to produce\" to me.\n\nAnd it should be pretty easy to reproduce the GNU grep behavior by just\nnot outputting anything in show_line().\n\n-Peff\n"},{"id":"347414","messageId":"cover.1526094978.git.me@ttaylorr.com","threadId":"48412","inReplyTo":"cover.1525492696.git.me@ttaylorr.com","subject":"[PATCH v2 0/2] builtin/grep.c: teach '-o', '--only-matching'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-05-12T03:21:03Z","receivedAt":"2018-05-12T03:21:11Z","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 add GNU grep's\n'--only-matching' to git-grep.\n\nThe main thing that has changed since last time is our handling of\n-{A,B,C}<N>. Previously, as Peff points out in [1], we handle this in a\nbuggy way different than GNU.\n\nI agree that although 'git grep -C<N> -o ...' is an unusual invocation,\nit is useful to (1) maintain as much consistency as reasonably makes\nsense, and (2) to at least not be buggy.\n\nI have also responded to Eric's suggestions in [2], and [3].\n\nThanks as always for your kind review :-).\n\n\nThanks,\nTaylor\n\n[1]: https://public-inbox.org/git/20180510064014.GA31779@sigill.intra.peff.net\n[2]: https://public-inbox.org/git/CAPig+cSRJWW4-7vj6wK8aOfNB20bqUCSOOySjdPci1r5Vb83Uw@mail.gmail.com\n[3]: https://public-inbox.org/git/CAPig+cRbBZ+QTqGiW_wQ9E-gROA-Wtevp1vcRqmJ5YQJ8tYEVQ@mail.gmail.com\n\nTaylor Blau (2):\n  grep.c: extract show_line_header()\n  builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'\n\n Documentation/git-grep.txt |  6 ++-\n builtin/grep.c             |  1 +\n grep.c                     | 78 +++++++++++++++++++++++++++-----------\n grep.h                     |  1 +\n t/t7810-grep.sh            | 69 +++++++++++++++++++++++++++++++++\n 5 files changed, 132 insertions(+), 23 deletions(-)\n\n--\n2.17.0\n"},{"id":"347415","messageId":"54b447cf2e5230a1e040fb4094f6108c071e2ce3.1526094978.git.me@ttaylorr.com","threadId":"48412","inReplyTo":"cover.1526094978.git.me@ttaylorr.com","subject":"[PATCH v2 1/2] grep.c: extract show_line_header()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-05-12T03:21:07Z","receivedAt":"2018-05-12T03:21:15Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Teach 'git-grep(1)' how to print a line header multiple times from\nshow_line() in preparation for it learning '--only-matching'.\n\nshow_line_header() comprises the code in show_line() executed in order\nto produce a line header. It is a one-for-one extraction of the existing\nimplementation.\n\nFor now, show_line_header() provides no benefit over the change before\nthis patch. The following patch will conditionally call\nshow_line_header() multiple times per invocation to show_line(), which\nis the desired benefit of this change.\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 5ba1b05526..36bf7cf08d 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1369,26 +1369,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, unsigned cno, char sign)\n+static void show_line_header(struct grep_opt *opt, const char *name,\n+                             unsigned lno, unsigned 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@@ -1417,6 +1400,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, unsigned 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.17.0\n\n"},{"id":"347416","messageId":"8258dac743a137196a6c43b2317248c5c079c115.1526094978.git.me@ttaylorr.com","threadId":"48412","inReplyTo":"cover.1526094978.git.me@ttaylorr.com","subject":"[PATCH v2 2/2] builtin/grep.c: teach '-o', '--only-matching' to 'git-grep'","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2018-05-12T03:21:11Z","receivedAt":"2018-05-12T03:21:18Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"Teach GNU grep(1)'s '-o' ('--only-matching') to 'git-grep'. This option\nprints only the matching components of each line. It writes multiple\nlines if more than one match exists on a given line.\n\nFor example:\n\n  $ /git grep -on --column git -- README.md | head -3\n  README.md:15:56:git\n  README.md:18:20:git\n  README.md:19:16:git\n\nBy using show_line_header(), 'git grep --only-matching' correctly\nrespects the '--heading' option:\n\n  $ git grep -on --column --heading git -- README.md | head -4\n  README.md\n  15:56:git\n  18:20:git\n  19:16:git\n\nWe mirror GNU grep's behavior when given -A, -B, or -C with\n--only-matching, by displaying only the matching portion(s) of a line,\nignoring contextual line(s), but displaying '--' (context separator)\nline(s).\n\nNotably: when show_line() is called on a line that contains _multiple_\nmatches, we keep track of a relative offset from the beginning of the\nline and increment 'cno' in subsequent calls to show_line_header() when\nthe expression is not extended. In the extended case, we do not do this,\nbecause the column of the first match is undefined, thus relative\noffsets are meaningless.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\n Documentation/git-grep.txt |  6 +++-\n builtin/grep.c             |  1 +\n grep.c                     | 34 +++++++++++++++++--\n grep.h                     |  1 +\n t/t7810-grep.sh            | 69 ++++++++++++++++++++++++++++++++++++++\n 5 files changed, 107 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex c48a578cb1..5c09abec4a 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -20,7 +20,7 @@ SYNOPSIS\n \t   [-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+\t   [--break] [--heading] [-o | --only-matching] [-p | --show-function]\n \t   [-A <post-context>] [-B <pre-context>] [-C <context>]\n \t   [-W | --function-context]\n \t   [--threads <num>]\n@@ -223,6 +223,10 @@ providing this option will cause it to die.\n \tShow the filename above the matches in that file instead of\n \tat the start of each shown line.\n \n+--o::\n+--only-matching::\n+\tPrints only the matching part of the lines.\n+\n -p::\n --show-function::\n \tShow the preceding line that contains the function name of\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex f9f516dfc4..0507ac335a 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -851,6 +851,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"print empty line between matches from different files\")),\n \t\tOPT_BOOL(0, \"heading\", &opt.heading,\n \t\t\tN_(\"show filename only once above matches from same file\")),\n+\t\tOPT_BOOL('o', \"only-matching\", &opt.only_matching, N_(\"show only matches\")),\n \t\tOPT_GROUP(\"\"),\n \t\tOPT_CALLBACK('C', \"context\", &opt, N_(\"n\"),\n \t\t\tN_(\"show <n> context lines before and after matches\"),\ndiff --git a/grep.c b/grep.c\nindex 36bf7cf08d..9297fde643 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1422,12 +1422,24 @@ 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+\tif (opt->only_matching && sign != ':') {\n+\t\t/*\n+\t\t * If we're given '--only-matching' and the line is a contextual\n+\t\t * one (i.e., we're given '-A', '-B', or '-C'), mark the line as\n+\t\t * shown (to advance opt->last_shown), but do not show it (since\n+\t\t * we are given '--only-matching').\n+\t\t */\n+\t\topt->last_shown = lno;\n+\t\treturn;\n+\t}\n \tshow_line_header(opt, name, lno, cno, sign);\n-\tif (opt->color) {\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+\t\tint first = 1;\n+\t\tint offset = 1;\n \n \t\tif (sign == ':')\n \t\t\tmatch_color = opt->color_match_selected;\n@@ -1444,16 +1456,32 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\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\toutput_color(opt, bol, match.rm_so, line_color);\n+\t\t\t} else if (!first) {\n+\t\t\t\t/*\n+\t\t\t\t * We are given --only-matching, and this is not\n+\t\t\t\t * the first match on a line. Reprint the\n+\t\t\t\t * newline and header before showing another\n+\t\t\t\t * match.\n+\t\t\t\t */\n+\t\t\t\topt->output(opt, \"\\n\", 1);\n+\t\t\t\tshow_line_header(opt, name, lno,\n+\t\t\t\t\topt->extended ? 0 : offset+match.rm_so,\n+\t\t\t\t\tsign);\n+\t\t\t}\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\toffset += match.rm_eo;\n \t\t\tbol += match.rm_eo;\n \t\t\trest -= match.rm_eo;\n \t\t\teflags = REG_NOTBOL;\n+\t\t\tfirst = 0;\n \t\t}\n \t\t*eol = ch;\n \t}\n-\toutput_color(opt, bol, rest, line_color);\n+\tif (!opt->only_matching)\n+\t\toutput_color(opt, bol, rest, line_color);\n \topt->output(opt, \"\\n\", 1);\n }\n \ndiff --git a/grep.h b/grep.h\nindex 08a0b391c5..24c1460100 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -126,6 +126,7 @@ struct grep_opt {\n \tconst char *prefix;\n \tint prefix_length;\n \tregex_t regexp;\n+\tint only_matching;\n \tint linenum;\n \tint columnnum;\n \tint invert;\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex 491b2e044a..6251ae678a 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -1432,6 +1432,75 @@ test_expect_success 'grep --heading' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'grep --only-matching' '\n+\tcat >expected <<-\\EOF &&\n+\tfile:1:5:mmap\n+\tfile:2:5:mmap\n+\tfile:3:5:mmap\n+\tfile:3:14:mmap\n+\tfile:4:5:mmap\n+\tfile:4:14:mmap\n+\tfile:5:5:mmap\n+\tfile:5:14:mmap\n+\tEOF\n+\tgit grep --only-matching --line-number --column mmap file >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --only-matching --column (unsupported)' '\n+\tcat >expected <<-\\EOF &&\n+\tfile:mmap\n+\tfile:mmap\n+\tfile:mmap\n+\tfile:mmap\n+\tfile:mmap\n+\tfile:mmap\n+\tfile:mmap\n+\tfile:mmap\n+\tEOF\n+\tgit grep --only-matching --column --not --not -e mmap -- file >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --only-matching -C' '\n+\tcat >expected <<-\\EOF &&\n+\thello.ps1:function\n+\thello.ps1:function\n+\t--\n+\thello.ps1:function\n+\tEOF\n+\tgit grep --only-matching -C1 function hello.ps1 >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --only-matching --heading' '\n+\tcat >expected <<-\\EOF &&\n+\tfile\n+\t1:5:mmap\n+\t2:5:mmap\n+\t3:5:mmap\n+\t3:14:mmap\n+\t4:5:mmap\n+\t4:14:mmap\n+\t5:5:mmap\n+\t5:14:mmap\n+\tEOF\n+\tgit grep --only-matching --heading --line-number --column mmap file >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --only-matching -i' '\n+\tcat >expected <<-\\EOF &&\n+\thello_world:1:1:Hello\n+\thello_world:2:1:HeLLo\n+\thello_world:3:1:Hello\n+\thello_world:4:1:HeLLo\n+\tEOF\n+\tgit grep --only-matching --line-number --column \\\n+\t\t-i hello hello_world >actual &&\n+\ttest_cmp expected actual\n+'\n+\n cat >expected <<EOF\n <BOLD;GREEN>hello.c<RESET>\n 4:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv)\n-- \n2.17.0\n"}]}