Skip to content

Commit 3f188a0

Browse files
phillipwoodgitster
authored andcommitted
commit: print advice when core.commentString=auto
Add some advice on how to change the config settings when "core.commentString=auto" or "core.commentChar=auto". The advice includes instructions for clearing the config setting or setting a fixed comment string. To try and be as specific as possible, the advice is customized based on the user's config. If "core.commentString=auto" is set in the system config and the user does not have write access then the advice omits the instructions to clear the config and recommends changing the global config instead. An alternative approach would be to advise the user to run "git config --show-origin" and leave them to figure out how to fix it themselves but that seems rather unfriendly. As we're forcing them to update their config we should try and make that as easy as possible. In order to generate this advice we need to record each file where either of the config keys is set and whether a key occurs more that once in a given file. This lets us generate the list of commands to remove all the keys and also tells us which key the "auto" setting comes from. As we want the user to update their config we do not provide a way for this advice to be disabled other than changing the value of "core.commentChar" or "core.commentString". Signed-off-by: Phillip Wood <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 3ea3696 commit 3f188a0

File tree

3 files changed

+232
-10
lines changed

3 files changed

+232
-10
lines changed

config.c

Lines changed: 187 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,9 +1956,51 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d
19561956
struct comment_char_config {
19571957
unsigned last_key_id;
19581958
bool auto_set;
1959+
bool auto_set_in_file;
1960+
struct strintmap key_flags;
1961+
size_t alloc, nr;
1962+
struct comment_char_config_item {
1963+
unsigned key_id;
1964+
char *path;
1965+
enum config_scope scope;
1966+
} *item;
19591967
};
19601968

1961-
#define COMMENT_CHAR_CFG_INIT { 0 }
1969+
#define COMMENT_CHAR_CFG_INIT { \
1970+
.key_flags = STRINTMAP_INIT, \
1971+
}
1972+
1973+
static void comment_char_config_release(struct comment_char_config *config)
1974+
{
1975+
strintmap_clear(&config->key_flags);
1976+
for (size_t i = 0; i < config->nr; i++)
1977+
free(config->item[i].path);
1978+
free(config->item);
1979+
}
1980+
1981+
/* Used to track whether the key occurs more than once in a given file */
1982+
#define KEY_SEEN_ONCE 1u
1983+
#define KEY_SEEN_TWICE 2u
1984+
#define COMMENT_KEY_SHIFT(id) (2 * (id))
1985+
#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id))
1986+
1987+
static void set_comment_key_flags(struct comment_char_config *config,
1988+
const char *path, unsigned id, unsigned value)
1989+
{
1990+
unsigned old = strintmap_get(&config->key_flags, path);
1991+
unsigned new = (old & ~COMMENT_KEY_MASK(id)) |
1992+
value << COMMENT_KEY_SHIFT(id);
1993+
1994+
strintmap_set(&config->key_flags, path, new);
1995+
}
1996+
1997+
static unsigned get_comment_key_flags(struct comment_char_config *config,
1998+
const char *path, unsigned id)
1999+
{
2000+
unsigned value = strintmap_get(&config->key_flags, path);
2001+
2002+
return (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id);
2003+
}
19622004

19632005
static const char* comment_key_name(unsigned id)
19642006
{
@@ -1974,10 +2016,10 @@ static const char* comment_key_name(unsigned id)
19742016
}
19752017

