{"thread":{"id":"58039","subject":"[PATCH] grep: add --max-count command line option","startedAt":"2022-06-20T15:49:40Z","lastAt":"2022-06-22T19:47:45Z","messageCount":14,"participants":["Carlos L. via GitGitGadget","Paul Eggert","Carlos L.","Junio C Hamano","Carlos López via GitGitGadget"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"457559","messageId":"pull.1278.git.git.1655740174420.gitgitgadget@gmail.com","threadId":"58039","inReplyTo":null,"subject":"[PATCH] grep: add --max-count command line option","fromName":"Carlos L. via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-20T15:49:34Z","receivedAt":"2022-06-20T15:49:40Z","isPatch":true,"sender":{"key":"00xc@protonmail.com","avatar":"https://avatars.githubusercontent.com/u/48725664?v=4"},"body":"From: =?UTF-8?q?Carlos=20L=C3=B3pez?= <00xc@protonmail.com>\n\nThis patch adds a command line option analogous to that of GNU\ngrep(1)'s -m / --max-count, which users might already be used to.\nThis makes it possible to limit the amount of matches shown in the\noutput while keeping the functionality of other options such as -C\n(show code context) or -p (show containing function), which would be\ndifficult to do with a shell pipeline (e.g. head(1)).\n\nSigned-off-by: Carlos López 00xc@protonmail.com\n---\n    grep: add --max-count command line option\n    \n    This patch adds a command line option analogous to that of GNU grep(1)'s\n    -m / --max-count, which users might already be used to. This makes it\n    possible to limit the amount of matches shown in the output while\n    keeping the functionality of other options such as -C (show code\n    context) or -p (show containing function), which would be difficult to\n    do with a shell pipeline (e.g. head(1)).\n    \n    Signed-off-by: Carlos López 00xc@protonmail.com\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1278%2F00xc%2Fmaster-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1278/00xc/master-v1\nPull-Request: https://github.com/git/git/pull/1278\n\n Documentation/git-grep.txt | 8 ++++++++\n builtin/grep.c             | 9 +++++++++\n grep.c                     | 2 ++\n grep.h                     | 2 ++\n 4 files changed, 21 insertions(+)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex 3d393fbac1b..19b817d5e58 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -23,6 +23,7 @@ SYNOPSIS\n \t   [--break] [--heading] [-p | --show-function]\n \t   [-A <post-context>] [-B <pre-context>] [-C <context>]\n \t   [-W | --function-context]\n+\t   [(-m | --max-count) <num>]\n \t   [--threads <num>]\n \t   [-f <file>] [-e] <pattern>\n \t   [--and|--or|--not|(|)|-e <pattern>...]\n@@ -238,6 +239,13 @@ providing this option will cause it to die.\n \t`git diff` works out patch hunk headers (see 'Defining a\n \tcustom hunk-header' in linkgit:gitattributes[5]).\n \n+-m <num>::\n+--max-count <num>::\n+\tLimit the amount of matches per file. When using the `-v` or\n+\t`--invert-match` option, the search stops after the specified\n+\tnumber of non-matches. A value of -1 will return unlimited\n+\tresults (the default).\n+\n --threads <num>::\n \tNumber of grep worker threads to use.\n \tSee `grep.threads` in 'CONFIGURATION' for more information.\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex bcb07ea7f75..4ab28995da0 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -961,6 +961,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL_F(0, \"ext-grep\", &external_grep_allowed__ignored,\n \t\t\t   N_(\"allow calling of grep(1) (ignored by this build)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_INTEGER('m', \"max-count\", &opt.max_count,\n+\t\t\tN_(\"maximum number of results per file\")),\n \t\tOPT_END()\n \t};\n \tgrep_prefix = prefix;\n@@ -1101,6 +1103,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (recurse_submodules && untracked)\n \t\tdie(_(\"--untracked not supported with --recurse-submodules\"));\n \n+\t/*\n+\t * Optimize out the case where the amount of matches is limited to zero.\n+\t * We do this to keep results consistent with GNU grep(1).\n+\t */\n+\tif (opt.max_count == 0)\n+\t\texit(EXIT_FAILURE);\n+\n \tif (show_in_pager) {\n \t\tif (num_threads > 1)\n \t\t\twarning(_(\"invalid option combination, ignoring --threads\"));\ndiff --git a/grep.c b/grep.c\nindex 82eb7da1022..a010f9f4132 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1686,6 +1686,8 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle\n \t\tbol = eol + 1;\n \t\tif (!left)\n \t\t\tbreak;\n+\t\tif (opt->max_count != (unsigned)-1 && count == opt->max_count)\n+\t\t\tbreak;\n \t\tleft--;\n \t\tlno++;\n \t}\ndiff --git a/grep.h b/grep.h\nindex c722d25ed9d..218585a8679 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -171,6 +171,7 @@ struct grep_opt {\n \tint show_hunk_mark;\n \tint file_break;\n \tint heading;\n+\tunsigned max_count;\n \tvoid *priv;\n \n \tvoid (*output)(struct grep_opt *opt, const void *data, size_t size);\n@@ -181,6 +182,7 @@ struct grep_opt {\n \t.relative = 1, \\\n \t.pathname = 1, \\\n \t.max_depth = -1, \\\n+\t.max_count = (unsigned)-1, \\\n \t.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \\\n \t.colors = { \\\n \t\t[GREP_COLOR_CONTEXT] = \"\", \\\n\nbase-commit: 5b71c59bc3b9365075e2a175aa7b6f2b0c84ce44\n-- \ngitgitgadget\n"},{"id":"457560","messageId":"449bede6-82b0-72ef-300d-bc0c49a1858b@cs.ucla.edu","threadId":"58039","inReplyTo":"pull.1278.git.git.1655740174420.gitgitgadget@gmail.com","subject":"Re: [PATCH] grep: add --max-count command line option","fromName":"Paul Eggert","fromEmail":"eggert@cs.ucla.edu","sentAt":"2022-06-20T15:57:40Z","receivedAt":"2022-06-20T15:57:47Z","isPatch":true,"sender":{"key":"eggert@cs.ucla.edu","avatar":"https://avatars.githubusercontent.com/u/572024?v=4"},"body":"On 6/20/22 10:49, Carlos L. via GitGitGadget wrote:\n> +\tunsigned max_count;\n\nWhy not make this intmax_t? That way, you don't have to worry about \ncasting -1 to unsigned. Also on typical 64-bit machines you no longer \nhave to worry about mishandling counts greater than 2**32 (the limit \nbecomes 2**63 - 1 which is plenty).\n\nThese days it's typically better to avoid unsigned types in C when you \ncan, as standard tools like 'gcc -fsanitize=undefined' can catch signed \nint overflow whereas unsigned int overflow always wraps around which is \ntypically bad news.\n\n"},{"id":"457561","messageId":"xy0kCdbtIjKb_GkXpHs7eeE8eWLbbct69_tVczBGS3CMnnnKTlTheaeNlH-RB1mMAfWThSTN6bMThsQ4lLtr0Ji6RwG3Nd52L1muiEqdtCU=@protonmail.com","threadId":"58039","inReplyTo":"449bede6-82b0-72ef-300d-bc0c49a1858b@cs.ucla.edu","subject":"Re: [PATCH] grep: add --max-count command line option","fromName":"Carlos L.","fromEmail":"00xc@protonmail.com","sentAt":"2022-06-20T16:25:21Z","receivedAt":"2022-06-20T16:27:27Z","isPatch":true,"sender":{"key":"00xc@protonmail.com","avatar":"https://avatars.githubusercontent.com/u/48725664?v=4"},"body":"Hi,\n\nOn Monday, June 20th, 2022 at 17:57, Paul Eggert <eggert@cs.ucla.edu> wrote:\n\n> On 6/20/22 10:49, Carlos L. via GitGitGadget wrote:\n>\n> > + unsigned max_count;\n>\n>\n> Why not make this intmax_t? That way, you don't have to worry about\n> casting -1 to unsigned. Also on typical 64-bit machines you no longer\n> have to worry about mishandling counts greater than 232 (the limit\n> becomes 263 - 1 which is plenty).\n\nThis does not work well with OPTION_INTEGER, since it assumes the value to be int-sized:\n\nparse-options.c:\n 219             *(int *)opt->value = strtol(arg, (char **)&s, 10);\n\nI also wanted to avoid using signed int so both sides of the comparison with `count` in grep_source_1() have the same sign.\n"},{"id":"457562","messageId":"7b7a2876-f6fe-1234-813b-71ba94a00422@cs.ucla.edu","threadId":"58039","inReplyTo":"xy0kCdbtIjKb_GkXpHs7eeE8eWLbbct69_tVczBGS3CMnnnKTlTheaeNlH-RB1mMAfWThSTN6bMThsQ4lLtr0Ji6RwG3Nd52L1muiEqdtCU=@protonmail.com","subject":"Re: [PATCH] grep: add --max-count command line option","fromName":"Paul Eggert","fromEmail":"eggert@cs.ucla.edu","sentAt":"2022-06-20T16:32:35Z","receivedAt":"2022-06-20T16:32:41Z","isPatch":true,"sender":{"key":"eggert@cs.ucla.edu","avatar":"https://avatars.githubusercontent.com/u/572024?v=4"},"body":"On 6/20/22 11:25, Carlos L. wrote:\n> This does not work well with OPTION_INTEGER, since it assumes the value to be int-sized:\n>\n> parse-options.c:\n>   219             *(int *)opt->value = strtol(arg, (char **)&s, 10);\n\nOK, so parse-options messes up if the user specifies a count that does \nnot fit in 'int'? Although that's a separate bug, let's not make things \nworse here; let's make the new count an 'int'.\n\nIn the long run parse-options should be changed to use strtoimax instead \nof strtol, and the corresponding integers should be changed to intmax_t, \nand the proper thing should be done if the string value does not fit \ninto intmax_t. But this longer-run fix affects all integer-valued \noptions, not just this one.\n\n\n> I also wanted to avoid using signed int so both sides of the comparison with `count` in grep_source_1() have the same sign.\n\nSuch comparisons cannot misfire if both values are nonnegative, and that \ncan easily be arranged here.\n\n"},{"id":"457589","messageId":"pull.1278.v2.git.git.1655789777023.gitgitgadget@gmail.com","threadId":"58039","inReplyTo":"pull.1278.git.git.1655740174420.gitgitgadget@gmail.com","subject":"[PATCH v2] grep: add --max-count command line option","fromName":"Carlos L. via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-21T05:36:16Z","receivedAt":"2022-06-21T05:36:42Z","isPatch":true,"sender":{"key":"00xc@protonmail.com","avatar":"https://avatars.githubusercontent.com/u/48725664?v=4"},"body":"From: =?UTF-8?q?Carlos=20L=C3=B3pez?= <00xc@protonmail.com>\n\nThis patch adds a command line option analogous to that of GNU\ngrep(1)'s -m / --max-count, which users might already be used to.\nThis makes it possible to limit the amount of matches shown in the\noutput while keeping the functionality of other options such as -C\n(show code context) or -p (show containing function), which would be\ndifficult to do with a shell pipeline (e.g. head(1)).\n\nSigned-off-by: Carlos López 00xc@protonmail.com\n---\n    grep: add --max-count command line option\n    \n    This patch adds a command line option analogous to that of GNU grep(1)'s\n    -m / --max-count, which users might already be used to. This makes it\n    possible to limit the amount of matches shown in the output while\n    keeping the functionality of other options such as -C (show code\n    context) or -p (show containing function), which would be difficult to\n    do with a shell pipeline (e.g. head(1)).\n    \n    Signed-off-by: Carlos López 00xc@protonmail.com\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1278%2F00xc%2Fmaster-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1278/00xc/master-v2\nPull-Request: https://github.com/git/git/pull/1278\n\nRange-diff vs v1:\n\n 1:  f89c6e244aa ! 1:  ee7eb298854 grep: add --max-count command line option\n     @@ grep.c: static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, i\n       \t\tbol = eol + 1;\n       \t\tif (!left)\n       \t\t\tbreak;\n     -+\t\tif (opt->max_count != (unsigned)-1 && count == opt->max_count)\n     ++\t\tif (opt->max_count != -1 && count == opt->max_count)\n      +\t\t\tbreak;\n       \t\tleft--;\n       \t\tlno++;\n     @@ grep.h: struct grep_opt {\n       \tint show_hunk_mark;\n       \tint file_break;\n       \tint heading;\n     -+\tunsigned max_count;\n     ++\tint max_count;\n       \tvoid *priv;\n       \n       \tvoid (*output)(struct grep_opt *opt, const void *data, size_t size);\n     @@ grep.h: struct grep_opt {\n       \t.relative = 1, \\\n       \t.pathname = 1, \\\n       \t.max_depth = -1, \\\n     -+\t.max_count = (unsigned)-1, \\\n     ++\t.max_count = -1, \\\n       \t.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \\\n       \t.colors = { \\\n       \t\t[GREP_COLOR_CONTEXT] = \"\", \\\n\n\n Documentation/git-grep.txt | 8 ++++++++\n builtin/grep.c             | 9 +++++++++\n grep.c                     | 2 ++\n grep.h                     | 2 ++\n 4 files changed, 21 insertions(+)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex 3d393fbac1b..19b817d5e58 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -23,6 +23,7 @@ SYNOPSIS\n \t   [--break] [--heading] [-p | --show-function]\n \t   [-A <post-context>] [-B <pre-context>] [-C <context>]\n \t   [-W | --function-context]\n+\t   [(-m | --max-count) <num>]\n \t   [--threads <num>]\n \t   [-f <file>] [-e] <pattern>\n \t   [--and|--or|--not|(|)|-e <pattern>...]\n@@ -238,6 +239,13 @@ providing this option will cause it to die.\n \t`git diff` works out patch hunk headers (see 'Defining a\n \tcustom hunk-header' in linkgit:gitattributes[5]).\n \n+-m <num>::\n+--max-count <num>::\n+\tLimit the amount of matches per file. When using the `-v` or\n+\t`--invert-match` option, the search stops after the specified\n+\tnumber of non-matches. A value of -1 will return unlimited\n+\tresults (the default).\n+\n --threads <num>::\n \tNumber of grep worker threads to use.\n \tSee `grep.threads` in 'CONFIGURATION' for more information.\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex bcb07ea7f75..4ab28995da0 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -961,6 +961,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL_F(0, \"ext-grep\", &external_grep_allowed__ignored,\n \t\t\t   N_(\"allow calling of grep(1) (ignored by this build)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_INTEGER('m', \"max-count\", &opt.max_count,\n+\t\t\tN_(\"maximum number of results per file\")),\n \t\tOPT_END()\n \t};\n \tgrep_prefix = prefix;\n@@ -1101,6 +1103,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (recurse_submodules && untracked)\n \t\tdie(_(\"--untracked not supported with --recurse-submodules\"));\n \n+\t/*\n+\t * Optimize out the case where the amount of matches is limited to zero.\n+\t * We do this to keep results consistent with GNU grep(1).\n+\t */\n+\tif (opt.max_count == 0)\n+\t\texit(EXIT_FAILURE);\n+\n \tif (show_in_pager) {\n \t\tif (num_threads > 1)\n \t\t\twarning(_(\"invalid option combination, ignoring --threads\"));\ndiff --git a/grep.c b/grep.c\nindex 82eb7da1022..b32ab75cb6b 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1686,6 +1686,8 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle\n \t\tbol = eol + 1;\n \t\tif (!left)\n \t\t\tbreak;\n+\t\tif (opt->max_count != -1 && count == opt->max_count)\n+\t\t\tbreak;\n \t\tleft--;\n \t\tlno++;\n \t}\ndiff --git a/grep.h b/grep.h\nindex c722d25ed9d..bdcadce61b8 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -171,6 +171,7 @@ struct grep_opt {\n \tint show_hunk_mark;\n \tint file_break;\n \tint heading;\n+\tint max_count;\n \tvoid *priv;\n \n \tvoid (*output)(struct grep_opt *opt, const void *data, size_t size);\n@@ -181,6 +182,7 @@ struct grep_opt {\n \t.relative = 1, \\\n \t.pathname = 1, \\\n \t.max_depth = -1, \\\n+\t.max_count = -1, \\\n \t.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \\\n \t.colors = { \\\n \t\t[GREP_COLOR_CONTEXT] = \"\", \\\n\nbase-commit: 5b71c59bc3b9365075e2a175aa7b6f2b0c84ce44\n-- \ngitgitgadget\n"},{"id":"457628","messageId":"xmqq5ykuhthp.fsf@gitster.g","threadId":"58039","inReplyTo":"pull.1278.v2.git.git.1655789777023.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] grep: add --max-count command line option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-21T16:27:30Z","receivedAt":"2022-06-21T16:27:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Carlos L. via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: =?UTF-8?q?Carlos=20L=C3=B3pez?= <00xc@protonmail.com>\n>\n> This patch adds a command line option analogous to that of GNU\n> grep(1)'s -m / --max-count, which users might already be used to.\n> This makes it possible to limit the amount of matches shown in the\n> output while keeping the functionality of other options such as -C\n> (show code context) or -p (show containing function), which would be\n> difficult to do with a shell pipeline (e.g. head(1)).\n>\n> Signed-off-by: Carlos López 00xc@protonmail.com\n> ---\n> ...\n>  Documentation/git-grep.txt | 8 ++++++++\n>  builtin/grep.c             | 9 +++++++++\n>  grep.c                     | 2 ++\n>  grep.h                     | 2 ++\n>  4 files changed, 21 insertions(+)\n\nTests?\n\n> diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\n> index 3d393fbac1b..19b817d5e58 100644\n> --- a/Documentation/git-grep.txt\n> +++ b/Documentation/git-grep.txt\n> @@ -23,6 +23,7 @@ SYNOPSIS\n>  \t   [--break] [--heading] [-p | --show-function]\n>  \t   [-A <post-context>] [-B <pre-context>] [-C <context>]\n>  \t   [-W | --function-context]\n> +\t   [(-m | --max-count) <num>]\n>  \t   [--threads <num>]\n>  \t   [-f <file>] [-e] <pattern>\n>  \t   [--and|--or|--not|(|)|-e <pattern>...]\n> @@ -238,6 +239,13 @@ providing this option will cause it to die.\n>  \t`git diff` works out patch hunk headers (see 'Defining a\n>  \tcustom hunk-header' in linkgit:gitattributes[5]).\n>  \n> +-m <num>::\n> +--max-count <num>::\n> +\tLimit the amount of matches per file. When using the `-v` or\n> +\t`--invert-match` option, the search stops after the specified\n> +\tnumber of non-matches. A value of -1 will return unlimited\n> +\tresults (the default).\n\nHmph ...\n\n> +\t/*\n> +\t * Optimize out the case where the amount of matches is limited to zero.\n> +\t * We do this to keep results consistent with GNU grep(1).\n> +\t */\n> +\tif (opt.max_count == 0)\n> +\t\texit(EXIT_FAILURE);\n> +\n\nOK, so \"stop before seeing any match\" logically leads to \"we found\nnothing, so exit with non-zero\".\n\n> diff --git a/grep.c b/grep.c\n> index 82eb7da1022..b32ab75cb6b 100644\n> --- a/grep.c\n> +++ b/grep.c\n> @@ -1686,6 +1686,8 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle\n>  \t\tbol = eol + 1;\n>  \t\tif (!left)\n>  \t\t\tbreak;\n> +\t\tif (opt->max_count != -1 && count == opt->max_count)\n> +\t\t\tbreak;\n\nI would have written it \"if (0 <= opt->max_count && ...)\".  What\nhappens when a trickster asks you to do \"git grep -m -2\"?\n\nI guess what I am getting at is if we are better off saying that\nnegative means unlimited, instead of special casing -1 like this.  I\ndidn't think it through so it may be perfectly possible that what\nyou wrote makes more sense than \"anything negative is unlimited\".\n\nI dunno.\n\n>  \t\tleft--;\n>  \t\tlno++;\n>  \t}\n\nThanks.\n\n> diff --git a/grep.h b/grep.h\n> index c722d25ed9d..bdcadce61b8 100644\n> --- a/grep.h\n> +++ b/grep.h\n> @@ -171,6 +171,7 @@ struct grep_opt {\n>  \tint show_hunk_mark;\n>  \tint file_break;\n>  \tint heading;\n> +\tint max_count;\n>  \tvoid *priv;\n>  \n>  \tvoid (*output)(struct grep_opt *opt, const void *data, size_t size);\n> @@ -181,6 +182,7 @@ struct grep_opt {\n>  \t.relative = 1, \\\n>  \t.pathname = 1, \\\n>  \t.max_depth = -1, \\\n> +\t.max_count = -1, \\\n>  \t.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \\\n>  \t.colors = { \\\n>  \t\t[GREP_COLOR_CONTEXT] = \"\", \\\n>\n> base-commit: 5b71c59bc3b9365075e2a175aa7b6f2b0c84ce44\n"},{"id":"457684","messageId":"XWF-gaPGsSObqE_fNBKYQ-577E-oM5mMtmnDeGE-oCRmjjTSnByfmgGwesPecoAAVN9B2Gk1mA-Hu-KVyGyPiwzUxgC4Yc1HUetOvYoC2YU=@protonmail.com","threadId":"58039","inReplyTo":"xmqq5ykuhthp.fsf@gitster.g","subject":"Re: [PATCH v2] grep: add --max-count command line option","fromName":"Carlos L.","fromEmail":"00xc@protonmail.com","sentAt":"2022-06-22T06:41:49Z","receivedAt":"2022-06-22T06:42:06Z","isPatch":true,"sender":{"key":"00xc@protonmail.com","avatar":"https://avatars.githubusercontent.com/u/48725664?v=4"},"body":"Hi,\n\nJust a couple of questions.\n\nOn Tuesday, June 21st, 2022 at 18:27, Junio C Hamano <gitster@pobox.com> wrote:\n\n> \"Carlos L. via GitGitGadget\" gitgitgadget@gmail.com writes:\n>\n> > From: =?UTF-8?q?Carlos=20L=C3=B3pez?= 00xc@protonmail.com\n> >\n> > This patch adds a command line option analogous to that of GNU\n> > grep(1)'s -m / --max-count, which users might already be used to.\n> > This makes it possible to limit the amount of matches shown in the\n> > output while keeping the functionality of other options such as -C\n> > (show code context) or -p (show containing function), which would be\n> > difficult to do with a shell pipeline (e.g. head(1)).\n> >\n> > Signed-off-by: Carlos López 00xc@protonmail.com\n> > ---\n> > ...\n> > Documentation/git-grep.txt | 8 ++++++++\n> > builtin/grep.c | 9 +++++++++\n> > grep.c | 2 ++\n> > grep.h | 2 ++\n> > 4 files changed, 21 insertions(+)\n>\n>\n> Tests?\n\nRight. Is it OK if I include my test(s) in t/t7810-grep.sh, or should it be a different/new file?\n\n> > diff --git a/grep.c b/grep.c\n> > index 82eb7da1022..b32ab75cb6b 100644\n> > --- a/grep.c\n> > +++ b/grep.c\n> > @@ -1686,6 +1686,8 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle\n> > bol = eol + 1;\n> > if (!left)\n> > break;\n> > + if (opt->max_count != -1 && count == opt->max_count)\n> > + break;\n>\n>\n> I would have written it \"if (0 <= opt->max_count && ...)\". What\n>\n> happens when a trickster asks you to do \"git grep -m -2\"?\n\nFair enough. Since it's already optimized out above, is there any reason we need to include zero (<=)?\n\n> I guess what I am getting at is if we are better off saying that\n> negative means unlimited, instead of special casing -1 like this. I\n> didn't think it through so it may be perfectly possible that what\n> you wrote makes more sense than \"anything negative is unlimited\".\n>\n> I dunno.\n\nI think you're right, I'll adjust my patch.\n\nBest,\nCarlos\n"},{"id":"457685","messageId":"xmqqo7yl6vad.fsf@gitster.g","threadId":"58039","inReplyTo":"XWF-gaPGsSObqE_fNBKYQ-577E-oM5mMtmnDeGE-oCRmjjTSnByfmgGwesPecoAAVN9B2Gk1mA-Hu-KVyGyPiwzUxgC4Yc1HUetOvYoC2YU=@protonmail.com","subject":"Re: [PATCH v2] grep: add --max-count command line option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-22T06:56:26Z","receivedAt":"2022-06-22T06:56:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Carlos L.\" <00xc@protonmail.com> writes:\n\n>> Tests?\n>\n> Right. Is it OK if I include my test(s) in t/t7810-grep.sh, or\n> should it be a different/new file?\n\nIt is preferrable to add new tests to existing scripts, rather than\nadding a new (and short) one.\n\nThanks.\n"},{"id":"457694","messageId":"ScVk65a3K94G-nmKXyUBKC94xIjYgn-W1kSJyb3DasKtGFF0rDJyOkZMmcnVyNH60I717KpBBN83QuMXHuIAYfbmQdxX2Woiw62m0QM6AdE=@protonmail.com","threadId":"58039","inReplyTo":"220622.86mte5knbe.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2] grep: add --max-count command line option","fromName":"Carlos L.","fromEmail":"00xc@protonmail.com","sentAt":"2022-06-22T13:23:04Z","receivedAt":"2022-06-22T13:23:55Z","isPatch":true,"sender":{"key":"00xc@protonmail.com","avatar":"https://avatars.githubusercontent.com/u/48725664?v=4"},"body":"Hi,\n\nOn Wednesday, June 22nd, 2022 at 12:22, Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n\n> On Tue, Jun 21 2022, Carlos L. via GitGitGadget wrote:\n>\n> > From: =?UTF-8?q?Carlos=20L=C3=B3pez?= 00xc@protonmail.com\n> >\n> > This patch adds a command line option analogous to that of GNU\n> > grep(1)'s -m / --max-count, which users might already be used to.\n>\n>\n> Thanks, this seems useful.\n>\n> > This makes it possible to limit the amount of matches shown in the\n> > output while keeping the functionality of other options such as -C\n> > (show code context) or -p (show containing function), which would be\n> > difficult to do with a shell pipeline (e.g. head(1)).\n>\n>\n> We start multi-threaded grep workers, how does this code handle races\n> between them finding things, this count being incremented, and the \"do\n> we have sufficient results?\" check?\n>\n> Is it guarded by the relevant mutexes?\n\nAFAICT only a single thread runs on each file via grep_source_1(), and we check `count`, which is local to this function.\n\n> > + /*\n> > + * Optimize out the case where the amount of matches is limited to zero.\n> > + * We do this to keep results consistent with GNU grep(1).\n> > + */\n> > + if (opt.max_count == 0)\n> > + exit(EXIT_FAILURE);\n>\n>\n> Don't use exit() in cmd_grep(), you should use \"return 1\".\n\nI'll use return in my follow-up patch, this can be improved afterwards.\n\n> But even better use usage_msg_opt() here, i.e. inform the user why this\n> was bad.\n>\n> Or hrm, it seems GNU grep silently returns 1 here, perhaps --max-count=0\n> is a feature for some?\n>\n> If this is intentional it's worth documenting and testing it explicitly.\n\nI will add a sentence about this in Documentation/git-grep.txt.\n\n> Re the comments from others about size_t or whatever, it might be better\n> here to use OPT_CALLBACK and an unsigned type.\n>\n> Then just have a \"int have_max_count:1\", which IMO is more obvious than\n> using integer wrap-around to test \"didn't provide this flag\".\n\nFWIW, I think it's fine to use int and a negative value as an special encoding, max_depth does the same thing. These are per-file matches, so they should not go over 2 billion in reasonable use cases.\n"},{"id":"457727","messageId":"pull.1278.v3.git.git.1655917628.gitgitgadget@gmail.com","threadId":"58039","inReplyTo":"pull.1278.v2.git.git.1655789777023.gitgitgadget@gmail.com","subject":"[PATCH v3 0/2] grep: add --max-count command line option","fromName":"Carlos L. via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-22T17:07:06Z","receivedAt":"2022-06-22T17:08:46Z","isPatch":true,"sender":{"key":"00xc@protonmail.com","avatar":"https://avatars.githubusercontent.com/u/48725664?v=4"},"body":"This patch adds a command line option analogous to that of GNU grep(1)'s -m\n/ --max-count, which users might already be used to. This makes it possible\nto limit the amount of matches shown in the output while keeping the\nfunctionality of other options such as -C (show code context) or -p (show\ncontaining function), which would be difficult to do with a shell pipeline\n(e.g. head(1)).\n\nSigned-off-by: Carlos López 00xc@protonmail.com\n\nCarlos López (2):\n  grep: add --max-count command line option\n  tests: add tests for grep --max-count\n\n Documentation/git-grep.txt |  9 +++++\n builtin/grep.c             |  9 +++++\n grep.c                     |  2 +-\n grep.h                     |  2 +\n t/t7810-grep.sh            | 83 ++++++++++++++++++++++++++++++++++++++\n 5 files changed, 104 insertions(+), 1 deletion(-)\n\n\nbase-commit: 5b71c59bc3b9365075e2a175aa7b6f2b0c84ce44\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1278%2F00xc%2Fmaster-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1278/00xc/master-v3\nPull-Request: https://github.com/git/git/pull/1278\n\nRange-diff vs v2:\n\n 1:  ee7eb298854 ! 1:  5bf7244437e grep: add --max-count command line option\n     @@ Documentation/git-grep.txt: providing this option will cause it to die.\n      +\tLimit the amount of matches per file. When using the `-v` or\n      +\t`--invert-match` option, the search stops after the specified\n      +\tnumber of non-matches. A value of -1 will return unlimited\n     -+\tresults (the default).\n     ++\tresults (the default). A value of 0 will exit immediately with\n     ++\ta non-zero status.\n      +\n       --threads <num>::\n       \tNumber of grep worker threads to use.\n     @@ builtin/grep.c: int cmd_grep(int argc, const char **argv, const char *prefix)\n      +\t * We do this to keep results consistent with GNU grep(1).\n      +\t */\n      +\tif (opt.max_count == 0)\n     -+\t\texit(EXIT_FAILURE);\n     ++\t\treturn 1;\n      +\n       \tif (show_in_pager) {\n       \t\tif (num_threads > 1)\n     @@ builtin/grep.c: int cmd_grep(int argc, const char **argv, const char *prefix)\n      \n       ## grep.c ##\n      @@ grep.c: static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle\n     - \t\tbol = eol + 1;\n     - \t\tif (!left)\n     - \t\t\tbreak;\n     -+\t\tif (opt->max_count != -1 && count == opt->max_count)\n     -+\t\t\tbreak;\n     - \t\tleft--;\n     - \t\tlno++;\n     - \t}\n     + \t\t\t\treturn 0;\n     + \t\t\tgoto next_line;\n     + \t\t}\n     +-\t\tif (hit) {\n     ++\t\tif (hit && (opt->max_count < 0 || count < opt->max_count)) {\n     + \t\t\tcount++;\n     + \t\t\tif (opt->status_only)\n     + \t\t\t\treturn 1;\n      \n       ## grep.h ##\n      @@ grep.h: struct grep_opt {\n -:  ----------- > 2:  525958af877 tests: add tests for grep --max-count\n\n-- \ngitgitgadget\n"},{"id":"457728","messageId":"525958af877c3bbd024ffd3aee0c64e469c3d6cf.1655917628.git.gitgitgadget@gmail.com","threadId":"58039","inReplyTo":"pull.1278.v3.git.git.1655917628.gitgitgadget@gmail.com","subject":"[PATCH v3 2/2] tests: add tests for grep --max-count","fromName":"Carlos López via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-22T17:07:08Z","receivedAt":"2022-06-22T17:08:48Z","isPatch":true,"sender":{"key":"name:Carlos López","avatar":null},"body":"From: =?UTF-8?q?Carlos=20L=C3=B3pez?= <00xc@protonmail.com>\n\nAdd tests for grep's -m / --max-count to check if the option correctly\noutputs limited results, and that it interacts properly with other flags\nthat could likely be used in conjunction.\n\nSigned-off-by: Carlos López 00xc@protonmail.com\n---\n t/t7810-grep.sh | 83 +++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 83 insertions(+)\n\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex 69356011713..7b1b8a3cd93 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -77,6 +77,7 @@ test_expect_success setup '\n \t# Say hello.\n \tfunction hello() {\n \t  echo \"Hello world.\"\n+\t  echo \"Hello again.\"\n \t} # hello\n \n \t# Still a no-op.\n@@ -595,6 +596,88 @@ test_expect_success 'grep --files-without-match --quiet' '\n \ttest_must_be_empty actual\n '\n \n+cat >expected <<EOF &&\n+EOF\n+\n+test_expect_success 'grep --max-count 0 (must exit with non-zero)' '\n+\ttest_must_fail git grep --max-count 0 foo >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF &&\n+file:foo mmap bar\n+EOF\n+\n+test_expect_success 'grep --max-count 1' '\n+\tgit grep --max-count 1 foo >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF &&\n+file:foo mmap bar\n+file:foo_mmap bar\n+file:foo_mmap bar mmap\n+EOF\n+\n+test_expect_success 'grep --max-count 3' '\n+\tgit grep --max-count 3 foo >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF &&\n+file:foo mmap bar\n+file:foo_mmap bar\n+file:foo_mmap bar mmap\n+file:foo mmap bar_mmap\n+file:foo_mmap bar mmap baz\n+EOF\n+\n+test_expect_success 'grep --max-count -1 (no limit)' '\n+\tgit grep --max-count -1 foo >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF &&\n+file-foo mmap bar\n+file:foo_mmap bar\n+file-foo_mmap bar mmap\n+EOF\n+\n+test_expect_success 'grep --max-count 1 --context 2' '\n+\tgit grep --max-count 1 --context 1 foo_mmap >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF &&\n+hello.ps1=function hello() {\n+hello.ps1:  echo \"Hello world.\"\n+EOF\n+\n+test_expect_success 'grep --max-count 1 --show-function' '\n+\tgit grep --max-count 1 --show-function Hello hello.ps1 >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF &&\n+hello.ps1=function hello() {\n+hello.ps1:  echo \"Hello world.\"\n+hello.ps1:  echo \"Hello again.\"\n+EOF\n+\n+test_expect_success 'grep --max-count 2 --show-function' '\n+\tgit grep --max-count 2 --show-function Hello hello.ps1 >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF &&\n+hello.ps1:1\n+EOF\n+\n+test_expect_success 'grep --max-count 1 -c' '\n+\tgit grep --max-count 1 --count Hello hello.ps1 >actual &&\n+\ttest_cmp expected actual\n+'\n+\n cat >expected <<EOF\n file:foo mmap bar_mmap\n EOF\n-- \ngitgitgadget\n"},{"id":"457729","messageId":"5bf7244437e30ae0810e323016de892b82365b12.1655917628.git.gitgitgadget@gmail.com","threadId":"58039","inReplyTo":"pull.1278.v3.git.git.1655917628.gitgitgadget@gmail.com","subject":"[PATCH v3 1/2] grep: add --max-count command line option","fromName":"Carlos López via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-22T17:07:07Z","receivedAt":"2022-06-22T17:08:50Z","isPatch":true,"sender":{"key":"name:Carlos López","avatar":null},"body":"From: =?UTF-8?q?Carlos=20L=C3=B3pez?= <00xc@protonmail.com>\n\nThis patch adds a command line option analogous to that of GNU\ngrep(1)'s -m / --max-count, which users might already be used to.\nThis makes it possible to limit the amount of matches shown in the\noutput while keeping the functionality of other options such as -C\n(show code context) or -p (show containing function), which would be\ndifficult to do with a shell pipeline (e.g. head(1)).\n\nSigned-off-by: Carlos López 00xc@protonmail.com\n---\n Documentation/git-grep.txt | 9 +++++++++\n builtin/grep.c             | 9 +++++++++\n grep.c                     | 2 +-\n grep.h                     | 2 ++\n 4 files changed, 21 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex 3d393fbac1b..58d944bd578 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -23,6 +23,7 @@ SYNOPSIS\n \t   [--break] [--heading] [-p | --show-function]\n \t   [-A <post-context>] [-B <pre-context>] [-C <context>]\n \t   [-W | --function-context]\n+\t   [(-m | --max-count) <num>]\n \t   [--threads <num>]\n \t   [-f <file>] [-e] <pattern>\n \t   [--and|--or|--not|(|)|-e <pattern>...]\n@@ -238,6 +239,14 @@ providing this option will cause it to die.\n \t`git diff` works out patch hunk headers (see 'Defining a\n \tcustom hunk-header' in linkgit:gitattributes[5]).\n \n+-m <num>::\n+--max-count <num>::\n+\tLimit the amount of matches per file. When using the `-v` or\n+\t`--invert-match` option, the search stops after the specified\n+\tnumber of non-matches. A value of -1 will return unlimited\n+\tresults (the default). A value of 0 will exit immediately with\n+\ta non-zero status.\n+\n --threads <num>::\n \tNumber of grep worker threads to use.\n \tSee `grep.threads` in 'CONFIGURATION' for more information.\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex bcb07ea7f75..e6bcdf860cc 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -961,6 +961,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL_F(0, \"ext-grep\", &external_grep_allowed__ignored,\n \t\t\t   N_(\"allow calling of grep(1) (ignored by this build)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_INTEGER('m', \"max-count\", &opt.max_count,\n+\t\t\tN_(\"maximum number of results per file\")),\n \t\tOPT_END()\n \t};\n \tgrep_prefix = prefix;\n@@ -1101,6 +1103,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (recurse_submodules && untracked)\n \t\tdie(_(\"--untracked not supported with --recurse-submodules\"));\n \n+\t/*\n+\t * Optimize out the case where the amount of matches is limited to zero.\n+\t * We do this to keep results consistent with GNU grep(1).\n+\t */\n+\tif (opt.max_count == 0)\n+\t\treturn 1;\n+\n \tif (show_in_pager) {\n \t\tif (num_threads > 1)\n \t\t\twarning(_(\"invalid option combination, ignoring --threads\"));\ndiff --git a/grep.c b/grep.c\nindex 82eb7da1022..52a894c9890 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1615,7 +1615,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle\n \t\t\t\treturn 0;\n \t\t\tgoto next_line;\n \t\t}\n-\t\tif (hit) {\n+\t\tif (hit && (opt->max_count < 0 || count < opt->max_count)) {\n \t\t\tcount++;\n \t\t\tif (opt->status_only)\n \t\t\t\treturn 1;\ndiff --git a/grep.h b/grep.h\nindex c722d25ed9d..bdcadce61b8 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -171,6 +171,7 @@ struct grep_opt {\n \tint show_hunk_mark;\n \tint file_break;\n \tint heading;\n+\tint max_count;\n \tvoid *priv;\n \n \tvoid (*output)(struct grep_opt *opt, const void *data, size_t size);\n@@ -181,6 +182,7 @@ struct grep_opt {\n \t.relative = 1, \\\n \t.pathname = 1, \\\n \t.max_depth = -1, \\\n+\t.max_count = -1, \\\n \t.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \\\n \t.colors = { \\\n \t\t[GREP_COLOR_CONTEXT] = \"\", \\\n-- \ngitgitgadget\n\n"},{"id":"457734","messageId":"xmqq7d58603l.fsf@gitster.g","threadId":"58039","inReplyTo":"525958af877c3bbd024ffd3aee0c64e469c3d6cf.1655917628.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/2] tests: add tests for grep --max-count","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-06-22T18:10:06Z","receivedAt":"2022-06-22T18:10:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Carlos López via GitGitGadget\"  <gitgitgadget@gmail.com> writes:\n\n> From: =?UTF-8?q?Carlos=20L=C3=B3pez?= <00xc@protonmail.com>\n>\n> Add tests for grep's -m / --max-count to check if the option correctly\n> outputs limited results, and that it interacts properly with other flags\n> that could likely be used in conjunction.\n>\n> Signed-off-by: Carlos López 00xc@protonmail.com\n> ---\n>  t/t7810-grep.sh | 83 +++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 83 insertions(+)\n\nThis is better done as part of the previous patch.  The new tests\nprotect the new code from future breakage.\n\n> diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\n> index 69356011713..7b1b8a3cd93 100755\n> --- a/t/t7810-grep.sh\n> +++ b/t/t7810-grep.sh\n> @@ -77,6 +77,7 @@ test_expect_success setup '\n>  \t# Say hello.\n>  \tfunction hello() {\n>  \t  echo \"Hello world.\"\n> +\t  echo \"Hello again.\"\n>  \t} # hello\n>  \n>  \t# Still a no-op.\n> @@ -595,6 +596,88 @@ test_expect_success 'grep --files-without-match --quiet' '\n>  \ttest_must_be_empty actual\n>  '\n>  \n> +cat >expected <<EOF &&\n> +EOF\n> +\n> +test_expect_success 'grep --max-count 0 (must exit with non-zero)' '\n> +\ttest_must_fail git grep --max-count 0 foo >actual &&\n> +\ttest_cmp expected actual\n> +'\n\nFor this particular one, \"test_must_be_empty actual\" would suffice,\nwithout comparing with the expected output.\n\n> +cat >expected <<EOF &&\n> +file:foo mmap bar\n> +EOF\n> +\n> +test_expect_success 'grep --max-count 1' '\n> +\tgit grep --max-count 1 foo >actual &&\n> +\ttest_cmp expected actual\n> +'\n\nWriting expected output outside test_expect_success that uses it is\na quite old style but that is because this test script is pretty\nmuch ancient, so mimicking it is OK.  We'd need to come back later\nwhen the tree is quiescent to clean them up, though (#leftoverbits).\n\n> ...\n> +\ttest_cmp expected actual\n> +'\n\nThe new tests seem to give us a reasonable test coverage.  We could\ndiscard one of the \"-m1\" vs \"-m3\" in the early ones, as they do not\ngive much extra test coverage over the other, to reduce repetition.\n\nWe do not test a case where we pick up-to N matches each from\nmultiple files, though.  Perhaps\n\n    git grep -m1 -e o -- hello.\\*\n\nmay stop after hitting \"No-op.\" in hello.ps1 and \"stdio\" in hello.c,\nwhich may make a good test, perhaps?\n\nThanks.\n"},{"id":"457742","messageId":"pull.1278.v4.git.git.1655927252899.gitgitgadget@gmail.com","threadId":"58039","inReplyTo":"pull.1278.v3.git.git.1655917628.gitgitgadget@gmail.com","subject":"[PATCH v4] grep: add --max-count command line option","fromName":"Carlos L. via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-22T19:47:32Z","receivedAt":"2022-06-22T19:47:45Z","isPatch":true,"sender":{"key":"00xc@protonmail.com","avatar":"https://avatars.githubusercontent.com/u/48725664?v=4"},"body":"From: =?UTF-8?q?Carlos=20L=C3=B3pez?= <00xc@protonmail.com>\n\nThis patch adds a command line option analogous to that of GNU\ngrep(1)'s -m / --max-count, which users might already be used to.\nThis makes it possible to limit the amount of matches shown in the\noutput while keeping the functionality of other options such as -C\n(show code context) or -p (show containing function), which would be\ndifficult to do with a shell pipeline (e.g. head(1)).\n\nSigned-off-by: Carlos López 00xc@protonmail.com\n---\n    grep: add --max-count command line option\n    \n    This patch adds a command line option analogous to that of GNU grep(1)'s\n    -m / --max-count, which users might already be used to. This makes it\n    possible to limit the amount of matches shown in the output while\n    keeping the functionality of other options such as -C (show code\n    context) or -p (show containing function), which would be difficult to\n    do with a shell pipeline (e.g. head(1)).\n    \n    Signed-off-by: Carlos López 00xc@protonmail.com\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1278%2F00xc%2Fmaster-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1278/00xc/master-v4\nPull-Request: https://github.com/git/git/pull/1278\n\nRange-diff vs v3:\n\n 1:  5bf7244437e ! 1:  89c0151c164 grep: add --max-count command line option\n     @@ grep.h: struct grep_opt {\n       \t.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \\\n       \t.colors = { \\\n       \t\t[GREP_COLOR_CONTEXT] = \"\", \\\n     +\n     + ## t/t7810-grep.sh ##\n     +@@ t/t7810-grep.sh: test_expect_success setup '\n     + \t# Say hello.\n     + \tfunction hello() {\n     + \t  echo \"Hello world.\"\n     ++\t  echo \"Hello again.\"\n     + \t} # hello\n     + \n     + \t# Still a no-op.\n     +@@ t/t7810-grep.sh: test_expect_success 'grep --files-without-match --quiet' '\n     + \ttest_must_be_empty actual\n     + '\n     + \n     ++test_expect_success 'grep --max-count 0 (must exit with non-zero)' '\n     ++\ttest_must_fail git grep --max-count 0 foo >actual &&\n     ++\ttest_must_be_empty actual\n     ++'\n     ++\n     ++test_expect_success 'grep --max-count 3' '\n     ++\tcat >expected <<-EOF &&\n     ++\tfile:foo mmap bar\n     ++\tfile:foo_mmap bar\n     ++\tfile:foo_mmap bar mmap\n     ++\tEOF\n     ++\tgit grep --max-count 3 foo >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'grep --max-count -1 (no limit)' '\n     ++\tcat >expected <<-EOF &&\n     ++\tfile:foo mmap bar\n     ++\tfile:foo_mmap bar\n     ++\tfile:foo_mmap bar mmap\n     ++\tfile:foo mmap bar_mmap\n     ++\tfile:foo_mmap bar mmap baz\n     ++\tEOF\n     ++\tgit grep --max-count -1 foo >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'grep --max-count 1 --context 2' '\n     ++\tcat >expected <<-EOF &&\n     ++\tfile-foo mmap bar\n     ++\tfile:foo_mmap bar\n     ++\tfile-foo_mmap bar mmap\n     ++\tEOF\n     ++\tgit grep --max-count 1 --context 1 foo_mmap >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'grep --max-count 1 --show-function' '\n     ++\tcat >expected <<-EOF &&\n     ++\thello.ps1=function hello() {\n     ++\thello.ps1:  echo \"Hello world.\"\n     ++\tEOF\n     ++\tgit grep --max-count 1 --show-function Hello hello.ps1 >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'grep --max-count 2 --show-function' '\n     ++\tcat >expected <<-EOF &&\n     ++\thello.ps1=function hello() {\n     ++\thello.ps1:  echo \"Hello world.\"\n     ++\thello.ps1:  echo \"Hello again.\"\n     ++\tEOF\n     ++\tgit grep --max-count 2 --show-function Hello hello.ps1 >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'grep --max-count 1 --count' '\n     ++\tcat >expected <<-EOF &&\n     ++\thello.ps1:1\n     ++\tEOF\n     ++\tgit grep --max-count 1 --count Hello hello.ps1 >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'grep --max-count 1 (multiple files)' '\n     ++\tcat >expected <<-EOF &&\n     ++\thello.c:#include <stdio.h>\n     ++\thello.ps1:# No-op.\n     ++\tEOF\n     ++\tgit grep --max-count 1 -e o -- hello.\\* >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     ++test_expect_success 'grep --max-count 1 --context 1 (multiple files)' '\n     ++\tcat >expected <<-EOF &&\n     ++\thello.c-#include <assert.h>\n     ++\thello.c:#include <stdio.h>\n     ++\thello.c-\n     ++\t--\n     ++\thello.ps1:# No-op.\n     ++\thello.ps1-function dummy() {}\n     ++\tEOF\n     ++\tgit grep --max-count 1 --context 1 -e o -- hello.\\* >actual &&\n     ++\ttest_cmp expected actual\n     ++'\n     ++\n     + cat >expected <<EOF\n     + file:foo mmap bar_mmap\n     + EOF\n 2:  525958af877 < -:  ----------- tests: add tests for grep --max-count\n\n\n Documentation/git-grep.txt |  9 ++++\n builtin/grep.c             |  9 ++++\n grep.c                     |  2 +-\n grep.h                     |  2 +\n t/t7810-grep.sh            | 87 ++++++++++++++++++++++++++++++++++++++\n 5 files changed, 108 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt\nindex 3d393fbac1b..58d944bd578 100644\n--- a/Documentation/git-grep.txt\n+++ b/Documentation/git-grep.txt\n@@ -23,6 +23,7 @@ SYNOPSIS\n \t   [--break] [--heading] [-p | --show-function]\n \t   [-A <post-context>] [-B <pre-context>] [-C <context>]\n \t   [-W | --function-context]\n+\t   [(-m | --max-count) <num>]\n \t   [--threads <num>]\n \t   [-f <file>] [-e] <pattern>\n \t   [--and|--or|--not|(|)|-e <pattern>...]\n@@ -238,6 +239,14 @@ providing this option will cause it to die.\n \t`git diff` works out patch hunk headers (see 'Defining a\n \tcustom hunk-header' in linkgit:gitattributes[5]).\n \n+-m <num>::\n+--max-count <num>::\n+\tLimit the amount of matches per file. When using the `-v` or\n+\t`--invert-match` option, the search stops after the specified\n+\tnumber of non-matches. A value of -1 will return unlimited\n+\tresults (the default). A value of 0 will exit immediately with\n+\ta non-zero status.\n+\n --threads <num>::\n \tNumber of grep worker threads to use.\n \tSee `grep.threads` in 'CONFIGURATION' for more information.\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex bcb07ea7f75..e6bcdf860cc 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -961,6 +961,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL_F(0, \"ext-grep\", &external_grep_allowed__ignored,\n \t\t\t   N_(\"allow calling of grep(1) (ignored by this build)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n+\t\tOPT_INTEGER('m', \"max-count\", &opt.max_count,\n+\t\t\tN_(\"maximum number of results per file\")),\n \t\tOPT_END()\n \t};\n \tgrep_prefix = prefix;\n@@ -1101,6 +1103,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tif (recurse_submodules && untracked)\n \t\tdie(_(\"--untracked not supported with --recurse-submodules\"));\n \n+\t/*\n+\t * Optimize out the case where the amount of matches is limited to zero.\n+\t * We do this to keep results consistent with GNU grep(1).\n+\t */\n+\tif (opt.max_count == 0)\n+\t\treturn 1;\n+\n \tif (show_in_pager) {\n \t\tif (num_threads > 1)\n \t\t\twarning(_(\"invalid option combination, ignoring --threads\"));\ndiff --git a/grep.c b/grep.c\nindex 82eb7da1022..52a894c9890 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -1615,7 +1615,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle\n \t\t\t\treturn 0;\n \t\t\tgoto next_line;\n \t\t}\n-\t\tif (hit) {\n+\t\tif (hit && (opt->max_count < 0 || count < opt->max_count)) {\n \t\t\tcount++;\n \t\t\tif (opt->status_only)\n \t\t\t\treturn 1;\ndiff --git a/grep.h b/grep.h\nindex c722d25ed9d..bdcadce61b8 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -171,6 +171,7 @@ struct grep_opt {\n \tint show_hunk_mark;\n \tint file_break;\n \tint heading;\n+\tint max_count;\n \tvoid *priv;\n \n \tvoid (*output)(struct grep_opt *opt, const void *data, size_t size);\n@@ -181,6 +182,7 @@ struct grep_opt {\n \t.relative = 1, \\\n \t.pathname = 1, \\\n \t.max_depth = -1, \\\n+\t.max_count = -1, \\\n \t.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \\\n \t.colors = { \\\n \t\t[GREP_COLOR_CONTEXT] = \"\", \\\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex 69356011713..0f937990a06 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -77,6 +77,7 @@ test_expect_success setup '\n \t# Say hello.\n \tfunction hello() {\n \t  echo \"Hello world.\"\n+\t  echo \"Hello again.\"\n \t} # hello\n \n \t# Still a no-op.\n@@ -595,6 +596,92 @@ test_expect_success 'grep --files-without-match --quiet' '\n \ttest_must_be_empty actual\n '\n \n+test_expect_success 'grep --max-count 0 (must exit with non-zero)' '\n+\ttest_must_fail git grep --max-count 0 foo >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_expect_success 'grep --max-count 3' '\n+\tcat >expected <<-EOF &&\n+\tfile:foo mmap bar\n+\tfile:foo_mmap bar\n+\tfile:foo_mmap bar mmap\n+\tEOF\n+\tgit grep --max-count 3 foo >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --max-count -1 (no limit)' '\n+\tcat >expected <<-EOF &&\n+\tfile:foo mmap bar\n+\tfile:foo_mmap bar\n+\tfile:foo_mmap bar mmap\n+\tfile:foo mmap bar_mmap\n+\tfile:foo_mmap bar mmap baz\n+\tEOF\n+\tgit grep --max-count -1 foo >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --max-count 1 --context 2' '\n+\tcat >expected <<-EOF &&\n+\tfile-foo mmap bar\n+\tfile:foo_mmap bar\n+\tfile-foo_mmap bar mmap\n+\tEOF\n+\tgit grep --max-count 1 --context 1 foo_mmap >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --max-count 1 --show-function' '\n+\tcat >expected <<-EOF &&\n+\thello.ps1=function hello() {\n+\thello.ps1:  echo \"Hello world.\"\n+\tEOF\n+\tgit grep --max-count 1 --show-function Hello hello.ps1 >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --max-count 2 --show-function' '\n+\tcat >expected <<-EOF &&\n+\thello.ps1=function hello() {\n+\thello.ps1:  echo \"Hello world.\"\n+\thello.ps1:  echo \"Hello again.\"\n+\tEOF\n+\tgit grep --max-count 2 --show-function Hello hello.ps1 >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --max-count 1 --count' '\n+\tcat >expected <<-EOF &&\n+\thello.ps1:1\n+\tEOF\n+\tgit grep --max-count 1 --count Hello hello.ps1 >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --max-count 1 (multiple files)' '\n+\tcat >expected <<-EOF &&\n+\thello.c:#include <stdio.h>\n+\thello.ps1:# No-op.\n+\tEOF\n+\tgit grep --max-count 1 -e o -- hello.\\* >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'grep --max-count 1 --context 1 (multiple files)' '\n+\tcat >expected <<-EOF &&\n+\thello.c-#include <assert.h>\n+\thello.c:#include <stdio.h>\n+\thello.c-\n+\t--\n+\thello.ps1:# No-op.\n+\thello.ps1-function dummy() {}\n+\tEOF\n+\tgit grep --max-count 1 --context 1 -e o -- hello.\\* >actual &&\n+\ttest_cmp expected actual\n+'\n+\n cat >expected <<EOF\n file:foo mmap bar_mmap\n EOF\n\nbase-commit: 5b71c59bc3b9365075e2a175aa7b6f2b0c84ce44\n-- \ngitgitgadget\n"}]}