Subject: [PATCH v4 3/3] advice: add --no-advice global option

From: James Liu <[email-masked]>
Date: Fri, 3 May 2024 17:17:06 +1000

Advice hints must be disabled individually by setting the relevant
advice.* variables to false in the Git configuration. For server-side
and scripted usages of Git where hints can be a hindrance, it can be
cumbersome to maintain configuration to ensure all advice hints are
disabled in perpetuity. This is a particular concern in tests, where
new or changed hints can result in failed assertions.

Add a --no-advice global option to disable all advice hints from being
displayed. This is independent of the toggles for individual advice
hints. Use an internal environment variable (GIT_ADVICE) to ensure this
configuration is propagated to the usage site, even if it executes in a
subprocess.

Signed-off-by: James Liu <[email-masked]>
---
 Documentation/git.txt |  8 +++--
 advice.c              |  7 +++++
 environment.h         |  7 +++++
 git.c                 |  9 ++++--
 t/t0018-advice.sh     | 68 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 95 insertions(+), 4 deletions(-)

diff --git a/Documentation/git.txt b/Documentation/git.txt
index d11d3d0c86..a0c07f1db8 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -12,8 +12,9 @@ SYNOPSIS
 'git' [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
     [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
     [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]
-    [--no-optional-locks] [--bare] [--git-dir=<path>] [--work-tree=<path>]
-    [--namespace=<name>] [--config-env=<name>=<envvar>] <command> [<args>]
+    [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]
+    [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]
+    <command> [<args>]
 
 DESCRIPTION
 -----------
@@ -190,6 +191,9 @@ If you just want to run git as if it was started in `<path>` then use
 	Do not perform optional operations that require locks. This is
 	equivalent to setting the `GIT_OPTIONAL_LOCKS` to `0`.
 
+--no-advice::
+	Disable all advice hints from being printed.
+
 --literal-pathspecs::
 	Treat pathspecs literally (i.e. no globbing, no pathspec magic).
 	This is equivalent to setting the `GIT_LITERAL_PATHSPECS` environment
diff --git a/advice.c b/advice.c
index 75111191ad..0a122c2020 100644
--- a/advice.c
+++ b/advice.c
@@ -2,6 +2,7 @@
 #include "advice.h"
 #include "config.h"
 #include "color.h"
+#include "environment.h"
 #include "gettext.h"
 #include "help.h"
 #include "string-list.h"
@@ -127,6 +128,12 @@ void advise(const char *advice, ...)
 int advice_enabled(enum advice_type type)
 {
 	int enabled = advice_setting[type].level != ADVICE_LEVEL_DISABLED;
+	static int globally_enabled = -1;
+
+	if (globally_enabled < 0)
+		globally_enabled = git_env_bool(GIT_ADVICE_ENVIRONMENT, 1);
+	if (!globally_enabled)
+		return 0;
 
 	if (type == ADVICE_PUSH_UPDATE_REJECTED)
 		return enabled &&
diff --git a/environment.h b/environment.h
index 05fd94d7be..0b2d457f07 100644
--- a/environment.h
+++ b/environment.h
@@ -57,6 +57,13 @@ const char *getenv_safe(struct strvec *argv, const char *name);
 #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR"
 #define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
 
+/*
+ * Environment variable used to propagate the --no-advice global option to the
+ * advice_enabled() helper, even when run in a subprocess.
+ * This is an internal variable that should not be set by the user.
+ */
+#define GIT_ADVICE_ENVIRONMENT "GIT_ADVICE"
+
 /*
  * Environment variable used in handshaking the wire protocol.
  * Contains a colon ':' separated list of keys with optional values
diff --git a/git.c b/git.c
index 7654571b75..637c61ca9c 100644
--- a/git.c
+++ b/git.c
@@ -37,8 +37,9 @@ const char git_usage_string[] =
 	N_("git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]\n"
 	   "           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
 	   "           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\n"
-	   "           [--no-optional-locks] [--bare] [--git-dir=<path>] [--work-tree=<path>]\n"
-	   "           [--namespace=<name>] [--config-env=<name>=<envvar>] <command> [<args>]");
+	   "           [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\n"
+	   "           [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\n"
+	   "           <command> [<args>]");
 
 const char git_more_info_string[] =
 	N_("'git help -a' and 'git help -g' list available subcommands and some\n"
@@ -337,6 +338,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 			setenv(GIT_ATTR_SOURCE_ENVIRONMENT, cmd, 1);
 			if (envchanged)
 				*envchanged = 1;
+		} else if (!strcmp(cmd, "--no-advice")) {
+			setenv(GIT_ADVICE_ENVIRONMENT, "0", 1);
+			if (envchanged)
+				*envchanged = 1;
 		} else {
 			fprintf(stderr, _("unknown option: %s\n"), cmd);
 			usage(git_usage_string);
diff --git a/t/t0018-advice.sh b/t/t0018-advice.sh
index 0dcfb760a2..b02448ea16 100755
--- a/t/t0018-advice.sh
+++ b/t/t0018-advice.sh
@@ -29,4 +29,72 @@ test_expect_success 'advice should not be printed when config variable is set to
 	test_must_be_empty actual
 '
 
+test_expect_success 'advice should not be printed when --no-advice is used' '
+	q_to_tab >expect <<-\EOF &&
+On branch master
+
+No commits yet
+
+Untracked files:
+QREADME
+
+nothing added to commit but untracked files present
+EOF
+
+	test_when_finished "rm -fr advice-test" &&
+	git init advice-test &&
+	(
+		cd advice-test &&
+		>README &&
+		git --no-advice status
+	) >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'advice should not be printed when GIT_ADVICE is set to false' '
+	q_to_tab >expect <<-\EOF &&
+On branch master
+
+No commits yet
+
+Untracked files:
+QREADME
+
+nothing added to commit but untracked files present
+EOF
+
+	test_when_finished "rm -fr advice-test" &&
+	git init advice-test &&
+	(
+		cd advice-test &&
+		>README &&
+		GIT_ADVICE=false git status
+	) >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'advice should be printed when GIT_ADVICE is set to true' '
+	q_to_tab >expect <<-\EOF &&
+On branch master
+
+No commits yet
+
+Untracked files:
+  (use "git add <file>..." to include in what will be committed)
+QREADME
+
+nothing added to commit but untracked files present (use "git add" to track)
+EOF
+
+	test_when_finished "rm -fr advice-test" &&
+	git init advice-test &&
+	(
+		cd advice-test &&
+		>README &&
+		GIT_ADVICE=true git status
+	) >actual &&
+	cat actual > /tmp/actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.44.0