Introduce the ability to append comments to modifications
made using git-config. Example usage:
git config --comment "changed via script" \
--add safe.directory /home/alice/repo.git
based on the proposed patch, the output produced is:
[safe]
directory = /home/alice/repo.git #changed via script
Users need to be able to distinguish between config entries made
using automation and entries made by a human. Automation can add
comments containing a URL pointing to explanations for the change
made, avoiding questions from users as to why their config file
was changed by a third party.
The implementation ensures that a # character is unconditionally
prepended to the provided comment string, and that the comment
text is appended as a suffix to the changed key-value-pair in the
same line of text. Multi-line comments (i.e. comments containing
linefeed) are rejected as errors, causing Git to exit without
making changes.
Comments are aimed at humans who inspect or change their Git
config using a pager or editor. Comments are not meant to be
read or displayed by git-config at a later time.
Signed-off-by: Ralph Seichter <github@seichter.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Ralph Seichter committedMar 12, 2024 at 21:47 UTC42d5c033945e4fc41d7268bfe4284d37651986b8
12 files changed+78-45
Documentation/git-config.txt
+8-3
index dff39093b5..e608d5ffef 100644--- a/Documentation/git-config.txt+++ b/Documentation/git-config.txt@@ -9,9 +9,9 @@ git-config - Get and set repository or global options SYNOPSIS -------- [verse]-'git config' [<file-option>] [--type=<type>] [--fixed-value] [--show-origin] [--show-scope] [-z|--null] <name> [<value> [<value-pattern>]]-'git config' [<file-option>] [--type=<type>] --add <name> <value>-'git config' [<file-option>] [--type=<type>] [--fixed-value] --replace-all <name> <value> [<value-pattern>]+'git config' [<file-option>] [--type=<type>] [--comment=<value>] [--fixed-value] [--show-origin] [--show-scope] [-z|--null] <name> [<value> [<value-pattern>]]+'git config' [<file-option>] [--type=<type>] [--comment=<value>] --add <name> <value>+'git config' [<file-option>] [--type=<type>] [--comment=<value>] [--fixed-value] --replace-all <name> <value> [<value-pattern>] 'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get <name> [<value-pattern>] 'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get-all <name> [<value-pattern>] 'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] [--name-only] --get-regexp <name-regex> [<value-pattern>]@@ -87,6 +87,11 @@ OPTIONS values. This is the same as providing '^$' as the `value-pattern` in `--replace-all`.+--comment <value>::+ Append a comment to new or modified lines. A '#' character will be+ unconditionally prepended to the value. The value must not contain+ linefeed characters (no multi-line comments are permitted).+ --get:: Get the value for a given key (optionally filtered by a regex matching the value). Returns error code 1 if the key was not
builtin/config.c
+15-7
index b55bfae7d6..c54e9941a5 100644--- a/builtin/config.c+++ b/builtin/config.c@@ -44,6 +44,7 @@ static struct config_options config_options; static int show_origin; static int show_scope; static int fixed_value;+static const char *comment; #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1)@@ -173,6 +174,7 @@ static struct option builtin_config_options[] = { OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")), OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")), OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),+ OPT_STRING(0, "comment", &comment, N_("value"), N_("human-readable comment string (# will be prepended automatically)")), OPT_END(), };@@ -797,6 +799,12 @@ int cmd_config(int argc, const char **argv, const char *prefix) usage_builtin_config(); }+ if (comment &&+ !(actions & (ACTION_ADD|ACTION_SET|ACTION_SET_ALL|ACTION_REPLACE_ALL))) {+ error(_("--comment is only applicable to add/set/replace operations"));+ usage_builtin_config();+ }+ /* check usage of --fixed-value */ if (fixed_value) { int allowed_usage = 0;@@ -880,7 +888,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1], &default_kvi);- ret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);+ ret = git_config_set_in_file_gently(given_config_source.file, argv[0], comment, value); if (ret == CONFIG_NOTHING_SET) error(_("cannot overwrite multiple values with a single value\n" " Use a regexp, --add or --replace-all to change %s."), argv[0]);@@ -891,7 +899,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) value = normalize_value(argv[0], argv[1], &default_kvi); ret = git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, argv[2],- flags);+ comment, flags); } else if (actions == ACTION_ADD) { check_write();@@ -900,7 +908,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) ret = git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, CONFIG_REGEX_NONE,- flags);+ comment, flags); } else if (actions == ACTION_REPLACE_ALL) { check_write();@@ -908,7 +916,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) value = normalize_value(argv[0], argv[1], &default_kvi); ret = git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, argv[2],- flags | CONFIG_FLAGS_MULTI_REPLACE);+ comment, flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_GET) { check_argc(argc, 1, 2);@@ -936,17 +944,17 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (argc == 2) return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], NULL, argv[1],- flags);+ NULL, flags); else return git_config_set_in_file_gently(given_config_source.file,- argv[0], NULL);+ argv[0], NULL, NULL); } else if (actions == ACTION_UNSET_ALL) { check_write(); check_argc(argc, 1, 2); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], NULL, argv[1],- flags | CONFIG_FLAGS_MULTI_REPLACE);+ NULL, flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_RENAME_SECTION) { check_write();
builtin/gc.c
+2-2
index cb80ced6cb..342907f7bd 100644--- a/builtin/gc.c+++ b/builtin/gc.c@@ -1553,7 +1553,7 @@ static int maintenance_register(int argc, const char **argv, const char *prefix) die(_("$HOME not set")); rc = git_config_set_multivar_in_file_gently( config_file, "maintenance.repo", maintpath,- CONFIG_REGEX_NONE, 0);+ CONFIG_REGEX_NONE, NULL, 0); free(global_config_file); if (rc)@@ -1620,7 +1620,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi if (!config_file) die(_("$HOME not set")); rc = git_config_set_multivar_in_file_gently(- config_file, key, NULL, maintpath,+ config_file, key, NULL, maintpath, NULL, CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE); free(global_config_file);
builtin/submodule--helper.c
+1-1
index fda50f2af1..e4e18adb57 100644--- a/builtin/submodule--helper.c+++ b/builtin/submodule--helper.c@@ -1283,7 +1283,7 @@ static void sync_submodule(const char *path, const char *prefix, submodule_to_gitdir(&sb, path); strbuf_addstr(&sb, "/config");- if (git_config_set_in_file_gently(sb.buf, remote_key, sub_origin_url))+ if (git_config_set_in_file_gently(sb.buf, remote_key, NULL, sub_origin_url)) die(_("failed to update remote for submodule '%s'"), path);
builtin/worktree.c
+2-2
index 9c76b62b02..a20cc8820e 100644--- a/builtin/worktree.c+++ b/builtin/worktree.c@@ -365,12 +365,12 @@ static void copy_filtered_worktree_config(const char *worktree_git_dir) if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare && git_config_set_multivar_in_file_gently(- to_file, "core.bare", NULL, "true", 0))+ to_file, "core.bare", NULL, "true", NULL, 0)) error(_("failed to unset '%s' in '%s'"), "core.bare", to_file); if (!git_configset_get(&cs, "core.worktree") && git_config_set_in_file_gently(to_file,- "core.worktree", NULL))+ "core.worktree", NULL, NULL)) error(_("failed to unset '%s' in '%s'"), "core.worktree", to_file);
index ea1441e617..8002b9a2ac 100644--- a/sequencer.c+++ b/sequencer.c@@ -3462,54 +3462,54 @@ static int save_opts(struct replay_opts *opts) if (opts->no_commit) res |= git_config_set_in_file_gently(opts_file,- "options.no-commit", "true");+ "options.no-commit", NULL, "true"); if (opts->edit >= 0)- res |= git_config_set_in_file_gently(opts_file, "options.edit",+ res |= git_config_set_in_file_gently(opts_file, "options.edit", NULL, opts->edit ? "true" : "false"); if (opts->allow_empty) res |= git_config_set_in_file_gently(opts_file,- "options.allow-empty", "true");+ "options.allow-empty", NULL, "true"); if (opts->allow_empty_message) res |= git_config_set_in_file_gently(opts_file,- "options.allow-empty-message", "true");+ "options.allow-empty-message", NULL, "true"); if (opts->keep_redundant_commits) res |= git_config_set_in_file_gently(opts_file,- "options.keep-redundant-commits", "true");+ "options.keep-redundant-commits", NULL, "true"); if (opts->signoff) res |= git_config_set_in_file_gently(opts_file,- "options.signoff", "true");+ "options.signoff", NULL, "true"); if (opts->record_origin) res |= git_config_set_in_file_gently(opts_file,- "options.record-origin", "true");+ "options.record-origin", NULL, "true"); if (opts->allow_ff) res |= git_config_set_in_file_gently(opts_file,- "options.allow-ff", "true");+ "options.allow-ff", NULL, "true"); if (opts->mainline) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%d", opts->mainline); res |= git_config_set_in_file_gently(opts_file,- "options.mainline", buf.buf);+ "options.mainline", NULL, buf.buf); strbuf_release(&buf); } if (opts->strategy) res |= git_config_set_in_file_gently(opts_file,- "options.strategy", opts->strategy);+ "options.strategy", NULL, opts->strategy); if (opts->gpg_sign) res |= git_config_set_in_file_gently(opts_file,- "options.gpg-sign", opts->gpg_sign);+ "options.gpg-sign", NULL, opts->gpg_sign); for (size_t i = 0; i < opts->xopts.nr; i++) res |= git_config_set_multivar_in_file_gently(opts_file, "options.strategy-option",- opts->xopts.v[i], "^$", 0);+ opts->xopts.v[i], "^$", NULL, 0); if (opts->allow_rerere_auto) res |= git_config_set_in_file_gently(opts_file,- "options.allow-rerere-auto",+ "options.allow-rerere-auto", NULL, opts->allow_rerere_auto == RERERE_AUTOUPDATE ? "true" : "false"); if (opts->explicit_cleanup) res |= git_config_set_in_file_gently(opts_file,- "options.default-msg-cleanup",+ "options.default-msg-cleanup", NULL, describe_cleanup_mode(opts->default_msg_cleanup)); return res; }
submodule-config.c
+1-1
index 54130f6a38..11428b4ada 100644--- a/submodule-config.c+++ b/submodule-config.c@@ -978,7 +978,7 @@ int config_set_in_gitmodules_file_gently(const char *key, const char *value) { int ret;- ret = git_config_set_in_file_gently(GITMODULES_FILE, key, value);+ ret = git_config_set_in_file_gently(GITMODULES_FILE, key, NULL, value); if (ret < 0) /* Maybe the user already did that, don't error out here */ warning(_("Could not update .gitmodules entry %s"), key);
submodule.c
+1-1
index f0ddb31e8f..ce2d032521 100644--- a/submodule.c+++ b/submodule.c@@ -2046,7 +2046,7 @@ void submodule_unset_core_worktree(const struct submodule *sub) submodule_name_to_gitdir(&config_path, the_repository, sub->name); strbuf_addstr(&config_path, "/config");- if (git_config_set_in_file_gently(config_path.buf, "core.worktree", NULL))+ if (git_config_set_in_file_gently(config_path.buf, "core.worktree", NULL, NULL)) warning(_("Could not unset core.worktree setting in submodule '%s'"), sub->path);