]> git.ipfire.org Git - thirdparty/git.git/blobdiff - config.c
Git 2.47-rc0
[thirdparty/git.git] / config.c
index 9ff6ae1cb903a0690295a7d5bb452ef5f35c12e1..1266eab08607155c1fd4ce2a9734243839d3f7bc 100644 (file)
--- a/config.c
+++ b/config.c
@@ -5,6 +5,9 @@
  * Copyright (C) Johannes Schindelin, 2005
  *
  */
+
+#define USE_THE_REPOSITORY_VARIABLE
+
 #include "git-compat-util.h"
 #include "abspath.h"
 #include "advice.h"
@@ -95,7 +98,6 @@ static long config_file_ftell(struct config_source *conf)
        return ftell(conf->u.file);
 }
 
-
 static int config_buf_fgetc(struct config_source *conf)
 {
        if (conf->u.buf.pos < conf->u.buf.len)
@@ -126,7 +128,7 @@ struct config_include_data {
        config_fn_t fn;
        void *data;
        const struct config_options *opts;
-       struct git_config_source *config_source;
+       const struct git_config_source *config_source;
        struct repository *repo;
 
        /*
@@ -298,13 +300,15 @@ done:
        return ret;
 }
 
-static int include_by_branch(const char *cond, size_t cond_len)
+static int include_by_branch(struct config_include_data *data,
+                            const char *cond, size_t cond_len)
 {
        int flags;
        int ret;
        struct strbuf pattern = STRBUF_INIT;
-       const char *refname = !the_repository->gitdir ?
-               NULL : resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+       const char *refname = (!data->repo || !data->repo->gitdir) ?
+               NULL : refs_resolve_ref_unsafe(get_main_ref_store(data->repo),
+                                              "HEAD", 0, NULL, &flags);
        const char *shortname;
 
        if (!refname || !(flags & REF_ISSYMREF) ||
@@ -403,7 +407,7 @@ static int include_condition_is_true(const struct key_value_info *kvi,
        else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
                return include_by_gitdir(kvi, opts, cond, cond_len, 1);
        else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
-               return include_by_branch(cond, cond_len);
+               return include_by_branch(inc, cond, cond_len);
        else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
                                   &cond_len))
                return include_by_remote_url(inc, cond, cond_len);
@@ -818,7 +822,8 @@ static int get_next_char(struct config_source *cs)
 
 static char *parse_value(struct config_source *cs)
 {
-       int quote = 0, comment = 0, space = 0;
+       int quote = 0, comment = 0;
+       size_t trim_len = 0;
 
        strbuf_reset(&cs->value);
        for (;;) {
@@ -828,13 +833,17 @@ static char *parse_value(struct config_source *cs)
                                cs->linenr--;
                                return NULL;
                        }
+                       if (trim_len)
+                               strbuf_setlen(&cs->value, trim_len);
                        return cs->value.buf;
                }
                if (comment)
                        continue;
                if (isspace(c) && !quote) {
+                       if (!trim_len)
+                               trim_len = cs->value.len;
                        if (cs->value.len)
-                               space++;
+                               strbuf_addch(&cs->value, c);
                        continue;
                }
                if (!quote) {
@@ -843,8 +852,8 @@ static char *parse_value(struct config_source *cs)
                                continue;
                        }
                }
-               for (; space; space--)
-                       strbuf_addch(&cs->value, ' ');
+               if (trim_len)
+                       trim_len = 0;
                if (c == '\\') {
                        c = get_next_char(cs);
                        switch (c) {
@@ -870,7 +879,7 @@ static char *parse_value(struct config_source *cs)
                        continue;
                }
                if (c == '"') {
-                       quote = 1-quote;
+                       quote = 1 - quote;
                        continue;
                }
                strbuf_addch(&cs->value, c);
@@ -1239,6 +1248,15 @@ ssize_t git_config_ssize_t(const char *name, const char *value,
        return ret;
 }
 
+double git_config_double(const char *name, const char *value,
+                        const struct key_value_info *kvi)
+{
+       double ret;
+       if (!git_parse_double(value, &ret))
+               die_bad_number(name, value, kvi);
+       return ret;
+}
+
 static const struct fsync_component_name {
        const char *name;
        enum fsync_component component_bits;
@@ -1333,7 +1351,7 @@ int git_config_bool(const char *name, const char *value)
        return v;
 }
 
-int git_config_string(const char **dest, const char *var, const char *value)
+int git_config_string(char **dest, const char *var, const char *value)
 {
        if (!value)
                return config_error_nonbool(var);
@@ -1341,7 +1359,7 @@ int git_config_string(const char **dest, const char *var, const char *value)
        return 0;
 }
 
-int git_config_pathname(const char **dest, const char *var, const char *value)
+int git_config_pathname(char **dest, const char *var, const char *value)
 {
        if (!value)
                return config_error_nonbool(var);
@@ -1409,11 +1427,15 @@ static int git_default_core_config(const char *var, const char *value,
                return 0;
        }
 
-       if (!strcmp(var, "core.attributesfile"))
+       if (!strcmp(var, "core.attributesfile")) {
+               FREE_AND_NULL(git_attributes_file);
                return git_config_pathname(&git_attributes_file, var, value);
+       }
 
-       if (!strcmp(var, "core.hookspath"))
+       if (!strcmp(var, "core.hookspath")) {
+               FREE_AND_NULL(git_hooks_path);
                return git_config_pathname(&git_hooks_path, var, value);
+       }
 
        if (!strcmp(var, "core.bare")) {
                is_bare_repository_cfg = git_config_bool(var, value);
@@ -1425,36 +1447,16 @@ static int git_default_core_config(const char *var, const char *value,
                return 0;
        }
 
-       if (!strcmp(var, "core.prefersymlinkrefs")) {
-               prefer_symlink_refs = git_config_bool(var, value);
-               return 0;
-       }
-
-       if (!strcmp(var, "core.logallrefupdates")) {
-               if (value && !strcasecmp(value, "always"))
-                       log_all_ref_updates = LOG_REFS_ALWAYS;
-               else if (git_config_bool(var, value))
-                       log_all_ref_updates = LOG_REFS_NORMAL;
-               else
-                       log_all_ref_updates = LOG_REFS_NONE;
-               return 0;
-       }
-
-       if (!strcmp(var, "core.warnambiguousrefs")) {
-               warn_ambiguous_refs = git_config_bool(var, value);
-               return 0;
-       }
-
        if (!strcmp(var, "core.abbrev")) {
                if (!value)
                        return config_error_nonbool(var);
                if (!strcasecmp(value, "auto"))
                        default_abbrev = -1;
                else if (!git_parse_maybe_bool_text(value))
-                       default_abbrev = the_hash_algo->hexsz;
+                       default_abbrev = GIT_MAX_HEXSZ;
                else {
                        int abbrev = git_config_int(var, value, ctx->kvi);
-                       if (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)
+                       if (abbrev < minimum_abbrev)
                                return error(_("abbrev length out of range: %d"), abbrev);
                        default_abbrev = abbrev;
                }
@@ -1548,37 +1550,42 @@ static int git_default_core_config(const char *var, const char *value,
                return 0;
        }
 
-       if (!strcmp(var, "core.checkroundtripencoding"))
+       if (!strcmp(var, "core.checkroundtripencoding")) {
+               FREE_AND_NULL(check_roundtrip_encoding);
                return git_config_string(&check_roundtrip_encoding, var, value);
-
-       if (!strcmp(var, "core.notesref")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               notes_ref_name = xstrdup(value);
-               return 0;
        }
 
-       if (!strcmp(var, "core.editor"))
+       if (!strcmp(var, "core.editor")) {
+               FREE_AND_NULL(editor_program);
                return git_config_string(&editor_program, var, value);
+       }
 
-       if (!strcmp(var, "core.commentchar")) {
+       if (!strcmp(var, "core.commentchar") ||
+           !strcmp(var, "core.commentstring")) {
                if (!value)
                        return config_error_nonbool(var);
                else if (!strcasecmp(value, "auto"))
                        auto_comment_line_char = 1;
-               else if (value[0] && !value[1]) {
-                       comment_line_char = value[0];
+               else if (value[0]) {
+                       if (strchr(value, '\n'))
+                               return error(_("%s cannot contain newline"), var);
+                       comment_line_str = value;
+                       FREE_AND_NULL(comment_line_str_to_free);
                        auto_comment_line_char = 0;
                } else
-                       return error(_("core.commentChar should only be one ASCII character"));
+                       return error(_("%s must have at least one character"), var);
                return 0;
        }
 
-       if (!strcmp(var, "core.askpass"))
+       if (!strcmp(var, "core.askpass")) {
+               FREE_AND_NULL(askpass_program);
                return git_config_string(&askpass_program, var, value);
+       }
 
-       if (!strcmp(var, "core.excludesfile"))
+       if (!strcmp(var, "core.excludesfile")) {
+               FREE_AND_NULL(excludes_file);
                return git_config_pathname(&excludes_file, var, value);
+       }
 
        if (!strcmp(var, "core.whitespace")) {
                if (!value)
@@ -1679,11 +1686,15 @@ static int git_default_sparse_config(const char *var, const char *value)
 
 static int git_default_i18n_config(const char *var, const char *value)
 {
-       if (!strcmp(var, "i18n.commitencoding"))
+       if (!strcmp(var, "i18n.commitencoding")) {
+               FREE_AND_NULL(git_commit_encoding);
                return git_config_string(&git_commit_encoding, var, value);
+       }
 
-       if (!strcmp(var, "i18n.logoutputencoding"))
+       if (!strcmp(var, "i18n.logoutputencoding")) {
+               FREE_AND_NULL(git_log_output_encoding);
                return git_config_string(&git_log_output_encoding, var, value);
+       }
 
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
@@ -1756,10 +1767,15 @@ static int git_default_push_config(const char *var, const char *value)
 
 static int git_default_mailmap_config(const char *var, const char *value)
 {
-       if (!strcmp(var, "mailmap.file"))
+       if (!strcmp(var, "mailmap.file")) {
+               FREE_AND_NULL(git_mailmap_file);
                return git_config_pathname(&git_mailmap_file, var, value);
-       if (!strcmp(var, "mailmap.blob"))
+       }
+
+       if (!strcmp(var, "mailmap.blob")) {
+               FREE_AND_NULL(git_mailmap_blob);
                return git_config_string(&git_mailmap_blob, var, value);
+       }
 
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
@@ -1767,8 +1783,10 @@ static int git_default_mailmap_config(const char *var, const char *value)
 
 static int git_default_attr_config(const char *var, const char *value)
 {
-       if (!strcmp(var, "attr.tree"))
+       if (!strcmp(var, "attr.tree")) {
+               FREE_AND_NULL(git_attr_tree);
                return git_config_string(&git_attr_tree, var, value);
+       }
 
        /*
         * Add other attribute related config variables here and to
@@ -1988,7 +2006,27 @@ char *git_system_config(void)
        return system_config;
 }
 
-void git_global_config(char **user_out, char **xdg_out)
+char *git_global_config(void)
+{
+       char *user_config, *xdg_config;
+
+       git_global_config_paths(&user_config, &xdg_config);
+       if (!user_config) {
+               free(xdg_config);
+               return NULL;
+       }
+
+       if (access_or_warn(user_config, R_OK, 0) && xdg_config &&
+           !access_or_warn(xdg_config, R_OK, 0)) {
+               free(user_config);
+               return xdg_config;
+       } else {
+               free(xdg_config);
+               return user_config;
+       }
+}
+
+void git_global_config_paths(char **user_out, char **xdg_out)
 {
        char *user_config = xstrdup_or_null(getenv("GIT_CONFIG_GLOBAL"));
        char *xdg_config = NULL;
@@ -2041,7 +2079,7 @@ static int do_git_config_sequence(const struct config_options *opts,
                                                         data, CONFIG_SCOPE_SYSTEM,
                                                         NULL);
 
-       git_global_config(&user_config, &xdg_config);
+       git_global_config_paths(&user_config, &xdg_config);
 
        if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
                ret += git_config_from_file_with_options(fn, xdg_config, data,
@@ -2076,7 +2114,7 @@ static int do_git_config_sequence(const struct config_options *opts,
 }
 
 int config_with_options(config_fn_t fn, void *data,
-                       struct git_config_source *config_source,
+                       const struct git_config_source *config_source,
                        struct repository *repo,
                        const struct config_options *opts)
 {
@@ -2138,7 +2176,7 @@ static void configset_iter(struct config_set *set, config_fn_t fn, void *data)
        }
 }
 
-void read_early_config(config_fn_t cb, void *data)
+void read_early_config(struct repository *repo, config_fn_t cb, void *data)
 {
        struct config_options opts = {0};
        struct strbuf commondir = STRBUF_INIT;
@@ -2146,9 +2184,9 @@ void read_early_config(config_fn_t cb, void *data)
 
        opts.respect_includes = 1;
 
-       if (have_git_dir()) {
-               opts.commondir = get_git_common_dir();
-               opts.git_dir = get_git_dir();
+       if (repo && repo->gitdir) {
+               opts.commondir = repo_get_common_dir(repo);
+               opts.git_dir = repo_get_git_dir(repo);
        /*
         * When setup_git_directory() was not yet asked to discover the
         * GIT_DIR, we ask discover_git_directory() to figure out whether there
@@ -2168,10 +2206,6 @@ void read_early_config(config_fn_t cb, void *data)
        strbuf_release(&gitdir);
 }
 
-/*
- * Read config but only enumerate system and global settings.
- * Omit any repo-local, worktree-local, or command-line settings.
- */
 void read_very_early_config(config_fn_t cb, void *data)
 {
        struct config_options opts = { 0 };
@@ -2375,7 +2409,7 @@ int git_configset_get_string(struct config_set *set, const char *key, char **des
 {
        const char *value;
        if (!git_configset_get_value(set, key, &value, NULL))
-               return git_config_string((const char **)dest, key, value);
+               return git_config_string(dest, key, value);
        else
                return 1;
 }
@@ -2453,7 +2487,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d
                return 1;
 }
 
-int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)
+int git_configset_get_pathname(struct config_set *set, const char *key, char **dest)
 {
        const char *value;
        if (!git_configset_get_value(set, key, &value, NULL))
@@ -2500,7 +2534,7 @@ static void git_config_check_init(struct repository *repo)
        repo_read_config(repo);
 }
 
-static void repo_config_clear(struct repository *repo)
+void repo_config_clear(struct repository *repo)
 {
        if (!repo->config || !repo->config->hash_initialized)
                return;
@@ -2547,7 +2581,7 @@ int repo_config_get_string(struct repository *repo,
        git_config_check_init(repo);
        ret = git_configset_get_string(repo->config, key, dest);
        if (ret < 0)
-               git_die_config(key, NULL);
+               git_die_config(repo, key, NULL);
        return ret;
 }
 
@@ -2558,7 +2592,7 @@ int repo_config_get_string_tmp(struct repository *repo,
        git_config_check_init(repo);
        ret = git_configset_get_string_tmp(repo->config, key, dest);
        if (ret < 0)
-               git_die_config(key, NULL);
+               git_die_config(repo, key, NULL);
        return ret;
 }
 
@@ -2598,13 +2632,13 @@ int repo_config_get_maybe_bool(struct repository *repo,
 }
 
 int repo_config_get_pathname(struct repository *repo,
-                            const char *key, const char **dest)
+                            const char *key, char **dest)
 {
        int ret;
        git_config_check_init(repo);
        ret = git_configset_get_pathname(repo->config, key, dest);
        if (ret < 0)
-               git_die_config(key, NULL);
+               git_die_config(repo, key, NULL);
        return ret;
 }
 
@@ -2630,98 +2664,28 @@ void git_protected_config(config_fn_t fn, void *data)
        configset_iter(&protected_config, fn, data);
 }
 
-/* Functions used historically to read configuration from 'the_repository' */
-void git_config(config_fn_t fn, void *data)
-{
-       repo_config(the_repository, fn, data);
-}
-
-void git_config_clear(void)
-{
-       repo_config_clear(the_repository);
-}
-
-int git_config_get(const char *key)
-{
-       return repo_config_get(the_repository, key);
-}
-
-int git_config_get_value(const char *key, const char **value)
-{
-       return repo_config_get_value(the_repository, key, value);
-}
-
-int git_config_get_value_multi(const char *key, const struct string_list **dest)
-{
-       return repo_config_get_value_multi(the_repository, key, dest);
-}
-
-int git_config_get_string_multi(const char *key,
-                               const struct string_list **dest)
-{
-       return repo_config_get_string_multi(the_repository, key, dest);
-}
-
-int git_config_get_string(const char *key, char **dest)
-{
-       return repo_config_get_string(the_repository, key, dest);
-}
-
-int git_config_get_string_tmp(const char *key, const char **dest)
-{
-       return repo_config_get_string_tmp(the_repository, key, dest);
-}
-
-int git_config_get_int(const char *key, int *dest)
-{
-       return repo_config_get_int(the_repository, key, dest);
-}
-
-int git_config_get_ulong(const char *key, unsigned long *dest)
-{
-       return repo_config_get_ulong(the_repository, key, dest);
-}
-
-int git_config_get_bool(const char *key, int *dest)
-{
-       return repo_config_get_bool(the_repository, key, dest);
-}
-
-int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)
+int repo_config_get_expiry(struct repository *r, const char *key, char **output)
 {
-       return repo_config_get_bool_or_int(the_repository, key, is_bool, dest);
-}
+       int ret = repo_config_get_string(r, key, output);
 
