Skip to content

Commit a957f46

Browse files
committed
commit: print advice when core.commentString=auto
Support for this config setting was deprecated in the last commit so print some advice to help the user update their config settings when they are using this setting. The advice message explains that the setting is deprecated and will be removed in future. It also shows the commands that the user needs to run to either unset core.commentChar and core.commentString completely or to change the current setting to a fixed comment string. In order to generate this advice we need to parse the config with a callback that records each file where either of the 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. The hard coding of some filenames in add_config_scope_arg() is unfortunate but as this temporary code that will be removed when Git 3.0 is released I decided it wasn't worth adding functions to get the name of the local and worktree config files. 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]>
1 parent 8053c27 commit a957f46

File tree

12 files changed

+369
-5
lines changed

12 files changed

+369
-5
lines changed

builtin/commit.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,6 +1783,9 @@ int cmd_commit(int argc,
17831783
show_usage_with_options_if_asked(argc, argv,
17841784
builtin_commit_usage, builtin_commit_options);
17851785

1786+
#ifndef WITH_BREAKING_CHANGES
1787+
warn_on_auto_comment_char = true;
1788+
#endif /* WITH_BREAKING_CHANGES */
17861789
prepare_repo_settings(the_repository);
17871790
the_repository->settings.command_requires_full_index = 0;
17881791

builtin/merge.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,6 +1378,9 @@ int cmd_merge(int argc,
13781378
show_usage_with_options_if_asked(argc, argv,
13791379
builtin_merge_usage, builtin_merge_options);
13801380

1381+
#ifndef WITH_BREAKING_CHANGES
1382+
warn_on_auto_comment_char = true;
1383+
#endif /* WITH_BREAKING_CHANGES */
13811384
prepare_repo_settings(the_repository);
13821385
the_repository->settings.command_requires_full_index = 0;
13831386

builtin/rebase.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,9 @@ int cmd_rebase(int argc,
12421242
builtin_rebase_usage,
12431243
builtin_rebase_options);
12441244

1245+
#ifndef WITH_BREAKING_CHANGES
1246+
warn_on_auto_comment_char = true;
1247+
#endif /* WITH_BREAKING_CHANGES */
12451248
prepare_repo_settings(the_repository);
12461249
the_repository->settings.command_requires_full_index = 0;
12471250

builtin/revert.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "builtin.h"
55
#include "parse-options.h"
66
#include "diff.h"
7+
#include "environment.h"
78
#include "gettext.h"
89
#include "revision.h"
910
#include "rerere.h"
@@ -285,6 +286,9 @@ int cmd_revert(int argc,
285286
struct replay_opts opts = REPLAY_OPTS_INIT;
286287
int res;
287288

289+
#ifndef WITH_BREAKING_CHANGES
290+
warn_on_auto_comment_char = true;
291+
#endif /* WITH_BREAKING_CHANGES */
288292
opts.action = REPLAY_REVERT;
289293
sequencer_init_config(&opts);
290294
res = run_sequencer(argc, argv, prefix, &opts);
@@ -302,6 +306,9 @@ struct repository *repo UNUSED)
302306
struct replay_opts opts = REPLAY_OPTS_INIT;
303307
int res;
304308

309+
#ifndef WITH_BREAKING_CHANGES
310+
warn_on_auto_comment_char = true;
311+
#endif /* WITH_BREAKING_CHANGES */
305312
opts.action = REPLAY_PICK;
306313
sequencer_init_config(&opts);
307314
res = run_sequencer(argc, argv, prefix, &opts);

config.c

Lines changed: 279 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "date.h"
1616
#include "branch.h"
1717
#include "config.h"
18+
#include "dir.h"
1819
#include "parse.h"
1920
#include "convert.h"
2021
#include "environment.h"
@@ -2470,10 +2471,276 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d
24702471
return 1;
24712472
}
24722473

2474+
struct comment_char_config {
2475+
unsigned last_key_id;
2476+
bool auto_set;
2477+
bool auto_set_in_file;
2478+
struct strintmap key_flags;
2479+
size_t alloc, nr;
2480+
struct comment_char_config_item {
2481+
unsigned key_id;
2482+
char *path;
2483+
enum config_scope scope;
2484+
} *item;
2485+
struct repository *repo;
2486+
};
2487+
2488+
#define COMMENT_CHAR_CFG_INIT { \
2489+
.key_flags = STRINTMAP_INIT, \
2490+
}
2491+
2492+
static void comment_char_config_release(struct comment_char_config *config)
2493+
{
2494+
strintmap_clear(&config->key_flags);
2495+
for (size_t i = 0; i < config->nr; i++)
2496+
free(config->item[i].path);
2497+
free(config->item);
2498+
}
2499+
2500+
/* Used to track whether the key occurs more than once in a given file */
2501+
#define KEY_SEEN_ONCE 1u
2502+
#define KEY_SEEN_TWICE 2u
2503+
#define COMMENT_KEY_SHIFT(id) (2 * (id))
2504+
#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id))
2505+
2506+
static void set_comment_key_flags(struct comment_char_config *config,
2507+
const char *path, unsigned id, unsigned value)
2508+
{
2509+
unsigned old = strintmap_get(&config->key_flags, path);
2510+
unsigned new = (old & ~COMMENT_KEY_MASK(id)) |
2511+
value << COMMENT_KEY_SHIFT(id);
2512+
2513+
strintmap_set(&config->key_flags, path, new);
2514+
}
2515+
2516+
static unsigned get_comment_key_flags(struct comment_char_config *config,
2517+
const char *path, unsigned id)
2518+
{
2519+
unsigned value = strintmap_get(&config->key_flags, path);
2520+
2521+
return (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id);
2522+
}
2523+
2524+
static const char* comment_key_name(unsigned id)
2525+
{
2526+
static const char *name[] = {
2527+
"core.commentChar",
2528+
"core.commentString",
2529+
};
2530+
2531+
if (id >= ARRAY_SIZE(name))
2532+
BUG("invalid comment key id");
2533+
2534+
return name[id];
2535+
}
2536+
2537+
static void comment_char_callback(const char *key, const char *value,
2538+
const struct config_context *ctx, void *data)
2539+
{
2540+
struct comment_char_config *config = data;
2541+
const struct key_value_info *kvi = ctx->kvi;
2542+
unsigned key_id;
2543+
2544+
if (!strcmp(key, "core.commentchar"))
2545+
key_id = 0;
2546+
else if (!strcmp(key, "core.commentstring"))
2547+
key_id = 1;
2548+
else
2549+
return;
2550+
2551+
config->last_key_id = key_id;
2552+
config->auto_set = value && !strcmp(value, "auto");
2553+
if (kvi->origin_type != CONFIG_ORIGIN_FILE) {
2554+
return;
2555+
} else if (get_comment_key_flags(config, kvi->filename, key_id)) {
2556+
set_comment_key_flags(config, kvi->filename, key_id,
2557+
KEY_SEEN_TWICE);
2558+
} else {
2559+
struct comment_char_config_item *item;
2560+
2561+
ALLOC_GROW_BY(config->item, config->nr, 1, config->alloc);
2562+
item = &config->item[config->nr - 1];
2563+
item->key_id = key_id;
2564+
item->scope = kvi->scope;
2565+
item->path = xstrdup(kvi->filename);
2566+
set_comment_key_flags(config, kvi->filename, key_id,
2567+
KEY_SEEN_ONCE);
2568+
}
2569+
config->auto_set_in_file = config->auto_set;
2570+
}
2571+
2572+
static void add_config_scope_arg(struct strbuf *buf,
2573+
struct comment_char_config_item *item)
2574+
{
2575+
char *global_config = git_global_config();
2576+
char *system_config = git_system_config();
2577+
2578+
if (item->scope == CONFIG_SCOPE_SYSTEM && access(item->path, W_OK)) {
2579+
/*
2580+
* If the user cannot write to the system config recommend
2581+
* setting the global config instead.
2582+
*/
2583+
strbuf_addstr(buf, "--global ");
2584+
} else if (fspatheq(item->path, system_config)) {
2585+
strbuf_addstr(buf, "--system ");
2586+
} else if (fspatheq(item->path, global_config)) {
2587+
strbuf_addstr(buf, "--global ");
2588+
} else if (fspatheq(item->path,
2589+
mkpath("%s/config",
2590+
repo_get_git_dir(the_repository)))) {
2591+
; /* --local is the default */
2592+
} else if (fspatheq(item->path,
2593+
mkpath("%s/config.worktree",
2594+
repo_get_common_dir(the_repository)))) {
2595+
strbuf_addstr(buf, "--worktree ");
2596+
} else {
2597+
const char *path = item->path;
2598+
const char *home = getenv("HOME");
2599+
2600+
strbuf_addstr(buf, "--file ");
2601+
if (home && !fspathncmp(path, home, strlen(home))) {
2602+
path += strlen(home);
2603+
if (!fspathncmp(path, "/", 1))
2604+
path++;
2605+
strbuf_addstr(buf, "~/");
2606+
}
2607+
sq_quote_buf_pretty(buf, path);
2608+
strbuf_addch(buf, ' ');
2609+
}
2610+
2611+
free(global_config);
2612+
free(system_config);
2613+
}
2614+
2615+
static bool can_unset_comment_char_config(struct comment_char_config *config)
2616+
{
2617+
for (size_t i = 0; i < config->nr; i++) {
2618+
struct comment_char_config_item *item = &config->item[i];
2619+
2620+
if (item->scope == CONFIG_SCOPE_SYSTEM &&
2621+
access(item->path, W_OK))
2622+
return false;
2623+
}
2624+
2625+
return true;
2626+
}
2627+
2628+
static void add_unset_auto_comment_char_advice(struct comment_char_config *config)
2629+
{
2630+
struct strbuf buf = STRBUF_INIT;
2631+
2632+
if (!can_unset_comment_char_config(config))
2633+
return;
2634+
2635+
for (size_t i = 0; i < config->nr; i++) {
2636+
struct comment_char_config_item *item = &config->item[i];
2637+
2638+
strbuf_addstr(&buf, " git config unset ");
2639+
add_config_scope_arg(&buf, item);
2640+
if (get_comment_key_flags(config, item->path, item->key_id) == KEY_SEEN_TWICE)
2641+
strbuf_addstr(&buf, "--all ");
2642+
strbuf_addf(&buf, "%s\n", comment_key_name(item->key_id));
2643+
}
2644+
advise(_("\nTo use the default comment string (#) please run\n\n%s"),
2645+
buf.buf);
2646+
strbuf_release(&buf);
2647+
}
2648+
2649+
static void add_comment_char_advice(struct comment_char_config *config)
2650+
{
2651+
struct strbuf buf = STRBUF_INIT;
2652+
struct comment_char_config_item *item;
2653+
/* TRANSLATORS this is a place holder for the value of core.commentString */
2654+
const char *placeholder = _("<comment string>");
2655+
2656+
/*
2657+
* If auto is set in the last file that we saw advise the user how to
2658+
* update their config.
2659+
*/
2660+
if (!config->auto_set_in_file)
2661+
return;
2662+
2663+
add_unset_auto_comment_char_advice(config);
2664+
item = &config->item[config->nr - 1];
2665+
strbuf_reset(&buf);
2666+
strbuf_addstr(&buf, " git config set ");
2667+
add_config_scope_arg(&buf, item);
2668+
strbuf_addf(&buf, "%s %s\n", comment_key_name(item->key_id),
2669+
placeholder);
2670+
advise(_("\nTo set a custom comment string please run\n\n"
2671+
"%s\nwhere '%s' is the string you wish to use.\n"),
2672+
buf.buf, placeholder);
2673+
strbuf_release(&buf);
2674+
}
2675+
2676+
#undef KEY_SEEN_ONCE
2677+
#undef KEY_SEEN_TWICE
2678+
#undef COMMENT_KEY_SHIFT
2679+
#undef COMMENT_KEY_MASK
2680+
2681+
struct repo_config {
2682+
struct repository *repo;
2683+
struct comment_char_config comment_char_config;
2684+
};
2685+
2686+
#define REPO_CONFIG_INIT(repo_) { \
2687+
.comment_char_config = COMMENT_CHAR_CFG_INIT, \
2688+
.repo = repo_, \
2689+
};
2690+
2691+
static void repo_config_release(struct repo_config *config)
2692+
{
2693+
comment_char_config_release(&config->comment_char_config);
2694+
}
2695+
2696+
static void check_auto_comment_str_config(struct comment_char_config *config)
2697+
{
2698+
if (!config->auto_set)
2699+
return;
2700+
2701+
#ifndef WITH_BREAKING_CHANGES
2702+
# define DEPRECATED_CONFIG_ENV "GIT_DEPRECATED_CONFIG_WARNING_GIVEN"
2703+
if (!warn_on_auto_comment_char ||
2704+
git_env_bool(DEPRECATED_CONFIG_ENV, 0))
2705+
return;
2706+
2707+
setenv(DEPRECATED_CONFIG_ENV, "true", 1);
2708+
warning(_("Support for '%s=auto' is deprecated and will be removed in "
2709+
"Git 3.0"), comment_key_name(config->last_key_id));
2710+
# undef DEPRECATED_CONFIG_ENV
2711+
#else
2712+
die_message(_("Support for '%s=auto' has been removed in Git 3.0"),
2713+
comment_key_name(config->last_key_id));
2714+
#endif /* WITH_BREAKING_CHANGES */
2715+
add_comment_char_advice(config);
2716+
#ifdef WITH_BREAKING_CHANGES
2717+
die(NULL);
2718+
#endif /* WITH_BREAKING_CHANGES */
2719+
}
2720+
2721+
static void check_deprecated_config(struct repo_config *config)
2722+
{
2723+
#ifdef WITH_BREAKING_CHANGES
2724+
if (!config->repo->check_deprecated_config)
2725+
return;
2726+
#endif /* WITH_BREAKING_CHANGES */
2727+
check_auto_comment_str_config(&config->comment_char_config);
2728+
}
2729+
2730+
static int repo_config_callback(const char *key, const char *value,
2731+
const struct config_context *ctx, void *data)
2732+
{
2733+
struct repo_config *config = data;
2734+
2735+
comment_char_callback(key, value, ctx, &config->comment_char_config);
2736+
return config_set_callback(key, value, ctx, config->repo->config);
2737+
}
2738+
24732739
/* Functions use to read configuration from a repository */
24742740
static void repo_read_config(struct repository *repo)
24752741
{
24762742
struct config_options opts = { 0 };
2743+
struct repo_config config = REPO_CONFIG_INIT(repo);
24772744

24782745
opts.respect_includes = 1;
24792746
opts.commondir = repo->commondir;
@@ -2485,8 +2752,8 @@ static void repo_read_config(struct repository *repo)
24852752
git_configset_clear(repo->config);
24862753

24872754
git_configset_init(repo->config);
2488-
if (config_with_options(config_set_callback, repo->config, NULL,
2489-
repo, &opts) < 0)
2755+
if (config_with_options(repo_config_callback, &config, NULL, repo,
2756+
&opts) < 0)
24902757
/*
24912758
* config_with_options() normally returns only
24922759
* zero, as most errors are fatal, and
@@ -2499,6 +2766,8 @@ static void repo_read_config(struct repository *repo)
24992766
* immediately.
25002767
*/
25012768
die(_("unknown error occurred while reading the configuration files"));
2769+
check_deprecated_config(&config);
2770+
repo_config_release(&config);
25022771
}
25032772

