config: warn on core.commentString=auto

As support for this setting was deprecated in the last commit print a warning (or die when WITH_BREAKING_CHANGES is enabled) if it is set. Avoid bombarding the user with warnings by only printing it (a) when running commands that call "git commit" and (b) only once per command. Some scaffolding is added to repo_read_config() to allow it to detect deprecated config settings and warn about them. As both "core.commentChar" and "core.commentString" set the comment character we record which one of them is used and tailor the warning message appropriately. Note the odd combination of die_message() followed by die(NULL) is to allow the next commit to insert a call to advise() in the middle. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Phillip Wood committed Aug 26, 2025 at 14:35 UTC a0e6aaea7da5134bdc784c6d68d4cc2125865330
11 files changed +157 -4
builtin/commit.c
+3
@@ -1783,6 +1783,9 @@ int cmd_commit(int argc,
1783 show_usage_with_options_if_asked(argc, argv,
1784 builtin_commit_usage, builtin_commit_options);
1785
1786 +#ifndef WITH_BREAKING_CHANGES
1787 + warn_on_auto_comment_char = true;
1788 +#endif /* !WITH_BREAKING_CHANGES */
1789 prepare_repo_settings(the_repository);
1790 the_repository->settings.command_requires_full_index = 0;
1791
builtin/merge.c
+3
@@ -1378,6 +1378,9 @@ int cmd_merge(int argc,
1378 show_usage_with_options_if_asked(argc, argv,
1379 builtin_merge_usage, builtin_merge_options);
1380
1381 +#ifndef WITH_BREAKING_CHANGES
1382 + warn_on_auto_comment_char = true;
1383 +#endif /* !WITH_BREAKING_CHANGES */
1384 prepare_repo_settings(the_repository);
1385 the_repository->settings.command_requires_full_index = 0;
1386
builtin/rebase.c
+3
@@ -1242,6 +1242,9 @@ int cmd_rebase(int argc,
1242 builtin_rebase_usage,
1243 builtin_rebase_options);
1244
1245 +#ifndef WITH_BREAKING_CHANGES
1246 + warn_on_auto_comment_char = true;
1247 +#endif /* !WITH_BREAKING_CHANGES */
1248 prepare_repo_settings(the_repository);
1249 the_repository->settings.command_requires_full_index = 0;
1250
builtin/revert.c
+7
@@ -4,6 +4,7 @@
4 #include "builtin.h"
5 #include "parse-options.h"
6 #include "diff.h"
7 +#include "environment.h"
8 #include "gettext.h"
9 #include "revision.h"
10 #include "rerere.h"
@@ -285,6 +286,9 @@ int cmd_revert(int argc,
286 struct replay_opts opts = REPLAY_OPTS_INIT;
287 int res;
288
289 +#ifndef WITH_BREAKING_CHANGES
290 + warn_on_auto_comment_char = true;
291 +#endif /* !WITH_BREAKING_CHANGES */
292 opts.action = REPLAY_REVERT;
293 sequencer_init_config(&opts);
294 res = run_sequencer(argc, argv, prefix, &opts);
@@ -302,6 +306,9 @@ struct repository *repo UNUSED)
306 struct replay_opts opts = REPLAY_OPTS_INIT;
307 int res;
308
309 +#ifndef WITH_BREAKING_CHANGES
310 + warn_on_auto_comment_char = true;
311 +#endif /* !WITH_BREAKING_CHANGES */
312 opts.action = REPLAY_PICK;
313 sequencer_init_config(&opts);
314 res = run_sequencer(argc, argv, prefix, &opts);
config.c
+113 -2
@@ -11,6 +11,7 @@
11 #include "date.h"
12 #include "branch.h"
13 #include "config.h"
14 +#include "dir.h"
15 #include "parse.h"
16 #include "convert.h"
17 #include "environment.h"
@@ -1951,10 +1952,110 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d
1952 return 1;
1953 }
1954
1955 +struct comment_char_config {
1956 + unsigned last_key_id;
1957 + bool auto_set;
1958 +};
1959 +
1960 +#define COMMENT_CHAR_CFG_INIT { 0 }
1961 +
1962 +static const char *comment_key_name(unsigned id)
1963 +{
1964 + static const char *name[] = {
1965 + "core.commentChar",
1966 + "core.commentString",
1967 + };
1968 +
1969 + if (id >= ARRAY_SIZE(name))
1970 + BUG("invalid comment key id");
1971 +
1972 + return name[id];
1973 +}
1974 +
1975 +static void comment_char_callback(const char *key, const char *value,
1976 + const struct config_context *ctx UNUSED,
1977 + void *data)
1978 +{
1979 + struct comment_char_config *config = data;
1980 + unsigned key_id;
1981 +
1982 + if (!strcmp(key, "core.commentchar"))
1983 + key_id = 0;
1984 + else if (!strcmp(key, "core.commentstring"))
1985 + key_id = 1;
1986 + else
1987 + return;
1988 +
1989 + config->last_key_id = key_id;
1990 + config->auto_set = value && !strcmp(value, "auto");
1991 +}
1992 +
1993 +struct repo_config {
1994 + struct repository *repo;
1995 + struct comment_char_config comment_char_config;
1996 +};
1997 +
1998 +#define REPO_CONFIG_INIT(repo_) { \
1999 + .comment_char_config = COMMENT_CHAR_CFG_INIT, \
2000 + .repo = repo_, \
2001 + };
2002 +
2003 +#ifdef WITH_BREAKING_CHANGES
2004 +static void check_auto_comment_char_config(struct comment_char_config *config)
2005 +{
2006 + if (!config->auto_set)
2007 + return;
2008 +
2009 + die_message(_("Support for '%s=auto' has been removed in Git 3.0"),
2010 + comment_key_name(config->last_key_id));
2011 + die(NULL);
2012 +}
2013 +#else
2014 +static void check_auto_comment_char_config(struct comment_char_config *config)
2015 +{
2016 + extern bool warn_on_auto_comment_char;
2017 + const char *DEPRECATED_CONFIG_ENV =
2018 + "GIT_AUTO_COMMENT_CHAR_CONFIG_WARNING_GIVEN";
2019 +
2020 + if (!config->auto_set || !warn_on_auto_comment_char)
2021 + return;
2022 +
2023 + /*
2024 + * Use an environment variable to ensure that subprocesses do not repeat
2025 + * the warning.
2026 + */
2027 + if (git_env_bool(DEPRECATED_CONFIG_ENV, false))
2028 + return;
2029 +
2030 + setenv(DEPRECATED_CONFIG_ENV, "true", true);
2031 +
2032 + warning(_("Support for '%s=auto' is deprecated and will be removed in "
2033 + "Git 3.0"), comment_key_name(config->last_key_id));
2034 +}
2035 +#endif /* WITH_BREAKING_CHANGES */
2036 +
2037 +static void check_deprecated_config(struct repo_config *config)
2038 +{
2039 + if (!config->repo->check_deprecated_config)
2040 + return;
2041 +
2042 + check_auto_comment_char_config(&config->comment_char_config);
2043 +}
2044 +
2045 +static int repo_config_callback(const char *key, const char *value,
2046 + const struct config_context *ctx, void *data)
2047 +{
2048 + struct repo_config *config = data;
2049 +
2050 + comment_char_callback(key, value, ctx, &config->comment_char_config);
2051 + return config_set_callback(key, value, ctx, config->repo->config);
2052 +}
2053 +
2054 /* Functions use to read configuration from a repository */
2055 static void repo_read_config(struct repository *repo)
2056 {
2057 struct config_options opts = { 0 };
2058 + struct repo_config config = REPO_CONFIG_INIT(repo);
2059
2060 opts.respect_includes = 1;
2061 opts.commondir = repo->commondir;
@@ -1966,8 +2067,8 @@ static void repo_read_config(struct repository *repo)
2067 git_configset_clear(repo->config);
2068
2069 git_configset_init(repo->config);
1969 - if (config_with_options(config_set_callback, repo->config, NULL,
1970 - repo, &opts) < 0)
2070 + if (config_with_options(repo_config_callback, &config, NULL, repo,
2071 + &opts) < 0)
2072 /*
2073 * config_with_options() normally returns only
2074 * zero, as most errors are fatal, and
@@ -1980,6 +2081,7 @@ static void repo_read_config(struct repository *repo)
2081 * immediately.
2082 */
2083 die(_("unknown error occurred while reading the configuration files"));
2084 + check_deprecated_config(&config);
2085 }
2086
2087 static void git_config_check_init(struct repository *repo)
@@ -2667,6 +2769,14 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
2769 char *contents = NULL;
2770 size_t contents_sz;
2771 struct config_store_data store = CONFIG_STORE_INIT;
2772 + bool saved_check_deprecated_config = r->check_deprecated_config;
2773 +
2774 + /*
2775 + * Do not warn or die if there are deprecated config settings as
2776 + * we want the user to be able to change those settings by running
2777 + * "git config".
2778 + */
2779 + r->check_deprecated_config = false;
2780
2781 validate_comment_string(comment);
2782
@@ -2898,6 +3008,7 @@ out_free:
3008 if (in_fd >= 0)
3009 close(in_fd);
3010 config_store_data_clear(&store);
3011 + r->check_deprecated_config = saved_check_deprecated_config;
3012 return ret;
3013
3014 write_err_out:
environment.c
+1
@@ -124,6 +124,7 @@ const char *comment_line_str = "#";
124 char *comment_line_str_to_free;
125 #ifndef WITH_BREAKING_CHANGES
126 int auto_comment_line_char;
127 +bool warn_on_auto_comment_char;
128 #endif /* !WITH_BREAKING_CHANGES */
129
130 /* This is set by setup_git_directory_gently() and/or git_default_config() */
environment.h
+1
@@ -210,6 +210,7 @@ extern const char *comment_line_str;
210 extern char *comment_line_str_to_free;
211 #ifndef WITH_BREAKING_CHANGES
212 extern int auto_comment_line_char;
213 +extern bool warn_on_auto_comment_char;
214 #endif /* !WITH_BREAKING_CHANGES */
215
216 # endif /* USE_THE_REPOSITORY_VARIABLE */
repository.c
+1
@@ -57,6 +57,7 @@ void initialize_repository(struct repository *repo)
57 repo->parsed_objects = parsed_object_pool_new(repo);
58 ALLOC_ARRAY(repo->index, 1);
59 index_state_init(repo->index, repo);
60 + repo->check_deprecated_config = true;
61
62 /*
63 * When a command runs inside a repository, it learns what
repository.h
+3
@@ -161,6 +161,9 @@ struct repository {
161
162 /* Indicate if a repository has a different 'commondir' from 'gitdir' */
163 unsigned different_commondir:1;
164 +
165 + /* Should repo_config() check for deprecated settings */
166 + bool check_deprecated_config;
167 };
168
169 #ifdef USE_THE_REPOSITORY_VARIABLE
t/t3404-rebase-interactive.sh
+6 -1
@@ -1184,8 +1184,13 @@ test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=
1184 test_when_finished "git rebase --abort || :" &&
1185 (
1186 test_set_editor "$(pwd)/copy-edit-script.sh" &&
1187 - git rebase -i HEAD^
1187 + git rebase -i HEAD^ 2>err
1188 ) &&
1189 + sed -n "s/^warning: //p" err >actual &&
1190 + cat >expect <<-EOF &&
1191 + Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0
1192 + EOF
1193 + test_cmp expect actual &&
1194 test -z "$(grep -ve "^#" -e "^\$" -e "^pick" edit-script)"
1195 '
1196
t/t7502-commit-porcelain.sh
+16 -1
@@ -958,7 +958,12 @@ test_expect_success 'commit --status with custom comment character' '
958
959 test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '
960 test_commit "#foo" foo &&
961 - GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&
961 + GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend 2>err &&
962 + sed -n "s/^warning: //p" err >actual &&
963 + cat >expect <<-EOF &&
964 + Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0
965 + EOF
966 + test_cmp expect actual &&
967 test_grep "^; Changes to be committed:" .git/COMMIT_EDITMSG
968 '
969
@@ -982,4 +987,14 @@ EOF
987 )
988 '
989
990 +test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '
991 + test_config core.commentChar auto &&
992 + test_must_fail git rev-parse --git-dir 2>err &&
993 + sed -n "s/^fatal: //p" err >actual &&
994 + cat >expect <<-EOF &&
995 + Support for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0
996 + EOF
997 + test_cmp expect actual
998 +'
999 +
1000 test_done