-int git_config_get_maybe_bool(const char *key, int *dest)
-{
-       return repo_config_get_maybe_bool(the_repository, key, dest);
-}
-
-int git_config_get_pathname(const char *key, const char **dest)
-{
-       return repo_config_get_pathname(the_repository, key, dest);
-}
-
-int git_config_get_expiry(const char *key, const char **output)
-{
-       int ret = git_config_get_string(key, (char **)output);
        if (ret)
                return ret;
        if (strcmp(*output, "now")) {
                timestamp_t now = approxidate("now");
                if (approxidate(*output) >= now)
-                       git_die_config(key, _("Invalid %s: '%s'"), key, *output);
+                       git_die_config(r, key, _("Invalid %s: '%s'"), key, *output);
        }
        return ret;
 }
 
-int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestamp_t now)
+int repo_config_get_expiry_in_days(struct repository *r, const char *key,
+                                  timestamp_t *expiry, timestamp_t now)
 {
        const char *expiry_string;
        intmax_t days;
        timestamp_t when;
 
-       if (git_config_get_string_tmp(key, &expiry_string))
+       if (repo_config_get_string_tmp(r, key, &expiry_string))
                return 1; /* no such thing */
 
        if (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {
@@ -2737,21 +2701,21 @@ int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestam
        return -1; /* thing exists but cannot be parsed */
 }
 
-int git_config_get_split_index(void)
+int repo_config_get_split_index(struct repository *r)
 {
        int val;
 
-       if (!git_config_get_maybe_bool("core.splitindex", &val))
+       if (!repo_config_get_maybe_bool(r, "core.splitindex", &val))
                return val;
 
        return -1; /* default value */
 }
 
-int git_config_get_max_percent_split_change(void)
+int repo_config_get_max_percent_split_change(struct repository *r)
 {
        int val = -1;
 
-       if (!git_config_get_int("splitindex.maxpercentchange", &val)) {
+       if (!repo_config_get_int(r, "splitindex.maxpercentchange", &val)) {
                if (0 <= val && val <= 100)
                        return val;
 
@@ -2762,7 +2726,7 @@ int git_config_get_max_percent_split_change(void)
        return -1; /* default value */
 }
 
-int git_config_get_index_threads(int *dest)
+int repo_config_get_index_threads(struct repository *r, int *dest)
 {
        int is_bool, val;
 
@@ -2772,7 +2736,7 @@ int git_config_get_index_threads(int *dest)
                return 0;
        }
 
-       if (!git_config_get_bool_or_int("index.threads", &is_bool, &val)) {
+       if (!repo_config_get_bool_or_int(r, "index.threads", &is_bool, &val)) {
                if (is_bool)
                        *dest = val ? 0 : 1;
                else
@@ -2793,8 +2757,7 @@ void git_die_config_linenr(const char *key, const char *filename, int linenr)
                    key, filename, linenr);
 }
 
-NORETURN __attribute__((format(printf, 2, 3)))
-void git_die_config(const char *key, const char *err, ...)
+void git_die_config(struct repository *r, const char *key, const char *err, ...)
 {
        const struct string_list *values;
        struct key_value_info *kv_info;
@@ -2806,7 +2769,7 @@ void git_die_config(const char *key, const char *err, ...)
                error_fn(err, params);
                va_end(params);
        }
-       if (git_config_get_value_multi(key, &values))
+       if (repo_config_get_value_multi(r, key, &values))
                BUG("for key '%s' we must have a value to report on", key);
        kv_info = values->items[values->nr - 1].util;
        git_die_config_linenr(key, kv_info->filename, kv_info->linenr);
@@ -2851,7 +2814,7 @@ static int matches(const char *key, const char *value,
 {
        if (strcmp(key, store->key))
                return 0; /* not ours */
-       if (store->fixed_value)
+       if (store->fixed_value && value)
                return !strcmp(store->fixed_value, value);
        if (!store->value_pattern)
                return 1; /* always matches */
@@ -2982,6 +2945,7 @@ static ssize_t write_section(int fd, const char *key,
 }
 
 static ssize_t write_pair(int fd, const char *key, const char *value,
+                         const char *comment,
                          const struct config_store_data *store)
 {
        int i;
@@ -3022,7 +2986,11 @@ static ssize_t write_pair(int fd, const char *key, const char *value,
                        strbuf_addch(&sb, value[i]);
                        break;
                }
-       strbuf_addf(&sb, "%s\n", quote);
+
+       if (comment)
+               strbuf_addf(&sb, "%s%s\n", quote, comment);
+       else
+               strbuf_addf(&sb, "%s\n", quote);
 
        ret = write_in_full(fd, sb.buf, sb.len);
        strbuf_release(&sb);
@@ -3110,21 +3078,21 @@ static void maybe_remove_section(struct config_store_data *store,
                *end_offset = store->parsed[store->parsed_nr - 1].end;
 }
 
-int git_config_set_in_file_gently(const char *config_filename,
-                                 const char *key, const char *value)
+int repo_config_set_in_file_gently(struct repository *r, const char *config_filename,
+                                  const char *key, const char *comment, const char *value)
 {
-       return git_config_set_multivar_in_file_gently(config_filename, key, value, NULL, 0);
+       return repo_config_set_multivar_in_file_gently(r, config_filename, key, value, NULL, comment, 0);
 }
 
-void git_config_set_in_file(const char *config_filename,
-                           const char *key, const char *value)
+void repo_config_set_in_file(struct repository *r, const char *config_filename,
+                            const char *key, const char *value)
 {
-       git_config_set_multivar_in_file(config_filename, key, value, NULL, 0);
+       repo_config_set_multivar_in_file(r, config_filename, key, value, NULL, 0);
 }
 
-int git_config_set_gently(const char *key, const char *value)
+int repo_config_set_gently(struct repository *r, const char *key, const char *value)
 {
-       return git_config_set_multivar_gently(key, value, NULL, 0);
+       return repo_config_set_multivar_gently(r, key, value, NULL, 0);
 }
 
 int repo_config_set_worktree_gently(struct repository *r,
@@ -3133,21 +3101,73 @@ int repo_config_set_worktree_gently(struct repository *r,
        /* Only use worktree-specific config if it is already enabled. */
        if (r->repository_format_worktree_config) {
                char *file = repo_git_path(r, "config.worktree");
-               int ret = git_config_set_multivar_in_file_gently(
-                                       file, key, value, NULL, 0);
+               int ret = repo_config_set_multivar_in_file_gently(
+                                       r, file, key, value, NULL, NULL, 0);
                free(file);
                return ret;
        }
        return repo_config_set_multivar_gently(r, key, value, NULL, 0);
 }
 
-void git_config_set(const char *key, const char *value)
+void repo_config_set(struct repository *r, const char *key, const char *value)
 {
-       git_config_set_multivar(key, value, NULL, 0);
+       repo_config_set_multivar(r, key, value, NULL, 0);
 
        trace2_cmd_set_config(key, value);
 }
 
+char *git_config_prepare_comment_string(const char *comment)
+{
+       size_t leading_blanks;
+       char *prepared;
+
+       if (!comment)
+               return NULL;
+
+       if (strchr(comment, '\n'))
+               die(_("no multi-line comment allowed: '%s'"), comment);
+
+       /*
+        * If it begins with one or more leading whitespace characters
+        * followed by '#", the comment string is used as-is.
+        *
+        * If it begins with '#', a SP is inserted between the comment
+        * and the value the comment is about.
+        *
+        * Otherwise, the value is followed by a SP followed by '#'
+        * followed by SP and then the comment string comes.
+        */
+
+       leading_blanks = strspn(comment, " \t");
+       if (leading_blanks && comment[leading_blanks] == '#')
+               prepared = xstrdup(comment); /* use it as-is */
+       else if (comment[0] == '#')
+               prepared = xstrfmt(" %s", comment);
+       else
+               prepared = xstrfmt(" # %s", comment);
+
+       return prepared;
+}
+
+static void validate_comment_string(const char *comment)
+{
+       size_t leading_blanks;
+
+       if (!comment)
+               return;
+       /*
+        * The front-end must have massaged the comment string
+        * properly before calling us.
+        */
+       if (strchr(comment, '\n'))
+               BUG("multi-line comments are not permitted: '%s'", comment);
+
+       leading_blanks = strspn(comment, " \t");
+       if (!leading_blanks || comment[leading_blanks] != '#')
+               BUG("comment must begin with one or more SP followed by '#': '%s'",
+                   comment);
+}
+
 /*
  * If value==NULL, unset in (remove from) config,
  * if value_pattern!=NULL, disregard key/value pairs where value does not match.
@@ -3173,10 +3193,12 @@ void git_config_set(const char *key, const char *value)
  * - the config file is removed and the lock file rename()d to it.
  *
  */
-int git_config_set_multivar_in_file_gently(const char *config_filename,
-                                          const char *key, const char *value,
-                                          const char *value_pattern,
-                                          unsigned flags)
+int repo_config_set_multivar_in_file_gently(struct repository *r,
+                                           const char *config_filename,
+                                           const char *key, const char *value,
+                                           const char *value_pattern,
+                                           const char *comment,
+                                           unsigned flags)
 {
        int fd = -1, in_fd = -1;
        int ret;
@@ -3186,6 +3208,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
        size_t contents_sz;
        struct config_store_data store = CONFIG_STORE_INIT;
 
+       validate_comment_string(comment);
+
        /* parse-key returns negative; flip the sign to feed exit(3) */
        ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
        if (ret)
@@ -3194,7 +3218,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
        store.multi_replace = (flags & CONFIG_FLAGS_MULTI_REPLACE) != 0;
 
        if (!config_filename)
-               config_filename = filename_buf = git_pathdup("config");
+               config_filename = filename_buf = repo_git_path(r, "config");
 
        /*
         * The lock serves a purpose in addition to locking: the new
@@ -3226,7 +3250,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                free(store.key);
                store.key = xstrdup(key);
                if (write_section(fd, key, &store) < 0 ||
-                   write_pair(fd, key, value, &store) < 0)
+                   write_pair(fd, key, value, comment, &store) < 0)
                        goto write_err_out;
        } else {
                struct stat st;
@@ -3380,7 +3404,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                                if (write_section(fd, key, &store) < 0)
                                        goto write_err_out;
                        }
-                       if (write_pair(fd, key, value, &store) < 0)
+                       if (write_pair(fd, key, value, comment, &store) < 0)
                                goto write_err_out;
                }
 
@@ -3403,7 +3427,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
        ret = 0;
 
        /* Invalidate the config cache */
-       git_config_clear();
+       repo_config_clear(r);
 
 out_free:
        rollback_lock_file(&lock);
@@ -3418,15 +3442,15 @@ out_free:
 write_err_out:
        ret = write_error(get_lock_file_path(&lock));
        goto out_free;
-
 }
 
-void git_config_set_multivar_in_file(const char *config_filename,
-                                    const char *key, const char *value,
-                                    const char *value_pattern, unsigned flags)
+void repo_config_set_multivar_in_file(struct repository *r,
+                                     const char *config_filename,
+                                     const char *key, const char *value,
+                                     const char *value_pattern, unsigned flags)
 {
-       if (!git_config_set_multivar_in_file_gently(config_filename, key, value,
-                                                   value_pattern, flags))
+       if (!repo_config_set_multivar_in_file_gently(r, config_filename, key, value,
+                                                    value_pattern, NULL, flags))
                return;
        if (value)
                die(_("could not set '%s' to '%s'"), key, value);
@@ -3434,32 +3458,27 @@ void git_config_set_multivar_in_file(const char *config_filename,
                die(_("could not unset '%s'"), key);
 }
 
-int git_config_set_multivar_gently(const char *key, const char *value,
-                                  const char *value_pattern, unsigned flags)
-{
-       return repo_config_set_multivar_gently(the_repository, key, value,
-                                              value_pattern, flags);
-}
-
 int repo_config_set_multivar_gently(struct repository *r, const char *key,
                                    const char *value,
                                    const char *value_pattern, unsigned flags)
 {
        char *file = repo_git_path(r, "config");
-       int res = git_config_set_multivar_in_file_gently(file,
-                                                        key, value,
-                                                        value_pattern,
-                                                        flags);
+       int res = repo_config_set_multivar_in_file_gently(r, file,
+                                                         key, value,
+                                                         value_pattern,
+                                                         NULL, flags);
        free(file);
        return res;
 }
 
-void git_config_set_multivar(const char *key, const char *value,
-                            const char *value_pattern, unsigned flags)
+void repo_config_set_multivar(struct repository *r,
+                             const char *key, const char *value,
+                             const char *value_pattern, unsigned flags)
 {
-       git_config_set_multivar_in_file(git_path("config"),
-                                       key, value, value_pattern,
-                                       flags);
+       char *file = repo_git_path(r, "config");
+       repo_config_set_multivar_in_file(r, file, key, value,
+                                        value_pattern, flags);
+       free(file);
 }
 
 static size_t section_name_match (const char *buf, const char *name)
@@ -3521,9 +3540,11 @@ static int section_name_is_ok(const char *name)
 #define GIT_CONFIG_MAX_LINE_LEN (512 * 1024)
 
 /* if new_name == NULL, the section is removed instead */
-static int git_config_copy_or_rename_section_in_file(const char *config_filename,
-                                     const char *old_name,
-                                     const char *new_name, int copy)
+static int repo_config_copy_or_rename_section_in_file(
+       struct repository *r,
+       const char *config_filename,
+       const char *old_name,
+       const char *new_name, int copy)
 {
        int ret = 0, remove = 0;
        char *filename_buf = NULL;
@@ -3544,7 +3565,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
        }
 
        if (!config_filename)
-               config_filename = filename_buf = git_pathdup("config");
+               config_filename = filename_buf = repo_git_path(r, "config");
 
        out_fd = hold_lock_file_for_update(&lock, config_filename, 0);
        if (out_fd < 0) {
@@ -3687,28 +3708,28 @@ out_no_rollback:
        return ret;
 }
 
-int git_config_rename_section_in_file(const char *config_filename,
-                                     const char *old_name, const char *new_name)
+int repo_config_rename_section_in_file(struct repository *r, const char *config_filename,
+                                      const char *old_name, const char *new_name)
 {
-       return git_config_copy_or_rename_section_in_file(config_filename,
+       return repo_config_copy_or_rename_section_in_file(r, config_filename,
                                         old_name, new_name, 0);
 }
 
-int git_config_rename_section(const char *old_name, const char *new_name)
+int repo_config_rename_section(struct repository *r, const char *old_name, const char *new_name)
 {
-       return git_config_rename_section_in_file(NULL, old_name, new_name);
+       return repo_config_rename_section_in_file(r, NULL, old_name, new_name);
 }
 
-int git_config_copy_section_in_file(const char *config_filename,
-                                     const char *old_name, const char *new_name)
+int repo_config_copy_section_in_file(struct repository *r, const char *config_filename,
+                                    const char *old_name, const char *new_name)
 {
-       return git_config_copy_or_rename_section_in_file(config_filename,
+       return repo_config_copy_or_rename_section_in_file(r, config_filename,
                                         old_name, new_name, 1);
 }
 
-int git_config_copy_section(const char *old_name, const char *new_name)
+int repo_config_copy_section(struct repository *r, const char *old_name, const char *new_name)
 {
-       return git_config_copy_section_in_file(NULL, old_name, new_name);
+       return repo_config_copy_section_in_file(r, NULL, old_name, new_name);
 }
 
 /*