25042773
static void git_config_check_init(struct repository *repo)
@@ -3185,6 +3454,11 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
31853454
char *contents = NULL;
31863455
size_t contents_sz;
31873456
struct config_store_data store = CONFIG_STORE_INIT;
3457+
#ifdef WITH_BREAKING_CHANGES
3458+
bool saved_check_deprecated_config = r->check_deprecated_config;
3459+
3460+
r->check_deprecated_config = false;
3461+
#endif /* WITH_BREAKING_CHANGES */
31883462

31893463
validate_comment_string(comment);
31903464

@@ -3415,6 +3689,9 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
34153689
if (in_fd >= 0)
34163690
close(in_fd);
34173691
config_store_data_clear(&store);
3692+
#ifdef WITH_BREAKING_CHANGES
3693+
r->check_deprecated_config = saved_check_deprecated_config;
3694+
#endif /* WITH_BREAKING_CHANGES */
34183695
return ret;
34193696

34203697
write_err_out:

environment.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ const char *comment_line_str = "#";
112112
char *comment_line_str_to_free;
113113
#ifndef WITH_BREAKING_CHANGES
114114
int auto_comment_line_char;
115+
bool warn_on_auto_comment_char;
115116
#endif /* WITH_BREAKING_CHANGES */
116117

117118
/* This is set by setup_git_directory_gently() and/or git_default_config() */

0 commit comments

Comments
 (0)