{"thread":{"id":"52771","subject":"[PATCH] advice: refactor advise API","startedAt":"2020-02-10T05:04:15Z","lastAt":"2020-03-04T03:22:51Z","messageCount":76,"participants":["Heba Waly via GitGitGadget","Derrick Stolee","Junio C Hamano","Taylor Blau","Jeff King","Emily Shaffer","Heba Waly","Eric Sunshine","Jonathan Tan"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"391439","messageId":"pull.548.git.1581311049547.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":null,"subject":"[PATCH] advice: refactor advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-10T05:04:09Z","receivedAt":"2020-02-10T05:04:15Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nAdd a new advise_ng function that can check the visibility of advice\nmessages before printing.\n\nCurrently it's very easy for the callers to miss checking the\nvisibility step. Also, it makes more sense for this step to be handled\nby the advice library.\n\nAlso change the advise call in tag library from advise() to advise_ng()\nto construct an example of the usage of the new API.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n    [RFC][Outreachy] advice: refactor advise API\n    \n    The advice API is currently a little bit confusing to call. quoting from\n    [1]:\n    \n    When introducing a new advice message, you would\n    \n     * come up with advice.frotz configuration variable\n    \n     * define and declare advice_frotz global variable that defaults to\n       true\n    \n     * sprinkle calls like this:\n    \n      if (advice_frotz)\n        advise(_(\"helpful message about frotz\"));\n    \n    A new approach was suggested in [1] which this patch is based upon.\n    \n    A new advise_ng() is introduced to gradually replace advise()\n    \n    pros of the new advise():\n    \n     * The caller doesn't need to define a new global variable when\n       introducing a new message.\n     * The caller doesn't need to check the visibility of the message before\n       calling advise_ng().\n     * The caller still needs to come up with advice.frotz config variable\n       and will call advice_ng as follows: advice_ng(\"advice.frotz\",\n       _(\"helpful message about frotz\"));\n    \n    After this patch the plan is to migrate the rest of the advise calls to\n    advise_ng and then finally remove advise() and rename advise_ng() to\n    advise()\n    \n    [1] \n    https://public-inbox.org/git/xmqqzhf5cw69.fsf@gitster-ct.c.googlers.com/\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-548%2FHebaWaly%2Fadvice_refactoring-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-548/HebaWaly/advice_refactoring-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/548\n\n Makefile               |  1 +\n advice.c               | 37 +++++++++++++++++++++++++++++++++++--\n advice.h               |  6 +++++-\n builtin/tag.c          |  4 ++--\n t/helper/test-advise.c | 15 +++++++++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 29 +++++++++++++++++++++++++++++\n t/t7004-tag.sh         |  2 ++\n 9 files changed, 91 insertions(+), 5 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\ndiff --git a/Makefile b/Makefile\nindex 09f98b777c..ed923a3e81 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -695,6 +695,7 @@ X =\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n+TEST_BUILTINS_OBJS += test-advise.o\n TEST_BUILTINS_OBJS += test-chmtime.o\n TEST_BUILTINS_OBJS += test-config.o\n TEST_BUILTINS_OBJS += test-ctype.o\ndiff --git a/advice.c b/advice.c\nindex 249c60dcf3..4793e59223 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -29,7 +29,6 @@ int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n int advice_graft_file_deprecated = 1;\n int advice_checkout_ambiguous_remote_branch_name = 1;\n-int advice_nested_tag = 1;\n int advice_submodule_alternate_error_strategy_die = 1;\n \n static int advice_use_color = -1;\n@@ -89,7 +88,6 @@ static struct {\n \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n \t{ \"checkoutAmbiguousRemoteBranchName\", &advice_checkout_ambiguous_remote_branch_name },\n-\t{ \"nestedTag\", &advice_nested_tag },\n \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n \n \t/* make this an alias for backward compatibility */\n@@ -118,6 +116,41 @@ void advise(const char *advice, ...)\n \tstrbuf_release(&buf);\n }\n \n+static const char turn_off_instructions[] =\n+N_(\"\\n\"\n+\"Turn this message off by running\\n\"\n+\"\\\"git config %s false\\\"\");\n+\n+void advise_ng(const char *key, const char *advice, ...)\n+{\n+\tint value = 1;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tva_list params;\n+\tconst char *cp, *np;\n+\t\n+\tgit_config_get_bool(key, &value);\n+\t\n+\tif(value)\n+\t{\n+\t\tva_start(params, advice);\n+\t\tstrbuf_vaddf(&buf, advice, params);\n+\t\tva_end(params);\n+\n+\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n+\t\t\n+\t\tfor (cp = buf.buf; *cp; cp = np) {\n+\t\t\tnp = strchrnul(cp, '\\n');\n+\t\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n+\t\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n+\t\t\t\t(int)(np - cp), cp,\n+\t\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n+\t\t\tif (*np)\n+\t\t\t\tnp++;\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+}\n+\n int git_default_advice_config(const char *var, const char *value)\n {\n \tconst char *k, *slot_name;\ndiff --git a/advice.h b/advice.h\nindex b706780614..ad4da2d65d 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -29,12 +29,16 @@ extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n extern int advice_graft_file_deprecated;\n extern int advice_checkout_ambiguous_remote_branch_name;\n-extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\n void advise(const char *advice, ...);\n+\n+/**\n+ Checks the visibility of the advice before priniting.\n+ */\n+void advise_ng(const char *key, const char *advice, ...);\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex e0a4c25382..7fe1ff3ed0 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -231,8 +231,8 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \tif (type <= OBJ_NONE)\n \t\tdie(_(\"bad object type.\"));\n \n-\tif (type == OBJ_TAG && advice_nested_tag)\n-\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n+\tif (type == OBJ_TAG)\n+\t\tadvise_ng(\"advice.nestedTag\", _(message_advice_nested_tag), tag, object_ref);\n \n \tstrbuf_addf(&header,\n \t\t    \"object %s\\n\"\ndiff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\nnew file mode 100644\nindex 0000000000..b6ec90fd18\n--- /dev/null\n+++ b/t/helper/test-advise.c\n@@ -0,0 +1,15 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"advice.h\"\n+\n+int cmd__advise_ng(int argc, const char **argv)\n+{\n+\tif (!argv[1] || !argv[2])\n+\tdie(\"usage: %s <key> <advice>\", argv[0]);\n+\n+\tsetup_git_directory();\n+\n+\tadvise_ng(argv[1], argv[2]);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d449..f903f32bb6 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -14,6 +14,7 @@ struct test_cmd {\n };\n \n static struct test_cmd cmds[] = {\n+\t{ \"advise\", cmd__advise_ng },\n \t{ \"chmtime\", cmd__chmtime },\n \t{ \"config\", cmd__config },\n \t{ \"ctype\", cmd__ctype },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1..e5e955beb9 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -4,6 +4,7 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"git-compat-util.h\"\n \n+int cmd__advise_ng(int argc, const char **argv);\n int cmd__chmtime(int argc, const char **argv);\n int cmd__config(int argc, const char **argv);\n int cmd__ctype(int argc, const char **argv);\ndiff --git a/t/t0018-advice.sh b/t/t0018-advice.sh\nnew file mode 100755\nindex 0000000000..d012db515d\n--- /dev/null\n+++ b/t/t0018-advice.sh\n@@ -0,0 +1,29 @@\n+#!/bin/sh\n+\n+test_description='Test advise_ng functionality'\n+\n+. ./test-lib.sh\n+\n+cat > expected <<EOF\n+hint: This is a piece of advice\n+hint: Turn this message off by running\n+hint: \"git config advice.configVariable false\"\n+EOF\n+test_expect_success 'advise should be printed when config variable is unset' '\n+\ttest-tool advise \"advice.configVariable\" \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expected actual\n+'\n+\n+test_expect_success 'advise should be printed when config variable is set to true' '\n+\ttest_config advice.configVariable true &&\n+\ttest-tool advise \"advice.configVariable\" \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expected actual\n+'\n+\n+test_expect_success 'advise should not be printed when config variable is set to false' '\n+\ttest_config advice.configVariable false &&\n+\ttest-tool advise \"advice.configVariable\" \"This is a piece of advice\" 2>actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_done\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 6db92bd3ba..b7c8d41899 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1726,6 +1726,8 @@ test_expect_success 'recursive tagging should give advice' '\n \thint: already a tag. If you meant to tag the object that it points to, use:\n \thint: |\n \thint: \tgit tag -f nested annotated-v4.0^{}\n+\thint: Turn this message off by running\n+\thint: \"git config advice.nestedTag false\"\n \tEOF\n \tgit tag -m nested nested annotated-v4.0 2>actual &&\n \ttest_i18ncmp expect actual\n\nbase-commit: c7a62075917b3340f908093f63f1161c44ed1475\n-- \ngitgitgadget\n"},{"id":"391452","messageId":"97406f9e-b8ef-b5b9-3987-cdef843b31a5@gmail.com","threadId":"52771","inReplyTo":"pull.548.git.1581311049547.gitgitgadget@gmail.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-02-10T14:38:51Z","receivedAt":"2020-02-10T14:38:56Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/10/2020 12:04 AM, Heba Waly via GitGitGadget wrote:\n> From: Heba Waly <heba.waly@gmail.com>\n> \n> Add a new advise_ng function that can check the visibility of advice\n> messages before printing.\n>\n> Currently it's very easy for the callers to miss checking the\n> visibility step. Also, it makes more sense for this step to be handled\n> by the advice library.\n\nThis makes the advice API much easier and its uses much cleaner. Thanks!\n \n> Also change the advise call in tag library from advise() to advise_ng()\n> to construct an example of the usage of the new API.\n\nThis is a good example case.\n\n> +static const char turn_off_instructions[] =\n> +N_(\"\\n\"\n> +\"Turn this message off by running\\n\"\n> +\"\\\"git config %s false\\\"\");\n\nI have mixed feelings on the use of these instructions. Perhaps at\nminimum the addition of these instructions could be left to a\nseparate patch than the creation of advise_ng().\n\nMy biggest concern is that this adds unexpected noise to users who\nwant the advice to stay. I'm calling attention to it, because this\npart isn't a simple refactor like the rest of the patch.\n\nIf it _does_ stay, then I recommend condensing the message to\na single line. For example:\n\n\tDisable this message with \"git config %d false\"\n\n> +void advise_ng(const char *key, const char *advice, ...)\n> +{\n> +\tint value = 1;\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tva_list params;\n> +\tconst char *cp, *np;\n> +\t\n> +\tgit_config_get_bool(key, &value);\n> +\t\n> +\tif(value)\n> +\t{\n\nStyle: spacing, and opening braces are on the same line as the if:\n\n\tif (value) {\n\nBut also, this method would be simpler if the opposite case was\nan early return:\n\n\tif (!value)\n\t\treturn;\n\nThen the rest could have one less indentation.\n\n> +\t\tva_start(params, advice);\n> +\t\tstrbuf_vaddf(&buf, advice, params);\n> +\t\tva_end(params);\n> +\n> +\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n> +\n> +\t\tfor (cp = buf.buf; *cp; cp = np) {\n> +\t\t\tnp = strchrnul(cp, '\\n');\n> +\t\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n> +\t\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n> +\t\t\t\t(int)(np - cp), cp,\n> +\t\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n> +\t\t\tif (*np)\n> +\t\t\t\tnp++;\n> +\t\t}\n> +\t\tstrbuf_release(&buf);\n\nThis loop looks like it was copied from advise(). Perhaps we could\nre-use that code better by creating a new vadvise() method that\ntakes a va_list, and have advise() and advise_ng() call it instead?\nI include a patch at the end of this method that does this conversion.\n(Feel free to incorporate it into your next version, if you want, but\nbe sure to add your sign-off.) Then, your advise_ng() can call these:\n\n\tvadvise(advice, params);\n\tadvise(turn_off_instructions, key);\n\nremoving the need to re-implement the for loop.\n\n> diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\n> new file mode 100644\n> index 0000000000..b6ec90fd18\n> --- /dev/null\n> +++ b/t/helper/test-advise.c\n> @@ -0,0 +1,15 @@\n> +#include \"test-tool.h\"\n> +#include \"cache.h\"\n> +#include \"advice.h\"\n> +\n> +int cmd__advise_ng(int argc, const char **argv)\n> +{\n> +\tif (!argv[1] || !argv[2])\n> +\tdie(\"usage: %s <key> <advice>\", argv[0]);\n> +\n> +\tsetup_git_directory();\n> +\n> +\tadvise_ng(argv[1], argv[2]);\n> +\n> +\treturn 0;\n> +}\n\nI definitely tend to recommend more tests than most, but perhaps this\nunit test is overkill? You demonstrate a good test below using a real\nGit command, which should be sufficient. If the \"turn this message off\"\npart gets removed, then you will still have coverage of your method.\nIt just won't require a test change because it would not modify behavior.\n\n> diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n> index 6db92bd3ba..b7c8d41899 100755\n> --- a/t/t7004-tag.sh\n> +++ b/t/t7004-tag.sh\n> @@ -1726,6 +1726,8 @@ test_expect_success 'recursive tagging should give advice' '\n>  \thint: already a tag. If you meant to tag the object that it points to, use:\n>  \thint: |\n>  \thint: \tgit tag -f nested annotated-v4.0^{}\n> +\thint: Turn this message off by running\n> +\thint: \"git config advice.nestedTag false\"\n>  \tEOF\n>  \tgit tag -m nested nested annotated-v4.0 2>actual &&\n>  \ttest_i18ncmp expect actual\n> \n> base-commit: c7a62075917b3340f908093f63f1161c44ed1475\n\nThanks,\n-Stolee\n\n-->8--\n\nFrom: Derrick Stolee <dstolee@microsoft.com>\nDate: Mon, 10 Feb 2020 09:33:20 -0500\nSubject: [PATCH] advice: extract vadvise() from advise()\n\nIn preparation for a new advice method, extract a version of advise()\nthat uses an explict 'va_list' parameter. Call it from advise() for a\nfunctionally equivalent version.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n advice.c | 13 +++++++++----\n 1 file changed, 9 insertions(+), 4 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 249c60dcf3..fd836332da 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -96,15 +96,12 @@ static struct {\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n-void advise(const char *advice, ...)\n+static void vadvise(const char *advice, va_list params)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tva_list params;\n \tconst char *cp, *np;\n \n-\tva_start(params, advice);\n \tstrbuf_vaddf(&buf, advice, params);\n-\tva_end(params);\n \n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n@@ -118,6 +115,14 @@ void advise(const char *advice, ...)\n \tstrbuf_release(&buf);\n }\n \n+void advise(const char *advice, ...)\n+{\n+\tva_list params;\n+\tva_start(params, advice);\n+\tvadvise(advice, params);\n+\tva_end(params);\n+}\n+\n int git_default_advice_config(const char *var, const char *value)\n {\n \tconst char *k, *slot_name;\n-- \n2.25.0.vfs.1.1.1.g9906319d24.dirty\n\n\n\n"},{"id":"391485","messageId":"xmqq8slaz1cp.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"97406f9e-b8ef-b5b9-3987-cdef843b31a5@gmail.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-10T19:30:46Z","receivedAt":"2020-02-10T19:30:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <stolee@gmail.com> writes:\n\n>> +static const char turn_off_instructions[] =\n>> +N_(\"\\n\"\n>> +\"Turn this message off by running\\n\"\n>> +\"\\\"git config %s false\\\"\");\n>\n> I have mixed feelings on the use of these instructions. Perhaps at\n> minimum the addition of these instructions could be left to a\n> separate patch than the creation of advise_ng().\n>\n> My biggest concern is that this adds unexpected noise to users who\n> want the advice to stay. I'm calling attention to it, because this\n> part isn't a simple refactor like the rest of the patch.\n> ...\n> I definitely tend to recommend more tests than most, but perhaps this\n> unit test is overkill? You demonstrate a good test below using a real\n> Git command, which should be sufficient. If the \"turn this message off\"\n> part gets removed, then you will still have coverage of your method.\n> It just won't require a test change because it would not modify behavior.\n> ...\n\nAll good suggestions.  Thanks for an excellent review.\n\nAnother thing.  \n\nadvise_ng() may have been a good name for illustration but is a\nhorrible name for real-world use (imagine we need to revamp the API\none more time in the future---what would it be called, which has to\nsay that it is newer than the \"next generation\"?\nadvise_3rd_try()?).\n\nThanks.\n"},{"id":"391486","messageId":"20200210194253.GA46461@syl.local","threadId":"52771","inReplyTo":"xmqq8slaz1cp.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2020-02-10T19:42:53Z","receivedAt":"2020-02-10T19:42:57Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Mon, Feb 10, 2020 at 11:30:46AM -0800, Junio C Hamano wrote:\n> Derrick Stolee <stolee@gmail.com> writes:\n>\n> >> +static const char turn_off_instructions[] =\n> >> +N_(\"\\n\"\n> >> +\"Turn this message off by running\\n\"\n> >> +\"\\\"git config %s false\\\"\");\n> >\n> > I have mixed feelings on the use of these instructions. Perhaps at\n> > minimum the addition of these instructions could be left to a\n> > separate patch than the creation of advise_ng().\n> >\n> > My biggest concern is that this adds unexpected noise to users who\n> > want the advice to stay. I'm calling attention to it, because this\n> > part isn't a simple refactor like the rest of the patch.\n> > ...\n> > I definitely tend to recommend more tests than most, but perhaps this\n> > unit test is overkill? You demonstrate a good test below using a real\n> > Git command, which should be sufficient. If the \"turn this message off\"\n> > part gets removed, then you will still have coverage of your method.\n> > It just won't require a test change because it would not modify behavior.\n> > ...\n>\n> All good suggestions.  Thanks for an excellent review.\n>\n> Another thing.\n>\n> advise_ng() may have been a good name for illustration but is a\n> horrible name for real-world use (imagine we need to revamp the API\n> one more time in the future---what would it be called, which has to\n> say that it is newer than the \"next generation\"?\n> advise_3rd_try()?).\n\nWhat about calling this new API 'advise()'? The first patch could call\nit 'advise_ng' or whatever other temporary name we feel comfortable\nusing, and then each subsequent patch would update callers of 'advise()'\nto use 'advise_ng()'. Once those patches have been applied, and no other\ncallers of 'advise()' exist, a final patch can be applied on top to\nrename 'advise_ng()' to 'advise()', and update the names of all of the\ncallers.\n\nThis makes for a rather noisy final patch, but the intermediate states\nare much clearer, and it would make this series rather self-contained.\n\nOn the other hand, having a version of 'advise_ng()' on master makes\nthis topic more incremental, meaning that we can pick it up and put it\ndown at ease and have more self-contained projects.\n\nI don't really have a preference between the two approaches, but if we\ngo with the latter, I do think we need something better than\n'advise_ng'. Maybe 'advise_warn'? I don't know.\n\n> Thanks.\n\nThanks,\nTaylor\n"},{"id":"391489","messageId":"20200210203725.GA620581@coredump.intra.peff.net","threadId":"52771","inReplyTo":"pull.548.git.1581311049547.gitgitgadget@gmail.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-02-10T20:37:25Z","receivedAt":"2020-02-10T20:37:28Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Feb 10, 2020 at 05:04:09AM +0000, Heba Waly via GitGitGadget wrote:\n\n>     The advice API is currently a little bit confusing to call. quoting from\n>     [1]:\n>     \n>     When introducing a new advice message, you would\n>     \n>      * come up with advice.frotz configuration variable\n>     \n>      * define and declare advice_frotz global variable that defaults to\n>        true\n>     \n>      * sprinkle calls like this:\n>     \n>       if (advice_frotz)\n>         advise(_(\"helpful message about frotz\"));\n>     \n>     A new approach was suggested in [1] which this patch is based upon.\n\nI agree that the current procedure is a bit painful, and I think this is\na step in the right direction. But...\n\n>     After this patch the plan is to migrate the rest of the advise calls to\n>     advise_ng and then finally remove advise() and rename advise_ng() to\n>     advise()\n\n...this step may not be possible, for a few reasons:\n\n  1. Some of the sites do more than just advise(). E.g., branch.c checks\n     the flag and calls both error() and advise().\n\n  2. Some callers may have to do work to generate the arguments. If I\n     have:\n\n       advise(\"advice.foo\", \"some data: %s\", generate_data());\n\n     then we'll call generate_data() even if we'll throw away the result\n     in the end.\n\nSimilarly, some users of advice_* variables do not call advise() at all\n(some call die(), some like builtin/rm.c stuff the result in a strbuf,\nand I don't even know what's going on with wt_status.hints. :)\n\nSo I think you may need to phase it in a bit more, like:\n\n  a. introduce want_advice() which decides whether or not to show the\n     advice based on a config key. I'd also suggest making the \"advice.\"\n     part of the key implicit, just to make life easier for the callers.\n\n  b. introduce advise_key() which uses want_advice() and advise() under\n     the hood to do what your advise_ng() is doing here.\n\n  c. convert simple patterns of:\n\n       if (advice_foo)\n          advise(\"bar\");\n\n     into:\n\n       advise_key(\"foo\", \"bar\");\n\n     and drop advice_foo where possible.\n\n  d. handle more complex cases one-by-one. For example, with something\n     like:\n\n       if (advice_foo)\n         die(\"bar\");\n\n     we probably want:\n\n       if (want_advice(\"foo\"))\n         die(\"bar\");\n\n     instead. Using string literals is more accident-prone than\n     variables (because the compiler doesn't notice if we misspell them)\n     but I think is OK for cases where we just refer to the key once.\n     For others (e.g., advice_commit_before_merge has 13 mentions),\n     either keep the variable. Or alternatively make a wrapper like:\n\n       int want_advice_commit_before_merge(void)\n       {\n               return want_advice(\"commitbeforemerge\");\n       }\n\n     if we want to drop the existing mechanism to load all of the\n     variables at the beginning.\n\n-Peff\n"},{"id":"391507","messageId":"20200210222932.GC190927@google.com","threadId":"52771","inReplyTo":"20200210194253.GA46461@syl.local","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-02-10T22:29:32Z","receivedAt":"2020-02-10T22:29:40Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Mon, Feb 10, 2020 at 11:42:53AM -0800, Taylor Blau wrote:\n> On Mon, Feb 10, 2020 at 11:30:46AM -0800, Junio C Hamano wrote:\n> > Derrick Stolee <stolee@gmail.com> writes:\n> >\n> > >> +static const char turn_off_instructions[] =\n> > >> +N_(\"\\n\"\n> > >> +\"Turn this message off by running\\n\"\n> > >> +\"\\\"git config %s false\\\"\");\n> > >\n> > > I have mixed feelings on the use of these instructions. Perhaps at\n> > > minimum the addition of these instructions could be left to a\n> > > separate patch than the creation of advise_ng().\n> > >\n> > > My biggest concern is that this adds unexpected noise to users who\n> > > want the advice to stay. I'm calling attention to it, because this\n> > > part isn't a simple refactor like the rest of the patch.\n> > > ...\n> > > I definitely tend to recommend more tests than most, but perhaps this\n> > > unit test is overkill? You demonstrate a good test below using a real\n> > > Git command, which should be sufficient. If the \"turn this message off\"\n> > > part gets removed, then you will still have coverage of your method.\n> > > It just won't require a test change because it would not modify behavior.\n> > > ...\n> >\n> > All good suggestions.  Thanks for an excellent review.\n> >\n> > Another thing.\n> >\n> > advise_ng() may have been a good name for illustration but is a\n> > horrible name for real-world use (imagine we need to revamp the API\n> > one more time in the future---what would it be called, which has to\n> > say that it is newer than the \"next generation\"?\n> > advise_3rd_try()?).\n> \n> What about calling this new API 'advise()'? The first patch could call\n> it 'advise_ng' or whatever other temporary name we feel comfortable\n> using, and then each subsequent patch would update callers of 'advise()'\n> to use 'advise_ng()'. Once those patches have been applied, and no other\n> callers of 'advise()' exist, a final patch can be applied on top to\n> rename 'advise_ng()' to 'advise()', and update the names of all of the\n> callers.\n\nI think this is the precise strategy called out in the patch\ndescription.\nhttps://lore.kernel.org/git/pull.548.git.1581311049547.gitgitgadget@gmail.com\n\n> This makes for a rather noisy final patch, but the intermediate states\n> are much clearer, and it would make this series rather self-contained.\n> \n> On the other hand, having a version of 'advise_ng()' on master makes\n> this topic more incremental, meaning that we can pick it up and put it\n> down at ease and have more self-contained projects.\n> \n> I don't really have a preference between the two approaches, but if we\n> go with the latter, I do think we need something better than\n> 'advise_ng'. Maybe 'advise_warn'? I don't know.\n\nI like that this opens up the possibility of advise_err(), advise_die(),\nwhatever to meet Peff's suggestion.\n\n - Emily\n"},{"id":"391510","messageId":"xmqqeev2xdq8.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"pull.548.git.1581311049547.gitgitgadget@gmail.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-10T22:46:23Z","receivedAt":"2020-02-10T22:46:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>     A new advise_ng() is introduced to gradually replace advise()\n>     \n>     pros of the new advise():\n>     \n>      * The caller doesn't need to define a new global variable when\n>        introducing a new message.\n>      * The caller doesn't need to check the visibility of the message before\n>        calling advise_ng().\n>      * The caller still needs to come up with advice.frotz config variable\n>        and will call advice_ng as follows: advice_ng(\"advice.frotz\",\n>        _(\"helpful message about frotz\"));\n\nReaders would expect to see \"cons of the same\" to follow \"pros\".\n\n>     After this patch the plan is to migrate the rest of the advise calls to\n>     advise_ng and then finally remove advise() and rename advise_ng() to\n>     advise()\n\nAs I outlined in [1], I think the over-simplified\n\"advise_ng(<advise.key>, _(<message>), ...)\"  would be too limited\nto replace the current users, without a pair of helper functions,\none to just check for the guarding advise.key, and the other to\nunconditionally show the message (i.e. the latter is what the\ncurrent advise() is).\n\n>     [1] \n>     https://public-inbox.org/git/xmqqzhf5cw69.fsf@gitster-ct.c.googlers.com/\n"},{"id":"391511","messageId":"20200210225528.GD190927@google.com","threadId":"52771","inReplyTo":"20200210203725.GA620581@coredump.intra.peff.net","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-02-10T22:55:28Z","receivedAt":"2020-02-10T22:55:37Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Mon, Feb 10, 2020 at 03:37:25PM -0500, Jeff King wrote:\n> On Mon, Feb 10, 2020 at 05:04:09AM +0000, Heba Waly via GitGitGadget wrote:\n> \n> >     The advice API is currently a little bit confusing to call. quoting from\n> >     [1]:\n> >     \n> >     When introducing a new advice message, you would\n> >     \n> >      * come up with advice.frotz configuration variable\n> >     \n> >      * define and declare advice_frotz global variable that defaults to\n> >        true\n> >     \n> >      * sprinkle calls like this:\n> >     \n> >       if (advice_frotz)\n> >         advise(_(\"helpful message about frotz\"));\n> >     \n> >     A new approach was suggested in [1] which this patch is based upon.\n> \n> I agree that the current procedure is a bit painful, and I think this is\n> a step in the right direction. But...\n> \n> >     After this patch the plan is to migrate the rest of the advise calls to\n> >     advise_ng and then finally remove advise() and rename advise_ng() to\n> >     advise()\n> \n> ...this step may not be possible, for a few reasons:\n> \n>   1. Some of the sites do more than just advise(). E.g., branch.c checks\n>      the flag and calls both error() and advise().\n> \n>   2. Some callers may have to do work to generate the arguments. If I\n>      have:\n> \n>        advise(\"advice.foo\", \"some data: %s\", generate_data());\n> \n>      then we'll call generate_data() even if we'll throw away the result\n>      in the end.\n> \n> Similarly, some users of advice_* variables do not call advise() at all\n> (some call die(), some like builtin/rm.c stuff the result in a strbuf,\n> and I don't even know what's going on with wt_status.hints. :)\n> \n> So I think you may need to phase it in a bit more, like:\n> \n>   a. introduce want_advice() which decides whether or not to show the\n>      advice based on a config key. I'd also suggest making the \"advice.\"\n>      part of the key implicit, just to make life easier for the callers.\n> \n>   b. introduce advise_key() which uses want_advice() and advise() under\n>      the hood to do what your advise_ng() is doing here.\n> \n>   c. convert simple patterns of:\n> \n>        if (advice_foo)\n>           advise(\"bar\");\n> \n>      into:\n> \n>        advise_key(\"foo\", \"bar\");\n> \n>      and drop advice_foo where possible.\n> \n>   d. handle more complex cases one-by-one. For example, with something\n>      like:\n> \n>        if (advice_foo)\n>          die(\"bar\");\n> \n>      we probably want:\n> \n>        if (want_advice(\"foo\"))\n>          die(\"bar\");\n> \n>      instead. Using string literals is more accident-prone than\n>      variables (because the compiler doesn't notice if we misspell them)\n>      but I think is OK for cases where we just refer to the key once.\n>      For others (e.g., advice_commit_before_merge has 13 mentions),\n>      either keep the variable. Or alternatively make a wrapper like:\n> \n>        int want_advice_commit_before_merge(void)\n>        {\n>                return want_advice(\"commitbeforemerge\");\n>        }\n> \n>      if we want to drop the existing mechanism to load all of the\n>      variables at the beginning.\n\nI tend to disagree on both counts. I'd personally rather see something\nlike 'void advise_key(enum advice, char *format, ...)'.\n\nAs I understand it, Heba wanted to avoid that pattern so that people\nadding a new advice didn't need to modify the advice library. However, I\nthink there's value to having a centralized list of all possible advices\n(besides the documentation). The per-advice wrapper is harder to iterate\nthan an enum, and will also result in a lot of samey code if we decide\nwe want to use that pattern for more advices.\n\n(In fact, with git-bugreport I'm running into a lot of regret that hooks\nare invoked in the way Peff describes - 'find_hook(\"pre-commit\")' -\nrather than with an enum naming the hook; it's very hard to check all\npossible hooks, and hard to examine the codebase and determine which\nhooks do and don't exist.)\n\nWhen Heba began to describe this project I had hoped for a final product\nlike 'void show_advice(enum advice_config)' which looked up the\nappropriate string from the advice library instead of asking the caller\nto provide it, although seeing the need for varargs has demonstrated to\nme that that's not feasible :) But I think taking the advice config key\nas an argument is possibly too far the other direction. At that point,\nit starts to beg the question, \"why isn't this function in config.h and\ncalled print_if_configured(cfgname, message, ...)?\"\n\nAlthough, take this all with a grain of salt. I think I lean towards\nthis much encapsulation after a sordid history with C++ and an\nenlightened C developer may not choose it ;)\n\n - Emily\n"},{"id":"391513","messageId":"CACg5j27SyHsc0soh_MUJHtAowjGxQ5e5ZBoXsDKfXCV2OMUpTQ@mail.gmail.com","threadId":"52771","inReplyTo":"97406f9e-b8ef-b5b9-3987-cdef843b31a5@gmail.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-10T23:56:46Z","receivedAt":"2020-02-10T23:57:01Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Tue, Feb 11, 2020 at 3:38 AM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 2/10/2020 12:04 AM, Heba Waly via GitGitGadget wrote:\n> > From: Heba Waly <heba.waly@gmail.com>\n> >\n> > Add a new advise_ng function that can check the visibility of advice\n> > messages before printing.\n> >\n> > Currently it's very easy for the callers to miss checking the\n> > visibility step. Also, it makes more sense for this step to be handled\n> > by the advice library.\n>\n> This makes the advice API much easier and its uses much cleaner. Thanks!\n>\n> > Also change the advise call in tag library from advise() to advise_ng()\n> > to construct an example of the usage of the new API.\n>\n> This is a good example case.\n>\n> > +static const char turn_off_instructions[] =\n> > +N_(\"\\n\"\n> > +\"Turn this message off by running\\n\"\n> > +\"\\\"git config %s false\\\"\");\n>\n> I have mixed feelings on the use of these instructions. Perhaps at\n> minimum the addition of these instructions could be left to a\n> separate patch than the creation of advise_ng().\n>\n> My biggest concern is that this adds unexpected noise to users who\n> want the advice to stay. I'm calling attention to it, because this\n> part isn't a simple refactor like the rest of the patch.\n>\n> If it _does_ stay, then I recommend condensing the message to\n> a single line. For example:\n>\n>         Disable this message with \"git config %d false\"\n>\n\nI agree with you, I had mixed feelings about it too when suggested on\na previous patch [2].\nBut then I realized that it's hard for the user to find the right\nconfig variable to turn off from the doc only.\nSo I like the compromise of condensing it to a single line.\n\n> > +     if(value)\n> > +     {\n>\n> Style: spacing, and opening braces are on the same line as the if:\n>\n>         if (value) {\n>\n> But also, this method would be simpler if the opposite case was\n> an early return:\n>\n>         if (!value)\n>                 return;\n> Then the rest could have one less indentation.\n\nAgree\n\n> This loop looks like it was copied from advise(). Perhaps we could\n> re-use that code better by creating a new vadvise() method that\n> takes a va_list, and have advise() and advise_ng() call it instead?\n> I include a patch at the end of this method that does this conversion.\n> (Feel free to incorporate it into your next version, if you want, but\n> be sure to add your sign-off.) Then, your advise_ng() can call these:\n>\n>         vadvise(advice, params);\n>         advise(turn_off_instructions, key);\n>\n> removing the need to re-implement the for loop.\n\nMy intention was to replace advise() by advise_ng(), so I didn't mind\na temp code repetition during the transition phase.\nBut as it seems like some folks would rather keep both, then yes of\ncourse a vadvise() function is the way to go, thanks.\n\n> > diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\n> > new file mode 100644\n> > index 0000000000..b6ec90fd18\n> > --- /dev/null\n> > +++ b/t/helper/test-advise.c\n> > @@ -0,0 +1,15 @@\n> > +#include \"test-tool.h\"\n> > +#include \"cache.h\"\n> > +#include \"advice.h\"\n> > +\n> > +int cmd__advise_ng(int argc, const char **argv)\n> > +{\n> > +     if (!argv[1] || !argv[2])\n> > +     die(\"usage: %s <key> <advice>\", argv[0]);\n> > +\n> > +     setup_git_directory();\n> > +\n> > +     advise_ng(argv[1], argv[2]);\n> > +\n> > +     return 0;\n> > +}\n>\n> I definitely tend to recommend more tests than most, but perhaps this\n> unit test is overkill? You demonstrate a good test below using a real\n> Git command, which should be sufficient. If the \"turn this message off\"\n> part gets removed, then you will still have coverage of your method.\n> It just won't require a test change because it would not modify behavior.\n>\n\nI see your point but I wanted to make sure advise_ng honors the config\nvariable using tests 2 & 3 in `t0018-advice.sh`\nand `t7004-tag.sh` didn't seem like a good place to add these tests.\n\n> > diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n> > index 6db92bd3ba..b7c8d41899 100755\n> > --- a/t/t7004-tag.sh\n> > +++ b/t/t7004-tag.sh\n> > @@ -1726,6 +1726,8 @@ test_expect_success 'recursive tagging should give advice' '\n> >       hint: already a tag. If you meant to tag the object that it points to, use:\n> >       hint: |\n> >       hint:   git tag -f nested annotated-v4.0^{}\n> > +     hint: Turn this message off by running\n> > +     hint: \"git config advice.nestedTag false\"\n> >       EOF\n> >       git tag -m nested nested annotated-v4.0 2>actual &&\n> >       test_i18ncmp expect actual\n> >\n> > base-commit: c7a62075917b3340f908093f63f1161c44ed1475\n>\n> Thanks,\n> -Stolee\n>\n> -->8--\n>\n> From: Derrick Stolee <dstolee@microsoft.com>\n> Date: Mon, 10 Feb 2020 09:33:20 -0500\n> Subject: [PATCH] advice: extract vadvise() from advise()\n>\n> In preparation for a new advice method, extract a version of advise()\n> that uses an explict 'va_list' parameter. Call it from advise() for a\n> functionally equivalent version.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> ---\n>  advice.c | 13 +++++++++----\n>  1 file changed, 9 insertions(+), 4 deletions(-)\n>\n> diff --git a/advice.c b/advice.c\n> index 249c60dcf3..fd836332da 100644\n> --- a/advice.c\n> +++ b/advice.c\n> @@ -96,15 +96,12 @@ static struct {\n>         { \"pushNonFastForward\", &advice_push_update_rejected }\n>  };\n>\n> -void advise(const char *advice, ...)\n> +static void vadvise(const char *advice, va_list params)\n>  {\n>         struct strbuf buf = STRBUF_INIT;\n> -       va_list params;\n>         const char *cp, *np;\n>\n> -       va_start(params, advice);\n>         strbuf_vaddf(&buf, advice, params);\n> -       va_end(params);\n>\n>         for (cp = buf.buf; *cp; cp = np) {\n>                 np = strchrnul(cp, '\\n');\n> @@ -118,6 +115,14 @@ void advise(const char *advice, ...)\n>         strbuf_release(&buf);\n>  }\n>\n> +void advise(const char *advice, ...)\n> +{\n> +       va_list params;\n> +       va_start(params, advice);\n> +       vadvise(advice, params);\n> +       va_end(params);\n> +}\n> +\n>  int git_default_advice_config(const char *var, const char *value)\n>  {\n>         const char *k, *slot_name;\n> --\n> 2.25.0.vfs.1.1.1.g9906319d24.dirty\n>\n>\n>\n\n[2] https://lore.kernel.org/git/CACg5j26DEXuxwqRYHi5UOBUpRwsu_2A9LwgyKq4qB9wxqasD7g@mail.gmail.com/\n"},{"id":"391515","messageId":"CACg5j26ROdUAqCNPMRoORweEEDaUyeZvSPKS0bYLt9qYART4bA@mail.gmail.com","threadId":"52771","inReplyTo":"20200210194253.GA46461@syl.local","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-11T00:08:48Z","receivedAt":"2020-02-11T00:09:02Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Tue, Feb 11, 2020 at 8:42 AM Taylor Blau <me@ttaylorr.com> wrote:\n>\n> On Mon, Feb 10, 2020 at 11:30:46AM -0800, Junio C Hamano wrote:\n> > Another thing.\n> >\n> > advise_ng() may have been a good name for illustration but is a\n> > horrible name for real-world use (imagine we need to revamp the API\n> > one more time in the future---what would it be called, which has to\n> > say that it is newer than the \"next generation\"?\n> > advise_3rd_try()?).\n\nAs I mentioned earlier, this patch is meant to be used as a transition\nbetween the current advise() and the refactored one.\nSo the name is just temporary and it'll be renamed to advise() once\nthe transition is done.\nBut if we want to keep both functions, or want a better name because\nit's an open-source project and the author might not complete the\ntransition, then I'll try to think of another name.\n\n> What about calling this new API 'advise()'? The first patch could call\n> it 'advise_ng' or whatever other temporary name we feel comfortable\n> using, and then each subsequent patch would update callers of 'advise()'\n> to use 'advise_ng()'. Once those patches have been applied, and no other\n> callers of 'advise()' exist, a final patch can be applied on top to\n> rename 'advise_ng()' to 'advise()', and update the names of all of the\n> callers.\n>\n\nYes, that's what I would like to do.\n\n> This makes for a rather noisy final patch, but the intermediate states\n> are much clearer, and it would make this series rather self-contained.\n>\n> On the other hand, having a version of 'advise_ng()' on master makes\n> this topic more incremental, meaning that we can pick it up and put it\n> down at ease and have more self-contained projects.\n>\n> I don't really have a preference between the two approaches, but if we\n> go with the latter, I do think we need something better than\n> 'advise_ng'. Maybe 'advise_warn'? I don't know.\n>\n> > Thanks.\n>\n> Thanks,\n> Taylor\n\nThanks,\nHeba\n"},{"id":"391518","messageId":"CACg5j26RAEdABySzpDEYmo4m+HDPn9jNDP087d3K9gCro4m-Sg@mail.gmail.com","threadId":"52771","inReplyTo":"xmqqeev2xdq8.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-11T02:01:33Z","receivedAt":"2020-02-11T02:01:48Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Tue, Feb 11, 2020 at 11:46 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n>\n> As I outlined in [1], I think the over-simplified\n> \"advise_ng(<advise.key>, _(<message>), ...)\"  would be too limited\n> to replace the current users, without a pair of helper functions,\n> one to just check for the guarding advise.key, and the other to\n> unconditionally show the message (i.e. the latter is what the\n> current advise() is).\n>\n\nI agree with adding the first helper, specially after Peff's comments,\nbut I don't see why we would keep the current advise() which\nunconditionally shows the message...\nAs far as I understand from a previous discussion [3], that's a wrong\nusage that we need to avoid, quoting from [3]:\n```\nIf there are many other places that calls to advise() are made\nwithout getting guarded by the toggles defined in advice.c, we\nshould fix them, I think.\n```\n\n> >     [1]\n> >     https://public-inbox.org/git/xmqqzhf5cw69.fsf@gitster-ct.c.googlers.com/\n[3] https://public-inbox.org/git/xmqqpng1eisc.fsf@gitster-ct.c.googlers.com/\n"},{"id":"391519","messageId":"CACg5j276+BnuTe+1eMQTALPL-Ngfy=N5udyz=EQ++29=_2R9aQ@mail.gmail.com","threadId":"52771","inReplyTo":"20200210203725.GA620581@coredump.intra.peff.net","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-11T02:20:15Z","receivedAt":"2020-02-11T02:20:29Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Tue, Feb 11, 2020 at 9:37 AM Jeff King <peff@peff.net> wrote:\n>\n> On Mon, Feb 10, 2020 at 05:04:09AM +0000, Heba Waly via GitGitGadget wrote:\n>\n> >     The advice API is currently a little bit confusing to call. quoting from\n> >     [1]:\n> >\n> >     When introducing a new advice message, you would\n> >\n> >      * come up with advice.frotz configuration variable\n> >\n> >      * define and declare advice_frotz global variable that defaults to\n> >        true\n> >\n> >      * sprinkle calls like this:\n> >\n> >       if (advice_frotz)\n> >         advise(_(\"helpful message about frotz\"));\n> >\n> >     A new approach was suggested in [1] which this patch is based upon.\n>\n> I agree that the current procedure is a bit painful, and I think this is\n> a step in the right direction. But...\n>\n> >     After this patch the plan is to migrate the rest of the advise calls to\n> >     advise_ng and then finally remove advise() and rename advise_ng() to\n> >     advise()\n>\n> ...this step may not be possible, for a few reasons:\n>\n>   1. Some of the sites do more than just advise(). E.g., branch.c checks\n>      the flag and calls both error() and advise().\n>\n>   2. Some callers may have to do work to generate the arguments. If I\n>      have:\n>\n>        advise(\"advice.foo\", \"some data: %s\", generate_data());\n>\n>      then we'll call generate_data() even if we'll throw away the result\n>      in the end.\n>\n> Similarly, some users of advice_* variables do not call advise() at all\n> (some call die(), some like builtin/rm.c stuff the result in a strbuf,\n> and I don't even know what's going on with wt_status.hints. :)\n>\n> So I think you may need to phase it in a bit more, like:\n>\n>   a. introduce want_advice() which decides whether or not to show the\n>      advice based on a config key. I'd also suggest making the \"advice.\"\n>      part of the key implicit, just to make life easier for the callers.\n>\n\nyes, I agree.\n\n>   b. introduce advise_key() which uses want_advice() and advise() under\n>      the hood to do what your advise_ng() is doing here.\n>\n>   c. convert simple patterns of:\n>\n>        if (advice_foo)\n>           advise(\"bar\");\n>\n>      into:\n>\n>        advise_key(\"foo\", \"bar\");\n>\n>      and drop advice_foo where possible.\n>\n>   d. handle more complex cases one-by-one. For example, with something\n>      like:\n>\n>        if (advice_foo)\n>          die(\"bar\");\n>\n>      we probably want:\n>\n>        if (want_advice(\"foo\"))\n>          die(\"bar\");\n>\n>      instead. Using string literals is more accident-prone than\n>      variables (because the compiler doesn't notice if we misspell them)\n>      but I think is OK for cases where we just refer to the key once.\n>      For others (e.g., advice_commit_before_merge has 13 mentions),\n>      either keep the variable. Or alternatively make a wrapper like:\n>\n>        int want_advice_commit_before_merge(void)\n>        {\n>                return want_advice(\"commitbeforemerge\");\n>        }\n>\n>      if we want to drop the existing mechanism to load all of the\n>      variables at the beginning.\n>\n\nAll make sense to me, thanks for the feedback.\n\n> -Peff\n\nHeba\n"},{"id":"391520","messageId":"CACg5j262pndH=-WdnxtRs63evL8DKvx5w2T0G2-R4LdjZBRf_g@mail.gmail.com","threadId":"52771","inReplyTo":"20200210225528.GD190927@google.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-11T02:35:02Z","receivedAt":"2020-02-11T02:35:16Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Tue, Feb 11, 2020 at 11:55 AM Emily Shaffer <emilyshaffer@google.com> wrote:\n>\n> On Mon, Feb 10, 2020 at 03:37:25PM -0500, Jeff King wrote:\n> >\n> >      instead. Using string literals is more accident-prone than\n> >      variables (because the compiler doesn't notice if we misspell them)\n> >      but I think is OK for cases where we just refer to the key once.\n> >      For others (e.g., advice_commit_before_merge has 13 mentions),\n> >      either keep the variable. Or alternatively make a wrapper like:\n> >\n> >        int want_advice_commit_before_merge(void)\n> >        {\n> >                return want_advice(\"commitbeforemerge\");\n> >        }\n> >\n> >      if we want to drop the existing mechanism to load all of the\n> >      variables at the beginning.\n>\n> I tend to disagree on both counts. I'd personally rather see something\n> like 'void advise_key(enum advice, char *format, ...)'.\n>\n> As I understand it, Heba wanted to avoid that pattern so that people\n> adding a new advice didn't need to modify the advice library. However, I\n> think there's value to having a centralized list of all possible advices\n> (besides the documentation). The per-advice wrapper is harder to iterate\n> than an enum, and will also result in a lot of samey code if we decide\n> we want to use that pattern for more advices.\n>\n\nI think Peff is suggesting the wrapper for only those rare cases where\na single advice config variable is being checked several times around\nthe code base, but this doesn't mean we'll have many of them. In my\nown opinion, I don't see the need for the list of advices, I think\nit'll add unneeded complexity to the advice library and the\nintroduction of new advice messages. Mainly because I don't see a\nscenario where we'd need to iterate through them, so I don't know ...\n\n> (In fact, with git-bugreport I'm running into a lot of regret that hooks\n> are invoked in the way Peff describes - 'find_hook(\"pre-commit\")' -\n> rather than with an enum naming the hook; it's very hard to check all\n> possible hooks, and hard to examine the codebase and determine which\n> hooks do and don't exist.)\n>\n> When Heba began to describe this project I had hoped for a final product\n> like 'void show_advice(enum advice_config)' which looked up the\n> appropriate string from the advice library instead of asking the caller\n> to provide it, although seeing the need for varargs has demonstrated to\n> me that that's not feasible :) But I think taking the advice config key\n> as an argument is possibly too far the other direction. At that point,\n> it starts to beg the question, \"why isn't this function in config.h and\n> called print_if_configured(cfgname, message, ...)?\"\n>\n> Although, take this all with a grain of salt. I think I lean towards\n> this much encapsulation after a sordid history with C++ and an\n> enlightened C developer may not choose it ;)\n>\n>  - Emily\n"},{"id":"391521","messageId":"86f2abc4-c5ee-1b6c-94d1-0c7576a16efa@gmail.com","threadId":"52771","inReplyTo":"CACg5j27SyHsc0soh_MUJHtAowjGxQ5e5ZBoXsDKfXCV2OMUpTQ@mail.gmail.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2020-02-11T02:39:14Z","receivedAt":"2020-02-11T02:39:19Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/10/2020 6:56 PM, Heba Waly wrote:\n> On Tue, Feb 11, 2020 at 3:38 AM Derrick Stolee <stolee@gmail.com> wrote:\n>>\n>> I definitely tend to recommend more tests than most, but perhaps this\n>> unit test is overkill? You demonstrate a good test below using a real\n>> Git command, which should be sufficient. If the \"turn this message off\"\n>> part gets removed, then you will still have coverage of your method.\n>> It just won't require a test change because it would not modify behavior.\n>>\n> \n> I see your point but I wanted to make sure advise_ng honors the config\n> variable using tests 2 & 3 in `t0018-advice.sh`\n> and `t7004-tag.sh` didn't seem like a good place to add these tests.\n\nYou're right. I wasn't considering the case of not showing the message\nwith respect to the config.\n\n-Stolee\n"},{"id":"391522","messageId":"xmqqa75py7u8.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"CACg5j26RAEdABySzpDEYmo4m+HDPn9jNDP087d3K9gCro4m-Sg@mail.gmail.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-11T06:08:15Z","receivedAt":"2020-02-11T06:08:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Heba Waly <heba.waly@gmail.com> writes:\n\n> On Tue, Feb 11, 2020 at 11:46 AM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>>\n>> As I outlined in [1], I think the over-simplified\n>> \"advise_ng(<advise.key>, _(<message>), ...)\"  would be too limited\n>> to replace the current users, without a pair of helper functions,\n>> one to just check for the guarding advise.key, and the other to\n>> unconditionally show the message (i.e. the latter is what the\n>> current advise() is).\n>>\n>\n> I agree with adding the first helper, specially after Peff's comments,\n> but I don't see why we would keep the current advise() which\n> unconditionally shows the message...\n\nLook again at the message you referenced in your message that\nstarted this round, and read its comment:\n\n\tif (advise_ng_enabled(\"frotz\")) {\n\t\tchar *result = expensive_computation(...);\n\n\t\t/*\n                 * advise_ng(\"frotz\", _(\"message %s about frotz\", result));\n                 * is fine as well, but slightly less efficient as\n                 * it would involve another call to *_enabled(), so use\n\t\t * the unconditional form of the call\n\t\t */\n\t\tadvise_ng_raw(_(\"message %s about frotz\", result));\n\n\t\tfree(result);\n\t}\n\n"},{"id":"391570","messageId":"20200211194943.GC2127797@coredump.intra.peff.net","threadId":"52771","inReplyTo":"20200210225528.GD190927@google.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-02-11T19:49:43Z","receivedAt":"2020-02-11T19:49:45Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Feb 10, 2020 at 02:55:28PM -0800, Emily Shaffer wrote:\n\n> I tend to disagree on both counts. I'd personally rather see something\n> like 'void advise_key(enum advice, char *format, ...)'.\n\nYeah, I don't mind that at all. It's mutually exclusive with not making\npeople add new enums when they want to add new advice types, but as you\nnote we do get some code maintenance benefit by having the variables,\neven if _adding_ a new one is a slightly larger pain.\n\n-Peff\n"},{"id":"391571","messageId":"20200211195159.GD2127797@coredump.intra.peff.net","threadId":"52771","inReplyTo":"20200211194943.GC2127797@coredump.intra.peff.net","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-02-11T19:51:59Z","receivedAt":"2020-02-11T19:52:02Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 11, 2020 at 02:49:43PM -0500, Jeff King wrote:\n\n> On Mon, Feb 10, 2020 at 02:55:28PM -0800, Emily Shaffer wrote:\n> \n> > I tend to disagree on both counts. I'd personally rather see something\n> > like 'void advise_key(enum advice, char *format, ...)'.\n> \n> Yeah, I don't mind that at all. It's mutually exclusive with not making\n> people add new enums when they want to add new advice types, but as you\n> note we do get some code maintenance benefit by having the variables,\n> even if _adding_ a new one is a slightly larger pain.\n\nThinking on it a bit more, one argument in favor of having enums or\nvariables or whatever is that we _do_ need a master list of all of the\nvariables in one spot: the documentation.\n\nSo one direction we _could_ go is either generating the enum from the\ndocumentation or vice versa (or generating both from a master list).\nThat would give us one place to define a new key along with its\ndescription.\n\n-Peff\n"},{"id":"391620","messageId":"20200212205725.GF4364@syl.local","threadId":"52771","inReplyTo":"CACg5j26ROdUAqCNPMRoORweEEDaUyeZvSPKS0bYLt9qYART4bA@mail.gmail.com","subject":"Re: [PATCH] advice: refactor advise API","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2020-02-12T20:57:25Z","receivedAt":"2020-02-12T20:57:28Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Feb 11, 2020 at 01:08:48PM +1300, Heba Waly wrote:\n> On Tue, Feb 11, 2020 at 8:42 AM Taylor Blau <me@ttaylorr.com> wrote:\n> >\n> > On Mon, Feb 10, 2020 at 11:30:46AM -0800, Junio C Hamano wrote:\n> > > Another thing.\n> > >\n> > > advise_ng() may have been a good name for illustration but is a\n> > > horrible name for real-world use (imagine we need to revamp the API\n> > > one more time in the future---what would it be called, which has to\n> > > say that it is newer than the \"next generation\"?\n> > > advise_3rd_try()?).\n>\n> As I mentioned earlier, this patch is meant to be used as a transition\n> between the current advise() and the refactored one.\n\nAh, thanks for pointing it out, and I'm sorry that I missed reading it\nin my first review. Your idea sounds quite good to me, and thanks for\nworking on this.\n\n> So the name is just temporary and it'll be renamed to advise() once\n> the transition is done.\n> But if we want to keep both functions, or want a better name because\n> it's an open-source project and the author might not complete the\n> transition, then I'll try to think of another name.\n>\n> > What about calling this new API 'advise()'? The first patch could call\n> > it 'advise_ng' or whatever other temporary name we feel comfortable\n> > using, and then each subsequent patch would update callers of 'advise()'\n> > to use 'advise_ng()'. Once those patches have been applied, and no other\n> > callers of 'advise()' exist, a final patch can be applied on top to\n> > rename 'advise_ng()' to 'advise()', and update the names of all of the\n> > callers.\n> >\n>\n> Yes, that's what I would like to do.\n>\n> > This makes for a rather noisy final patch, but the intermediate states\n> > are much clearer, and it would make this series rather self-contained.\n> >\n> > On the other hand, having a version of 'advise_ng()' on master makes\n> > this topic more incremental, meaning that we can pick it up and put it\n> > down at ease and have more self-contained projects.\n> >\n> > I don't really have a preference between the two approaches, but if we\n> > go with the latter, I do think we need something better than\n> > 'advise_ng'. Maybe 'advise_warn'? I don't know.\n> >\n> > > Thanks.\n> >\n> > Thanks,\n> > Taylor\n>\n> Thanks,\n> Heba\n\nThanks,\nTaylor\n"},{"id":"391877","messageId":"3e4f52e55268727815990b9a96daffc365bd99e0.1581889150.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v2.git.1581889150.gitgitgadget@gmail.com","subject":"[PATCH v2 2/2] advice: extract vadvise() from advise()","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-16T21:39:09Z","receivedAt":"2020-02-16T21:39:19Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nextract a version of advise() that uses an explict 'va_list' parameter.\nCall it from advise() and advise_if_enabled() for a functionally\nequivalent version.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c | 45 +++++++++++++++++++--------------------------\n 1 file changed, 19 insertions(+), 26 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 8cedc649afa..6c0be19a7c5 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -128,15 +128,20 @@ static const char *advice_config_keys[] = {\n \t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n };\n \n-void advise(const char *advice, ...)\n+static const char turn_off_instructions[] =\n+N_(\"\\n\"\n+   \"Disable this message with \\\"git config %s false\\\"\");\n+\n+static void vadvise(const char *advice, va_list params,\n+\t\t    int display_instructions, char *key)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tva_list params;\n \tconst char *cp, *np;\n \n-\tva_start(params, advice);\n \tstrbuf_vaddf(&buf, advice, params);\n-\tva_end(params);\n+\n+\tif(display_instructions)\n+\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n \n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n@@ -165,37 +170,25 @@ int advice_push_update_rejected_enabled(void)\n \t\n }\n \n-static const char turn_off_instructions[] =\n-N_(\"\\n\"\n-   \"Disable this message with \\\"git config %s false\\\"\");\n+void advise(const char *advice, ...)\n+{\n+\tva_list params;\n+\tva_start(params, advice);\n+\tvadvise(advice, params, 0, \"\");\n+\tva_end(params);\n+}\n \n void advise_if_enabled(enum advice_type type, const char *advice, ...)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n \tva_list params;\n-\tconst char *cp, *np;\n-\t\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\n \tif(!advice_enabled(type))\n \t\treturn;\n \n \tva_start(params, advice);\n-\tstrbuf_vaddf(&buf, advice, params);\n+\tvadvise(advice, params, 1, key);\n \tva_end(params);\n-\n-\tstrbuf_addf(&buf, turn_off_instructions, key);\n-\t\n-\tfor (cp = buf.buf; *cp; cp = np) {\n-\t\tnp = strchrnul(cp, '\\n');\n-\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n-\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n-\t\t\t(int)(np - cp), cp,\n-\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n-\t\tif (*np)\n-\t\t\tnp++;\n-\t}\n-\tstrbuf_release(&buf);\n-\n }\n \n int git_default_advice_config(const char *var, const char *value)\n-- \ngitgitgadget\n"},{"id":"391878","messageId":"pull.548.v2.git.1581889150.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.git.1581311049547.gitgitgadget@gmail.com","subject":"[PATCH v2 0/2] [RFC][Outreachy] advice: refactor advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-16T21:39:07Z","receivedAt":"2020-02-16T21:39:19Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"Main changes in V2: 1- Rename advise_ng to advise_if_enabled. 2- Add a new\nadvise_enabled() helper. 3- Add a list of config variables names to replace\nadvice_config[] (used by list_config_advices()). 4- Send an enum parameter\nto the new advise helpers instead of strings. 5- Extract vadvise() from\nadvise() and advise_if enabled().\n\n\n----------------------------------------------------------------------------\n\nThe advice API is currently a little bit confusing to call. quoting from\n[1]:\n\nWhen introducing a new advice message, you would\n\n * come up with advice.frotz configuration variable\n\n * define and declare advice_frotz global variable that defaults to\n   true\n\n * sprinkle calls like this:\n\n  if (advice_frotz)\n    advise(_(\"helpful message about frotz\"));\n\nA new approach was suggested in [1] which this patch is based upon.\n\nA new advise_if_enabled() is introduced to gradually replace advise()\nadvice_enabled() helper is also introduced to be used by those callers who:\n\n * Only need to check the visibility without calling advise() (they call\n   die() or error() instead for example)\n * Need to carry out some heavy processing to display an advice, in this\n   case they'll do: if(advice_enabled(advice_type))  advise(\"some advice message\");\n   \n   \n\nTo introduce a new advice message, the caller needs to:\n\n * Add a new_advice_type to 'enum advice_type'\n * Come up with a new config variable name and add this name to \n   advice_config_keys[]\n * Call advise_if_enabled(new_advice_type, \"advice message to be printed\")\n * Or call advice_enabled(new_advice_type) first and then follow is by\n   advice(\"advice message to be printed\") as explained earlier.\n * Add the new config variable to Documentation/config/advice.txt\n\nThe reason a new list of configuration variables was added to the library is\nto be used by the list_config_advices() function instead of advice_config[].\nAnd we should get rid of advice_config[] once we migrate all the callers to\nuse the new APIs instead of checking the global variables (which we'll get\nrid of as well).\n\nIn the future, we can investigate generating the documentation from the list\nof config variables or vice versa to make introducing a new advice much\neasier, but this approach will do it for now.\n\nV2 makes the process of introducing a new advice longer than V1 and almost\nas long as the original library, but having the advice library responsible\nfor checking the message visibility is still an improvement and in my own\nopinion the new structure makes better sense and makes the library less\nconfusing to use.\n\nAfter this patch the plan is to change the advise() calls to\nadvise_if_enabled() whenever possible, or at least replace the global\nvariables checks by advise_enabled() when advise_if_enabled() is not\nsuitable.\n\n[1] https://public-inbox.org/git/xmqqzhf5cw69.fsf@gitster-ct.c.googlers.com/\n\nHeba Waly (2):\n  advice: refactor advise API\n  advice: extract vadvise() from advise()\n\n Makefile               |  1 +\n advice.c               | 89 ++++++++++++++++++++++++++++++++++++++----\n advice.h               | 58 ++++++++++++++++++++++++++-\n builtin/tag.c          |  4 +-\n t/helper/test-advise.c | 16 ++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 28 +++++++++++++\n t/t7004-tag.sh         |  1 +\n 9 files changed, 188 insertions(+), 11 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\n\nbase-commit: c7a62075917b3340f908093f63f1161c44ed1475\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-548%2FHebaWaly%2Fadvice_refactoring-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-548/HebaWaly/advice_refactoring-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/548\n\nRange-diff vs v1:\n\n 1:  0dcab7b4eb3 ! 1:  080d12b5c69 advice: refactor advise API\n     @@ -2,15 +2,25 @@\n      \n          advice: refactor advise API\n      \n     -    Add a new advise_ng function that can check the visibility of advice\n     -    messages before printing.\n     +    Currently it's very easy for the advice library's callers to miss\n     +    checking the visibility step before printing an advice. Also, it makes\n     +    more sense for this step to be handled by the advice library.\n      \n     -    Currently it's very easy for the callers to miss checking the\n     -    visibility step. Also, it makes more sense for this step to be handled\n     -    by the advice library.\n     +    Add a new advise_if_enabled function that checks the visibility of\n     +    advice messages before printing.\n      \n     -    Also change the advise call in tag library from advise() to advise_ng()\n     -    to construct an example of the usage of the new API.\n     +    Add a new helper advise_enabled to check the visibility of the advice\n     +    if the caller needs to carry out complicated processing based on that\n     +    value.\n     +\n     +    A list of config variables 'advice_config_keys' is added to be used by\n     +    list_config_advices() instead of 'advice_config[]' because we'll get\n     +    rid of 'advice_config[]' and the global variables once we migrate all\n     +    the callers to use the new APIs.\n     +\n     +    Also change the advise call in tag library from advise() to\n     +    advise_if_enabled() to construct an example of the usage of the new\n     +    API.\n      \n          Signed-off-by: Heba Waly <heba.waly@gmail.com>\n      \n     @@ -45,48 +55,112 @@\n       \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n       \n       \t/* make this an alias for backward compatibility */\n     + \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n     + };\n     + \n     ++static const char *advice_config_keys[] = {\n     ++\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n     ++\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n     ++\t/* make this an alias for backward compatibility */\n     ++\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n     ++\t\n     ++\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n     ++\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n     ++\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n     ++\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n     ++\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n     ++\t[PUSH_UNQUALIFIED_REF_NAME]\t\t = \"pushUnqualifiedRefName\",\n     ++\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n     ++\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n     ++\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n     ++\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n     ++\t[RESET_QUIET_WARNING]\t\t\t = \"resetQuiet\",\n     ++\t[RESOLVE_CONFLICT]\t\t\t = \"resolveConflict\",\n     ++\t[SEQUENCER_IN_USE]\t\t\t = \"sequencerInUse\",\n     ++\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n     ++\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n     ++\t[SET_UPSTREAM_FAILURE]\t\t\t = \"setupStreamFailure\",\n     ++\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n     ++\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n     ++\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n     ++\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n     ++\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n     ++\t[WAITING_FOR_EDITOR] \t\t\t = \"waitingForEditor\",\n     ++\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n     ++\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n     ++\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n     ++\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n     ++};\n     ++\n     + void advise(const char *advice, ...)\n     + {\n     + \tstruct strbuf buf = STRBUF_INIT;\n      @@\n       \tstrbuf_release(&buf);\n       }\n       \n     ++int advice_enabled(enum advice_type type)\n     ++{\n     ++\tint value = 1;\n     ++\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n     ++\tgit_config_get_bool(key, &value);\n     ++\treturn value;\n     ++}\n     ++\n     ++int advice_push_update_rejected_enabled(void)\n     ++{\n     ++\treturn advice_enabled(PUSH_UPDATE_REJECTED) ||\n     ++\t       advice_enabled(PUSH_UPDATE_REJECTED_ALIAS);\n     ++\t\n     ++}\n     ++\n      +static const char turn_off_instructions[] =\n      +N_(\"\\n\"\n     -+\"Turn this message off by running\\n\"\n     -+\"\\\"git config %s false\\\"\");\n     ++   \"Disable this message with \\\"git config %s false\\\"\");\n      +\n     -+void advise_ng(const char *key, const char *advice, ...)\n     ++void advise_if_enabled(enum advice_type type, const char *advice, ...)\n      +{\n     -+\tint value = 1;\n      +\tstruct strbuf buf = STRBUF_INIT;\n     ++\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n      +\tva_list params;\n      +\tconst char *cp, *np;\n      +\t\n     -+\tgit_config_get_bool(key, &value);\n     ++\tif(!advice_enabled(type))\n     ++\t\treturn;\n     ++\n     ++\tva_start(params, advice);\n     ++\tstrbuf_vaddf(&buf, advice, params);\n     ++\tva_end(params);\n     ++\n     ++\tstrbuf_addf(&buf, turn_off_instructions, key);\n      +\t\n     -+\tif(value)\n     -+\t{\n     -+\t\tva_start(params, advice);\n     -+\t\tstrbuf_vaddf(&buf, advice, params);\n     -+\t\tva_end(params);\n     -+\n     -+\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n     -+\t\t\n     -+\t\tfor (cp = buf.buf; *cp; cp = np) {\n     -+\t\t\tnp = strchrnul(cp, '\\n');\n     -+\t\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n     -+\t\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n     -+\t\t\t\t(int)(np - cp), cp,\n     -+\t\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n     -+\t\t\tif (*np)\n     -+\t\t\t\tnp++;\n     -+\t\t}\n     -+\t\tstrbuf_release(&buf);\n     ++\tfor (cp = buf.buf; *cp; cp = np) {\n     ++\t\tnp = strchrnul(cp, '\\n');\n     ++\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n     ++\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n     ++\t\t\t(int)(np - cp), cp,\n     ++\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n     ++\t\tif (*np)\n     ++\t\t\tnp++;\n      +\t}\n     ++\tstrbuf_release(&buf);\n     ++\n      +}\n      +\n       int git_default_advice_config(const char *var, const char *value)\n       {\n       \tconst char *k, *slot_name;\n     +@@\n     + {\n     + \tint i;\n     + \n     +-\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n     +-\t\tlist_config_item(list, prefix, advice_config[i].name);\n     ++\tfor (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n     ++\t\tlist_config_item(list, prefix, advice_config_keys[i]);\n     + }\n     + \n     + int error_resolve_conflict(const char *me)\n      \n       diff --git a/advice.h b/advice.h\n       --- a/advice.h\n     @@ -98,14 +172,66 @@\n      -extern int advice_nested_tag;\n       extern int advice_submodule_alternate_error_strategy_die;\n       \n     ++/**\n     ++ To add a new advice, you need to:\n     ++ - Define an advice_type.\n     ++ - Add a new entry to advice_config_keys list.\n     ++ - Add the new config variable to Documentation/config/advice.txt.\n     ++ - Call advise_if_enabled to print your advice.\n     ++ */\n     ++enum advice_type {\n     ++\tFETCH_SHOW_FORCED_UPDATES = 0,\n     ++\tPUSH_UPDATE_REJECTED = 1,\n     ++\tPUSH_UPDATE_REJECTED_ALIAS = 2,\n     ++\tPUSH_NON_FF_CURRENT = 3,\n     ++\tPUSH_NON_FF_MATCHING = 4,\n     ++\tPUSH_ALREADY_EXISTS = 5,\n     ++\tPUSH_FETCH_FIRST = 6,\n     ++\tPUSH_NEEDS_FORCE = 7,\n     ++\tPUSH_UNQUALIFIED_REF_NAME = 8,\n     ++\tSTATUS_HINTS = 9,\n     ++\tSTATUS_U_OPTION = 10,\n     ++\tSTATUS_AHEAD_BEHIND_WARNING = 11,\n     ++\tCOMMIT_BEFORE_MERGE = 12,\n     ++\tRESET_QUIET_WARNING = 13,\n     ++\tRESOLVE_CONFLICT = 14,\n     ++\tSEQUENCER_IN_USE = 15,\n     ++\tIMPLICIT_IDENTITY = 16,\n     ++\tDETACHED_HEAD = 17,\n     ++\tSET_UPSTREAM_FAILURE = 18,\n     ++\tOBJECT_NAME_WARNING = 19,\n     ++\tAMWORKDIR = 20,\n     ++\tRM_HINTS = 21,\n     ++\tADD_EMBEDDED_REPO = 22,\n     ++\tIGNORED_HOOK = 23,\n     ++\tWAITING_FOR_EDITOR = 24,\n     ++\tGRAFT_FILE_DEPRECATED = 25,\n     ++\tCHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME = 26,\n     ++\tNESTED_TAG = 27,\n     ++\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE = 28,\n     ++};\n     ++\n     ++\n       int git_default_advice_config(const char *var, const char *value);\n       __attribute__((format (printf, 1, 2)))\n       void advise(const char *advice, ...);\n      +\n      +/**\n     ++ Checks if advice type is enabled (can be printed to the user).\n     ++ Should be called before advise().\n     ++ */\n     ++int advice_enabled(enum advice_type type);\n     ++\n     ++/**\n     ++ Checks if PUSH_UPDATE_REJECTED advice type is enabled.\n     ++ */\n     ++int advice_push_update_rejected_enabled(void);\n     ++\n     ++/**\n      + Checks the visibility of the advice before priniting.\n      + */\n     -+void advise_ng(const char *key, const char *advice, ...);\n     ++void advise_if_enabled(enum advice_type type, const char *advice, ...);\n     ++\n       int error_resolve_conflict(const char *me);\n       void NORETURN die_resolve_conflict(const char *me);\n       void NORETURN die_conclude_merge(void);\n     @@ -120,7 +246,7 @@\n      -\tif (type == OBJ_TAG && advice_nested_tag)\n      -\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n      +\tif (type == OBJ_TAG)\n     -+\t\tadvise_ng(\"advice.nestedTag\", _(message_advice_nested_tag), tag, object_ref);\n     ++\t\tadvise_if_enabled(NESTED_TAG, _(message_advice_nested_tag), tag, object_ref);\n       \n       \tstrbuf_addf(&header,\n       \t\t    \"object %s\\n\"\n     @@ -134,14 +260,15 @@\n      +#include \"cache.h\"\n      +#include \"advice.h\"\n      +\n     -+int cmd__advise_ng(int argc, const char **argv)\n     ++int cmd__advise_if_enabled(int argc, const char **argv)\n      +{\n     -+\tif (!argv[1] || !argv[2])\n     -+\tdie(\"usage: %s <key> <advice>\", argv[0]);\n     ++\tif (!argv[1])\n     ++\tdie(\"usage: %s <advice>\", argv[0]);\n      +\n      +\tsetup_git_directory();\n     -+\n     -+\tadvise_ng(argv[1], argv[2]);\n     ++\t\n     ++\t//use any advice type for testing\n     ++\tadvise_if_enabled(NESTED_TAG, argv[1]);\n      +\n      +\treturn 0;\n      +}\n     @@ -153,7 +280,7 @@\n       };\n       \n       static struct test_cmd cmds[] = {\n     -+\t{ \"advise\", cmd__advise_ng },\n     ++\t{ \"advise\", cmd__advise_if_enabled },\n       \t{ \"chmtime\", cmd__chmtime },\n       \t{ \"config\", cmd__config },\n       \t{ \"ctype\", cmd__ctype },\n     @@ -165,7 +292,7 @@\n       #define USE_THE_INDEX_COMPATIBILITY_MACROS\n       #include \"git-compat-util.h\"\n       \n     -+int cmd__advise_ng(int argc, const char **argv);\n     ++int cmd__advise_if_enabled(int argc, const char **argv);\n       int cmd__chmtime(int argc, const char **argv);\n       int cmd__config(int argc, const char **argv);\n       int cmd__ctype(int argc, const char **argv);\n     @@ -177,29 +304,28 @@\n      @@\n      +#!/bin/sh\n      +\n     -+test_description='Test advise_ng functionality'\n     ++test_description='Test advise_if_enabled functionality'\n      +\n      +. ./test-lib.sh\n      +\n      +cat > expected <<EOF\n      +hint: This is a piece of advice\n     -+hint: Turn this message off by running\n     -+hint: \"git config advice.configVariable false\"\n     ++hint: Disable this message with \"git config advice.nestedTag false\"\n      +EOF\n      +test_expect_success 'advise should be printed when config variable is unset' '\n     -+\ttest-tool advise \"advice.configVariable\" \"This is a piece of advice\" 2>actual &&\n     ++\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n      +\ttest_i18ncmp expected actual\n      +'\n      +\n      +test_expect_success 'advise should be printed when config variable is set to true' '\n     -+\ttest_config advice.configVariable true &&\n     -+\ttest-tool advise \"advice.configVariable\" \"This is a piece of advice\" 2>actual &&\n     ++\ttest_config advice.nestedTag true &&\n     ++\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n      +\ttest_i18ncmp expected actual\n      +'\n      +\n      +test_expect_success 'advise should not be printed when config variable is set to false' '\n     -+\ttest_config advice.configVariable false &&\n     -+\ttest-tool advise \"advice.configVariable\" \"This is a piece of advice\" 2>actual &&\n     ++\ttest_config advice.nestedTag false &&\n     ++\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n      +\ttest_must_be_empty actual\n      +'\n      +\n     @@ -212,8 +338,7 @@\n       \thint: already a tag. If you meant to tag the object that it points to, use:\n       \thint: |\n       \thint: \tgit tag -f nested annotated-v4.0^{}\n     -+\thint: Turn this message off by running\n     -+\thint: \"git config advice.nestedTag false\"\n     ++\thint: Disable this message with \"git config advice.nestedTag false\"\n       \tEOF\n       \tgit tag -m nested nested annotated-v4.0 2>actual &&\n       \ttest_i18ncmp expect actual\n -:  ----------- > 2:  3e4f52e5526 advice: extract vadvise() from advise()\n\n-- \ngitgitgadget\n"},{"id":"391879","messageId":"080d12b5c696d61ac1052d5863f7260a4005346e.1581889150.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v2.git.1581889150.gitgitgadget@gmail.com","subject":"[PATCH v2 1/2] advice: refactor advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-16T21:39:08Z","receivedAt":"2020-02-16T21:39:19Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nCurrently it's very easy for the advice library's callers to miss\nchecking the visibility step before printing an advice. Also, it makes\nmore sense for this step to be handled by the advice library.\n\nAdd a new advise_if_enabled function that checks the visibility of\nadvice messages before printing.\n\nAdd a new helper advise_enabled to check the visibility of the advice\nif the caller needs to carry out complicated processing based on that\nvalue.\n\nA list of config variables 'advice_config_keys' is added to be used by\nlist_config_advices() instead of 'advice_config[]' because we'll get\nrid of 'advice_config[]' and the global variables once we migrate all\nthe callers to use the new APIs.\n\nAlso change the advise call in tag library from advise() to\nadvise_if_enabled() to construct an example of the usage of the new\nAPI.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n Makefile               |  1 +\n advice.c               | 88 ++++++++++++++++++++++++++++++++++++++++--\n advice.h               | 58 +++++++++++++++++++++++++++-\n builtin/tag.c          |  4 +-\n t/helper/test-advise.c | 16 ++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 28 ++++++++++++++\n t/t7004-tag.sh         |  1 +\n 9 files changed, 191 insertions(+), 7 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\ndiff --git a/Makefile b/Makefile\nindex 09f98b777ca..ed923a3e818 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -695,6 +695,7 @@ X =\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n+TEST_BUILTINS_OBJS += test-advise.o\n TEST_BUILTINS_OBJS += test-chmtime.o\n TEST_BUILTINS_OBJS += test-config.o\n TEST_BUILTINS_OBJS += test-ctype.o\ndiff --git a/advice.c b/advice.c\nindex 249c60dcf32..8cedc649afa 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -29,7 +29,6 @@ int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n int advice_graft_file_deprecated = 1;\n int advice_checkout_ambiguous_remote_branch_name = 1;\n-int advice_nested_tag = 1;\n int advice_submodule_alternate_error_strategy_die = 1;\n \n static int advice_use_color = -1;\n@@ -89,13 +88,46 @@ static struct {\n \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n \t{ \"checkoutAmbiguousRemoteBranchName\", &advice_checkout_ambiguous_remote_branch_name },\n-\t{ \"nestedTag\", &advice_nested_tag },\n \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n \n \t/* make this an alias for backward compatibility */\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n+static const char *advice_config_keys[] = {\n+\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n+\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n+\t/* make this an alias for backward compatibility */\n+\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n+\t\n+\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n+\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n+\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n+\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n+\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n+\t[PUSH_UNQUALIFIED_REF_NAME]\t\t = \"pushUnqualifiedRefName\",\n+\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n+\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n+\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n+\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n+\t[RESET_QUIET_WARNING]\t\t\t = \"resetQuiet\",\n+\t[RESOLVE_CONFLICT]\t\t\t = \"resolveConflict\",\n+\t[SEQUENCER_IN_USE]\t\t\t = \"sequencerInUse\",\n+\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n+\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n+\t[SET_UPSTREAM_FAILURE]\t\t\t = \"setupStreamFailure\",\n+\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n+\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n+\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n+\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n+\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n+\t[WAITING_FOR_EDITOR] \t\t\t = \"waitingForEditor\",\n+\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n+\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n+\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n+\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n+};\n+\n void advise(const char *advice, ...)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -118,6 +150,54 @@ void advise(const char *advice, ...)\n \tstrbuf_release(&buf);\n }\n \n+int advice_enabled(enum advice_type type)\n+{\n+\tint value = 1;\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\tgit_config_get_bool(key, &value);\n+\treturn value;\n+}\n+\n+int advice_push_update_rejected_enabled(void)\n+{\n+\treturn advice_enabled(PUSH_UPDATE_REJECTED) ||\n+\t       advice_enabled(PUSH_UPDATE_REJECTED_ALIAS);\n+\t\n+}\n+\n+static const char turn_off_instructions[] =\n+N_(\"\\n\"\n+   \"Disable this message with \\\"git config %s false\\\"\");\n+\n+void advise_if_enabled(enum advice_type type, const char *advice, ...)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\tva_list params;\n+\tconst char *cp, *np;\n+\t\n+\tif(!advice_enabled(type))\n+\t\treturn;\n+\n+\tva_start(params, advice);\n+\tstrbuf_vaddf(&buf, advice, params);\n+\tva_end(params);\n+\n+\tstrbuf_addf(&buf, turn_off_instructions, key);\n+\t\n+\tfor (cp = buf.buf; *cp; cp = np) {\n+\t\tnp = strchrnul(cp, '\\n');\n+\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n+\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n+\t\t\t(int)(np - cp), cp,\n+\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n+\t\tif (*np)\n+\t\t\tnp++;\n+\t}\n+\tstrbuf_release(&buf);\n+\n+}\n+\n int git_default_advice_config(const char *var, const char *value)\n {\n \tconst char *k, *slot_name;\n@@ -154,8 +234,8 @@ void list_config_advices(struct string_list *list, const char *prefix)\n {\n \tint i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n-\t\tlist_config_item(list, prefix, advice_config[i].name);\n+\tfor (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n+\t\tlist_config_item(list, prefix, advice_config_keys[i]);\n }\n \n int error_resolve_conflict(const char *me)\ndiff --git a/advice.h b/advice.h\nindex b706780614d..d2ac3084067 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -29,12 +29,68 @@ extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n extern int advice_graft_file_deprecated;\n extern int advice_checkout_ambiguous_remote_branch_name;\n-extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n+/**\n+ To add a new advice, you need to:\n+ - Define an advice_type.\n+ - Add a new entry to advice_config_keys list.\n+ - Add the new config variable to Documentation/config/advice.txt.\n+ - Call advise_if_enabled to print your advice.\n+ */\n+enum advice_type {\n+\tFETCH_SHOW_FORCED_UPDATES = 0,\n+\tPUSH_UPDATE_REJECTED = 1,\n+\tPUSH_UPDATE_REJECTED_ALIAS = 2,\n+\tPUSH_NON_FF_CURRENT = 3,\n+\tPUSH_NON_FF_MATCHING = 4,\n+\tPUSH_ALREADY_EXISTS = 5,\n+\tPUSH_FETCH_FIRST = 6,\n+\tPUSH_NEEDS_FORCE = 7,\n+\tPUSH_UNQUALIFIED_REF_NAME = 8,\n+\tSTATUS_HINTS = 9,\n+\tSTATUS_U_OPTION = 10,\n+\tSTATUS_AHEAD_BEHIND_WARNING = 11,\n+\tCOMMIT_BEFORE_MERGE = 12,\n+\tRESET_QUIET_WARNING = 13,\n+\tRESOLVE_CONFLICT = 14,\n+\tSEQUENCER_IN_USE = 15,\n+\tIMPLICIT_IDENTITY = 16,\n+\tDETACHED_HEAD = 17,\n+\tSET_UPSTREAM_FAILURE = 18,\n+\tOBJECT_NAME_WARNING = 19,\n+\tAMWORKDIR = 20,\n+\tRM_HINTS = 21,\n+\tADD_EMBEDDED_REPO = 22,\n+\tIGNORED_HOOK = 23,\n+\tWAITING_FOR_EDITOR = 24,\n+\tGRAFT_FILE_DEPRECATED = 25,\n+\tCHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME = 26,\n+\tNESTED_TAG = 27,\n+\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE = 28,\n+};\n+\n+\n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\n void advise(const char *advice, ...);\n+\n+/**\n+ Checks if advice type is enabled (can be printed to the user).\n+ Should be called before advise().\n+ */\n+int advice_enabled(enum advice_type type);\n+\n+/**\n+ Checks if PUSH_UPDATE_REJECTED advice type is enabled.\n+ */\n+int advice_push_update_rejected_enabled(void);\n+\n+/**\n+ Checks the visibility of the advice before priniting.\n+ */\n+void advise_if_enabled(enum advice_type type, const char *advice, ...);\n+\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex e0a4c253828..247d9075e19 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -231,8 +231,8 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \tif (type <= OBJ_NONE)\n \t\tdie(_(\"bad object type.\"));\n \n-\tif (type == OBJ_TAG && advice_nested_tag)\n-\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n+\tif (type == OBJ_TAG)\n+\t\tadvise_if_enabled(NESTED_TAG, _(message_advice_nested_tag), tag, object_ref);\n \n \tstrbuf_addf(&header,\n \t\t    \"object %s\\n\"\ndiff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\nnew file mode 100644\nindex 00000000000..c49be89f970\n--- /dev/null\n+++ b/t/helper/test-advise.c\n@@ -0,0 +1,16 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"advice.h\"\n+\n+int cmd__advise_if_enabled(int argc, const char **argv)\n+{\n+\tif (!argv[1])\n+\tdie(\"usage: %s <advice>\", argv[0]);\n+\n+\tsetup_git_directory();\n+\t\n+\t//use any advice type for testing\n+\tadvise_if_enabled(NESTED_TAG, argv[1]);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d4497..6977badc690 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -14,6 +14,7 @@ struct test_cmd {\n };\n \n static struct test_cmd cmds[] = {\n+\t{ \"advise\", cmd__advise_if_enabled },\n \t{ \"chmtime\", cmd__chmtime },\n \t{ \"config\", cmd__config },\n \t{ \"ctype\", cmd__ctype },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1b..ca5e33b842f 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -4,6 +4,7 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"git-compat-util.h\"\n \n+int cmd__advise_if_enabled(int argc, const char **argv);\n int cmd__chmtime(int argc, const char **argv);\n int cmd__config(int argc, const char **argv);\n int cmd__ctype(int argc, const char **argv);\ndiff --git a/t/t0018-advice.sh b/t/t0018-advice.sh\nnew file mode 100755\nindex 00000000000..f4cdb649d51\n--- /dev/null\n+++ b/t/t0018-advice.sh\n@@ -0,0 +1,28 @@\n+#!/bin/sh\n+\n+test_description='Test advise_if_enabled functionality'\n+\n+. ./test-lib.sh\n+\n+cat > expected <<EOF\n+hint: This is a piece of advice\n+hint: Disable this message with \"git config advice.nestedTag false\"\n+EOF\n+test_expect_success 'advise should be printed when config variable is unset' '\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expected actual\n+'\n+\n+test_expect_success 'advise should be printed when config variable is set to true' '\n+\ttest_config advice.nestedTag true &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expected actual\n+'\n+\n+test_expect_success 'advise should not be printed when config variable is set to false' '\n+\ttest_config advice.nestedTag false &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_done\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 6db92bd3ba6..74b637deb25 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1726,6 +1726,7 @@ test_expect_success 'recursive tagging should give advice' '\n \thint: already a tag. If you meant to tag the object that it points to, use:\n \thint: |\n \thint: \tgit tag -f nested annotated-v4.0^{}\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n \tEOF\n \tgit tag -m nested nested annotated-v4.0 2>actual &&\n \ttest_i18ncmp expect actual\n-- \ngitgitgadget\n\n"},{"id":"391885","messageId":"xmqqftfam5ux.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"pull.548.v2.git.1581889150.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/2] [RFC][Outreachy] advice: refactor advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-17T00:09:58Z","receivedAt":"2020-02-17T00:10:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Main changes in V2: 1- Rename advise_ng to advise_if_enabled. 2- Add a new\n> advise_enabled() helper. 3- Add a list of config variables names to replace\n> advice_config[] (used by list_config_advices()). 4- Send an enum parameter\n> to the new advise helpers instead of strings. 5- Extract vadvise() from\n> advise() and advise_if enabled().\n\n\nNice.  There is not much point for me to correct mistakes in the\ntitle of the cover letter, but anyway... I think these changes are\nnot \"refactoring\" the API, but these are enhancing, extending or\neven revamping the API.  \n\n> To introduce a new advice message, the caller needs to:\n>\n>  * Add a new_advice_type to 'enum advice_type'\n>  * Come up with a new config variable name and add this name to \n>    advice_config_keys[]\n>  * Call advise_if_enabled(new_advice_type, \"advice message to be printed\")\n>  * Or call advice_enabled(new_advice_type) first and then follow is by\n>    advice(\"advice message to be printed\") as explained earlier.\n>  * Add the new config variable to Documentation/config/advice.txt\n\nAnd I see that now you are going all the way to discard the\nstring-based keys to enumeration, I think this deserves to be called\n\"revamp advise API\".\n\n> In the future, we can investigate generating the documentation from the list\n> of config variables or vice versa to make introducing a new advice much\n> easier, but this approach will do it for now.\n\nYup.  One step at a time.\n"},{"id":"391886","messageId":"xmqq8sl1nb98.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"080d12b5c696d61ac1052d5863f7260a4005346e.1581889150.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/2] advice: refactor advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-17T03:28:03Z","receivedAt":"2020-02-17T03:28:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"There seem to be a handful of lines with trailing whitespaces.  I\nthink I've fixed them all on the receiving end, but please proofread\nbefore you come up with the next round.\n\nThanks.\n"},{"id":"391914","messageId":"CACg5j25nNAWqXP6jXUBxkwC=hEd7_9Nw3hfQNpPKgEW1JabxgQ@mail.gmail.com","threadId":"52771","inReplyTo":"xmqq8sl1nb98.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 1/2] advice: refactor advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-17T10:03:18Z","receivedAt":"2020-02-17T10:03:34Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Mon, Feb 17, 2020 at 4:28 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n>And I see that now you are going all the way to discard the\n>string-based keys to enumeration, I think this deserves to be called\n>\"revamp advise API\".\n\nFair enough.\n\n> There seem to be a handful of lines with trailing whitespaces.  I\n> think I've fixed them all on the receiving end, but please proofread\n> before you come up with the next round.\n>\n\nSorry about that, I fixed this setting in my IDE a while ago but looks\nlike it has a bug.\nWill keep an eye on it in the future, thanks\n\n> Thanks.\n\nHeba\n"},{"id":"392053","messageId":"CACg5j25yoXi_aAwd0uf7ZgiCPE23Ej89bSnPiz1vxrhWNmkK3g@mail.gmail.com","threadId":"52771","inReplyTo":"080d12b5c696d61ac1052d5863f7260a4005346e.1581889150.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/2] advice: refactor advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-19T09:59:28Z","receivedAt":"2020-02-19T09:59:46Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Mon, Feb 17, 2020 at 10:39 AM Heba Waly via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> +int advice_enabled(enum advice_type type)\n> +{\n> +       int value = 1;\n> +       char *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> +       git_config_get_bool(key, &value);\n> +       return value;\n> +}\n> +\n> +int advice_push_update_rejected_enabled(void)\n> +{\n> +       return advice_enabled(PUSH_UPDATE_REJECTED) ||\n> +              advice_enabled(PUSH_UPDATE_REJECTED_ALIAS);\n> +\n> +}\n\nI just realized that the return value in this function should be &&\ninstead of ||\n(if any of the config variables is 0, then the advice should be\ndisabled), will fix that in the next round.\n\nI'll probably remove this function and add a switch case to\nadvice_enabled() to handle this case as well as another one that I\ncame across while migrating the rest of the calls to the new helpers.\nWill post an updated version soon.\n\n> --\n> gitgitgadget\n>\n"},{"id":"392104","messageId":"a2a145c705e2751d4ced9cc71e62d5c560adb6e6.1582144442.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v3.git.1582144442.gitgitgadget@gmail.com","subject":"[PATCH v3 2/2] advice: extract vadvise() from advise()","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-19T20:34:01Z","receivedAt":"2020-02-19T20:34:11Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nextract a version of advise() that uses an explict 'va_list' parameter.\nCall it from advise() and advise_if_enabled() for a functionally\nequivalent version.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c | 45 +++++++++++++++++++--------------------------\n 1 file changed, 19 insertions(+), 26 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 345319005ac..0c144c69487 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -128,15 +128,20 @@ static const char *advice_config_keys[] = {\n \t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n };\n \n-void advise(const char *advice, ...)\n+static const char turn_off_instructions[] =\n+N_(\"\\n\"\n+   \"Disable this message with \\\"git config %s false\\\"\");\n+\n+static void vadvise(const char *advice, va_list params,\n+\t\t    int display_instructions, char *key)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tva_list params;\n \tconst char *cp, *np;\n \n-\tva_start(params, advice);\n \tstrbuf_vaddf(&buf, advice, params);\n-\tva_end(params);\n+\n+\tif(display_instructions)\n+\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n \n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n@@ -169,37 +174,25 @@ int advice_enabled(enum advice_type type)\n \t}\n }\n \n-static const char turn_off_instructions[] =\n-N_(\"\\n\"\n-   \"Disable this message with \\\"git config %s false\\\"\");\n+void advise(const char *advice, ...)\n+{\n+\tva_list params;\n+\tva_start(params, advice);\n+\tvadvise(advice, params, 0, \"\");\n+\tva_end(params);\n+}\n \n void advise_if_enabled(enum advice_type type, const char *advice, ...)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n-\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n \tva_list params;\n-\tconst char *cp, *np;\n-\t\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\n \tif(!advice_enabled(type))\n \t\treturn;\n \n \tva_start(params, advice);\n-\tstrbuf_vaddf(&buf, advice, params);\n+\tvadvise(advice, params, 1, key);\n \tva_end(params);\n-\n-\tstrbuf_addf(&buf, turn_off_instructions, key);\n-\t\n-\tfor (cp = buf.buf; *cp; cp = np) {\n-\t\tnp = strchrnul(cp, '\\n');\n-\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n-\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n-\t\t\t(int)(np - cp), cp,\n-\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n-\t\tif (*np)\n-\t\t\tnp++;\n-\t}\n-\tstrbuf_release(&buf);\n-\n }\n \n int git_default_advice_config(const char *var, const char *value)\n-- \ngitgitgadget\n"},{"id":"392105","messageId":"pull.548.v3.git.1582144442.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v2.git.1581889150.gitgitgadget@gmail.com","subject":"[PATCH v3 0/2] [Outreachy] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-19T20:33:59Z","receivedAt":"2020-02-19T20:34:11Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"Changes in V3:\n\n * Remove the new wrapper advice_push_update_rejected_enabled() (which was\n   added in V2 to handle a special case of having a config variable alias),\n   and replace it by adding switch cases to advice_enabled() (The reason\n   behind this change is that another special case came up while I was\n   migrating the rest of the advise calls to the new APIs.)\n * Remove trailing whitespaces.\n\n\n----------------------------------------------------------------------------\n\nMain changes in V2:\n\n * Rename advise_ng to advise_if_enabled.\n * Add a new advise_enabled() helper.\n * Add a list of config variables names to replace advice_config[] (used by\n   list_config_advices()).\n * Send an enum parameter to the new advise helpers instead of strings.\n * Extract vadvise() from advise() and advise_if enabled().\n\n\n----------------------------------------------------------------------------\n\nThe advice API is currently a little bit confusing to call. quoting from\n[1]:\n\nWhen introducing a new advice message, you would\n\n * come up with advice.frotz configuration variable\n\n * define and declare advice_frotz global variable that defaults to\n   true\n\n * sprinkle calls like this:\n\n  if (advice_frotz)\n    advise(_(\"helpful message about frotz\"));\n\nA new approach was suggested in [1] which this patch is based upon.\n\nA new advise_if_enabled() is introduced to gradually replace advise()\nadvice_enabled() helper is also introduced to be used by those callers who:\n\n * Only need to check the visibility without calling advise() (they call\n   die() or error() instead for example)\n * Need to carry out some heavy processing to display an advice, in this\n   case they'll do: if(advice_enabled(advice_type))  advise(\"some advice message\");\n   \n   \n\nTo introduce a new advice message, the caller needs to:\n\n * Add a new_advice_type to 'enum advice_type'\n * Come up with a new config variable name and add this name to \n   advice_config_keys[]\n * Call advise_if_enabled(new_advice_type, \"advice message to be printed\")\n * Or call advice_enabled(new_advice_type) first and then follow is by\n   advice(\"advice message to be printed\") as explained earlier.\n * Add the new config variable to Documentation/config/advice.txt\n\nThe reason a new list of configuration variables was added to the library is\nto be used by the list_config_advices() function instead of advice_config[].\nAnd we should get rid of advice_config[] once we migrate all the callers to\nuse the new APIs instead of checking the global variables (which we'll get\nrid of as well).\n\nIn the future, we can investigate generating the documentation from the list\nof config variables or vice versa to make introducing a new advice much\neasier, but this approach will do it for now.\n\nV2 makes the process of introducing a new advice longer than V1 and almost\nas long as the original library, but having the advice library responsible\nfor checking the message visibility is still an improvement and in my own\nopinion the new structure makes better sense and makes the library less\nconfusing to use.\n\nAfter this patch the plan is to change the advise() calls to\nadvise_if_enabled() whenever possible, or at least replace the global\nvariables checks by advise_enabled() when advise_if_enabled() is not\nsuitable.\n\n[1] https://public-inbox.org/git/xmqqzhf5cw69.fsf@gitster-ct.c.googlers.com/\n\nHeba Waly (2):\n  advice: revamp advise API\n  advice: extract vadvise() from advise()\n\n Makefile               |  1 +\n advice.c               | 93 ++++++++++++++++++++++++++++++++++++++----\n advice.h               | 53 +++++++++++++++++++++++-\n builtin/tag.c          |  4 +-\n t/helper/test-advise.c | 16 ++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 28 +++++++++++++\n t/t7004-tag.sh         |  1 +\n 9 files changed, 187 insertions(+), 11 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\n\nbase-commit: c7a62075917b3340f908093f63f1161c44ed1475\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-548%2FHebaWaly%2Fadvice_refactoring-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-548/HebaWaly/advice_refactoring-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/548\n\nRange-diff vs v2:\n\n 1:  080d12b5c69 ! 1:  4ab141426f3 advice: refactor advise API\n     @@ -1,6 +1,6 @@\n      Author: Heba Waly <heba.waly@gmail.com>\n      \n     -    advice: refactor advise API\n     +    advice: revamp advise API\n      \n          Currently it's very easy for the advice library's callers to miss\n          checking the visibility step before printing an advice. Also, it makes\n     @@ -63,7 +63,7 @@\n      +\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n      +\t/* make this an alias for backward compatibility */\n      +\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n     -+\t\n     ++\n      +\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n      +\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n      +\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n     @@ -99,7 +99,7 @@\n       \tstrbuf_release(&buf);\n       }\n       \n     -+int advice_enabled(enum advice_type type)\n     ++static int get_config_value(enum advice_type type)\n      +{\n      +\tint value = 1;\n      +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n     @@ -107,11 +107,15 @@\n      +\treturn value;\n      +}\n      +\n     -+int advice_push_update_rejected_enabled(void)\n     ++int advice_enabled(enum advice_type type)\n      +{\n     -+\treturn advice_enabled(PUSH_UPDATE_REJECTED) ||\n     -+\t       advice_enabled(PUSH_UPDATE_REJECTED_ALIAS);\n     -+\t\n     ++\tswitch(type) {\n     ++\tcase PUSH_UPDATE_REJECTED:\n     ++\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n     ++\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n     ++\tdefault:\n     ++\t\treturn get_config_value(type);\n     ++\t}\n      +}\n      +\n      +static const char turn_off_instructions[] =\n     @@ -223,12 +227,7 @@\n      +int advice_enabled(enum advice_type type);\n      +\n      +/**\n     -+ Checks if PUSH_UPDATE_REJECTED advice type is enabled.\n     -+ */\n     -+int advice_push_update_rejected_enabled(void);\n     -+\n     -+/**\n     -+ Checks the visibility of the advice before priniting.\n     ++ Checks the visibility of the advice before printing.\n      + */\n      +void advise_if_enabled(enum advice_type type, const char *advice, ...);\n      +\n     @@ -266,7 +265,7 @@\n      +\tdie(\"usage: %s <advice>\", argv[0]);\n      +\n      +\tsetup_git_directory();\n     -+\t\n     ++\n      +\t//use any advice type for testing\n      +\tadvise_if_enabled(NESTED_TAG, argv[1]);\n      +\n 2:  3e4f52e5526 ! 2:  a2a145c705e advice: extract vadvise() from advise()\n     @@ -38,7 +38,7 @@\n       \tfor (cp = buf.buf; *cp; cp = np) {\n       \t\tnp = strchrnul(cp, '\\n');\n      @@\n     - \t\n     + \t}\n       }\n       \n      -static const char turn_off_instructions[] =\n\n-- \ngitgitgadget\n"},{"id":"392106","messageId":"4ab141426f30dfea19518a78c1de7b5cfbd0802c.1582144442.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v3.git.1582144442.gitgitgadget@gmail.com","subject":"[PATCH v3 1/2] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-19T20:34:00Z","receivedAt":"2020-02-19T20:34:12Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nCurrently it's very easy for the advice library's callers to miss\nchecking the visibility step before printing an advice. Also, it makes\nmore sense for this step to be handled by the advice library.\n\nAdd a new advise_if_enabled function that checks the visibility of\nadvice messages before printing.\n\nAdd a new helper advise_enabled to check the visibility of the advice\nif the caller needs to carry out complicated processing based on that\nvalue.\n\nA list of config variables 'advice_config_keys' is added to be used by\nlist_config_advices() instead of 'advice_config[]' because we'll get\nrid of 'advice_config[]' and the global variables once we migrate all\nthe callers to use the new APIs.\n\nAlso change the advise call in tag library from advise() to\nadvise_if_enabled() to construct an example of the usage of the new\nAPI.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n Makefile               |  1 +\n advice.c               | 92 ++++++++++++++++++++++++++++++++++++++++--\n advice.h               | 53 +++++++++++++++++++++++-\n builtin/tag.c          |  4 +-\n t/helper/test-advise.c | 16 ++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 28 +++++++++++++\n t/t7004-tag.sh         |  1 +\n 9 files changed, 190 insertions(+), 7 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\ndiff --git a/Makefile b/Makefile\nindex 09f98b777ca..ed923a3e818 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -695,6 +695,7 @@ X =\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n+TEST_BUILTINS_OBJS += test-advise.o\n TEST_BUILTINS_OBJS += test-chmtime.o\n TEST_BUILTINS_OBJS += test-config.o\n TEST_BUILTINS_OBJS += test-ctype.o\ndiff --git a/advice.c b/advice.c\nindex 249c60dcf32..345319005ac 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -29,7 +29,6 @@ int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n int advice_graft_file_deprecated = 1;\n int advice_checkout_ambiguous_remote_branch_name = 1;\n-int advice_nested_tag = 1;\n int advice_submodule_alternate_error_strategy_die = 1;\n \n static int advice_use_color = -1;\n@@ -89,13 +88,46 @@ static struct {\n \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n \t{ \"checkoutAmbiguousRemoteBranchName\", &advice_checkout_ambiguous_remote_branch_name },\n-\t{ \"nestedTag\", &advice_nested_tag },\n \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n \n \t/* make this an alias for backward compatibility */\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n+static const char *advice_config_keys[] = {\n+\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n+\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n+\t/* make this an alias for backward compatibility */\n+\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n+\n+\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n+\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n+\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n+\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n+\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n+\t[PUSH_UNQUALIFIED_REF_NAME]\t\t = \"pushUnqualifiedRefName\",\n+\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n+\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n+\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n+\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n+\t[RESET_QUIET_WARNING]\t\t\t = \"resetQuiet\",\n+\t[RESOLVE_CONFLICT]\t\t\t = \"resolveConflict\",\n+\t[SEQUENCER_IN_USE]\t\t\t = \"sequencerInUse\",\n+\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n+\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n+\t[SET_UPSTREAM_FAILURE]\t\t\t = \"setupStreamFailure\",\n+\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n+\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n+\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n+\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n+\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n+\t[WAITING_FOR_EDITOR] \t\t\t = \"waitingForEditor\",\n+\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n+\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n+\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n+\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n+};\n+\n void advise(const char *advice, ...)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -118,6 +150,58 @@ void advise(const char *advice, ...)\n \tstrbuf_release(&buf);\n }\n \n+static int get_config_value(enum advice_type type)\n+{\n+\tint value = 1;\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\tgit_config_get_bool(key, &value);\n+\treturn value;\n+}\n+\n+int advice_enabled(enum advice_type type)\n+{\n+\tswitch(type) {\n+\tcase PUSH_UPDATE_REJECTED:\n+\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n+\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n+\tdefault:\n+\t\treturn get_config_value(type);\n+\t}\n+}\n+\n+static const char turn_off_instructions[] =\n+N_(\"\\n\"\n+   \"Disable this message with \\\"git config %s false\\\"\");\n+\n+void advise_if_enabled(enum advice_type type, const char *advice, ...)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\tva_list params;\n+\tconst char *cp, *np;\n+\t\n+\tif(!advice_enabled(type))\n+\t\treturn;\n+\n+\tva_start(params, advice);\n+\tstrbuf_vaddf(&buf, advice, params);\n+\tva_end(params);\n+\n+\tstrbuf_addf(&buf, turn_off_instructions, key);\n+\t\n+\tfor (cp = buf.buf; *cp; cp = np) {\n+\t\tnp = strchrnul(cp, '\\n');\n+\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n+\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n+\t\t\t(int)(np - cp), cp,\n+\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n+\t\tif (*np)\n+\t\t\tnp++;\n+\t}\n+\tstrbuf_release(&buf);\n+\n+}\n+\n int git_default_advice_config(const char *var, const char *value)\n {\n \tconst char *k, *slot_name;\n@@ -154,8 +238,8 @@ void list_config_advices(struct string_list *list, const char *prefix)\n {\n \tint i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n-\t\tlist_config_item(list, prefix, advice_config[i].name);\n+\tfor (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n+\t\tlist_config_item(list, prefix, advice_config_keys[i]);\n }\n \n int error_resolve_conflict(const char *me)\ndiff --git a/advice.h b/advice.h\nindex b706780614d..c8be662c4b1 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -29,12 +29,63 @@ extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n extern int advice_graft_file_deprecated;\n extern int advice_checkout_ambiguous_remote_branch_name;\n-extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n+/**\n+ To add a new advice, you need to:\n+ - Define an advice_type.\n+ - Add a new entry to advice_config_keys list.\n+ - Add the new config variable to Documentation/config/advice.txt.\n+ - Call advise_if_enabled to print your advice.\n+ */\n+enum advice_type {\n+\tFETCH_SHOW_FORCED_UPDATES = 0,\n+\tPUSH_UPDATE_REJECTED = 1,\n+\tPUSH_UPDATE_REJECTED_ALIAS = 2,\n+\tPUSH_NON_FF_CURRENT = 3,\n+\tPUSH_NON_FF_MATCHING = 4,\n+\tPUSH_ALREADY_EXISTS = 5,\n+\tPUSH_FETCH_FIRST = 6,\n+\tPUSH_NEEDS_FORCE = 7,\n+\tPUSH_UNQUALIFIED_REF_NAME = 8,\n+\tSTATUS_HINTS = 9,\n+\tSTATUS_U_OPTION = 10,\n+\tSTATUS_AHEAD_BEHIND_WARNING = 11,\n+\tCOMMIT_BEFORE_MERGE = 12,\n+\tRESET_QUIET_WARNING = 13,\n+\tRESOLVE_CONFLICT = 14,\n+\tSEQUENCER_IN_USE = 15,\n+\tIMPLICIT_IDENTITY = 16,\n+\tDETACHED_HEAD = 17,\n+\tSET_UPSTREAM_FAILURE = 18,\n+\tOBJECT_NAME_WARNING = 19,\n+\tAMWORKDIR = 20,\n+\tRM_HINTS = 21,\n+\tADD_EMBEDDED_REPO = 22,\n+\tIGNORED_HOOK = 23,\n+\tWAITING_FOR_EDITOR = 24,\n+\tGRAFT_FILE_DEPRECATED = 25,\n+\tCHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME = 26,\n+\tNESTED_TAG = 27,\n+\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE = 28,\n+};\n+\n+\n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\n void advise(const char *advice, ...);\n+\n+/**\n+ Checks if advice type is enabled (can be printed to the user).\n+ Should be called before advise().\n+ */\n+int advice_enabled(enum advice_type type);\n+\n+/**\n+ Checks the visibility of the advice before printing.\n+ */\n+void advise_if_enabled(enum advice_type type, const char *advice, ...);\n+\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex e0a4c253828..247d9075e19 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -231,8 +231,8 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \tif (type <= OBJ_NONE)\n \t\tdie(_(\"bad object type.\"));\n \n-\tif (type == OBJ_TAG && advice_nested_tag)\n-\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n+\tif (type == OBJ_TAG)\n+\t\tadvise_if_enabled(NESTED_TAG, _(message_advice_nested_tag), tag, object_ref);\n \n \tstrbuf_addf(&header,\n \t\t    \"object %s\\n\"\ndiff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\nnew file mode 100644\nindex 00000000000..6d28c9cd5aa\n--- /dev/null\n+++ b/t/helper/test-advise.c\n@@ -0,0 +1,16 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"advice.h\"\n+\n+int cmd__advise_if_enabled(int argc, const char **argv)\n+{\n+\tif (!argv[1])\n+\tdie(\"usage: %s <advice>\", argv[0]);\n+\n+\tsetup_git_directory();\n+\n+\t//use any advice type for testing\n+\tadvise_if_enabled(NESTED_TAG, argv[1]);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d4497..6977badc690 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -14,6 +14,7 @@ struct test_cmd {\n };\n \n static struct test_cmd cmds[] = {\n+\t{ \"advise\", cmd__advise_if_enabled },\n \t{ \"chmtime\", cmd__chmtime },\n \t{ \"config\", cmd__config },\n \t{ \"ctype\", cmd__ctype },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1b..ca5e33b842f 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -4,6 +4,7 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"git-compat-util.h\"\n \n+int cmd__advise_if_enabled(int argc, const char **argv);\n int cmd__chmtime(int argc, const char **argv);\n int cmd__config(int argc, const char **argv);\n int cmd__ctype(int argc, const char **argv);\ndiff --git a/t/t0018-advice.sh b/t/t0018-advice.sh\nnew file mode 100755\nindex 00000000000..f4cdb649d51\n--- /dev/null\n+++ b/t/t0018-advice.sh\n@@ -0,0 +1,28 @@\n+#!/bin/sh\n+\n+test_description='Test advise_if_enabled functionality'\n+\n+. ./test-lib.sh\n+\n+cat > expected <<EOF\n+hint: This is a piece of advice\n+hint: Disable this message with \"git config advice.nestedTag false\"\n+EOF\n+test_expect_success 'advise should be printed when config variable is unset' '\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expected actual\n+'\n+\n+test_expect_success 'advise should be printed when config variable is set to true' '\n+\ttest_config advice.nestedTag true &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expected actual\n+'\n+\n+test_expect_success 'advise should not be printed when config variable is set to false' '\n+\ttest_config advice.nestedTag false &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_done\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 6db92bd3ba6..74b637deb25 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1726,6 +1726,7 @@ test_expect_success 'recursive tagging should give advice' '\n \thint: already a tag. If you meant to tag the object that it points to, use:\n \thint: |\n \thint: \tgit tag -f nested annotated-v4.0^{}\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n \tEOF\n \tgit tag -m nested nested annotated-v4.0 2>actual &&\n \ttest_i18ncmp expect actual\n-- \ngitgitgadget\n\n"},{"id":"392133","messageId":"20200220013730.GA2447@google.com","threadId":"52771","inReplyTo":"4ab141426f30dfea19518a78c1de7b5cfbd0802c.1582144442.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/2] advice: revamp advise API","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-02-20T01:37:30Z","receivedAt":"2020-02-20T01:37:43Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Wed, Feb 19, 2020 at 08:34:00PM +0000, Heba Waly via GitGitGadget wrote:\n> From: Heba Waly <heba.waly@gmail.com>\n> \n> Currently it's very easy for the advice library's callers to miss\n> checking the visibility step before printing an advice. Also, it makes\n> more sense for this step to be handled by the advice library.\n> \n> Add a new advise_if_enabled function that checks the visibility of\n> advice messages before printing.\n> \n> Add a new helper advise_enabled to check the visibility of the advice\n> if the caller needs to carry out complicated processing based on that\n> value.\n> \n> A list of config variables 'advice_config_keys' is added to be used by\n> list_config_advices() instead of 'advice_config[]' because we'll get\n> rid of 'advice_config[]' and the global variables once we migrate all\n> the callers to use the new APIs.\n> \n> Also change the advise call in tag library from advise() to\n> advise_if_enabled() to construct an example of the usage of the new\n> API.\n> \n> Signed-off-by: Heba Waly <heba.waly@gmail.com>\n> ---\n>  Makefile               |  1 +\n>  advice.c               | 92 ++++++++++++++++++++++++++++++++++++++++--\n>  advice.h               | 53 +++++++++++++++++++++++-\n>  builtin/tag.c          |  4 +-\n>  t/helper/test-advise.c | 16 ++++++++\n>  t/helper/test-tool.c   |  1 +\n>  t/helper/test-tool.h   |  1 +\n>  t/t0018-advice.sh      | 28 +++++++++++++\n>  t/t7004-tag.sh         |  1 +\n>  9 files changed, 190 insertions(+), 7 deletions(-)\n>  create mode 100644 t/helper/test-advise.c\n>  create mode 100755 t/t0018-advice.sh\n> \n> diff --git a/Makefile b/Makefile\n> index 09f98b777ca..ed923a3e818 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -695,6 +695,7 @@ X =\n>  \n>  PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n>  \n> +TEST_BUILTINS_OBJS += test-advise.o\n>  TEST_BUILTINS_OBJS += test-chmtime.o\n>  TEST_BUILTINS_OBJS += test-config.o\n>  TEST_BUILTINS_OBJS += test-ctype.o\n> diff --git a/advice.c b/advice.c\n> index 249c60dcf32..345319005ac 100644\n> --- a/advice.c\n> +++ b/advice.c\n> @@ -29,7 +29,6 @@ int advice_ignored_hook = 1;\n>  int advice_waiting_for_editor = 1;\n>  int advice_graft_file_deprecated = 1;\n>  int advice_checkout_ambiguous_remote_branch_name = 1;\n> -int advice_nested_tag = 1;\n>  int advice_submodule_alternate_error_strategy_die = 1;\n>  \n>  static int advice_use_color = -1;\n> @@ -89,13 +88,46 @@ static struct {\n>  \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n>  \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n>  \t{ \"checkoutAmbiguousRemoteBranchName\", &advice_checkout_ambiguous_remote_branch_name },\n> -\t{ \"nestedTag\", &advice_nested_tag },\n>  \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n>  \n>  \t/* make this an alias for backward compatibility */\n>  \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n>  };\n>  \n> +static const char *advice_config_keys[] = {\n> +\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n> +\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n> +\t/* make this an alias for backward compatibility */\n> +\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n> +\n> +\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n> +\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n> +\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n> +\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n> +\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n> +\t[PUSH_UNQUALIFIED_REF_NAME]\t\t = \"pushUnqualifiedRefName\",\n> +\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n> +\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n> +\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n> +\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n> +\t[RESET_QUIET_WARNING]\t\t\t = \"resetQuiet\",\n> +\t[RESOLVE_CONFLICT]\t\t\t = \"resolveConflict\",\n> +\t[SEQUENCER_IN_USE]\t\t\t = \"sequencerInUse\",\n> +\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n> +\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n> +\t[SET_UPSTREAM_FAILURE]\t\t\t = \"setupStreamFailure\",\n> +\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n> +\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n> +\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n> +\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n> +\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n> +\t[WAITING_FOR_EDITOR] \t\t\t = \"waitingForEditor\",\n> +\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n> +\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n> +\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n> +\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n> +};\n> +\n>  void advise(const char *advice, ...)\n>  {\n>  \tstruct strbuf buf = STRBUF_INIT;\n> @@ -118,6 +150,58 @@ void advise(const char *advice, ...)\n>  \tstrbuf_release(&buf);\n>  }\n>  \n> +static int get_config_value(enum advice_type type)\n> +{\n> +\tint value = 1;\n> +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n\nSame comment as your other call for xstrfmt. I think you need to manage\nthe output.\n> +\tgit_config_get_bool(key, &value);\n> +\treturn value;\n> +}\n> +\n> +int advice_enabled(enum advice_type type)\n> +{\n> +\tswitch(type) {\n> +\tcase PUSH_UPDATE_REJECTED:\n> +\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n> +\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n> +\tdefault:\n> +\t\treturn get_config_value(type);\n> +\t}\n> +}\n> +\n> +static const char turn_off_instructions[] =\n> +N_(\"\\n\"\n> +   \"Disable this message with \\\"git config %s false\\\"\");\n> +\n> +void advise_if_enabled(enum advice_type type, const char *advice, ...)\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n\nHmm, doesn't this leak?\n\nOn the surface it looks like xstrfmt can save you a strbuf allocation,\nbut if you check in strbuf.c, it actually allocates and detaches a\nstrbuf for you anyways. I'd argue that it's easier to tell whether\nyou're leaking a strbuf than the result of this call, so you might as\nwell do it that way.\n\n> +\tva_list params;\n> +\tconst char *cp, *np;\n> +\t\n> +\tif(!advice_enabled(type))\n> +\t\treturn;\n> +\n> +\tva_start(params, advice);\n> +\tstrbuf_vaddf(&buf, advice, params);\n> +\tva_end(params);\n> +\n> +\tstrbuf_addf(&buf, turn_off_instructions, key);\n> +\t\n> +\tfor (cp = buf.buf; *cp; cp = np) {\n> +\t\tnp = strchrnul(cp, '\\n');\n> +\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n> +\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n> +\t\t\t(int)(np - cp), cp,\n> +\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n> +\t\tif (*np)\n> +\t\t\tnp++;\n> +\t}\n\nHm. This seems like something the project would use if strbuf knew how\nto do it. Something like strbuf_prefix_lines(strbuf, prefix, delimiter)\nand strbuf_suffix_lines(strbuf, suffix, delimiter)? I think there are\nother types of output which need to prepend each line like this?\n\n> +\tstrbuf_release(&buf);\n> +\n> +}\n> +\n>  int git_default_advice_config(const char *var, const char *value)\n>  {\n>  \tconst char *k, *slot_name;\n> @@ -154,8 +238,8 @@ void list_config_advices(struct string_list *list, const char *prefix)\n>  {\n>  \tint i;\n>  \n> -\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n> -\t\tlist_config_item(list, prefix, advice_config[i].name);\n> +\tfor (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n> +\t\tlist_config_item(list, prefix, advice_config_keys[i]);\n>  }\n>  \n>  int error_resolve_conflict(const char *me)\n> diff --git a/advice.h b/advice.h\n> index b706780614d..c8be662c4b1 100644\n> --- a/advice.h\n> +++ b/advice.h\n> @@ -29,12 +29,63 @@ extern int advice_ignored_hook;\n>  extern int advice_waiting_for_editor;\n>  extern int advice_graft_file_deprecated;\n>  extern int advice_checkout_ambiguous_remote_branch_name;\n> -extern int advice_nested_tag;\n>  extern int advice_submodule_alternate_error_strategy_die;\n>  \n> +/**\n> + To add a new advice, you need to:\n> + - Define an advice_type.\n> + - Add a new entry to advice_config_keys list.\n> + - Add the new config variable to Documentation/config/advice.txt.\n> + - Call advise_if_enabled to print your advice.\n> + */\n> +enum advice_type {\n> +\tFETCH_SHOW_FORCED_UPDATES = 0,\n> +\tPUSH_UPDATE_REJECTED = 1,\n> +\tPUSH_UPDATE_REJECTED_ALIAS = 2,\n> +\tPUSH_NON_FF_CURRENT = 3,\n> +\tPUSH_NON_FF_MATCHING = 4,\n> +\tPUSH_ALREADY_EXISTS = 5,\n> +\tPUSH_FETCH_FIRST = 6,\n> +\tPUSH_NEEDS_FORCE = 7,\n> +\tPUSH_UNQUALIFIED_REF_NAME = 8,\n> +\tSTATUS_HINTS = 9,\n> +\tSTATUS_U_OPTION = 10,\n> +\tSTATUS_AHEAD_BEHIND_WARNING = 11,\n> +\tCOMMIT_BEFORE_MERGE = 12,\n> +\tRESET_QUIET_WARNING = 13,\n> +\tRESOLVE_CONFLICT = 14,\n> +\tSEQUENCER_IN_USE = 15,\n> +\tIMPLICIT_IDENTITY = 16,\n> +\tDETACHED_HEAD = 17,\n> +\tSET_UPSTREAM_FAILURE = 18,\n> +\tOBJECT_NAME_WARNING = 19,\n> +\tAMWORKDIR = 20,\n> +\tRM_HINTS = 21,\n> +\tADD_EMBEDDED_REPO = 22,\n> +\tIGNORED_HOOK = 23,\n> +\tWAITING_FOR_EDITOR = 24,\n> +\tGRAFT_FILE_DEPRECATED = 25,\n> +\tCHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME = 26,\n> +\tNESTED_TAG = 27,\n> +\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE = 28,\n> +};\n\nHmm. I wanted to say, \"Our codebase uses ALL_CAPS or snake_case in enums\nso you could use lowers if you wanted\" - but based on 'git grep -A4\n\"^enum\"' it's actually pretty unusual to see enums with lower-case\nmembers. Dang :)\n\n> +\n> +\n>  int git_default_advice_config(const char *var, const char *value);\n>  __attribute__((format (printf, 1, 2)))\n>  void advise(const char *advice, ...);\n> +\n> +/**\n> + Checks if advice type is enabled (can be printed to the user).\n> + Should be called before advise().\n> + */\n> +int advice_enabled(enum advice_type type);\n> +\n> +/**\n> + Checks the visibility of the advice before printing.\n> + */\n> +void advise_if_enabled(enum advice_type type, const char *advice, ...);\n> +\n>  int error_resolve_conflict(const char *me);\n>  void NORETURN die_resolve_conflict(const char *me);\n>  void NORETURN die_conclude_merge(void);\n> diff --git a/builtin/tag.c b/builtin/tag.c\n> index e0a4c253828..247d9075e19 100644\n> --- a/builtin/tag.c\n> +++ b/builtin/tag.c\n> @@ -231,8 +231,8 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n>  \tif (type <= OBJ_NONE)\n>  \t\tdie(_(\"bad object type.\"));\n>  \n> -\tif (type == OBJ_TAG && advice_nested_tag)\n> -\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n> +\tif (type == OBJ_TAG)\n> +\t\tadvise_if_enabled(NESTED_TAG, _(message_advice_nested_tag), tag, object_ref);\n\nHm, if it was me, I would have put this bit in its own commit. But I see\nthat it's a pretty tiny change...\n\n>  \n>  \tstrbuf_addf(&header,\n>  \t\t    \"object %s\\n\"\n> diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\n> new file mode 100644\n> index 00000000000..6d28c9cd5aa\n> --- /dev/null\n> +++ b/t/helper/test-advise.c\n> @@ -0,0 +1,16 @@\n> +#include \"test-tool.h\"\n> +#include \"cache.h\"\n> +#include \"advice.h\"\n> +\n> +int cmd__advise_if_enabled(int argc, const char **argv)\n> +{\n> +\tif (!argv[1])\n> +\tdie(\"usage: %s <advice>\", argv[0]);\n> +\n> +\tsetup_git_directory();\n> +\n> +\t//use any advice type for testing\nI think this might be misleading - your t0018 does quite a few checks\nexplicitly for the NESTED_TAG advice. Maybe it's better to say something\nlike \"Make sure this agrees with t0018\"? Also nit, I've been told off a\nfew times for using //c++ style comments.\n> +\tadvise_if_enabled(NESTED_TAG, argv[1]);\n> +\n> +\treturn 0;\n> +}\n> diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\n> index f20989d4497..6977badc690 100644\n> --- a/t/helper/test-tool.c\n> +++ b/t/helper/test-tool.c\n> @@ -14,6 +14,7 @@ struct test_cmd {\n>  };\n>  \n>  static struct test_cmd cmds[] = {\n> +\t{ \"advise\", cmd__advise_if_enabled },\n>  \t{ \"chmtime\", cmd__chmtime },\n>  \t{ \"config\", cmd__config },\n>  \t{ \"ctype\", cmd__ctype },\n> diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\n> index 8ed2af71d1b..ca5e33b842f 100644\n> --- a/t/helper/test-tool.h\n> +++ b/t/helper/test-tool.h\n> @@ -4,6 +4,7 @@\n>  #define USE_THE_INDEX_COMPATIBILITY_MACROS\n>  #include \"git-compat-util.h\"\n>  \n> +int cmd__advise_if_enabled(int argc, const char **argv);\n>  int cmd__chmtime(int argc, const char **argv);\n>  int cmd__config(int argc, const char **argv);\n>  int cmd__ctype(int argc, const char **argv);\n> diff --git a/t/t0018-advice.sh b/t/t0018-advice.sh\n> new file mode 100755\n> index 00000000000..f4cdb649d51\n> --- /dev/null\n> +++ b/t/t0018-advice.sh\n> @@ -0,0 +1,28 @@\n> +#!/bin/sh\n> +\n> +test_description='Test advise_if_enabled functionality'\n> +\n> +. ./test-lib.sh\n> +\n> +cat > expected <<EOF\n> +hint: This is a piece of advice\n> +hint: Disable this message with \"git config advice.nestedTag false\"\n> +EOF\n> +test_expect_success 'advise should be printed when config variable is unset' '\n> +\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n> +\ttest_i18ncmp expected actual\n> +'\n> +\n> +test_expect_success 'advise should be printed when config variable is set to true' '\n> +\ttest_config advice.nestedTag true &&\n> +\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n> +\ttest_i18ncmp expected actual\n> +'\n> +\n> +test_expect_success 'advise should not be printed when config variable is set to false' '\n> +\ttest_config advice.nestedTag false &&\n> +\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n> +\ttest_must_be_empty actual\n> +'\n> +\n> +test_done\n> diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n> index 6db92bd3ba6..74b637deb25 100755\n> --- a/t/t7004-tag.sh\n> +++ b/t/t7004-tag.sh\n> @@ -1726,6 +1726,7 @@ test_expect_success 'recursive tagging should give advice' '\n>  \thint: already a tag. If you meant to tag the object that it points to, use:\n>  \thint: |\n>  \thint: \tgit tag -f nested annotated-v4.0^{}\n> +\thint: Disable this message with \"git config advice.nestedTag false\"\n>  \tEOF\n>  \tgit tag -m nested nested annotated-v4.0 2>actual &&\n>  \ttest_i18ncmp expect actual\n> -- \n> gitgitgadget\n> \n"},{"id":"392134","messageId":"20200220014205.GB2447@google.com","threadId":"52771","inReplyTo":"a2a145c705e2751d4ced9cc71e62d5c560adb6e6.1582144442.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/2] advice: extract vadvise() from advise()","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-02-20T01:42:05Z","receivedAt":"2020-02-20T01:42:12Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Wed, Feb 19, 2020 at 08:34:01PM +0000, Heba Waly via GitGitGadget wrote:\n> From: Heba Waly <heba.waly@gmail.com>\n> \n> extract a version of advise() that uses an explict 'va_list' parameter.\n> Call it from advise() and advise_if_enabled() for a functionally\n> equivalent version.\n\nHm, I'd put this patch before the advise_if_enabled() one, so that each\ncommit makes sense by itself (rather than adding a bunch of code last\npatch only to remove it in this patch).\n\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> Signed-off-by: Heba Waly <heba.waly@gmail.com>\n> ---\n> -\tfor (cp = buf.buf; *cp; cp = np) {\n> -\t\tnp = strchrnul(cp, '\\n');\n> -\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n> -\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n> -\t\t\t(int)(np - cp), cp,\n> -\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n> -\t\tif (*np)\n> -\t\t\tnp++;\n> -\t}\n\nI see - this hunk that I commented on in the other review is actually\nduplicated from advise(). Hm, I still think it'd be useful to put this\nfunctionality into strbuf, but I guess since it's not new code you're\nadding there's not a lot of need to sweat about it.\n\n - Emily\n"},{"id":"392229","messageId":"CACg5j262SM1i9FOn4Of2rFgxaur_mOSGs90QBG4fKbSrj05YqQ@mail.gmail.com","threadId":"52771","inReplyTo":"20200220013730.GA2447@google.com","subject":"Re: [PATCH v3 1/2] advice: revamp advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-21T00:31:38Z","receivedAt":"2020-02-21T00:31:52Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Thu, Feb 20, 2020 at 2:37 PM Emily Shaffer <emilyshaffer@google.com> wrote:\n>\n> On Wed, Feb 19, 2020 at 08:34:00PM +0000, Heba Waly via GitGitGadget wrote:\n> > From: Heba Waly <heba.waly@gmail.com>\n> >\n> > +static int get_config_value(enum advice_type type)\n> > +{\n> > +     int value = 1;\n> > +     char *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n>\n> Same comment as your other call for xstrfmt. I think you need to manage\n> the output.\n\nGot it.\n\n> > +void advise_if_enabled(enum advice_type type, const char *advice, ...)\n> > +{\n> > +     struct strbuf buf = STRBUF_INIT;\n> > +     char *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n>\n> Hmm, doesn't this leak?\n>\n> On the surface it looks like xstrfmt can save you a strbuf allocation,\n> but if you check in strbuf.c, it actually allocates and detaches a\n> strbuf for you anyways. I'd argue that it's easier to tell whether\n> you're leaking a strbuf than the result of this call, so you might as\n> well do it that way.\n>\n\nYou're right.\n\n> > +     SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE = 28,\n> > +};\n>\n> Hmm. I wanted to say, \"Our codebase uses ALL_CAPS or snake_case in enums\n> so you could use lowers if you wanted\" - but based on 'git grep -A4\n> \"^enum\"' it's actually pretty unusual to see enums with lower-case\n> members. Dang :)\n>\n\nYeah I thought the same at first too but reached the same result.\n\n> > +\n> > +             advise_if_enabled(NESTED_TAG, _(message_advice_nested_tag), tag, object_ref);\n>\n> Hm, if it was me, I would have put this bit in its own commit. But I see\n> that it's a pretty tiny change...\n>\n\nThat'd have been better of course, don't know why I didn't think of that :)\n\n> > +     //use any advice type for testing\n> I think this might be misleading - your t0018 does quite a few checks\n> explicitly for the NESTED_TAG advice. Maybe it's better to say something\n> like \"Make sure this agrees with t0018\"?\n\nI see your point, I'll need to think more about this one.\n\n> Also nit, I've been told off a\n> few times for using //c++ style comments.\n\nOh ok.\n\nThanks a lot, Emily\n\nHeba\n"},{"id":"392230","messageId":"CACg5j27MDAq6dHD02SUZ-vyiYePUf=B1vJrrjxoj0RbLS=qEng@mail.gmail.com","threadId":"52771","inReplyTo":"20200220014205.GB2447@google.com","subject":"Re: [PATCH v3 2/2] advice: extract vadvise() from advise()","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-21T00:34:50Z","receivedAt":"2020-02-21T00:35:04Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Thu, Feb 20, 2020 at 2:42 PM Emily Shaffer <emilyshaffer@google.com> wrote:\n>\n> On Wed, Feb 19, 2020 at 08:34:01PM +0000, Heba Waly via GitGitGadget wrote:\n> > From: Heba Waly <heba.waly@gmail.com>\n> >\n> > extract a version of advise() that uses an explict 'va_list' parameter.\n> > Call it from advise() and advise_if_enabled() for a functionally\n> > equivalent version.\n>\n> Hm, I'd put this patch before the advise_if_enabled() one, so that each\n> commit makes sense by itself (rather than adding a bunch of code last\n> patch only to remove it in this patch).\n>\n\nYou're right, that was me avoiding the commits re-order conflicts but\nI'll give it a second try.\n\n> >\n> > Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> > Signed-off-by: Heba Waly <heba.waly@gmail.com>\n> > ---\n> > -     for (cp = buf.buf; *cp; cp = np) {\n> > -             np = strchrnul(cp, '\\n');\n> > -             fprintf(stderr, _(\"%shint: %.*s%s\\n\"),\n> > -                     advise_get_color(ADVICE_COLOR_HINT),\n> > -                     (int)(np - cp), cp,\n> > -                     advise_get_color(ADVICE_COLOR_RESET));\n> > -             if (*np)\n> > -                     np++;\n> > -     }\n>\n> I see - this hunk that I commented on in the other review is actually\n> duplicated from advise(). Hm, I still think it'd be useful to put this\n> functionality into strbuf, but I guess since it's not new code you're\n> adding there's not a lot of need to sweat about it.\n>\n\nAgree.\n\n>  - Emily\n\nThank you Emily\n\nHeba\n"},{"id":"392407","messageId":"f668d9b7ca04da1d8adf0a146b48a46559471501.1582557199.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v4.git.1582557199.gitgitgadget@gmail.com","subject":"[PATCH v4 1/3] advice: extract vadvise() from advise()","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-24T15:13:16Z","receivedAt":"2020-02-24T15:13:24Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nIn preparation for a new advice method, extract a version of advise()\nthat uses an explict 'va_list' parameter. Call it from advise() for a\nfunctionally equivalent version.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c | 13 +++++++++----\n 1 file changed, 9 insertions(+), 4 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 249c60dcf32..fd836332dad 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -96,15 +96,12 @@ static struct {\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n-void advise(const char *advice, ...)\n+static void vadvise(const char *advice, va_list params)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tva_list params;\n \tconst char *cp, *np;\n \n-\tva_start(params, advice);\n \tstrbuf_vaddf(&buf, advice, params);\n-\tva_end(params);\n \n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n@@ -118,6 +115,14 @@ void advise(const char *advice, ...)\n \tstrbuf_release(&buf);\n }\n \n+void advise(const char *advice, ...)\n+{\n+\tva_list params;\n+\tva_start(params, advice);\n+\tvadvise(advice, params);\n+\tva_end(params);\n+}\n+\n int git_default_advice_config(const char *var, const char *value)\n {\n \tconst char *k, *slot_name;\n-- \ngitgitgadget\n\n"},{"id":"392408","messageId":"pull.548.v4.git.1582557199.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v3.git.1582144442.gitgitgadget@gmail.com","subject":"[PATCH v4 0/3] [Outreachy] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-24T15:13:15Z","receivedAt":"2020-02-24T15:13:25Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"Main changes in V4:\n\n * Re-order the commits.\n * Free the output after using xstrfmt().\n\n\n----------------------------------------------------------------------------\n\nChanges in V3:\n\n * Remove the new wrapper advice_push_update_rejected_enabled() (which was\n   added in V2 to handle a special case of having a config variable alias),\n   and replace it by adding switch cases to advice_enabled() (The reason\n   behind this change is that another special case came up while I was\n   migrating the rest of the advise calls to the new APIs.)\n * Remove trailing whitespaces.\n\n\n----------------------------------------------------------------------------\n\nMain changes in V2:\n\n * Rename advise_ng to advise_if_enabled.\n * Add a new advise_enabled() helper.\n * Add a list of config variables names to replace advice_config[] (used by\n   list_config_advices()).\n * Send an enum parameter to the new advise helpers instead of strings.\n * Extract vadvise() from advise() and advise_if enabled().\n\n\n----------------------------------------------------------------------------\n\nThe advice API is currently a little bit confusing to call. quoting from\n[1]:\n\nWhen introducing a new advice message, you would\n\n * come up with advice.frotz configuration variable\n\n * define and declare advice_frotz global variable that defaults to\n   true\n\n * sprinkle calls like this:\n\n  if (advice_frotz)\n    advise(_(\"helpful message about frotz\"));\n\nA new approach was suggested in [1] which this patch is based upon.\n\nA new advise_if_enabled() is introduced to gradually replace advise()\nadvice_enabled() helper is also introduced to be used by those callers who:\n\n * Only need to check the visibility without calling advise() (they call\n   die() or error() instead for example)\n * Need to carry out some heavy processing to display an advice, in this\n   case they'll do: if(advice_enabled(advice_type))  advise(\"some advice message\");\n   \n   \n\nTo introduce a new advice message, the caller needs to:\n\n * Add a new_advice_type to 'enum advice_type'\n * Come up with a new config variable name and add this name to \n   advice_config_keys[]\n * Call advise_if_enabled(new_advice_type, \"advice message to be printed\")\n * Or call advice_enabled(new_advice_type) first and then follow is by\n   advice(\"advice message to be printed\") as explained earlier.\n * Add the new config variable to Documentation/config/advice.txt\n\nThe reason a new list of configuration variables was added to the library is\nto be used by the list_config_advices() function instead of advice_config[].\nAnd we should get rid of advice_config[] once we migrate all the callers to\nuse the new APIs instead of checking the global variables (which we'll get\nrid of as well).\n\nIn the future, we can investigate generating the documentation from the list\nof config variables or vice versa to make introducing a new advice much\neasier, but this approach will do it for now.\n\nV2 makes the process of introducing a new advice longer than V1 and almost\nas long as the original library, but having the advice library responsible\nfor checking the message visibility is still an improvement and in my own\nopinion the new structure makes better sense and makes the library less\nconfusing to use.\n\nAfter this patch the plan is to change the advise() calls to\nadvise_if_enabled() whenever possible, or at least replace the global\nvariables checks by advise_enabled() when advise_if_enabled() is not\nsuitable.\n\n[1] https://public-inbox.org/git/xmqqzhf5cw69.fsf@gitster-ct.c.googlers.com/\n\nHeba Waly (3):\n  advice: extract vadvise() from advise()\n  advice: revamp advise API\n  tag: use new advice API to check visibility\n\n Makefile               |  1 +\n advice.c               | 95 ++++++++++++++++++++++++++++++++++++++----\n advice.h               | 53 ++++++++++++++++++++++-\n builtin/tag.c          |  5 ++-\n t/helper/test-advise.c | 19 +++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 28 +++++++++++++\n t/t7004-tag.sh         |  1 +\n 9 files changed, 193 insertions(+), 11 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\n\nbase-commit: c7a62075917b3340f908093f63f1161c44ed1475\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-548%2FHebaWaly%2Fadvice_refactoring-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-548/HebaWaly/advice_refactoring-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/548\n\nRange-diff vs v3:\n\n 2:  a2a145c705e ! 1:  f668d9b7ca0 advice: extract vadvise() from advise()\n     @@ -2,9 +2,9 @@\n      \n          advice: extract vadvise() from advise()\n      \n     -    extract a version of advise() that uses an explict 'va_list' parameter.\n     -    Call it from advise() and advise_if_enabled() for a functionally\n     -    equivalent version.\n     +    In preparation for a new advice method, extract a version of advise()\n     +    that uses an explict 'va_list' parameter. Call it from advise() for a\n     +    functionally equivalent version.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n          Signed-off-by: Heba Waly <heba.waly@gmail.com>\n     @@ -13,16 +13,11 @@\n       --- a/advice.c\n       +++ b/advice.c\n      @@\n     - \t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n     + \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n       };\n       \n      -void advise(const char *advice, ...)\n     -+static const char turn_off_instructions[] =\n     -+N_(\"\\n\"\n     -+   \"Disable this message with \\\"git config %s false\\\"\");\n     -+\n     -+static void vadvise(const char *advice, va_list params,\n     -+\t\t    int display_instructions, char *key)\n     ++static void vadvise(const char *advice, va_list params)\n       {\n       \tstruct strbuf buf = STRBUF_INIT;\n      -\tva_list params;\n     @@ -31,57 +26,21 @@\n      -\tva_start(params, advice);\n       \tstrbuf_vaddf(&buf, advice, params);\n      -\tva_end(params);\n     -+\n     -+\tif(display_instructions)\n     -+\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n       \n       \tfor (cp = buf.buf; *cp; cp = np) {\n       \t\tnp = strchrnul(cp, '\\n');\n      @@\n     - \t}\n     + \tstrbuf_release(&buf);\n       }\n       \n     --static const char turn_off_instructions[] =\n     --N_(\"\\n\"\n     --   \"Disable this message with \\\"git config %s false\\\"\");\n      +void advise(const char *advice, ...)\n      +{\n      +\tva_list params;\n      +\tva_start(params, advice);\n     -+\tvadvise(advice, params, 0, \"\");\n     ++\tvadvise(advice, params);\n      +\tva_end(params);\n      +}\n     - \n     - void advise_if_enabled(enum advice_type type, const char *advice, ...)\n     - {\n     --\tstruct strbuf buf = STRBUF_INIT;\n     --\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n     - \tva_list params;\n     --\tconst char *cp, *np;\n     --\t\n     -+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n      +\n     - \tif(!advice_enabled(type))\n     - \t\treturn;\n     - \n     - \tva_start(params, advice);\n     --\tstrbuf_vaddf(&buf, advice, params);\n     -+\tvadvise(advice, params, 1, key);\n     - \tva_end(params);\n     --\n     --\tstrbuf_addf(&buf, turn_off_instructions, key);\n     --\t\n     --\tfor (cp = buf.buf; *cp; cp = np) {\n     --\t\tnp = strchrnul(cp, '\\n');\n     --\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n     --\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n     --\t\t\t(int)(np - cp), cp,\n     --\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n     --\t\tif (*np)\n     --\t\t\tnp++;\n     --\t}\n     --\tstrbuf_release(&buf);\n     --\n     - }\n     - \n       int git_default_advice_config(const char *var, const char *value)\n     + {\n     + \tconst char *k, *slot_name;\n 1:  4ab141426f3 ! 2:  04c3e5760f6 advice: revamp advise API\n     @@ -40,24 +40,10 @@\n       --- a/advice.c\n       +++ b/advice.c\n      @@\n     - int advice_waiting_for_editor = 1;\n     - int advice_graft_file_deprecated = 1;\n     - int advice_checkout_ambiguous_remote_branch_name = 1;\n     --int advice_nested_tag = 1;\n     - int advice_submodule_alternate_error_strategy_die = 1;\n     - \n     - static int advice_use_color = -1;\n     -@@\n     - \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n     - \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n     - \t{ \"checkoutAmbiguousRemoteBranchName\", &advice_checkout_ambiguous_remote_branch_name },\n     --\t{ \"nestedTag\", &advice_nested_tag },\n     - \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n     - \n     - \t/* make this an alias for backward compatibility */\n       \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n       };\n       \n     +-static void vadvise(const char *advice, va_list params)\n      +static const char *advice_config_keys[] = {\n      +\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n      +\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n     @@ -92,18 +78,39 @@\n      +\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n      +};\n      +\n     - void advise(const char *advice, ...)\n     ++static const char turn_off_instructions[] =\n     ++N_(\"\\n\"\n     ++   \"Disable this message with \\\"git config %s false\\\"\");\n     ++\n     ++static void vadvise(const char *advice, va_list params,\n     ++\t\t    int display_instructions, char *key)\n       {\n       \tstruct strbuf buf = STRBUF_INIT;\n     -@@\n     - \tstrbuf_release(&buf);\n     - }\n     + \tconst char *cp, *np;\n       \n     + \tstrbuf_vaddf(&buf, advice, params);\n     + \n     ++\tif(display_instructions)\n     ++\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n     ++\n     + \tfor (cp = buf.buf; *cp; cp = np) {\n     + \t\tnp = strchrnul(cp, '\\n');\n     + \t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n     +@@\n     + {\n     + \tva_list params;\n     + \tva_start(params, advice);\n     +-\tvadvise(advice, params);\n     ++\tvadvise(advice, params, 0, \"\");\n     ++\tva_end(params);\n     ++}\n     ++\n      +static int get_config_value(enum advice_type type)\n      +{\n      +\tint value = 1;\n      +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n      +\tgit_config_get_bool(key, &value);\n     ++\tfree(key);\n      +\treturn value;\n      +}\n      +\n     @@ -118,42 +125,21 @@\n      +\t}\n      +}\n      +\n     -+static const char turn_off_instructions[] =\n     -+N_(\"\\n\"\n     -+   \"Disable this message with \\\"git config %s false\\\"\");\n     -+\n      +void advise_if_enabled(enum advice_type type, const char *advice, ...)\n      +{\n     -+\tstruct strbuf buf = STRBUF_INIT;\n      +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n      +\tva_list params;\n     -+\tconst char *cp, *np;\n     -+\t\n     ++\n      +\tif(!advice_enabled(type))\n      +\t\treturn;\n      +\n      +\tva_start(params, advice);\n     -+\tstrbuf_vaddf(&buf, advice, params);\n     -+\tva_end(params);\n     -+\n     -+\tstrbuf_addf(&buf, turn_off_instructions, key);\n     -+\t\n     -+\tfor (cp = buf.buf; *cp; cp = np) {\n     -+\t\tnp = strchrnul(cp, '\\n');\n     -+\t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n     -+\t\t\tadvise_get_color(ADVICE_COLOR_HINT),\n     -+\t\t\t(int)(np - cp), cp,\n     -+\t\t\tadvise_get_color(ADVICE_COLOR_RESET));\n     -+\t\tif (*np)\n     -+\t\t\tnp++;\n     -+\t}\n     -+\tstrbuf_release(&buf);\n     -+\n     -+}\n     -+\n     ++\tvadvise(advice, params, 1, key);\n     + \tva_end(params);\n     ++\tfree(key);\n     + }\n     + \n       int git_default_advice_config(const char *var, const char *value)\n     - {\n     - \tconst char *k, *slot_name;\n      @@\n       {\n       \tint i;\n     @@ -170,10 +156,7 @@\n       --- a/advice.h\n       +++ b/advice.h\n      @@\n     - extern int advice_waiting_for_editor;\n     - extern int advice_graft_file_deprecated;\n     - extern int advice_checkout_ambiguous_remote_branch_name;\n     --extern int advice_nested_tag;\n     + extern int advice_nested_tag;\n       extern int advice_submodule_alternate_error_strategy_die;\n       \n      +/**\n     @@ -235,21 +218,6 @@\n       void NORETURN die_resolve_conflict(const char *me);\n       void NORETURN die_conclude_merge(void);\n      \n     - diff --git a/builtin/tag.c b/builtin/tag.c\n     - --- a/builtin/tag.c\n     - +++ b/builtin/tag.c\n     -@@\n     - \tif (type <= OBJ_NONE)\n     - \t\tdie(_(\"bad object type.\"));\n     - \n     --\tif (type == OBJ_TAG && advice_nested_tag)\n     --\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n     -+\tif (type == OBJ_TAG)\n     -+\t\tadvise_if_enabled(NESTED_TAG, _(message_advice_nested_tag), tag, object_ref);\n     - \n     - \tstrbuf_addf(&header,\n     - \t\t    \"object %s\\n\"\n     -\n       diff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\n       new file mode 100644\n       --- /dev/null\n     @@ -266,7 +234,10 @@\n      +\n      +\tsetup_git_directory();\n      +\n     -+\t//use any advice type for testing\n     ++\t/*\n     ++\t  Any advice type can be used for testing, but NESTED_TAG was selected\n     ++\t  here and in t0018 where this command is being executed.\n     ++\t */\n      +\tadvise_if_enabled(NESTED_TAG, argv[1]);\n      +\n      +\treturn 0;\n     @@ -329,15 +300,3 @@\n      +'\n      +\n      +test_done\n     -\n     - diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n     - --- a/t/t7004-tag.sh\n     - +++ b/t/t7004-tag.sh\n     -@@\n     - \thint: already a tag. If you meant to tag the object that it points to, use:\n     - \thint: |\n     - \thint: \tgit tag -f nested annotated-v4.0^{}\n     -+\thint: Disable this message with \"git config advice.nestedTag false\"\n     - \tEOF\n     - \tgit tag -m nested nested annotated-v4.0 2>actual &&\n     - \ttest_i18ncmp expect actual\n -:  ----------- > 3:  3cc0a17123d tag: use new advice API to check visibility\n\n-- \ngitgitgadget\n"},{"id":"392409","messageId":"3cc0a17123dc2575deacb62f762a35fef5f2c185.1582557199.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v4.git.1582557199.gitgitgadget@gmail.com","subject":"[PATCH v4 3/3] tag: use new advice API to check visibility","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-24T15:13:18Z","receivedAt":"2020-02-24T15:13:26Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nFollowing the new helpers added to the advice library,\nreplace the global variable check approach by the new\nAPI calls\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c       | 2 --\n advice.h       | 1 -\n builtin/tag.c  | 5 +++--\n t/t7004-tag.sh | 1 +\n 4 files changed, 4 insertions(+), 5 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex ff25087fa7e..4af5a4205de 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -29,7 +29,6 @@ int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n int advice_graft_file_deprecated = 1;\n int advice_checkout_ambiguous_remote_branch_name = 1;\n-int advice_nested_tag = 1;\n int advice_submodule_alternate_error_strategy_die = 1;\n \n static int advice_use_color = -1;\n@@ -89,7 +88,6 @@ static struct {\n \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n \t{ \"checkoutAmbiguousRemoteBranchName\", &advice_checkout_ambiguous_remote_branch_name },\n-\t{ \"nestedTag\", &advice_nested_tag },\n \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n \n \t/* make this an alias for backward compatibility */\ndiff --git a/advice.h b/advice.h\nindex 61a7ee82827..c8be662c4b1 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -29,7 +29,6 @@ extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n extern int advice_graft_file_deprecated;\n extern int advice_checkout_ambiguous_remote_branch_name;\n-extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n /**\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex e0a4c253828..45e959d5f8f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \tif (type <= OBJ_NONE)\n \t\tdie(_(\"bad object type.\"));\n \n-\tif (type == OBJ_TAG && advice_nested_tag)\n-\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n+\tif (type == OBJ_TAG)\n+\t\tadvise_if_enabled(NESTED_TAG, _(message_advice_nested_tag),\n+\t\t\t\t  tag, object_ref);\n \n \tstrbuf_addf(&header,\n \t\t    \"object %s\\n\"\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 6db92bd3ba6..74b637deb25 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1726,6 +1726,7 @@ test_expect_success 'recursive tagging should give advice' '\n \thint: already a tag. If you meant to tag the object that it points to, use:\n \thint: |\n \thint: \tgit tag -f nested annotated-v4.0^{}\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n \tEOF\n \tgit tag -m nested nested annotated-v4.0 2>actual &&\n \ttest_i18ncmp expect actual\n-- \ngitgitgadget\n"},{"id":"392410","messageId":"04c3e5760f60cf394af66bfdb4fe1d561a093567.1582557199.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v4.git.1582557199.gitgitgadget@gmail.com","subject":"[PATCH v4 2/3] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-24T15:13:17Z","receivedAt":"2020-02-24T15:13:27Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nCurrently it's very easy for the advice library's callers to miss\nchecking the visibility step before printing an advice. Also, it makes\nmore sense for this step to be handled by the advice library.\n\nAdd a new advise_if_enabled function that checks the visibility of\nadvice messages before printing.\n\nAdd a new helper advise_enabled to check the visibility of the advice\nif the caller needs to carry out complicated processing based on that\nvalue.\n\nA list of config variables 'advice_config_keys' is added to be used by\nlist_config_advices() instead of 'advice_config[]' because we'll get\nrid of 'advice_config[]' and the global variables once we migrate all\nthe callers to use the new APIs.\n\nAlso change the advise call in tag library from advise() to\nadvise_if_enabled() to construct an example of the usage of the new\nAPI.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n Makefile               |  1 +\n advice.c               | 84 ++++++++++++++++++++++++++++++++++++++++--\n advice.h               | 52 ++++++++++++++++++++++++++\n t/helper/test-advise.c | 19 ++++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 28 ++++++++++++++\n 7 files changed, 182 insertions(+), 4 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\ndiff --git a/Makefile b/Makefile\nindex 09f98b777ca..ed923a3e818 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -695,6 +695,7 @@ X =\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n+TEST_BUILTINS_OBJS += test-advise.o\n TEST_BUILTINS_OBJS += test-chmtime.o\n TEST_BUILTINS_OBJS += test-config.o\n TEST_BUILTINS_OBJS += test-ctype.o\ndiff --git a/advice.c b/advice.c\nindex fd836332dad..ff25087fa7e 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -96,13 +96,55 @@ static struct {\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n-static void vadvise(const char *advice, va_list params)\n+static const char *advice_config_keys[] = {\n+\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n+\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n+\t/* make this an alias for backward compatibility */\n+\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n+\n+\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n+\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n+\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n+\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n+\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n+\t[PUSH_UNQUALIFIED_REF_NAME]\t\t = \"pushUnqualifiedRefName\",\n+\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n+\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n+\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n+\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n+\t[RESET_QUIET_WARNING]\t\t\t = \"resetQuiet\",\n+\t[RESOLVE_CONFLICT]\t\t\t = \"resolveConflict\",\n+\t[SEQUENCER_IN_USE]\t\t\t = \"sequencerInUse\",\n+\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n+\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n+\t[SET_UPSTREAM_FAILURE]\t\t\t = \"setupStreamFailure\",\n+\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n+\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n+\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n+\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n+\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n+\t[WAITING_FOR_EDITOR] \t\t\t = \"waitingForEditor\",\n+\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n+\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n+\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n+\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n+};\n+\n+static const char turn_off_instructions[] =\n+N_(\"\\n\"\n+   \"Disable this message with \\\"git config %s false\\\"\");\n+\n+static void vadvise(const char *advice, va_list params,\n+\t\t    int display_instructions, char *key)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *cp, *np;\n \n \tstrbuf_vaddf(&buf, advice, params);\n \n+\tif(display_instructions)\n+\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n+\n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n \t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n@@ -119,8 +161,42 @@ void advise(const char *advice, ...)\n {\n \tva_list params;\n \tva_start(params, advice);\n-\tvadvise(advice, params);\n+\tvadvise(advice, params, 0, \"\");\n+\tva_end(params);\n+}\n+\n+static int get_config_value(enum advice_type type)\n+{\n+\tint value = 1;\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\tgit_config_get_bool(key, &value);\n+\tfree(key);\n+\treturn value;\n+}\n+\n+int advice_enabled(enum advice_type type)\n+{\n+\tswitch(type) {\n+\tcase PUSH_UPDATE_REJECTED:\n+\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n+\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n+\tdefault:\n+\t\treturn get_config_value(type);\n+\t}\n+}\n+\n+void advise_if_enabled(enum advice_type type, const char *advice, ...)\n+{\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\tva_list params;\n+\n+\tif(!advice_enabled(type))\n+\t\treturn;\n+\n+\tva_start(params, advice);\n+\tvadvise(advice, params, 1, key);\n \tva_end(params);\n+\tfree(key);\n }\n \n int git_default_advice_config(const char *var, const char *value)\n@@ -159,8 +235,8 @@ void list_config_advices(struct string_list *list, const char *prefix)\n {\n \tint i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n-\t\tlist_config_item(list, prefix, advice_config[i].name);\n+\tfor (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n+\t\tlist_config_item(list, prefix, advice_config_keys[i]);\n }\n \n int error_resolve_conflict(const char *me)\ndiff --git a/advice.h b/advice.h\nindex b706780614d..61a7ee82827 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -32,9 +32,61 @@ extern int advice_checkout_ambiguous_remote_branch_name;\n extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n+/**\n+ To add a new advice, you need to:\n+ - Define an advice_type.\n+ - Add a new entry to advice_config_keys list.\n+ - Add the new config variable to Documentation/config/advice.txt.\n+ - Call advise_if_enabled to print your advice.\n+ */\n+enum advice_type {\n+\tFETCH_SHOW_FORCED_UPDATES = 0,\n+\tPUSH_UPDATE_REJECTED = 1,\n+\tPUSH_UPDATE_REJECTED_ALIAS = 2,\n+\tPUSH_NON_FF_CURRENT = 3,\n+\tPUSH_NON_FF_MATCHING = 4,\n+\tPUSH_ALREADY_EXISTS = 5,\n+\tPUSH_FETCH_FIRST = 6,\n+\tPUSH_NEEDS_FORCE = 7,\n+\tPUSH_UNQUALIFIED_REF_NAME = 8,\n+\tSTATUS_HINTS = 9,\n+\tSTATUS_U_OPTION = 10,\n+\tSTATUS_AHEAD_BEHIND_WARNING = 11,\n+\tCOMMIT_BEFORE_MERGE = 12,\n+\tRESET_QUIET_WARNING = 13,\n+\tRESOLVE_CONFLICT = 14,\n+\tSEQUENCER_IN_USE = 15,\n+\tIMPLICIT_IDENTITY = 16,\n+\tDETACHED_HEAD = 17,\n+\tSET_UPSTREAM_FAILURE = 18,\n+\tOBJECT_NAME_WARNING = 19,\n+\tAMWORKDIR = 20,\n+\tRM_HINTS = 21,\n+\tADD_EMBEDDED_REPO = 22,\n+\tIGNORED_HOOK = 23,\n+\tWAITING_FOR_EDITOR = 24,\n+\tGRAFT_FILE_DEPRECATED = 25,\n+\tCHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME = 26,\n+\tNESTED_TAG = 27,\n+\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE = 28,\n+};\n+\n+\n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\n void advise(const char *advice, ...);\n+\n+/**\n+ Checks if advice type is enabled (can be printed to the user).\n+ Should be called before advise().\n+ */\n+int advice_enabled(enum advice_type type);\n+\n+/**\n+ Checks the visibility of the advice before printing.\n+ */\n+void advise_if_enabled(enum advice_type type, const char *advice, ...);\n+\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\ndiff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\nnew file mode 100644\nindex 00000000000..279cad6460e\n--- /dev/null\n+++ b/t/helper/test-advise.c\n@@ -0,0 +1,19 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"advice.h\"\n+\n+int cmd__advise_if_enabled(int argc, const char **argv)\n+{\n+\tif (!argv[1])\n+\tdie(\"usage: %s <advice>\", argv[0]);\n+\n+\tsetup_git_directory();\n+\n+\t/*\n+\t  Any advice type can be used for testing, but NESTED_TAG was selected\n+\t  here and in t0018 where this command is being executed.\n+\t */\n+\tadvise_if_enabled(NESTED_TAG, argv[1]);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d4497..6977badc690 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -14,6 +14,7 @@ struct test_cmd {\n };\n \n static struct test_cmd cmds[] = {\n+\t{ \"advise\", cmd__advise_if_enabled },\n \t{ \"chmtime\", cmd__chmtime },\n \t{ \"config\", cmd__config },\n \t{ \"ctype\", cmd__ctype },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1b..ca5e33b842f 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -4,6 +4,7 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"git-compat-util.h\"\n \n+int cmd__advise_if_enabled(int argc, const char **argv);\n int cmd__chmtime(int argc, const char **argv);\n int cmd__config(int argc, const char **argv);\n int cmd__ctype(int argc, const char **argv);\ndiff --git a/t/t0018-advice.sh b/t/t0018-advice.sh\nnew file mode 100755\nindex 00000000000..f4cdb649d51\n--- /dev/null\n+++ b/t/t0018-advice.sh\n@@ -0,0 +1,28 @@\n+#!/bin/sh\n+\n+test_description='Test advise_if_enabled functionality'\n+\n+. ./test-lib.sh\n+\n+cat > expected <<EOF\n+hint: This is a piece of advice\n+hint: Disable this message with \"git config advice.nestedTag false\"\n+EOF\n+test_expect_success 'advise should be printed when config variable is unset' '\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expected actual\n+'\n+\n+test_expect_success 'advise should be printed when config variable is set to true' '\n+\ttest_config advice.nestedTag true &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expected actual\n+'\n+\n+test_expect_success 'advise should not be printed when config variable is set to false' '\n+\ttest_config advice.nestedTag false &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"392446","messageId":"20200224220415.GA255447@google.com","threadId":"52771","inReplyTo":"f668d9b7ca04da1d8adf0a146b48a46559471501.1582557199.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 1/3] advice: extract vadvise() from advise()","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-02-24T22:04:15Z","receivedAt":"2020-02-24T22:04:23Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Mon, Feb 24, 2020 at 03:13:16PM +0000, Heba Waly via GitGitGadget wrote:\n> From: Heba Waly <heba.waly@gmail.com>\n> \n> In preparation for a new advice method, extract a version of advise()\n> that uses an explict 'va_list' parameter. Call it from advise() for a\n> functionally equivalent version.\n> \n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> Signed-off-by: Heba Waly <heba.waly@gmail.com>\n\nThis seems very straightforward and now appears to be in the right\ncommit order.\n\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n\n> ---\n>  advice.c | 13 +++++++++----\n>  1 file changed, 9 insertions(+), 4 deletions(-)\n> \n> diff --git a/advice.c b/advice.c\n> index 249c60dcf32..fd836332dad 100644\n> --- a/advice.c\n> +++ b/advice.c\n> @@ -96,15 +96,12 @@ static struct {\n>  \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n>  };\n>  \n> -void advise(const char *advice, ...)\n> +static void vadvise(const char *advice, va_list params)\n>  {\n>  \tstruct strbuf buf = STRBUF_INIT;\n> -\tva_list params;\n>  \tconst char *cp, *np;\n>  \n> -\tva_start(params, advice);\n>  \tstrbuf_vaddf(&buf, advice, params);\n> -\tva_end(params);\n>  \n>  \tfor (cp = buf.buf; *cp; cp = np) {\n>  \t\tnp = strchrnul(cp, '\\n');\n> @@ -118,6 +115,14 @@ void advise(const char *advice, ...)\n>  \tstrbuf_release(&buf);\n>  }\n>  \n> +void advise(const char *advice, ...)\n> +{\n> +\tva_list params;\n> +\tva_start(params, advice);\n> +\tvadvise(advice, params);\n> +\tva_end(params);\n> +}\n> +\n>  int git_default_advice_config(const char *var, const char *value)\n>  {\n>  \tconst char *k, *slot_name;\n> -- \n> gitgitgadget\n> \n"},{"id":"392447","messageId":"xmqqftezod3n.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"04c3e5760f60cf394af66bfdb4fe1d561a093567.1582557199.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 2/3] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-24T22:05:32Z","receivedAt":"2020-02-24T22:05:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> -static void vadvise(const char *advice, va_list params)\n> +static const char *advice_config_keys[] = {\n> +\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n> +\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n> +\t/* make this an alias for backward compatibility */\n> +\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n> +...\n> +\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n> +\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n> +\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n> +};\n\nTerminate the last entry of the array with a trailing comma ',', so\nthat the next person who adds one new advise key to the table at the\nend has to just add only one line without changing any existing lines.\n\nAs you are using the designated initializers for this array, we are\nfree to order the lines in any way that makes most sense to us and\ndo not have to order the lines in numerical order.  In what order\nare these lines sorted right now?  I am tempted to suggest that we\nshould sort alphabetically on the values, i.e. run the contents of\nthe table through \"LC_ALL=C sort -k2,2 -t=\".\n\n> +\n> +static const char turn_off_instructions[] =\n> +N_(\"\\n\"\n> +   \"Disable this message with \\\"git config %s false\\\"\");\n> +\n> +static void vadvise(const char *advice, va_list params,\n> +\t\t    int display_instructions, char *key)\n\nIt may be just me, but I feel uneasy when I see va_list in the\nmiddle of the parameter list.  As it is a mechanism to allow us\nhandle \"the remainder of the arguments\", it logically makes more\nsense to have it as the last parameter.\n\n>  {\n>  \tstruct strbuf buf = STRBUF_INIT;\n>  \tconst char *cp, *np;\n>  \n>  \tstrbuf_vaddf(&buf, advice, params);\n>  \n> +\tif(display_instructions)\n> +\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n\nStyle.  We always have one SP between a syntactic keyword like \"if\"\nand open parenthesis.\n\n> +\n>  \tfor (cp = buf.buf; *cp; cp = np) {\n>  \t\tnp = strchrnul(cp, '\\n');\n>  \t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n> @@ -119,8 +161,42 @@ void advise(const char *advice, ...)\n>  {\n>  \tva_list params;\n>  \tva_start(params, advice);\n> -\tvadvise(advice, params);\n> +\tvadvise(advice, params, 0, \"\");\n> +\tva_end(params);\n> +}\n> +\n> +static int get_config_value(enum advice_type type)\n> +{\n> +\tint value = 1;\n> +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n\nHave a blank line between the decl and the first statement, i.e. here.\n\n> +\tgit_config_get_bool(key, &value);\n> +\tfree(key);\n> +\treturn value;\n> +}\n> +\n> +int advice_enabled(enum advice_type type)\n> +{\n> +\tswitch(type) {\n\nStyle.\n\n> +\tcase PUSH_UPDATE_REJECTED:\n> +\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n> +\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n> +\tdefault:\n> +\t\treturn get_config_value(type);\n> +\t}\n> +}\n> +\n> +void advise_if_enabled(enum advice_type type, const char *advice, ...)\n> +{\n> +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> +\tva_list params;\n> +\n> +\tif(!advice_enabled(type))\n\nStile.\n\n> +\t\treturn;\n> +\n> +\tva_start(params, advice);\n> +\tvadvise(advice, params, 1, key);\n>  \tva_end(params);\n> +\tfree(key);\n>  }\n>  \n>  int git_default_advice_config(const char *var, const char *value)\n> @@ -159,8 +235,8 @@ void list_config_advices(struct string_list *list, const char *prefix)\n>  {\n>  \tint i;\n>  \n> -\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n> -\t\tlist_config_item(list, prefix, advice_config[i].name);\n> +\tfor (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n> +\t\tlist_config_item(list, prefix, advice_config_keys[i]);\n>  }\n>  \n>  int error_resolve_conflict(const char *me)\n> diff --git a/advice.h b/advice.h\n> index b706780614d..61a7ee82827 100644\n> --- a/advice.h\n> +++ b/advice.h\n> @@ -32,9 +32,61 @@ extern int advice_checkout_ambiguous_remote_branch_name;\n>  extern int advice_nested_tag;\n>  extern int advice_submodule_alternate_error_strategy_die;\n>  \n> +/**\n> + To add a new advice, you need to:\n> + - Define an advice_type.\n> + - Add a new entry to advice_config_keys list.\n> + - Add the new config variable to Documentation/config/advice.txt.\n> + - Call advise_if_enabled to print your advice.\n> + */\n\n    /*\n     * Our multi-line comments should look\n     * more like this (multiple style violations\n     * in this patch).\n     */\n\n> +enum advice_type {\n> +\tFETCH_SHOW_FORCED_UPDATES = 0,\n> +\tPUSH_UPDATE_REJECTED = 1,\n> +\tPUSH_UPDATE_REJECTED_ALIAS = 2,\n> +\tPUSH_NON_FF_CURRENT = 3,\n\nDo we need to spell out the values, or is it sufficient to let the\ncompiler automatically count up?  Does any code depend on the exact\nnumeric value of the advice type, or at least at the source code\nlevel the only thing we care about them is that they are distinct?\n\nI'd really want to get rid of these exact value assignments---they\nare source of unnecessary conflicts when two or more topics want to\nadd new advice types of their own.\n\nI also suggest that these are sorted alphabetically.\n\n> + ...\n> +\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE = 28,\n> +};\n\n> +++ b/t/t0018-advice.sh\n> @@ -0,0 +1,28 @@\n> +#!/bin/sh\n> +\n> +test_description='Test advise_if_enabled functionality'\n> +\n> +. ./test-lib.sh\n> +\n> +cat > expected <<EOF\n> +hint: This is a piece of advice\n> +hint: Disable this message with \"git config advice.nestedTag false\"\n> +EOF\n> +test_expect_success 'advise should be printed when config variable is unset' '\n> +\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n> +\ttest_i18ncmp expected actual\n> +'\n\n - Prepare the expected output inside test_expect_success block that\n   uses it.\n\n - There should be no SP between a redirection operator and the\n   filename.\n\n - Here-doc that does not use parameter expansion should use a\n   quoted EOF marker.\n\n - The file that gets compared with \"actual\" is by convention called\n   \"expect\", not \"expected\".\n\ni.e.\n\ntest_expect_success 'advise should be printed when config variable is unset' '\n\tcat >expect <<-\\EOF &&\n\thint: ...\n\thint: ...\n\tEOF\n\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n\ttest_i18ncmp expected actual\n'\n"},{"id":"392448","messageId":"xmqqblpnod05.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"3cc0a17123dc2575deacb62f762a35fef5f2c185.1582557199.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 3/3] tag: use new advice API to check visibility","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-24T22:07:38Z","receivedAt":"2020-02-24T22:07:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Heba Waly <heba.waly@gmail.com>\n>\n> Following the new helpers added to the advice library,\n> replace the global variable check approach by the new\n> API calls\n>\n> Signed-off-by: Heba Waly <heba.waly@gmail.com>\n> ---\n>  advice.c       | 2 --\n>  advice.h       | 1 -\n>  builtin/tag.c  | 5 +++--\n>  t/t7004-tag.sh | 1 +\n>  4 files changed, 4 insertions(+), 5 deletions(-)\n\nNice.\n"},{"id":"392449","messageId":"CAPig+cRWEnQL2Pye=20M0cjHsAFPvLVFWpOv_JM-Gm0hXZUS7w@mail.gmail.com","threadId":"52771","inReplyTo":"xmqqftezod3n.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 2/3] advice: revamp advise API","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-02-24T22:11:11Z","receivedAt":"2020-02-24T22:11:26Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Feb 24, 2020 at 5:05 PM Junio C Hamano <gitster@pobox.com> wrote:\n> \"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> > +test_expect_success 'advise should be printed when config variable is unset' '\n> > +     test-tool advise \"This is a piece of advice\" 2>actual &&\n> > +     test_i18ncmp expected actual\n> > +'\n>\n>  - Prepare the expected output inside test_expect_success block that\n>    uses it.\n>  - There should be no SP between a redirection operator and the\n>    filename.\n>  - Here-doc that does not use parameter expansion should use a\n>    quoted EOF marker.\n>  - The file that gets compared with \"actual\" is by convention called\n>    \"expect\", not \"expected\".\n>\n> test_expect_success 'advise should be printed when config variable is unset' '\n\nAlso, s/advise/advice/ in the test title.\n\n>         cat >expect <<-\\EOF &&\n>         hint: ...\n>         hint: ...\n>         EOF\n>         test-tool advise \"This is a piece of advice\" 2>actual &&\n>         test_i18ncmp expected actual\n> '\n\ns/expected/expect/\n"},{"id":"392453","messageId":"20200224234554.GB255447@google.com","threadId":"52771","inReplyTo":"04c3e5760f60cf394af66bfdb4fe1d561a093567.1582557199.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 2/3] advice: revamp advise API","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-02-24T23:45:54Z","receivedAt":"2020-02-24T23:46:02Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Mon, Feb 24, 2020 at 03:13:17PM +0000, Heba Waly via GitGitGadget wrote:\n> From: Heba Waly <heba.waly@gmail.com>\n> \n> Currently it's very easy for the advice library's callers to miss\n> checking the visibility step before printing an advice. Also, it makes\n> more sense for this step to be handled by the advice library.\n> \n> Add a new advise_if_enabled function that checks the visibility of\n> advice messages before printing.\n> \n> Add a new helper advise_enabled to check the visibility of the advice\n> if the caller needs to carry out complicated processing based on that\n> value.\n> \n> A list of config variables 'advice_config_keys' is added to be used by\n> list_config_advices() instead of 'advice_config[]' because we'll get\n> rid of 'advice_config[]' and the global variables once we migrate all\n> the callers to use the new APIs.\n> \n> Also change the advise call in tag library from advise() to\n> advise_if_enabled() to construct an example of the usage of the new\n> API.\n> \n> Signed-off-by: Heba Waly <heba.waly@gmail.com>\n\nI read Junio's review and agree with that too; but here are some more\nthoughts.\n\n> +static int get_config_value(enum advice_type type)\n> +{\n> +\tint value = 1;\n\nSo we default to true if the config is unset...\n\n> +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> +\tgit_config_get_bool(key, &value);\n...and per config.h, \"when the configuration variable `key` is now\nfound, returns 1 without touching `dest`. Nice, so the default-true\nworks. If some problem is found when converting the value to a bool,\nthis function die()s, so you don't have to check the return value.\n\n> +\tfree(key);\n> +\treturn value;\n> +}\n> +\n> +int advice_enabled(enum advice_type type)\n> +{\n> +\tswitch(type) {\n> +\tcase PUSH_UPDATE_REJECTED:\n> +\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n> +\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\nSo I can ask advice_enabled(PUSH_UPDATE_REJECTED) and still be told\n'false' if earlier I set \"advice.pushNonfastForward\" to 0.\n\nI wondered if this was really identical behavior to how this thing\nworked before.\n\nBefore, it looks like we use the older config callback method\n(git_default_advice_config()): we read each config option the user has,\nin order, and then we check it against each member of advice_config, and\nif it's a match, we set the appropriate value. That means that\nadvice_push_update_rejected is determined by whichever config is set\nlast, e.g. a config like so:\n\nglobal: advice.pushUpdateRejected = 1\nlocal:  advice.pushNonFastForward = 0\n\nresults in advice_push_update_rejected == 0.\n\nNow, though, you consider the values of both. In the example above, you\nhave the same value; but if you reverse the values:\n\nglobal: advice.pushUpdateRejected = 0\nlocal:  advice.pushNonFastForward = 1\n\nthen your new code says advice_enabled(PUSH_UPDATE_REJECTED) == 0, and\nthe old codepath says advice_push_update_rejected = 1.\n\nAlthough, using the config lookup methods as you are, I don't think you\ncan make it stay the same - I don't think there's a way to compare the\nrelative position of two different configs, is there?\n\nIs this such a big deal? My gut says no; my gut says if someone had\nadvice.pushNonFastForward set, they aren't touching their config to\nunset it, which means they aren't touching their config to fiddle with\nadvice.pushUpdateRejected either.\n\nMaybe someone who was around when this alias was added can provide some\ncontext?\n\n - Emily\n"},{"id":"392454","messageId":"20200224234654.GC255447@google.com","threadId":"52771","inReplyTo":"3cc0a17123dc2575deacb62f762a35fef5f2c185.1582557199.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 3/3] tag: use new advice API to check visibility","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-02-24T23:46:54Z","receivedAt":"2020-02-24T23:47:00Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Mon, Feb 24, 2020 at 03:13:18PM +0000, Heba Waly via GitGitGadget wrote:\n> From: Heba Waly <heba.waly@gmail.com>\n> \n> Following the new helpers added to the advice library,\n> replace the global variable check approach by the new\n> API calls\n> \n> Signed-off-by: Heba Waly <heba.waly@gmail.com>\n> ---\n>  advice.c       | 2 --\n>  advice.h       | 1 -\n>  builtin/tag.c  | 5 +++--\n>  t/t7004-tag.sh | 1 +\n>  4 files changed, 4 insertions(+), 5 deletions(-)\n\nMore deleted lines than added lines always makes me a little happier ;)\n\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n"},{"id":"392455","messageId":"CACg5j25XQLC1bYxm4244UQddL5AVENg-0XwJQFvLpdQ-n1hVqg@mail.gmail.com","threadId":"52771","inReplyTo":"xmqqftezod3n.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 2/3] advice: revamp advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-24T23:49:57Z","receivedAt":"2020-02-24T23:50:11Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Tue, Feb 25, 2020 at 11:05 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > -static void vadvise(const char *advice, va_list params)\n> > +static const char *advice_config_keys[] = {\n> > +     [FETCH_SHOW_FORCED_UPDATES]              = \"fetchShowForcedUpdates\",\n> > +     [PUSH_UPDATE_REJECTED]                   = \"pushUpdateRejected\",\n> > +     /* make this an alias for backward compatibility */\n> > +     [PUSH_UPDATE_REJECTED_ALIAS]             = \"pushNonFastForward\",\n> > +...\n> > +     [CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]  = \"checkoutAmbiguousRemoteBranchName\",\n> > +     [NESTED_TAG]                             = \"nestedTag\",\n> > +     [SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n> > +};\n>\n> Terminate the last entry of the array with a trailing comma ',', so\n> that the next person who adds one new advise key to the table at the\n> end has to just add only one line without changing any existing lines.\n>\n\nSure, thank you for the explanation, that makes sense.\n\n> As you are using the designated initializers for this array, we are\n> free to order the lines in any way that makes most sense to us and\n> do not have to order the lines in numerical order.  In what order\n> are these lines sorted right now?  I am tempted to suggest that we\n> should sort alphabetically on the values, i.e. run the contents of\n> the table through \"LC_ALL=C sort -k2,2 -t=\".\n>\n\nCurrently it's sorted in the same order that was originally used for\nadvice_config[] and the global variables, which I assume is the order\nin which every config variable was added to the code. Sorting\nalphabetically will be neater of course.\n\n> > +\n> > +static const char turn_off_instructions[] =\n> > +N_(\"\\n\"\n> > +   \"Disable this message with \\\"git config %s false\\\"\");\n> > +\n> > +static void vadvise(const char *advice, va_list params,\n> > +                 int display_instructions, char *key)\n>\n> It may be just me, but I feel uneasy when I see va_list in the\n> middle of the parameter list.  As it is a mechanism to allow us\n> handle \"the remainder of the arguments\", it logically makes more\n> sense to have it as the last parameter.\n>\n\nI don't mind it either way, so yeah no problem, I'll change that.\n\n> >  {\n> >       struct strbuf buf = STRBUF_INIT;\n> >       const char *cp, *np;\n> >\n> >       strbuf_vaddf(&buf, advice, params);\n> >\n> > +     if(display_instructions)\n> > +             strbuf_addf(&buf, turn_off_instructions, key);\n>\n> Style.  We always have one SP between a syntactic keyword like \"if\"\n> and open parenthesis.\n\nNoted.\n\n>\n> > +\n> >       for (cp = buf.buf; *cp; cp = np) {\n> >               np = strchrnul(cp, '\\n');\n> >               fprintf(stderr, _(\"%shint: %.*s%s\\n\"),\n> > @@ -119,8 +161,42 @@ void advise(const char *advice, ...)\n> >  {\n> >       va_list params;\n> >       va_start(params, advice);\n> > -     vadvise(advice, params);\n> > +     vadvise(advice, params, 0, \"\");\n> > +     va_end(params);\n> > +}\n> > +\n> > +static int get_config_value(enum advice_type type)\n> > +{\n> > +     int value = 1;\n> > +     char *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n>\n> Have a blank line between the decl and the first statement, i.e. here.\n>\n\nRight, I missed this one.\n\n> > +     git_config_get_bool(key, &value);\n> > +     free(key);\n> > +     return value;\n> > +}\n> > +\n> > +int advice_enabled(enum advice_type type)\n> > +{\n> > +     switch(type) {\n>\n> Style.\n\nNoted.\n\n>\n> > +     case PUSH_UPDATE_REJECTED:\n> > +             return get_config_value(PUSH_UPDATE_REJECTED) &&\n> > +                    get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n> > +     default:\n> > +             return get_config_value(type);\n> > +     }\n> > +}\n> > +\n> > +void advise_if_enabled(enum advice_type type, const char *advice, ...)\n> > +{\n> > +     char *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> > +     va_list params;\n> > +\n> > +     if(!advice_enabled(type))\n>\n> Stile.\n\nTakes time changing the style I've been using for years, sorry.\n\n>\n> > +             return;\n> > +\n> > +     va_start(params, advice);\n> > +     vadvise(advice, params, 1, key);\n> >       va_end(params);\n> > +     free(key);\n> >  }\n> >\n> >  int git_default_advice_config(const char *var, const char *value)\n> > @@ -159,8 +235,8 @@ void list_config_advices(struct string_list *list, const char *prefix)\n> >  {\n> >       int i;\n> >\n> > -     for (i = 0; i < ARRAY_SIZE(advice_config); i++)\n> > -             list_config_item(list, prefix, advice_config[i].name);\n> > +     for (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n> > +             list_config_item(list, prefix, advice_config_keys[i]);\n> >  }\n> >\n> >  int error_resolve_conflict(const char *me)\n> > diff --git a/advice.h b/advice.h\n> > index b706780614d..61a7ee82827 100644\n> > --- a/advice.h\n> > +++ b/advice.h\n> > @@ -32,9 +32,61 @@ extern int advice_checkout_ambiguous_remote_branch_name;\n> >  extern int advice_nested_tag;\n> >  extern int advice_submodule_alternate_error_strategy_die;\n> >\n> > +/**\n> > + To add a new advice, you need to:\n> > + - Define an advice_type.\n> > + - Add a new entry to advice_config_keys list.\n> > + - Add the new config variable to Documentation/config/advice.txt.\n> > + - Call advise_if_enabled to print your advice.\n> > + */\n>\n>     /*\n>      * Our multi-line comments should look\n>      * more like this (multiple style violations\n>      * in this patch).\n>      */\n>\n\nGot it.\n\n> > +enum advice_type {\n> > +     FETCH_SHOW_FORCED_UPDATES = 0,\n> > +     PUSH_UPDATE_REJECTED = 1,\n> > +     PUSH_UPDATE_REJECTED_ALIAS = 2,\n> > +     PUSH_NON_FF_CURRENT = 3,\n>\n> Do we need to spell out the values, or is it sufficient to let the\n> compiler automatically count up?  Does any code depend on the exact\n> numeric value of the advice type, or at least at the source code\n> level the only thing we care about them is that they are distinct?\n>\n> I'd really want to get rid of these exact value assignments---they\n> are source of unnecessary conflicts when two or more topics want to\n> add new advice types of their own.\n>\n\nYes, we can get rid of them.\n\n> I also suggest that these are sorted alphabetically.\n\nAs we'll get rid of the value assignments, so sorting alphabetically\nshould not introduce problems when adding new advice types, will do.\n\n> > + ...\n> > +     SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE = 28,\n> > +};\n>\n> > +++ b/t/t0018-advice.sh\n> > @@ -0,0 +1,28 @@\n> > +#!/bin/sh\n> > +\n> > +test_description='Test advise_if_enabled functionality'\n> > +\n> > +. ./test-lib.sh\n> > +\n> > +cat > expected <<EOF\n> > +hint: This is a piece of advice\n> > +hint: Disable this message with \"git config advice.nestedTag false\"\n> > +EOF\n> > +test_expect_success 'advise should be printed when config variable is unset' '\n> > +     test-tool advise \"This is a piece of advice\" 2>actual &&\n> > +     test_i18ncmp expected actual\n> > +'\n>\n>  - Prepare the expected output inside test_expect_success block that\n>    uses it.\n>\n>  - There should be no SP between a redirection operator and the\n>    filename.\n>\n>  - Here-doc that does not use parameter expansion should use a\n>    quoted EOF marker.\n>\n>  - The file that gets compared with \"actual\" is by convention called\n>    \"expect\", not \"expected\".\n>\n> i.e.\n>\n> test_expect_success 'advise should be printed when config variable is unset' '\n>         cat >expect <<-\\EOF &&\n>         hint: ...\n>         hint: ...\n>         EOF\n>         test-tool advise \"This is a piece of advice\" 2>actual &&\n>         test_i18ncmp expected actual\n> '\n\nNoted.\n\nWill send an updated version soon.\n\nThank you,\nHeba\n"},{"id":"392456","messageId":"CACg5j27MmFgo5vLse2RroRPUaaOh_nH2cWpvLKZhuFmQFw939A@mail.gmail.com","threadId":"52771","inReplyTo":"CAPig+cRWEnQL2Pye=20M0cjHsAFPvLVFWpOv_JM-Gm0hXZUS7w@mail.gmail.com","subject":"Re: [PATCH v4 2/3] advice: revamp advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-24T23:51:13Z","receivedAt":"2020-02-24T23:51:27Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Tue, Feb 25, 2020 at 11:11 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Mon, Feb 24, 2020 at 5:05 PM Junio C Hamano <gitster@pobox.com> wrote:\n> > \"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> > > +test_expect_success 'advise should be printed when config variable is unset' '\n> > > +     test-tool advise \"This is a piece of advice\" 2>actual &&\n> > > +     test_i18ncmp expected actual\n> > > +'\n> >\n> >  - Prepare the expected output inside test_expect_success block that\n> >    uses it.\n> >  - There should be no SP between a redirection operator and the\n> >    filename.\n> >  - Here-doc that does not use parameter expansion should use a\n> >    quoted EOF marker.\n> >  - The file that gets compared with \"actual\" is by convention called\n> >    \"expect\", not \"expected\".\n> >\n> > test_expect_success 'advise should be printed when config variable is unset' '\n>\n> Also, s/advise/advice/ in the test title.\n>\n> >         cat >expect <<-\\EOF &&\n> >         hint: ...\n> >         hint: ...\n> >         EOF\n> >         test-tool advise \"This is a piece of advice\" 2>actual &&\n> >         test_i18ncmp expected actual\n> > '\n>\n> s/expected/expect/\n\nNoted, thank you.\n\nHeba\n"},{"id":"392468","messageId":"pull.548.v5.git.1582628141.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v4.git.1582557199.gitgitgadget@gmail.com","subject":"[PATCH v5 0/3] [Outreachy] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-25T10:55:37Z","receivedAt":"2020-02-25T10:55:47Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"Main changes in V4:\n\n * Re-order the commits.\n * Free the output after using xstrfmt().\n\n\n----------------------------------------------------------------------------\n\nChanges in V3:\n\n * Remove the new wrapper advice_push_update_rejected_enabled() (which was\n   added in V2 to handle a special case of having a config variable alias),\n   and replace it by adding switch cases to advice_enabled() (The reason\n   behind this change is that another special case came up while I was\n   migrating the rest of the advise calls to the new APIs.)\n * Remove trailing whitespaces.\n\n\n----------------------------------------------------------------------------\n\nMain changes in V2:\n\n * Rename advise_ng to advise_if_enabled.\n * Add a new advise_enabled() helper.\n * Add a list of config variables names to replace advice_config[] (used by\n   list_config_advices()).\n * Send an enum parameter to the new advise helpers instead of strings.\n * Extract vadvise() from advise() and advise_if enabled().\n\n\n----------------------------------------------------------------------------\n\nThe advice API is currently a little bit confusing to call. quoting from\n[1]:\n\nWhen introducing a new advice message, you would\n\n * come up with advice.frotz configuration variable\n\n * define and declare advice_frotz global variable that defaults to\n   true\n\n * sprinkle calls like this:\n\n  if (advice_frotz)\n    advise(_(\"helpful message about frotz\"));\n\nA new approach was suggested in [1] which this patch is based upon.\n\nA new advise_if_enabled() is introduced to gradually replace advise()\nadvice_enabled() helper is also introduced to be used by those callers who:\n\n * Only need to check the visibility without calling advise() (they call\n   die() or error() instead for example)\n * Need to carry out some heavy processing to display an advice, in this\n   case they'll do: if(advice_enabled(advice_type))  advise(\"some advice message\");\n   \n   \n\nTo introduce a new advice message, the caller needs to:\n\n * Add a new_advice_type to 'enum advice_type'\n * Come up with a new config variable name and add this name to \n   advice_config_keys[]\n * Call advise_if_enabled(new_advice_type, \"advice message to be printed\")\n * Or call advice_enabled(new_advice_type) first and then follow is by\n   advice(\"advice message to be printed\") as explained earlier.\n * Add the new config variable to Documentation/config/advice.txt\n\nThe reason a new list of configuration variables was added to the library is\nto be used by the list_config_advices() function instead of advice_config[].\nAnd we should get rid of advice_config[] once we migrate all the callers to\nuse the new APIs instead of checking the global variables (which we'll get\nrid of as well).\n\nIn the future, we can investigate generating the documentation from the list\nof config variables or vice versa to make introducing a new advice much\neasier, but this approach will do it for now.\n\nV2 makes the process of introducing a new advice longer than V1 and almost\nas long as the original library, but having the advice library responsible\nfor checking the message visibility is still an improvement and in my own\nopinion the new structure makes better sense and makes the library less\nconfusing to use.\n\nAfter this patch the plan is to change the advise() calls to\nadvise_if_enabled() whenever possible, or at least replace the global\nvariables checks by advise_enabled() when advise_if_enabled() is not\nsuitable.\n\n[1] https://public-inbox.org/git/xmqqzhf5cw69.fsf@gitster-ct.c.googlers.com/\n\nHeba Waly (3):\n  advice: extract vadvise() from advise()\n  advice: revamp advise API\n  tag: use new advice API to check visibility\n\n Makefile               |  1 +\n advice.c               | 97 ++++++++++++++++++++++++++++++++++++++----\n advice.h               | 53 ++++++++++++++++++++++-\n builtin/tag.c          |  5 ++-\n t/helper/test-advise.c | 19 +++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 32 ++++++++++++++\n t/t7004-tag.sh         |  1 +\n 9 files changed, 199 insertions(+), 11 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\n\nbase-commit: c7a62075917b3340f908093f63f1161c44ed1475\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-548%2FHebaWaly%2Fadvice_refactoring-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-548/HebaWaly/advice_refactoring-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/548\n\nRange-diff vs v4:\n\n 1:  f668d9b7ca0 = 1:  f668d9b7ca0 advice: extract vadvise() from advise()\n 2:  04c3e5760f6 ! 2:  b7f10d060a4 advice: revamp advise API\n     @@ -45,52 +45,53 @@\n       \n      -static void vadvise(const char *advice, va_list params)\n      +static const char *advice_config_keys[] = {\n     ++\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n     ++\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n     ++\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n     ++\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n     ++\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n      +\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n     -+\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n     ++\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n     ++\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n     ++\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n     ++\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n     ++\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n     ++\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n     ++\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n     ++\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n     ++\n      +\t/* make this an alias for backward compatibility */\n      +\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n      +\n      +\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n      +\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n     -+\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n     -+\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n     -+\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n      +\t[PUSH_UNQUALIFIED_REF_NAME]\t\t = \"pushUnqualifiedRefName\",\n     -+\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n     -+\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n     -+\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n     -+\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n     ++\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n      +\t[RESET_QUIET_WARNING]\t\t\t = \"resetQuiet\",\n      +\t[RESOLVE_CONFLICT]\t\t\t = \"resolveConflict\",\n     ++\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n      +\t[SEQUENCER_IN_USE]\t\t\t = \"sequencerInUse\",\n     -+\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n     -+\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n      +\t[SET_UPSTREAM_FAILURE]\t\t\t = \"setupStreamFailure\",\n     -+\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n     -+\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n     -+\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n     -+\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n     -+\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n     ++\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n     ++\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n     ++\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n     ++\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\",\n      +\t[WAITING_FOR_EDITOR] \t\t\t = \"waitingForEditor\",\n     -+\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n     -+\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n     -+\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n     -+\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\"\n      +};\n      +\n      +static const char turn_off_instructions[] =\n      +N_(\"\\n\"\n      +   \"Disable this message with \\\"git config %s false\\\"\");\n      +\n     -+static void vadvise(const char *advice, va_list params,\n     -+\t\t    int display_instructions, char *key)\n     ++static void vadvise(const char *advice, int display_instructions,\n     ++\t\t    char *key, va_list params)\n       {\n       \tstruct strbuf buf = STRBUF_INIT;\n       \tconst char *cp, *np;\n       \n       \tstrbuf_vaddf(&buf, advice, params);\n       \n     -+\tif(display_instructions)\n     ++\tif (display_instructions)\n      +\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n      +\n       \tfor (cp = buf.buf; *cp; cp = np) {\n     @@ -101,7 +102,7 @@\n       \tva_list params;\n       \tva_start(params, advice);\n      -\tvadvise(advice, params);\n     -+\tvadvise(advice, params, 0, \"\");\n     ++\tvadvise(advice, 0, \"\", params);\n      +\tva_end(params);\n      +}\n      +\n     @@ -109,6 +110,7 @@\n      +{\n      +\tint value = 1;\n      +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n     ++\n      +\tgit_config_get_bool(key, &value);\n      +\tfree(key);\n      +\treturn value;\n     @@ -116,7 +118,7 @@\n      +\n      +int advice_enabled(enum advice_type type)\n      +{\n     -+\tswitch(type) {\n     ++\tswitch (type) {\n      +\tcase PUSH_UPDATE_REJECTED:\n      +\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n      +\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n     @@ -130,11 +132,11 @@\n      +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n      +\tva_list params;\n      +\n     -+\tif(!advice_enabled(type))\n     ++\tif (!advice_enabled(type))\n      +\t\treturn;\n      +\n      +\tva_start(params, advice);\n     -+\tvadvise(advice, params, 1, key);\n     ++\tvadvise(advice, 1, key, params);\n       \tva_end(params);\n      +\tfree(key);\n       }\n     @@ -159,43 +161,43 @@\n       extern int advice_nested_tag;\n       extern int advice_submodule_alternate_error_strategy_die;\n       \n     -+/**\n     -+ To add a new advice, you need to:\n     -+ - Define an advice_type.\n     -+ - Add a new entry to advice_config_keys list.\n     -+ - Add the new config variable to Documentation/config/advice.txt.\n     -+ - Call advise_if_enabled to print your advice.\n     ++/*\n     ++ * To add a new advice, you need to:\n     ++ * Define an advice_type.\n     ++ * Add a new entry to advice_config_keys list.\n     ++ * Add the new config variable to Documentation/config/advice.txt.\n     ++ * Call advise_if_enabled to print your advice.\n      + */\n      +enum advice_type {\n     -+\tFETCH_SHOW_FORCED_UPDATES = 0,\n     -+\tPUSH_UPDATE_REJECTED = 1,\n     -+\tPUSH_UPDATE_REJECTED_ALIAS = 2,\n     -+\tPUSH_NON_FF_CURRENT = 3,\n     -+\tPUSH_NON_FF_MATCHING = 4,\n     -+\tPUSH_ALREADY_EXISTS = 5,\n     -+\tPUSH_FETCH_FIRST = 6,\n     -+\tPUSH_NEEDS_FORCE = 7,\n     -+\tPUSH_UNQUALIFIED_REF_NAME = 8,\n     -+\tSTATUS_HINTS = 9,\n     -+\tSTATUS_U_OPTION = 10,\n     -+\tSTATUS_AHEAD_BEHIND_WARNING = 11,\n     -+\tCOMMIT_BEFORE_MERGE = 12,\n     -+\tRESET_QUIET_WARNING = 13,\n     -+\tRESOLVE_CONFLICT = 14,\n     -+\tSEQUENCER_IN_USE = 15,\n     -+\tIMPLICIT_IDENTITY = 16,\n     -+\tDETACHED_HEAD = 17,\n     -+\tSET_UPSTREAM_FAILURE = 18,\n     -+\tOBJECT_NAME_WARNING = 19,\n     -+\tAMWORKDIR = 20,\n     -+\tRM_HINTS = 21,\n     -+\tADD_EMBEDDED_REPO = 22,\n     -+\tIGNORED_HOOK = 23,\n     -+\tWAITING_FOR_EDITOR = 24,\n     -+\tGRAFT_FILE_DEPRECATED = 25,\n     -+\tCHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME = 26,\n     -+\tNESTED_TAG = 27,\n     -+\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE = 28,\n     ++\tADD_EMBEDDED_REPO,\n     ++\tAMWORKDIR,\n     ++\tCHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n     ++\tCOMMIT_BEFORE_MERGE,\n     ++\tDETACHED_HEAD,\n     ++\tFETCH_SHOW_FORCED_UPDATES,\n     ++\tGRAFT_FILE_DEPRECATED,\n     ++\tIGNORED_HOOK,\n     ++\tIMPLICIT_IDENTITY,\n     ++\tNESTED_TAG,\n     ++\tOBJECT_NAME_WARNING,\n     ++\tPUSH_ALREADY_EXISTS,\n     ++\tPUSH_FETCH_FIRST,\n     ++\tPUSH_NEEDS_FORCE,\n     ++\tPUSH_NON_FF_CURRENT,\n     ++\tPUSH_NON_FF_MATCHING,\n     ++\tPUSH_UNQUALIFIED_REF_NAME,\n     ++\tPUSH_UPDATE_REJECTED_ALIAS,\n     ++\tPUSH_UPDATE_REJECTED,\n     ++\tRESET_QUIET_WARNING,\n     ++\tRESOLVE_CONFLICT,\n     ++\tRM_HINTS,\n     ++\tSEQUENCER_IN_USE,\n     ++\tSET_UPSTREAM_FAILURE,\n     ++\tSTATUS_AHEAD_BEHIND_WARNING,\n     ++\tSTATUS_HINTS,\n     ++\tSTATUS_U_OPTION,\n     ++\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n     ++\tWAITING_FOR_EDITOR,\n      +};\n      +\n      +\n     @@ -278,22 +280,26 @@\n      +\n      +. ./test-lib.sh\n      +\n     -+cat > expected <<EOF\n     -+hint: This is a piece of advice\n     -+hint: Disable this message with \"git config advice.nestedTag false\"\n     -+EOF\n     -+test_expect_success 'advise should be printed when config variable is unset' '\n     ++test_expect_success 'advice should be printed when config variable is unset' '\n     ++\tcat >expect <<-\\EOF &&\n     ++\thint: This is a piece of advice\n     ++\thint: Disable this message with \"git config advice.nestedTag false\"\n     ++\tEOF\n      +\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n     -+\ttest_i18ncmp expected actual\n     ++\ttest_i18ncmp expect actual\n      +'\n      +\n     -+test_expect_success 'advise should be printed when config variable is set to true' '\n     ++test_expect_success 'advice should be printed when config variable is set to true' '\n     ++\tcat >expect <<-\\EOF &&\n     ++\thint: This is a piece of advice\n     ++\thint: Disable this message with \"git config advice.nestedTag false\"\n     ++\tEOF\n      +\ttest_config advice.nestedTag true &&\n      +\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n     -+\ttest_i18ncmp expected actual\n     ++\ttest_i18ncmp expect actual\n      +'\n      +\n     -+test_expect_success 'advise should not be printed when config variable is set to false' '\n     ++test_expect_success 'advice should not be printed when config variable is set to false' '\n      +\ttest_config advice.nestedTag false &&\n      +\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n      +\ttest_must_be_empty actual\n 3:  3cc0a17123d ! 3:  01b195ebe1d tag: use new advice API to check visibility\n     @@ -38,7 +38,7 @@\n      -extern int advice_nested_tag;\n       extern int advice_submodule_alternate_error_strategy_die;\n       \n     - /**\n     + /*\n      \n       diff --git a/builtin/tag.c b/builtin/tag.c\n       --- a/builtin/tag.c\n\n-- \ngitgitgadget\n"},{"id":"392465","messageId":"f668d9b7ca04da1d8adf0a146b48a46559471501.1582628141.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v5.git.1582628141.gitgitgadget@gmail.com","subject":"[PATCH v5 1/3] advice: extract vadvise() from advise()","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-25T10:55:38Z","receivedAt":"2020-02-25T10:55:48Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nIn preparation for a new advice method, extract a version of advise()\nthat uses an explict 'va_list' parameter. Call it from advise() for a\nfunctionally equivalent version.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c | 13 +++++++++----\n 1 file changed, 9 insertions(+), 4 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 249c60dcf32..fd836332dad 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -96,15 +96,12 @@ static struct {\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n-void advise(const char *advice, ...)\n+static void vadvise(const char *advice, va_list params)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tva_list params;\n \tconst char *cp, *np;\n \n-\tva_start(params, advice);\n \tstrbuf_vaddf(&buf, advice, params);\n-\tva_end(params);\n \n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n@@ -118,6 +115,14 @@ void advise(const char *advice, ...)\n \tstrbuf_release(&buf);\n }\n \n+void advise(const char *advice, ...)\n+{\n+\tva_list params;\n+\tva_start(params, advice);\n+\tvadvise(advice, params);\n+\tva_end(params);\n+}\n+\n int git_default_advice_config(const char *var, const char *value)\n {\n \tconst char *k, *slot_name;\n-- \ngitgitgadget\n\n"},{"id":"392466","messageId":"b7f10d060a41c1ef3d25e4c07be3747c7902b997.1582628141.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v5.git.1582628141.gitgitgadget@gmail.com","subject":"[PATCH v5 2/3] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-25T10:55:39Z","receivedAt":"2020-02-25T10:55:49Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nCurrently it's very easy for the advice library's callers to miss\nchecking the visibility step before printing an advice. Also, it makes\nmore sense for this step to be handled by the advice library.\n\nAdd a new advise_if_enabled function that checks the visibility of\nadvice messages before printing.\n\nAdd a new helper advise_enabled to check the visibility of the advice\nif the caller needs to carry out complicated processing based on that\nvalue.\n\nA list of config variables 'advice_config_keys' is added to be used by\nlist_config_advices() instead of 'advice_config[]' because we'll get\nrid of 'advice_config[]' and the global variables once we migrate all\nthe callers to use the new APIs.\n\nAlso change the advise call in tag library from advise() to\nadvise_if_enabled() to construct an example of the usage of the new\nAPI.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n Makefile               |  1 +\n advice.c               | 86 ++++++++++++++++++++++++++++++++++++++++--\n advice.h               | 52 +++++++++++++++++++++++++\n t/helper/test-advise.c | 19 ++++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 32 ++++++++++++++++\n 7 files changed, 188 insertions(+), 4 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\ndiff --git a/Makefile b/Makefile\nindex 09f98b777ca..ed923a3e818 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -695,6 +695,7 @@ X =\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n+TEST_BUILTINS_OBJS += test-advise.o\n TEST_BUILTINS_OBJS += test-chmtime.o\n TEST_BUILTINS_OBJS += test-config.o\n TEST_BUILTINS_OBJS += test-ctype.o\ndiff --git a/advice.c b/advice.c\nindex fd836332dad..5c2068b8f8a 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -96,13 +96,56 @@ static struct {\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n-static void vadvise(const char *advice, va_list params)\n+static const char *advice_config_keys[] = {\n+\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n+\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n+\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n+\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n+\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n+\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n+\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n+\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n+\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n+\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n+\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n+\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n+\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n+\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n+\n+\t/* make this an alias for backward compatibility */\n+\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n+\n+\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n+\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n+\t[PUSH_UNQUALIFIED_REF_NAME]\t\t = \"pushUnqualifiedRefName\",\n+\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n+\t[RESET_QUIET_WARNING]\t\t\t = \"resetQuiet\",\n+\t[RESOLVE_CONFLICT]\t\t\t = \"resolveConflict\",\n+\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n+\t[SEQUENCER_IN_USE]\t\t\t = \"sequencerInUse\",\n+\t[SET_UPSTREAM_FAILURE]\t\t\t = \"setupStreamFailure\",\n+\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n+\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n+\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n+\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\",\n+\t[WAITING_FOR_EDITOR] \t\t\t = \"waitingForEditor\",\n+};\n+\n+static const char turn_off_instructions[] =\n+N_(\"\\n\"\n+   \"Disable this message with \\\"git config %s false\\\"\");\n+\n+static void vadvise(const char *advice, int display_instructions,\n+\t\t    char *key, va_list params)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *cp, *np;\n \n \tstrbuf_vaddf(&buf, advice, params);\n \n+\tif (display_instructions)\n+\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n+\n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n \t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n@@ -119,8 +162,43 @@ void advise(const char *advice, ...)\n {\n \tva_list params;\n \tva_start(params, advice);\n-\tvadvise(advice, params);\n+\tvadvise(advice, 0, \"\", params);\n+\tva_end(params);\n+}\n+\n+static int get_config_value(enum advice_type type)\n+{\n+\tint value = 1;\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\n+\tgit_config_get_bool(key, &value);\n+\tfree(key);\n+\treturn value;\n+}\n+\n+int advice_enabled(enum advice_type type)\n+{\n+\tswitch (type) {\n+\tcase PUSH_UPDATE_REJECTED:\n+\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n+\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n+\tdefault:\n+\t\treturn get_config_value(type);\n+\t}\n+}\n+\n+void advise_if_enabled(enum advice_type type, const char *advice, ...)\n+{\n+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n+\tva_list params;\n+\n+\tif (!advice_enabled(type))\n+\t\treturn;\n+\n+\tva_start(params, advice);\n+\tvadvise(advice, 1, key, params);\n \tva_end(params);\n+\tfree(key);\n }\n \n int git_default_advice_config(const char *var, const char *value)\n@@ -159,8 +237,8 @@ void list_config_advices(struct string_list *list, const char *prefix)\n {\n \tint i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n-\t\tlist_config_item(list, prefix, advice_config[i].name);\n+\tfor (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n+\t\tlist_config_item(list, prefix, advice_config_keys[i]);\n }\n \n int error_resolve_conflict(const char *me)\ndiff --git a/advice.h b/advice.h\nindex b706780614d..a8461a362a3 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -32,9 +32,61 @@ extern int advice_checkout_ambiguous_remote_branch_name;\n extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n+/*\n+ * To add a new advice, you need to:\n+ * Define an advice_type.\n+ * Add a new entry to advice_config_keys list.\n+ * Add the new config variable to Documentation/config/advice.txt.\n+ * Call advise_if_enabled to print your advice.\n+ */\n+enum advice_type {\n+\tADD_EMBEDDED_REPO,\n+\tAMWORKDIR,\n+\tCHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n+\tCOMMIT_BEFORE_MERGE,\n+\tDETACHED_HEAD,\n+\tFETCH_SHOW_FORCED_UPDATES,\n+\tGRAFT_FILE_DEPRECATED,\n+\tIGNORED_HOOK,\n+\tIMPLICIT_IDENTITY,\n+\tNESTED_TAG,\n+\tOBJECT_NAME_WARNING,\n+\tPUSH_ALREADY_EXISTS,\n+\tPUSH_FETCH_FIRST,\n+\tPUSH_NEEDS_FORCE,\n+\tPUSH_NON_FF_CURRENT,\n+\tPUSH_NON_FF_MATCHING,\n+\tPUSH_UNQUALIFIED_REF_NAME,\n+\tPUSH_UPDATE_REJECTED_ALIAS,\n+\tPUSH_UPDATE_REJECTED,\n+\tRESET_QUIET_WARNING,\n+\tRESOLVE_CONFLICT,\n+\tRM_HINTS,\n+\tSEQUENCER_IN_USE,\n+\tSET_UPSTREAM_FAILURE,\n+\tSTATUS_AHEAD_BEHIND_WARNING,\n+\tSTATUS_HINTS,\n+\tSTATUS_U_OPTION,\n+\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tWAITING_FOR_EDITOR,\n+};\n+\n+\n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\n void advise(const char *advice, ...);\n+\n+/**\n+ Checks if advice type is enabled (can be printed to the user).\n+ Should be called before advise().\n+ */\n+int advice_enabled(enum advice_type type);\n+\n+/**\n+ Checks the visibility of the advice before printing.\n+ */\n+void advise_if_enabled(enum advice_type type, const char *advice, ...);\n+\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\ndiff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\nnew file mode 100644\nindex 00000000000..279cad6460e\n--- /dev/null\n+++ b/t/helper/test-advise.c\n@@ -0,0 +1,19 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"advice.h\"\n+\n+int cmd__advise_if_enabled(int argc, const char **argv)\n+{\n+\tif (!argv[1])\n+\tdie(\"usage: %s <advice>\", argv[0]);\n+\n+\tsetup_git_directory();\n+\n+\t/*\n+\t  Any advice type can be used for testing, but NESTED_TAG was selected\n+\t  here and in t0018 where this command is being executed.\n+\t */\n+\tadvise_if_enabled(NESTED_TAG, argv[1]);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d4497..6977badc690 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -14,6 +14,7 @@ struct test_cmd {\n };\n \n static struct test_cmd cmds[] = {\n+\t{ \"advise\", cmd__advise_if_enabled },\n \t{ \"chmtime\", cmd__chmtime },\n \t{ \"config\", cmd__config },\n \t{ \"ctype\", cmd__ctype },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1b..ca5e33b842f 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -4,6 +4,7 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"git-compat-util.h\"\n \n+int cmd__advise_if_enabled(int argc, const char **argv);\n int cmd__chmtime(int argc, const char **argv);\n int cmd__config(int argc, const char **argv);\n int cmd__ctype(int argc, const char **argv);\ndiff --git a/t/t0018-advice.sh b/t/t0018-advice.sh\nnew file mode 100755\nindex 00000000000..e03554d2f34\n--- /dev/null\n+++ b/t/t0018-advice.sh\n@@ -0,0 +1,32 @@\n+#!/bin/sh\n+\n+test_description='Test advise_if_enabled functionality'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'advice should be printed when config variable is unset' '\n+\tcat >expect <<-\\EOF &&\n+\thint: This is a piece of advice\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n+\tEOF\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n+test_expect_success 'advice should be printed when config variable is set to true' '\n+\tcat >expect <<-\\EOF &&\n+\thint: This is a piece of advice\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n+\tEOF\n+\ttest_config advice.nestedTag true &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n+test_expect_success 'advice should not be printed when config variable is set to false' '\n+\ttest_config advice.nestedTag false &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"392467","messageId":"01b195ebe1d2ab1593915318b7e8adbee5d9614c.1582628141.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v5.git.1582628141.gitgitgadget@gmail.com","subject":"[PATCH v5 3/3] tag: use new advice API to check visibility","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-25T10:55:40Z","receivedAt":"2020-02-25T10:55:50Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nFollowing the new helpers added to the advice library,\nreplace the global variable check approach by the new\nAPI calls\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c       | 2 --\n advice.h       | 1 -\n builtin/tag.c  | 5 +++--\n t/t7004-tag.sh | 1 +\n 4 files changed, 4 insertions(+), 5 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 5c2068b8f8a..ea6e65c1ce0 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -29,7 +29,6 @@ int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n int advice_graft_file_deprecated = 1;\n int advice_checkout_ambiguous_remote_branch_name = 1;\n-int advice_nested_tag = 1;\n int advice_submodule_alternate_error_strategy_die = 1;\n \n static int advice_use_color = -1;\n@@ -89,7 +88,6 @@ static struct {\n \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n \t{ \"checkoutAmbiguousRemoteBranchName\", &advice_checkout_ambiguous_remote_branch_name },\n-\t{ \"nestedTag\", &advice_nested_tag },\n \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n \n \t/* make this an alias for backward compatibility */\ndiff --git a/advice.h b/advice.h\nindex a8461a362a3..509b562edb1 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -29,7 +29,6 @@ extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n extern int advice_graft_file_deprecated;\n extern int advice_checkout_ambiguous_remote_branch_name;\n-extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n /*\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex e0a4c253828..45e959d5f8f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \tif (type <= OBJ_NONE)\n \t\tdie(_(\"bad object type.\"));\n \n-\tif (type == OBJ_TAG && advice_nested_tag)\n-\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n+\tif (type == OBJ_TAG)\n+\t\tadvise_if_enabled(NESTED_TAG, _(message_advice_nested_tag),\n+\t\t\t\t  tag, object_ref);\n \n \tstrbuf_addf(&header,\n \t\t    \"object %s\\n\"\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 6db92bd3ba6..74b637deb25 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1726,6 +1726,7 @@ test_expect_success 'recursive tagging should give advice' '\n \thint: already a tag. If you meant to tag the object that it points to, use:\n \thint: |\n \thint: \tgit tag -f nested annotated-v4.0^{}\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n \tEOF\n \tgit tag -m nested nested annotated-v4.0 2>actual &&\n \ttest_i18ncmp expect actual\n-- \ngitgitgadget\n"},{"id":"392474","messageId":"xmqqsgiymupf.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"b7f10d060a41c1ef3d25e4c07be3747c7902b997.1582628141.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-25T17:40:28Z","receivedAt":"2020-02-25T17:40:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Heba Waly <heba.waly@gmail.com>\n>\n> Currently it's very easy for the advice library's callers to miss\n> checking the visibility step before printing an advice. Also, it makes\n> more sense for this step to be handled by the advice library.\n>\n> Add a new advise_if_enabled function that checks the visibility of\n> advice messages before printing.\n>\n> Add a new helper advise_enabled to check the visibility of the advice\n> if the caller needs to carry out complicated processing based on that\n> value.\n>\n> A list of config variables 'advice_config_keys' is added to be used by\n> list_config_advices() instead of 'advice_config[]' because we'll get\n> rid of 'advice_config[]' and the global variables once we migrate all\n> the callers to use the new APIs.\n>\n\n\n> Also change the advise call in tag library from advise() to\n> advise_if_enabled() to construct an example of the usage of the new\n> API.\n\nThis is for step [3/3], isn't it?  I'll discard this paragraph.\n\n>\n> Signed-off-by: Heba Waly <heba.waly@gmail.com>\n> ---\n>  Makefile               |  1 +\n>  advice.c               | 86 ++++++++++++++++++++++++++++++++++++++++--\n>  advice.h               | 52 +++++++++++++++++++++++++\n>  t/helper/test-advise.c | 19 ++++++++++\n>  t/helper/test-tool.c   |  1 +\n>  t/helper/test-tool.h   |  1 +\n>  t/t0018-advice.sh      | 32 ++++++++++++++++\n>  7 files changed, 188 insertions(+), 4 deletions(-)\n>  create mode 100644 t/helper/test-advise.c\n>  create mode 100755 t/t0018-advice.sh\n>\n> diff --git a/Makefile b/Makefile\n> index 09f98b777ca..ed923a3e818 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -695,6 +695,7 @@ X =\n>  \n>  PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n>  \n> +TEST_BUILTINS_OBJS += test-advise.o\n>  TEST_BUILTINS_OBJS += test-chmtime.o\n>  TEST_BUILTINS_OBJS += test-config.o\n>  TEST_BUILTINS_OBJS += test-ctype.o\n> diff --git a/advice.c b/advice.c\n> index fd836332dad..5c2068b8f8a 100644\n> --- a/advice.c\n> +++ b/advice.c\n> @@ -96,13 +96,56 @@ static struct {\n>  \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n>  };\n>  \n> -static void vadvise(const char *advice, va_list params)\n> +static const char *advice_config_keys[] = {\n> +\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n> +\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n> +\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n> +\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n> +\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n> +\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n> +\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n> +\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n> +\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n> +\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n> +\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n> +\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n> +\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n> +\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n> +\n> +\t/* make this an alias for backward compatibility */\n> +\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n> +\n> +\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n> +\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n> +\t[PUSH_UNQUALIFIED_REF_NAME]\t\t = \"pushUnqualifiedRefName\",\n> +\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n> +\t[RESET_QUIET_WARNING]\t\t\t = \"resetQuiet\",\n> +\t[RESOLVE_CONFLICT]\t\t\t = \"resolveConflict\",\n> +\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n> +\t[SEQUENCER_IN_USE]\t\t\t = \"sequencerInUse\",\n> +\t[SET_UPSTREAM_FAILURE]\t\t\t = \"setupStreamFailure\",\n> +\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n> +\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n> +\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n> +\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\",\n> +\t[WAITING_FOR_EDITOR] \t\t\t = \"waitingForEditor\",\n> +};\n> +\n> +static const char turn_off_instructions[] =\n> +N_(\"\\n\"\n> +   \"Disable this message with \\\"git config %s false\\\"\");\n> +\n> +static void vadvise(const char *advice, int display_instructions,\n> +\t\t    char *key, va_list params)\n>  {\n>  \tstruct strbuf buf = STRBUF_INIT;\n>  \tconst char *cp, *np;\n>  \n>  \tstrbuf_vaddf(&buf, advice, params);\n>  \n> +\tif (display_instructions)\n> +\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n> +\n>  \tfor (cp = buf.buf; *cp; cp = np) {\n>  \t\tnp = strchrnul(cp, '\\n');\n>  \t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n> @@ -119,8 +162,43 @@ void advise(const char *advice, ...)\n>  {\n>  \tva_list params;\n>  \tva_start(params, advice);\n> -\tvadvise(advice, params);\n> +\tvadvise(advice, 0, \"\", params);\n> +\tva_end(params);\n> +}\n> +\n> +static int get_config_value(enum advice_type type)\n> +{\n> +\tint value = 1;\n> +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> +\n> +\tgit_config_get_bool(key, &value);\n> +\tfree(key);\n> +\treturn value;\n> +}\n\nSo, in this hypothetical but quite realistic example:\n\n\tif (advice_enabled(ADVICE_FOO)) {\n\t\tchar *foo = expensive_preparation();\n\t\tadvice_if_enabled(ADVICE_FOO, \"use of %s is discouraged\", foo);\n\t}\n\nwe end up formulating the \"advice.*\" key twice and ask git_config_get_bool()\nabout the same key twice?\n\n> +int advice_enabled(enum advice_type type)\n> +{\n> +\tswitch (type) {\n> +\tcase PUSH_UPDATE_REJECTED:\n> +\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n> +\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n> +\tdefault:\n> +\t\treturn get_config_value(type);\n> +\t}\n> +}\n\nAlso, as \"enum advice_type\" will be part of the public API, and\nthere is little type checking for enums, we shouldn't be naming them\nrandomly like these---we'd at least want to use a common prefix,\nlike \"ADVICE_\", in front of them.  Those who are focused only on\nadvice subsystem may feel that names like PUSH_UPDATE_REJECTED are\nsufficiently clear, but within the context of the whole system,\nthere is no cue that these UPCASED_WORDS identifiers belong to the\nadvice subsystem or somewhere else.\n\n> +void advise_if_enabled(enum advice_type type, const char *advice, ...)\n> +{\n> +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> +\tva_list params;\n> +\n> +\tif (!advice_enabled(type))\n> +\t\treturn;\n\nOh, no, make the number of calls to xstrfmr() three times, not\ntwice, as I said in the previous example.\n\nI wonder if it would make the implementation better to do these:\n\n - Rename advice_config_keys[] to advice_setting[] that does not\n   imply it is only about the keys;\n\n - This table will know, for each enum advice_type, which\n   configuration variable enables it, *and* if it is enabled.\n\ni.e.\n\n        static struct {\n                const char *config_key;\n                int disabled;\n        } advice_setting[] = {\n                [ADVICE_ADD_EMBEDED_REPO] = { \"addEmbeddedRepo\" },\n                [ADVICE_AM_WORK_DIR]      = { \"amWorkDir\" },\n                ...\n                [ADVICE_WAITING_FOR_EDITOR] = { \"waitingForEditor\" },\n        };\n\n\nSide Note: you have AMWORKDIR that is unreadable.  If the config\n           name uses camelCase by convention, the UPCASED_WORDS\n           should be separated with underscore at the same word\n           boundary.\n\nThen, upon the first call to advice_enabled(), call git_config()\nwith a callback like\n\n\tstatic int populate_advice_settings(const char *var, const char *value, void *cb)\n\t{\n\t\tint advice_type;\n\t\tconst char *name;\n\n\t\tif (!skip_prefix(var, \"advice.\", &name))\n\t\t\treturn 0;\n\t\tadvice_type = find_advice_type_by_name(advice_setting, name);\n\t\tif (advice_type < 0)\n\t\t\treturn 0; /* unknown advice.* variable */\n\t\t/* advice.foo=false means advice.foo is disabled */\n\t\tadvice_setting[advice_type].disabled = !git_config_bool(var, value);\n\t}\n\nonly once.  Your get_config_value() would then become a mere lookup\nin advice_setting[] array, e.g.\n\n\tint advice_enabled(unsigned advice_type)\n\t{\n\t\tstatic int initialized;\n\n\t\tif (!initialized) {\n\t\t\tinitialized = 1;\n\t\t\tgit_config(populate_advice_settings, NULL);\n\t\t}\n\t\tif (ARRAY_SIZE(advice_setting) <= advice_type)\n\t\t\tBUG(\"OOB advice type requested???\");\n\t\treturn !advice_setting[advice_type].disabled;\n\t}\n\nwith your \"push-update-rejected has two names\" twist added.\n\nHmm?\n"},{"id":"392475","messageId":"xmqqlfoqmuct.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"01b195ebe1d2ab1593915318b7e8adbee5d9614c.1582628141.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 3/3] tag: use new advice API to check visibility","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-25T17:48:02Z","receivedAt":"2020-02-25T17:48:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Heba Waly <heba.waly@gmail.com>\n>\n> Following the new helpers added to the advice library,\n> replace the global variable check approach by the new\n> API calls\n\nThe last paragraph of the proposed log message you had for [2/3]\ndescribed that this step is just an example better than the above\none, which would leave readers puzzled what our plans are for dozens\nof existing advise() calls.\n\n> +\tif (type == OBJ_TAG)\n> +\t\tadvise_if_enabled(NESTED_TAG, _(message_advice_nested_tag),\n> +\t\t\t\t  tag, object_ref);\n\nThis is probably a good enough example why OBJ_TAG is a good name\nbut NESTED_TAG is not---type could be something different from\nOBJ_TAG but the other possiblities are all OBJ_<object-type>.  We\nwant the advice types to have the same property.\n\n"},{"id":"392479","messageId":"20200225195648.GA212281@google.com","threadId":"52771","inReplyTo":"xmqqsgiymupf.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-02-25T19:56:48Z","receivedAt":"2020-02-25T19:56:56Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Tue, Feb 25, 2020 at 09:40:28AM -0800, Junio C Hamano wrote:\n> \"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> > +static int get_config_value(enum advice_type type)\n> > +{\n> > +\tint value = 1;\n> > +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> > +\n> > +\tgit_config_get_bool(key, &value);\n> > +\tfree(key);\n> > +\treturn value;\n> > +}\n> \n> So, in this hypothetical but quite realistic example:\n> \n> \tif (advice_enabled(ADVICE_FOO)) {\n> \t\tchar *foo = expensive_preparation();\n> \t\tadvice_if_enabled(ADVICE_FOO, \"use of %s is discouraged\", foo);\n> \t}\n> \n> we end up formulating the \"advice.*\" key twice and ask git_config_get_bool()\n> about the same key twice?\n> \n> > +void advise_if_enabled(enum advice_type type, const char *advice, ...)\n> > +{\n> > +\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> > +\tva_list params;\n> > +\n> > +\tif (!advice_enabled(type))\n> > +\t\treturn;\n> \n> Oh, no, make the number of calls to xstrfmr() three times, not\n> twice, as I said in the previous example.\n> \n> I wonder if it would make the implementation better to do these:\n> \n>  - Rename advice_config_keys[] to advice_setting[] that does not\n>    imply it is only about the keys;\n> \n>  - This table will know, for each enum advice_type, which\n>    configuration variable enables it, *and* if it is enabled.\n> \n> i.e.\n> \n>         static struct {\n>                 const char *config_key;\n>                 int disabled;\n>         } advice_setting[] = {\n>                 [ADVICE_ADD_EMBEDED_REPO] = { \"addEmbeddedRepo\" },\n>                 [ADVICE_AM_WORK_DIR]      = { \"amWorkDir\" },\n>                 ...\n>                 [ADVICE_WAITING_FOR_EDITOR] = { \"waitingForEditor\" },\n>         };\n> \n> \n> Side Note: you have AMWORKDIR that is unreadable.  If the config\n>            name uses camelCase by convention, the UPCASED_WORDS\n>            should be separated with underscore at the same word\n>            boundary.\n> \n> Then, upon the first call to advice_enabled(), call git_config()\n> with a callback like\n> \n> \tstatic int populate_advice_settings(const char *var, const char *value, void *cb)\n> \t{\n> \t\tint advice_type;\n> \t\tconst char *name;\n> \n> \t\tif (!skip_prefix(var, \"advice.\", &name))\n> \t\t\treturn 0;\n> \t\tadvice_type = find_advice_type_by_name(advice_setting, name);\n> \t\tif (advice_type < 0)\n> \t\t\treturn 0; /* unknown advice.* variable */\n> \t\t/* advice.foo=false means advice.foo is disabled */\n> \t\tadvice_setting[advice_type].disabled = !git_config_bool(var, value);\n> \t}\n> \n> only once.  Your get_config_value() would then become a mere lookup\n> in advice_setting[] array, e.g.\n> \n> \tint advice_enabled(unsigned advice_type)\n> \t{\n> \t\tstatic int initialized;\n> \n> \t\tif (!initialized) {\n> \t\t\tinitialized = 1;\n> \t\t\tgit_config(populate_advice_settings, NULL);\n> \t\t}\n> \t\tif (ARRAY_SIZE(advice_setting) <= advice_type)\n> \t\t\tBUG(\"OOB advice type requested???\");\n> \t\treturn !advice_setting[advice_type].disabled;\n> \t}\n> \n> with your \"push-update-rejected has two names\" twist added.\n\nI'm a little confused about the need to cache the result of\ngit_config_get_bool() - isn't that a lookup from a hashmap which is\nalready populated at setup time, and therefore inexpensive? I would\nthink the only expensive part here is the xstrfmt() calls, which it\nseems like would be easy to do away with by storing the fully-qualified\nadvice key in the array instead. What am I missing?\n\n - Emily\n"},{"id":"392480","messageId":"xmqqblpmmntj.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"20200225195648.GA212281@google.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-25T20:09:12Z","receivedAt":"2020-02-25T20:09:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Emily Shaffer <emilyshaffer@google.com> writes:\n\n>> ...  Your get_config_value() would then become a mere lookup\n>> in advice_setting[] array, e.g.\n>> \n>> \tint advice_enabled(unsigned advice_type)\n>> \t{\n>> \t\tstatic int initialized;\n>> \n>> \t\tif (!initialized) {\n>> \t\t\tinitialized = 1;\n>> \t\t\tgit_config(populate_advice_settings, NULL);\n>> \t\t}\n>> \t\tif (ARRAY_SIZE(advice_setting) <= advice_type)\n>> \t\t\tBUG(\"OOB advice type requested???\");\n>> \t\treturn !advice_setting[advice_type].disabled;\n>> \t}\n>> \n>> with your \"push-update-rejected has two names\" twist added.\n>\n> I'm a little confused about the need to cache the result of\n> git_config_get_bool() - isn't that a lookup from a hashmap which is\n> already populated at setup time, and therefore inexpensive?\n\nLooking up from hashmap with a string key is always more expensive\nthan indexing into a linear array with the array index.  Also, the\nsuggested arrangement makes the advice API implementation more self\ncontained, I'd think.\n\n\n"},{"id":"392483","messageId":"xmqq36aymmlg.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"xmqqblpmmntj.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-25T20:35:39Z","receivedAt":"2020-02-25T20:35:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Emily Shaffer <emilyshaffer@google.com> writes:\n>\n>>> ...  Your get_config_value() would then become a mere lookup\n>>> in advice_setting[] array, e.g.\n>>> \n>>> \tint advice_enabled(unsigned advice_type)\n>>> \t{\n>>> \t\tstatic int initialized;\n>>> \n>>> \t\tif (!initialized) {\n>>> \t\t\tinitialized = 1;\n>>> \t\t\tgit_config(populate_advice_settings, NULL);\n>>> \t\t}\n>>> \t\tif (ARRAY_SIZE(advice_setting) <= advice_type)\n>>> \t\t\tBUG(\"OOB advice type requested???\");\n>>> \t\treturn !advice_setting[advice_type].disabled;\n>>> \t}\n>>> \n>>> with your \"push-update-rejected has two names\" twist added.\n\nOne beauty of the approach is that the \"twist\" can be done in the\ninitialization codepath, e.g.\n\n \tint advice_enabled(unsigned advice_type)\n \t{\n \t\tstatic int initialized;\n \n \t\tif (!initialized) {\n \t\t\tinitialized = 1;\n \t\t\tgit_config(populate_advice_settings, NULL);\n\n                        advice_setting[ADVICE_PUSH_UPDATE_REJECTED] &=\n                        advice_setting[ADVICE_PUSH_UPDATE_REJECTED_ALIAS];\n \t\t}\n \t\tif (ARRAY_SIZE(advice_setting) <= advice_type)\n \t\t\tBUG(\"OOB advice type requested???\");\n \t\treturn !advice_setting[advice_type].disabled;\n \t}\n\nwhich means that the function literally becomes an array access that\nis guarded for out-of-bounds index.\n\nThanks, Emily, for making me look at the suggested code again to\nrealize this ;-)\n"},{"id":"392484","messageId":"CACg5j27SfWsj2t_z8zxOvjc6MSot2yMi1J+R4HJinFhHgTpveg@mail.gmail.com","threadId":"52771","inReplyTo":"xmqqsgiymupf.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-25T21:19:25Z","receivedAt":"2020-02-25T21:19:41Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Wed, Feb 26, 2020 at 6:40 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: Heba Waly <heba.waly@gmail.com>\n> >\n> > Currently it's very easy for the advice library's callers to miss\n> > checking the visibility step before printing an advice. Also, it makes\n> > more sense for this step to be handled by the advice library.\n> >\n> > Add a new advise_if_enabled function that checks the visibility of\n> > advice messages before printing.\n> >\n> > Add a new helper advise_enabled to check the visibility of the advice\n> > if the caller needs to carry out complicated processing based on that\n> > value.\n> >\n> > A list of config variables 'advice_config_keys' is added to be used by\n> > list_config_advices() instead of 'advice_config[]' because we'll get\n> > rid of 'advice_config[]' and the global variables once we migrate all\n> > the callers to use the new APIs.\n> >\n>\n>\n> > Also change the advise call in tag library from advise() to\n> > advise_if_enabled() to construct an example of the usage of the new\n> > API.\n>\n> This is for step [3/3], isn't it?  I'll discard this paragraph.\n\nYes, should have been discarded.\n\n> >\n> > Signed-off-by: Heba Waly <heba.waly@gmail.com>\n> > ---\n> >  Makefile               |  1 +\n> >  advice.c               | 86 ++++++++++++++++++++++++++++++++++++++++--\n> >  advice.h               | 52 +++++++++++++++++++++++++\n> >  t/helper/test-advise.c | 19 ++++++++++\n> >  t/helper/test-tool.c   |  1 +\n> >  t/helper/test-tool.h   |  1 +\n> >  t/t0018-advice.sh      | 32 ++++++++++++++++\n> >  7 files changed, 188 insertions(+), 4 deletions(-)\n> >  create mode 100644 t/helper/test-advise.c\n> >  create mode 100755 t/t0018-advice.sh\n> >\n> > diff --git a/Makefile b/Makefile\n> > index 09f98b777ca..ed923a3e818 100644\n> > --- a/Makefile\n> > +++ b/Makefile\n> > @@ -695,6 +695,7 @@ X =\n> >\n> >  PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n> >\n> > +TEST_BUILTINS_OBJS += test-advise.o\n> >  TEST_BUILTINS_OBJS += test-chmtime.o\n> >  TEST_BUILTINS_OBJS += test-config.o\n> >  TEST_BUILTINS_OBJS += test-ctype.o\n> > diff --git a/advice.c b/advice.c\n> > index fd836332dad..5c2068b8f8a 100644\n> > --- a/advice.c\n> > +++ b/advice.c\n> > @@ -96,13 +96,56 @@ static struct {\n> >       { \"pushNonFastForward\", &advice_push_update_rejected }\n> >  };\n> >\n> > -static void vadvise(const char *advice, va_list params)\n> > +static const char *advice_config_keys[] = {\n> > +     [ADD_EMBEDDED_REPO]                      = \"addEmbeddedRepo\",\n> > +     [AMWORKDIR]                              = \"amWorkDir\",\n> > +     [CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]  = \"checkoutAmbiguousRemoteBranchName\",\n> > +     [COMMIT_BEFORE_MERGE]                    = \"commitBeforeMerge\",\n> > +     [DETACHED_HEAD]                          = \"detachedHead\",\n> > +     [FETCH_SHOW_FORCED_UPDATES]              = \"fetchShowForcedUpdates\",\n> > +     [GRAFT_FILE_DEPRECATED]                  = \"graftFileDeprecated\",\n> > +     [IGNORED_HOOK]                           = \"ignoredHook\",\n> > +     [IMPLICIT_IDENTITY]                      = \"implicitIdentity\",\n> > +     [NESTED_TAG]                             = \"nestedTag\",\n> > +     [OBJECT_NAME_WARNING]                    = \"objectNameWarning\",\n> > +     [PUSH_ALREADY_EXISTS]                    = \"pushAlreadyExists\",\n> > +     [PUSH_FETCH_FIRST]                       = \"pushFetchFirst\",\n> > +     [PUSH_NEEDS_FORCE]                       = \"pushNeedsForce\",\n> > +\n> > +     /* make this an alias for backward compatibility */\n> > +     [PUSH_UPDATE_REJECTED_ALIAS]             = \"pushNonFastForward\",\n> > +\n> > +     [PUSH_NON_FF_CURRENT]                    = \"pushNonFFCurrent\",\n> > +     [PUSH_NON_FF_MATCHING]                   = \"pushNonFFMatching\",\n> > +     [PUSH_UNQUALIFIED_REF_NAME]              = \"pushUnqualifiedRefName\",\n> > +     [PUSH_UPDATE_REJECTED]                   = \"pushUpdateRejected\",\n> > +     [RESET_QUIET_WARNING]                    = \"resetQuiet\",\n> > +     [RESOLVE_CONFLICT]                       = \"resolveConflict\",\n> > +     [RM_HINTS]                               = \"rmHints\",\n> > +     [SEQUENCER_IN_USE]                       = \"sequencerInUse\",\n> > +     [SET_UPSTREAM_FAILURE]                   = \"setupStreamFailure\",\n> > +     [STATUS_AHEAD_BEHIND_WARNING]            = \"statusAheadBehindWarning\",\n> > +     [STATUS_HINTS]                           = \"statusHints\",\n> > +     [STATUS_U_OPTION]                        = \"statusUoption\",\n> > +     [SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\",\n> > +     [WAITING_FOR_EDITOR]                     = \"waitingForEditor\",\n> > +};\n> > +\n> > +static const char turn_off_instructions[] =\n> > +N_(\"\\n\"\n> > +   \"Disable this message with \\\"git config %s false\\\"\");\n> > +\n> > +static void vadvise(const char *advice, int display_instructions,\n> > +                 char *key, va_list params)\n> >  {\n> >       struct strbuf buf = STRBUF_INIT;\n> >       const char *cp, *np;\n> >\n> >       strbuf_vaddf(&buf, advice, params);\n> >\n> > +     if (display_instructions)\n> > +             strbuf_addf(&buf, turn_off_instructions, key);\n> > +\n> >       for (cp = buf.buf; *cp; cp = np) {\n> >               np = strchrnul(cp, '\\n');\n> >               fprintf(stderr, _(\"%shint: %.*s%s\\n\"),\n> > @@ -119,8 +162,43 @@ void advise(const char *advice, ...)\n> >  {\n> >       va_list params;\n> >       va_start(params, advice);\n> > -     vadvise(advice, params);\n> > +     vadvise(advice, 0, \"\", params);\n> > +     va_end(params);\n> > +}\n> > +\n> > +static int get_config_value(enum advice_type type)\n> > +{\n> > +     int value = 1;\n> > +     char *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> > +\n> > +     git_config_get_bool(key, &value);\n> > +     free(key);\n> > +     return value;\n> > +}\n>\n> So, in this hypothetical but quite realistic example:\n>\n>         if (advice_enabled(ADVICE_FOO)) {\n>                 char *foo = expensive_preparation();\n>                 advice_if_enabled(ADVICE_FOO, \"use of %s is discouraged\", foo);\n>         }\n>\n> we end up formulating the \"advice.*\" key twice and ask git_config_get_bool()\n> about the same key twice?\n\nNo, in the above example, advise() should be called not advise_if_enabled().\nAs we discussed in the beginning of this thread.\nhttps://public-inbox.org/git/xmqqa75py7u8.fsf@gitster-ct.c.googlers.com/\n\n>\n> > +int advice_enabled(enum advice_type type)\n> > +{\n> > +     switch (type) {\n> > +     case PUSH_UPDATE_REJECTED:\n> > +             return get_config_value(PUSH_UPDATE_REJECTED) &&\n> > +                    get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n> > +     default:\n> > +             return get_config_value(type);\n> > +     }\n> > +}\n>\n> Also, as \"enum advice_type\" will be part of the public API, and\n> there is little type checking for enums, we shouldn't be naming them\n> randomly like these---we'd at least want to use a common prefix,\n> like \"ADVICE_\", in front of them.  Those who are focused only on\n> advice subsystem may feel that names like PUSH_UPDATE_REJECTED are\n> sufficiently clear, but within the context of the whole system,\n> there is no cue that these UPCASED_WORDS identifiers belong to the\n> advice subsystem or somewhere else.\n>\n\nI agree.\n\n> > +void advise_if_enabled(enum advice_type type, const char *advice, ...)\n> > +{\n> > +     char *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n> > +     va_list params;\n> > +\n> > +     if (!advice_enabled(type))\n> > +             return;\n>\n> Oh, no, make the number of calls to xstrfmr() three times, not\n> twice, as I said in the previous example.\n>\n> I wonder if it would make the implementation better to do these:\n>\n>  - Rename advice_config_keys[] to advice_setting[] that does not\n>    imply it is only about the keys;\n>\n>  - This table will know, for each enum advice_type, which\n>    configuration variable enables it, *and* if it is enabled.\n>\n> i.e.\n>\n>         static struct {\n>                 const char *config_key;\n>                 int disabled;\n>         } advice_setting[] = {\n>                 [ADVICE_ADD_EMBEDED_REPO] = { \"addEmbeddedRepo\" },\n>                 [ADVICE_AM_WORK_DIR]      = { \"amWorkDir\" },\n>                 ...\n>                 [ADVICE_WAITING_FOR_EDITOR] = { \"waitingForEditor\" },\n>         };\n>\n>\n> Side Note: you have AMWORKDIR that is unreadable.  If the config\n>            name uses camelCase by convention, the UPCASED_WORDS\n>            should be separated with underscore at the same word\n>            boundary.\n\nI followed the original global variable name, which is\n`advice_amworkdir`, we can change that.\n\n>\n> Then, upon the first call to advice_enabled(), call git_config()\n> with a callback like\n>\n>         static int populate_advice_settings(const char *var, const char *value, void *cb)\n>         {\n>                 int advice_type;\n>                 const char *name;\n>\n>                 if (!skip_prefix(var, \"advice.\", &name))\n>                         return 0;\n>                 advice_type = find_advice_type_by_name(advice_setting, name);\n>                 if (advice_type < 0)\n>                         return 0; /* unknown advice.* variable */\n>                 /* advice.foo=false means advice.foo is disabled */\n>                 advice_setting[advice_type].disabled = !git_config_bool(var, value);\n>         }\n>\n> only once.  Your get_config_value() would then become a mere lookup\n> in advice_setting[] array, e.g.\n>\n>         int advice_enabled(unsigned advice_type)\n>         {\n>                 static int initialized;\n>\n>                 if (!initialized) {\n>                         initialized = 1;\n>                         git_config(populate_advice_settings, NULL);\n>                 }\n>                 if (ARRAY_SIZE(advice_setting) <= advice_type)\n>                         BUG(\"OOB advice type requested???\");\n>                 return !advice_setting[advice_type].disabled;\n>         }\n>\n> with your \"push-update-rejected has two names\" twist added.\n>\n> Hmm?\n\nI wasn't very happy about having to keep the list of config keys in\nmemory, but that was a good enough solution for now.\nI also agree that there could be benefits for caching the values, as\nyou mentioned it will be less expensive than looking up from the\nhashmap, but this array will grow with every new advice added to the\nsystem. And this data is already loaded in the hashmap, so we are\nduplicating it.\nSo are the benefits worth the duplication? I don't know.\n\nThanks,\nHeba\n"},{"id":"392485","messageId":"xmqqy2sql405.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"CACg5j27SfWsj2t_z8zxOvjc6MSot2yMi1J+R4HJinFhHgTpveg@mail.gmail.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-25T22:02:34Z","receivedAt":"2020-02-25T22:02:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Heba Waly <heba.waly@gmail.com> writes:\n\n> I wasn't very happy about having to keep the list of config keys in\n> memory, but that was a good enough solution for now.\n\nIf you force your programmers to specify the advice_type as a small\ninteger, and the setting is stored in the config as string keys,\nsomebody MUST have a table to convert from one to the other.  So I\nam not sure if it is even sensible to feel unhappy about having to\nhave a list in the first place.  Are we looking for some kind of\nmiracles ;-)?\n\nOn the other hand, it does bother my sense of aesthetics a lot it\nour code forces our programmers to give a small integer to us, only\nso that we convert that integer to a string and use the string to\nlook up a value in a hashtable, every time the program wants a\nlookup.  Performance-wise, that's not a huge downside.  It just rubs\nmy sense of code hygiene the wrong way.\n\nEspecially when the primary way for our programmers to specify which\nadvice they are talking about is by passing an integer, and if we\nneed to have a table indexed by that integer in the program anyway.\n\nWe could instead do something like:\n\n    /* advice.h */\n    #ifndef _ADVICE_H_\n    #define _ADVICE_H_ 1\n    extern const char ADVICE_ADD_EMBEDDED_REPO[];\n    extern const char ADVICE_AM_WORK_DIR[];\n    ...\n    #endif\n\n    /* advice.c */\n    const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n    const char ADVICE_ADD_AM_WORK_DIR[] = \"advice.amWorkDir\";\n    ...\n\nand the callers can still do\n\n    advise_if_enabled(ADVICE_NESTED_TAG,\n\t\t      _(message_advice_nested_tag), tag, object_ref);\n\nwith the benefit of compiler catching a silly typo, without having\nto have any \"enum-to-string\" table while letting the config API\nlayer do any caching transparently.  As these calls will never be\nplaced in a performance critical codepath, that might be more\nappropriate.  \n\nI dunno.\n\n\n\n\n"},{"id":"392497","messageId":"CACg5j25EdX2fPHpAq3TEhaJPiQg4dA52soOyCMm17wg_O-c4Ng@mail.gmail.com","threadId":"52771","inReplyTo":"xmqqy2sql405.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-26T00:37:36Z","receivedAt":"2020-02-26T00:37:50Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Wed, Feb 26, 2020 at 11:02 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Heba Waly <heba.waly@gmail.com> writes:\n>\n> > I wasn't very happy about having to keep the list of config keys in\n> > memory, but that was a good enough solution for now.\n>\n> If you force your programmers to specify the advice_type as a small\n> integer, and the setting is stored in the config as string keys,\n> somebody MUST have a table to convert from one to the other.  So I\n> am not sure if it is even sensible to feel unhappy about having to\n> have a list in the first place.  Are we looking for some kind of\n> miracles ;-)?\n>\n\nThe reason I had to add the list of keys wasn't the enums, but because\nit's needed by list_config_advices() which returns all the advice\nconfig variables names. This is used when the user runs `git help\n--config`.\nAnd as a result, I added the enum to utilize it in accessing the list\nand avoid hard coded strings in functions calls.\n\n> On the other hand, it does bother my sense of aesthetics a lot it\n> our code forces our programmers to give a small integer to us, only\n> so that we convert that integer to a string and use the string to\n> look up a value in a hashtable, every time the program wants a\n> lookup.  Performance-wise, that's not a huge downside.  It just rubs\n> my sense of code hygiene the wrong way.\n>\n> Especially when the primary way for our programmers to specify which\n> advice they are talking about is by passing an integer, and if we\n> need to have a table indexed by that integer in the program anyway.\n>\n> We could instead do something like:\n>\n>     /* advice.h */\n>     #ifndef _ADVICE_H_\n>     #define _ADVICE_H_ 1\n>     extern const char ADVICE_ADD_EMBEDDED_REPO[];\n>     extern const char ADVICE_AM_WORK_DIR[];\n>     ...\n>     #endif\n>\n>     /* advice.c */\n>     const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n>     const char ADVICE_ADD_AM_WORK_DIR[] = \"advice.amWorkDir\";\n>     ...\n>\n> and the callers can still do\n>\n>     advise_if_enabled(ADVICE_NESTED_TAG,\n>                       _(message_advice_nested_tag), tag, object_ref);\n>\n> with the benefit of compiler catching a silly typo, without having\n> to have any \"enum-to-string\" table while letting the config API\n> layer do any caching transparently.  As these calls will never be\n> placed in a performance critical codepath, that might be more\n> appropriate.\n>\n\nI'm not against this approach as well, but as I mentioned above, we\nneed a list of keys to be returned by list_config_advices(), that's\nwhy defining the constant strings will not be sufficient in our case.\n\nThanks,\nHeba\n"},{"id":"392504","messageId":"xmqq8skqkq35.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"CACg5j25EdX2fPHpAq3TEhaJPiQg4dA52soOyCMm17wg_O-c4Ng@mail.gmail.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-26T03:03:10Z","receivedAt":"2020-02-26T03:03:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Heba Waly <heba.waly@gmail.com> writes:\n\n> I'm not against this approach as well, but as I mentioned above, we\n> need a list of keys to be returned by list_config_advices(), that's\n> why defining the constant strings will not be sufficient in our case.\n\nSorry, but I do not get it.  \n\nEither you use enum or a bunch of variables of type const char [],\n\"list all of them\" would need an array whose elements are all of\nthem, so\n\n        const char ADVICE_FOO[] = \"advice.foo\";\n        const char ADVICE_BAR[] = \"advice.bar\";\n        ...\n\n        static const char *all_advice_type[] = {\n                ADVICE_FOO, ADVICE_BAR, ...\n        };\n\n\tvoid for_each_advice_type(int (*fn)(const char *name))\n\t{\n\t\tint i;\n\t\tfor (i = 0; i < ARRAY_SIZE(all_advice_type); i++)\n\t\t\tfn(all_advice_type[i]);\n\t}\n\nwould be sufficient, and I do not think it takes any more effort to\ncreate and manage than using an array indexed with the enum, no?\n\n"},{"id":"392569","messageId":"CACg5j24F=rU1EKPpq-TnMOUNLZqT3HTJZXuCXORVtPPa=hK-Cg@mail.gmail.com","threadId":"52771","inReplyTo":"xmqq8skqkq35.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-26T20:28:20Z","receivedAt":"2020-02-26T20:28:34Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Wed, Feb 26, 2020 at 4:03 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Heba Waly <heba.waly@gmail.com> writes:\n>\n> > I'm not against this approach as well, but as I mentioned above, we\n> > need a list of keys to be returned by list_config_advices(), that's\n> > why defining the constant strings will not be sufficient in our case.\n>\n> Sorry, but I do not get it.\n>\n> Either you use enum or a bunch of variables of type const char [],\n> \"list all of them\" would need an array whose elements are all of\n> them, so\n>\n>         const char ADVICE_FOO[] = \"advice.foo\";\n>         const char ADVICE_BAR[] = \"advice.bar\";\n>         ...\n>\n>         static const char *all_advice_type[] = {\n>                 ADVICE_FOO, ADVICE_BAR, ...\n>         };\n>\n>         void for_each_advice_type(int (*fn)(const char *name))\n>         {\n>                 int i;\n>                 for (i = 0; i < ARRAY_SIZE(all_advice_type); i++)\n>                         fn(all_advice_type[i]);\n>         }\n>\n> would be sufficient, and I do not think it takes any more effort to\n> create and manage than using an array indexed with the enum, no?\n>\n\nhmm, you're right, I just personally prefer having related variables\ncollected in one data structure (whenever possible) like a list (or\nenum in this case) rather than defining each independently as a const\nvariable. On the other hand, I understand that you'd prefer to skip\nthe extra step of converting the enum to string.\nhmmm ok, I'll change the enum and send a new version soon.\n\nThanks,\nHeba\n"},{"id":"392573","messageId":"xmqq7e09hydx.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"CACg5j24F=rU1EKPpq-TnMOUNLZqT3HTJZXuCXORVtPPa=hK-Cg@mail.gmail.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-26T20:44:26Z","receivedAt":"2020-02-26T20:44:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Heba Waly <heba.waly@gmail.com> writes:\n\n> variable. On the other hand, I understand that you'd prefer to skip\n> the extra step of converting the enum to string.\n> hmmm ok, I'll change the enum and send a new version soon.\n\nTo avoid misunderstanding, I do not object to enum based approach at\nall.  In fact, I'd rather prefer it over bunch of const strings that\ncan be checked by the compiler.  What I do not prefer compared to\neither of these approaches is to accept enum from the caller and\nconvert it to string to consult config API every time, which is\nworse than \"bunch of const strings\".\n\nThanks.\n"},{"id":"392581","messageId":"20200226214811.227720-1-jonathantanmy@google.com","threadId":"52771","inReplyTo":"xmqq8skqkq35.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 2/3] advice: revamp advise API","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-02-26T21:48:11Z","receivedAt":"2020-02-26T21:48:18Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> Heba Waly <heba.waly@gmail.com> writes:\n> \n> > I'm not against this approach as well, but as I mentioned above, we\n> > need a list of keys to be returned by list_config_advices(), that's\n> > why defining the constant strings will not be sufficient in our case.\n> \n> Sorry, but I do not get it.  \n> \n> Either you use enum or a bunch of variables of type const char [],\n> \"list all of them\" would need an array whose elements are all of\n> them, so\n> \n>         const char ADVICE_FOO[] = \"advice.foo\";\n>         const char ADVICE_BAR[] = \"advice.bar\";\n>         ...\n> \n>         static const char *all_advice_type[] = {\n>                 ADVICE_FOO, ADVICE_BAR, ...\n>         };\n> \n> \tvoid for_each_advice_type(int (*fn)(const char *name))\n> \t{\n> \t\tint i;\n> \t\tfor (i = 0; i < ARRAY_SIZE(all_advice_type); i++)\n> \t\t\tfn(all_advice_type[i]);\n> \t}\n> \n> would be sufficient, and I do not think it takes any more effort to\n> create and manage than using an array indexed with the enum, no?\n\nWith the enum:\n\n(.h)\nenum advice_type {\n\tADVICE_FOO,\n\tADVICE_BAR\n};\n\n(.c)\nstatic const char *advice_config_keys[] = {\n\t[ADVICE_FOO] = \"advice.foo\",\n\t[ADVICE_BAR] = \"advice.bar\"\n};\n/* No need for all_advice_type because we can loop over advice_config_keys */\n\nWith the bunch of variables of type const char []:\n\n(.h)\nextern const char ADVICE_FOO[];\nextern const char ADVICE_BAR[];\n\n(.c)\nconst char ADVICE_FOO[] = \"advice.foo\";\nconst char ADVICE_BAR[] = \"advice.bar\";\nstatic const char *all_advice_type[] = {\n\tADVICE_FOO,\n\tADVICE_BAR\n};\n\nJunio, is this what you meant? It seems to me that there is an extra array to\nbe managed in the latter case. Admittedly, this is a tradeoff against needing\nto convert the enum to a string when checking config, as you describe [1].\n\n[1] https://lore.kernel.org/git/xmqq7e09hydx.fsf@gitster-ct.c.googlers.com/\n"},{"id":"392595","messageId":"f668d9b7ca04da1d8adf0a146b48a46559471501.1582778112.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v6.git.1582778112.gitgitgadget@gmail.com","subject":"[PATCH v6 1/4] advice: extract vadvise() from advise()","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-27T04:35:08Z","receivedAt":"2020-02-27T04:35:17Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nIn preparation for a new advice method, extract a version of advise()\nthat uses an explict 'va_list' parameter. Call it from advise() for a\nfunctionally equivalent version.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c | 13 +++++++++----\n 1 file changed, 9 insertions(+), 4 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 249c60dcf32..fd836332dad 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -96,15 +96,12 @@ static struct {\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n-void advise(const char *advice, ...)\n+static void vadvise(const char *advice, va_list params)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tva_list params;\n \tconst char *cp, *np;\n \n-\tva_start(params, advice);\n \tstrbuf_vaddf(&buf, advice, params);\n-\tva_end(params);\n \n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n@@ -118,6 +115,14 @@ void advise(const char *advice, ...)\n \tstrbuf_release(&buf);\n }\n \n+void advise(const char *advice, ...)\n+{\n+\tva_list params;\n+\tva_start(params, advice);\n+\tvadvise(advice, params);\n+\tva_end(params);\n+}\n+\n int git_default_advice_config(const char *var, const char *value)\n {\n \tconst char *k, *slot_name;\n-- \ngitgitgadget\n\n"},{"id":"392596","messageId":"828b4aee19d09218beca6ad1d93006012be99a0a.1582778112.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v6.git.1582778112.gitgitgadget@gmail.com","subject":"[PATCH v6 2/4] advice: change \"setupStreamFailure\" to \"setUpstreamFailure\"","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-27T04:35:09Z","receivedAt":"2020-02-27T04:35:18Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nfb6fbffbda (advice: keep config name in camelCase in advice_config[],\n2018-05-26) changed the config names to camelCase, but one of the names\nwasn't changed correctly. Fix it.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/advice.c b/advice.c\nindex fd836332dad..258cc9ba7af 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -80,7 +80,7 @@ static struct {\n \t{ \"sequencerInUse\", &advice_sequencer_in_use },\n \t{ \"implicitIdentity\", &advice_implicit_identity },\n \t{ \"detachedHead\", &advice_detached_head },\n-\t{ \"setupStreamFailure\", &advice_set_upstream_failure },\n+\t{ \"setUpstreamFailure\", &advice_set_upstream_failure },\n \t{ \"objectNameWarning\", &advice_object_name_warning },\n \t{ \"amWorkDir\", &advice_amworkdir },\n \t{ \"rmHints\", &advice_rm_hints },\n-- \ngitgitgadget\n\n"},{"id":"392597","messageId":"pull.548.v6.git.1582778112.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v5.git.1582628141.gitgitgadget@gmail.com","subject":"[PATCH v6 0/4] [Outreachy] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-27T04:35:07Z","receivedAt":"2020-02-27T04:35:18Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"In V6:\n\n * Replace the enum approach by const char arrays.\n * Fix camelCase name for one of the config variables.\n\n\n----------------------------------------------------------------------------\n\nMain changes in V4:\n\n * Re-order the commits.\n * Free the output after using xstrfmt().\n\n\n----------------------------------------------------------------------------\n\nChanges in V3:\n\n * Remove the new wrapper advice_push_update_rejected_enabled() (which was\n   added in V2 to handle a special case of having a config variable alias),\n   and replace it by adding switch cases to advice_enabled() (The reason\n   behind this change is that another special case came up while I was\n   migrating the rest of the advise calls to the new APIs.)\n * Remove trailing whitespaces.\n\n\n----------------------------------------------------------------------------\n\nMain changes in V2:\n\n * Rename advise_ng to advise_if_enabled.\n * Add a new advise_enabled() helper.\n * Add a list of config variables names to replace advice_config[] (used by\n   list_config_advices()).\n * Send an enum parameter to the new advise helpers instead of strings.\n * Extract vadvise() from advise() and advise_if enabled().\n\n\n----------------------------------------------------------------------------\n\nThe advice API is currently a little bit confusing to call. quoting from\n[1]:\n\nWhen introducing a new advice message, you would\n\n * come up with advice.frotz configuration variable\n\n * define and declare advice_frotz global variable that defaults to\n   true\n\n * sprinkle calls like this:\n\n  if (advice_frotz)\n    advise(_(\"helpful message about frotz\"));\n\nA new approach was suggested in [1] which this patch is based upon.\n\nA new advise_if_enabled() is introduced to gradually replace advise()\nadvice_enabled() helper is also introduced to be used by those callers who:\n\n * Only need to check the visibility without calling advise() (they call\n   die() or error() instead for example)\n * Need to carry out some heavy processing to display an advice, in this\n   case they'll do: if(advice_enabled(advice_type))  advise(\"some advice message\");\n   \n   \n\nTo introduce a new advice message, the caller needs to:\n\n * Define a new const char array for the new advice's key.\n * Come up with a new config variable name and add this name to \n   advice_config_keys[]\n * Call advise_if_enabled(new_advice_key, \"advice message to be printed\")\n * Or call advice_enabled(new_advice_key) first and then follow it by\n   advise(\"advice message to be printed\") as explained earlier.\n * Add the new config variable to Documentation/config/advice.txt\n\nThe reason a new list of configuration variables was added to the library is\nto be used by the list_config_advices() function instead of advice_config[].\nAnd we should get rid of advice_config[] once we migrate all the callers to\nuse the new APIs instead of checking the global variables (which we'll get\nrid of as well).\n\nIn the future, we can investigate generating the documentation from the list\nof config variables or vice versa to make introducing a new advice much\neasier, but this approach will do it for now.\n\nV2 makes the process of introducing a new advice longer than V1 and almost\nas long as the original library, but having the advice library responsible\nfor checking the message visibility is still an improvement and in my own\nopinion the new structure makes better sense and makes the library less\nconfusing to use.\n\nAfter this patch the plan is to change the advise() calls to\nadvise_if_enabled() whenever possible, or at least replace the global\nvariables checks by advise_enabled() when advise_if_enabled() is not\nsuitable.\n\n[1] https://public-inbox.org/git/xmqqzhf5cw69.fsf@gitster-ct.c.googlers.com/\n\nHeba Waly (4):\n  advice: extract vadvise() from advise()\n  advice: change \"setupStreamFailure\" to \"setUpstreamFailure\"\n  advice: revamp advise API\n  tag: use new advice API to check visibility\n\n Makefile               |   1 +\n advice.c               | 124 ++++++++++++++++++++++++++++++++++++++---\n advice.h               |  50 ++++++++++++++++-\n builtin/tag.c          |   5 +-\n t/helper/test-advise.c |  19 +++++++\n t/helper/test-tool.c   |   1 +\n t/helper/test-tool.h   |   1 +\n t/t0018-advice.sh      |  32 +++++++++++\n t/t7004-tag.sh         |   1 +\n 9 files changed, 222 insertions(+), 12 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\n\nbase-commit: c7a62075917b3340f908093f63f1161c44ed1475\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-548%2FHebaWaly%2Fadvice_refactoring-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-548/HebaWaly/advice_refactoring-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/548\n\nRange-diff vs v5:\n\n 1:  f668d9b7ca0 = 1:  f668d9b7ca0 advice: extract vadvise() from advise()\n -:  ----------- > 2:  828b4aee19d advice: change \"setupStreamFailure\" to \"setUpstreamFailure\"\n 2:  b7f10d060a4 ! 3:  176e38a841d advice: revamp advise API\n     @@ -18,10 +18,6 @@\n          rid of 'advice_config[]' and the global variables once we migrate all\n          the callers to use the new APIs.\n      \n     -    Also change the advise call in tag library from advise() to\n     -    advise_if_enabled() to construct an example of the usage of the new\n     -    API.\n     -\n          Signed-off-by: Heba Waly <heba.waly@gmail.com>\n      \n       diff --git a/Makefile b/Makefile\n     @@ -39,44 +35,82 @@\n       diff --git a/advice.c b/advice.c\n       --- a/advice.c\n       +++ b/advice.c\n     +@@\n     + int advice_nested_tag = 1;\n     + int advice_submodule_alternate_error_strategy_die = 1;\n     + \n     ++const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n     ++const char ADVICE_AM_WORK_DIR[] = \"advice.amWorkDir\";\n     ++const char ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME[] = \"advice.checkoutAmbiguousRemoteBranchName\";\n     ++const char ADVICE_COMMIT_BEFORE_MERGE[] = \"advice.commitBeforeMerge\";\n     ++const char ADVICE_DETACHED_HEAD[] = \"advice.detachedHead\";\n     ++const char ADVICE_FETCH_SHOW_FORCED_UPDATES[] = \"advice.fetchShowForcedUpdates\";\n     ++const char ADVICE_GRAFT_FILE_DEPRECATED[] = \"advice.graftFileDeprecated\";\n     ++const char ADVICE_IGNORED_HOOK[] = \"advice.ignoredHook\";\n     ++const char ADVICE_IMPLICIT_IDENTITY[] = \"advice.implicitIdentity\";\n     ++const char ADVICE_NESTED_TAG[] = \"advice.nestedTag\";\n     ++const char ADVICE_OBJECT_NAME_WARNING[] = \"advice.objectNameWarning\";\n     ++const char ADVICE_PUSH_ALREADY_EXISTS[] = \"advice.pushAlreadyExists\";\n     ++const char ADVICE_PUSH_FETCH_FIRST[] = \"advice.pushFetchFirst\";\n     ++const char ADVICE_PUSH_NEEDS_FORCE[] = \"advice.pushNeedsForce\";\n     ++const char ADVICE_PUSH_NON_FF_CURRENT[] = \"advice.pushNonFFCurrent\";\n     ++const char ADVICE_PUSH_NON_FF_MATCHING[] = \"advice.pushNonFFMatching\";\n     ++const char ADVICE_PUSH_UNQUALIFIED_REF_NAME[] = \"advice.pushUnqualifiedRefName\";\n     ++const char ADVICE_PUSH_UPDATE_REJECTED[] = \"advice.pushUpdateRejected\";\n     ++\n     ++/* make this an alias for backward compatibility */\n     ++const char ADVICE_PUSH_UPDATE_REJECTED_ALIAS[] = \"advice.pushNonFastForward\";\n     ++\n     ++const char ADVICE_RESET_QUIET_WARNING[] = \"advice.resetQuiet\";\n     ++const char ADVICE_RESOLVE_CONFLICT[] = \"advice.resolveConflict\";\n     ++const char ADVICE_RM_HINTS[] = \"advice.rmHints\";\n     ++const char ADVICE_SEQUENCER_IN_USE[] = \"advice.sequencerInUse\";\n     ++const char ADVICE_SET_UPSTREAM_FAILURE[] = \"advice.setUpstreamFailure\";\n     ++const char ADVICE_STATUS_AHEAD_BEHIND_WARNING[] = \"advice.statusAheadBehindWarning\";\n     ++const char ADVICE_STATUS_HINTS[] = \"advice.statusHints\";\n     ++const char ADVICE_STATUS_U_OPTION[] = \"advice.statusUoption\";\n     ++const char ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE[] = \"advice.submoduleAlternateErrorStrategyDie\";\n     ++const char ADVICE_WAITING_FOR_EDITOR[] = \"advice.waitingForEditor\";\n     ++\n     ++\n     + static int advice_use_color = -1;\n     + static char advice_colors[][COLOR_MAXLEN] = {\n     + \tGIT_COLOR_RESET,\n      @@\n       \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n       };\n       \n      -static void vadvise(const char *advice, va_list params)\n      +static const char *advice_config_keys[] = {\n     -+\t[ADD_EMBEDDED_REPO]\t\t\t = \"addEmbeddedRepo\",\n     -+\t[AMWORKDIR]\t\t\t\t = \"amWorkDir\",\n     -+\t[CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]\t = \"checkoutAmbiguousRemoteBranchName\",\n     -+\t[COMMIT_BEFORE_MERGE]\t\t\t = \"commitBeforeMerge\",\n     -+\t[DETACHED_HEAD]\t\t\t\t = \"detachedHead\",\n     -+\t[FETCH_SHOW_FORCED_UPDATES]\t\t = \"fetchShowForcedUpdates\",\n     -+\t[GRAFT_FILE_DEPRECATED]\t\t\t = \"graftFileDeprecated\",\n     -+\t[IGNORED_HOOK]\t\t\t\t = \"ignoredHook\",\n     -+\t[IMPLICIT_IDENTITY]\t\t\t = \"implicitIdentity\",\n     -+\t[NESTED_TAG]\t\t\t\t = \"nestedTag\",\n     -+\t[OBJECT_NAME_WARNING]\t\t\t = \"objectNameWarning\",\n     -+\t[PUSH_ALREADY_EXISTS]\t\t\t = \"pushAlreadyExists\",\n     -+\t[PUSH_FETCH_FIRST]\t\t\t = \"pushFetchFirst\",\n     -+\t[PUSH_NEEDS_FORCE]\t\t\t = \"pushNeedsForce\",\n     -+\n     -+\t/* make this an alias for backward compatibility */\n     -+\t[PUSH_UPDATE_REJECTED_ALIAS]\t\t = \"pushNonFastForward\",\n     -+\n     -+\t[PUSH_NON_FF_CURRENT]\t\t\t = \"pushNonFFCurrent\",\n     -+\t[PUSH_NON_FF_MATCHING]\t\t\t = \"pushNonFFMatching\",\n     -+\t[PUSH_UNQUALIFIED_REF_NAME]\t\t = \"pushUnqualifiedRefName\",\n     -+\t[PUSH_UPDATE_REJECTED]\t\t\t = \"pushUpdateRejected\",\n     -+\t[RESET_QUIET_WARNING]\t\t\t = \"resetQuiet\",\n     -+\t[RESOLVE_CONFLICT]\t\t\t = \"resolveConflict\",\n     -+\t[RM_HINTS]\t\t\t\t = \"rmHints\",\n     -+\t[SEQUENCER_IN_USE]\t\t\t = \"sequencerInUse\",\n     -+\t[SET_UPSTREAM_FAILURE]\t\t\t = \"setupStreamFailure\",\n     -+\t[STATUS_AHEAD_BEHIND_WARNING]\t\t = \"statusAheadBehindWarning\",\n     -+\t[STATUS_HINTS]\t\t\t\t = \"statusHints\",\n     -+\t[STATUS_U_OPTION]\t\t\t = \"statusUoption\",\n     -+\t[SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = \"submoduleAlternateErrorStrategyDie\",\n     -+\t[WAITING_FOR_EDITOR] \t\t\t = \"waitingForEditor\",\n     ++\tADVICE_ADD_EMBEDDED_REPO,\n     ++\tADVICE_AM_WORK_DIR,\n     ++\tADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n     ++\tADVICE_COMMIT_BEFORE_MERGE,\n     ++\tADVICE_DETACHED_HEAD,\n     ++\tADVICE_FETCH_SHOW_FORCED_UPDATES,\n     ++\tADVICE_GRAFT_FILE_DEPRECATED,\n     ++\tADVICE_IGNORED_HOOK,\n     ++\tADVICE_IMPLICIT_IDENTITY,\n     ++\tADVICE_NESTED_TAG,\n     ++\tADVICE_OBJECT_NAME_WARNING,\n     ++\tADVICE_PUSH_ALREADY_EXISTS,\n     ++\tADVICE_PUSH_FETCH_FIRST,\n     ++\tADVICE_PUSH_NEEDS_FORCE,\n     ++\tADVICE_PUSH_UPDATE_REJECTED_ALIAS,\n     ++\tADVICE_PUSH_NON_FF_CURRENT,\n     ++\tADVICE_PUSH_NON_FF_MATCHING,\n     ++\tADVICE_PUSH_UNQUALIFIED_REF_NAME,\n     ++\tADVICE_PUSH_UPDATE_REJECTED,\n     ++\tADVICE_RESET_QUIET_WARNING,\n     ++\tADVICE_RESOLVE_CONFLICT,\n     ++\tADVICE_RM_HINTS,\n     ++\tADVICE_SEQUENCER_IN_USE,\n     ++\tADVICE_SET_UPSTREAM_FAILURE,\n     ++\tADVICE_STATUS_AHEAD_BEHIND_WARNING,\n     ++\tADVICE_STATUS_HINTS,\n     ++\tADVICE_STATUS_U_OPTION,\n     ++\tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n     ++\tADVICE_WAITING_FOR_EDITOR,\n      +};\n      +\n      +static const char turn_off_instructions[] =\n     @@ -84,7 +118,7 @@\n      +   \"Disable this message with \\\"git config %s false\\\"\");\n      +\n      +static void vadvise(const char *advice, int display_instructions,\n     -+\t\t    char *key, va_list params)\n     ++\t\t    const char *key, va_list params)\n       {\n       \tstruct strbuf buf = STRBUF_INIT;\n       \tconst char *cp, *np;\n     @@ -106,42 +140,35 @@\n      +\tva_end(params);\n      +}\n      +\n     -+static int get_config_value(enum advice_type type)\n     ++static int get_config_value(const char *advice_key)\n      +{\n      +\tint value = 1;\n     -+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n      +\n     -+\tgit_config_get_bool(key, &value);\n     -+\tfree(key);\n     ++\tgit_config_get_bool(advice_key, &value);\n      +\treturn value;\n      +}\n      +\n     -+int advice_enabled(enum advice_type type)\n     ++int advice_enabled(const char *advice_key)\n      +{\n     -+\tswitch (type) {\n     -+\tcase PUSH_UPDATE_REJECTED:\n     -+\t\treturn get_config_value(PUSH_UPDATE_REJECTED) &&\n     -+\t\t       get_config_value(PUSH_UPDATE_REJECTED_ALIAS);\n     -+\tdefault:\n     -+\t\treturn get_config_value(type);\n     -+\t}\n     ++\tif (advice_key == ADVICE_PUSH_UPDATE_REJECTED)\n     ++\t\treturn get_config_value(ADVICE_PUSH_UPDATE_REJECTED) &&\n     ++\t\t       get_config_value(ADVICE_PUSH_UPDATE_REJECTED_ALIAS);\n     ++\telse\n     ++\t\treturn get_config_value(advice_key);\n      +}\n      +\n     -+void advise_if_enabled(enum advice_type type, const char *advice, ...)\n     ++void advise_if_enabled(const char *advice_key, const char *advice, ...)\n      +{\n     -+\tchar *key = xstrfmt(\"%s.%s\", \"advice\", advice_config_keys[type]);\n      +\tva_list params;\n      +\n     -+\tif (!advice_enabled(type))\n     ++\tif (!advice_enabled(advice_key))\n      +\t\treturn;\n      +\n      +\tva_start(params, advice);\n     -+\tvadvise(advice, 1, key, params);\n     ++\tvadvise(advice, 1, advice_key, params);\n       \tva_end(params);\n     -+\tfree(key);\n       }\n       \n     - int git_default_advice_config(const char *var, const char *value)\n      @@\n       {\n       \tint i;\n     @@ -163,58 +190,55 @@\n       \n      +/*\n      + * To add a new advice, you need to:\n     -+ * Define an advice_type.\n     ++ * Define a new const char array.\n      + * Add a new entry to advice_config_keys list.\n      + * Add the new config variable to Documentation/config/advice.txt.\n      + * Call advise_if_enabled to print your advice.\n      + */\n     -+enum advice_type {\n     -+\tADD_EMBEDDED_REPO,\n     -+\tAMWORKDIR,\n     -+\tCHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n     -+\tCOMMIT_BEFORE_MERGE,\n     -+\tDETACHED_HEAD,\n     -+\tFETCH_SHOW_FORCED_UPDATES,\n     -+\tGRAFT_FILE_DEPRECATED,\n     -+\tIGNORED_HOOK,\n     -+\tIMPLICIT_IDENTITY,\n     -+\tNESTED_TAG,\n     -+\tOBJECT_NAME_WARNING,\n     -+\tPUSH_ALREADY_EXISTS,\n     -+\tPUSH_FETCH_FIRST,\n     -+\tPUSH_NEEDS_FORCE,\n     -+\tPUSH_NON_FF_CURRENT,\n     -+\tPUSH_NON_FF_MATCHING,\n     -+\tPUSH_UNQUALIFIED_REF_NAME,\n     -+\tPUSH_UPDATE_REJECTED_ALIAS,\n     -+\tPUSH_UPDATE_REJECTED,\n     -+\tRESET_QUIET_WARNING,\n     -+\tRESOLVE_CONFLICT,\n     -+\tRM_HINTS,\n     -+\tSEQUENCER_IN_USE,\n     -+\tSET_UPSTREAM_FAILURE,\n     -+\tSTATUS_AHEAD_BEHIND_WARNING,\n     -+\tSTATUS_HINTS,\n     -+\tSTATUS_U_OPTION,\n     -+\tSUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n     -+\tWAITING_FOR_EDITOR,\n     -+};\n     -+\n     ++extern const char ADVICE_ADD_EMBEDDED_REPO[];\n     ++extern const char ADVICE_AM_WORK_DIR[];\n     ++extern const char ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME[];\n     ++extern const char ADVICE_COMMIT_BEFORE_MERGE[];\n     ++extern const char ADVICE_DETACHED_HEAD[];\n     ++extern const char ADVICE_FETCH_SHOW_FORCED_UPDATES[];\n     ++extern const char ADVICE_GRAFT_FILE_DEPRECATED[];\n     ++extern const char ADVICE_IGNORED_HOOK[];\n     ++extern const char ADVICE_IMPLICIT_IDENTITY[];\n     ++extern const char ADVICE_NESTED_TAG[];\n     ++extern const char ADVICE_OBJECT_NAME_WARNING[];\n     ++extern const char ADVICE_PUSH_ALREADY_EXISTS[];\n     ++extern const char ADVICE_PUSH_FETCH_FIRST[];\n     ++extern const char ADVICE_PUSH_NEEDS_FORCE[];\n     ++extern const char ADVICE_PUSH_NON_FF_CURRENT[];\n     ++extern const char ADVICE_PUSH_NON_FF_MATCHING[];\n     ++extern const char ADVICE_PUSH_UNQUALIFIED_REF_NAME[];\n     ++extern const char ADVICE_PUSH_UPDATE_REJECTED_ALIAS[];\n     ++extern const char ADVICE_PUSH_UPDATE_REJECTED[];\n     ++extern const char ADVICE_RESET_QUIET_WARNING[];\n     ++extern const char ADVICE_RESOLVE_CONFLICT[];\n     ++extern const char ADVICE_RM_HINTS[];\n     ++extern const char ADVICE_SEQUENCER_IN_USE[];\n     ++extern const char ADVICE_SET_UPSTREAM_FAILURE[];\n     ++extern const char ADVICE_STATUS_AHEAD_BEHIND_WARNING[];\n     ++extern const char ADVICE_STATUS_HINTS[];\n     ++extern const char ADVICE_STATUS_U_OPTION[];\n     ++extern const char ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE[];\n     ++extern const char ADVICE_WAITING_FOR_EDITOR[];\n      +\n       int git_default_advice_config(const char *var, const char *value);\n       __attribute__((format (printf, 1, 2)))\n       void advise(const char *advice, ...);\n      +\n      +/**\n     -+ Checks if advice type is enabled (can be printed to the user).\n     -+ Should be called before advise().\n     ++ * Checks if advice type is enabled (can be printed to the user).\n     ++ * Should be called before advise().\n      + */\n     -+int advice_enabled(enum advice_type type);\n     ++int advice_enabled(const char *advice_key);\n      +\n      +/**\n     -+ Checks the visibility of the advice before printing.\n     ++ * Checks the visibility of the advice before printing.\n      + */\n     -+void advise_if_enabled(enum advice_type type, const char *advice, ...);\n     ++void advise_if_enabled(const char *advice_key, const char *advice, ...);\n      +\n       int error_resolve_conflict(const char *me);\n       void NORETURN die_resolve_conflict(const char *me);\n     @@ -240,7 +264,7 @@\n      +\t  Any advice type can be used for testing, but NESTED_TAG was selected\n      +\t  here and in t0018 where this command is being executed.\n      +\t */\n     -+\tadvise_if_enabled(NESTED_TAG, argv[1]);\n     ++\tadvise_if_enabled(ADVICE_NESTED_TAG, argv[1]);\n      +\n      +\treturn 0;\n      +}\n 3:  01b195ebe1d ! 4:  2dee0abd452 tag: use new advice API to check visibility\n     @@ -2,9 +2,9 @@\n      \n          tag: use new advice API to check visibility\n      \n     -    Following the new helpers added to the advice library,\n     -    replace the global variable check approach by the new\n     -    API calls\n     +    change the advise call in tag library from advise() to\n     +    advise_if_enabled() to construct an example of the usage of\n     +    the new API.\n      \n          Signed-off-by: Heba Waly <heba.waly@gmail.com>\n      \n     @@ -18,7 +18,7 @@\n      -int advice_nested_tag = 1;\n       int advice_submodule_alternate_error_strategy_die = 1;\n       \n     - static int advice_use_color = -1;\n     + const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n      @@\n       \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n       \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n     @@ -50,7 +50,7 @@\n      -\tif (type == OBJ_TAG && advice_nested_tag)\n      -\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n      +\tif (type == OBJ_TAG)\n     -+\t\tadvise_if_enabled(NESTED_TAG, _(message_advice_nested_tag),\n     ++\t\tadvise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag),\n      +\t\t\t\t  tag, object_ref);\n       \n       \tstrbuf_addf(&header,\n\n-- \ngitgitgadget\n"},{"id":"392598","messageId":"176e38a841d1bbb138ae31b6db34fb597f9188b2.1582778112.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v6.git.1582778112.gitgitgadget@gmail.com","subject":"[PATCH v6 3/4] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-27T04:35:10Z","receivedAt":"2020-02-27T04:35:20Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nCurrently it's very easy for the advice library's callers to miss\nchecking the visibility step before printing an advice. Also, it makes\nmore sense for this step to be handled by the advice library.\n\nAdd a new advise_if_enabled function that checks the visibility of\nadvice messages before printing.\n\nAdd a new helper advise_enabled to check the visibility of the advice\nif the caller needs to carry out complicated processing based on that\nvalue.\n\nA list of config variables 'advice_config_keys' is added to be used by\nlist_config_advices() instead of 'advice_config[]' because we'll get\nrid of 'advice_config[]' and the global variables once we migrate all\nthe callers to use the new APIs.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n Makefile               |   1 +\n advice.c               | 111 +++++++++++++++++++++++++++++++++++++++--\n advice.h               |  49 ++++++++++++++++++\n t/helper/test-advise.c |  19 +++++++\n t/helper/test-tool.c   |   1 +\n t/helper/test-tool.h   |   1 +\n t/t0018-advice.sh      |  32 ++++++++++++\n 7 files changed, 210 insertions(+), 4 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\ndiff --git a/Makefile b/Makefile\nindex 09f98b777ca..ed923a3e818 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -695,6 +695,7 @@ X =\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n+TEST_BUILTINS_OBJS += test-advise.o\n TEST_BUILTINS_OBJS += test-chmtime.o\n TEST_BUILTINS_OBJS += test-config.o\n TEST_BUILTINS_OBJS += test-ctype.o\ndiff --git a/advice.c b/advice.c\nindex 258cc9ba7af..8d9f2910663 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -32,6 +32,40 @@ int advice_checkout_ambiguous_remote_branch_name = 1;\n int advice_nested_tag = 1;\n int advice_submodule_alternate_error_strategy_die = 1;\n \n+const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n+const char ADVICE_AM_WORK_DIR[] = \"advice.amWorkDir\";\n+const char ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME[] = \"advice.checkoutAmbiguousRemoteBranchName\";\n+const char ADVICE_COMMIT_BEFORE_MERGE[] = \"advice.commitBeforeMerge\";\n+const char ADVICE_DETACHED_HEAD[] = \"advice.detachedHead\";\n+const char ADVICE_FETCH_SHOW_FORCED_UPDATES[] = \"advice.fetchShowForcedUpdates\";\n+const char ADVICE_GRAFT_FILE_DEPRECATED[] = \"advice.graftFileDeprecated\";\n+const char ADVICE_IGNORED_HOOK[] = \"advice.ignoredHook\";\n+const char ADVICE_IMPLICIT_IDENTITY[] = \"advice.implicitIdentity\";\n+const char ADVICE_NESTED_TAG[] = \"advice.nestedTag\";\n+const char ADVICE_OBJECT_NAME_WARNING[] = \"advice.objectNameWarning\";\n+const char ADVICE_PUSH_ALREADY_EXISTS[] = \"advice.pushAlreadyExists\";\n+const char ADVICE_PUSH_FETCH_FIRST[] = \"advice.pushFetchFirst\";\n+const char ADVICE_PUSH_NEEDS_FORCE[] = \"advice.pushNeedsForce\";\n+const char ADVICE_PUSH_NON_FF_CURRENT[] = \"advice.pushNonFFCurrent\";\n+const char ADVICE_PUSH_NON_FF_MATCHING[] = \"advice.pushNonFFMatching\";\n+const char ADVICE_PUSH_UNQUALIFIED_REF_NAME[] = \"advice.pushUnqualifiedRefName\";\n+const char ADVICE_PUSH_UPDATE_REJECTED[] = \"advice.pushUpdateRejected\";\n+\n+/* make this an alias for backward compatibility */\n+const char ADVICE_PUSH_UPDATE_REJECTED_ALIAS[] = \"advice.pushNonFastForward\";\n+\n+const char ADVICE_RESET_QUIET_WARNING[] = \"advice.resetQuiet\";\n+const char ADVICE_RESOLVE_CONFLICT[] = \"advice.resolveConflict\";\n+const char ADVICE_RM_HINTS[] = \"advice.rmHints\";\n+const char ADVICE_SEQUENCER_IN_USE[] = \"advice.sequencerInUse\";\n+const char ADVICE_SET_UPSTREAM_FAILURE[] = \"advice.setUpstreamFailure\";\n+const char ADVICE_STATUS_AHEAD_BEHIND_WARNING[] = \"advice.statusAheadBehindWarning\";\n+const char ADVICE_STATUS_HINTS[] = \"advice.statusHints\";\n+const char ADVICE_STATUS_U_OPTION[] = \"advice.statusUoption\";\n+const char ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE[] = \"advice.submoduleAlternateErrorStrategyDie\";\n+const char ADVICE_WAITING_FOR_EDITOR[] = \"advice.waitingForEditor\";\n+\n+\n static int advice_use_color = -1;\n static char advice_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_RESET,\n@@ -96,13 +130,53 @@ static struct {\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n-static void vadvise(const char *advice, va_list params)\n+static const char *advice_config_keys[] = {\n+\tADVICE_ADD_EMBEDDED_REPO,\n+\tADVICE_AM_WORK_DIR,\n+\tADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n+\tADVICE_COMMIT_BEFORE_MERGE,\n+\tADVICE_DETACHED_HEAD,\n+\tADVICE_FETCH_SHOW_FORCED_UPDATES,\n+\tADVICE_GRAFT_FILE_DEPRECATED,\n+\tADVICE_IGNORED_HOOK,\n+\tADVICE_IMPLICIT_IDENTITY,\n+\tADVICE_NESTED_TAG,\n+\tADVICE_OBJECT_NAME_WARNING,\n+\tADVICE_PUSH_ALREADY_EXISTS,\n+\tADVICE_PUSH_FETCH_FIRST,\n+\tADVICE_PUSH_NEEDS_FORCE,\n+\tADVICE_PUSH_UPDATE_REJECTED_ALIAS,\n+\tADVICE_PUSH_NON_FF_CURRENT,\n+\tADVICE_PUSH_NON_FF_MATCHING,\n+\tADVICE_PUSH_UNQUALIFIED_REF_NAME,\n+\tADVICE_PUSH_UPDATE_REJECTED,\n+\tADVICE_RESET_QUIET_WARNING,\n+\tADVICE_RESOLVE_CONFLICT,\n+\tADVICE_RM_HINTS,\n+\tADVICE_SEQUENCER_IN_USE,\n+\tADVICE_SET_UPSTREAM_FAILURE,\n+\tADVICE_STATUS_AHEAD_BEHIND_WARNING,\n+\tADVICE_STATUS_HINTS,\n+\tADVICE_STATUS_U_OPTION,\n+\tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_WAITING_FOR_EDITOR,\n+};\n+\n+static const char turn_off_instructions[] =\n+N_(\"\\n\"\n+   \"Disable this message with \\\"git config %s false\\\"\");\n+\n+static void vadvise(const char *advice, int display_instructions,\n+\t\t    const char *key, va_list params)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *cp, *np;\n \n \tstrbuf_vaddf(&buf, advice, params);\n \n+\tif (display_instructions)\n+\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n+\n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n \t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n@@ -119,7 +193,36 @@ void advise(const char *advice, ...)\n {\n \tva_list params;\n \tva_start(params, advice);\n-\tvadvise(advice, params);\n+\tvadvise(advice, 0, \"\", params);\n+\tva_end(params);\n+}\n+\n+static int get_config_value(const char *advice_key)\n+{\n+\tint value = 1;\n+\n+\tgit_config_get_bool(advice_key, &value);\n+\treturn value;\n+}\n+\n+int advice_enabled(const char *advice_key)\n+{\n+\tif (advice_key == ADVICE_PUSH_UPDATE_REJECTED)\n+\t\treturn get_config_value(ADVICE_PUSH_UPDATE_REJECTED) &&\n+\t\t       get_config_value(ADVICE_PUSH_UPDATE_REJECTED_ALIAS);\n+\telse\n+\t\treturn get_config_value(advice_key);\n+}\n+\n+void advise_if_enabled(const char *advice_key, const char *advice, ...)\n+{\n+\tva_list params;\n+\n+\tif (!advice_enabled(advice_key))\n+\t\treturn;\n+\n+\tva_start(params, advice);\n+\tvadvise(advice, 1, advice_key, params);\n \tva_end(params);\n }\n \n@@ -159,8 +262,8 @@ void list_config_advices(struct string_list *list, const char *prefix)\n {\n \tint i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n-\t\tlist_config_item(list, prefix, advice_config[i].name);\n+\tfor (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n+\t\tlist_config_item(list, prefix, advice_config_keys[i]);\n }\n \n int error_resolve_conflict(const char *me)\ndiff --git a/advice.h b/advice.h\nindex b706780614d..78905c7ad57 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -32,9 +32,58 @@ extern int advice_checkout_ambiguous_remote_branch_name;\n extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n+/*\n+ * To add a new advice, you need to:\n+ * Define a new const char array.\n+ * Add a new entry to advice_config_keys list.\n+ * Add the new config variable to Documentation/config/advice.txt.\n+ * Call advise_if_enabled to print your advice.\n+ */\n+extern const char ADVICE_ADD_EMBEDDED_REPO[];\n+extern const char ADVICE_AM_WORK_DIR[];\n+extern const char ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME[];\n+extern const char ADVICE_COMMIT_BEFORE_MERGE[];\n+extern const char ADVICE_DETACHED_HEAD[];\n+extern const char ADVICE_FETCH_SHOW_FORCED_UPDATES[];\n+extern const char ADVICE_GRAFT_FILE_DEPRECATED[];\n+extern const char ADVICE_IGNORED_HOOK[];\n+extern const char ADVICE_IMPLICIT_IDENTITY[];\n+extern const char ADVICE_NESTED_TAG[];\n+extern const char ADVICE_OBJECT_NAME_WARNING[];\n+extern const char ADVICE_PUSH_ALREADY_EXISTS[];\n+extern const char ADVICE_PUSH_FETCH_FIRST[];\n+extern const char ADVICE_PUSH_NEEDS_FORCE[];\n+extern const char ADVICE_PUSH_NON_FF_CURRENT[];\n+extern const char ADVICE_PUSH_NON_FF_MATCHING[];\n+extern const char ADVICE_PUSH_UNQUALIFIED_REF_NAME[];\n+extern const char ADVICE_PUSH_UPDATE_REJECTED_ALIAS[];\n+extern const char ADVICE_PUSH_UPDATE_REJECTED[];\n+extern const char ADVICE_RESET_QUIET_WARNING[];\n+extern const char ADVICE_RESOLVE_CONFLICT[];\n+extern const char ADVICE_RM_HINTS[];\n+extern const char ADVICE_SEQUENCER_IN_USE[];\n+extern const char ADVICE_SET_UPSTREAM_FAILURE[];\n+extern const char ADVICE_STATUS_AHEAD_BEHIND_WARNING[];\n+extern const char ADVICE_STATUS_HINTS[];\n+extern const char ADVICE_STATUS_U_OPTION[];\n+extern const char ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE[];\n+extern const char ADVICE_WAITING_FOR_EDITOR[];\n+\n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\n void advise(const char *advice, ...);\n+\n+/**\n+ * Checks if advice type is enabled (can be printed to the user).\n+ * Should be called before advise().\n+ */\n+int advice_enabled(const char *advice_key);\n+\n+/**\n+ * Checks the visibility of the advice before printing.\n+ */\n+void advise_if_enabled(const char *advice_key, const char *advice, ...);\n+\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\ndiff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\nnew file mode 100644\nindex 00000000000..73e6edd07c8\n--- /dev/null\n+++ b/t/helper/test-advise.c\n@@ -0,0 +1,19 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"advice.h\"\n+\n+int cmd__advise_if_enabled(int argc, const char **argv)\n+{\n+\tif (!argv[1])\n+\tdie(\"usage: %s <advice>\", argv[0]);\n+\n+\tsetup_git_directory();\n+\n+\t/*\n+\t  Any advice type can be used for testing, but NESTED_TAG was selected\n+\t  here and in t0018 where this command is being executed.\n+\t */\n+\tadvise_if_enabled(ADVICE_NESTED_TAG, argv[1]);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d4497..6977badc690 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -14,6 +14,7 @@ struct test_cmd {\n };\n \n static struct test_cmd cmds[] = {\n+\t{ \"advise\", cmd__advise_if_enabled },\n \t{ \"chmtime\", cmd__chmtime },\n \t{ \"config\", cmd__config },\n \t{ \"ctype\", cmd__ctype },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1b..ca5e33b842f 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -4,6 +4,7 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"git-compat-util.h\"\n \n+int cmd__advise_if_enabled(int argc, const char **argv);\n int cmd__chmtime(int argc, const char **argv);\n int cmd__config(int argc, const char **argv);\n int cmd__ctype(int argc, const char **argv);\ndiff --git a/t/t0018-advice.sh b/t/t0018-advice.sh\nnew file mode 100755\nindex 00000000000..e03554d2f34\n--- /dev/null\n+++ b/t/t0018-advice.sh\n@@ -0,0 +1,32 @@\n+#!/bin/sh\n+\n+test_description='Test advise_if_enabled functionality'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'advice should be printed when config variable is unset' '\n+\tcat >expect <<-\\EOF &&\n+\thint: This is a piece of advice\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n+\tEOF\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n+test_expect_success 'advice should be printed when config variable is set to true' '\n+\tcat >expect <<-\\EOF &&\n+\thint: This is a piece of advice\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n+\tEOF\n+\ttest_config advice.nestedTag true &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n+test_expect_success 'advice should not be printed when config variable is set to false' '\n+\ttest_config advice.nestedTag false &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"392599","messageId":"2dee0abd452188addc93959af6e67e6f585de300.1582778112.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v6.git.1582778112.gitgitgadget@gmail.com","subject":"[PATCH v6 4/4] tag: use new advice API to check visibility","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-27T04:35:11Z","receivedAt":"2020-02-27T04:35:22Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nchange the advise call in tag library from advise() to\nadvise_if_enabled() to construct an example of the usage of\nthe new API.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c       | 2 --\n advice.h       | 1 -\n builtin/tag.c  | 5 +++--\n t/t7004-tag.sh | 1 +\n 4 files changed, 4 insertions(+), 5 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 8d9f2910663..07edd04e6c4 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -29,7 +29,6 @@ int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n int advice_graft_file_deprecated = 1;\n int advice_checkout_ambiguous_remote_branch_name = 1;\n-int advice_nested_tag = 1;\n int advice_submodule_alternate_error_strategy_die = 1;\n \n const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n@@ -123,7 +122,6 @@ static struct {\n \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n \t{ \"checkoutAmbiguousRemoteBranchName\", &advice_checkout_ambiguous_remote_branch_name },\n-\t{ \"nestedTag\", &advice_nested_tag },\n \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n \n \t/* make this an alias for backward compatibility */\ndiff --git a/advice.h b/advice.h\nindex 78905c7ad57..e68c3e807a0 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -29,7 +29,6 @@ extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n extern int advice_graft_file_deprecated;\n extern int advice_checkout_ambiguous_remote_branch_name;\n-extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n /*\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex e0a4c253828..cc30d346f5d 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \tif (type <= OBJ_NONE)\n \t\tdie(_(\"bad object type.\"));\n \n-\tif (type == OBJ_TAG && advice_nested_tag)\n-\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n+\tif (type == OBJ_TAG)\n+\t\tadvise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag),\n+\t\t\t\t  tag, object_ref);\n \n \tstrbuf_addf(&header,\n \t\t    \"object %s\\n\"\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 6db92bd3ba6..74b637deb25 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1726,6 +1726,7 @@ test_expect_success 'recursive tagging should give advice' '\n \thint: already a tag. If you meant to tag the object that it points to, use:\n \thint: |\n \thint: \tgit tag -f nested annotated-v4.0^{}\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n \tEOF\n \tgit tag -m nested nested annotated-v4.0 2>actual &&\n \ttest_i18ncmp expect actual\n-- \ngitgitgadget\n"},{"id":"392629","messageId":"xmqqo8tkexru.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"828b4aee19d09218beca6ad1d93006012be99a0a.1582778112.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 2/4] advice: change \"setupStreamFailure\" to \"setUpstreamFailure\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-27T17:38:13Z","receivedAt":"2020-02-27T17:38:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Heba Waly <heba.waly@gmail.com>\n>\n> fb6fbffbda (advice: keep config name in camelCase in advice_config[],\n> 2018-05-26) changed the config names to camelCase, but one of the names\n> wasn't changed correctly. Fix it.\n>\n> Signed-off-by: Heba Waly <heba.waly@gmail.com>\n> ---\n>  advice.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/advice.c b/advice.c\n> index fd836332dad..258cc9ba7af 100644\n> --- a/advice.c\n> +++ b/advice.c\n> @@ -80,7 +80,7 @@ static struct {\n>  \t{ \"sequencerInUse\", &advice_sequencer_in_use },\n>  \t{ \"implicitIdentity\", &advice_implicit_identity },\n>  \t{ \"detachedHead\", &advice_detached_head },\n> -\t{ \"setupStreamFailure\", &advice_set_upstream_failure },\n> +\t{ \"setUpstreamFailure\", &advice_set_upstream_failure },\n\nThe mistake is sort-of understandable, when \"setup\" is taken as\na verb.  Well spotted ;-)\n\n>  \t{ \"objectNameWarning\", &advice_object_name_warning },\n>  \t{ \"amWorkDir\", &advice_amworkdir },\n>  \t{ \"rmHints\", &advice_rm_hints },\n"},{"id":"392639","messageId":"xmqq7e07g3hr.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"176e38a841d1bbb138ae31b6db34fb597f9188b2.1582778112.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 3/4] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-27T20:49:20Z","receivedAt":"2020-02-27T20:49:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/advice.c b/advice.c\n> index 258cc9ba7af..8d9f2910663 100644\n> --- a/advice.c\n> +++ b/advice.c\n> @@ -32,6 +32,40 @@ int advice_checkout_ambiguous_remote_branch_name = 1;\n>  int advice_nested_tag = 1;\n>  int advice_submodule_alternate_error_strategy_die = 1;\n>  \n> +const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n> +const char ADVICE_AM_WORK_DIR[] = \"advice.amWorkDir\";\n> ...\n> +static const char *advice_config_keys[] = {\n> +\tADVICE_ADD_EMBEDDED_REPO,\n> +\tADVICE_AM_WORK_DIR,\n> ...\n> +/*\n> + * To add a new advice, you need to:\n> + * Define a new const char array.\n> + * Add a new entry to advice_config_keys list.\n> + * Add the new config variable to Documentation/config/advice.txt.\n> + * Call advise_if_enabled to print your advice.\n> + */\n> +extern const char ADVICE_ADD_EMBEDDED_REPO[];\n> +extern const char ADVICE_AM_WORK_DIR[];\n> ...\n\nHmph.\n\nEven though I said that I would prefer it over the current one,\nin that it allows the compilers to catch typo, and over the one\nin v5 which uses enum, in that we do not have to go through\nenum->string->hash conversion all the time, I have to say that I\nam not very happy that we'd need to make a consistent change to\nthree separate places.\n\nWhat's the ideal long-term outcome?  The reason why I suggested\nduring the v5 review an array of structure, a field in which can\nbe the .disabled field, was because it would allow us to later\nextend the struct to help users.  Wouldn't it be nice if we can\ndo something like:\n\n    $ git advice --list \"^fetch\"\n    fetchShowForcedUpdates\tenabled\n    $ git advice --list --verbose fetchShowForcedUpdates\n    fetchShowForcedUpdates\tenabled\n\t\"git fetch\" by default spends cycles to compute which\n\tbranches were forcibly updated, but it can be turned off.\n\tTo avoid mistaken sense of safety, however, a reminder\n\tmessage is issued instead when it is turned off.  The\n\treminder can be turned off with this advice key.\n\nSuch a future enhancement will become possible by assiciating a\nhelp text for each advice key, and the data structure introduced\nin <xmqqsgiymupf.fsf@gitster-ct.c.googlers.com> was meant to be\nthe beginning of it.\n\nI dunno.\n"},{"id":"392703","messageId":"CACg5j25odrJXOt7o5QadSWBAyYnKbSOWKNOjRKCRXPcVjqyheQ@mail.gmail.com","threadId":"52771","inReplyTo":"xmqq7e07g3hr.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v6 3/4] advice: revamp advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-02-29T00:58:51Z","receivedAt":"2020-02-29T00:59:09Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Fri, Feb 28, 2020 at 9:49 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > diff --git a/advice.c b/advice.c\n> > index 258cc9ba7af..8d9f2910663 100644\n> > --- a/advice.c\n> > +++ b/advice.c\n> > @@ -32,6 +32,40 @@ int advice_checkout_ambiguous_remote_branch_name = 1;\n> >  int advice_nested_tag = 1;\n> >  int advice_submodule_alternate_error_strategy_die = 1;\n> >\n> > +const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n> > +const char ADVICE_AM_WORK_DIR[] = \"advice.amWorkDir\";\n> > ...\n> > +static const char *advice_config_keys[] = {\n> > +     ADVICE_ADD_EMBEDDED_REPO,\n> > +     ADVICE_AM_WORK_DIR,\n> > ...\n> > +/*\n> > + * To add a new advice, you need to:\n> > + * Define a new const char array.\n> > + * Add a new entry to advice_config_keys list.\n> > + * Add the new config variable to Documentation/config/advice.txt.\n> > + * Call advise_if_enabled to print your advice.\n> > + */\n> > +extern const char ADVICE_ADD_EMBEDDED_REPO[];\n> > +extern const char ADVICE_AM_WORK_DIR[];\n> > ...\n>\n> Hmph.\n>\n> Even though I said that I would prefer it over the current one,\n> in that it allows the compilers to catch typo, and over the one\n> in v5 which uses enum, in that we do not have to go through\n> enum->string->hash conversion all the time, I have to say that I\n> am not very happy that we'd need to make a consistent change to\n> three separate places.\n>\n\nUnfortunately we'll have to make changes to three separate places\nuntil we implement a way to generate the documentation from the main\nlist or vice versa, which we agreed in v2 not to do that now.\nhttps://lore.kernel.org/git/xmqqftfam5ux.fsf@gitster-ct.c.googlers.com/\n\n> What's the ideal long-term outcome?  The reason why I suggested\n> during the v5 review an array of structure, a field in which can\n> be the .disabled field, was because it would allow us to later\n> extend the struct to help users.  Wouldn't it be nice if we can\n> do something like:\n>\n>     $ git advice --list \"^fetch\"\n>     fetchShowForcedUpdates      enabled\n>     $ git advice --list --verbose fetchShowForcedUpdates\n>     fetchShowForcedUpdates      enabled\n>         \"git fetch\" by default spends cycles to compute which\n>         branches were forcibly updated, but it can be turned off.\n>         To avoid mistaken sense of safety, however, a reminder\n>         message is issued instead when it is turned off.  The\n>         reminder can be turned off with this advice key.\n>\n> Such a future enhancement will become possible by assiciating a\n> help text for each advice key, and the data structure introduced\n> in <xmqqsgiymupf.fsf@gitster-ct.c.googlers.com> was meant to be\n> the beginning of it.\n\nOk, you convinced me, will send a new version.\n\nThanks,\nHeba\n"},{"id":"392751","messageId":"f668d9b7ca04da1d8adf0a146b48a46559471501.1583179320.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v7.git.1583179320.gitgitgadget@gmail.com","subject":"[PATCH v7 1/4] advice: extract vadvise() from advise()","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-03-02T20:01:57Z","receivedAt":"2020-03-02T20:02:08Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nIn preparation for a new advice method, extract a version of advise()\nthat uses an explict 'va_list' parameter. Call it from advise() for a\nfunctionally equivalent version.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c | 13 +++++++++----\n 1 file changed, 9 insertions(+), 4 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 249c60dcf32..fd836332dad 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -96,15 +96,12 @@ static struct {\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n-void advise(const char *advice, ...)\n+static void vadvise(const char *advice, va_list params)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n-\tva_list params;\n \tconst char *cp, *np;\n \n-\tva_start(params, advice);\n \tstrbuf_vaddf(&buf, advice, params);\n-\tva_end(params);\n \n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n@@ -118,6 +115,14 @@ void advise(const char *advice, ...)\n \tstrbuf_release(&buf);\n }\n \n+void advise(const char *advice, ...)\n+{\n+\tva_list params;\n+\tva_start(params, advice);\n+\tvadvise(advice, params);\n+\tva_end(params);\n+}\n+\n int git_default_advice_config(const char *var, const char *value)\n {\n \tconst char *k, *slot_name;\n-- \ngitgitgadget\n\n"},{"id":"392752","messageId":"pull.548.v7.git.1583179320.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v6.git.1582778112.gitgitgadget@gmail.com","subject":"[PATCH v7 0/4] [Outreachy] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-03-02T20:01:56Z","receivedAt":"2020-03-02T20:02:08Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"V7:\n\n * Back to the enum approach.\n * Cache enabled values in advice_setting array.\n * Although an approach was suggested to get rid of the switch case in\n   advice_enabled(), it is kept because it'll be needed when handling other\n   special cases e.g: ADVICE_ GRAFT_FILE_DEPRECATED.\n\n\n----------------------------------------------------------------------------\n\nIn V6:\n\n * Replace the enum approach by const char arrays.\n * Fix camelCase name for one of the config variables.\n\n\n----------------------------------------------------------------------------\n\nMain changes in V4:\n\n * Re-order the commits.\n * Free the output after using xstrfmt().\n\n\n----------------------------------------------------------------------------\n\nChanges in V3:\n\n * Remove the new wrapper advice_push_update_rejected_enabled() (which was\n   added in V2 to handle a special case of having a config variable alias),\n   and replace it by adding switch cases to advice_enabled() (The reason\n   behind this change is that another special case came up while I was\n   migrating the rest of the advise calls to the new APIs.)\n * Remove trailing whitespaces.\n\n\n----------------------------------------------------------------------------\n\nMain changes in V2:\n\n * Rename advise_ng to advise_if_enabled.\n * Add a new advise_enabled() helper.\n * Add a list of config variables names to replace advice_config[] (used by\n   list_config_advices()).\n * Send an enum parameter to the new advise helpers instead of strings.\n * Extract vadvise() from advise() and advise_if enabled().\n\n\n----------------------------------------------------------------------------\n\nThe advice API is currently a little bit confusing to call. quoting from\n[1]:\n\nWhen introducing a new advice message, you would\n\n * come up with advice.frotz configuration variable\n\n * define and declare advice_frotz global variable that defaults to\n   true\n\n * sprinkle calls like this:\n\n  if (advice_frotz)\n    advise(_(\"helpful message about frotz\"));\n\nA new approach was suggested in [1] which this patch is based upon.\n\nA new advise_if_enabled() is introduced to gradually replace advise()\nadvice_enabled() helper is also introduced to be used by those callers who:\n\n * Only need to check the visibility without calling advise() (they call\n   die() or error() instead for example)\n * Need to carry out some heavy processing to display an advice, in this\n   case they'll do: if(advice_enabled(advice_type))  advise(\"some advice message\");\n   \n   \n\nTo introduce a new advice message, the caller needs to:\n\n * Define a new advice_type enum.\n * Come up with a new config variable name and add this name to \n   advice_setting[]\n * Call advise_if_enabled(new_advice_type, \"advice message to be printed\")\n * Or call advice_enabled(new_advice_type) first and then follow it by\n   advise(\"advice message to be printed\") as explained earlier.\n * Add the new config variable to Documentation/config/advice.txt\n\nThe reason a new list of configuration variables was added to the library is\nto be used by the list_config_advices() function instead of advice_config[].\nAnd we should get rid of advice_config[] once we migrate all the callers to\nuse the new APIs instead of checking the global variables (which we'll get\nrid of as well).\n\nIn the future, we can investigate generating the documentation from the list\nof config variables or vice versa to make introducing a new advice much\neasier, but this approach will do it for now.\n\nV2 makes the process of introducing a new advice longer than V1 and almost\nas long as the original library, but having the advice library responsible\nfor checking the message visibility is still an improvement and in my own\nopinion the new structure makes better sense and makes the library less\nconfusing to use.\n\nAfter this patch the plan is to change the advise() calls to\nadvise_if_enabled() whenever possible, or at least replace the global\nvariables checks by advise_enabled() when advise_if_enabled() is not\nsuitable.\n\n[1] https://public-inbox.org/git/xmqqzhf5cw69.fsf@gitster-ct.c.googlers.com/\n\nHeba Waly (4):\n  advice: extract vadvise() from advise()\n  advice: change \"setupStreamFailure\" to \"setUpstreamFailure\"\n  advice: revamp advise API\n  tag: use new advice API to check visibility\n\n Makefile               |  1 +\n advice.c               | 97 ++++++++++++++++++++++++++++++++++++++----\n advice.h               | 52 +++++++++++++++++++++-\n builtin/tag.c          |  5 ++-\n t/helper/test-advise.c | 21 +++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 32 ++++++++++++++\n t/t7004-tag.sh         |  1 +\n 9 files changed, 199 insertions(+), 12 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\n\nbase-commit: c7a62075917b3340f908093f63f1161c44ed1475\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-548%2FHebaWaly%2Fadvice_refactoring-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-548/HebaWaly/advice_refactoring-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/548\n\nRange-diff vs v6:\n\n 1:  f668d9b7ca0 = 1:  f668d9b7ca0 advice: extract vadvise() from advise()\n 2:  828b4aee19d = 2:  828b4aee19d advice: change \"setupStreamFailure\" to \"setUpstreamFailure\"\n 3:  176e38a841d ! 3:  a4673803eaf advice: revamp advise API\n     @@ -13,10 +13,9 @@\n          if the caller needs to carry out complicated processing based on that\n          value.\n      \n     -    A list of config variables 'advice_config_keys' is added to be used by\n     -    list_config_advices() instead of 'advice_config[]' because we'll get\n     -    rid of 'advice_config[]' and the global variables once we migrate all\n     -    the callers to use the new APIs.\n     +    A list of advice_settings is added to cache the config variables names\n     +    and values, it's intended to replace advice_config[] and the global\n     +    variables once we migrate all the callers to use the new APIs.\n      \n          Signed-off-by: Heba Waly <heba.waly@gmail.com>\n      \n     @@ -35,87 +34,52 @@\n       diff --git a/advice.c b/advice.c\n       --- a/advice.c\n       +++ b/advice.c\n     -@@\n     - int advice_nested_tag = 1;\n     - int advice_submodule_alternate_error_strategy_die = 1;\n     - \n     -+const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n     -+const char ADVICE_AM_WORK_DIR[] = \"advice.amWorkDir\";\n     -+const char ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME[] = \"advice.checkoutAmbiguousRemoteBranchName\";\n     -+const char ADVICE_COMMIT_BEFORE_MERGE[] = \"advice.commitBeforeMerge\";\n     -+const char ADVICE_DETACHED_HEAD[] = \"advice.detachedHead\";\n     -+const char ADVICE_FETCH_SHOW_FORCED_UPDATES[] = \"advice.fetchShowForcedUpdates\";\n     -+const char ADVICE_GRAFT_FILE_DEPRECATED[] = \"advice.graftFileDeprecated\";\n     -+const char ADVICE_IGNORED_HOOK[] = \"advice.ignoredHook\";\n     -+const char ADVICE_IMPLICIT_IDENTITY[] = \"advice.implicitIdentity\";\n     -+const char ADVICE_NESTED_TAG[] = \"advice.nestedTag\";\n     -+const char ADVICE_OBJECT_NAME_WARNING[] = \"advice.objectNameWarning\";\n     -+const char ADVICE_PUSH_ALREADY_EXISTS[] = \"advice.pushAlreadyExists\";\n     -+const char ADVICE_PUSH_FETCH_FIRST[] = \"advice.pushFetchFirst\";\n     -+const char ADVICE_PUSH_NEEDS_FORCE[] = \"advice.pushNeedsForce\";\n     -+const char ADVICE_PUSH_NON_FF_CURRENT[] = \"advice.pushNonFFCurrent\";\n     -+const char ADVICE_PUSH_NON_FF_MATCHING[] = \"advice.pushNonFFMatching\";\n     -+const char ADVICE_PUSH_UNQUALIFIED_REF_NAME[] = \"advice.pushUnqualifiedRefName\";\n     -+const char ADVICE_PUSH_UPDATE_REJECTED[] = \"advice.pushUpdateRejected\";\n     -+\n     -+/* make this an alias for backward compatibility */\n     -+const char ADVICE_PUSH_UPDATE_REJECTED_ALIAS[] = \"advice.pushNonFastForward\";\n     -+\n     -+const char ADVICE_RESET_QUIET_WARNING[] = \"advice.resetQuiet\";\n     -+const char ADVICE_RESOLVE_CONFLICT[] = \"advice.resolveConflict\";\n     -+const char ADVICE_RM_HINTS[] = \"advice.rmHints\";\n     -+const char ADVICE_SEQUENCER_IN_USE[] = \"advice.sequencerInUse\";\n     -+const char ADVICE_SET_UPSTREAM_FAILURE[] = \"advice.setUpstreamFailure\";\n     -+const char ADVICE_STATUS_AHEAD_BEHIND_WARNING[] = \"advice.statusAheadBehindWarning\";\n     -+const char ADVICE_STATUS_HINTS[] = \"advice.statusHints\";\n     -+const char ADVICE_STATUS_U_OPTION[] = \"advice.statusUoption\";\n     -+const char ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE[] = \"advice.submoduleAlternateErrorStrategyDie\";\n     -+const char ADVICE_WAITING_FOR_EDITOR[] = \"advice.waitingForEditor\";\n     -+\n     -+\n     - static int advice_use_color = -1;\n     - static char advice_colors[][COLOR_MAXLEN] = {\n     - \tGIT_COLOR_RESET,\n      @@\n       \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n       };\n       \n      -static void vadvise(const char *advice, va_list params)\n     -+static const char *advice_config_keys[] = {\n     -+\tADVICE_ADD_EMBEDDED_REPO,\n     -+\tADVICE_AM_WORK_DIR,\n     -+\tADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n     -+\tADVICE_COMMIT_BEFORE_MERGE,\n     -+\tADVICE_DETACHED_HEAD,\n     -+\tADVICE_FETCH_SHOW_FORCED_UPDATES,\n     -+\tADVICE_GRAFT_FILE_DEPRECATED,\n     -+\tADVICE_IGNORED_HOOK,\n     -+\tADVICE_IMPLICIT_IDENTITY,\n     -+\tADVICE_NESTED_TAG,\n     -+\tADVICE_OBJECT_NAME_WARNING,\n     -+\tADVICE_PUSH_ALREADY_EXISTS,\n     -+\tADVICE_PUSH_FETCH_FIRST,\n     -+\tADVICE_PUSH_NEEDS_FORCE,\n     -+\tADVICE_PUSH_UPDATE_REJECTED_ALIAS,\n     -+\tADVICE_PUSH_NON_FF_CURRENT,\n     -+\tADVICE_PUSH_NON_FF_MATCHING,\n     -+\tADVICE_PUSH_UNQUALIFIED_REF_NAME,\n     -+\tADVICE_PUSH_UPDATE_REJECTED,\n     -+\tADVICE_RESET_QUIET_WARNING,\n     -+\tADVICE_RESOLVE_CONFLICT,\n     -+\tADVICE_RM_HINTS,\n     -+\tADVICE_SEQUENCER_IN_USE,\n     -+\tADVICE_SET_UPSTREAM_FAILURE,\n     -+\tADVICE_STATUS_AHEAD_BEHIND_WARNING,\n     -+\tADVICE_STATUS_HINTS,\n     -+\tADVICE_STATUS_U_OPTION,\n     -+\tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n     -+\tADVICE_WAITING_FOR_EDITOR,\n     ++static struct {\n     ++\tconst char *key;\n     ++\tint enabled;\n     ++} advice_setting[] = {\n     ++\t[ADVICE_ADD_EMBEDDED_REPO]\t\t\t= { \"addEmbeddedRepo\", 1 },\n     ++\t[ADVICE_AM_WORK_DIR] \t\t\t\t= { \"amWorkDir\", 1 },\n     ++\t[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] \t= { \"checkoutAmbiguousRemoteBranchName\", 1 },\n     ++\t[ADVICE_COMMIT_BEFORE_MERGE]\t\t\t= { \"commitBeforeMerge\", 1 },\n     ++\t[ADVICE_DETACHED_HEAD]\t\t\t\t= { \"detachedHead\", 1 },\n     ++\t[ADVICE_FETCH_SHOW_FORCED_UPDATES]\t\t= { \"fetchShowForcedUpdates\", 1 },\n     ++\t[ADVICE_GRAFT_FILE_DEPRECATED]\t\t\t= { \"graftFileDeprecated\", 1 },\n     ++\t[ADVICE_IGNORED_HOOK]\t\t\t\t= { \"ignoredHook\", 1 },\n     ++\t[ADVICE_IMPLICIT_IDENTITY]\t\t\t= { \"implicitIdentity\", 1 },\n     ++\t[ADVICE_NESTED_TAG]\t\t\t\t= { \"nestedTag\", 1 },\n     ++\t[ADVICE_OBJECT_NAME_WARNING]\t\t\t= { \"objectNameWarning\", 1 },\n     ++\t[ADVICE_PUSH_ALREADY_EXISTS]\t\t\t= { \"pushAlreadyExists\", 1 },\n     ++\t[ADVICE_PUSH_FETCH_FIRST]\t\t\t= { \"pushFetchFirst\", 1 },\n     ++\t[ADVICE_PUSH_NEEDS_FORCE]\t\t\t= { \"pushNeedsForce\", 1 },\n     ++\n     ++\t/* make this an alias for backward compatibility */\n     ++\t[ADVICE_PUSH_UPDATE_REJECTED_ALIAS]\t\t= { \"pushNonFastForward\", 1 },\n     ++\n     ++\t[ADVICE_PUSH_NON_FF_CURRENT]\t\t\t= { \"pushNonFFCurrent\", 1 },\n     ++\t[ADVICE_PUSH_NON_FF_MATCHING]\t\t\t= { \"pushNonFFMatching\", 1 },\n     ++\t[ADVICE_PUSH_UNQUALIFIED_REF_NAME]\t\t= { \"pushUnqualifiedRefName\", 1 },\n     ++\t[ADVICE_PUSH_UPDATE_REJECTED]\t\t\t= { \"pushUpdateRejected\", 1 },\n     ++\t[ADVICE_RESET_QUIET_WARNING]\t\t\t= { \"resetQuiet\", 1 },\n     ++\t[ADVICE_RESOLVE_CONFLICT]\t\t\t= { \"resolveConflict\", 1 },\n     ++\t[ADVICE_RM_HINTS]\t\t\t\t= { \"rmHints\", 1 },\n     ++\t[ADVICE_SEQUENCER_IN_USE]\t\t\t= { \"sequencerInUse\", 1 },\n     ++\t[ADVICE_SET_UPSTREAM_FAILURE]\t\t\t= { \"setUpstreamFailure\", 1 },\n     ++\t[ADVICE_STATUS_AHEAD_BEHIND_WARNING]\t\t= { \"statusAheadBehindWarning\", 1 },\n     ++\t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n     ++\t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n     ++\t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n     ++\t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n      +};\n      +\n      +static const char turn_off_instructions[] =\n      +N_(\"\\n\"\n     -+   \"Disable this message with \\\"git config %s false\\\"\");\n     ++   \"Disable this message with \\\"git config advice.%s false\\\"\");\n      +\n      +static void vadvise(const char *advice, int display_instructions,\n      +\t\t    const char *key, va_list params)\n     @@ -140,43 +104,51 @@\n      +\tva_end(params);\n      +}\n      +\n     -+static int get_config_value(const char *advice_key)\n     ++int advice_enabled(enum advice_type type)\n      +{\n     -+\tint value = 1;\n     -+\n     -+\tgit_config_get_bool(advice_key, &value);\n     -+\treturn value;\n     ++\tswitch(type) {\n     ++\tcase ADVICE_PUSH_UPDATE_REJECTED:\n     ++\t\treturn advice_setting[ADVICE_PUSH_UPDATE_REJECTED].enabled &&\n     ++\t\t       advice_setting[ADVICE_PUSH_UPDATE_REJECTED_ALIAS].enabled;\n     ++\tdefault:\n     ++\t\treturn advice_setting[type].enabled;\n     ++\t}\n      +}\n      +\n     -+int advice_enabled(const char *advice_key)\n     -+{\n     -+\tif (advice_key == ADVICE_PUSH_UPDATE_REJECTED)\n     -+\t\treturn get_config_value(ADVICE_PUSH_UPDATE_REJECTED) &&\n     -+\t\t       get_config_value(ADVICE_PUSH_UPDATE_REJECTED_ALIAS);\n     -+\telse\n     -+\t\treturn get_config_value(advice_key);\n     -+}\n     -+\n     -+void advise_if_enabled(const char *advice_key, const char *advice, ...)\n     ++void advise_if_enabled(enum advice_type type, const char *advice, ...)\n      +{\n      +\tva_list params;\n      +\n     -+\tif (!advice_enabled(advice_key))\n     ++\tif (!advice_enabled(type))\n      +\t\treturn;\n      +\n      +\tva_start(params, advice);\n     -+\tvadvise(advice, 1, advice_key, params);\n     ++\tvadvise(advice, 1, advice_setting[type].key, params);\n       \tva_end(params);\n       }\n       \n     +@@\n     + \t\tif (strcasecmp(k, advice_config[i].name))\n     + \t\t\tcontinue;\n     + \t\t*advice_config[i].preference = git_config_bool(var, value);\n     ++\t\tbreak;\n     ++\t}\n     ++\n     ++\tfor (i = 0; i < ARRAY_SIZE(advice_setting); i++) {\n     ++\t\tif (strcasecmp(k, advice_setting[i].key))\n     ++\t\t\tcontinue;\n     ++\t\tadvice_setting[i].enabled = git_config_bool(var, value);\n     + \t\treturn 0;\n     + \t}\n     + \n      @@\n       {\n       \tint i;\n       \n      -\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n      -\t\tlist_config_item(list, prefix, advice_config[i].name);\n     -+\tfor (i = 0; i < ARRAY_SIZE(advice_config_keys); i++)\n     -+\t\tlist_config_item(list, prefix, advice_config_keys[i]);\n     ++\tfor (i = 0; i < ARRAY_SIZE(advice_setting); i++)\n     ++\t\tlist_config_item(list, prefix, advice_setting[i].key);\n       }\n       \n       int error_resolve_conflict(const char *me)\n     @@ -190,40 +162,42 @@\n       \n      +/*\n      + * To add a new advice, you need to:\n     -+ * Define a new const char array.\n     -+ * Add a new entry to advice_config_keys list.\n     ++ * Define a new advice_type.\n     ++ * Add a new entry to advice_setting array.\n      + * Add the new config variable to Documentation/config/advice.txt.\n      + * Call advise_if_enabled to print your advice.\n      + */\n     -+extern const char ADVICE_ADD_EMBEDDED_REPO[];\n     -+extern const char ADVICE_AM_WORK_DIR[];\n     -+extern const char ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME[];\n     -+extern const char ADVICE_COMMIT_BEFORE_MERGE[];\n     -+extern const char ADVICE_DETACHED_HEAD[];\n     -+extern const char ADVICE_FETCH_SHOW_FORCED_UPDATES[];\n     -+extern const char ADVICE_GRAFT_FILE_DEPRECATED[];\n     -+extern const char ADVICE_IGNORED_HOOK[];\n     -+extern const char ADVICE_IMPLICIT_IDENTITY[];\n     -+extern const char ADVICE_NESTED_TAG[];\n     -+extern const char ADVICE_OBJECT_NAME_WARNING[];\n     -+extern const char ADVICE_PUSH_ALREADY_EXISTS[];\n     -+extern const char ADVICE_PUSH_FETCH_FIRST[];\n     -+extern const char ADVICE_PUSH_NEEDS_FORCE[];\n     -+extern const char ADVICE_PUSH_NON_FF_CURRENT[];\n     -+extern const char ADVICE_PUSH_NON_FF_MATCHING[];\n     -+extern const char ADVICE_PUSH_UNQUALIFIED_REF_NAME[];\n     -+extern const char ADVICE_PUSH_UPDATE_REJECTED_ALIAS[];\n     -+extern const char ADVICE_PUSH_UPDATE_REJECTED[];\n     -+extern const char ADVICE_RESET_QUIET_WARNING[];\n     -+extern const char ADVICE_RESOLVE_CONFLICT[];\n     -+extern const char ADVICE_RM_HINTS[];\n     -+extern const char ADVICE_SEQUENCER_IN_USE[];\n     -+extern const char ADVICE_SET_UPSTREAM_FAILURE[];\n     -+extern const char ADVICE_STATUS_AHEAD_BEHIND_WARNING[];\n     -+extern const char ADVICE_STATUS_HINTS[];\n     -+extern const char ADVICE_STATUS_U_OPTION[];\n     -+extern const char ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE[];\n     -+extern const char ADVICE_WAITING_FOR_EDITOR[];\n     ++ enum advice_type {\n     ++\tADVICE_ADD_EMBEDDED_REPO,\n     ++\tADVICE_AM_WORK_DIR,\n     ++\tADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n     ++\tADVICE_COMMIT_BEFORE_MERGE,\n     ++\tADVICE_DETACHED_HEAD,\n     ++\tADVICE_FETCH_SHOW_FORCED_UPDATES,\n     ++\tADVICE_GRAFT_FILE_DEPRECATED,\n     ++\tADVICE_IGNORED_HOOK,\n     ++\tADVICE_IMPLICIT_IDENTITY,\n     ++\tADVICE_NESTED_TAG,\n     ++\tADVICE_OBJECT_NAME_WARNING,\n     ++\tADVICE_PUSH_ALREADY_EXISTS,\n     ++\tADVICE_PUSH_FETCH_FIRST,\n     ++\tADVICE_PUSH_NEEDS_FORCE,\n     ++\tADVICE_PUSH_NON_FF_CURRENT,\n     ++\tADVICE_PUSH_NON_FF_MATCHING,\n     ++\tADVICE_PUSH_UNQUALIFIED_REF_NAME,\n     ++\tADVICE_PUSH_UPDATE_REJECTED_ALIAS,\n     ++\tADVICE_PUSH_UPDATE_REJECTED,\n     ++\tADVICE_RESET_QUIET_WARNING,\n     ++\tADVICE_RESOLVE_CONFLICT,\n     ++\tADVICE_RM_HINTS,\n     ++\tADVICE_SEQUENCER_IN_USE,\n     ++\tADVICE_SET_UPSTREAM_FAILURE,\n     ++\tADVICE_STATUS_AHEAD_BEHIND_WARNING,\n     ++\tADVICE_STATUS_HINTS,\n     ++\tADVICE_STATUS_U_OPTION,\n     ++\tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n     ++\tADVICE_WAITING_FOR_EDITOR,\n     ++};\n      +\n       int git_default_advice_config(const char *var, const char *value);\n       __attribute__((format (printf, 1, 2)))\n     @@ -233,12 +207,12 @@\n      + * Checks if advice type is enabled (can be printed to the user).\n      + * Should be called before advise().\n      + */\n     -+int advice_enabled(const char *advice_key);\n     ++int advice_enabled(enum advice_type type);\n      +\n      +/**\n      + * Checks the visibility of the advice before printing.\n      + */\n     -+void advise_if_enabled(const char *advice_key, const char *advice, ...);\n     ++void advise_if_enabled(enum advice_type type, const char *advice, ...);\n      +\n       int error_resolve_conflict(const char *me);\n       void NORETURN die_resolve_conflict(const char *me);\n     @@ -252,6 +226,7 @@\n      +#include \"test-tool.h\"\n      +#include \"cache.h\"\n      +#include \"advice.h\"\n     ++#include \"config.h\"\n      +\n      +int cmd__advise_if_enabled(int argc, const char **argv)\n      +{\n     @@ -259,6 +234,7 @@\n      +\tdie(\"usage: %s <advice>\", argv[0]);\n      +\n      +\tsetup_git_directory();\n     ++\tgit_config(git_default_config, NULL);\n      +\n      +\t/*\n      +\t  Any advice type can be used for testing, but NESTED_TAG was selected\n 4:  2dee0abd452 ! 4:  3e213cb59b7 tag: use new advice API to check visibility\n     @@ -18,7 +18,7 @@\n      -int advice_nested_tag = 1;\n       int advice_submodule_alternate_error_strategy_die = 1;\n       \n     - const char ADVICE_ADD_EMBEDDED_REPO[] = \"advice.addEmbeddedRepo\";\n     + static int advice_use_color = -1;\n      @@\n       \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n       \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n\n-- \ngitgitgadget\n"},{"id":"392753","messageId":"828b4aee19d09218beca6ad1d93006012be99a0a.1583179320.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v7.git.1583179320.gitgitgadget@gmail.com","subject":"[PATCH v7 2/4] advice: change \"setupStreamFailure\" to \"setUpstreamFailure\"","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-03-02T20:01:58Z","receivedAt":"2020-03-02T20:02:09Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nfb6fbffbda (advice: keep config name in camelCase in advice_config[],\n2018-05-26) changed the config names to camelCase, but one of the names\nwasn't changed correctly. Fix it.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/advice.c b/advice.c\nindex fd836332dad..258cc9ba7af 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -80,7 +80,7 @@ static struct {\n \t{ \"sequencerInUse\", &advice_sequencer_in_use },\n \t{ \"implicitIdentity\", &advice_implicit_identity },\n \t{ \"detachedHead\", &advice_detached_head },\n-\t{ \"setupStreamFailure\", &advice_set_upstream_failure },\n+\t{ \"setUpstreamFailure\", &advice_set_upstream_failure },\n \t{ \"objectNameWarning\", &advice_object_name_warning },\n \t{ \"amWorkDir\", &advice_amworkdir },\n \t{ \"rmHints\", &advice_rm_hints },\n-- \ngitgitgadget\n\n"},{"id":"392754","messageId":"3e213cb59b7237fda0f3cea125155e961b6a462e.1583179320.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v7.git.1583179320.gitgitgadget@gmail.com","subject":"[PATCH v7 4/4] tag: use new advice API to check visibility","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-03-02T20:02:00Z","receivedAt":"2020-03-02T20:02:10Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nchange the advise call in tag library from advise() to\nadvise_if_enabled() to construct an example of the usage of\nthe new API.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n advice.c       | 2 --\n advice.h       | 1 -\n builtin/tag.c  | 5 +++--\n t/t7004-tag.sh | 1 +\n 4 files changed, 4 insertions(+), 5 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 9814d6cdfd8..ef4bef65d8c 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -29,7 +29,6 @@ int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n int advice_graft_file_deprecated = 1;\n int advice_checkout_ambiguous_remote_branch_name = 1;\n-int advice_nested_tag = 1;\n int advice_submodule_alternate_error_strategy_die = 1;\n \n static int advice_use_color = -1;\n@@ -89,7 +88,6 @@ static struct {\n \t{ \"waitingForEditor\", &advice_waiting_for_editor },\n \t{ \"graftFileDeprecated\", &advice_graft_file_deprecated },\n \t{ \"checkoutAmbiguousRemoteBranchName\", &advice_checkout_ambiguous_remote_branch_name },\n-\t{ \"nestedTag\", &advice_nested_tag },\n \t{ \"submoduleAlternateErrorStrategyDie\", &advice_submodule_alternate_error_strategy_die },\n \n \t/* make this an alias for backward compatibility */\ndiff --git a/advice.h b/advice.h\nindex d72ab4a06c1..77cbe5c6b36 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -29,7 +29,6 @@ extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n extern int advice_graft_file_deprecated;\n extern int advice_checkout_ambiguous_remote_branch_name;\n-extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n /*\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex e0a4c253828..cc30d346f5d 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref,\n \tif (type <= OBJ_NONE)\n \t\tdie(_(\"bad object type.\"));\n \n-\tif (type == OBJ_TAG && advice_nested_tag)\n-\t\tadvise(_(message_advice_nested_tag), tag, object_ref);\n+\tif (type == OBJ_TAG)\n+\t\tadvise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag),\n+\t\t\t\t  tag, object_ref);\n \n \tstrbuf_addf(&header,\n \t\t    \"object %s\\n\"\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 6db92bd3ba6..74b637deb25 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1726,6 +1726,7 @@ test_expect_success 'recursive tagging should give advice' '\n \thint: already a tag. If you meant to tag the object that it points to, use:\n \thint: |\n \thint: \tgit tag -f nested annotated-v4.0^{}\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n \tEOF\n \tgit tag -m nested nested annotated-v4.0 2>actual &&\n \ttest_i18ncmp expect actual\n-- \ngitgitgadget\n"},{"id":"392755","messageId":"a4673803eaf881f7caa25d97e432c0ff0d065060.1583179320.git.gitgitgadget@gmail.com","threadId":"52771","inReplyTo":"pull.548.v7.git.1583179320.gitgitgadget@gmail.com","subject":"[PATCH v7 3/4] advice: revamp advise API","fromName":"Heba Waly via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-03-02T20:01:59Z","receivedAt":"2020-03-02T20:02:10Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"From: Heba Waly <heba.waly@gmail.com>\n\nCurrently it's very easy for the advice library's callers to miss\nchecking the visibility step before printing an advice. Also, it makes\nmore sense for this step to be handled by the advice library.\n\nAdd a new advise_if_enabled function that checks the visibility of\nadvice messages before printing.\n\nAdd a new helper advise_enabled to check the visibility of the advice\nif the caller needs to carry out complicated processing based on that\nvalue.\n\nA list of advice_settings is added to cache the config variables names\nand values, it's intended to replace advice_config[] and the global\nvariables once we migrate all the callers to use the new APIs.\n\nSigned-off-by: Heba Waly <heba.waly@gmail.com>\n---\n Makefile               |  1 +\n advice.c               | 84 ++++++++++++++++++++++++++++++++++++++++--\n advice.h               | 51 +++++++++++++++++++++++++\n t/helper/test-advise.c | 21 +++++++++++\n t/helper/test-tool.c   |  1 +\n t/helper/test-tool.h   |  1 +\n t/t0018-advice.sh      | 32 ++++++++++++++++\n 7 files changed, 187 insertions(+), 4 deletions(-)\n create mode 100644 t/helper/test-advise.c\n create mode 100755 t/t0018-advice.sh\n\ndiff --git a/Makefile b/Makefile\nindex 09f98b777ca..ed923a3e818 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -695,6 +695,7 @@ X =\n \n PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))\n \n+TEST_BUILTINS_OBJS += test-advise.o\n TEST_BUILTINS_OBJS += test-chmtime.o\n TEST_BUILTINS_OBJS += test-config.o\n TEST_BUILTINS_OBJS += test-ctype.o\ndiff --git a/advice.c b/advice.c\nindex 258cc9ba7af..9814d6cdfd8 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -96,13 +96,59 @@ static struct {\n \t{ \"pushNonFastForward\", &advice_push_update_rejected }\n };\n \n-static void vadvise(const char *advice, va_list params)\n+static struct {\n+\tconst char *key;\n+\tint enabled;\n+} advice_setting[] = {\n+\t[ADVICE_ADD_EMBEDDED_REPO]\t\t\t= { \"addEmbeddedRepo\", 1 },\n+\t[ADVICE_AM_WORK_DIR] \t\t\t\t= { \"amWorkDir\", 1 },\n+\t[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] \t= { \"checkoutAmbiguousRemoteBranchName\", 1 },\n+\t[ADVICE_COMMIT_BEFORE_MERGE]\t\t\t= { \"commitBeforeMerge\", 1 },\n+\t[ADVICE_DETACHED_HEAD]\t\t\t\t= { \"detachedHead\", 1 },\n+\t[ADVICE_FETCH_SHOW_FORCED_UPDATES]\t\t= { \"fetchShowForcedUpdates\", 1 },\n+\t[ADVICE_GRAFT_FILE_DEPRECATED]\t\t\t= { \"graftFileDeprecated\", 1 },\n+\t[ADVICE_IGNORED_HOOK]\t\t\t\t= { \"ignoredHook\", 1 },\n+\t[ADVICE_IMPLICIT_IDENTITY]\t\t\t= { \"implicitIdentity\", 1 },\n+\t[ADVICE_NESTED_TAG]\t\t\t\t= { \"nestedTag\", 1 },\n+\t[ADVICE_OBJECT_NAME_WARNING]\t\t\t= { \"objectNameWarning\", 1 },\n+\t[ADVICE_PUSH_ALREADY_EXISTS]\t\t\t= { \"pushAlreadyExists\", 1 },\n+\t[ADVICE_PUSH_FETCH_FIRST]\t\t\t= { \"pushFetchFirst\", 1 },\n+\t[ADVICE_PUSH_NEEDS_FORCE]\t\t\t= { \"pushNeedsForce\", 1 },\n+\n+\t/* make this an alias for backward compatibility */\n+\t[ADVICE_PUSH_UPDATE_REJECTED_ALIAS]\t\t= { \"pushNonFastForward\", 1 },\n+\n+\t[ADVICE_PUSH_NON_FF_CURRENT]\t\t\t= { \"pushNonFFCurrent\", 1 },\n+\t[ADVICE_PUSH_NON_FF_MATCHING]\t\t\t= { \"pushNonFFMatching\", 1 },\n+\t[ADVICE_PUSH_UNQUALIFIED_REF_NAME]\t\t= { \"pushUnqualifiedRefName\", 1 },\n+\t[ADVICE_PUSH_UPDATE_REJECTED]\t\t\t= { \"pushUpdateRejected\", 1 },\n+\t[ADVICE_RESET_QUIET_WARNING]\t\t\t= { \"resetQuiet\", 1 },\n+\t[ADVICE_RESOLVE_CONFLICT]\t\t\t= { \"resolveConflict\", 1 },\n+\t[ADVICE_RM_HINTS]\t\t\t\t= { \"rmHints\", 1 },\n+\t[ADVICE_SEQUENCER_IN_USE]\t\t\t= { \"sequencerInUse\", 1 },\n+\t[ADVICE_SET_UPSTREAM_FAILURE]\t\t\t= { \"setUpstreamFailure\", 1 },\n+\t[ADVICE_STATUS_AHEAD_BEHIND_WARNING]\t\t= { \"statusAheadBehindWarning\", 1 },\n+\t[ADVICE_STATUS_HINTS]\t\t\t\t= { \"statusHints\", 1 },\n+\t[ADVICE_STATUS_U_OPTION]\t\t\t= { \"statusUoption\", 1 },\n+\t[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { \"submoduleAlternateErrorStrategyDie\", 1 },\n+\t[ADVICE_WAITING_FOR_EDITOR]\t\t\t= { \"waitingForEditor\", 1 },\n+};\n+\n+static const char turn_off_instructions[] =\n+N_(\"\\n\"\n+   \"Disable this message with \\\"git config advice.%s false\\\"\");\n+\n+static void vadvise(const char *advice, int display_instructions,\n+\t\t    const char *key, va_list params)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *cp, *np;\n \n \tstrbuf_vaddf(&buf, advice, params);\n \n+\tif (display_instructions)\n+\t\tstrbuf_addf(&buf, turn_off_instructions, key);\n+\n \tfor (cp = buf.buf; *cp; cp = np) {\n \t\tnp = strchrnul(cp, '\\n');\n \t\tfprintf(stderr,\t_(\"%shint: %.*s%s\\n\"),\n@@ -119,7 +165,30 @@ void advise(const char *advice, ...)\n {\n \tva_list params;\n \tva_start(params, advice);\n-\tvadvise(advice, params);\n+\tvadvise(advice, 0, \"\", params);\n+\tva_end(params);\n+}\n+\n+int advice_enabled(enum advice_type type)\n+{\n+\tswitch(type) {\n+\tcase ADVICE_PUSH_UPDATE_REJECTED:\n+\t\treturn advice_setting[ADVICE_PUSH_UPDATE_REJECTED].enabled &&\n+\t\t       advice_setting[ADVICE_PUSH_UPDATE_REJECTED_ALIAS].enabled;\n+\tdefault:\n+\t\treturn advice_setting[type].enabled;\n+\t}\n+}\n+\n+void advise_if_enabled(enum advice_type type, const char *advice, ...)\n+{\n+\tva_list params;\n+\n+\tif (!advice_enabled(type))\n+\t\treturn;\n+\n+\tva_start(params, advice);\n+\tvadvise(advice, 1, advice_setting[type].key, params);\n \tva_end(params);\n }\n \n@@ -149,6 +218,13 @@ int git_default_advice_config(const char *var, const char *value)\n \t\tif (strcasecmp(k, advice_config[i].name))\n \t\t\tcontinue;\n \t\t*advice_config[i].preference = git_config_bool(var, value);\n+\t\tbreak;\n+\t}\n+\n+\tfor (i = 0; i < ARRAY_SIZE(advice_setting); i++) {\n+\t\tif (strcasecmp(k, advice_setting[i].key))\n+\t\t\tcontinue;\n+\t\tadvice_setting[i].enabled = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n@@ -159,8 +235,8 @@ void list_config_advices(struct string_list *list, const char *prefix)\n {\n \tint i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(advice_config); i++)\n-\t\tlist_config_item(list, prefix, advice_config[i].name);\n+\tfor (i = 0; i < ARRAY_SIZE(advice_setting); i++)\n+\t\tlist_config_item(list, prefix, advice_setting[i].key);\n }\n \n int error_resolve_conflict(const char *me)\ndiff --git a/advice.h b/advice.h\nindex b706780614d..d72ab4a06c1 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -32,9 +32,60 @@ extern int advice_checkout_ambiguous_remote_branch_name;\n extern int advice_nested_tag;\n extern int advice_submodule_alternate_error_strategy_die;\n \n+/*\n+ * To add a new advice, you need to:\n+ * Define a new advice_type.\n+ * Add a new entry to advice_setting array.\n+ * Add the new config variable to Documentation/config/advice.txt.\n+ * Call advise_if_enabled to print your advice.\n+ */\n+ enum advice_type {\n+\tADVICE_ADD_EMBEDDED_REPO,\n+\tADVICE_AM_WORK_DIR,\n+\tADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n+\tADVICE_COMMIT_BEFORE_MERGE,\n+\tADVICE_DETACHED_HEAD,\n+\tADVICE_FETCH_SHOW_FORCED_UPDATES,\n+\tADVICE_GRAFT_FILE_DEPRECATED,\n+\tADVICE_IGNORED_HOOK,\n+\tADVICE_IMPLICIT_IDENTITY,\n+\tADVICE_NESTED_TAG,\n+\tADVICE_OBJECT_NAME_WARNING,\n+\tADVICE_PUSH_ALREADY_EXISTS,\n+\tADVICE_PUSH_FETCH_FIRST,\n+\tADVICE_PUSH_NEEDS_FORCE,\n+\tADVICE_PUSH_NON_FF_CURRENT,\n+\tADVICE_PUSH_NON_FF_MATCHING,\n+\tADVICE_PUSH_UNQUALIFIED_REF_NAME,\n+\tADVICE_PUSH_UPDATE_REJECTED_ALIAS,\n+\tADVICE_PUSH_UPDATE_REJECTED,\n+\tADVICE_RESET_QUIET_WARNING,\n+\tADVICE_RESOLVE_CONFLICT,\n+\tADVICE_RM_HINTS,\n+\tADVICE_SEQUENCER_IN_USE,\n+\tADVICE_SET_UPSTREAM_FAILURE,\n+\tADVICE_STATUS_AHEAD_BEHIND_WARNING,\n+\tADVICE_STATUS_HINTS,\n+\tADVICE_STATUS_U_OPTION,\n+\tADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,\n+\tADVICE_WAITING_FOR_EDITOR,\n+};\n+\n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\n void advise(const char *advice, ...);\n+\n+/**\n+ * Checks if advice type is enabled (can be printed to the user).\n+ * Should be called before advise().\n+ */\n+int advice_enabled(enum advice_type type);\n+\n+/**\n+ * Checks the visibility of the advice before printing.\n+ */\n+void advise_if_enabled(enum advice_type type, const char *advice, ...);\n+\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\ndiff --git a/t/helper/test-advise.c b/t/helper/test-advise.c\nnew file mode 100644\nindex 00000000000..cdafa413e37\n--- /dev/null\n+++ b/t/helper/test-advise.c\n@@ -0,0 +1,21 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"advice.h\"\n+#include \"config.h\"\n+\n+int cmd__advise_if_enabled(int argc, const char **argv)\n+{\n+\tif (!argv[1])\n+\tdie(\"usage: %s <advice>\", argv[0]);\n+\n+\tsetup_git_directory();\n+\tgit_config(git_default_config, NULL);\n+\n+\t/*\n+\t  Any advice type can be used for testing, but NESTED_TAG was selected\n+\t  here and in t0018 where this command is being executed.\n+\t */\n+\tadvise_if_enabled(ADVICE_NESTED_TAG, argv[1]);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex f20989d4497..6977badc690 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -14,6 +14,7 @@ struct test_cmd {\n };\n \n static struct test_cmd cmds[] = {\n+\t{ \"advise\", cmd__advise_if_enabled },\n \t{ \"chmtime\", cmd__chmtime },\n \t{ \"config\", cmd__config },\n \t{ \"ctype\", cmd__ctype },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 8ed2af71d1b..ca5e33b842f 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -4,6 +4,7 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"git-compat-util.h\"\n \n+int cmd__advise_if_enabled(int argc, const char **argv);\n int cmd__chmtime(int argc, const char **argv);\n int cmd__config(int argc, const char **argv);\n int cmd__ctype(int argc, const char **argv);\ndiff --git a/t/t0018-advice.sh b/t/t0018-advice.sh\nnew file mode 100755\nindex 00000000000..e03554d2f34\n--- /dev/null\n+++ b/t/t0018-advice.sh\n@@ -0,0 +1,32 @@\n+#!/bin/sh\n+\n+test_description='Test advise_if_enabled functionality'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'advice should be printed when config variable is unset' '\n+\tcat >expect <<-\\EOF &&\n+\thint: This is a piece of advice\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n+\tEOF\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n+test_expect_success 'advice should be printed when config variable is set to true' '\n+\tcat >expect <<-\\EOF &&\n+\thint: This is a piece of advice\n+\thint: Disable this message with \"git config advice.nestedTag false\"\n+\tEOF\n+\ttest_config advice.nestedTag true &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_i18ncmp expect actual\n+'\n+\n+test_expect_success 'advice should not be printed when config variable is set to false' '\n+\ttest_config advice.nestedTag false &&\n+\ttest-tool advise \"This is a piece of advice\" 2>actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"392756","messageId":"xmqqblpebhae.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"a4673803eaf881f7caa25d97e432c0ff0d065060.1583179320.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v7 3/4] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-02T21:03:53Z","receivedAt":"2020-03-02T21:04:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +static struct {\n> +\tconst char *key;\n> +\tint enabled;\n> +} advice_setting[] = {\n> +\t[ADVICE_ADD_EMBEDDED_REPO]\t\t\t= { \"addEmbeddedRepo\", 1 },\n\nIt would be nicer to future developers to flip the polarity, as we\ndo not have to write 1 all over the place, especially if we plan to\nextend the structure over time and to use designated initializers\nfor only certain fields:\n\n\tstatic struct {\n\t\tconst char *key;\n\t\tint disabled;\n\t} advice_setting[] = {\n\t\t[ADDVICE_ADD_EMBEDDED_REPO] = { .key = \"addEmbeddedRepo\" },\n\n> @@ -149,6 +218,13 @@ int git_default_advice_config(const char *var, const char *value)\n>  \t\tif (strcasecmp(k, advice_config[i].name))\n>  \t\t\tcontinue;\n>  \t\t*advice_config[i].preference = git_config_bool(var, value);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tfor (i = 0; i < ARRAY_SIZE(advice_setting); i++) {\n> +\t\tif (strcasecmp(k, advice_setting[i].key))\n> +\t\t\tcontinue;\n> +\t\tadvice_setting[i].enabled = git_config_bool(var, value);\n>  \t\treturn 0;\n\nTurning this into \"break;\" would make it similar to the loop before\nthis one, and will allow other people to add more code after this\nloop later.\n\n> +int cmd__advise_if_enabled(int argc, const char **argv)\n> +{\n> +\tif (!argv[1])\n> +\tdie(\"usage: %s <advice>\", argv[0]);\n> +\n> +\tsetup_git_directory();\n> +\tgit_config(git_default_config, NULL);\n> +\n> +\t/*\n> +\t  Any advice type can be used for testing, but NESTED_TAG was selected\n> +\t  here and in t0018 where this command is being executed.\n> +\t */\n\nStyle (will fix up locally).\n\nThanks.  I think this is reasonable with or without the suggested\nfixes.\n"},{"id":"392781","messageId":"xmqqlfoha5ia.fsf@gitster-ct.c.googlers.com","threadId":"52771","inReplyTo":"xmqqblpebhae.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v7 3/4] advice: revamp advise API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-03T14:15:57Z","receivedAt":"2020-03-03T14:16:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> +static struct {\n>> +\tconst char *key;\n>> +\tint enabled;\n>> +} advice_setting[] = {\n>> +\t[ADVICE_ADD_EMBEDDED_REPO]\t\t\t= { \"addEmbeddedRepo\", 1 },\n>\n> It would be nicer to future developers to flip the polarity, as we\n> do not have to write 1 all over the place, especially if we plan to\n> extend the structure over time and to use designated initializers\n> for only certain fields...\n\nJust to avoid needless churn, I think this does not matter in the\nlonger term, so .enabled is OK as-is.  The reason I say so is\nbecause, even though renaming to .disabled to allow initializers to\ndefault it to 0 is nicer for those who write the initializers\nmanually, and it especially is true when we have more fields in the\nstruct (we may add descriptive text so that we can issue an on-line\nhelp, for example), but I expect that would happen much later than\nwe start generating these parts of the source code in two places\n(the initializer for advice_setting[] and the advice_type enum) from\na single source by mechanical process.  And the auto-generation will\neliminate the burden of writing 1 manually.\n"},{"id":"392837","messageId":"CACg5j26fiJQJZ1CZy2AHWEiPmzJ+XsiA+Zfsp4Mc6MgP9i=Crg@mail.gmail.com","threadId":"52771","inReplyTo":"xmqqlfoha5ia.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v7 3/4] advice: revamp advise API","fromName":"Heba Waly","fromEmail":"heba.waly@gmail.com","sentAt":"2020-03-04T03:22:36Z","receivedAt":"2020-03-04T03:22:51Z","isPatch":true,"sender":{"key":"heba.waly@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1539076?v=4"},"body":"On Wed, Mar 4, 2020 at 3:16 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n> > \"Heba Waly via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> >> +static struct {\n> >> +    const char *key;\n> >> +    int enabled;\n> >> +} advice_setting[] = {\n> >> +    [ADVICE_ADD_EMBEDDED_REPO]                      = { \"addEmbeddedRepo\", 1 },\n> >\n> > It would be nicer to future developers to flip the polarity, as we\n> > do not have to write 1 all over the place, especially if we plan to\n> > extend the structure over time and to use designated initializers\n> > for only certain fields...\n>\n> Just to avoid needless churn, I think this does not matter in the\n> longer term, so .enabled is OK as-is.  The reason I say so is\n> because, even though renaming to .disabled to allow initializers to\n> default it to 0 is nicer for those who write the initializers\n> manually, and it especially is true when we have more fields in the\n> struct (we may add descriptive text so that we can issue an on-line\n> help, for example), but I expect that would happen much later than\n> we start generating these parts of the source code in two places\n> (the initializer for advice_setting[] and the advice_type enum) from\n> a single source by mechanical process.  And the auto-generation will\n> eliminate the burden of writing 1 manually.\n\nAgree.\n\n> Turning this into \"break;\" would make it similar to the loop before\n> this one, and will allow other people to add more code after this\n> loop later.\n\nThe loop with break is redundant and will be removed in the next\npatch, but the rest of the loops in this function end with return\nwhich I think makes more sense assuming any new code/loop that will\nneed to be added in the future is expected to handle a different\nconfiguration group.\n\n> > +     /*\n> > +       Any advice type can be used for testing, but NESTED_TAG was selected\n> > +       here and in t0018 where this command is being executed.\n> > +      */\n>\n> Style (will fix up locally).\n>\n> Thanks.  I think this is reasonable with or without the suggested\n> fixes.\n\nGreat, thanks.\n\nHeba\n"}]}