19762018
static void comment_char_callback(const char *key, const char *value,
1977-
const struct config_context *ctx UNUSED,
1978-
void *data)
2019+
const struct config_context *ctx, void *data)
19792020
{
19802021
struct comment_char_config *config = data;
2022+
const struct key_value_info *kvi = ctx->kvi;
19812023
unsigned key_id;
19822024

19832025
if (!strcmp(key, "core.commentchar"))
@@ -1989,8 +2031,136 @@ static void comment_char_callback(const char *key, const char *value,
19892031

19902032
config->last_key_id = key_id;
19912033
config->auto_set = value && !strcmp(value, "auto");
2034+
if (kvi->origin_type != CONFIG_ORIGIN_FILE) {
2035+
return;
2036+
} else if (get_comment_key_flags(config, kvi->filename, key_id)) {
2037+
set_comment_key_flags(config, kvi->filename, key_id,
2038+
KEY_SEEN_TWICE);
2039+
} else {
2040+
struct comment_char_config_item *item;
2041+
2042+
ALLOC_GROW_BY(config->item, config->nr, 1, config->alloc);
2043+
item = &config->item[config->nr - 1];
2044+
item->key_id = key_id;
2045+
item->scope = kvi->scope;
2046+
item->path = xstrdup(kvi->filename);
2047+
set_comment_key_flags(config, kvi->filename, key_id,
2048+
KEY_SEEN_ONCE);
2049+
}
2050+
config->auto_set_in_file = config->auto_set;
19922051
}
19932052

2053+
static void add_config_scope_arg(struct repository *repo, struct strbuf *buf,
2054+
struct comment_char_config_item *item)
2055+
{
2056+
char *global_config = git_global_config();
2057+
char *system_config = git_system_config();
2058+
2059+
if (item->scope == CONFIG_SCOPE_SYSTEM && access(item->path, W_OK)) {
2060+
/*
2061+
* If the user cannot write to the system config recommend
2062+
* setting the global config instead.
2063+
*/
2064+
strbuf_addstr(buf, "--global ");
2065+
} else if (fspatheq(item->path, system_config)) {
2066+
strbuf_addstr(buf, "--system ");
2067+
} else if (fspatheq(item->path, global_config)) {
2068+
strbuf_addstr(buf, "--global ");
2069+
} else if (fspatheq(item->path,
2070+
mkpath("%s/config",
2071+
repo_get_git_dir(repo)))) {
2072+
; /* --local is the default */
2073+
} else if (fspatheq(item->path,
2074+
mkpath("%s/config.worktree",
2075+
repo_get_common_dir(repo)))) {
2076+
strbuf_addstr(buf, "--worktree ");
2077+
} else {
2078+
const char *path = item->path;
2079+
const char *home = getenv("HOME");
2080+
2081+
strbuf_addstr(buf, "--file ");
2082+
if (home && !fspathncmp(path, home, strlen(home))) {
2083+
path += strlen(home);
2084+
if (!fspathncmp(path, "/", 1))
2085+
path++;
2086+
strbuf_addstr(buf, "~/");
2087+
}
2088+
sq_quote_buf_pretty(buf, path);
2089+
strbuf_addch(buf, ' ');
2090+
}
2091+
2092+
free(global_config);
2093+
free(system_config);
2094+
}
2095+
2096+
static bool can_unset_comment_char_config(struct comment_char_config *config)
2097+
{
2098+
for (size_t i = 0; i < config->nr; i++) {
2099+
struct comment_char_config_item *item = &config->item[i];
2100+
2101+
if (item->scope == CONFIG_SCOPE_SYSTEM &&
2102+
access(item->path, W_OK))
2103+
return false;
2104+
}
2105+
2106+
return true;
2107+
}
2108+
2109+
static void add_unset_auto_comment_char_advice(struct repository *repo,
2110+
struct comment_char_config *config)
2111+
{
2112+
struct strbuf buf = STRBUF_INIT;
2113+
2114+
if (!can_unset_comment_char_config(config))
2115+
return;
2116+
2117+
for (size_t i = 0; i < config->nr; i++) {
2118+
struct comment_char_config_item *item = &config->item[i];
2119+
2120+
strbuf_addstr(&buf, " git config unset ");
2121+
add_config_scope_arg(repo, &buf, item);
2122+
if (get_comment_key_flags(config, item->path, item->key_id) == KEY_SEEN_TWICE)
2123+
strbuf_addstr(&buf, "--all ");
2124+
strbuf_addf(&buf, "%s\n", comment_key_name(item->key_id));
2125+
}
2126+
advise(_("\nTo use the default comment string (#) please run\n\n%s"),
2127+
buf.buf);
2128+
strbuf_release(&buf);
2129+
}
2130+
2131+
static void add_comment_char_advice(struct repository *repo,
2132+
struct comment_char_config *config)
2133+
{
2134+
struct strbuf buf = STRBUF_INIT;
2135+
struct comment_char_config_item *item;
2136+
/* TRANSLATORS this is a place holder for the value of core.commentString */
2137+
const char *placeholder = _("<comment string>");
2138+
2139+
/*
2140+
* If auto is set in the last file that we saw advise the user how to
2141+
* update their config.
2142+
*/
2143+
if (!config->auto_set_in_file)
2144+
return;
2145+
2146+
add_unset_auto_comment_char_advice(repo, config);
2147+
item = &config->item[config->nr - 1];
2148+
strbuf_reset(&buf);
2149+
strbuf_addstr(&buf, " git config set ");
2150+
add_config_scope_arg(repo, &buf, item);
2151+
strbuf_addf(&buf, "%s %s\n", comment_key_name(item->key_id),
2152+
placeholder);
2153+
advise(_("\nTo set a custom comment string please run\n\n"
2154+
"%s\nwhere '%s' is the string you wish to use.\n"),
2155+
buf.buf, placeholder);
2156+
strbuf_release(&buf);
2157+
}
2158+
2159+
#undef KEY_SEEN_ONCE
2160+
#undef KEY_SEEN_TWICE
2161+
#undef COMMENT_KEY_SHIFT
2162+
#undef COMMENT_KEY_MASK
2163+
19942164
struct repo_config {
19952165
struct repository *repo;
19962166
struct comment_char_config comment_char_config;
@@ -2001,18 +2171,26 @@ struct repo_config {
20012171
.repo = repo_, \
20022172
};
20032173

2174+
static void repo_config_release(struct repo_config *config)
2175+
{
2176+
comment_char_config_release(&config->comment_char_config);
2177+
}
2178+
20042179
#ifdef WITH_BREAKING_CHANGES
2005-
static void check_auto_comment_char_config(struct comment_char_config *config)
2180+
static void check_auto_comment_char_config(struct repository *repo,
2181+
struct comment_char_config *config)
20062182
{
20072183
if (!config->auto_set)
20082184
return;
20092185

20102186
die_message(_("Support for '%s=auto' has been removed in Git 3.0"),
20112187
comment_key_name(config->last_key_id));
2188+
add_comment_char_advice(repo, config);
20122189
die(NULL);
20132190
}
20142191
#else
2015-
static void check_auto_comment_char_config(struct comment_char_config *config)
2192+
static void check_auto_comment_char_config(struct repository *repo,
2193+
struct comment_char_config *config)
20162194
{
20172195
extern bool warn_on_auto_comment_char;
20182196
const char *DEPRECATED_CONFIG_ENV =
@@ -2032,6 +2210,7 @@ static void check_auto_comment_char_config(struct comment_char_config *config)
20322210

20332211
warning(_("Support for '%s=auto' is deprecated and will be removed in "
20342212
"Git 3.0"), comment_key_name(config->last_key_id));
2213+
add_comment_char_advice(repo, config);
20352214
}
20362215
#endif /* WITH_BREAKING_CHANGES */
20372216

@@ -2040,7 +2219,8 @@ static void check_deprecated_config(struct repo_config *config)
20402219
if (!config->repo->check_deprecated_config)
20412220
return;
20422221

2043-
check_auto_comment_char_config(&config->comment_char_config);
2222+
check_auto_comment_char_config(config->repo,
2223+
&config->comment_char_config);
20442224
}
20452225

20462226
static int repo_config_callback(const char *key, const char *value,
@@ -2083,6 +2263,7 @@ static void repo_read_config(struct repository *repo)
20832263
*/
20842264
die(_("unknown error occurred while reading the configuration files"));
20852265
check_deprecated_config(&config);
2266+
repo_config_release(&config);
20862267
}
20872268

20882269
static void git_config_check_init(struct repository *repo)

t/t3404-rebase-interactive.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1186,9 +1186,19 @@ test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=
11861186
test_set_editor "$(pwd)/copy-edit-script.sh" &&
11871187
git rebase -i HEAD^ 2>err
11881188
) &&
1189-
sed -n "s/^warning: //p" err >actual &&
1189+
sed -n "s/^hint: *\$//p; s/^hint: //p; s/^warning: //p" err >actual &&
11901190
cat >expect <<-EOF &&
11911191
Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0
1192+
1193+
To use the default comment string (#) please run
1194+
1195+
git config unset core.commentChar
1196+
1197+
To set a custom comment string please run
1198+
1199+
git config set core.commentChar <comment string>
1200+
1201+
where ${SQ}<comment string>${SQ} is the string you wish to use.
11921202
EOF
11931203
test_cmp expect actual &&
11941204
test -z "$(grep -ve "^#" -e "^\$" -e "^pick" edit-script)"

t/t7502-commit-porcelain.sh

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -958,10 +958,31 @@ test_expect_success 'commit --status with custom comment character' '
958958

959959
test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' '
960960
test_commit "#foo" foo &&
961-
GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend 2>err &&
962-
sed -n "s/^warning: //p" err >actual &&
961+
cat >config-include <<-\EOF &&
962+
[core]
963+
commentString=:
964+
commentString=%
965+
commentChar=auto
966+
EOF
967+
test_when_finished "rm config-include" &&
968+
test_config include.path "$(pwd)/config-include" &&
969+
test_config core.commentChar ! &&
970+
GIT_EDITOR=.git/FAKE_EDITOR git commit --amend 2>err &&
971+
sed -n "s/^hint: *\$//p; s/^hint: //p; s/^warning: //p" err >actual &&
963972
cat >expect <<-EOF &&
964973
Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0
974+
975+
To use the default comment string (#) please run
976+
977+
git config unset core.commentChar
978+
git config unset --file ~/config-include --all core.commentString
979+
git config unset --file ~/config-include core.commentChar
980+
981+
To set a custom comment string please run
982+
983+
git config set --file ~/config-include core.commentChar <comment string>
984+
985+
where ${SQ}<comment string>${SQ} is the string you wish to use.
965986
EOF
966987
test_cmp expect actual &&
967988
test_grep "^; Changes to be committed:" .git/COMMIT_EDITMSG
@@ -990,9 +1011,19 @@ EOF
9901011
test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '
9911012
test_config core.commentChar auto &&
9921013
test_must_fail git rev-parse --git-dir 2>err &&
993-
sed -n "s/^fatal: //p" err >actual &&
1014+
sed -n "s/^hint: *\$//p; s/^hint: //p; s/^fatal: //p" err >actual &&
9941015
cat >expect <<-EOF &&
9951016
Support for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0
1017+
1018+
To use the default comment string (#) please run
1019+
1020+
git config unset core.commentChar
1021+
1022+
To set a custom comment string please run
1023+
1024+
git config set core.commentChar <comment string>
1025+
1026+
where ${SQ}<comment string>${SQ} is the string you wish to use.
9961027
EOF
9971028
test_cmp expect actual
9981029
'

0 commit comments

Comments
 (0)