{"thread":{"id":"63762","subject":"[PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","startedAt":"2025-07-08T13:57:09Z","lastAt":"2025-08-27T22:38:22Z","messageCount":45,"participants":["Phillip Wood","Ayush Chandekar","Junio C Hamano","Oswald Buddenhagen"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"521546","messageId":"cover.1751983009.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":null,"subject":"[PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-08T13:56:50Z","receivedAt":"2025-07-08T13:57:09Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis series implements the plan to deprecate and remove support for\ncore.commentChar=auto outlined in [1]. This feature has been the\nsource of a couple of bug reports recently [2,3] and as explained in\nthe first patch the design is tricky to fix. When git sees the\ndeprecated config setting it will print advice like the example below\nto help the user either remove the setting or set a custom comment\nstring.\n\nhint: Support for 'core.commentChar=auto' is deprecated and will be removed in git 3.0\nhint:\nhint: To use the default comment string (#) please run\nhint:\nhint:     git config unset --file ~/.config/git/config --all core.commentString\nhint:     git config unset --file ~/.config/git/config core.commentChar\nhint:     git config unset --global core.commentChar\nhint:\nhint: To set a custom comment string please run\nhint:\nhint:     git config set --global core.commentChar <comment string>\nhint:\nhint: where '<comment string>' is the string you wish to use.\n\n[1] https://lore.kernel.org/git/6a3154e0-e7bc-45ae-b554-67ccab18727a@gmail.com\n[2] https://lore.kernel.org/git/20250315140913.577404-1-oswald.buddenhagen@gmx.de\n[3] https://lore.kernel.org/git/20250626132233.414789-1-ayu.chandekar@gmail.com\n\nBase-Commit: f0135a9047ca37d4d117dcf21f7e3e89fad85d00\nPublished-As: https://github.com/phillipwood/git/releases/tag/pw%2Fremove-auto-comment-char%2Fv1\nView-Changes-At: https://github.com/phillipwood/git/compare/f0135a904...83d0d3ece\nFetch-It-Via: git fetch https://github.com/phillipwood/git pw/remove-auto-comment-char/v1\n\n\nPhillip Wood (2):\n  breaking-changes: deprecate support for core.commentString=auto\n  commit: print advice when core.commentString=auto\n\n Documentation/BreakingChanges.adoc |   4 +\n Documentation/config/core.adoc     |  20 ++-\n builtin/commit.c                   | 192 +++++++++++++++++++++++++++++\n config.c                           |   4 +\n environment.c                      |   2 +\n environment.h                      |   2 +\n t/t3404-rebase-interactive.sh      |   2 +-\n t/t7502-commit-porcelain.sh        |  32 ++++-\n 8 files changed, 252 insertions(+), 6 deletions(-)\n\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"521547","messageId":"3747a1f77f03bf2572e195673d021dcf827ae59b.1751983009.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1751983009.git.phillip.wood@dunelm.org.uk","subject":"[PATCH 1/2] breaking-changes: deprecate support for core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-08T13:56:51Z","receivedAt":"2025-07-08T13:57:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen \"core.commentString\" is set to \"auto\" then \"git commit\"\nwill automatically select the comment character ensuring that it\ndoes not the first character on any of the lines in the commit\nmessage. This was introduced by commit 84c9dc2c5a2 (commit: allow\ncore.commentChar=auto for character auto selection, 2014-05-17) The\nmotivation seems to be to avoid commenting out lines from the existing\nmessage when amending a commit that was created with a message from\na file.\n\nUnfortunately this feature does not work with:\n\n * commit message templates that contain comments.\n\n * prepare-commit-msg hooks that introduce comments.\n\n * \"git commit --cleanup=strip --edit -F <file>\" which means that it\n   is incompatible with\n\n   - the \"fixup\" and \"squash\" commands of \"git rebase -i\" as the\n     comments added by those commands are then treated as part of the\n     commit message.\n\n   - the conflict comments added to the commit message by \"git\n     cherry-pick\", \"git rebase\" etc. as these comments are then treated\n     as part of the commit message.\n\nIt is also ignored by \"git notes\" when amending a note.\n\nThe issues with comments coming from a template, hook or file are a\nconsequence of the design of this feature and are therefore hard to\nfix.\n\nAs the costs of this feature outweigh the benefits deprecate it and\nremove it in Git 3.0. If someone comes up with some patches that fix all\nthe issues in a maintainable way then I'd be happy to see this change\nreverted.\n\nThe next commit will add some advice for users on how they can update\ntheir config settings.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n Documentation/BreakingChanges.adoc |  4 ++++\n Documentation/config/core.adoc     | 20 ++++++++++++++++++--\n builtin/commit.c                   |  4 ++++\n config.c                           |  4 ++++\n environment.c                      |  2 ++\n environment.h                      |  2 ++\n t/t3404-rebase-interactive.sh      |  2 +-\n t/t7502-commit-porcelain.sh        |  4 ++--\n 8 files changed, 37 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc\nindex 61bdd586b9e..f38ba1de6e4 100644\n--- a/Documentation/BreakingChanges.adoc\n+++ b/Documentation/BreakingChanges.adoc\n@@ -183,6 +183,10 @@ These features will be removed.\n   timeframe, in preference to its synonym \"--annotate-stdin\".  Git 3.0\n   removes the support for \"--stdin\" altogether.\n \n+* Support for `core.commentString=auto` has been deprecated and will\n+  be removed in Git 3.0.\n++\n+cf. <xmqqa59i45wc.fsf@gitster.g>\n \n == Superseded features that will not be deprecated\n \ndiff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc\nindex 9fde1ab63a7..7133f00c38b 100644\n--- a/Documentation/config/core.adoc\n+++ b/Documentation/config/core.adoc\n@@ -531,9 +531,25 @@ core.commentString::\n \tcommented, and removes them after the editor returns\n \t(default '#').\n +\n-If set to \"auto\", `git-commit` would select a character that is not\n+ifndef::with-breaking-changes[]\n+If set to \"auto\", `git-commit` will select a character that is not\n the beginning character of any line in existing commit messages.\n-+\n+Support for this value is deprecated and will be removed in Git 3.0\n+due to the following limitations:\n++\n+--\n+* It is incompatible with adding comments in a commit message\n+  template. This includes the conflicts comments added to\n+  the commit message by `cherry-pick`, `merge`, `rebase` and\n+  `revert`.\n+* It is incompatible with adding comments to the commit message\n+  in the `prepare-commit-msg` hook.\n+* It is incompatible with the `fixup` and `squash` commands when\n+  rebasing,\n+* It is not respected by `git notes`\n+--\n++\n+endif::with-breaking-changes[]\n Note that these two variables are aliases of each other, and in modern\n versions of Git you are free to use a string (e.g., `//` or `⁑⁕⁑`) with\n `commentChar`. Versions of Git prior to v2.45.0 will ignore\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex fba0dded64a..8794b24572b 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -683,6 +683,7 @@ static int author_date_is_interesting(void)\n \treturn author_message || force_date;\n }\n \n+#ifndef WITH_BREAKING_CHANGES\n static void adjust_comment_line_char(const struct strbuf *sb)\n {\n \tchar candidates[] = \"#;@!$%^&|:\";\n@@ -716,6 +717,7 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tfree(comment_line_str_to_free);\n \tcomment_line_str = comment_line_str_to_free = xstrfmt(\"%c\", *p);\n }\n+#endif /* WITH_BREAKING_CHANGES */\n \n static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\n \t\t\t\tstruct pretty_print_context *ctx)\n@@ -912,8 +914,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tif (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)\n \t\tdie_errno(_(\"could not write commit template\"));\n \n+#ifndef WITH_BREAKING_CHANGES\n \tif (auto_comment_line_char)\n \t\tadjust_comment_line_char(&sb);\n+#endif /* WITH_BREAKING_CHANGES */\n \tstrbuf_release(&sb);\n \n \t/* This checks if committer ident is explicitly given */\ndiff --git a/config.c b/config.c\nindex eb60c293ab3..f99496b16c0 100644\n--- a/config.c\n+++ b/config.c\n@@ -1537,14 +1537,18 @@ static int git_default_core_config(const char *var, const char *value,\n \t    !strcmp(var, \"core.commentstring\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n+#ifndef WITH_BREAKING_CHANGES\n \t\telse if (!strcasecmp(value, \"auto\"))\n \t\t\tauto_comment_line_char = 1;\n+#endif /* WITH_BREAKING_CHANGES */\n \t\telse if (value[0]) {\n \t\t\tif (strchr(value, '\\n'))\n \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n \t\t\tcomment_line_str = value;\n \t\t\tFREE_AND_NULL(comment_line_str_to_free);\n+#ifndef WITH_BREAKING_CHANGES\n \t\t\tauto_comment_line_char = 0;\n+#endif /* WITH_BREAKING_CHANGES */\n \t\t} else\n \t\t\treturn error(_(\"%s must have at least one character\"), var);\n \t\treturn 0;\ndiff --git a/environment.c b/environment.c\nindex 7bf0390a335..6804380889f 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -111,7 +111,9 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n  */\n const char *comment_line_str = \"#\";\n char *comment_line_str_to_free;\n+#ifndef WITH_BREAKING_CHANGES\n int auto_comment_line_char;\n+#endif /* WITH_BREAKING_CHANGES */\n \n /* This is set by setup_git_directory_gently() and/or git_default_config() */\n char *git_work_tree_cfg;\ndiff --git a/environment.h b/environment.h\nindex 9a3d05d414a..871596afcef 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -207,7 +207,9 @@ extern char *excludes_file;\n  */\n extern const char *comment_line_str;\n extern char *comment_line_str_to_free;\n+#ifndef WITH_BREAKING_CHANGES\n extern int auto_comment_line_char;\n+#endif /* WITH_BREAKING_CHANGES */\n \n # endif /* USE_THE_REPOSITORY_VARIABLE */\n #endif /* ENVIRONMENT_H */\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 6bac217ed35..ce0aebb9a7e 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1176,7 +1176,7 @@ test_expect_success 'rebase -i respects core.commentchar' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n-test_expect_success 'rebase -i respects core.commentchar=auto' '\n+test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=auto' '\n \ttest_config core.commentchar auto &&\n \twrite_script copy-edit-script.sh <<-\\EOF &&\n \tcp \"$1\" edit-script\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex b37e2018a74..65b4519a715 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -956,13 +956,13 @@ test_expect_success 'commit --status with custom comment character' '\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n-test_expect_success 'switch core.commentchar' '\n+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n \tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n-test_expect_success 'switch core.commentchar but out of options' '\n+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar but out of options' '\n \tcat >text <<\\EOF &&\n # 1\n ; 2\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"521548","messageId":"83d0d3ece86811452614ee5e567481588b135632.1751983009.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1751983009.git.phillip.wood@dunelm.org.uk","subject":"[PATCH 2/2] commit: print advice when core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-08T13:56:52Z","receivedAt":"2025-07-08T13:57:11Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nSupport for this config setting was deprecated in the last commit\nso print some advice to help the user update their config settings\nwhen they are using this setting. The advice message explains that the\nsetting is deprecated and will be removed in future. It also shows the\ncommands that the user needs to run to either unset core.commentChar\nand core.commentString completely or to change the current setting\nto a fixed comment string.\n\nIn order to generate this advice we need to parse the config with a\ncallback that records each file where either of the keys is set and\nwhether a key occurs more that once in a given file. This lets us\ngenerate the list of commands to remove all the keys and also tells us\nwhich key the \"auto\" setting comes from. The hard coding of some\nfilenames in add_config_scope_arg() is unfortunate but as this temporary\ncode that will be removed when Git 3.0 is released I decided it wasn't\nworth adding functions to get the name of the local and worktree config\nfiles.\n\nAs we want the user to update their config we do not provide a way for\nthis advice to be disabled other than changing the value of\ncore.commentChar or core.commentString.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/commit.c            | 188 ++++++++++++++++++++++++++++++++++++\n t/t7502-commit-porcelain.sh |  28 +++++-\n 2 files changed, 215 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 8794b24572b..21839db7fce 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -41,6 +41,8 @@\n #include \"commit-reach.h\"\n #include \"commit-graph.h\"\n #include \"pretty.h\"\n+#include \"quote.h\"\n+#include \"strmap.h\"\n #include \"trailer.h\"\n \n static const char * const builtin_commit_usage[] = {\n@@ -684,12 +686,198 @@ static int author_date_is_interesting(void)\n }\n \n #ifndef WITH_BREAKING_CHANGES\n+struct comment_char_cfg {\n+\tunsigned last_key_id;\n+\tint auto_set_in_file;\n+\tstruct strintmap key_flags;\n+\tsize_t alloc, nr;\n+\tstruct comment_char_cfg_item {\n+\t\tunsigned key_id;\n+\t\tchar *path;\n+\t\tenum config_scope scope;\n+\t} *item;\n+};\n+\n+#define COMMENT_CHAR_CFG_INIT { .key_flags = STRINTMAP_INIT }\n+\n+static void comment_char_cfg_release(struct comment_char_cfg *cfg)\n+{\n+\tstrintmap_clear(&cfg->key_flags);\n+\tfor (size_t i = 0; i < cfg->nr; i++)\n+\t\tfree(cfg->item[i].path);\n+\tfree(cfg->item);\n+}\n+\n+/* Used to track whether the key occurs more than once in a given file */\n+#define KEY_SEEN_ONCE 1u\n+#define KEY_SEEN_TWICE 2u\n+#define COMMENT_KEY_SHIFT(id) (2 * (id))\n+#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id))\n+\n+static void set_comment_key_flags(struct comment_char_cfg *cfg,\n+\t\t\t\t  const char *path, unsigned id, unsigned value)\n+{\n+\tunsigned old = strintmap_get(&cfg->key_flags, path);\n+\tunsigned new = (old & ~COMMENT_KEY_MASK(id)) |\n+\t\t\t\tvalue << COMMENT_KEY_SHIFT(id);\n+\n+\tstrintmap_set(&cfg->key_flags, path, new);\n+}\n+\n+static unsigned get_comment_key_flags(struct comment_char_cfg *cfg,\n+\t\t\t\t      const char *path, unsigned id)\n+{\n+\tunsigned value = strintmap_get(&cfg->key_flags, path);\n+\n+\treturn (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id);\n+}\n+\n+static const char* comment_key_name(unsigned id)\n+{\n+\tstatic const char *name[] = {\n+\t\t\"core.commentChar\", \"core.commentString\",\n+\t};\n+\n+\tif (id >= ARRAY_SIZE(name))\n+\t\tBUG(\"invalid comment key id\");\n+\n+\treturn name[id];\n+}\n+\n+static int comment_char_config_cb(const char *key, const char *value,\n+\t\t\t\t  const struct config_context *ctx, void *data)\n+{\n+\tstruct comment_char_cfg *cfg = data;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\tunsigned key_id;\n+\n+\tif (!strcmp(key, \"core.commentchar\"))\n+\t\tkey_id = 0;\n+\telse if (!strcmp(key, \"core.commentstring\"))\n+\t\tkey_id = 1;\n+\telse\n+\t\treturn 0;\n+\n+\tcfg->last_key_id = key_id;\n+\tif (!kvi->path) {\n+\t\treturn 0;\n+\t} else if (get_comment_key_flags(cfg, kvi->path, key_id)) {\n+\t\tset_comment_key_flags(cfg, kvi->path, key_id, KEY_SEEN_TWICE);\n+\t} else {\n+\t\tstruct comment_char_cfg_item *item;\n+\n+\t\tALLOC_GROW_BY(cfg->item, cfg->nr, 1, cfg->alloc);\n+\t\titem = &cfg->item[cfg->nr - 1];\n+\t\titem->key_id = key_id;\n+\t\titem->scope = kvi->scope;\n+\t\titem->path = xstrdup(kvi->path);\n+\t\tset_comment_key_flags(cfg, kvi->path, key_id, KEY_SEEN_ONCE);\n+\t}\n+\tcfg->auto_set_in_file =\tvalue && !strcmp(value, \"auto\");\n+\n+\treturn 0;\n+}\n+\n+static void add_config_scope_arg(struct strbuf *buf,\n+\t\t\t\t struct comment_char_cfg_item *item)\n+{\n+\tchar *global_config = git_global_config();\n+\tchar *system_config = git_system_config();\n+\n+\tif (fspatheq(item->path, system_config)) {\n+\t\tstrbuf_addstr(buf, \"--system \");\n+\t} else if (fspatheq(item->path, global_config)) {\n+\t\tstrbuf_addstr(buf, \"--global \");\n+\t} else if (fspatheq(item->path,\n+\t\t\t    mkpath(\"%s/config\",\n+\t\t\t\t   repo_get_git_dir(the_repository)))) {\n+\t\t; /* --local is the default */\n+\t} else if (fspatheq(item->path,\n+\t\t\t    mkpath(\"%s/config.worktree\",\n+\t\t\t\t   repo_get_common_dir(the_repository)))) {\n+\t\tstrbuf_addstr(buf, \"--worktree \");\n+\t} else {\n+\t\tconst char *path = item->path;\n+\t\tconst char *home = getenv(\"HOME\");\n+\n+\t\tstrbuf_addstr(buf, \"--file \");\n+\t\tif (home && !fspathncmp(path, home, strlen(home))) {\n+\t\t\tpath += strlen(home);\n+\t\t\tif (!fspathncmp(path, \"/\", 1))\n+\t\t\t\tpath++;\n+\t\t\tstrbuf_addstr(buf, \"~/\");\n+\t\t}\n+\t\tsq_quote_buf_pretty(buf, path);\n+\t\tstrbuf_addch(buf, ' ');\n+\t}\n+\n+\tfree(global_config);\n+\tfree(system_config);\n+}\n+\n+static void add_optional_comment_char_advice(struct comment_char_cfg *cfg)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct comment_char_cfg_item *item;\n+\t/* TRANSLATORS this is a place holder for the value of core.commentString */\n+\tconst char *placeholder = _(\"<comment string>\");\n+\n+\t/*\n+\t * If auto is set in the last file that we saw advise the user how to\n+\t * update their config.\n+\t */\n+\tif (!cfg->auto_set_in_file)\n+\t\treturn;\n+\n+\tfor (size_t i = 0; i < cfg->nr; i++) {\n+\t\titem = &cfg->item[i];\n+\n+\t\tstrbuf_addstr(&buf, \"    git config unset \");\n+\t\tadd_config_scope_arg(&buf, item);\n+\t\tif (get_comment_key_flags(cfg, item->path, item->key_id) == KEY_SEEN_TWICE)\n+\t\t\tstrbuf_addstr(&buf, \"--all \");\n+\t\tstrbuf_addf(&buf, \"%s\\n\", comment_key_name(item->key_id));\n+\t}\n+\tadvise(_(\"\\nTo use the default comment string (#) please run\\n\\n%s\"),\n+\t       buf.buf);\n+\n+\titem = &cfg->item[cfg->nr - 1];\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, \"    git config set \");\n+\tadd_config_scope_arg(&buf, item);\n+\tstrbuf_addf(&buf, \"%s %s\\n\", comment_key_name(item->key_id),\n+\t\t    placeholder);\n+\tadvise(_(\"\\nTo set a custom comment string please run\\n\\n\"\n+\t\t \"%s\\nwhere '%s' is the string you wish to use.\\n\"),\n+\t       buf.buf, placeholder);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void advise_auto_comment_char(void)\n+{\n+\tstruct comment_char_cfg cfg = COMMENT_CHAR_CFG_INIT;\n+\tstruct config_options opts = {\n+\t\t.commondir = repo_get_common_dir(the_repository),\n+\t\t.git_dir = repo_get_git_dir(the_repository),\n+\t\t.respect_includes = 1,\n+\t};\n+\n+\tconfig_with_options(comment_char_config_cb, &cfg, NULL, the_repository,\n+\t\t\t    &opts);\n+\tadvise(_(\"Support for '%s=auto' is deprecated and will be removed in \"\n+\t\t \"git 3.0\\n\"), comment_key_name(cfg.last_key_id));\n+\tadd_optional_comment_char_advice(&cfg);\n+\tcomment_char_cfg_release(&cfg);\n+}\n+\n static void adjust_comment_line_char(const struct strbuf *sb)\n {\n \tchar candidates[] = \"#;@!$%^&|:\";\n \tchar *candidate;\n \tconst char *p;\n \n+\tadvise_auto_comment_char();\n+\n \tif (!memchr(sb->buf, candidates[0], sb->len)) {\n \t\tfree(comment_line_str_to_free);\n \t\tcomment_line_str = comment_line_str_to_free =\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 65b4519a715..c8c00d316be 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -958,7 +958,33 @@ test_expect_success 'commit --status with custom comment character' '\n \n test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n-\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&\n+\tcat >config-include <<-\\EOF &&\n+\t[core]\n+\t    commentString=:\n+\t    commentString=%\n+\t    commentChar=auto\n+\tEOF\n+\ttest_when_finished \"rm config-include\" &&\n+\ttest_config include.path \"$(pwd)/config-include\" &&\n+\ttest_config core.commentChar ! &&\n+\tGIT_EDITOR=.git/FAKE_EDITOR git commit --amend 2>err &&\n+\tsed -n \"/^hint: *\\$/s///p; /^hint: /s///p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\t    git config unset --file ~/config-include --all core.commentString\n+\t    git config unset --file ~/config-include core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set --file ~/config-include core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n+\tEOF\n+\ttest_cmp expect actual &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"521551","messageId":"CAE7as+YyKHzHjpCn2fFym0e5JquhroeDPHo_FHtdNCCb1HVSgw@mail.gmail.com","threadId":"63762","inReplyTo":"3747a1f77f03bf2572e195673d021dcf827ae59b.1751983009.git.phillip.wood@dunelm.org.uk","subject":"Re: [PATCH 1/2] breaking-changes: deprecate support for core.commentString=auto","fromName":"Ayush Chandekar","fromEmail":"ayu.chandekar@gmail.com","sentAt":"2025-07-08T15:28:17Z","receivedAt":"2025-07-08T15:28:29Z","isPatch":true,"sender":{"key":"ayu.chandekar@gmail.com","avatar":"https://avatars.githubusercontent.com/u/137001939?v=4"},"body":"Hi Phillip,\n\nOn Tue, Jul 8, 2025 at 7:27 PM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> When \"core.commentString\" is set to \"auto\" then \"git commit\"\n> will automatically select the comment character ensuring that it\n> does not the first character on any of the lines in the commit\n> message. This was introduced by commit 84c9dc2c5a2 (commit: allow\n> core.commentChar=auto for character auto selection, 2014-05-17) The\n> motivation seems to be to avoid commenting out lines from the existing\n> message when amending a commit that was created with a message from\n> a file.\n>\n\ns/that it does not the first character/that it does not appear on the\nfirst character?\n\n> Unfortunately this feature does not work with:\n>\n>  * commit message templates that contain comments.\n>\n>  * prepare-commit-msg hooks that introduce comments.\n>\n>  * \"git commit --cleanup=strip --edit -F <file>\" which means that it\n>    is incompatible with\n>\n>    - the \"fixup\" and \"squash\" commands of \"git rebase -i\" as the\n>      comments added by those commands are then treated as part of the\n>      commit message.\n>\n>    - the conflict comments added to the commit message by \"git\n>      cherry-pick\", \"git rebase\" etc. as these comments are then treated\n>      as part of the commit message.\n>\n> It is also ignored by \"git notes\" when amending a note.\n>\n> The issues with comments coming from a template, hook or file are a\n> consequence of the design of this feature and are therefore hard to\n> fix.\n>\n> As the costs of this feature outweigh the benefits deprecate it and\n> remove it in Git 3.0. If someone comes up with some patches that fix all\n> the issues in a maintainable way then I'd be happy to see this change\n> reverted.\n>\nNit: s/benefits deprecate/benefits, deprecate.\n\n> The next commit will add some advice for users on how they can update\n> their config settings.\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  Documentation/BreakingChanges.adoc |  4 ++++\n>  Documentation/config/core.adoc     | 20 ++++++++++++++++++--\n>  builtin/commit.c                   |  4 ++++\n>  config.c                           |  4 ++++\n>  environment.c                      |  2 ++\n>  environment.h                      |  2 ++\n>  t/t3404-rebase-interactive.sh      |  2 +-\n>  t/t7502-commit-porcelain.sh        |  4 ++--\n>  8 files changed, 37 insertions(+), 5 deletions(-)\n>\n> diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc\n> index 61bdd586b9e..f38ba1de6e4 100644\n> --- a/Documentation/BreakingChanges.adoc\n> +++ b/Documentation/BreakingChanges.adoc\n> @@ -183,6 +183,10 @@ These features will be removed.\n>    timeframe, in preference to its synonym \"--annotate-stdin\".  Git 3.0\n>    removes the support for \"--stdin\" altogether.\n>\n> +* Support for `core.commentString=auto` has been deprecated and will\n> +  be removed in Git 3.0.\n> ++\n> +cf. <xmqqa59i45wc.fsf@gitster.g>\n>\n>  == Superseded features that will not be deprecated\n>\n> diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc\n> index 9fde1ab63a7..7133f00c38b 100644\n> --- a/Documentation/config/core.adoc\n> +++ b/Documentation/config/core.adoc\n> @@ -531,9 +531,25 @@ core.commentString::\n>         commented, and removes them after the editor returns\n>         (default '#').\n>  +\n> -If set to \"auto\", `git-commit` would select a character that is not\n> +ifndef::with-breaking-changes[]\n> +If set to \"auto\", `git-commit` will select a character that is not\n>  the beginning character of any line in existing commit messages.\n> -+\n> +Support for this value is deprecated and will be removed in Git 3.0\n> +due to the following limitations:\n> ++\n> +--\n> +* It is incompatible with adding comments in a commit message\n> +  template. This includes the conflicts comments added to\n> +  the commit message by `cherry-pick`, `merge`, `rebase` and\n> +  `revert`.\n> +* It is incompatible with adding comments to the commit message\n> +  in the `prepare-commit-msg` hook.\n> +* It is incompatible with the `fixup` and `squash` commands when\n> +  rebasing,\n> +* It is not respected by `git notes`\n> +--\n> ++\n> +endif::with-breaking-changes[]\n>  Note that these two variables are aliases of each other, and in modern\n>  versions of Git you are free to use a string (e.g., `//` or `⁑⁕⁑`) with\n>  `commentChar`. Versions of Git prior to v2.45.0 will ignore\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index fba0dded64a..8794b24572b 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -683,6 +683,7 @@ static int author_date_is_interesting(void)\n>         return author_message || force_date;\n>  }\n>\n> +#ifndef WITH_BREAKING_CHANGES\n>  static void adjust_comment_line_char(const struct strbuf *sb)\n>  {\n>         char candidates[] = \"#;@!$%^&|:\";\n> @@ -716,6 +717,7 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n>         free(comment_line_str_to_free);\n>         comment_line_str = comment_line_str_to_free = xstrfmt(\"%c\", *p);\n>  }\n> +#endif /* WITH_BREAKING_CHANGES */\n>\n>  static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\n>                                 struct pretty_print_context *ctx)\n> @@ -912,8 +914,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>         if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)\n>                 die_errno(_(\"could not write commit template\"));\n>\n> +#ifndef WITH_BREAKING_CHANGES\n>         if (auto_comment_line_char)\n>                 adjust_comment_line_char(&sb);\n> +#endif /* WITH_BREAKING_CHANGES */\n>         strbuf_release(&sb);\n>\n>         /* This checks if committer ident is explicitly given */\n> diff --git a/config.c b/config.c\n> index eb60c293ab3..f99496b16c0 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -1537,14 +1537,18 @@ static int git_default_core_config(const char *var, const char *value,\n>             !strcmp(var, \"core.commentstring\")) {\n>                 if (!value)\n>                         return config_error_nonbool(var);\n> +#ifndef WITH_BREAKING_CHANGES\n>                 else if (!strcasecmp(value, \"auto\"))\n>                         auto_comment_line_char = 1;\n> +#endif /* WITH_BREAKING_CHANGES */\n>                 else if (value[0]) {\n>                         if (strchr(value, '\\n'))\n>                                 return error(_(\"%s cannot contain newline\"), var);\n>                         comment_line_str = value;\n>                         FREE_AND_NULL(comment_line_str_to_free);\n> +#ifndef WITH_BREAKING_CHANGES\n>                         auto_comment_line_char = 0;\n> +#endif /* WITH_BREAKING_CHANGES */\n>                 } else\n>                         return error(_(\"%s must have at least one character\"), var);\n>                 return 0;\n> diff --git a/environment.c b/environment.c\n> index 7bf0390a335..6804380889f 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -111,7 +111,9 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n>   */\n>  const char *comment_line_str = \"#\";\n>  char *comment_line_str_to_free;\n> +#ifndef WITH_BREAKING_CHANGES\n>  int auto_comment_line_char;\n> +#endif /* WITH_BREAKING_CHANGES */\n>\n>  /* This is set by setup_git_directory_gently() and/or git_default_config() */\n>  char *git_work_tree_cfg;\n> diff --git a/environment.h b/environment.h\n> index 9a3d05d414a..871596afcef 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -207,7 +207,9 @@ extern char *excludes_file;\n>   */\n>  extern const char *comment_line_str;\n>  extern char *comment_line_str_to_free;\n> +#ifndef WITH_BREAKING_CHANGES\n>  extern int auto_comment_line_char;\n> +#endif /* WITH_BREAKING_CHANGES */\n>\n>  # endif /* USE_THE_REPOSITORY_VARIABLE */\n>  #endif /* ENVIRONMENT_H */\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 6bac217ed35..ce0aebb9a7e 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1176,7 +1176,7 @@ test_expect_success 'rebase -i respects core.commentchar' '\n>         test B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n>  '\n>\n> -test_expect_success 'rebase -i respects core.commentchar=auto' '\n> +test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=auto' '\n>         test_config core.commentchar auto &&\n>         write_script copy-edit-script.sh <<-\\EOF &&\n>         cp \"$1\" edit-script\n> diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\n> index b37e2018a74..65b4519a715 100755\n> --- a/t/t7502-commit-porcelain.sh\n> +++ b/t/t7502-commit-porcelain.sh\n> @@ -956,13 +956,13 @@ test_expect_success 'commit --status with custom comment character' '\n>         test_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n>  '\n>\n> -test_expect_success 'switch core.commentchar' '\n> +test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n>         test_commit \"#foo\" foo &&\n>         GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&\n>         test_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n>  '\n>\n> -test_expect_success 'switch core.commentchar but out of options' '\n> +test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar but out of options' '\n>         cat >text <<\\EOF &&\n>  # 1\n>  ; 2\n> --\n> 2.49.0.897.gfad3eb7d210\n>\n\nThanks for initiating this topic.\n\nThese changes look good to me.\n\nAyush\n"},{"id":"521560","messageId":"xmqqjz4iv7mt.fsf@gitster.g","threadId":"63762","inReplyTo":"cover.1751983009.git.phillip.wood@dunelm.org.uk","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-08T18:51:06Z","receivedAt":"2025-07-08T18:51:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> This series implements the plan to deprecate and remove support for\n> core.commentChar=auto outlined in [1]. This feature has been the\n> source of a couple of bug reports recently [2,3] and as explained in\n> the first patch the design is tricky to fix. When git sees the\n> deprecated config setting it will print advice like the example below\n> to help the user either remove the setting or set a custom comment\n> string.\n>\n> hint: Support for 'core.commentChar=auto' is deprecated and will be removed in git 3.0\n> hint:\n> hint: To use the default comment string (#) please run\n> hint:\n> hint:     git config unset --file ~/.config/git/config --all core.commentString\n> hint:     git config unset --file ~/.config/git/config core.commentChar\n> hint:     git config unset --global core.commentChar\n\nWe'd need to clear both variants from all scopes, wouldn't we?\n\n\tfor scope in \"\" --local --global --worktree\n\tdo\n\t\tfor variant in commentString commentChar\n\t\tdo\n\t\t\tgit config unset $scope --all core.$variant\n\t\tdone\n\tdone\n\n> hint:\n> hint: To set a custom comment string please run\n> hint:\n> hint:     git config set --global core.commentChar <comment string>\n> hint:\n> hint: where '<comment string>' is the string you wish to use.\n\nI do not particulary find it sensible to nudge users to use the same\ncommentChar across all projects with possibly different project\nconventions by suggesting use of the --global option here.\n\nIt would be necessary to special case \"auto\" after 3.0 boundary\nanyway, whether we (1) die when we notice the value is set to\n\"auto\", and refuse to work until the user chooses a comment char, or\n(2) use \"#\" or something hardcoded.  Either would be better than\nusing literal string \"auto\" as comment char.\n\nSo, a simpler approach might be to treat literal string \"auto\" as if\n\"#\" was specified under WITH_BREAKING_CHANGES so that the end-user\ndoes not have to do anything when they want to \"revert\" to the\ndefault comment string.  Then we do not have to give any large text\nlike the above.  We can instead say something like\n\n\tThe 'auto' setting of core.commentChar (or core.commentString) \n\twill change its meaning in Git 3.0 and later and will always\n\tuse the default '#'.\n\nHmm?\n\n> [1] https://lore.kernel.org/git/6a3154e0-e7bc-45ae-b554-67ccab18727a@gmail.com\n> [2] https://lore.kernel.org/git/20250315140913.577404-1-oswald.buddenhagen@gmx.de\n> [3] https://lore.kernel.org/git/20250626132233.414789-1-ayu.chandekar@gmail.com\n>\n> Base-Commit: f0135a9047ca37d4d117dcf21f7e3e89fad85d00\n> Published-As: https://github.com/phillipwood/git/releases/tag/pw%2Fremove-auto-comment-char%2Fv1\n> View-Changes-At: https://github.com/phillipwood/git/compare/f0135a904...83d0d3ece\n> Fetch-It-Via: git fetch https://github.com/phillipwood/git pw/remove-auto-comment-char/v1\n>\n>\n> Phillip Wood (2):\n>   breaking-changes: deprecate support for core.commentString=auto\n>   commit: print advice when core.commentString=auto\n>\n>  Documentation/BreakingChanges.adoc |   4 +\n>  Documentation/config/core.adoc     |  20 ++-\n>  builtin/commit.c                   | 192 +++++++++++++++++++++++++++++\n>  config.c                           |   4 +\n>  environment.c                      |   2 +\n>  environment.h                      |   2 +\n>  t/t3404-rebase-interactive.sh      |   2 +-\n>  t/t7502-commit-porcelain.sh        |  32 ++++-\n>  8 files changed, 252 insertions(+), 6 deletions(-)\n"},{"id":"521602","messageId":"xmqqy0syp30y.fsf@gitster.g","threadId":"63762","inReplyTo":"cover.1751983009.git.phillip.wood@dunelm.org.uk","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-09T01:27:09Z","receivedAt":"2025-07-09T01:27:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> This series implements the plan to deprecate and remove support for\n> core.commentChar=auto outlined in [1]. This feature has been the\n> source of a couple of bug reports recently [2,3] and as explained in\n> the first patch the design is tricky to fix.\n\nFWIW, this fails some tests that expect \"# commented lines\" by\ntreating \"auto\" too literally.\n\nhttps://github.com/git/git/actions/runs/16157263228/job/45602188411#step:10:2970\n\nI wonder if our braincycles are better spent to actually perform the\n\"tricky\"[*] fix than deprecating the feature and then perfecting the\ndeprecation process (which does not seem to be without cost either).\n\n - We can and should keep the \"auto\" magic and use '#' when it gets\n   specified, if we really wanted to do this deprecation.  I am not\n   a huge fan of it, though.\n\n - Or leave it as a known-broken feature in certain corner cases,\n   which may motivate some future developers to tackle these\n   \"tricky\" code paths.  If we were to go this route, the first step\n   would be to document what works and what does not as \"known\n   limitations\".  I am slightly more in favor of this than \"we punt,\n   because we cannot fix it\", but not by a large margin.\n\nSo, I dunno.\n\nThanks.\n\n[Footnote]\n\n * Essentially we would need to collect all information (like hook\n   output and template files) before we produce our own message to\n   be commented out because we need to know what symbol is\n   available.  Such a change may mean a major reshuffling of some\n   code paths (or worse, some code paths may have to be made to fail\n   and retry).  As long as the damage is limited to the case where\n   \"auto\" setting is used, such a \"solution\" is acceptable.\n"},{"id":"521604","messageId":"CAE7as+bN53MrA5_kF7pXD2V7dT8vNgJK0dn9r1Hq5vus7JaFtQ@mail.gmail.com","threadId":"63762","inReplyTo":"xmqqy0syp30y.fsf@gitster.g","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Ayush Chandekar","fromEmail":"ayu.chandekar@gmail.com","sentAt":"2025-07-09T01:52:35Z","receivedAt":"2025-07-09T01:52:47Z","isPatch":true,"sender":{"key":"ayu.chandekar@gmail.com","avatar":"https://avatars.githubusercontent.com/u/137001939?v=4"},"body":"On Wed, Jul 9, 2025 at 6:57 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>\n> > This series implements the plan to deprecate and remove support for\n> > core.commentChar=auto outlined in [1]. This feature has been the\n> > source of a couple of bug reports recently [2,3] and as explained in\n> > the first patch the design is tricky to fix.\n>\n> FWIW, this fails some tests that expect \"# commented lines\" by\n> treating \"auto\" too literally.\n>\n> https://github.com/git/git/actions/runs/16157263228/job/45602188411#step:10:2970\n>\n\nThe failing test is a test which I added in the bug-fix patch: [1]\nI don't understand what you meant by \"treating auto too literally\".\n\n> I wonder if our braincycles are better spent to actually perform the\n> \"tricky\"[*] fix than deprecating the feature and then perfecting the\n> deprecation process (which does not seem to be without cost either).\n>\n>  - We can and should keep the \"auto\" magic and use '#' when it gets\n>    specified, if we really wanted to do this deprecation.  I am not\n>    a huge fan of it, though.\n>\n>  - Or leave it as a known-broken feature in certain corner cases,\n>    which may motivate some future developers to tackle these\n>    \"tricky\" code paths.  If we were to go this route, the first step\n>    would be to document what works and what does not as \"known\n>    limitations\".  I am slightly more in favor of this than \"we punt,\n>    because we cannot fix it\", but not by a large margin.\n>\n> So, I dunno.\n>\n> Thanks.\n>\n> [Footnote]\n>\n>  * Essentially we would need to collect all information (like hook\n>    output and template files) before we produce our own message to\n>    be commented out because we need to know what symbol is\n>    available.  Such a change may mean a major reshuffling of some\n>    code paths (or worse, some code paths may have to be made to fail\n>    and retry).  As long as the damage is limited to the case where\n>    \"auto\" setting is used, such a \"solution\" is acceptable.\n\n[1]: https://lore.kernel.org/git/20250630182527.69167-1-ayu.chandekar@gmail.com\n"},{"id":"521632","messageId":"f1fad7f4-209f-47a9-8ad2-5e236c0b0006@gmail.com","threadId":"63762","inReplyTo":"xmqqy0syp30y.fsf@gitster.g","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-09T09:38:00Z","receivedAt":"2025-07-09T09:38:03Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 09/07/2025 02:27, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> This series implements the plan to deprecate and remove support for\n>> core.commentChar=auto outlined in [1]. This feature has been the\n>> source of a couple of bug reports recently [2,3] and as explained in\n>> the first patch the design is tricky to fix.\n> \n> FWIW, this fails some tests that expect \"# commented lines\" by\n> treating \"auto\" too literally.\n> \n> https://github.com/git/git/actions/runs/16157263228/job/45602188411#step:10:2970\n\nThat's a semantic conflict between this series and seen - the test \nshould be marked !WITH_BREAKING_CHANGES as it is testing \ncore.commentChar=auto\n> I wonder if our braincycles are better spent to actually perform the\n> \"tricky\"[*] fix than deprecating the feature and then perfecting the\n> deprecation process (which does not seem to be without cost either).\n> \n>   - We can and should keep the \"auto\" magic and use '#' when it gets\n>     specified, if we really wanted to do this deprecation.  I am not\n>     a huge fan of it, though.\n> \n>   - Or leave it as a known-broken feature in certain corner cases,\n>     which may motivate some future developers to tackle these\n>     \"tricky\" code paths.  If we were to go this route, the first step\n>     would be to document what works and what does not as \"known\n>     limitations\".  I am slightly more in favor of this than \"we punt,\n>     because we cannot fix it\", but not by a large margin.\n\nI've thought about fixing it but I don't see a good way for templates \nand hooks to say \"lines that begin with this character are comments\" - \nfor a hook the choice of comment char needs to be based on the message \nso we cannot use a fixed config setting. Unless we have a way of doing \nthat it is never going to work. The cherry-pick/rebase issues could be \naddressed by writing some state that is then read by \"git commit\" though \ngoodness knows how we'd make that work with the prepare-commit-msg hook \nthat wants to introduce its own comments. I don't really see the point \nof just fixing the rebase case if templates and hooks are still broken.\n\nNow that the commentChar can be an arbitrary string rather than just a \nsingle byte I think is much easier for users to pick something unique \nthat isn't going to be at the start of a line in their commit messages.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"521633","messageId":"f679151a-c843-44d4-9e28-27112d26f30c@gmail.com","threadId":"63762","inReplyTo":"xmqqjz4iv7mt.fsf@gitster.g","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-09T09:38:19Z","receivedAt":"2025-07-09T09:38:23Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 08/07/2025 19:51, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> This series implements the plan to deprecate and remove support for\n>> core.commentChar=auto outlined in [1]. This feature has been the\n>> source of a couple of bug reports recently [2,3] and as explained in\n>> the first patch the design is tricky to fix. When git sees the\n>> deprecated config setting it will print advice like the example below\n>> to help the user either remove the setting or set a custom comment\n>> string.\n\nWith hindsight I should have been clearer here that the advice given is \nbased on the user's config settings. In this case the files look like\n\n~/.gitconfig:\n     [core]\n         commentChar = auto\n\n~/$XDG_COMFIG/HOME/git/config\n     [core]\n         commentString = %\n         commentString = !\n         commentChar = auto\n\n>> hint: Support for 'core.commentChar=auto' is deprecated and will be removed in git 3.0\n>> hint:\n>> hint: To use the default comment string (#) please run\n>> hint:\n>> hint:     git config unset --file ~/.config/git/config --all core.commentString\n>> hint:     git config unset --file ~/.config/git/config core.commentChar\n>> hint:     git config unset --global core.commentChar\n> \n> We'd need to clear both variants from all scopes, wouldn't we?\n> \n> \tfor scope in \"\" --local --global --worktree\n> \tdo\n> \t\tfor variant in commentString commentChar\n> \t\tdo\n> \t\t\tgit config unset $scope --all core.$variant\n> \t\tdone\n> \tdone\n\nThe advice includes commands to clear all the scopes that are set. It \ncorrectly handles include files and correctly handles cases where both \n.gitconfig and $XDG_CONFIG_HOME/git/config exist (where \"git config \nunset --global <key>\" will only unset <key> in ~/.gitconfig)\n\n>> hint:\n>> hint: To set a custom comment string please run\n>> hint:\n>> hint:     git config set --global core.commentChar <comment string>\n>> hint:\n>> hint: where '<comment string>' is the string you wish to use.\n> \n> I do not particulary find it sensible to nudge users to use the same\n> commentChar across all projects with possibly different project\n> conventions by suggesting use of the --global option here.\n\nThe advice will recommend a command that updates commentChar in the \nscope where it is currently set so if it is set globally it will not \nprompt you to set it locally in each repository and if it is set locally \nit will prompt you to update it there.\n> It would be necessary to special case \"auto\" after 3.0 boundary\n> anyway, whether we (1) die when we notice the value is set to\n> \"auto\", and refuse to work until the user chooses a comment char, or\n> (2) use \"#\" or something hardcoded.  Either would be better than\n> using literal string \"auto\" as comment char.\n\nWe can do that if you've changed your view from \n<xmqqfrj6vfsn.fsf@gitster.g>\n\n> So, a simpler approach might be to treat literal string \"auto\" as if\n> \"#\" was specified under WITH_BREAKING_CHANGES so that the end-user\n> does not have to do anything when they want to \"revert\" to the\n> default comment string.  Then we do not have to give any large text\n> like the above.  We can instead say something like\n> \n> \tThe 'auto' setting of core.commentChar (or core.commentString)\n> \twill change its meaning in Git 3.0 and later and will always\n> \tuse the default '#'.\n\nThat's certainly simpler for us but it does not help the user to update \ntheir config. Presumably they're using the auto commentchar because '#' \ndoes not work for them.\n\nThanks\n\nPhillip\n> Hmm?\n> \n>> [1] https://lore.kernel.org/git/6a3154e0-e7bc-45ae-b554-67ccab18727a@gmail.com\n>> [2] https://lore.kernel.org/git/20250315140913.577404-1-oswald.buddenhagen@gmx.de\n>> [3] https://lore.kernel.org/git/20250626132233.414789-1-ayu.chandekar@gmail.com\n>>\n>> Base-Commit: f0135a9047ca37d4d117dcf21f7e3e89fad85d00\n>> Published-As: https://github.com/phillipwood/git/releases/tag/pw%2Fremove-auto-comment-char%2Fv1\n>> View-Changes-At: https://github.com/phillipwood/git/compare/f0135a904...83d0d3ece\n>> Fetch-It-Via: git fetch https://github.com/phillipwood/git pw/remove-auto-comment-char/v1\n>>\n>>\n>> Phillip Wood (2):\n>>    breaking-changes: deprecate support for core.commentString=auto\n>>    commit: print advice when core.commentString=auto\n>>\n>>   Documentation/BreakingChanges.adoc |   4 +\n>>   Documentation/config/core.adoc     |  20 ++-\n>>   builtin/commit.c                   | 192 +++++++++++++++++++++++++++++\n>>   config.c                           |   4 +\n>>   environment.c                      |   2 +\n>>   environment.h                      |   2 +\n>>   t/t3404-rebase-interactive.sh      |   2 +-\n>>   t/t7502-commit-porcelain.sh        |  32 ++++-\n>>   8 files changed, 252 insertions(+), 6 deletions(-)\n\n"},{"id":"521634","messageId":"2b0ddaf6-9242-47b5-914a-73fdd1dde4a4@gmail.com","threadId":"63762","inReplyTo":"CAE7as+YyKHzHjpCn2fFym0e5JquhroeDPHo_FHtdNCCb1HVSgw@mail.gmail.com","subject":"Re: [PATCH 1/2] breaking-changes: deprecate support for core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-09T09:40:34Z","receivedAt":"2025-07-09T09:40:36Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Ayush\n\nOn 08/07/2025 16:28, Ayush Chandekar wrote:\n> On Tue, Jul 8, 2025 at 7:27 PM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>>\n>> When \"core.commentString\" is set to \"auto\" then \"git commit\"\n>> will automatically select the comment character ensuring that it\n>> does not the first character on any of the lines in the commit\n>> message. This was introduced by commit 84c9dc2c5a2 (commit: allow\n>> core.commentChar=auto for character auto selection, 2014-05-17) The\n>> motivation seems to be to avoid commenting out lines from the existing\n>> message when amending a commit that was created with a message from\n>> a file.\n>>\n> \n> s/that it does not the first character/that it does not appear on the\n> first character?\n\nWell spotted - I was trying to change it say \"that it is not the first \ncharacter\" but edited the message badly\n\n>> Unfortunately this feature does not work with:\n>>\n>>   * commit message templates that contain comments.\n>>\n>>   * prepare-commit-msg hooks that introduce comments.\n>>\n>>   * \"git commit --cleanup=strip --edit -F <file>\" which means that it\n>>     is incompatible with\n>>\n>>     - the \"fixup\" and \"squash\" commands of \"git rebase -i\" as the\n>>       comments added by those commands are then treated as part of the\n>>       commit message.\n>>\n>>     - the conflict comments added to the commit message by \"git\n>>       cherry-pick\", \"git rebase\" etc. as these comments are then treated\n>>       as part of the commit message.\n>>\n>> It is also ignored by \"git notes\" when amending a note.\n>>\n>> The issues with comments coming from a template, hook or file are a\n>> consequence of the design of this feature and are therefore hard to\n>> fix.\n>>\n>> As the costs of this feature outweigh the benefits deprecate it and\n>> remove it in Git 3.0. If someone comes up with some patches that fix all\n>> the issues in a maintainable way then I'd be happy to see this change\n>> reverted.\n>>\n> Nit: s/benefits deprecate/benefits, deprecate.\n\nGood idea\n\nThanks\n\nPhillip\n\n"},{"id":"521702","messageId":"xmqqfrf5nxnq.fsf@gitster.g","threadId":"63762","inReplyTo":"f679151a-c843-44d4-9e28-27112d26f30c@gmail.com","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-09T16:20:41Z","receivedAt":"2025-07-09T16:20:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> With hindsight I should have been clearer here that the advice given\n> is based on the user's config settings.\n\nAhh, OK.  If the \"hint\" advice message gets generated with custom\nsequence of commands, that explains why the sample looked so uneven.\nDisregard what I said about clearing every variant from every scope.\n\n> The advice will recommend a command that updates commentChar in the\n> scope where it is currently set so if it is set globally it will not\n> prompt you to set it locally in each repository and if it is set\n> locally it will prompt you to update it there.\n\nAgain, I misunderstood the set-up that would lead to the sample\noutput.  If the user has \"auto\" in ~/.gitconfig, replacing it at the\nsame place may make sense.\n\nIf the \"auto\" comes from /etc/gitconfig then we'd recommend\nchanging it there, instead of overriding it per-user in ~/.gitconfig?\n\n>> It would be necessary to special case \"auto\" after 3.0 boundary\n>> anyway, whether we (1) die when we notice the value is set to\n>> \"auto\", and refuse to work until the user chooses a comment char, or\n>> (2) use \"#\" or something hardcoded.  Either would be better than\n>> using literal string \"auto\" as comment char.\n>\n> We can do that if you've changed your view from\n> <xmqqfrj6vfsn.fsf@gitster.g>\n\nYeah, I think using \"auto \" as comment line prefix is simply a\nnonsense.  Thanks.\n\n>> So, a simpler approach might be to treat literal string \"auto\" as if\n>> \"#\" was specified under WITH_BREAKING_CHANGES so that the end-user\n>> does not have to do anything when they want to \"revert\" to the\n>> default comment string.  Then we do not have to give any large text\n>> like the above.  We can instead say something like\n>> \tThe 'auto' setting of core.commentChar (or core.commentString)\n>> \twill change its meaning in Git 3.0 and later and will always\n>> \tuse the default '#'.\n>\n> That's certainly simpler for us but it does not help the user to\n> update their config. Presumably they're using the auto commentchar\n> because '#' does not work for them.\n\nOK.  But those with \"auto\" because '#' did not work for them are\nsetting \"auto\" not because '#' does not work, but because none of\nthese \"#;@!$%^&|:\" work for them, no?\n\nAs you said earlier, the \"auto\" setting cannot fundamentally work at\nall if we let a third-party inject any commented material into the\neditor buffer.  The comment we inject ourselves we can control (and\nnotice), and perhaps back in the simpler days when \"auto\" setting\nwas invented, it was sufficient.  But that may be no longer true, so\nit may not be just \"tricky to fix\" but simply \"unworkable\".  From\nthat point of view, as long as the reason clearly is explained to\nend-users, I am fine with \"'auto' stops Git and you'd need to unset\nor set it to something else at the 3.0 boundary\".\n\nThanks.\n"},{"id":"521821","messageId":"b811a0dc-fb49-4f66-a9ae-89a45d7ff104@gmail.com","threadId":"63762","inReplyTo":"xmqqfrf5nxnq.fsf@gitster.g","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-11T15:09:17Z","receivedAt":"2025-07-11T15:09:23Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 09/07/2025 17:20, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> With hindsight I should have been clearer here that the advice given\n>> is based on the user's config settings.\n> \n> Ahh, OK.  If the \"hint\" advice message gets generated with custom\n> sequence of commands, that explains why the sample looked so uneven.\n> Disregard what I said about clearing every variant from every scope.\n> \n>> The advice will recommend a command that updates commentChar in the\n>> scope where it is currently set so if it is set globally it will not\n>> prompt you to set it locally in each repository and if it is set\n>> locally it will prompt you to update it there.\n> \n> Again, I misunderstood the set-up that would lead to the sample\n> output.  If the user has \"auto\" in ~/.gitconfig, replacing it at the\n> same place may make sense.\n> \n> If the \"auto\" comes from /etc/gitconfig then we'd recommend\n> changing it there, instead of overriding it per-user in ~/.gitconfig?\n\nYes, though I'm on the fence about that. I wonder if we should recommend \n~/.gitconfig instead if the user account that git is running under does \nnot have write access to /etc/gitconfig. That also raises the question \nof what advice we should give about clearing settings in the system \nconfig file if the user does not have write access to it. It is possible \nthe human user has write access to the system config even if the user \naccount that git is running under does not but we have no way of finding \nthat out.\n\n>>> It would be necessary to special case \"auto\" after 3.0 boundary\n>>> anyway, whether we (1) die when we notice the value is set to\n>>> \"auto\", and refuse to work until the user chooses a comment char, or\n>>> (2) use \"#\" or something hardcoded.  Either would be better than\n>>> using literal string \"auto\" as comment char.\n\nI'm leaning towards dying to avoid any nasty surprises when the commit \nmessage contains lines beginning with '#'.\n\nI'll try and re-roll next week\n\nThanks\n\nPhillip\n\n"},{"id":"521834","messageId":"xmqqsej21wsq.fsf@gitster.g","threadId":"63762","inReplyTo":"b811a0dc-fb49-4f66-a9ae-89a45d7ff104@gmail.com","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-11T17:07:01Z","receivedAt":"2025-07-11T17:07:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> If the \"auto\" comes from /etc/gitconfig then we'd recommend\n>> changing it there, instead of overriding it per-user in ~/.gitconfig?\n>\n> Yes, though I'm on the fence about that. I wonder if we should\n> recommend ~/.gitconfig instead if the user account that git is running\n> under does not have write access to /etc/gitconfig. That also raises\n> the question of what advice we should give about clearing settings in\n> the system config file if the user does not have write access to\n> it. It is possible the human user has write access to the system\n> config even if the user account that git is running under does not but\n> we have no way of finding that out.\n\nIsn't it last-one-wins?  How about just telling them to do without\nany \"git config unset\" and just do a single \"git config set\", either\nto the repository (when the \"auto\" we saw came from the repository)\nor to the per-user configuration (when the \"auto\" we saw came from\nelsewhere, either per-user, or system-wide)?\n\n>>>> It would be necessary to special case \"auto\" after 3.0 boundary\n>>>> anyway, whether we (1) die when we notice the value is set to\n>>>> \"auto\", and refuse to work until the user chooses a comment char, or\n>>>> (2) use \"#\" or something hardcoded.  Either would be better than\n>>>> using literal string \"auto\" as comment char.\n>\n> I'm leaning towards dying to avoid any nasty surprises when the commit\n> message contains lines beginning with '#'.\n>\n> I'll try and re-roll next week\n\nThanks!\n\n"},{"id":"521856","messageId":"aHIWaQUC_wQbMw-V@ugly","threadId":"63762","inReplyTo":"xmqqsej21wsq.fsf@gitster.g","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Oswald Buddenhagen","fromEmail":"oswald.buddenhagen@gmx.de","sentAt":"2025-07-12T08:01:45Z","receivedAt":"2025-07-12T08:01:48Z","isPatch":true,"sender":{"key":"oswald.buddenhagen@gmx.de","avatar":"https://avatars.githubusercontent.com/u/812380?v=4"},"body":"On Fri, Jul 11, 2025 at 10:07:01AM -0700, Junio C Hamano wrote:\n>Isn't it last-one-wins?  How about just telling them to do without\n>any \"git config unset\" [...]\n>\ni wouldn't bother suggesting specific fixes, and just suggest using `git \nconfig list --show-scope` to figure out where the config comes from.  \nwaaaay simpler, and avoids the pesky policy questions.\n\ni'm also in favor of just refusing to operate when the 'auto' setting is \nencountered, as that also is the simplest and fail-safe approach.\n"},{"id":"521876","messageId":"xmqq34b1y04o.fsf@gitster.g","threadId":"63762","inReplyTo":"aHIWaQUC_wQbMw-V@ugly","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-12T14:06:15Z","receivedAt":"2025-07-12T14:06:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Oswald Buddenhagen <oswald.buddenhagen@gmx.de> writes:\n\n> On Fri, Jul 11, 2025 at 10:07:01AM -0700, Junio C Hamano wrote:\n>>Isn't it last-one-wins?  How about just telling them to do without\n>>any \"git config unset\" [...]\n>>\n> i wouldn't bother suggesting specific fixes, and just suggest using\n> `git config list --show-scope` to figure out where the config comes\n> from.  waaaay simpler, and avoids the pesky policy questions.\n\nYes, making it explicitly a responsibility of the end-users to\nfigure out what is the best approach to take would always work.\n\nThe approach taken by the patch gives series of commands that can be\ncopied and pasted without thinking, which is, even though it does\nimply that we make a policy decision for those who do not want to\nthink for themselves, easy to use, though.\n\n> i'm also in favor of just refusing to operate when the 'auto' setting\n> is encountered, as that also is the simplest and fail-safe approach.\n\nYes, I agree that it is a very sensible thing to do.\n"},{"id":"522815","messageId":"xmqqh5yy1r3t.fsf@gitster.g","threadId":"63762","inReplyTo":"b811a0dc-fb49-4f66-a9ae-89a45d7ff104@gmail.com","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-26T23:15:18Z","receivedAt":"2025-07-26T23:15:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> I'm leaning towards dying to avoid any nasty surprises when the commit\n> message contains lines beginning with '#'.\n>\n> I'll try and re-roll next week\n\nWhat's the current state of this effort?\n"},{"id":"522826","messageId":"b4316fea-ad1f-495c-bf38-e39f5180bdc5@gmail.com","threadId":"63762","inReplyTo":"xmqqh5yy1r3t.fsf@gitster.g","subject":"Re: [PATCH 0/2] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-27T15:46:44Z","receivedAt":"2025-07-27T15:46:52Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 27/07/2025 00:15, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> I'm leaning towards dying to avoid any nasty surprises when the commit\n>> message contains lines beginning with '#'.\n>>\n>> I'll try and re-roll next week\n> \n> What's the current state of this effort?\n\nIt's getting there, unfortunately it has taken longer than I thought it \nwould. I reasonably confident that I'll have something to post later \nthis week.\n\nThanks\n\nPhillip\n"},{"id":"523104","messageId":"cover.1753975294.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1751983009.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v2 0/3] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-31T15:21:52Z","receivedAt":"2025-07-31T15:22:25Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThanks to Ayush, Junio and Oswald for their comments on V1.\nThis series implements the plan to deprecate and remove support for\ncore.commentChar=auto outlined in [1]. This feature has been the\nsource of a couple of bug reports recently [2,3] and it is hard to\nsee how the design can be fixed as it is incompatible with preparing\na commit message template containing comments. When git sees the\ndeprecated config setting it will print advice based on the user's\nconfig setting to help the user either remove the setting or set a\ncustom comment string. In the example below core.commentString is set\nmultiple times in $XDG_CONFIG_HOME/git/config and core.commentChar\nis set in ~/.gitconfig and $XDG_CONFIG_HOME/git/config.\n\nwarning: Support for 'core.commentChar=auto' is deprecated and will be removed in Git 3.0\nhint:\nhint: To use the default comment string (#) please run\nhint:\nhint:     git config unset --file ~/.config/git/config --all core.commentString\nhint:     git config unset --file ~/.config/git/config core.commentChar\nhint:     git config unset --global core.commentChar\nhint:\nhint: To set a custom comment string please run\nhint:\nhint:     git config set --global core.commentChar <comment string>\nhint:\nhint: where '<comment string>' is the string you wish to use.\n\n[1] https://lore.kernel.org/git/6a3154e0-e7bc-45ae-b554-67ccab18727a@gmail.com\n[2] https://lore.kernel.org/git/20250315140913.577404-1-oswald.buddenhagen@gmx.de\n[3] https://lore.kernel.org/git/20250626132233.414789-1-ayu.chandekar@gmail.com\n\nChanges since V1:\n - Rebased onto a merge of 'ps/config-wo-the-repository' and 'master'\n - Reworded commit messages\n - What was patch 2 has been split into two separate patches and\n   reworked to die when core.commentChar=auto and WITH_BREAKING_CHANGES\n   is enabled.\n\nBase-Commit: 1ae5bd276bdf101e37c1a8f2904a2eae05fbb744\nPublished-As: https://github.com/phillipwood/git/releases/tag/pw%2Fremove-auto-comment-char%2Fv2\nView-Changes-At: https://github.com/phillipwood/git/compare/1ae5bd276...0e7c08b15\nFetch-It-Via: git fetch https://github.com/phillipwood/git pw/remove-auto-comment-char/v2\n\n\nPhillip Wood (3):\n  breaking-changes: deprecate support for core.commentString=auto\n  config: warn on core.commentString=auto\n  commit: print advice when core.commentString=auto\n\n Documentation/BreakingChanges.adoc |   5 +\n Documentation/config/core.adoc     |  20 +-\n builtin/commit.c                   |   7 +\n builtin/merge.c                    |   3 +\n builtin/rebase.c                   |   3 +\n builtin/revert.c                   |   7 +\n config.c                           | 297 ++++++++++++++++++++++++++++-\n environment.c                      |  11 +-\n environment.h                      |   3 +\n repository.c                       |   1 +\n repository.h                       |   3 +\n t/t3404-rebase-interactive.sh      |  19 +-\n t/t3418-rebase-continue.sh         |   2 +-\n t/t7502-commit-porcelain.sh        |  52 ++++-\n 14 files changed, 421 insertions(+), 12 deletions(-)\n\nRange-diff against v1:\n1:  3747a1f77f0 < -:  ----------- breaking-changes: deprecate support for core.commentString=auto\n2:  83d0d3ece86 < -:  ----------- commit: print advice when core.commentString=auto\n-:  ----------- > 1:  a6355451d4b breaking-changes: deprecate support for core.commentString=auto\n-:  ----------- > 2:  8b575980426 config: warn on core.commentString=auto\n-:  ----------- > 3:  0e7c08b15e5 commit: print advice when core.commentString=auto\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"523105","messageId":"a6355451d4b22730ffd20eb5ea5e820582d20b03.1753975294.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1753975294.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v2 1/3] breaking-changes: deprecate support for core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-31T15:21:53Z","receivedAt":"2025-07-31T15:22:27Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen \"core.commentString\" is set to \"auto\" then \"git commit\" will\nautomatically select the comment character ensuring that it is not the\nfirst character on any of the lines in the commit message. This was\nintroduced by commit 84c9dc2c5a2 (commit: allow core.commentChar=auto\nfor character auto selection, 2014-05-17) The motivation seems to be\nto avoid commenting out lines from the existing message when amending\na commit that was created with a message from a file.\n\nUnfortunately this feature does not work with:\n\n * commit message templates that contain comments.\n\n * prepare-commit-msg hooks that introduce comments.\n\n * \"git commit --cleanup=strip --edit -F <file>\" which means that it\n   is incompatible with\n\n   - the \"fixup\" and \"squash\" commands of \"git rebase -i\" as the\n     comments added by those commands are then treated as part of\n     the commit message.\n\n   - the conflict comments added to the commit message by \"git\n     cherry-pick\", \"git rebase\" etc. as these comments are then\n     treated as part of the commit message.\n\nIt is also ignored by \"git notes\" when amending a note.\n\nThe issues with comments coming from a template, hook or file are a\nconsequence of the design of this feature and are therefore hard to\nfix.\n\nAs the costs of this feature outweigh the benefits deprecate it and\nremove it in Git 3.0. If someone comes up with some patches that fix\nall the issues in a maintainable way then I'd be happy to see this\nchange reverted.\n\nThe next commits will add a warning and some advice for users on how\nthey can update their config settings.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n Documentation/BreakingChanges.adoc |  5 +++++\n Documentation/config/core.adoc     | 20 ++++++++++++++++++--\n builtin/commit.c                   |  4 ++++\n environment.c                      | 10 ++++++++--\n environment.h                      |  2 ++\n t/t3404-rebase-interactive.sh      |  2 +-\n t/t3418-rebase-continue.sh         |  2 +-\n t/t7502-commit-porcelain.sh        |  4 ++--\n 8 files changed, 41 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc\nindex f8d2eba061c..344ce500603 100644\n--- a/Documentation/BreakingChanges.adoc\n+++ b/Documentation/BreakingChanges.adoc\n@@ -239,6 +239,11 @@ These features will be removed.\n +\n The command will be removed.\n \n+* Support for `core.commentString=auto` has been deprecated and will\n+  be removed in Git 3.0.\n++\n+cf. <xmqqa59i45wc.fsf@gitster.g>\n+\n == Superseded features that will not be deprecated\n \n Some features have gained newer replacements that aim to improve the design in\ndiff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc\nindex 9fde1ab63a7..7133f00c38b 100644\n--- a/Documentation/config/core.adoc\n+++ b/Documentation/config/core.adoc\n@@ -531,9 +531,25 @@ core.commentString::\n \tcommented, and removes them after the editor returns\n \t(default '#').\n +\n-If set to \"auto\", `git-commit` would select a character that is not\n+ifndef::with-breaking-changes[]\n+If set to \"auto\", `git-commit` will select a character that is not\n the beginning character of any line in existing commit messages.\n-+\n+Support for this value is deprecated and will be removed in Git 3.0\n+due to the following limitations:\n++\n+--\n+* It is incompatible with adding comments in a commit message\n+  template. This includes the conflicts comments added to\n+  the commit message by `cherry-pick`, `merge`, `rebase` and\n+  `revert`.\n+* It is incompatible with adding comments to the commit message\n+  in the `prepare-commit-msg` hook.\n+* It is incompatible with the `fixup` and `squash` commands when\n+  rebasing,\n+* It is not respected by `git notes`\n+--\n++\n+endif::with-breaking-changes[]\n Note that these two variables are aliases of each other, and in modern\n versions of Git you are free to use a string (e.g., `//` or `⁑⁕⁑`) with\n `commentChar`. Versions of Git prior to v2.45.0 will ignore\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 757f51eac82..d25cc07a355 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -683,6 +683,7 @@ static int author_date_is_interesting(void)\n \treturn author_message || force_date;\n }\n \n+#ifndef WITH_BREAKING_CHANGES\n static void adjust_comment_line_char(const struct strbuf *sb)\n {\n \tchar candidates[] = \"#;@!$%^&|:\";\n@@ -720,6 +721,7 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tfree(comment_line_str_to_free);\n \tcomment_line_str = comment_line_str_to_free = xstrfmt(\"%c\", *p);\n }\n+#endif /* !WITH_BREAKING_CHANGES */\n \n static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\n \t\t\t\tstruct pretty_print_context *ctx)\n@@ -916,8 +918,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tif (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)\n \t\tdie_errno(_(\"could not write commit template\"));\n \n+#ifndef WITH_BREAKING_CHANGES\n \tif (auto_comment_line_char)\n \t\tadjust_comment_line_char(&sb);\n+#endif /* !WITH_BREAKING_CHANGES */\n \tstrbuf_release(&sb);\n \n \t/* This checks if committer ident is explicitly given */\ndiff --git a/environment.c b/environment.c\nindex a0ac5934b37..4c87876d483 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -122,7 +122,9 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n  */\n const char *comment_line_str = \"#\";\n char *comment_line_str_to_free;\n+#ifndef WITH_BREAKING_CHANGES\n int auto_comment_line_char;\n+#endif /* !WITH_BREAKING_CHANGES */\n \n /* This is set by setup_git_directory_gently() and/or git_default_config() */\n char *git_work_tree_cfg;\n@@ -459,18 +461,22 @@ static int git_default_core_config(const char *var, const char *value,\n \n \tif (!strcmp(var, \"core.commentchar\") ||\n \t    !strcmp(var, \"core.commentstring\")) {\n-\t\tif (!value)\n+\t\tif (!value) {\n \t\t\treturn config_error_nonbool(var);\n-\t\telse if (!strcasecmp(value, \"auto\")) {\n+#ifndef WITH_BREAKING_CHANGES\n+\t\t} else if (!strcasecmp(value, \"auto\")) {\n \t\t\tauto_comment_line_char = 1;\n \t\t\tFREE_AND_NULL(comment_line_str_to_free);\n \t\t\tcomment_line_str = \"#\";\n+#endif /* !WITH_BREAKING_CHANGES */\n \t\t} else if (value[0]) {\n \t\t\tif (strchr(value, '\\n'))\n \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n \t\t\tcomment_line_str = value;\n \t\t\tFREE_AND_NULL(comment_line_str_to_free);\n+#ifndef WITH_BREAKING_CHANGES\n \t\t\tauto_comment_line_char = 0;\n+#endif /* !WITH_BREAKING_CHANGES */\n \t\t} else\n \t\t\treturn error(_(\"%s must have at least one character\"), var);\n \t\treturn 0;\ndiff --git a/environment.h b/environment.h\nindex 8cfce41015b..e75c4abb388 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -208,7 +208,9 @@ extern char *excludes_file;\n  */\n extern const char *comment_line_str;\n extern char *comment_line_str_to_free;\n+#ifndef WITH_BREAKING_CHANGES\n extern int auto_comment_line_char;\n+#endif /* !WITH_BREAKING_CHANGES */\n \n # endif /* USE_THE_REPOSITORY_VARIABLE */\n #endif /* ENVIRONMENT_H */\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 6bac217ed35..ce0aebb9a7e 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1176,7 +1176,7 @@ test_expect_success 'rebase -i respects core.commentchar' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n-test_expect_success 'rebase -i respects core.commentchar=auto' '\n+test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=auto' '\n \ttest_config core.commentchar auto &&\n \twrite_script copy-edit-script.sh <<-\\EOF &&\n \tcp \"$1\" edit-script\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex b8a8dd77e74..f9b8999db50 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -328,7 +328,7 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas\n \ttest_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec\n '\n \n-test_expect_success 'no change in comment character due to conflicts markers with core.commentChar=auto' '\n+test_expect_success !WITH_BREAKING_CHANGES 'no change in comment character due to conflicts markers with core.commentChar=auto' '\n \tgit checkout -b branch-a &&\n \ttest_commit A F1 &&\n \tgit checkout -b branch-b HEAD^ &&\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex b37e2018a74..65b4519a715 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -956,13 +956,13 @@ test_expect_success 'commit --status with custom comment character' '\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n-test_expect_success 'switch core.commentchar' '\n+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n \tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n-test_expect_success 'switch core.commentchar but out of options' '\n+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar but out of options' '\n \tcat >text <<\\EOF &&\n # 1\n ; 2\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"523106","messageId":"8b57598042642dd0c56e39be03c1c45a62accfb0.1753975294.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1753975294.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v2 2/3] config: warn on core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-31T15:21:54Z","receivedAt":"2025-07-31T15:22:28Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAs support for this setting was deprecated in the last commit print a\nwarning (or die when WITH_BREAKING_CHANGES is enabled) if it is set.\nWhen printing a warning avoid bombarding the user by only printing it\nwhen running commands commands that run \"git commit\" and only only\nonce per command. Some scaffolding is added to repo_read_config()\nto allow it to detect deprecated config settings and warn about\nthem. As both \"core.commentChar\" and \"core.commentString\" set the\ncomment character we record which one of them is used and tailor the\nwarning message appropriately.\n\nNote the odd combination of die_message() followed by die(NULL)\nis to allow the next commit to insert a call to advise() in the middle.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/commit.c              |   3 +\n builtin/merge.c               |   3 +\n builtin/rebase.c              |   3 +\n builtin/revert.c              |   7 ++\n config.c                      | 116 +++++++++++++++++++++++++++++++++-\n environment.c                 |   1 +\n environment.h                 |   1 +\n repository.c                  |   1 +\n repository.h                  |   3 +\n t/t3404-rebase-interactive.sh |   7 +-\n t/t7502-commit-porcelain.sh   |  17 ++++-\n 11 files changed, 158 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex d25cc07a355..f821fdcfcc3 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1783,6 +1783,9 @@ int cmd_commit(int argc,\n \tshow_usage_with_options_if_asked(argc, argv,\n \t\t\t\t\t builtin_commit_usage, builtin_commit_options);\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex dc4cb8fb14d..794cb7bb269 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1378,6 +1378,9 @@ int cmd_merge(int argc,\n \tshow_usage_with_options_if_asked(argc, argv,\n \t\t\t\t\t builtin_merge_usage, builtin_merge_options);\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 72a52bdfb98..962917ec480 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1242,6 +1242,9 @@ int cmd_rebase(int argc,\n \t\t\t\t\t builtin_rebase_usage,\n \t\t\t\t\t builtin_rebase_options);\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex e07c2217fe8..b197848bb0a 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -4,6 +4,7 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"diff.h\"\n+#include \"environment.h\"\n #include \"gettext.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n@@ -285,6 +286,9 @@ int cmd_revert(int argc,\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tint res;\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \topts.action = REPLAY_REVERT;\n \tsequencer_init_config(&opts);\n \tres = run_sequencer(argc, argv, prefix, &opts);\n@@ -302,6 +306,9 @@ struct repository *repo UNUSED)\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tint res;\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \topts.action = REPLAY_PICK;\n \tsequencer_init_config(&opts);\n \tres = run_sequencer(argc, argv, prefix, &opts);\ndiff --git a/config.c b/config.c\nindex 97ffef42700..c36ead76005 100644\n--- a/config.c\n+++ b/config.c\n@@ -8,9 +8,11 @@\n \n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n+#include \"advice.h\"\n #include \"date.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"dir.h\"\n #include \"parse.h\"\n #include \"convert.h\"\n #include \"environment.h\"\n@@ -1951,10 +1953,110 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d\n \t\treturn 1;\n }\n \n+struct comment_char_config {\n+\tunsigned last_key_id;\n+\tbool auto_set;\n+};\n+\n+#define COMMENT_CHAR_CFG_INIT { 0 }\n+\n+static const char* comment_key_name(unsigned id)\n+{\n+\tstatic const char *name[] = {\n+\t\t\"core.commentChar\",\n+\t\t\"core.commentString\",\n+\t};\n+\n+\tif (id >= ARRAY_SIZE(name))\n+\t\tBUG(\"invalid comment key id\");\n+\n+\treturn name[id];\n+}\n+\n+static void comment_char_callback(const char *key, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *data)\n+{\n+\tstruct comment_char_config *config = data;\n+\tunsigned key_id;\n+\n+\tif (!strcmp(key, \"core.commentchar\"))\n+\t\tkey_id = 0;\n+\telse if (!strcmp(key, \"core.commentstring\"))\n+\t\tkey_id = 1;\n+\telse\n+\t\treturn;\n+\n+\tconfig->last_key_id = key_id;\n+\tconfig->auto_set = value && !strcmp(value, \"auto\");\n+}\n+\n+struct repo_config {\n+\tstruct repository *repo;\n+\tstruct comment_char_config comment_char_config;\n+};\n+\n+#define REPO_CONFIG_INIT(repo_) {\t\t\t\t\\\n+\t\t.comment_char_config = COMMENT_CHAR_CFG_INIT,\t\\\n+\t\t.repo = repo_,\t\t\t\t\t\\\n+\t};\n+\n+#ifdef WITH_BREAKING_CHANGES\n+static void check_auto_comment_char_config(struct comment_char_config *config)\n+{\n+\tif (!config->auto_set)\n+\t\treturn;\n+\n+\tdie_message(_(\"Support for '%s=auto' has been removed in Git 3.0\"),\n+\t\t    comment_key_name(config->last_key_id));\n+\tdie(NULL);\n+}\n+#else\n+static void check_auto_comment_char_config(struct comment_char_config *config)\n+{\n+\textern bool warn_on_auto_comment_char;\n+\tconst char *DEPRECATED_CONFIG_ENV =\n+\t\t\t\t\"GIT_AUTO_COMMENT_CHAR_CONFIG_WARNING_GIVEN\";\n+\n+\tif (!config->auto_set || !warn_on_auto_comment_char)\n+\t\treturn;\n+\n+\t/*\n+\t * Use an environment variable to ensure that subprocesses do not repeat\n+\t * the warning.\n+\t */\n+\tif (git_env_bool(DEPRECATED_CONFIG_ENV, false))\n+\t\treturn;\n+\n+\tsetenv(DEPRECATED_CONFIG_ENV, \"true\", true);\n+\n+\twarning(_(\"Support for '%s=auto' is deprecated and will be removed in \"\n+\t\t  \"Git 3.0\"), comment_key_name(config->last_key_id));\n+}\n+#endif /* WITH_BREAKING_CHANGES */\n+\n+static void check_deprecated_config(struct repo_config *config)\n+{\n+\tif (!config->repo->check_deprecated_config)\n+\t\t\treturn;\n+\n+\tcheck_auto_comment_char_config(&config->comment_char_config);\n+}\n+\n+static int repo_config_callback(const char *key, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *data)\n+{\n+\tstruct repo_config *config = data;\n+\n+\tcomment_char_callback(key, value, ctx, &config->comment_char_config);\n+\treturn config_set_callback(key, value, ctx, config->repo->config);\n+}\n+\n /* Functions use to read configuration from a repository */\n static void repo_read_config(struct repository *repo)\n {\n \tstruct config_options opts = { 0 };\n+\tstruct repo_config config = REPO_CONFIG_INIT(repo);\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -1966,8 +2068,8 @@ static void repo_read_config(struct repository *repo)\n \t\tgit_configset_clear(repo->config);\n \n \tgit_configset_init(repo->config);\n-\tif (config_with_options(config_set_callback, repo->config, NULL,\n-\t\t\t\trepo, &opts) < 0)\n+\tif (config_with_options(repo_config_callback, &config, NULL, repo,\n+\t\t\t\t&opts) < 0)\n \t\t/*\n \t\t * config_with_options() normally returns only\n \t\t * zero, as most errors are fatal, and\n@@ -1980,6 +2082,7 @@ static void repo_read_config(struct repository *repo)\n \t\t * immediately.\n \t\t */\n \t\tdie(_(\"unknown error occurred while reading the configuration files\"));\n+\tcheck_deprecated_config(&config);\n }\n \n static void git_config_check_init(struct repository *repo)\n@@ -2667,6 +2770,14 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,\n \tchar *contents = NULL;\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n+\tbool saved_check_deprecated_config = r->check_deprecated_config;\n+\n+\t/*\n+\t * Do not warn or die if there are deprecated config settings as\n+\t * we want the user to be able to change those settings by running\n+\t * \"git config\".\n+\t */\n+\tr->check_deprecated_config = false;\n \n \tvalidate_comment_string(comment);\n \n@@ -2898,6 +3009,7 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,\n \tif (in_fd >= 0)\n \t\tclose(in_fd);\n \tconfig_store_data_clear(&store);\n+\tr->check_deprecated_config = saved_check_deprecated_config;\n \treturn ret;\n \n write_err_out:\ndiff --git a/environment.c b/environment.c\nindex 4c87876d483..1ffa2ff30b2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -124,6 +124,7 @@ const char *comment_line_str = \"#\";\n char *comment_line_str_to_free;\n #ifndef WITH_BREAKING_CHANGES\n int auto_comment_line_char;\n+bool warn_on_auto_comment_char;\n #endif /* !WITH_BREAKING_CHANGES */\n \n /* This is set by setup_git_directory_gently() and/or git_default_config() */\ndiff --git a/environment.h b/environment.h\nindex e75c4abb388..51898c99cd1 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -210,6 +210,7 @@ extern const char *comment_line_str;\n extern char *comment_line_str_to_free;\n #ifndef WITH_BREAKING_CHANGES\n extern int auto_comment_line_char;\n+extern bool warn_on_auto_comment_char;\n #endif /* !WITH_BREAKING_CHANGES */\n \n # endif /* USE_THE_REPOSITORY_VARIABLE */\ndiff --git a/repository.c b/repository.c\nindex ecd691181fc..8af73923d34 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -57,6 +57,7 @@ void initialize_repository(struct repository *repo)\n \trepo->parsed_objects = parsed_object_pool_new(repo);\n \tALLOC_ARRAY(repo->index, 1);\n \tindex_state_init(repo->index, repo);\n+\trepo->check_deprecated_config = true;\n \n \t/*\n \t * When a command runs inside a repository, it learns what\ndiff --git a/repository.h b/repository.h\nindex 042dc93f0f2..5808a5d6108 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -161,6 +161,9 @@ struct repository {\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\n \tunsigned different_commondir:1;\n+\n+\t/* Should repo_config() check for deprecated settings */\n+\tbool check_deprecated_config;\n };\n \n #ifdef USE_THE_REPOSITORY_VARIABLE\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ce0aebb9a7e..3b2a46c25ce 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1184,8 +1184,13 @@ test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=\n \ttest_when_finished \"git rebase --abort || :\" &&\n \t(\n \t\ttest_set_editor \"$(pwd)/copy-edit-script.sh\" &&\n-\t\tgit rebase -i HEAD^\n+\t\tgit rebase -i HEAD^ 2>err\n \t) &&\n+\tsed -n \"s/^warning: //p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\tEOF\n+\ttest_cmp expect actual &&\n \ttest -z \"$(grep -ve \"^#\" -e \"^\\$\" -e \"^pick\" edit-script)\"\n '\n \ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 65b4519a715..a9dc1e416d1 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -958,7 +958,12 @@ test_expect_success 'commit --status with custom comment character' '\n \n test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n-\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&\n+\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend 2>err &&\n+\tsed -n \"s/^warning: //p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\tEOF\n+\ttest_cmp expect actual &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n@@ -982,4 +987,14 @@ EOF\n \t)\n '\n \n+test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '\n+\ttest_config core.commentChar auto &&\n+\ttest_must_fail git rev-parse --git-dir 2>err &&\n+\tsed -n \"s/^fatal: //p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"523107","messageId":"0e7c08b15e5923ae03f5630a8286c7dcebdbcfb9.1753975294.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1753975294.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v2 3/3] commit: print advice when core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-07-31T15:21:55Z","receivedAt":"2025-07-31T15:22:29Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAdd some advice on how to change the config settings when\n\"core.commentString=auto\" or \"core.commentChar=auto\". The advice\nincludes instructions for clearing the config setting or setting a\nfixed comment string. To try and be as specific as possible, the advice\nis customized based on the user's config. If \"core.commentString=auto\"\nis set in the system config and the user does not have write\naccess then the advice omits the instructions to clear the config\nand recommends changing the global config instead. An alternative\napproach would be to advise the user to run \"git config --show-origin\"\nand leave them to figure out how to fix it themselves but that seems\nrather unfriendly. As we're forcing them to update their config we\nshould try and make that as easy as possible.\n\nIn order to generate this advice we need to record each file where\neither of the config keys is set and whether a key occurs more that\nonce in a given file. This lets us generate the list of commands to\nremove all the keys and also tells us which key the \"auto\" setting\ncomes from.\n\nAs we want the user to update their config we do not provide a way\nfor this advice to be disabled other than changing the value of\n\"core.commentChar\" or \"core.commentString\".\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n config.c                      | 195 ++++++++++++++++++++++++++++++++--\n t/t3404-rebase-interactive.sh |  12 ++-\n t/t7502-commit-porcelain.sh   |  37 ++++++-\n 3 files changed, 233 insertions(+), 11 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex c36ead76005..e04cb386161 100644\n--- a/config.c\n+++ b/config.c\n@@ -1956,9 +1956,51 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d\n struct comment_char_config {\n \tunsigned last_key_id;\n \tbool auto_set;\n+\tbool auto_set_in_file;\n+\tstruct strintmap key_flags;\n+\tsize_t alloc, nr;\n+\tstruct comment_char_config_item {\n+\t\tunsigned key_id;\n+\t\tchar *path;\n+\t\tenum config_scope scope;\n+\t} *item;\n };\n \n-#define COMMENT_CHAR_CFG_INIT { 0 }\n+#define COMMENT_CHAR_CFG_INIT {\t\t\t\\\n+\t\t.key_flags = STRINTMAP_INIT,\t\\\n+\t}\n+\n+static void comment_char_config_release(struct comment_char_config *config)\n+{\n+\tstrintmap_clear(&config->key_flags);\n+\tfor (size_t i = 0; i < config->nr; i++)\n+\t\tfree(config->item[i].path);\n+\tfree(config->item);\n+}\n+\n+/* Used to track whether the key occurs more than once in a given file */\n+#define KEY_SEEN_ONCE 1u\n+#define KEY_SEEN_TWICE 2u\n+#define COMMENT_KEY_SHIFT(id) (2 * (id))\n+#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id))\n+\n+static void set_comment_key_flags(struct comment_char_config *config,\n+\t\t\t\t  const char *path, unsigned id, unsigned value)\n+{\n+\tunsigned old = strintmap_get(&config->key_flags, path);\n+\tunsigned new = (old & ~COMMENT_KEY_MASK(id)) |\n+\t\t\t\tvalue << COMMENT_KEY_SHIFT(id);\n+\n+\tstrintmap_set(&config->key_flags, path, new);\n+}\n+\n+static unsigned get_comment_key_flags(struct comment_char_config *config,\n+\t\t\t\t      const char *path, unsigned id)\n+{\n+\tunsigned value = strintmap_get(&config->key_flags, path);\n+\n+\treturn (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id);\n+}\n \n static const char* comment_key_name(unsigned id)\n {\n@@ -1974,10 +2016,10 @@ static const char* comment_key_name(unsigned id)\n }\n \n static void comment_char_callback(const char *key, const char *value,\n-\t\t\t\t  const struct config_context *ctx UNUSED,\n-\t\t\t\t  void *data)\n+\t\t\t\t  const struct config_context *ctx, void *data)\n {\n \tstruct comment_char_config *config = data;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \tunsigned key_id;\n \n \tif (!strcmp(key, \"core.commentchar\"))\n@@ -1989,7 +2031,135 @@ static void comment_char_callback(const char *key, const char *value,\n \n \tconfig->last_key_id = key_id;\n \tconfig->auto_set = value && !strcmp(value, \"auto\");\n-}\n+\tif (kvi->origin_type != CONFIG_ORIGIN_FILE) {\n+\t\treturn;\n+\t} else if (get_comment_key_flags(config, kvi->filename, key_id)) {\n+\t\tset_comment_key_flags(config, kvi->filename, key_id,\n+\t\t\t\t      KEY_SEEN_TWICE);\n+\t} else {\n+\t\tstruct comment_char_config_item *item;\n+\n+\t\tALLOC_GROW_BY(config->item, config->nr, 1, config->alloc);\n+\t\titem = &config->item[config->nr - 1];\n+\t\titem->key_id = key_id;\n+\t\titem->scope = kvi->scope;\n+\t\titem->path = xstrdup(kvi->filename);\n+\t\tset_comment_key_flags(config, kvi->filename, key_id,\n+\t\t\t\t      KEY_SEEN_ONCE);\n+\t}\n+\tconfig->auto_set_in_file = config->auto_set;\n+}\n+\n+static void add_config_scope_arg(struct repository *repo, struct strbuf *buf,\n+\t\t\t\t struct comment_char_config_item *item)\n+{\n+\tchar *global_config = git_global_config();\n+\tchar *system_config = git_system_config();\n+\n+\tif (item->scope == CONFIG_SCOPE_SYSTEM && access(item->path, W_OK)) {\n+\t\t/*\n+\t\t * If the user cannot write to the system config recommend\n+\t\t * setting the global config instead.\n+\t\t */\n+\t\tstrbuf_addstr(buf, \"--global \");\n+\t} else if (fspatheq(item->path, system_config)) {\n+\t\tstrbuf_addstr(buf, \"--system \");\n+\t} else if (fspatheq(item->path, global_config)) {\n+\t\tstrbuf_addstr(buf, \"--global \");\n+\t} else if (fspatheq(item->path,\n+\t\t\t    mkpath(\"%s/config\",\n+\t\t\t\t   repo_get_git_dir(repo)))) {\n+\t\t; /* --local is the default */\n+\t} else if (fspatheq(item->path,\n+\t\t\t    mkpath(\"%s/config.worktree\",\n+\t\t\t\t   repo_get_common_dir(repo)))) {\n+\t\tstrbuf_addstr(buf, \"--worktree \");\n+\t} else {\n+\t\tconst char *path = item->path;\n+\t\tconst char *home = getenv(\"HOME\");\n+\n+\t\tstrbuf_addstr(buf, \"--file \");\n+\t\tif (home && !fspathncmp(path, home, strlen(home))) {\n+\t\t\tpath += strlen(home);\n+\t\t\tif (!fspathncmp(path, \"/\", 1))\n+\t\t\t\tpath++;\n+\t\t\tstrbuf_addstr(buf, \"~/\");\n+\t\t}\n+\t\tsq_quote_buf_pretty(buf, path);\n+\t\tstrbuf_addch(buf, ' ');\n+\t}\n+\n+\tfree(global_config);\n+\tfree(system_config);\n+}\n+\n+static bool can_unset_comment_char_config(struct comment_char_config *config)\n+{\n+\tfor (size_t i = 0; i < config->nr; i++) {\n+\t\tstruct comment_char_config_item *item = &config->item[i];\n+\n+\t\tif (item->scope == CONFIG_SCOPE_SYSTEM &&\n+\t\t    access(item->path, W_OK))\n+\t\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+static void add_unset_auto_comment_char_advice(struct repository *repo,\n+\t\t\t\t\t       struct comment_char_config *config)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (!can_unset_comment_char_config(config))\n+\t\treturn;\n+\n+\tfor (size_t i = 0; i < config->nr; i++) {\n+\t\tstruct comment_char_config_item *item = &config->item[i];\n+\n+\t\tstrbuf_addstr(&buf, \"    git config unset \");\n+\t\tadd_config_scope_arg(repo, &buf, item);\n+\t\tif (get_comment_key_flags(config, item->path, item->key_id) == KEY_SEEN_TWICE)\n+\t\t\tstrbuf_addstr(&buf, \"--all \");\n+\t\tstrbuf_addf(&buf, \"%s\\n\", comment_key_name(item->key_id));\n+\t}\n+\tadvise(_(\"\\nTo use the default comment string (#) please run\\n\\n%s\"),\n+\t       buf.buf);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void add_comment_char_advice(struct repository *repo,\n+\t\t\t\t    struct comment_char_config *config)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct comment_char_config_item *item;\n+\t/* TRANSLATORS this is a place holder for the value of core.commentString */\n+\tconst char *placeholder = _(\"<comment string>\");\n+\n+\t/*\n+\t * If auto is set in the last file that we saw advise the user how to\n+\t * update their config.\n+\t */\n+\tif (!config->auto_set_in_file)\n+\t\treturn;\n+\n+\tadd_unset_auto_comment_char_advice(repo, config);\n+\titem = &config->item[config->nr - 1];\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, \"    git config set \");\n+\tadd_config_scope_arg(repo, &buf, item);\n+\tstrbuf_addf(&buf, \"%s %s\\n\", comment_key_name(item->key_id),\n+\t\t    placeholder);\n+\tadvise(_(\"\\nTo set a custom comment string please run\\n\\n\"\n+\t\t \"%s\\nwhere '%s' is the string you wish to use.\\n\"),\n+\t       buf.buf, placeholder);\n+\tstrbuf_release(&buf);\n+}\n+\n+#undef KEY_SEEN_ONCE\n+#undef KEY_SEEN_TWICE\n+#undef COMMENT_KEY_SHIFT\n+#undef COMMENT_KEY_MASK\n \n struct repo_config {\n \tstruct repository *repo;\n@@ -2001,18 +2171,26 @@ struct repo_config {\n \t\t.repo = repo_,\t\t\t\t\t\\\n \t};\n \n+static void repo_config_release(struct repo_config *config)\n+{\n+\tcomment_char_config_release(&config->comment_char_config);\n+}\n+\n #ifdef WITH_BREAKING_CHANGES\n-static void check_auto_comment_char_config(struct comment_char_config *config)\n+static void check_auto_comment_char_config(struct repository *repo,\n+\t\t\t\t\t   struct comment_char_config *config)\n {\n \tif (!config->auto_set)\n \t\treturn;\n \n \tdie_message(_(\"Support for '%s=auto' has been removed in Git 3.0\"),\n \t\t    comment_key_name(config->last_key_id));\n+\tadd_comment_char_advice(repo, config);\n \tdie(NULL);\n }\n #else\n-static void check_auto_comment_char_config(struct comment_char_config *config)\n+static void check_auto_comment_char_config(struct repository *repo,\n+\t\t\t\t\t   struct comment_char_config *config)\n {\n \textern bool warn_on_auto_comment_char;\n \tconst char *DEPRECATED_CONFIG_ENV =\n@@ -2032,6 +2210,7 @@ static void check_auto_comment_char_config(struct comment_char_config *config)\n \n \twarning(_(\"Support for '%s=auto' is deprecated and will be removed in \"\n \t\t  \"Git 3.0\"), comment_key_name(config->last_key_id));\n+\tadd_comment_char_advice(repo, config);\n }\n #endif /* WITH_BREAKING_CHANGES */\n \n@@ -2040,7 +2219,8 @@ static void check_deprecated_config(struct repo_config *config)\n \tif (!config->repo->check_deprecated_config)\n \t\t\treturn;\n \n-\tcheck_auto_comment_char_config(&config->comment_char_config);\n+\tcheck_auto_comment_char_config(config->repo,\n+\t\t\t\t       &config->comment_char_config);\n }\n \n static int repo_config_callback(const char *key, const char *value,\n@@ -2083,6 +2263,7 @@ static void repo_read_config(struct repository *repo)\n \t\t */\n \t\tdie(_(\"unknown error occurred while reading the configuration files\"));\n \tcheck_deprecated_config(&config);\n+\trepo_config_release(&config);\n }\n \n static void git_config_check_init(struct repository *repo)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 3b2a46c25ce..cc97628d810 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1186,9 +1186,19 @@ test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=\n \t\ttest_set_editor \"$(pwd)/copy-edit-script.sh\" &&\n \t\tgit rebase -i HEAD^ 2>err\n \t) &&\n-\tsed -n \"s/^warning: //p\" err >actual &&\n+\tsed -n \"s/^hint: *\\$//p; s/^hint: //p; s/^warning: //p\" err >actual &&\n \tcat >expect <<-EOF &&\n \tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n \tEOF\n \ttest_cmp expect actual &&\n \ttest -z \"$(grep -ve \"^#\" -e \"^\\$\" -e \"^pick\" edit-script)\"\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex a9dc1e416d1..05f6da4ad98 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -958,10 +958,31 @@ test_expect_success 'commit --status with custom comment character' '\n \n test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n-\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend 2>err &&\n-\tsed -n \"s/^warning: //p\" err >actual &&\n+\tcat >config-include <<-\\EOF &&\n+\t[core]\n+\t    commentString=:\n+\t    commentString=%\n+\t    commentChar=auto\n+\tEOF\n+\ttest_when_finished \"rm config-include\" &&\n+\ttest_config include.path \"$(pwd)/config-include\" &&\n+\ttest_config core.commentChar ! &&\n+\tGIT_EDITOR=.git/FAKE_EDITOR git commit --amend 2>err &&\n+\tsed -n \"s/^hint: *\\$//p; s/^hint: //p; s/^warning: //p\" err >actual &&\n \tcat >expect <<-EOF &&\n \tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\t    git config unset --file ~/config-include --all core.commentString\n+\t    git config unset --file ~/config-include core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set --file ~/config-include core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n \tEOF\n \ttest_cmp expect actual &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n@@ -990,9 +1011,19 @@ EOF\n test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '\n \ttest_config core.commentChar auto &&\n \ttest_must_fail git rev-parse --git-dir 2>err &&\n-\tsed -n \"s/^fatal: //p\" err >actual &&\n+\tsed -n \"s/^hint: *\\$//p; s/^hint: //p; s/^fatal: //p\" err >actual &&\n \tcat >expect <<-EOF &&\n \tSupport for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n \tEOF\n \ttest_cmp expect actual\n '\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"523125","messageId":"xmqqwm7oozku.fsf@gitster.g","threadId":"63762","inReplyTo":"a6355451d4b22730ffd20eb5ea5e820582d20b03.1753975294.git.phillip.wood@dunelm.org.uk","subject":"Re: [PATCH v2 1/3] breaking-changes: deprecate support for core.commentString=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T20:49:37Z","receivedAt":"2025-07-31T20:49:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> When \"core.commentString\" is set to \"auto\" then \"git commit\" will\n> automatically select the comment character ensuring that it is not the\n> first character on any of the lines in the commit message. This was\n> introduced by commit 84c9dc2c5a2 (commit: allow core.commentChar=auto\n> for character auto selection, 2014-05-17) The motivation seems to be\n\n\"5-17) The\" -> \"5-17). The\".\n\n> to avoid commenting out lines from the existing message when amending\n> a commit that was created with a message from a file.\n>\n> Unfortunately this feature does not work with:\n>\n>  * commit message templates that contain comments.\n>\n>  * prepare-commit-msg hooks that introduce comments.\n>\n>  * \"git commit --cleanup=strip --edit -F <file>\" which means that it\n>    is incompatible with\n>\n>    - the \"fixup\" and \"squash\" commands of \"git rebase -i\" as the\n>      comments added by those commands are then treated as part of\n>      the commit message.\n>\n>    - the conflict comments added to the commit message by \"git\n>      cherry-pick\", \"git rebase\" etc. as these comments are then\n>      treated as part of the commit message.\n>\n> It is also ignored by \"git notes\" when amending a note.\n>\n> The issues with comments coming from a template, hook or file are a\n> consequence of the design of this feature and are therefore hard to\n> fix.\n>\n> As the costs of this feature outweigh the benefits deprecate it and\n\n\"the benefits deprecate\" -> \"the benefits, deprecate\".\n\n> remove it in Git 3.0. If someone comes up with some patches that fix\n> all the issues in a maintainable way then I'd be happy to see this\n> change reverted.\n>\n> The next commits will add a warning and some advice for users on how\n> they can update their config settings.\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  +\n> -If set to \"auto\", `git-commit` would select a character that is not\n> +ifndef::with-breaking-changes[]\n> +If set to \"auto\", `git-commit` will select a character that is not\n>  the beginning character of any line in existing commit messages.\n> -+\n> +Support for this value is deprecated and will be removed in Git 3.0\n> +due to the following limitations:\n> ++\n> +--\n> +* It is incompatible with adding comments in a commit message\n> +  template. This includes the conflicts comments added to\n> +  the commit message by `cherry-pick`, `merge`, `rebase` and\n> +  `revert`.\n> +* It is incompatible with adding comments to the commit message\n> +  in the `prepare-commit-msg` hook.\n> +* It is incompatible with the `fixup` and `squash` commands when\n> +  rebasing,\n> +* It is not respected by `git notes`\n> +--\n> ++\n> +endif::with-breaking-changes[]\n\nThe above is shown to everybody before the 3.0 happens (and if you\nopt into 3.0 early, you will stop seeing it earlier than others).\n\nThe rest of the patch looks good to me.\n"},{"id":"523128","messageId":"xmqqa54koyb2.fsf@gitster.g","threadId":"63762","inReplyTo":"8b57598042642dd0c56e39be03c1c45a62accfb0.1753975294.git.phillip.wood@dunelm.org.uk","subject":"Re: [PATCH v2 2/3] config: warn on core.commentString=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-31T21:17:05Z","receivedAt":"2025-07-31T21:17:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> diff --git a/config.c b/config.c\n> index 97ffef42700..c36ead76005 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -8,9 +8,11 @@\n>  \n>  #include \"git-compat-util.h\"\n>  #include \"abspath.h\"\n> +#include \"advice.h\"\n\nHmph.  Do you still need this?\n\nI do not think a separate advice_if variable is warranted in this\ncase.  They see a warning that says that their \"auto\" will not do\nanything useful in the future.  They will keep seeing it until they\ndecide what to use, and once they decide and set a value that is\ndifferent from \"auto\" to core.commentchar, they will stop seeing\nthe warning.\n\n> +static const char* comment_key_name(unsigned id)\n\nThe asterisk sticks to the identifier, not type.\n\n> +static void comment_char_callback(const char *key, const char *value,\n> +\t\t\t\t  const struct config_context *ctx UNUSED,\n> +\t\t\t\t  void *data)\n> +{\n> +\tstruct comment_char_config *config = data;\n> +\tunsigned key_id;\n> +\n> +\tif (!strcmp(key, \"core.commentchar\"))\n> +\t\tkey_id = 0;\n> +\telse if (!strcmp(key, \"core.commentstring\"))\n> +\t\tkey_id = 1;\n> +\telse\n> +\t\treturn;\n\nYuck.  We cannot help the joy of last-one-wins here X-<.\n\n> +\n> +\tconfig->last_key_id = key_id;\n> +\tconfig->auto_set = value && !strcmp(value, \"auto\");\n> +}\n\nIt probably becomes simpler (and easier to debug) if you made the\ntype of .last_key_id member \"const char *\" to point at the variable\nname.  You are not switching on the .last_key_id member.  The only\nuse of that member is to be fed to die().  And by doing so, you can\ndrop comment_key_name().\n\n> +struct repo_config {\n> +\tstruct repository *repo;\n> +\tstruct comment_char_config comment_char_config;\n> +};\n> +\n> +#define REPO_CONFIG_INIT(repo_) {\t\t\t\t\\\n> +\t\t.comment_char_config = COMMENT_CHAR_CFG_INIT,\t\\\n> +\t\t.repo = repo_,\t\t\t\t\t\\\n> +\t};\n> +\n> +#ifdef WITH_BREAKING_CHANGES\n> +static void check_auto_comment_char_config(struct comment_char_config *config)\n> +{\n> +\tif (!config->auto_set)\n> +\t\treturn;\n> +\n> +\tdie_message(_(\"Support for '%s=auto' has been removed in Git 3.0\"),\n> +\t\t    comment_key_name(config->last_key_id));\n> +\tdie(NULL);\n> +}\n> +#else\n> +static void check_auto_comment_char_config(struct comment_char_config *config)\n> +{\n> +\textern bool warn_on_auto_comment_char;\n> +\tconst char *DEPRECATED_CONFIG_ENV =\n> +\t\t\t\t\"GIT_AUTO_COMMENT_CHAR_CONFIG_WARNING_GIVEN\";\n> +\n> +\tif (!config->auto_set || !warn_on_auto_comment_char)\n> +\t\treturn;\n> +\n> +\t/*\n> +\t * Use an environment variable to ensure that subprocesses do not repeat\n> +\t * the warning.\n> +\t */\n> +\tif (git_env_bool(DEPRECATED_CONFIG_ENV, false))\n> +\t\treturn;\n> +\n> +\tsetenv(DEPRECATED_CONFIG_ENV, \"true\", true);\n\nI know this means well, but it might give users a better experience\nif we went a much simpler route.  In your top-level project with two\nsubmodules, you may have core.commentchar set to auto in the top-level\nand only one of the submodules, and then you let \"git\" go recursive.\nWouldn't it be simpler for the user to diagnose which one(s) among\nthe three repositories need fixing, if the stderr said something\nlike:\n\n    doing X\n    warning core.commentChar is set to auto\n    going into submodule A\n      doing X\n    going into submodule B\n      doing X\n      warning core.commentString is set to auto\n\nI dunno.\n\n> +\twarning(_(\"Support for '%s=auto' is deprecated and will be removed in \"\n> +\t\t  \"Git 3.0\"), comment_key_name(config->last_key_id));\n> +}\n> +#endif /* WITH_BREAKING_CHANGES */\n> +\n> +static void check_deprecated_config(struct repo_config *config)\n> +{\n> +\tif (!config->repo->check_deprecated_config)\n> +\t\t\treturn;\n> +\n> +\tcheck_auto_comment_char_config(&config->comment_char_config);\n\nThe handling of .check_deprecated_config flag is a bit tricky, and\nit is great that this design allows us to write a similar\ncheck_foo_config() helper and make a call to it here, without \nhaving to worry about it again.\n\n"},{"id":"523163","messageId":"xmqq7bznog2o.fsf@gitster.g","threadId":"63762","inReplyTo":"cover.1753975294.git.phillip.wood@dunelm.org.uk","subject":"Re: [PATCH v2 0/3] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T03:50:55Z","receivedAt":"2025-08-01T03:50:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Changes since V1:\n>  - Rebased onto a merge of 'ps/config-wo-the-repository' and 'master'\n\nOK.  I needed the following merge-fix to make this merge work.\n\ndiff --git w/environment.c c/environment.c\nindex ae1427bb9e..a0ac5934b3 100644\n--- w/environment.c\n+++ c/environment.c\n@@ -461,9 +461,11 @@ static int git_default_core_config(const char *var, const char *value,\n \t    !strcmp(var, \"core.commentstring\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n-\t\telse if (!strcasecmp(value, \"auto\"))\n+\t\telse if (!strcasecmp(value, \"auto\")) {\n \t\t\tauto_comment_line_char = 1;\n-\t\telse if (value[0]) {\n+\t\t\tFREE_AND_NULL(comment_line_str_to_free);\n+\t\t\tcomment_line_str = \"#\";\n+\t\t} else if (value[0]) {\n \t\t\tif (strchr(value, '\\n'))\n \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n \t\t\tcomment_line_str = value;\n\nI guess I used to carry an equivalent as a recurrent merge-fix for\nyour topic branch, but rolling it into the base of the series is\ncertainly safer (i.e. we have to do a merge and resolve conflicts\njust once, and after that we won't even touch it---as opposed to\nkeep recreating the same conflict and resolving every time we merge\nyour topic via rerere & merge-fix mechanism).\n\nThanks.\n"},{"id":"523192","messageId":"a9ddf6cd-28c5-4030-9e63-80df191f1397@gmail.com","threadId":"63762","inReplyTo":"xmqq7bznog2o.fsf@gitster.g","subject":"Re: [PATCH v2 0/3] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-01T10:36:57Z","receivedAt":"2025-08-01T10:37:00Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 01/08/2025 04:50, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> Changes since V1:\n>>   - Rebased onto a merge of 'ps/config-wo-the-repository' and 'master'\n> \n> OK.  I needed the following merge-fix to make this merge work.\n> \n> diff --git w/environment.c c/environment.c\n> index ae1427bb9e..a0ac5934b3 100644\n> --- w/environment.c\n> +++ c/environment.c\n> @@ -461,9 +461,11 @@ static int git_default_core_config(const char *var, const char *value,\n>   \t    !strcmp(var, \"core.commentstring\")) {\n>   \t\tif (!value)\n>   \t\t\treturn config_error_nonbool(var);\n> -\t\telse if (!strcasecmp(value, \"auto\"))\n> +\t\telse if (!strcasecmp(value, \"auto\")) {\n>   \t\t\tauto_comment_line_char = 1;\n> -\t\telse if (value[0]) {\n> +\t\t\tFREE_AND_NULL(comment_line_str_to_free);\n> +\t\t\tcomment_line_str = \"#\";\n> +\t\t} else if (value[0]) {\n>   \t\t\tif (strchr(value, '\\n'))\n>   \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n>   \t\t\tcomment_line_str = value;\n> \n> I guess I used to carry an equivalent as a recurrent merge-fix for\n> your topic branch, but rolling it into the base of the series is\n> certainly safer (i.e. we have to do a merge and resolve conflicts\n> just once, and after that we won't even touch it---as opposed to\n> keep recreating the same conflict and resolving every time we merge\n> your topic via rerere & merge-fix mechanism).\n\nI was hoping that rebasing on master would eliminate the need for a fix \nas 'ac/auto-comment-char-fix' is now in master but in the meantime \n'ps/config-wo-the-repository' came along and moved code from config.c to \nenvironment.c without those changes. I'd assumed you already had a \nsimilar fixup when merging 'ps/config-wo-the-repository' into seen.\n\nThanks\n\nPhillip\n\n"},{"id":"523193","messageId":"34905f47-5600-4728-8611-4abc166d199f@gmail.com","threadId":"63762","inReplyTo":"xmqqa54koyb2.fsf@gitster.g","subject":"Re: [PATCH v2 2/3] config: warn on core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-01T10:37:24Z","receivedAt":"2025-08-01T10:37:27Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 31/07/2025 22:17, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>>   #include \"git-compat-util.h\"\n>>   #include \"abspath.h\"\n>> +#include \"advice.h\"\n> \n> Hmph.  Do you still need this?\n\nNo, it's left over from splitting one patch into two. It should be in \nthe next patch that adds a call to advise.\n\n> I do not think a separate advice_if variable is warranted in this\n> case.  They see a warning that says that their \"auto\" will not do\n> anything useful in the future.  They will keep seeing it until they\n> decide what to use, and once they decide and set a value that is\n> different from \"auto\" to core.commentchar, they will stop seeing\n> the warning.\n\nI agree\n\n>> +static const char* comment_key_name(unsigned id)\n> \n> The asterisk sticks to the identifier, not type.\n\nOops I'll fix that\n\n>> +\n>> +\tconfig->last_key_id = key_id;\n>> +\tconfig->auto_set = value && !strcmp(value, \"auto\");\n>> +}\n> \n> It probably becomes simpler (and easier to debug) if you made the\n> type of .last_key_id member \"const char *\" to point at the variable\n> name.  You are not switching on the .last_key_id member.  The only\n> use of that member is to be fed to die().  And by doing so, you can\n> drop comment_key_name().\n\nI agree the integer id is pointless here, but it is useful in the next \npatch for tracking if a key occurs more than once in a given file.\n\n>> +static void check_auto_comment_char_config(struct comment_char_config *config)\n>> +{\n>> +\textern bool warn_on_auto_comment_char;\n>> +\tconst char *DEPRECATED_CONFIG_ENV =\n>> +\t\t\t\t\"GIT_AUTO_COMMENT_CHAR_CONFIG_WARNING_GIVEN\";\n>> +\n>> +\tif (!config->auto_set || !warn_on_auto_comment_char)\n>> +\t\treturn;\n>> +\n>> +\t/*\n>> +\t * Use an environment variable to ensure that subprocesses do not repeat\n>> +\t * the warning.\n>> +\t */\n>> +\tif (git_env_bool(DEPRECATED_CONFIG_ENV, false))\n>> +\t\treturn;\n>> +\n>> +\tsetenv(DEPRECATED_CONFIG_ENV, \"true\", true);\n> \n> I know this means well, but it might give users a better experience\n> if we went a much simpler route.  In your top-level project with two\n> submodules, you may have core.commentchar set to auto in the top-level\n> and only one of the submodules, and then you let \"git\" go recursive.\n> Wouldn't it be simpler for the user to diagnose which one(s) among\n> the three repositories need fixing, if the stderr said something\n> like:\n> \n>      doing X\n>      warning core.commentChar is set to auto\n>      going into submodule A\n>        doing X\n>      going into submodule B\n>        doing X\n>        warning core.commentString is set to auto\n> \n> I dunno.\n\nThere is definitely a trade off here. The motivation was to stop the \nsequencer printing the same warning every time it forked \"git commit\" \nand to stop each sumbodule warning about config set in the global config \nfile. As you say the downside is that it hides warnings from a \nsubmodule's local config. I did wonder about somehow tracking the local \nconfig separately to the global and system config when setting the \nenvironment variable but it felt like it would get quite complicated \ntracking which .git/config we'd already warned about. We'd need to be \nclear about which repository the user needed to run 'git config' in as \nwell which adds to the complications.\n\n>> +static void check_deprecated_config(struct repo_config *config)\n>> +{\n>> +\tif (!config->repo->check_deprecated_config)\n>> +\t\t\treturn;\n>> +\n>> +\tcheck_auto_comment_char_config(&config->comment_char_config);\n> \n> The handling of .check_deprecated_config flag is a bit tricky, and\n> it is great that this design allows us to write a similar\n> check_foo_config() helper and make a call to it here, without\n> having to worry about it again.\n\nThanks\n\nPhillip\n\n"},{"id":"523205","messageId":"aIzQ4tw8Xp7upyo3@ugly","threadId":"63762","inReplyTo":"8b57598042642dd0c56e39be03c1c45a62accfb0.1753975294.git.phillip.wood@dunelm.org.uk","subject":"Re: [PATCH v2 2/3] config: warn on core.commentString=auto","fromName":"Oswald Buddenhagen","fromEmail":"oswald.buddenhagen@gmx.de","sentAt":"2025-08-01T14:36:18Z","receivedAt":"2025-08-01T14:36:20Z","isPatch":true,"sender":{"key":"oswald.buddenhagen@gmx.de","avatar":"https://avatars.githubusercontent.com/u/812380?v=4"},"body":"On Thu, Jul 31, 2025 at 04:21:54PM +0100, Phillip Wood wrote:\n>From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n>As support for this setting was deprecated in the last commit print a\n>warning (or die when WITH_BREAKING_CHANGES is enabled) if it is set.\n>When printing a warning avoid bombarding the user by only printing it\n\n>when running commands commands that run \"git commit\" and only only\n>\nsomething is very wrong with this line.\n\n>once per command. Some scaffolding is added to repo_read_config()\n>to allow it to detect deprecated config settings and warn about\n>them. As both \"core.commentChar\" and \"core.commentString\" set the\n>comment character we record which one of them is used and tailor the\n>warning message appropriately.\n>\n"},{"id":"523215","messageId":"aIzayan9nFZo4XYv@ugly","threadId":"63762","inReplyTo":"0e7c08b15e5923ae03f5630a8286c7dcebdbcfb9.1753975294.git.phillip.wood@dunelm.org.uk","subject":"Re: [PATCH v2 3/3] commit: print advice when core.commentString=auto","fromName":"Oswald Buddenhagen","fromEmail":"oswald.buddenhagen@gmx.de","sentAt":"2025-08-01T15:18:33Z","receivedAt":"2025-08-01T15:18:35Z","isPatch":true,"sender":{"key":"oswald.buddenhagen@gmx.de","avatar":"https://avatars.githubusercontent.com/u/812380?v=4"},"body":"On Thu, Jul 31, 2025 at 04:21:55PM +0100, Phillip Wood wrote:\n>An alternative\n>approach would be to advise the user to run \"git config --show-origin\"\n>and leave them to figure out how to fix it themselves but that seems\n>rather unfriendly. As we're forcing them to update their config we\n>should try and make that as easy as possible.\n>\nyour approach certainly helps the user to fix their acute problem \nquickly, but\n- why should it? it's not like leaving it to the user would cause them a \n   huge burden, or that a noteworthy number of users are even going to be \n   affected. i don't think the fact that the update is forced justifies \n   making it a lot more user friendly than git configuration usually is, \n   esp. at this cost in complexity.\n- i don't think i'd appreciate the tool lecturing me about trivial usage \n   patterns, when the real question in that situation is why the option \n   was set like that in the first place and whether/how the replacement \n   is actually equivalent or even superior.\n- given that it doesn't print the entire decision tree (when \n   encountering read-only files), it doesn't necessarily guide the user \n   towards the best overall solution. that makes it _less_ user-friendly, \n   in a way.\n"},{"id":"523224","messageId":"xmqqv7n7m1ur.fsf@gitster.g","threadId":"63762","inReplyTo":"a9ddf6cd-28c5-4030-9e63-80df191f1397@gmail.com","subject":"Re: [PATCH v2 0/3] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T16:41:00Z","receivedAt":"2025-08-01T16:41:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> I guess I used to carry an equivalent as a recurrent merge-fix for\n>> your topic branch, but rolling it into the base of the series is\n>> certainly safer (i.e. we have to do a merge and resolve conflicts\n>> just once, and after that we won't even touch it---as opposed to\n>> keep recreating the same conflict and resolving every time we merge\n>> your topic via rerere & merge-fix mechanism).\n>\n> I was hoping that rebasing on master would eliminate the need for a\n> fix as 'ac/auto-comment-char-fix' is now in master but in the meantime\n> 'ps/config-wo-the-repository' came along and moved code from config.c\n> to environment.c without those changes. I'd assumed you already had a\n> similar fixup when merging 'ps/config-wo-the-repository' into seen.\n\nExactly.  That is what I just said, in case it was unclear.\n"},{"id":"523231","messageId":"xmqqpldfklhz.fsf@gitster.g","threadId":"63762","inReplyTo":"aIzayan9nFZo4XYv@ugly","subject":"Re: [PATCH v2 3/3] commit: print advice when core.commentString=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-01T17:19:36Z","receivedAt":"2025-08-01T17:19:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Oswald Buddenhagen <oswald.buddenhagen@gmx.de> writes:\n\n> On Thu, Jul 31, 2025 at 04:21:55PM +0100, Phillip Wood wrote:\n>>An alternative\n>>approach would be to advise the user to run \"git config --show-origin\"\n>>and leave them to figure out how to fix it themselves but that seems\n>>rather unfriendly. As we're forcing them to update their config we\n>>should try and make that as easy as possible.\n>>\n> your approach certainly helps the user to fix their acute problem\n> quickly, but\n> - why should it? it's not like leaving it to the user would cause them\n>   a    huge burden, or that a noteworthy number of users are even\n>   going to be    affected. i don't think the fact that the update is\n>   forced justifies    making it a lot more user friendly than git\n>   configuration usually is,    esp. at this cost in complexity.\n\nI tend to agree that I prefer a simpler code that leaves a simple\nexception handling to the users ;-)\n\n> - given that it doesn't print the entire decision tree (when\n>   encountering read-only files), it doesn't necessarily guide the user\n>   towards the best overall solution. that makes it _less_\n>   user-friendly,    in a way.\n\nEven though we often do not like it, majority of users prefer to be\ntold what to do without having to think, so it is acceptable as long\nas the suggestion does not take them in a direction that would hurt\nthem, even if it were not optimal.\n"},{"id":"524947","messageId":"af0c22b9-5034-4bbd-9cdd-f1f16d933e4d@gmail.com","threadId":"63762","inReplyTo":"aIzayan9nFZo4XYv@ugly","subject":"Re: [PATCH v2 3/3] commit: print advice when core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-26T13:33:10Z","receivedAt":"2025-08-26T13:33:13Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 01/08/2025 16:18, Oswald Buddenhagen wrote:\n> On Thu, Jul 31, 2025 at 04:21:55PM +0100, Phillip Wood wrote:\n>> An alternative\n>> approach would be to advise the user to run \"git config --show-origin\"\n>> and leave them to figure out how to fix it themselves but that seems\n>> rather unfriendly. As we're forcing them to update their config we\n>> should try and make that as easy as possible.\n>>\n> your approach certainly helps the user to fix their acute problem \n> quickly, but\n> - why should it? it's not like leaving it to the user would cause them a \n>    huge burden, or that a noteworthy number of users are even going to \n> be   affected. i don't think the fact that the update is forced \n> justifies   making it a lot more user friendly than git configuration \n> usually is,   esp. at this cost in complexity.\n\nI think the fact that we're forcing the user to update does matter \nbecause it means they're having to update their config when they \notherwise would not have to. I'd much rather it gave me a suggestion on \nhow to proceed rather than told me to check my config and figure out \nwhat to do. There is certainly a complexity cost but I don't think it is \nthat high. Some of git's reputation for being hard to use is well earned \nand I don't want to add to that.\n\n> - i don't think i'd appreciate the tool lecturing me about trivial usage \n>    patterns, when the real question in that situation is why the option \n>    was set like that in the first place and whether/how the replacement \n>    is actually equivalent or even superior.\n\nI don't think offering a suggestion is \"lecturing about trivial usage \npatterns\", I see it as offering assistance to users. The reason the \nadvice offers two suggestions is because we cannot second guess whether \nthe user wants to use the default or set a fixed string - it is up to \nthem to decide.\n\n> - given that it doesn't print the entire decision tree (when   \n> encountering read-only files), it doesn't necessarily guide the user   \n> towards the best overall solution. that makes it _less_ user-friendly,   \n> in a way.\n\nIt provides a reasonable way of updating the config that we know will \nwork when a user does not have write access to the system config. More \nexperienced users are of course free to update their config as they see fit.\n\nThanks\n\nPhillip\n\n"},{"id":"524948","messageId":"cover.1756215326.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1751983009.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v3 0/3] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-26T13:35:25Z","receivedAt":"2025-08-26T13:35:49Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThanks to Junio and Oswald for their comments on V2.\nThis series implements the plan to deprecate and remove support for\ncore.commentChar=auto outlined in [1]. This feature has been the\nsource of a couple of bug reports recently [2,3] and it is hard to\nsee how the design can be fixed as it is incompatible with preparing\na commit message template containing comments. When git sees the\ndeprecated config setting it will print advice based on the user's\nconfig setting to help the user either remove the setting or set a\ncustom comment string. In the example below core.commentString is set\nmultiple times in $XDG_CONFIG_HOME/git/config and core.commentChar\nis set in ~/.gitconfig and $XDG_CONFIG_HOME/git/config.\n\nwarning: Support for 'core.commentChar=auto' is deprecated and will be removed in Git 3.0\nhint:\nhint: To use the default comment string (#) please run\nhint:\nhint:     git config unset --file ~/.config/git/config --all core.commentString\nhint:     git config unset --file ~/.config/git/config core.commentChar\nhint:     git config unset --global core.commentChar\nhint:\nhint: To set a custom comment string please run\nhint:\nhint:     git config set --global core.commentChar <comment string>\nhint:\nhint: where '<comment string>' is the string you wish to use.\n\n[1] https://lore.kernel.org/git/6a3154e0-e7bc-45ae-b554-67ccab18727a@gmail.com\n[2] https://lore.kernel.org/git/20250315140913.577404-1-oswald.buddenhagen@gmx.de\n[3] https://lore.kernel.org/git/20250626132233.414789-1-ayu.chandekar@gmail.com\n\nChanges since V2:\n - Patch 1: Punctuation fixes\n - Patch 2: Reworded the commit message slightly\n            Remove unnecessary include of advice.h\n            Fix variable declaration\n - Patch 3: Include advice.h\n\nChanges since V1:\n - Rebased onto a merge of 'ps/config-wo-the-repository' and 'master'\n - Reworded commit messages\n - What was patch 2 has been split into two separate patches and\n   reworked to die when core.commentChar=auto and WITH_BREAKING_CHANGES\n   is enabled.\n\nBase-Commit: 1ae5bd276bdf101e37c1a8f2904a2eae05fbb744\nPublished-As: https://github.com/phillipwood/git/releases/tag/pw%2Fremove-auto-comment-char%2Fv3\nView-Changes-At: https://github.com/phillipwood/git/compare/1ae5bd276...ee6cf11a8\nFetch-It-Via: git fetch https://github.com/phillipwood/git pw/remove-auto-comment-char/v3\n\n\nPhillip Wood (3):\n  breaking-changes: deprecate support for core.commentString=auto\n  config: warn on core.commentString=auto\n  commit: print advice when core.commentString=auto\n\n Documentation/BreakingChanges.adoc |   5 +\n Documentation/config/core.adoc     |  20 +-\n builtin/commit.c                   |   7 +\n builtin/merge.c                    |   3 +\n builtin/rebase.c                   |   3 +\n builtin/revert.c                   |   7 +\n config.c                           | 297 ++++++++++++++++++++++++++++-\n environment.c                      |  11 +-\n environment.h                      |   3 +\n repository.c                       |   1 +\n repository.h                       |   3 +\n t/t3404-rebase-interactive.sh      |  19 +-\n t/t3418-rebase-continue.sh         |   2 +-\n t/t7502-commit-porcelain.sh        |  52 ++++-\n 14 files changed, 421 insertions(+), 12 deletions(-)\n\nRange-diff against v2:\n1:  a6355451d4b ! 1:  5b921064f1e breaking-changes: deprecate support for core.commentString=auto\n    @@ Commit message\n         automatically select the comment character ensuring that it is not the\n         first character on any of the lines in the commit message. This was\n         introduced by commit 84c9dc2c5a2 (commit: allow core.commentChar=auto\n    -    for character auto selection, 2014-05-17) The motivation seems to be\n    +    for character auto selection, 2014-05-17). The motivation seems to be\n         to avoid commenting out lines from the existing message when amending\n         a commit that was created with a message from a file.\n     \n    @@ Commit message\n         consequence of the design of this feature and are therefore hard to\n         fix.\n     \n    -    As the costs of this feature outweigh the benefits deprecate it and\n    +    As the costs of this feature outweigh the benefits, deprecate it and\n         remove it in Git 3.0. If someone comes up with some patches that fix\n         all the issues in a maintainable way then I'd be happy to see this\n         change reverted.\n2:  8b575980426 ! 2:  5dd897c95e6 config: warn on core.commentString=auto\n    @@ Commit message\n     \n         As support for this setting was deprecated in the last commit print a\n         warning (or die when WITH_BREAKING_CHANGES is enabled) if it is set.\n    -    When printing a warning avoid bombarding the user by only printing it\n    -    when running commands commands that run \"git commit\" and only only\n    -    once per command. Some scaffolding is added to repo_read_config()\n    -    to allow it to detect deprecated config settings and warn about\n    -    them. As both \"core.commentChar\" and \"core.commentString\" set the\n    -    comment character we record which one of them is used and tailor the\n    -    warning message appropriately.\n    +    Avoid bombarding the user with warnings by only printing it (a) when\n    +    running commands commands that call \"git commit\" and (b) only once\n    +    per command. Some scaffolding is added to repo_read_config() to allow\n    +    it to detect deprecated config settings and warn about them. As both\n    +    \"core.commentChar\" and \"core.commentString\" set the comment character\n    +    we record which one of them is used and tailor the warning message\n    +    appropriately.\n     \n         Note the odd combination of die_message() followed by die(NULL)\n         is to allow the next commit to insert a call to advise() in the middle.\n    @@ builtin/revert.c: struct repository *repo UNUSED)\n     \n      ## config.c ##\n     @@\n    - \n    - #include \"git-compat-util.h\"\n    - #include \"abspath.h\"\n    -+#include \"advice.h\"\n      #include \"date.h\"\n      #include \"branch.h\"\n      #include \"config.h\"\n    @@ config.c: int git_configset_get_pathname(struct config_set *set, const char *key\n     +\n     +#define COMMENT_CHAR_CFG_INIT { 0 }\n     +\n    -+static const char* comment_key_name(unsigned id)\n    ++static const char *comment_key_name(unsigned id)\n     +{\n     +\tstatic const char *name[] = {\n     +\t\t\"core.commentChar\",\n3:  0e7c08b15e5 ! 3:  ee6cf11a82c commit: print advice when core.commentString=auto\n    @@ Commit message\n         Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n     \n      ## config.c ##\n    +@@\n    + \n    + #include \"git-compat-util.h\"\n    + #include \"abspath.h\"\n    ++#include \"advice.h\"\n    + #include \"date.h\"\n    + #include \"branch.h\"\n    + #include \"config.h\"\n     @@ config.c: int git_configset_get_pathname(struct config_set *set, const char *key, char **d\n      struct comment_char_config {\n      \tunsigned last_key_id;\n    @@ config.c: int git_configset_get_pathname(struct config_set *set, const char *key\n     +\treturn (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id);\n     +}\n      \n    - static const char* comment_key_name(unsigned id)\n    + static const char *comment_key_name(unsigned id)\n      {\n    -@@ config.c: static const char* comment_key_name(unsigned id)\n    +@@ config.c: static const char *comment_key_name(unsigned id)\n      }\n      \n      static void comment_char_callback(const char *key, const char *value,\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"524949","messageId":"5b921064f1e6c06ec2c150b1cce9d2b2b87a74a1.1756215326.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1756215326.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v3 1/3] breaking-changes: deprecate support for core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-26T13:35:26Z","receivedAt":"2025-08-26T13:35:51Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen \"core.commentString\" is set to \"auto\" then \"git commit\" will\nautomatically select the comment character ensuring that it is not the\nfirst character on any of the lines in the commit message. This was\nintroduced by commit 84c9dc2c5a2 (commit: allow core.commentChar=auto\nfor character auto selection, 2014-05-17). The motivation seems to be\nto avoid commenting out lines from the existing message when amending\na commit that was created with a message from a file.\n\nUnfortunately this feature does not work with:\n\n * commit message templates that contain comments.\n\n * prepare-commit-msg hooks that introduce comments.\n\n * \"git commit --cleanup=strip --edit -F <file>\" which means that it\n   is incompatible with\n\n   - the \"fixup\" and \"squash\" commands of \"git rebase -i\" as the\n     comments added by those commands are then treated as part of\n     the commit message.\n\n   - the conflict comments added to the commit message by \"git\n     cherry-pick\", \"git rebase\" etc. as these comments are then\n     treated as part of the commit message.\n\nIt is also ignored by \"git notes\" when amending a note.\n\nThe issues with comments coming from a template, hook or file are a\nconsequence of the design of this feature and are therefore hard to\nfix.\n\nAs the costs of this feature outweigh the benefits, deprecate it and\nremove it in Git 3.0. If someone comes up with some patches that fix\nall the issues in a maintainable way then I'd be happy to see this\nchange reverted.\n\nThe next commits will add a warning and some advice for users on how\nthey can update their config settings.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n Documentation/BreakingChanges.adoc |  5 +++++\n Documentation/config/core.adoc     | 20 ++++++++++++++++++--\n builtin/commit.c                   |  4 ++++\n environment.c                      | 10 ++++++++--\n environment.h                      |  2 ++\n t/t3404-rebase-interactive.sh      |  2 +-\n t/t3418-rebase-continue.sh         |  2 +-\n t/t7502-commit-porcelain.sh        |  4 ++--\n 8 files changed, 41 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc\nindex f8d2eba061c..344ce500603 100644\n--- a/Documentation/BreakingChanges.adoc\n+++ b/Documentation/BreakingChanges.adoc\n@@ -239,6 +239,11 @@ These features will be removed.\n +\n The command will be removed.\n \n+* Support for `core.commentString=auto` has been deprecated and will\n+  be removed in Git 3.0.\n++\n+cf. <xmqqa59i45wc.fsf@gitster.g>\n+\n == Superseded features that will not be deprecated\n \n Some features have gained newer replacements that aim to improve the design in\ndiff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc\nindex 9fde1ab63a7..7133f00c38b 100644\n--- a/Documentation/config/core.adoc\n+++ b/Documentation/config/core.adoc\n@@ -531,9 +531,25 @@ core.commentString::\n \tcommented, and removes them after the editor returns\n \t(default '#').\n +\n-If set to \"auto\", `git-commit` would select a character that is not\n+ifndef::with-breaking-changes[]\n+If set to \"auto\", `git-commit` will select a character that is not\n the beginning character of any line in existing commit messages.\n-+\n+Support for this value is deprecated and will be removed in Git 3.0\n+due to the following limitations:\n++\n+--\n+* It is incompatible with adding comments in a commit message\n+  template. This includes the conflicts comments added to\n+  the commit message by `cherry-pick`, `merge`, `rebase` and\n+  `revert`.\n+* It is incompatible with adding comments to the commit message\n+  in the `prepare-commit-msg` hook.\n+* It is incompatible with the `fixup` and `squash` commands when\n+  rebasing,\n+* It is not respected by `git notes`\n+--\n++\n+endif::with-breaking-changes[]\n Note that these two variables are aliases of each other, and in modern\n versions of Git you are free to use a string (e.g., `//` or `⁑⁕⁑`) with\n `commentChar`. Versions of Git prior to v2.45.0 will ignore\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 757f51eac82..d25cc07a355 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -683,6 +683,7 @@ static int author_date_is_interesting(void)\n \treturn author_message || force_date;\n }\n \n+#ifndef WITH_BREAKING_CHANGES\n static void adjust_comment_line_char(const struct strbuf *sb)\n {\n \tchar candidates[] = \"#;@!$%^&|:\";\n@@ -720,6 +721,7 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tfree(comment_line_str_to_free);\n \tcomment_line_str = comment_line_str_to_free = xstrfmt(\"%c\", *p);\n }\n+#endif /* !WITH_BREAKING_CHANGES */\n \n static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\n \t\t\t\tstruct pretty_print_context *ctx)\n@@ -916,8 +918,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tif (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)\n \t\tdie_errno(_(\"could not write commit template\"));\n \n+#ifndef WITH_BREAKING_CHANGES\n \tif (auto_comment_line_char)\n \t\tadjust_comment_line_char(&sb);\n+#endif /* !WITH_BREAKING_CHANGES */\n \tstrbuf_release(&sb);\n \n \t/* This checks if committer ident is explicitly given */\ndiff --git a/environment.c b/environment.c\nindex a0ac5934b37..4c87876d483 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -122,7 +122,9 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n  */\n const char *comment_line_str = \"#\";\n char *comment_line_str_to_free;\n+#ifndef WITH_BREAKING_CHANGES\n int auto_comment_line_char;\n+#endif /* !WITH_BREAKING_CHANGES */\n \n /* This is set by setup_git_directory_gently() and/or git_default_config() */\n char *git_work_tree_cfg;\n@@ -459,18 +461,22 @@ static int git_default_core_config(const char *var, const char *value,\n \n \tif (!strcmp(var, \"core.commentchar\") ||\n \t    !strcmp(var, \"core.commentstring\")) {\n-\t\tif (!value)\n+\t\tif (!value) {\n \t\t\treturn config_error_nonbool(var);\n-\t\telse if (!strcasecmp(value, \"auto\")) {\n+#ifndef WITH_BREAKING_CHANGES\n+\t\t} else if (!strcasecmp(value, \"auto\")) {\n \t\t\tauto_comment_line_char = 1;\n \t\t\tFREE_AND_NULL(comment_line_str_to_free);\n \t\t\tcomment_line_str = \"#\";\n+#endif /* !WITH_BREAKING_CHANGES */\n \t\t} else if (value[0]) {\n \t\t\tif (strchr(value, '\\n'))\n \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n \t\t\tcomment_line_str = value;\n \t\t\tFREE_AND_NULL(comment_line_str_to_free);\n+#ifndef WITH_BREAKING_CHANGES\n \t\t\tauto_comment_line_char = 0;\n+#endif /* !WITH_BREAKING_CHANGES */\n \t\t} else\n \t\t\treturn error(_(\"%s must have at least one character\"), var);\n \t\treturn 0;\ndiff --git a/environment.h b/environment.h\nindex 8cfce41015b..e75c4abb388 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -208,7 +208,9 @@ extern char *excludes_file;\n  */\n extern const char *comment_line_str;\n extern char *comment_line_str_to_free;\n+#ifndef WITH_BREAKING_CHANGES\n extern int auto_comment_line_char;\n+#endif /* !WITH_BREAKING_CHANGES */\n \n # endif /* USE_THE_REPOSITORY_VARIABLE */\n #endif /* ENVIRONMENT_H */\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 6bac217ed35..ce0aebb9a7e 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1176,7 +1176,7 @@ test_expect_success 'rebase -i respects core.commentchar' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n-test_expect_success 'rebase -i respects core.commentchar=auto' '\n+test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=auto' '\n \ttest_config core.commentchar auto &&\n \twrite_script copy-edit-script.sh <<-\\EOF &&\n \tcp \"$1\" edit-script\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex b8a8dd77e74..f9b8999db50 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -328,7 +328,7 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas\n \ttest_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec\n '\n \n-test_expect_success 'no change in comment character due to conflicts markers with core.commentChar=auto' '\n+test_expect_success !WITH_BREAKING_CHANGES 'no change in comment character due to conflicts markers with core.commentChar=auto' '\n \tgit checkout -b branch-a &&\n \ttest_commit A F1 &&\n \tgit checkout -b branch-b HEAD^ &&\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex b37e2018a74..65b4519a715 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -956,13 +956,13 @@ test_expect_success 'commit --status with custom comment character' '\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n-test_expect_success 'switch core.commentchar' '\n+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n \tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n-test_expect_success 'switch core.commentchar but out of options' '\n+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar but out of options' '\n \tcat >text <<\\EOF &&\n # 1\n ; 2\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"524950","messageId":"ee6cf11a82c96f166941b66a172b15f5ed308098.1756215326.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1756215326.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v3 3/3] commit: print advice when core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-26T13:35:28Z","receivedAt":"2025-08-26T13:35:52Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAdd some advice on how to change the config settings when\n\"core.commentString=auto\" or \"core.commentChar=auto\". The advice\nincludes instructions for clearing the config setting or setting a\nfixed comment string. To try and be as specific as possible, the advice\nis customized based on the user's config. If \"core.commentString=auto\"\nis set in the system config and the user does not have write\naccess then the advice omits the instructions to clear the config\nand recommends changing the global config instead. An alternative\napproach would be to advise the user to run \"git config --show-origin\"\nand leave them to figure out how to fix it themselves but that seems\nrather unfriendly. As we're forcing them to update their config we\nshould try and make that as easy as possible.\n\nIn order to generate this advice we need to record each file where\neither of the config keys is set and whether a key occurs more that\nonce in a given file. This lets us generate the list of commands to\nremove all the keys and also tells us which key the \"auto\" setting\ncomes from.\n\nAs we want the user to update their config we do not provide a way\nfor this advice to be disabled other than changing the value of\n\"core.commentChar\" or \"core.commentString\".\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n config.c                      | 196 ++++++++++++++++++++++++++++++++--\n t/t3404-rebase-interactive.sh |  12 ++-\n t/t7502-commit-porcelain.sh   |  37 ++++++-\n 3 files changed, 234 insertions(+), 11 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 18b42197095..18dcf341d58 100644\n--- a/config.c\n+++ b/config.c\n@@ -8,6 +8,7 @@\n \n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n+#include \"advice.h\"\n #include \"date.h\"\n #include \"branch.h\"\n #include \"config.h\"\n@@ -1955,9 +1956,51 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d\n struct comment_char_config {\n \tunsigned last_key_id;\n \tbool auto_set;\n+\tbool auto_set_in_file;\n+\tstruct strintmap key_flags;\n+\tsize_t alloc, nr;\n+\tstruct comment_char_config_item {\n+\t\tunsigned key_id;\n+\t\tchar *path;\n+\t\tenum config_scope scope;\n+\t} *item;\n };\n \n-#define COMMENT_CHAR_CFG_INIT { 0 }\n+#define COMMENT_CHAR_CFG_INIT {\t\t\t\\\n+\t\t.key_flags = STRINTMAP_INIT,\t\\\n+\t}\n+\n+static void comment_char_config_release(struct comment_char_config *config)\n+{\n+\tstrintmap_clear(&config->key_flags);\n+\tfor (size_t i = 0; i < config->nr; i++)\n+\t\tfree(config->item[i].path);\n+\tfree(config->item);\n+}\n+\n+/* Used to track whether the key occurs more than once in a given file */\n+#define KEY_SEEN_ONCE 1u\n+#define KEY_SEEN_TWICE 2u\n+#define COMMENT_KEY_SHIFT(id) (2 * (id))\n+#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id))\n+\n+static void set_comment_key_flags(struct comment_char_config *config,\n+\t\t\t\t  const char *path, unsigned id, unsigned value)\n+{\n+\tunsigned old = strintmap_get(&config->key_flags, path);\n+\tunsigned new = (old & ~COMMENT_KEY_MASK(id)) |\n+\t\t\t\tvalue << COMMENT_KEY_SHIFT(id);\n+\n+\tstrintmap_set(&config->key_flags, path, new);\n+}\n+\n+static unsigned get_comment_key_flags(struct comment_char_config *config,\n+\t\t\t\t      const char *path, unsigned id)\n+{\n+\tunsigned value = strintmap_get(&config->key_flags, path);\n+\n+\treturn (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id);\n+}\n \n static const char *comment_key_name(unsigned id)\n {\n@@ -1973,10 +2016,10 @@ static const char *comment_key_name(unsigned id)\n }\n \n static void comment_char_callback(const char *key, const char *value,\n-\t\t\t\t  const struct config_context *ctx UNUSED,\n-\t\t\t\t  void *data)\n+\t\t\t\t  const struct config_context *ctx, void *data)\n {\n \tstruct comment_char_config *config = data;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \tunsigned key_id;\n \n \tif (!strcmp(key, \"core.commentchar\"))\n@@ -1988,7 +2031,135 @@ static void comment_char_callback(const char *key, const char *value,\n \n \tconfig->last_key_id = key_id;\n \tconfig->auto_set = value && !strcmp(value, \"auto\");\n-}\n+\tif (kvi->origin_type != CONFIG_ORIGIN_FILE) {\n+\t\treturn;\n+\t} else if (get_comment_key_flags(config, kvi->filename, key_id)) {\n+\t\tset_comment_key_flags(config, kvi->filename, key_id,\n+\t\t\t\t      KEY_SEEN_TWICE);\n+\t} else {\n+\t\tstruct comment_char_config_item *item;\n+\n+\t\tALLOC_GROW_BY(config->item, config->nr, 1, config->alloc);\n+\t\titem = &config->item[config->nr - 1];\n+\t\titem->key_id = key_id;\n+\t\titem->scope = kvi->scope;\n+\t\titem->path = xstrdup(kvi->filename);\n+\t\tset_comment_key_flags(config, kvi->filename, key_id,\n+\t\t\t\t      KEY_SEEN_ONCE);\n+\t}\n+\tconfig->auto_set_in_file = config->auto_set;\n+}\n+\n+static void add_config_scope_arg(struct repository *repo, struct strbuf *buf,\n+\t\t\t\t struct comment_char_config_item *item)\n+{\n+\tchar *global_config = git_global_config();\n+\tchar *system_config = git_system_config();\n+\n+\tif (item->scope == CONFIG_SCOPE_SYSTEM && access(item->path, W_OK)) {\n+\t\t/*\n+\t\t * If the user cannot write to the system config recommend\n+\t\t * setting the global config instead.\n+\t\t */\n+\t\tstrbuf_addstr(buf, \"--global \");\n+\t} else if (fspatheq(item->path, system_config)) {\n+\t\tstrbuf_addstr(buf, \"--system \");\n+\t} else if (fspatheq(item->path, global_config)) {\n+\t\tstrbuf_addstr(buf, \"--global \");\n+\t} else if (fspatheq(item->path,\n+\t\t\t    mkpath(\"%s/config\",\n+\t\t\t\t   repo_get_git_dir(repo)))) {\n+\t\t; /* --local is the default */\n+\t} else if (fspatheq(item->path,\n+\t\t\t    mkpath(\"%s/config.worktree\",\n+\t\t\t\t   repo_get_common_dir(repo)))) {\n+\t\tstrbuf_addstr(buf, \"--worktree \");\n+\t} else {\n+\t\tconst char *path = item->path;\n+\t\tconst char *home = getenv(\"HOME\");\n+\n+\t\tstrbuf_addstr(buf, \"--file \");\n+\t\tif (home && !fspathncmp(path, home, strlen(home))) {\n+\t\t\tpath += strlen(home);\n+\t\t\tif (!fspathncmp(path, \"/\", 1))\n+\t\t\t\tpath++;\n+\t\t\tstrbuf_addstr(buf, \"~/\");\n+\t\t}\n+\t\tsq_quote_buf_pretty(buf, path);\n+\t\tstrbuf_addch(buf, ' ');\n+\t}\n+\n+\tfree(global_config);\n+\tfree(system_config);\n+}\n+\n+static bool can_unset_comment_char_config(struct comment_char_config *config)\n+{\n+\tfor (size_t i = 0; i < config->nr; i++) {\n+\t\tstruct comment_char_config_item *item = &config->item[i];\n+\n+\t\tif (item->scope == CONFIG_SCOPE_SYSTEM &&\n+\t\t    access(item->path, W_OK))\n+\t\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+static void add_unset_auto_comment_char_advice(struct repository *repo,\n+\t\t\t\t\t       struct comment_char_config *config)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (!can_unset_comment_char_config(config))\n+\t\treturn;\n+\n+\tfor (size_t i = 0; i < config->nr; i++) {\n+\t\tstruct comment_char_config_item *item = &config->item[i];\n+\n+\t\tstrbuf_addstr(&buf, \"    git config unset \");\n+\t\tadd_config_scope_arg(repo, &buf, item);\n+\t\tif (get_comment_key_flags(config, item->path, item->key_id) == KEY_SEEN_TWICE)\n+\t\t\tstrbuf_addstr(&buf, \"--all \");\n+\t\tstrbuf_addf(&buf, \"%s\\n\", comment_key_name(item->key_id));\n+\t}\n+\tadvise(_(\"\\nTo use the default comment string (#) please run\\n\\n%s\"),\n+\t       buf.buf);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void add_comment_char_advice(struct repository *repo,\n+\t\t\t\t    struct comment_char_config *config)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct comment_char_config_item *item;\n+\t/* TRANSLATORS this is a place holder for the value of core.commentString */\n+\tconst char *placeholder = _(\"<comment string>\");\n+\n+\t/*\n+\t * If auto is set in the last file that we saw advise the user how to\n+\t * update their config.\n+\t */\n+\tif (!config->auto_set_in_file)\n+\t\treturn;\n+\n+\tadd_unset_auto_comment_char_advice(repo, config);\n+\titem = &config->item[config->nr - 1];\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, \"    git config set \");\n+\tadd_config_scope_arg(repo, &buf, item);\n+\tstrbuf_addf(&buf, \"%s %s\\n\", comment_key_name(item->key_id),\n+\t\t    placeholder);\n+\tadvise(_(\"\\nTo set a custom comment string please run\\n\\n\"\n+\t\t \"%s\\nwhere '%s' is the string you wish to use.\\n\"),\n+\t       buf.buf, placeholder);\n+\tstrbuf_release(&buf);\n+}\n+\n+#undef KEY_SEEN_ONCE\n+#undef KEY_SEEN_TWICE\n+#undef COMMENT_KEY_SHIFT\n+#undef COMMENT_KEY_MASK\n \n struct repo_config {\n \tstruct repository *repo;\n@@ -2000,18 +2171,26 @@ struct repo_config {\n \t\t.repo = repo_,\t\t\t\t\t\\\n \t};\n \n+static void repo_config_release(struct repo_config *config)\n+{\n+\tcomment_char_config_release(&config->comment_char_config);\n+}\n+\n #ifdef WITH_BREAKING_CHANGES\n-static void check_auto_comment_char_config(struct comment_char_config *config)\n+static void check_auto_comment_char_config(struct repository *repo,\n+\t\t\t\t\t   struct comment_char_config *config)\n {\n \tif (!config->auto_set)\n \t\treturn;\n \n \tdie_message(_(\"Support for '%s=auto' has been removed in Git 3.0\"),\n \t\t    comment_key_name(config->last_key_id));\n+\tadd_comment_char_advice(repo, config);\n \tdie(NULL);\n }\n #else\n-static void check_auto_comment_char_config(struct comment_char_config *config)\n+static void check_auto_comment_char_config(struct repository *repo,\n+\t\t\t\t\t   struct comment_char_config *config)\n {\n \textern bool warn_on_auto_comment_char;\n \tconst char *DEPRECATED_CONFIG_ENV =\n@@ -2031,6 +2210,7 @@ static void check_auto_comment_char_config(struct comment_char_config *config)\n \n \twarning(_(\"Support for '%s=auto' is deprecated and will be removed in \"\n \t\t  \"Git 3.0\"), comment_key_name(config->last_key_id));\n+\tadd_comment_char_advice(repo, config);\n }\n #endif /* WITH_BREAKING_CHANGES */\n \n@@ -2039,7 +2219,8 @@ static void check_deprecated_config(struct repo_config *config)\n \tif (!config->repo->check_deprecated_config)\n \t\t\treturn;\n \n-\tcheck_auto_comment_char_config(&config->comment_char_config);\n+\tcheck_auto_comment_char_config(config->repo,\n+\t\t\t\t       &config->comment_char_config);\n }\n \n static int repo_config_callback(const char *key, const char *value,\n@@ -2082,6 +2263,7 @@ static void repo_read_config(struct repository *repo)\n \t\t */\n \t\tdie(_(\"unknown error occurred while reading the configuration files\"));\n \tcheck_deprecated_config(&config);\n+\trepo_config_release(&config);\n }\n \n static void git_config_check_init(struct repository *repo)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 3b2a46c25ce..cc97628d810 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1186,9 +1186,19 @@ test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=\n \t\ttest_set_editor \"$(pwd)/copy-edit-script.sh\" &&\n \t\tgit rebase -i HEAD^ 2>err\n \t) &&\n-\tsed -n \"s/^warning: //p\" err >actual &&\n+\tsed -n \"s/^hint: *\\$//p; s/^hint: //p; s/^warning: //p\" err >actual &&\n \tcat >expect <<-EOF &&\n \tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n \tEOF\n \ttest_cmp expect actual &&\n \ttest -z \"$(grep -ve \"^#\" -e \"^\\$\" -e \"^pick\" edit-script)\"\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex a9dc1e416d1..05f6da4ad98 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -958,10 +958,31 @@ test_expect_success 'commit --status with custom comment character' '\n \n test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n-\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend 2>err &&\n-\tsed -n \"s/^warning: //p\" err >actual &&\n+\tcat >config-include <<-\\EOF &&\n+\t[core]\n+\t    commentString=:\n+\t    commentString=%\n+\t    commentChar=auto\n+\tEOF\n+\ttest_when_finished \"rm config-include\" &&\n+\ttest_config include.path \"$(pwd)/config-include\" &&\n+\ttest_config core.commentChar ! &&\n+\tGIT_EDITOR=.git/FAKE_EDITOR git commit --amend 2>err &&\n+\tsed -n \"s/^hint: *\\$//p; s/^hint: //p; s/^warning: //p\" err >actual &&\n \tcat >expect <<-EOF &&\n \tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\t    git config unset --file ~/config-include --all core.commentString\n+\t    git config unset --file ~/config-include core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set --file ~/config-include core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n \tEOF\n \ttest_cmp expect actual &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n@@ -990,9 +1011,19 @@ EOF\n test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '\n \ttest_config core.commentChar auto &&\n \ttest_must_fail git rev-parse --git-dir 2>err &&\n-\tsed -n \"s/^fatal: //p\" err >actual &&\n+\tsed -n \"s/^hint: *\\$//p; s/^hint: //p; s/^fatal: //p\" err >actual &&\n \tcat >expect <<-EOF &&\n \tSupport for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n \tEOF\n \ttest_cmp expect actual\n '\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"524951","messageId":"5dd897c95e680c4e3f26ec3945fe649b4b61681a.1756215326.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1756215326.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v3 2/3] config: warn on core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-26T13:35:27Z","receivedAt":"2025-08-26T13:35:52Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAs support for this setting was deprecated in the last commit print a\nwarning (or die when WITH_BREAKING_CHANGES is enabled) if it is set.\nAvoid bombarding the user with warnings by only printing it (a) when\nrunning commands commands that call \"git commit\" and (b) only once\nper command. Some scaffolding is added to repo_read_config() to allow\nit to detect deprecated config settings and warn about them. As both\n\"core.commentChar\" and \"core.commentString\" set the comment character\nwe record which one of them is used and tailor the warning message\nappropriately.\n\nNote the odd combination of die_message() followed by die(NULL)\nis to allow the next commit to insert a call to advise() in the middle.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/commit.c              |   3 +\n builtin/merge.c               |   3 +\n builtin/rebase.c              |   3 +\n builtin/revert.c              |   7 +++\n config.c                      | 115 +++++++++++++++++++++++++++++++++-\n environment.c                 |   1 +\n environment.h                 |   1 +\n repository.c                  |   1 +\n repository.h                  |   3 +\n t/t3404-rebase-interactive.sh |   7 ++-\n t/t7502-commit-porcelain.sh   |  17 ++++-\n 11 files changed, 157 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex d25cc07a355..f821fdcfcc3 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1783,6 +1783,9 @@ int cmd_commit(int argc,\n \tshow_usage_with_options_if_asked(argc, argv,\n \t\t\t\t\t builtin_commit_usage, builtin_commit_options);\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex dc4cb8fb14d..794cb7bb269 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1378,6 +1378,9 @@ int cmd_merge(int argc,\n \tshow_usage_with_options_if_asked(argc, argv,\n \t\t\t\t\t builtin_merge_usage, builtin_merge_options);\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 72a52bdfb98..962917ec480 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1242,6 +1242,9 @@ int cmd_rebase(int argc,\n \t\t\t\t\t builtin_rebase_usage,\n \t\t\t\t\t builtin_rebase_options);\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex e07c2217fe8..b197848bb0a 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -4,6 +4,7 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"diff.h\"\n+#include \"environment.h\"\n #include \"gettext.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n@@ -285,6 +286,9 @@ int cmd_revert(int argc,\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tint res;\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \topts.action = REPLAY_REVERT;\n \tsequencer_init_config(&opts);\n \tres = run_sequencer(argc, argv, prefix, &opts);\n@@ -302,6 +306,9 @@ struct repository *repo UNUSED)\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tint res;\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \topts.action = REPLAY_PICK;\n \tsequencer_init_config(&opts);\n \tres = run_sequencer(argc, argv, prefix, &opts);\ndiff --git a/config.c b/config.c\nindex 97ffef42700..18b42197095 100644\n--- a/config.c\n+++ b/config.c\n@@ -11,6 +11,7 @@\n #include \"date.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"dir.h\"\n #include \"parse.h\"\n #include \"convert.h\"\n #include \"environment.h\"\n@@ -1951,10 +1952,110 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d\n \t\treturn 1;\n }\n \n+struct comment_char_config {\n+\tunsigned last_key_id;\n+\tbool auto_set;\n+};\n+\n+#define COMMENT_CHAR_CFG_INIT { 0 }\n+\n+static const char *comment_key_name(unsigned id)\n+{\n+\tstatic const char *name[] = {\n+\t\t\"core.commentChar\",\n+\t\t\"core.commentString\",\n+\t};\n+\n+\tif (id >= ARRAY_SIZE(name))\n+\t\tBUG(\"invalid comment key id\");\n+\n+\treturn name[id];\n+}\n+\n+static void comment_char_callback(const char *key, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *data)\n+{\n+\tstruct comment_char_config *config = data;\n+\tunsigned key_id;\n+\n+\tif (!strcmp(key, \"core.commentchar\"))\n+\t\tkey_id = 0;\n+\telse if (!strcmp(key, \"core.commentstring\"))\n+\t\tkey_id = 1;\n+\telse\n+\t\treturn;\n+\n+\tconfig->last_key_id = key_id;\n+\tconfig->auto_set = value && !strcmp(value, \"auto\");\n+}\n+\n+struct repo_config {\n+\tstruct repository *repo;\n+\tstruct comment_char_config comment_char_config;\n+};\n+\n+#define REPO_CONFIG_INIT(repo_) {\t\t\t\t\\\n+\t\t.comment_char_config = COMMENT_CHAR_CFG_INIT,\t\\\n+\t\t.repo = repo_,\t\t\t\t\t\\\n+\t};\n+\n+#ifdef WITH_BREAKING_CHANGES\n+static void check_auto_comment_char_config(struct comment_char_config *config)\n+{\n+\tif (!config->auto_set)\n+\t\treturn;\n+\n+\tdie_message(_(\"Support for '%s=auto' has been removed in Git 3.0\"),\n+\t\t    comment_key_name(config->last_key_id));\n+\tdie(NULL);\n+}\n+#else\n+static void check_auto_comment_char_config(struct comment_char_config *config)\n+{\n+\textern bool warn_on_auto_comment_char;\n+\tconst char *DEPRECATED_CONFIG_ENV =\n+\t\t\t\t\"GIT_AUTO_COMMENT_CHAR_CONFIG_WARNING_GIVEN\";\n+\n+\tif (!config->auto_set || !warn_on_auto_comment_char)\n+\t\treturn;\n+\n+\t/*\n+\t * Use an environment variable to ensure that subprocesses do not repeat\n+\t * the warning.\n+\t */\n+\tif (git_env_bool(DEPRECATED_CONFIG_ENV, false))\n+\t\treturn;\n+\n+\tsetenv(DEPRECATED_CONFIG_ENV, \"true\", true);\n+\n+\twarning(_(\"Support for '%s=auto' is deprecated and will be removed in \"\n+\t\t  \"Git 3.0\"), comment_key_name(config->last_key_id));\n+}\n+#endif /* WITH_BREAKING_CHANGES */\n+\n+static void check_deprecated_config(struct repo_config *config)\n+{\n+\tif (!config->repo->check_deprecated_config)\n+\t\t\treturn;\n+\n+\tcheck_auto_comment_char_config(&config->comment_char_config);\n+}\n+\n+static int repo_config_callback(const char *key, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *data)\n+{\n+\tstruct repo_config *config = data;\n+\n+\tcomment_char_callback(key, value, ctx, &config->comment_char_config);\n+\treturn config_set_callback(key, value, ctx, config->repo->config);\n+}\n+\n /* Functions use to read configuration from a repository */\n static void repo_read_config(struct repository *repo)\n {\n \tstruct config_options opts = { 0 };\n+\tstruct repo_config config = REPO_CONFIG_INIT(repo);\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -1966,8 +2067,8 @@ static void repo_read_config(struct repository *repo)\n \t\tgit_configset_clear(repo->config);\n \n \tgit_configset_init(repo->config);\n-\tif (config_with_options(config_set_callback, repo->config, NULL,\n-\t\t\t\trepo, &opts) < 0)\n+\tif (config_with_options(repo_config_callback, &config, NULL, repo,\n+\t\t\t\t&opts) < 0)\n \t\t/*\n \t\t * config_with_options() normally returns only\n \t\t * zero, as most errors are fatal, and\n@@ -1980,6 +2081,7 @@ static void repo_read_config(struct repository *repo)\n \t\t * immediately.\n \t\t */\n \t\tdie(_(\"unknown error occurred while reading the configuration files\"));\n+\tcheck_deprecated_config(&config);\n }\n \n static void git_config_check_init(struct repository *repo)\n@@ -2667,6 +2769,14 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,\n \tchar *contents = NULL;\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n+\tbool saved_check_deprecated_config = r->check_deprecated_config;\n+\n+\t/*\n+\t * Do not warn or die if there are deprecated config settings as\n+\t * we want the user to be able to change those settings by running\n+\t * \"git config\".\n+\t */\n+\tr->check_deprecated_config = false;\n \n \tvalidate_comment_string(comment);\n \n@@ -2898,6 +3008,7 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,\n \tif (in_fd >= 0)\n \t\tclose(in_fd);\n \tconfig_store_data_clear(&store);\n+\tr->check_deprecated_config = saved_check_deprecated_config;\n \treturn ret;\n \n write_err_out:\ndiff --git a/environment.c b/environment.c\nindex 4c87876d483..1ffa2ff30b2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -124,6 +124,7 @@ const char *comment_line_str = \"#\";\n char *comment_line_str_to_free;\n #ifndef WITH_BREAKING_CHANGES\n int auto_comment_line_char;\n+bool warn_on_auto_comment_char;\n #endif /* !WITH_BREAKING_CHANGES */\n \n /* This is set by setup_git_directory_gently() and/or git_default_config() */\ndiff --git a/environment.h b/environment.h\nindex e75c4abb388..51898c99cd1 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -210,6 +210,7 @@ extern const char *comment_line_str;\n extern char *comment_line_str_to_free;\n #ifndef WITH_BREAKING_CHANGES\n extern int auto_comment_line_char;\n+extern bool warn_on_auto_comment_char;\n #endif /* !WITH_BREAKING_CHANGES */\n \n # endif /* USE_THE_REPOSITORY_VARIABLE */\ndiff --git a/repository.c b/repository.c\nindex ecd691181fc..8af73923d34 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -57,6 +57,7 @@ void initialize_repository(struct repository *repo)\n \trepo->parsed_objects = parsed_object_pool_new(repo);\n \tALLOC_ARRAY(repo->index, 1);\n \tindex_state_init(repo->index, repo);\n+\trepo->check_deprecated_config = true;\n \n \t/*\n \t * When a command runs inside a repository, it learns what\ndiff --git a/repository.h b/repository.h\nindex 042dc93f0f2..5808a5d6108 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -161,6 +161,9 @@ struct repository {\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\n \tunsigned different_commondir:1;\n+\n+\t/* Should repo_config() check for deprecated settings */\n+\tbool check_deprecated_config;\n };\n \n #ifdef USE_THE_REPOSITORY_VARIABLE\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ce0aebb9a7e..3b2a46c25ce 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1184,8 +1184,13 @@ test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=\n \ttest_when_finished \"git rebase --abort || :\" &&\n \t(\n \t\ttest_set_editor \"$(pwd)/copy-edit-script.sh\" &&\n-\t\tgit rebase -i HEAD^\n+\t\tgit rebase -i HEAD^ 2>err\n \t) &&\n+\tsed -n \"s/^warning: //p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\tEOF\n+\ttest_cmp expect actual &&\n \ttest -z \"$(grep -ve \"^#\" -e \"^\\$\" -e \"^pick\" edit-script)\"\n '\n \ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 65b4519a715..a9dc1e416d1 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -958,7 +958,12 @@ test_expect_success 'commit --status with custom comment character' '\n \n test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n-\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&\n+\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend 2>err &&\n+\tsed -n \"s/^warning: //p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\tEOF\n+\ttest_cmp expect actual &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n@@ -982,4 +987,14 @@ EOF\n \t)\n '\n \n+test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '\n+\ttest_config core.commentChar auto &&\n+\ttest_must_fail git rev-parse --git-dir 2>err &&\n+\tsed -n \"s/^fatal: //p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"524968","messageId":"xmqqldn6krmi.fsf@gitster.g","threadId":"63762","inReplyTo":"5dd897c95e680c4e3f26ec3945fe649b4b61681a.1756215326.git.phillip.wood@dunelm.org.uk","subject":"Re: [PATCH v3 2/3] config: warn on core.commentString=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-26T15:52:05Z","receivedAt":"2025-08-26T15:52:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> As support for this setting was deprecated in the last commit print a\n> warning (or die when WITH_BREAKING_CHANGES is enabled) if it is set.\n> Avoid bombarding the user with warnings by only printing it (a) when\n> running commands commands that call \"git commit\" and (b) only once\n\n\"commands commands\" -> \"commands\".\n\n> per command.\n"},{"id":"525026","messageId":"aK6_ivfXr7jIbBCz@ugly.lan","threadId":"63762","inReplyTo":"af0c22b9-5034-4bbd-9cdd-f1f16d933e4d@gmail.com","subject":"Re: [PATCH v2 3/3] commit: print advice when core.commentString=auto","fromName":"Oswald Buddenhagen","fromEmail":"oswald.buddenhagen@gmx.de","sentAt":"2025-08-27T08:19:22Z","receivedAt":"2025-08-27T08:19:24Z","isPatch":true,"sender":{"key":"oswald.buddenhagen@gmx.de","avatar":"https://avatars.githubusercontent.com/u/812380?v=4"},"body":"On Tue, Aug 26, 2025 at 02:33:10PM +0100, Phillip Wood wrote:\n>Some of git's reputation for being hard to use is well earned and I \n>don't want to add to that.\n>\ni would find that reasoning compelling if we weren't talking about a \ncase that is likely to affect only very few, and probably rather \nadvanced users (who somehow managed to make the feature actually useful \nfor them - others would have quickly reverted the setting, because it \nwould have gotten into their way).\n"},{"id":"525046","messageId":"cover.1756308431.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1751983009.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v4 0/3] breaking-changes: deprecate support for core.commentChar=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-27T15:27:27Z","receivedAt":"2025-08-27T15:27:46Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis series implements the plan to deprecate and remove support for\ncore.commentChar=auto outlined in [1]. This feature has been the\nsource of a couple of bug reports recently [2,3] and it is hard to\nsee how the design can be fixed as it is incompatible with preparing\na commit message template containing comments. When git sees the\ndeprecated config setting it will print advice based on the user's\nconfig setting to help the user either remove the setting or set a\ncustom comment string. In the example below core.commentString is set\nmultiple times in $XDG_CONFIG_HOME/git/config and core.commentChar\nis set in ~/.gitconfig and $XDG_CONFIG_HOME/git/config.\n\nwarning: Support for 'core.commentChar=auto' is deprecated and will be removed in Git 3.0\nhint:\nhint: To use the default comment string (#) please run\nhint:\nhint:     git config unset --file ~/.config/git/config --all core.commentString\nhint:     git config unset --file ~/.config/git/config core.commentChar\nhint:     git config unset --global core.commentChar\nhint:\nhint: To set a custom comment string please run\nhint:\nhint:     git config set --global core.commentChar <comment string>\nhint:\nhint: where '<comment string>' is the string you wish to use.\n\n[1] https://lore.kernel.org/git/6a3154e0-e7bc-45ae-b554-67ccab18727a@gmail.com\n[2] https://lore.kernel.org/git/20250315140913.577404-1-oswald.buddenhagen@gmx.de\n[3] https://lore.kernel.org/git/20250626132233.414789-1-ayu.chandekar@gmail.com\n\nChanges since V3:\n - Patch 2: Remove repeated word from commit message\n\nChanges since V2:\n - Patch 1: Punctuation fixes\n - Patch 2: Reworded the commit message slightly\n            Remove unnecessary include of advice.h\n            Fix variable declaration\n - Patch 3: Include advice.h\n\nChanges since V1:\n - Rebased onto a merge of 'ps/config-wo-the-repository' and 'master'\n - Reworded commit messages\n - What was patch 2 has been split into two separate patches and\n   reworked to die when core.commentChar=auto and WITH_BREAKING_CHANGES\n   is enabled.\n\nBase-Commit: 1ae5bd276bdf101e37c1a8f2904a2eae05fbb744\nPublished-As: https://github.com/phillipwood/git/releases/tag/pw%2Fremove-auto-comment-char%2Fv4\nView-Changes-At: https://github.com/phillipwood/git/compare/1ae5bd276...39d824db4\nFetch-It-Via: git fetch https://github.com/phillipwood/git pw/remove-auto-comment-char/v4\n\n\nPhillip Wood (3):\n  breaking-changes: deprecate support for core.commentString=auto\n  config: warn on core.commentString=auto\n  commit: print advice when core.commentString=auto\n\n Documentation/BreakingChanges.adoc |   5 +\n Documentation/config/core.adoc     |  20 +-\n builtin/commit.c                   |   7 +\n builtin/merge.c                    |   3 +\n builtin/rebase.c                   |   3 +\n builtin/revert.c                   |   7 +\n config.c                           | 297 ++++++++++++++++++++++++++++-\n environment.c                      |  11 +-\n environment.h                      |   3 +\n repository.c                       |   1 +\n repository.h                       |   3 +\n t/t3404-rebase-interactive.sh      |  19 +-\n t/t3418-rebase-continue.sh         |   2 +-\n t/t7502-commit-porcelain.sh        |  52 ++++-\n 14 files changed, 421 insertions(+), 12 deletions(-)\n\nRange-diff against v3:\n1:  5b921064f1e = 1:  5b921064f1e breaking-changes: deprecate support for core.commentString=auto\n2:  5dd897c95e6 ! 2:  e92511ce21c config: warn on core.commentString=auto\n    @@ Commit message\n     \n         As support for this setting was deprecated in the last commit print a\n         warning (or die when WITH_BREAKING_CHANGES is enabled) if it is set.\n    -    Avoid bombarding the user with warnings by only printing it (a) when\n    -    running commands commands that call \"git commit\" and (b) only once\n    -    per command. Some scaffolding is added to repo_read_config() to allow\n    +    Avoid bombarding the user with warnings by only printing it (a)\n    +    when running commands that call \"git commit\" and (b) only once per\n    +    command. Some scaffolding is added to repo_read_config() to allow\n         it to detect deprecated config settings and warn about them. As both\n         \"core.commentChar\" and \"core.commentString\" set the comment character\n         we record which one of them is used and tailor the warning message\n3:  ee6cf11a82c = 3:  39d824db4ab commit: print advice when core.commentString=auto\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"525047","messageId":"5b921064f1e6c06ec2c150b1cce9d2b2b87a74a1.1756308431.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1756308431.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v4 1/3] breaking-changes: deprecate support for core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-27T15:27:28Z","receivedAt":"2025-08-27T15:27:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen \"core.commentString\" is set to \"auto\" then \"git commit\" will\nautomatically select the comment character ensuring that it is not the\nfirst character on any of the lines in the commit message. This was\nintroduced by commit 84c9dc2c5a2 (commit: allow core.commentChar=auto\nfor character auto selection, 2014-05-17). The motivation seems to be\nto avoid commenting out lines from the existing message when amending\na commit that was created with a message from a file.\n\nUnfortunately this feature does not work with:\n\n * commit message templates that contain comments.\n\n * prepare-commit-msg hooks that introduce comments.\n\n * \"git commit --cleanup=strip --edit -F <file>\" which means that it\n   is incompatible with\n\n   - the \"fixup\" and \"squash\" commands of \"git rebase -i\" as the\n     comments added by those commands are then treated as part of\n     the commit message.\n\n   - the conflict comments added to the commit message by \"git\n     cherry-pick\", \"git rebase\" etc. as these comments are then\n     treated as part of the commit message.\n\nIt is also ignored by \"git notes\" when amending a note.\n\nThe issues with comments coming from a template, hook or file are a\nconsequence of the design of this feature and are therefore hard to\nfix.\n\nAs the costs of this feature outweigh the benefits, deprecate it and\nremove it in Git 3.0. If someone comes up with some patches that fix\nall the issues in a maintainable way then I'd be happy to see this\nchange reverted.\n\nThe next commits will add a warning and some advice for users on how\nthey can update their config settings.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n Documentation/BreakingChanges.adoc |  5 +++++\n Documentation/config/core.adoc     | 20 ++++++++++++++++++--\n builtin/commit.c                   |  4 ++++\n environment.c                      | 10 ++++++++--\n environment.h                      |  2 ++\n t/t3404-rebase-interactive.sh      |  2 +-\n t/t3418-rebase-continue.sh         |  2 +-\n t/t7502-commit-porcelain.sh        |  4 ++--\n 8 files changed, 41 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc\nindex f8d2eba061c..344ce500603 100644\n--- a/Documentation/BreakingChanges.adoc\n+++ b/Documentation/BreakingChanges.adoc\n@@ -239,6 +239,11 @@ These features will be removed.\n +\n The command will be removed.\n \n+* Support for `core.commentString=auto` has been deprecated and will\n+  be removed in Git 3.0.\n++\n+cf. <xmqqa59i45wc.fsf@gitster.g>\n+\n == Superseded features that will not be deprecated\n \n Some features have gained newer replacements that aim to improve the design in\ndiff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc\nindex 9fde1ab63a7..7133f00c38b 100644\n--- a/Documentation/config/core.adoc\n+++ b/Documentation/config/core.adoc\n@@ -531,9 +531,25 @@ core.commentString::\n \tcommented, and removes them after the editor returns\n \t(default '#').\n +\n-If set to \"auto\", `git-commit` would select a character that is not\n+ifndef::with-breaking-changes[]\n+If set to \"auto\", `git-commit` will select a character that is not\n the beginning character of any line in existing commit messages.\n-+\n+Support for this value is deprecated and will be removed in Git 3.0\n+due to the following limitations:\n++\n+--\n+* It is incompatible with adding comments in a commit message\n+  template. This includes the conflicts comments added to\n+  the commit message by `cherry-pick`, `merge`, `rebase` and\n+  `revert`.\n+* It is incompatible with adding comments to the commit message\n+  in the `prepare-commit-msg` hook.\n+* It is incompatible with the `fixup` and `squash` commands when\n+  rebasing,\n+* It is not respected by `git notes`\n+--\n++\n+endif::with-breaking-changes[]\n Note that these two variables are aliases of each other, and in modern\n versions of Git you are free to use a string (e.g., `//` or `⁑⁕⁑`) with\n `commentChar`. Versions of Git prior to v2.45.0 will ignore\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 757f51eac82..d25cc07a355 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -683,6 +683,7 @@ static int author_date_is_interesting(void)\n \treturn author_message || force_date;\n }\n \n+#ifndef WITH_BREAKING_CHANGES\n static void adjust_comment_line_char(const struct strbuf *sb)\n {\n \tchar candidates[] = \"#;@!$%^&|:\";\n@@ -720,6 +721,7 @@ static void adjust_comment_line_char(const struct strbuf *sb)\n \tfree(comment_line_str_to_free);\n \tcomment_line_str = comment_line_str_to_free = xstrfmt(\"%c\", *p);\n }\n+#endif /* !WITH_BREAKING_CHANGES */\n \n static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,\n \t\t\t\tstruct pretty_print_context *ctx)\n@@ -916,8 +918,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tif (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)\n \t\tdie_errno(_(\"could not write commit template\"));\n \n+#ifndef WITH_BREAKING_CHANGES\n \tif (auto_comment_line_char)\n \t\tadjust_comment_line_char(&sb);\n+#endif /* !WITH_BREAKING_CHANGES */\n \tstrbuf_release(&sb);\n \n \t/* This checks if committer ident is explicitly given */\ndiff --git a/environment.c b/environment.c\nindex a0ac5934b37..4c87876d483 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -122,7 +122,9 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT;\n  */\n const char *comment_line_str = \"#\";\n char *comment_line_str_to_free;\n+#ifndef WITH_BREAKING_CHANGES\n int auto_comment_line_char;\n+#endif /* !WITH_BREAKING_CHANGES */\n \n /* This is set by setup_git_directory_gently() and/or git_default_config() */\n char *git_work_tree_cfg;\n@@ -459,18 +461,22 @@ static int git_default_core_config(const char *var, const char *value,\n \n \tif (!strcmp(var, \"core.commentchar\") ||\n \t    !strcmp(var, \"core.commentstring\")) {\n-\t\tif (!value)\n+\t\tif (!value) {\n \t\t\treturn config_error_nonbool(var);\n-\t\telse if (!strcasecmp(value, \"auto\")) {\n+#ifndef WITH_BREAKING_CHANGES\n+\t\t} else if (!strcasecmp(value, \"auto\")) {\n \t\t\tauto_comment_line_char = 1;\n \t\t\tFREE_AND_NULL(comment_line_str_to_free);\n \t\t\tcomment_line_str = \"#\";\n+#endif /* !WITH_BREAKING_CHANGES */\n \t\t} else if (value[0]) {\n \t\t\tif (strchr(value, '\\n'))\n \t\t\t\treturn error(_(\"%s cannot contain newline\"), var);\n \t\t\tcomment_line_str = value;\n \t\t\tFREE_AND_NULL(comment_line_str_to_free);\n+#ifndef WITH_BREAKING_CHANGES\n \t\t\tauto_comment_line_char = 0;\n+#endif /* !WITH_BREAKING_CHANGES */\n \t\t} else\n \t\t\treturn error(_(\"%s must have at least one character\"), var);\n \t\treturn 0;\ndiff --git a/environment.h b/environment.h\nindex 8cfce41015b..e75c4abb388 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -208,7 +208,9 @@ extern char *excludes_file;\n  */\n extern const char *comment_line_str;\n extern char *comment_line_str_to_free;\n+#ifndef WITH_BREAKING_CHANGES\n extern int auto_comment_line_char;\n+#endif /* !WITH_BREAKING_CHANGES */\n \n # endif /* USE_THE_REPOSITORY_VARIABLE */\n #endif /* ENVIRONMENT_H */\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 6bac217ed35..ce0aebb9a7e 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1176,7 +1176,7 @@ test_expect_success 'rebase -i respects core.commentchar' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n-test_expect_success 'rebase -i respects core.commentchar=auto' '\n+test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=auto' '\n \ttest_config core.commentchar auto &&\n \twrite_script copy-edit-script.sh <<-\\EOF &&\n \tcp \"$1\" edit-script\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex b8a8dd77e74..f9b8999db50 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -328,7 +328,7 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas\n \ttest_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec\n '\n \n-test_expect_success 'no change in comment character due to conflicts markers with core.commentChar=auto' '\n+test_expect_success !WITH_BREAKING_CHANGES 'no change in comment character due to conflicts markers with core.commentChar=auto' '\n \tgit checkout -b branch-a &&\n \ttest_commit A F1 &&\n \tgit checkout -b branch-b HEAD^ &&\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex b37e2018a74..65b4519a715 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -956,13 +956,13 @@ test_expect_success 'commit --status with custom comment character' '\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n-test_expect_success 'switch core.commentchar' '\n+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n \tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n-test_expect_success 'switch core.commentchar but out of options' '\n+test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar but out of options' '\n \tcat >text <<\\EOF &&\n # 1\n ; 2\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"525048","messageId":"e92511ce21c9211964e7f18964a927166fb411ae.1756308431.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1756308431.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v4 2/3] config: warn on core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-27T15:27:29Z","receivedAt":"2025-08-27T15:27:48Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAs support for this setting was deprecated in the last commit print a\nwarning (or die when WITH_BREAKING_CHANGES is enabled) if it is set.\nAvoid bombarding the user with warnings by only printing it (a)\nwhen running commands that call \"git commit\" and (b) only once per\ncommand. Some scaffolding is added to repo_read_config() to allow\nit to detect deprecated config settings and warn about them. As both\n\"core.commentChar\" and \"core.commentString\" set the comment character\nwe record which one of them is used and tailor the warning message\nappropriately.\n\nNote the odd combination of die_message() followed by die(NULL)\nis to allow the next commit to insert a call to advise() in the middle.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n builtin/commit.c              |   3 +\n builtin/merge.c               |   3 +\n builtin/rebase.c              |   3 +\n builtin/revert.c              |   7 +++\n config.c                      | 115 +++++++++++++++++++++++++++++++++-\n environment.c                 |   1 +\n environment.h                 |   1 +\n repository.c                  |   1 +\n repository.h                  |   3 +\n t/t3404-rebase-interactive.sh |   7 ++-\n t/t7502-commit-porcelain.sh   |  17 ++++-\n 11 files changed, 157 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex d25cc07a355..f821fdcfcc3 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1783,6 +1783,9 @@ int cmd_commit(int argc,\n \tshow_usage_with_options_if_asked(argc, argv,\n \t\t\t\t\t builtin_commit_usage, builtin_commit_options);\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex dc4cb8fb14d..794cb7bb269 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1378,6 +1378,9 @@ int cmd_merge(int argc,\n \tshow_usage_with_options_if_asked(argc, argv,\n \t\t\t\t\t builtin_merge_usage, builtin_merge_options);\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 72a52bdfb98..962917ec480 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1242,6 +1242,9 @@ int cmd_rebase(int argc,\n \t\t\t\t\t builtin_rebase_usage,\n \t\t\t\t\t builtin_rebase_options);\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \tprepare_repo_settings(the_repository);\n \tthe_repository->settings.command_requires_full_index = 0;\n \ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex e07c2217fe8..b197848bb0a 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -4,6 +4,7 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"diff.h\"\n+#include \"environment.h\"\n #include \"gettext.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n@@ -285,6 +286,9 @@ int cmd_revert(int argc,\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tint res;\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \topts.action = REPLAY_REVERT;\n \tsequencer_init_config(&opts);\n \tres = run_sequencer(argc, argv, prefix, &opts);\n@@ -302,6 +306,9 @@ struct repository *repo UNUSED)\n \tstruct replay_opts opts = REPLAY_OPTS_INIT;\n \tint res;\n \n+#ifndef WITH_BREAKING_CHANGES\n+\twarn_on_auto_comment_char = true;\n+#endif /* !WITH_BREAKING_CHANGES */\n \topts.action = REPLAY_PICK;\n \tsequencer_init_config(&opts);\n \tres = run_sequencer(argc, argv, prefix, &opts);\ndiff --git a/config.c b/config.c\nindex 97ffef42700..18b42197095 100644\n--- a/config.c\n+++ b/config.c\n@@ -11,6 +11,7 @@\n #include \"date.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"dir.h\"\n #include \"parse.h\"\n #include \"convert.h\"\n #include \"environment.h\"\n@@ -1951,10 +1952,110 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d\n \t\treturn 1;\n }\n \n+struct comment_char_config {\n+\tunsigned last_key_id;\n+\tbool auto_set;\n+};\n+\n+#define COMMENT_CHAR_CFG_INIT { 0 }\n+\n+static const char *comment_key_name(unsigned id)\n+{\n+\tstatic const char *name[] = {\n+\t\t\"core.commentChar\",\n+\t\t\"core.commentString\",\n+\t};\n+\n+\tif (id >= ARRAY_SIZE(name))\n+\t\tBUG(\"invalid comment key id\");\n+\n+\treturn name[id];\n+}\n+\n+static void comment_char_callback(const char *key, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *data)\n+{\n+\tstruct comment_char_config *config = data;\n+\tunsigned key_id;\n+\n+\tif (!strcmp(key, \"core.commentchar\"))\n+\t\tkey_id = 0;\n+\telse if (!strcmp(key, \"core.commentstring\"))\n+\t\tkey_id = 1;\n+\telse\n+\t\treturn;\n+\n+\tconfig->last_key_id = key_id;\n+\tconfig->auto_set = value && !strcmp(value, \"auto\");\n+}\n+\n+struct repo_config {\n+\tstruct repository *repo;\n+\tstruct comment_char_config comment_char_config;\n+};\n+\n+#define REPO_CONFIG_INIT(repo_) {\t\t\t\t\\\n+\t\t.comment_char_config = COMMENT_CHAR_CFG_INIT,\t\\\n+\t\t.repo = repo_,\t\t\t\t\t\\\n+\t};\n+\n+#ifdef WITH_BREAKING_CHANGES\n+static void check_auto_comment_char_config(struct comment_char_config *config)\n+{\n+\tif (!config->auto_set)\n+\t\treturn;\n+\n+\tdie_message(_(\"Support for '%s=auto' has been removed in Git 3.0\"),\n+\t\t    comment_key_name(config->last_key_id));\n+\tdie(NULL);\n+}\n+#else\n+static void check_auto_comment_char_config(struct comment_char_config *config)\n+{\n+\textern bool warn_on_auto_comment_char;\n+\tconst char *DEPRECATED_CONFIG_ENV =\n+\t\t\t\t\"GIT_AUTO_COMMENT_CHAR_CONFIG_WARNING_GIVEN\";\n+\n+\tif (!config->auto_set || !warn_on_auto_comment_char)\n+\t\treturn;\n+\n+\t/*\n+\t * Use an environment variable to ensure that subprocesses do not repeat\n+\t * the warning.\n+\t */\n+\tif (git_env_bool(DEPRECATED_CONFIG_ENV, false))\n+\t\treturn;\n+\n+\tsetenv(DEPRECATED_CONFIG_ENV, \"true\", true);\n+\n+\twarning(_(\"Support for '%s=auto' is deprecated and will be removed in \"\n+\t\t  \"Git 3.0\"), comment_key_name(config->last_key_id));\n+}\n+#endif /* WITH_BREAKING_CHANGES */\n+\n+static void check_deprecated_config(struct repo_config *config)\n+{\n+\tif (!config->repo->check_deprecated_config)\n+\t\t\treturn;\n+\n+\tcheck_auto_comment_char_config(&config->comment_char_config);\n+}\n+\n+static int repo_config_callback(const char *key, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *data)\n+{\n+\tstruct repo_config *config = data;\n+\n+\tcomment_char_callback(key, value, ctx, &config->comment_char_config);\n+\treturn config_set_callback(key, value, ctx, config->repo->config);\n+}\n+\n /* Functions use to read configuration from a repository */\n static void repo_read_config(struct repository *repo)\n {\n \tstruct config_options opts = { 0 };\n+\tstruct repo_config config = REPO_CONFIG_INIT(repo);\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -1966,8 +2067,8 @@ static void repo_read_config(struct repository *repo)\n \t\tgit_configset_clear(repo->config);\n \n \tgit_configset_init(repo->config);\n-\tif (config_with_options(config_set_callback, repo->config, NULL,\n-\t\t\t\trepo, &opts) < 0)\n+\tif (config_with_options(repo_config_callback, &config, NULL, repo,\n+\t\t\t\t&opts) < 0)\n \t\t/*\n \t\t * config_with_options() normally returns only\n \t\t * zero, as most errors are fatal, and\n@@ -1980,6 +2081,7 @@ static void repo_read_config(struct repository *repo)\n \t\t * immediately.\n \t\t */\n \t\tdie(_(\"unknown error occurred while reading the configuration files\"));\n+\tcheck_deprecated_config(&config);\n }\n \n static void git_config_check_init(struct repository *repo)\n@@ -2667,6 +2769,14 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,\n \tchar *contents = NULL;\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n+\tbool saved_check_deprecated_config = r->check_deprecated_config;\n+\n+\t/*\n+\t * Do not warn or die if there are deprecated config settings as\n+\t * we want the user to be able to change those settings by running\n+\t * \"git config\".\n+\t */\n+\tr->check_deprecated_config = false;\n \n \tvalidate_comment_string(comment);\n \n@@ -2898,6 +3008,7 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,\n \tif (in_fd >= 0)\n \t\tclose(in_fd);\n \tconfig_store_data_clear(&store);\n+\tr->check_deprecated_config = saved_check_deprecated_config;\n \treturn ret;\n \n write_err_out:\ndiff --git a/environment.c b/environment.c\nindex 4c87876d483..1ffa2ff30b2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -124,6 +124,7 @@ const char *comment_line_str = \"#\";\n char *comment_line_str_to_free;\n #ifndef WITH_BREAKING_CHANGES\n int auto_comment_line_char;\n+bool warn_on_auto_comment_char;\n #endif /* !WITH_BREAKING_CHANGES */\n \n /* This is set by setup_git_directory_gently() and/or git_default_config() */\ndiff --git a/environment.h b/environment.h\nindex e75c4abb388..51898c99cd1 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -210,6 +210,7 @@ extern const char *comment_line_str;\n extern char *comment_line_str_to_free;\n #ifndef WITH_BREAKING_CHANGES\n extern int auto_comment_line_char;\n+extern bool warn_on_auto_comment_char;\n #endif /* !WITH_BREAKING_CHANGES */\n \n # endif /* USE_THE_REPOSITORY_VARIABLE */\ndiff --git a/repository.c b/repository.c\nindex ecd691181fc..8af73923d34 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -57,6 +57,7 @@ void initialize_repository(struct repository *repo)\n \trepo->parsed_objects = parsed_object_pool_new(repo);\n \tALLOC_ARRAY(repo->index, 1);\n \tindex_state_init(repo->index, repo);\n+\trepo->check_deprecated_config = true;\n \n \t/*\n \t * When a command runs inside a repository, it learns what\ndiff --git a/repository.h b/repository.h\nindex 042dc93f0f2..5808a5d6108 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -161,6 +161,9 @@ struct repository {\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\n \tunsigned different_commondir:1;\n+\n+\t/* Should repo_config() check for deprecated settings */\n+\tbool check_deprecated_config;\n };\n \n #ifdef USE_THE_REPOSITORY_VARIABLE\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ce0aebb9a7e..3b2a46c25ce 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1184,8 +1184,13 @@ test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=\n \ttest_when_finished \"git rebase --abort || :\" &&\n \t(\n \t\ttest_set_editor \"$(pwd)/copy-edit-script.sh\" &&\n-\t\tgit rebase -i HEAD^\n+\t\tgit rebase -i HEAD^ 2>err\n \t) &&\n+\tsed -n \"s/^warning: //p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\tEOF\n+\ttest_cmp expect actual &&\n \ttest -z \"$(grep -ve \"^#\" -e \"^\\$\" -e \"^pick\" edit-script)\"\n '\n \ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 65b4519a715..a9dc1e416d1 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -958,7 +958,12 @@ test_expect_success 'commit --status with custom comment character' '\n \n test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n-\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend &&\n+\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend 2>err &&\n+\tsed -n \"s/^warning: //p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\tEOF\n+\ttest_cmp expect actual &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n '\n \n@@ -982,4 +987,14 @@ EOF\n \t)\n '\n \n+test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '\n+\ttest_config core.commentChar auto &&\n+\ttest_must_fail git rev-parse --git-dir 2>err &&\n+\tsed -n \"s/^fatal: //p\" err >actual &&\n+\tcat >expect <<-EOF &&\n+\tSupport for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"525049","messageId":"39d824db4aba5c2b70e5e9b2d86125de2e9605fc.1756308431.git.phillip.wood@dunelm.org.uk","threadId":"63762","inReplyTo":"cover.1756308431.git.phillip.wood@dunelm.org.uk","subject":"[PATCH v4 3/3] commit: print advice when core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-27T15:27:30Z","receivedAt":"2025-08-27T15:27:49Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAdd some advice on how to change the config settings when\n\"core.commentString=auto\" or \"core.commentChar=auto\". The advice\nincludes instructions for clearing the config setting or setting a\nfixed comment string. To try and be as specific as possible, the advice\nis customized based on the user's config. If \"core.commentString=auto\"\nis set in the system config and the user does not have write\naccess then the advice omits the instructions to clear the config\nand recommends changing the global config instead. An alternative\napproach would be to advise the user to run \"git config --show-origin\"\nand leave them to figure out how to fix it themselves but that seems\nrather unfriendly. As we're forcing them to update their config we\nshould try and make that as easy as possible.\n\nIn order to generate this advice we need to record each file where\neither of the config keys is set and whether a key occurs more that\nonce in a given file. This lets us generate the list of commands to\nremove all the keys and also tells us which key the \"auto\" setting\ncomes from.\n\nAs we want the user to update their config we do not provide a way\nfor this advice to be disabled other than changing the value of\n\"core.commentChar\" or \"core.commentString\".\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n config.c                      | 196 ++++++++++++++++++++++++++++++++--\n t/t3404-rebase-interactive.sh |  12 ++-\n t/t7502-commit-porcelain.sh   |  37 ++++++-\n 3 files changed, 234 insertions(+), 11 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 18b42197095..18dcf341d58 100644\n--- a/config.c\n+++ b/config.c\n@@ -8,6 +8,7 @@\n \n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n+#include \"advice.h\"\n #include \"date.h\"\n #include \"branch.h\"\n #include \"config.h\"\n@@ -1955,9 +1956,51 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d\n struct comment_char_config {\n \tunsigned last_key_id;\n \tbool auto_set;\n+\tbool auto_set_in_file;\n+\tstruct strintmap key_flags;\n+\tsize_t alloc, nr;\n+\tstruct comment_char_config_item {\n+\t\tunsigned key_id;\n+\t\tchar *path;\n+\t\tenum config_scope scope;\n+\t} *item;\n };\n \n-#define COMMENT_CHAR_CFG_INIT { 0 }\n+#define COMMENT_CHAR_CFG_INIT {\t\t\t\\\n+\t\t.key_flags = STRINTMAP_INIT,\t\\\n+\t}\n+\n+static void comment_char_config_release(struct comment_char_config *config)\n+{\n+\tstrintmap_clear(&config->key_flags);\n+\tfor (size_t i = 0; i < config->nr; i++)\n+\t\tfree(config->item[i].path);\n+\tfree(config->item);\n+}\n+\n+/* Used to track whether the key occurs more than once in a given file */\n+#define KEY_SEEN_ONCE 1u\n+#define KEY_SEEN_TWICE 2u\n+#define COMMENT_KEY_SHIFT(id) (2 * (id))\n+#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id))\n+\n+static void set_comment_key_flags(struct comment_char_config *config,\n+\t\t\t\t  const char *path, unsigned id, unsigned value)\n+{\n+\tunsigned old = strintmap_get(&config->key_flags, path);\n+\tunsigned new = (old & ~COMMENT_KEY_MASK(id)) |\n+\t\t\t\tvalue << COMMENT_KEY_SHIFT(id);\n+\n+\tstrintmap_set(&config->key_flags, path, new);\n+}\n+\n+static unsigned get_comment_key_flags(struct comment_char_config *config,\n+\t\t\t\t      const char *path, unsigned id)\n+{\n+\tunsigned value = strintmap_get(&config->key_flags, path);\n+\n+\treturn (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id);\n+}\n \n static const char *comment_key_name(unsigned id)\n {\n@@ -1973,10 +2016,10 @@ static const char *comment_key_name(unsigned id)\n }\n \n static void comment_char_callback(const char *key, const char *value,\n-\t\t\t\t  const struct config_context *ctx UNUSED,\n-\t\t\t\t  void *data)\n+\t\t\t\t  const struct config_context *ctx, void *data)\n {\n \tstruct comment_char_config *config = data;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \tunsigned key_id;\n \n \tif (!strcmp(key, \"core.commentchar\"))\n@@ -1988,7 +2031,135 @@ static void comment_char_callback(const char *key, const char *value,\n \n \tconfig->last_key_id = key_id;\n \tconfig->auto_set = value && !strcmp(value, \"auto\");\n-}\n+\tif (kvi->origin_type != CONFIG_ORIGIN_FILE) {\n+\t\treturn;\n+\t} else if (get_comment_key_flags(config, kvi->filename, key_id)) {\n+\t\tset_comment_key_flags(config, kvi->filename, key_id,\n+\t\t\t\t      KEY_SEEN_TWICE);\n+\t} else {\n+\t\tstruct comment_char_config_item *item;\n+\n+\t\tALLOC_GROW_BY(config->item, config->nr, 1, config->alloc);\n+\t\titem = &config->item[config->nr - 1];\n+\t\titem->key_id = key_id;\n+\t\titem->scope = kvi->scope;\n+\t\titem->path = xstrdup(kvi->filename);\n+\t\tset_comment_key_flags(config, kvi->filename, key_id,\n+\t\t\t\t      KEY_SEEN_ONCE);\n+\t}\n+\tconfig->auto_set_in_file = config->auto_set;\n+}\n+\n+static void add_config_scope_arg(struct repository *repo, struct strbuf *buf,\n+\t\t\t\t struct comment_char_config_item *item)\n+{\n+\tchar *global_config = git_global_config();\n+\tchar *system_config = git_system_config();\n+\n+\tif (item->scope == CONFIG_SCOPE_SYSTEM && access(item->path, W_OK)) {\n+\t\t/*\n+\t\t * If the user cannot write to the system config recommend\n+\t\t * setting the global config instead.\n+\t\t */\n+\t\tstrbuf_addstr(buf, \"--global \");\n+\t} else if (fspatheq(item->path, system_config)) {\n+\t\tstrbuf_addstr(buf, \"--system \");\n+\t} else if (fspatheq(item->path, global_config)) {\n+\t\tstrbuf_addstr(buf, \"--global \");\n+\t} else if (fspatheq(item->path,\n+\t\t\t    mkpath(\"%s/config\",\n+\t\t\t\t   repo_get_git_dir(repo)))) {\n+\t\t; /* --local is the default */\n+\t} else if (fspatheq(item->path,\n+\t\t\t    mkpath(\"%s/config.worktree\",\n+\t\t\t\t   repo_get_common_dir(repo)))) {\n+\t\tstrbuf_addstr(buf, \"--worktree \");\n+\t} else {\n+\t\tconst char *path = item->path;\n+\t\tconst char *home = getenv(\"HOME\");\n+\n+\t\tstrbuf_addstr(buf, \"--file \");\n+\t\tif (home && !fspathncmp(path, home, strlen(home))) {\n+\t\t\tpath += strlen(home);\n+\t\t\tif (!fspathncmp(path, \"/\", 1))\n+\t\t\t\tpath++;\n+\t\t\tstrbuf_addstr(buf, \"~/\");\n+\t\t}\n+\t\tsq_quote_buf_pretty(buf, path);\n+\t\tstrbuf_addch(buf, ' ');\n+\t}\n+\n+\tfree(global_config);\n+\tfree(system_config);\n+}\n+\n+static bool can_unset_comment_char_config(struct comment_char_config *config)\n+{\n+\tfor (size_t i = 0; i < config->nr; i++) {\n+\t\tstruct comment_char_config_item *item = &config->item[i];\n+\n+\t\tif (item->scope == CONFIG_SCOPE_SYSTEM &&\n+\t\t    access(item->path, W_OK))\n+\t\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+static void add_unset_auto_comment_char_advice(struct repository *repo,\n+\t\t\t\t\t       struct comment_char_config *config)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (!can_unset_comment_char_config(config))\n+\t\treturn;\n+\n+\tfor (size_t i = 0; i < config->nr; i++) {\n+\t\tstruct comment_char_config_item *item = &config->item[i];\n+\n+\t\tstrbuf_addstr(&buf, \"    git config unset \");\n+\t\tadd_config_scope_arg(repo, &buf, item);\n+\t\tif (get_comment_key_flags(config, item->path, item->key_id) == KEY_SEEN_TWICE)\n+\t\t\tstrbuf_addstr(&buf, \"--all \");\n+\t\tstrbuf_addf(&buf, \"%s\\n\", comment_key_name(item->key_id));\n+\t}\n+\tadvise(_(\"\\nTo use the default comment string (#) please run\\n\\n%s\"),\n+\t       buf.buf);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void add_comment_char_advice(struct repository *repo,\n+\t\t\t\t    struct comment_char_config *config)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct comment_char_config_item *item;\n+\t/* TRANSLATORS this is a place holder for the value of core.commentString */\n+\tconst char *placeholder = _(\"<comment string>\");\n+\n+\t/*\n+\t * If auto is set in the last file that we saw advise the user how to\n+\t * update their config.\n+\t */\n+\tif (!config->auto_set_in_file)\n+\t\treturn;\n+\n+\tadd_unset_auto_comment_char_advice(repo, config);\n+\titem = &config->item[config->nr - 1];\n+\tstrbuf_reset(&buf);\n+\tstrbuf_addstr(&buf, \"    git config set \");\n+\tadd_config_scope_arg(repo, &buf, item);\n+\tstrbuf_addf(&buf, \"%s %s\\n\", comment_key_name(item->key_id),\n+\t\t    placeholder);\n+\tadvise(_(\"\\nTo set a custom comment string please run\\n\\n\"\n+\t\t \"%s\\nwhere '%s' is the string you wish to use.\\n\"),\n+\t       buf.buf, placeholder);\n+\tstrbuf_release(&buf);\n+}\n+\n+#undef KEY_SEEN_ONCE\n+#undef KEY_SEEN_TWICE\n+#undef COMMENT_KEY_SHIFT\n+#undef COMMENT_KEY_MASK\n \n struct repo_config {\n \tstruct repository *repo;\n@@ -2000,18 +2171,26 @@ struct repo_config {\n \t\t.repo = repo_,\t\t\t\t\t\\\n \t};\n \n+static void repo_config_release(struct repo_config *config)\n+{\n+\tcomment_char_config_release(&config->comment_char_config);\n+}\n+\n #ifdef WITH_BREAKING_CHANGES\n-static void check_auto_comment_char_config(struct comment_char_config *config)\n+static void check_auto_comment_char_config(struct repository *repo,\n+\t\t\t\t\t   struct comment_char_config *config)\n {\n \tif (!config->auto_set)\n \t\treturn;\n \n \tdie_message(_(\"Support for '%s=auto' has been removed in Git 3.0\"),\n \t\t    comment_key_name(config->last_key_id));\n+\tadd_comment_char_advice(repo, config);\n \tdie(NULL);\n }\n #else\n-static void check_auto_comment_char_config(struct comment_char_config *config)\n+static void check_auto_comment_char_config(struct repository *repo,\n+\t\t\t\t\t   struct comment_char_config *config)\n {\n \textern bool warn_on_auto_comment_char;\n \tconst char *DEPRECATED_CONFIG_ENV =\n@@ -2031,6 +2210,7 @@ static void check_auto_comment_char_config(struct comment_char_config *config)\n \n \twarning(_(\"Support for '%s=auto' is deprecated and will be removed in \"\n \t\t  \"Git 3.0\"), comment_key_name(config->last_key_id));\n+\tadd_comment_char_advice(repo, config);\n }\n #endif /* WITH_BREAKING_CHANGES */\n \n@@ -2039,7 +2219,8 @@ static void check_deprecated_config(struct repo_config *config)\n \tif (!config->repo->check_deprecated_config)\n \t\t\treturn;\n \n-\tcheck_auto_comment_char_config(&config->comment_char_config);\n+\tcheck_auto_comment_char_config(config->repo,\n+\t\t\t\t       &config->comment_char_config);\n }\n \n static int repo_config_callback(const char *key, const char *value,\n@@ -2082,6 +2263,7 @@ static void repo_read_config(struct repository *repo)\n \t\t */\n \t\tdie(_(\"unknown error occurred while reading the configuration files\"));\n \tcheck_deprecated_config(&config);\n+\trepo_config_release(&config);\n }\n \n static void git_config_check_init(struct repository *repo)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 3b2a46c25ce..cc97628d810 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1186,9 +1186,19 @@ test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=\n \t\ttest_set_editor \"$(pwd)/copy-edit-script.sh\" &&\n \t\tgit rebase -i HEAD^ 2>err\n \t) &&\n-\tsed -n \"s/^warning: //p\" err >actual &&\n+\tsed -n \"s/^hint: *\\$//p; s/^hint: //p; s/^warning: //p\" err >actual &&\n \tcat >expect <<-EOF &&\n \tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n \tEOF\n \ttest_cmp expect actual &&\n \ttest -z \"$(grep -ve \"^#\" -e \"^\\$\" -e \"^pick\" edit-script)\"\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex a9dc1e416d1..05f6da4ad98 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -958,10 +958,31 @@ test_expect_success 'commit --status with custom comment character' '\n \n test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '\n \ttest_commit \"#foo\" foo &&\n-\tGIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend 2>err &&\n-\tsed -n \"s/^warning: //p\" err >actual &&\n+\tcat >config-include <<-\\EOF &&\n+\t[core]\n+\t    commentString=:\n+\t    commentString=%\n+\t    commentChar=auto\n+\tEOF\n+\ttest_when_finished \"rm config-include\" &&\n+\ttest_config include.path \"$(pwd)/config-include\" &&\n+\ttest_config core.commentChar ! &&\n+\tGIT_EDITOR=.git/FAKE_EDITOR git commit --amend 2>err &&\n+\tsed -n \"s/^hint: *\\$//p; s/^hint: //p; s/^warning: //p\" err >actual &&\n \tcat >expect <<-EOF &&\n \tSupport for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\t    git config unset --file ~/config-include --all core.commentString\n+\t    git config unset --file ~/config-include core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set --file ~/config-include core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n \tEOF\n \ttest_cmp expect actual &&\n \ttest_grep \"^; Changes to be committed:\" .git/COMMIT_EDITMSG\n@@ -990,9 +1011,19 @@ EOF\n test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '\n \ttest_config core.commentChar auto &&\n \ttest_must_fail git rev-parse --git-dir 2>err &&\n-\tsed -n \"s/^fatal: //p\" err >actual &&\n+\tsed -n \"s/^hint: *\\$//p; s/^hint: //p; s/^fatal: //p\" err >actual &&\n \tcat >expect <<-EOF &&\n \tSupport for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0\n+\n+\tTo use the default comment string (#) please run\n+\n+\t    git config unset core.commentChar\n+\n+\tTo set a custom comment string please run\n+\n+\t    git config set core.commentChar <comment string>\n+\n+\twhere ${SQ}<comment string>${SQ} is the string you wish to use.\n \tEOF\n \ttest_cmp expect actual\n '\n-- \n2.49.0.897.gfad3eb7d210\n\n"},{"id":"525050","messageId":"90a1f901-fcce-4275-b1a5-8ed50620f50d@gmail.com","threadId":"63762","inReplyTo":"xmqqldn6krmi.fsf@gitster.g","subject":"Re: [PATCH v3 2/3] config: warn on core.commentString=auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-08-27T15:29:35Z","receivedAt":"2025-08-27T15:29:39Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 26/08/2025 16:52, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> As support for this setting was deprecated in the last commit print a\n>> warning (or die when WITH_BREAKING_CHANGES is enabled) if it is set.\n>> Avoid bombarding the user with warnings by only printing it (a) when\n>> running commands commands that call \"git commit\" and (b) only once\n> \n> \"commands commands\" -> \"commands\".\n\nSigh, I removed \"only only\" which Oswald had pointed out only to add \nanother repeated word. I've just sent V4 with the typo fixed.\n\nThanks\n\nPhillip\n\n"},{"id":"525057","messageId":"xmqqfrdc3eiq.fsf@gitster.g","threadId":"63762","inReplyTo":"aK6_ivfXr7jIbBCz@ugly.lan","subject":"Re: [PATCH v2 3/3] commit: print advice when core.commentString=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-27T16:39:25Z","receivedAt":"2025-08-27T16:39:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Oswald Buddenhagen <oswald.buddenhagen@gmx.de> writes:\n\n> On Tue, Aug 26, 2025 at 02:33:10PM +0100, Phillip Wood wrote:\n>> Some of git's reputation for being hard to use is well earned and I\n>> don't want to add to that.\n>>\n> i would find that reasoning compelling if we weren't talking about a\n> case that is likely to affect only very few, and probably rather\n> advanced users (who somehow managed to make the feature actually\n> useful for them - others would have quickly reverted the setting,\n> because it would have gotten into their way).\n\nSo your counter-proposal is just stop at saying (possibly a milder\nequivalent of) \"nope, auto is no longer available. deal with it\"?\nOr does it go even stronger and not even special case \"auto\" that\nuser sets (i.e. and start commented lines with \"auto \" prefix)?\n\nA simpler solution that would work for existing users is more\nattractive than an overly complex one, of course, but I need to\ngauge how simple you want to go.\n\nThanks.\n"},{"id":"525068","messageId":"xmqqldn4zj9i.fsf@gitster.g","threadId":"63762","inReplyTo":"90a1f901-fcce-4275-b1a5-8ed50620f50d@gmail.com","subject":"Re: [PATCH v3 2/3] config: warn on core.commentString=auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-08-27T18:55:53Z","receivedAt":"2025-08-27T18:55:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 26/08/2025 16:52, Junio C Hamano wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>> \n>>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>>\n>>> As support for this setting was deprecated in the last commit print a\n>>> warning (or die when WITH_BREAKING_CHANGES is enabled) if it is set.\n>>> Avoid bombarding the user with warnings by only printing it (a) when\n>>> running commands commands that call \"git commit\" and (b) only once\n>> \"commands commands\" -> \"commands\".\n>\n> Sigh, I removed \"only only\" which Oswald had pointed out only to add\n> another repeated word. I've just sent V4 with the typo fixed.\n\nHeh, I've locally amended so no need to resend.  I'd rather see the\n\"what degree of help would our users need, and is it worth trying to\nfind the (impossible) definition of being 'good enough for most\npeople'\" resolved soon so that we can move forward.  I'll read the\nseries again, especially the recovery recipe the topic would give\nour users, to see if my stance would change from my previous one.\n\nThanks.\n\n"},{"id":"525082","messageId":"aK-I04a0lI-R8IEv@ugly.lan","threadId":"63762","inReplyTo":"xmqqfrdc3eiq.fsf@gitster.g","subject":"Re: [PATCH v2 3/3] commit: print advice when core.commentString=auto","fromName":"Oswald Buddenhagen","fromEmail":"oswald.buddenhagen@gmx.de","sentAt":"2025-08-27T22:38:11Z","receivedAt":"2025-08-27T22:38:22Z","isPatch":true,"sender":{"key":"oswald.buddenhagen@gmx.de","avatar":"https://avatars.githubusercontent.com/u/812380?v=4"},"body":"On Wed, Aug 27, 2025 at 09:39:25AM -0700, Junio C Hamano wrote:\n>So your counter-proposal is just stop at saying (possibly a milder\n>equivalent of) \"nope, auto is no longer available. deal with it\"?\n>\nyes.\nthough it should probably include \"because it was found to have \nfundamentally flawed semantics. re-check the manual for alternatives.\" \nor something like that.\n\n>Or does it go even stronger and not even special case \"auto\" that\n>user sets (i.e. and start commented lines with \"auto \" prefix)?\n>\nno, silent failure would be counter-productive.\n\n"}]}