]> git.ipfire.org Git - thirdparty/git.git/blobdiff - config.c
Merge branch 'jc/unleak-core-excludesfile'
[thirdparty/git.git] / config.c
index 146856567a6e4b1a9b740c814b92640d831d8268..ae3652b08fa6f36af6c085e36b89efdd46d72389 100644 (file)
--- a/config.c
+++ b/config.c
@@ -5,24 +5,39 @@
  * Copyright (C) Johannes Schindelin, 2005
  *
  */
-#include "cache.h"
+#include "git-compat-util.h"
+#include "abspath.h"
+#include "advice.h"
 #include "date.h"
 #include "branch.h"
 #include "config.h"
+#include "parse.h"
+#include "convert.h"
 #include "environment.h"
+#include "gettext.h"
+#include "ident.h"
 #include "repository.h"
 #include "lockfile.h"
+#include "mailmap.h"
+#include "attr.h"
 #include "exec-cmd.h"
 #include "strbuf.h"
 #include "quote.h"
 #include "hashmap.h"
 #include "string-list.h"
-#include "object-store.h"
+#include "object-name.h"
+#include "object-store-ll.h"
+#include "pager.h"
+#include "path.h"
 #include "utf8.h"
-#include "dir.h"
 #include "color.h"
 #include "refs.h"
-#include "worktree.h"
+#include "setup.h"
+#include "strvec.h"
+#include "trace2.h"
+#include "wildmatch.h"
+#include "ws.h"
+#include "write-or-die.h"
 
 struct config_source {
        struct config_source *prev;
@@ -49,34 +64,7 @@ struct config_source {
        int (*do_ungetc)(int c, struct config_source *conf);
        long (*do_ftell)(struct config_source *c);
 };
-
-/*
- * These variables record the "current" config source, which
- * can be accessed by parsing callbacks.
- *
- * The "cf" variable will be non-NULL only when we are actually parsing a real
- * config source (file, blob, cmdline, etc).
- *
- * The "current_config_kvi" variable will be non-NULL only when we are feeding
- * cached config from a configset into a callback.
- *
- * They should generally never be non-NULL at the same time. If they are both
- * NULL, then we aren't parsing anything (and depending on the function looking
- * at the variables, it's either a bug for it to be called in the first place,
- * or it's a function which can be reused for non-config purposes, and should
- * fall back to some sane behavior).
- */
-static struct config_source *cf;
-static struct key_value_info *current_config_kvi;
-
-/*
- * Similar to the variables above, this gives access to the "scope" of the
- * current value (repo, global, etc). For cached values, it can be found via
- * the current_config_kvi as above. During parsing, the current value can be
- * found in this variable. It's not part of "cf" because it transcends a single
- * file (i.e., a file included from .git/config is still in "repo" scope).
- */
-static enum config_scope current_parsing_scope;
+#define CONFIG_SOURCE_INIT { 0 }
 
 static int pack_compression_seen;
 static int zlib_compression_seen;
@@ -107,7 +95,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)
@@ -139,6 +126,7 @@ struct config_include_data {
        void *data;
        const struct config_options *opts;
        struct git_config_source *config_source;
+       struct repository *repo;
 
        /*
         * All remote URLs discovered when reading all config files.
@@ -147,7 +135,8 @@ struct config_include_data {
 };
 #define CONFIG_INCLUDE_INIT { 0 }
 
-static int git_config_include(const char *var, const char *value, void *data);
+static int git_config_include(const char *var, const char *value,
+                             const struct config_context *ctx, void *data);
 
 #define MAX_INCLUDE_DEPTH 10
 static const char include_depth_advice[] = N_(
@@ -156,7 +145,9 @@ static const char include_depth_advice[] = N_(
 "from\n"
 "      %s\n"
 "This might be due to circular includes.");
-static int handle_path_include(const char *path, struct config_include_data *inc)
+static int handle_path_include(const struct key_value_info *kvi,
+                              const char *path,
+                              struct config_include_data *inc)
 {
        int ret = 0;
        struct strbuf buf = STRBUF_INIT;
@@ -177,14 +168,14 @@ static int handle_path_include(const char *path, struct config_include_data *inc
        if (!is_absolute_path(path)) {
                char *slash;
 
-               if (!cf || !cf->path) {
+               if (!kvi || !kvi->path) {
                        ret = error(_("relative config includes must come from files"));
                        goto cleanup;
                }
 
-               slash = find_last_dir_sep(cf->path);
+               slash = find_last_dir_sep(kvi->path);
                if (slash)
-                       strbuf_add(&buf, cf->path, slash - cf->path + 1);
+                       strbuf_add(&buf, kvi->path, slash - kvi->path + 1);
                strbuf_addstr(&buf, path);
                path = buf.buf;
        }
@@ -192,10 +183,11 @@ static int handle_path_include(const char *path, struct config_include_data *inc
        if (!access_or_die(path, R_OK, 0)) {
                if (++inc->depth > MAX_INCLUDE_DEPTH)
                        die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
-                           !cf ? "<unknown>" :
-                           cf->name ? cf->name :
+                           !kvi ? "<unknown>" :
+                           kvi->filename ? kvi->filename :
                            "the command line");
-               ret = git_config_from_file(git_config_include, path, inc);
+               ret = git_config_from_file_with_options(git_config_include, path, inc,
+                                                       kvi->scope, NULL);
                inc->depth--;
        }
 cleanup:
@@ -210,7 +202,8 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)
                strbuf_addstr(pat, "**");
 }
 
-static int prepare_include_condition_pattern(struct strbuf *pat)
+static int prepare_include_condition_pattern(const struct key_value_info *kvi,
+                                            struct strbuf *pat)
 {
        struct strbuf path = STRBUF_INIT;
        char *expanded;
@@ -226,11 +219,11 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
        if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
                const char *slash;
 
-               if (!cf || !cf->path)
+               if (!kvi || !kvi->path)
                        return error(_("relative config include "
                                       "conditionals must come from files"));
 
-               strbuf_realpath(&path, cf->path, 1);
+               strbuf_realpath(&path, kvi->path, 1);
                slash = find_last_dir_sep(path.buf);
                if (!slash)
                        BUG("how is this possible?");
@@ -245,7 +238,8 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
        return prefix;
 }
 
-static int include_by_gitdir(const struct config_options *opts,
+static int include_by_gitdir(const struct key_value_info *kvi,
+                            const struct config_options *opts,
                             const char *cond, size_t cond_len, int icase)
 {
        struct strbuf text = STRBUF_INIT;
@@ -261,7 +255,7 @@ static int include_by_gitdir(const struct config_options *opts,
 
        strbuf_realpath(&text, git_dir, 1);
        strbuf_add(&pattern, cond, cond_len);
-       prefix = prepare_include_condition_pattern(&pattern);
+       prefix = prepare_include_condition_pattern(kvi, &pattern);
 
 again:
        if (prefix < 0)
@@ -323,7 +317,8 @@ static int include_by_branch(const char *cond, size_t cond_len)
        return ret;
 }
 
-static int add_remote_url(const char *var, const char *value, void *data)
+static int add_remote_url(const char *var, const char *value,
+                         const struct config_context *ctx UNUSED, void *data)
 {
        struct string_list *remote_urls = data;
        const char *remote_name;
@@ -342,27 +337,17 @@ static void populate_remote_urls(struct config_include_data *inc)
 {
        struct config_options opts;
 
-       struct config_source *store_cf = cf;
-       struct key_value_info *store_kvi = current_config_kvi;
-       enum config_scope store_scope = current_parsing_scope;
-
        opts = *inc->opts;
        opts.unconditional_remote_url = 1;
 
-       cf = NULL;
-       current_config_kvi = NULL;
-       current_parsing_scope = 0;
-
        inc->remote_urls = xmalloc(sizeof(*inc->remote_urls));
        string_list_init_dup(inc->remote_urls);
-       config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);
-
-       cf = store_cf;
-       current_config_kvi = store_kvi;
-       current_parsing_scope = store_scope;
+       config_with_options(add_remote_url, inc->remote_urls,
+                           inc->config_source, inc->repo, &opts);
 }
 
 static int forbid_remote_url(const char *var, const char *value UNUSED,
+                            const struct config_context *ctx UNUSED,
                             void *data UNUSED)
 {
        const char *remote_name;
@@ -406,15 +391,16 @@ static int include_by_remote_url(struct config_include_data *inc,
                                             inc->remote_urls);
 }
 
-static int include_condition_is_true(struct config_include_data *inc,
+static int include_condition_is_true(const struct key_value_info *kvi,
+                                    struct config_include_data *inc,
                                     const char *cond, size_t cond_len)
 {
        const struct config_options *opts = inc->opts;
 
        if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
-               return include_by_gitdir(opts, cond, cond_len, 0);
+               return include_by_gitdir(kvi, opts, cond, cond_len, 0);
        else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
-               return include_by_gitdir(opts, cond, cond_len, 1);
+               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);
        else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
@@ -425,7 +411,9 @@ static int include_condition_is_true(struct config_include_data *inc,
        return 0;
 }
 
-static int git_config_include(const char *var, const char *value, void *data)
+static int git_config_include(const char *var, const char *value,
+                             const struct config_context *ctx,
+                             void *data)
 {
        struct config_include_data *inc = data;
        const char *cond, *key;
@@ -436,21 +424,21 @@ static int git_config_include(const char *var, const char *value, void *data)
         * Pass along all values, including "include" directives; this makes it
         * possible to query information on the includes themselves.
         */
-       ret = inc->fn(var, value, inc->data);
+       ret = inc->fn(var, value, ctx, inc->data);
        if (ret < 0)
                return ret;
 
        if (!strcmp(var, "include.path"))
-               ret = handle_path_include(value, inc);
+               ret = handle_path_include(ctx->kvi, value, inc);
 
        if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
-           cond && include_condition_is_true(inc, cond, cond_len) &&
+           cond && include_condition_is_true(ctx->kvi, inc, cond, cond_len) &&
            !strcmp(key, "path")) {
                config_fn_t old_fn = inc->fn;
 
                if (inc->opts->unconditional_remote_url)
                        inc->fn = forbid_remote_url;
-               ret = handle_path_include(value, inc);
+               ret = handle_path_include(ctx->kvi, value, inc);
                inc->fn = old_fn;
        }
 
@@ -608,27 +596,45 @@ out_free_ret_1:
 }
 
 static int config_parse_pair(const char *key, const char *value,
-                         config_fn_t fn, void *data)
+                            struct key_value_info *kvi,
+                            config_fn_t fn, void *data)
 {
        char *canonical_name;
        int ret;
+       struct config_context ctx = {
+               .kvi = kvi,
+       };
 
        if (!strlen(key))
                return error(_("empty config key"));
        if (git_config_parse_key(key, &canonical_name, NULL))
                return -1;
 
-       ret = (fn(canonical_name, value, data) < 0) ? -1 : 0;
+       ret = (fn(canonical_name, value, &ctx, data) < 0) ? -1 : 0;
        free(canonical_name);
        return ret;
 }
 
+
+/* for values read from `git_config_from_parameters()` */
+void kvi_from_param(struct key_value_info *out)
+{
+       out->filename = NULL;
+       out->linenr = -1;
+       out->origin_type = CONFIG_ORIGIN_CMDLINE;
+       out->scope = CONFIG_SCOPE_COMMAND;
+       out->path = NULL;
+}
+
 int git_config_parse_parameter(const char *text,
                               config_fn_t fn, void *data)
 {
        const char *value;
        struct strbuf **pair;
        int ret;
+       struct key_value_info kvi = KVI_INIT;
+
+       kvi_from_param(&kvi);
 
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
@@ -647,12 +653,13 @@ int git_config_parse_parameter(const char *text,
                return error(_("bogus config parameter: %s"), text);
        }
 
-       ret = config_parse_pair(pair[0]->buf, value, fn, data);
+       ret = config_parse_pair(pair[0]->buf, value, &kvi, fn, data);
        strbuf_list_free(pair);
        return ret;
 }
 
-static int parse_config_env_list(char *env, config_fn_t fn, void *data)
+static int parse_config_env_list(char *env, struct key_value_info *kvi,
+                                config_fn_t fn, void *data)
 {
        char *cur = env;
        while (cur && *cur) {
@@ -686,7 +693,7 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)
                                             CONFIG_DATA_ENVIRONMENT);
                        }
 
-                       if (config_parse_pair(key, value, fn, data) < 0)
+                       if (config_parse_pair(key, value, kvi, fn, data) < 0)
                                return -1;
                }
                else {
@@ -710,13 +717,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)
        struct strvec to_free = STRVEC_INIT;
        int ret = 0;
        char *envw = NULL;
-       struct config_source source;
-
-       memset(&source, 0, sizeof(source));
-       source.prev = cf;
-       source.origin_type = CONFIG_ORIGIN_CMDLINE;
-       cf = &source;
+       struct key_value_info kvi = KVI_INIT;
 
+       kvi_from_param(&kvi);
        env = getenv(CONFIG_COUNT_ENVIRONMENT);
        if (env) {
                unsigned long count;
@@ -752,7 +755,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)
                        }
                        strbuf_reset(&envvar);
 
-                       if (config_parse_pair(key, value, fn, data) < 0) {
+                       if (config_parse_pair(key, value, &kvi, fn, data) < 0) {
                                ret = -1;
                                goto out;
                        }
@@ -763,7 +766,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)
        if (env) {
                /* sq_dequote will write over it */
                envw = xstrdup(env);
-               if (parse_config_env_list(envw, fn, data) < 0) {
+               if (parse_config_env_list(envw, &kvi, fn, data) < 0) {
                        ret = -1;
                        goto out;
                }
@@ -773,25 +776,24 @@ out:
        strbuf_release(&envvar);
        strvec_clear(&to_free);
        free(envw);
-       cf = source.prev;
        return ret;
 }
 
-static int get_next_char(void)
+static int get_next_char(struct config_source *cs)
 {
-       int c = cf->do_fgetc(cf);
+       int c = cs->do_fgetc(cs);
 
        if (c == '\r') {
                /* DOS like systems */
-               c = cf->do_fgetc(cf);
+               c = cs->do_fgetc(cs);
                if (c != '\n') {
                        if (c != EOF)
-                               cf->do_ungetc(c, cf);
+                               cs->do_ungetc(c, cs);
                        c = '\r';
                }
        }
 
-       if (c != EOF && ++cf->total_len > INT_MAX) {
+       if (c != EOF && ++cs->total_len > INT_MAX) {
                /*
                 * This is an absurdly long config file; refuse to parse
                 * further in order to protect downstream code from integer
@@ -799,39 +801,44 @@ static int get_next_char(void)
                 * but we can mark EOF and put trash in the return value,
                 * which will trigger a parse error.
                 */
-               cf->eof = 1;
+               cs->eof = 1;
                return 0;
        }
 
        if (c == '\n')
-               cf->linenr++;
+               cs->linenr++;
        if (c == EOF) {
-               cf->eof = 1;
-               cf->linenr++;
+               cs->eof = 1;
+               cs->linenr++;
                c = '\n';
        }
        return c;
 }
 
-static char *parse_value(void)
+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(&cf->value);
+       strbuf_reset(&cs->value);
        for (;;) {
-               int c = get_next_char();
+               int c = get_next_char(cs);
                if (c == '\n') {
                        if (quote) {
-                               cf->linenr--;
+                               cs->linenr--;
                                return NULL;
                        }
-                       return cf->value.buf;
+                       if (trim_len)
+                               strbuf_setlen(&cs->value, trim_len);
+                       return cs->value.buf;
                }
                if (comment)
                        continue;
                if (isspace(c) && !quote) {
-                       if (cf->value.len)
-                               space++;
+                       if (!trim_len)
+                               trim_len = cs->value.len;
+                       if (cs->value.len)
+                               strbuf_addch(&cs->value, c);
                        continue;
                }
                if (!quote) {
@@ -840,10 +847,10 @@ static char *parse_value(void)
                                continue;
                        }
                }
-               for (; space; space--)
-                       strbuf_addch(&cf->value, ' ');
+               if (trim_len)
+                       trim_len = 0;
                if (c == '\\') {
-                       c = get_next_char();
+                       c = get_next_char(cs);
                        switch (c) {
                        case '\n':
                                continue;
@@ -863,27 +870,31 @@ static char *parse_value(void)
                        default:
                                return NULL;
                        }
-                       strbuf_addch(&cf->value, c);
+                       strbuf_addch(&cs->value, c);
                        continue;
                }
                if (c == '"') {
-                       quote = 1-quote;
+                       quote = 1 - quote;
                        continue;
                }
-               strbuf_addch(&cf->value, c);
+               strbuf_addch(&cs->value, c);
        }
 }
 
-static int get_value(config_fn_t fn, void *data, struct strbuf *name)
+static int get_value(struct config_source *cs, struct key_value_info *kvi,
+                    config_fn_t fn, void *data, struct strbuf *name)
 {
        int c;
        char *value;
        int ret;
+       struct config_context ctx = {
+               .kvi = kvi,
+       };
 
        /* Get the full name */
        for (;;) {
-               c = get_next_char();
-               if (cf->eof)
+               c = get_next_char(cs);
+               if (cs->eof)
                        break;
                if (!iskeychar(c))
                        break;
@@ -891,13 +902,13 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
        }
 
        while (c == ' ' || c == '\t')
-               c = get_next_char();
+               c = get_next_char(cs);
 
        value = NULL;
        if (c != '\n') {
                if (c != '=')
                        return -1;
-               value = parse_value();
+               value = parse_value(cs);
                if (!value)
                        return -1;
        }
@@ -906,20 +917,22 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
         * the line we just parsed during the call to fn to get
         * accurate line number in error messages.
         */
-       cf->linenr--;
-       ret = fn(name->buf, value, data);
+       cs->linenr--;
+       kvi->linenr = cs->linenr;
+       ret = fn(name->buf, value, &ctx, data);
        if (ret >= 0)
-               cf->linenr++;
+               cs->linenr++;
        return ret;
 }
 
-static int get_extended_base_var(struct strbuf *name, int c)
+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
+                                int c)
 {
-       cf->subsection_case_sensitive = 0;
+       cs->subsection_case_sensitive = 0;
        do {
                if (c == '\n')
                        goto error_incomplete_line;
-               c = get_next_char();
+               c = get_next_char(cs);
        } while (isspace(c));
 
        /* We require the format to be '[base "extension"]' */
@@ -928,13 +941,13 @@ static int get_extended_base_var(struct strbuf *name, int c)
        strbuf_addch(name, '.');
 
        for (;;) {
-               int c = get_next_char();
+               int c = get_next_char(cs);
                if (c == '\n')
                        goto error_incomplete_line;
                if (c == '"')
                        break;
                if (c == '\\') {
-                       c = get_next_char();
+                       c = get_next_char(cs);
                        if (c == '\n')
                                goto error_incomplete_line;
                }
@@ -942,25 +955,25 @@ static int get_extended_base_var(struct strbuf *name, int c)
        }
 
        /* Final ']' */
-       if (get_next_char() != ']')
+       if (get_next_char(cs) != ']')
                return -1;
        return 0;
 error_incomplete_line:
-       cf->linenr--;
+       cs->linenr--;
        return -1;
 }
 
-static int get_base_var(struct strbuf *name)
+static int get_base_var(struct config_source *cs, struct strbuf *name)
 {
-       cf->subsection_case_sensitive = 1;
+       cs->subsection_case_sensitive = 1;
        for (;;) {
-               int c = get_next_char();
-               if (cf->eof)
+               int c = get_next_char(cs);
+               if (cs->eof)
                        return -1;
                if (c == ']')
                        return 0;
                if (isspace(c))
-                       return get_extended_base_var(name, c);
+                       return get_extended_base_var(cs, name, c);
                if (!iskeychar(c) && c != '.')
                        return -1;
                strbuf_addch(name, tolower(c));
@@ -973,7 +986,8 @@ struct parse_event_data {
        const struct config_options *opts;
 };
 
-static int do_event(enum config_event_t type, struct parse_event_data *data)
+static int do_event(struct config_source *cs, enum config_event_t type,
+                   struct parse_event_data *data)
 {
        size_t offset;
 
@@ -984,7 +998,7 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)
            data->previous_type == type)
                return 0;
 
-       offset = cf->do_ftell(cf);
+       offset = cs->do_ftell(cs);
        /*
         * At EOF, the parser always "inserts" an extra '\n', therefore
         * the end offset of the event is the current file position, otherwise
@@ -995,7 +1009,7 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)
 
        if (data->previous_type != CONFIG_EVENT_EOF &&
            data->opts->event_fn(data->previous_type, data->previous_offset,
-                                offset, data->opts->event_fn_data) < 0)
+                                offset, cs, data->opts->event_fn_data) < 0)
                return -1;
 
        data->previous_type = type;
@@ -1004,12 +1018,24 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)
        return 0;
 }
 
-static int git_parse_source(config_fn_t fn, void *data,
+static void kvi_from_source(struct config_source *cs,
+                           enum config_scope scope,
+                           struct key_value_info *out)
+{
+       out->filename = strintern(cs->name);
+       out->origin_type = cs->origin_type;
+       out->linenr = cs->linenr;
+       out->scope = scope;
+       out->path = cs->path;
+}
+
+static int git_parse_source(struct config_source *cs, config_fn_t fn,
+                           struct key_value_info *kvi, void *data,
                            const struct config_options *opts)
 {
        int comment = 0;
        size_t baselen = 0;
-       struct strbuf *var = &cf->var;
+       struct strbuf *var = &cs->var;
        int error_return = 0;
        char *error_msg = NULL;
 
@@ -1024,7 +1050,7 @@ static int git_parse_source(config_fn_t fn, void *data,
        for (;;) {
                int c;
 
-               c = get_next_char();
+               c = get_next_char(cs);
                if (bomptr && *bomptr) {
                        /* We are at the file beginning; skip UTF8-encoded BOM
                         * if present. Sane editors won't put this in on their
@@ -1041,12 +1067,12 @@ static int git_parse_source(config_fn_t fn, void *data,
                        }
                }
                if (c == '\n') {
-                       if (cf->eof) {
-                               if (do_event(CONFIG_EVENT_EOF, &event_data) < 0)
+                       if (cs->eof) {
+                               if (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)
                                        return -1;
                                return 0;
                        }
-                       if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+                       if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
                                return -1;
                        comment = 0;
                        continue;
@@ -1054,23 +1080,23 @@ static int git_parse_source(config_fn_t fn, void *data,
                if (comment)
                        continue;
                if (isspace(c)) {
-                       if (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)
+                       if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
                                        return -1;
                        continue;
                }
                if (c == '#' || c == ';') {
-                       if (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)
+                       if (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)
                                        return -1;
                        comment = 1;
                        continue;
                }
                if (c == '[') {
-                       if (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)
+                       if (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)
                                        return -1;
 
                        /* Reset prior to determining a new stem */
                        strbuf_reset(var);
-                       if (get_base_var(var) < 0 || var->len < 1)
+                       if (get_base_var(cs, var) < 0 || var->len < 1)
                                break;
                        strbuf_addch(var, '.');
                        baselen = var->len;
@@ -1079,7 +1105,7 @@ static int git_parse_source(config_fn_t fn, void *data,
                if (!isalpha(c))
                        break;
 
-               if (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)
+               if (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)
                        return -1;
 
                /*
@@ -1089,42 +1115,42 @@ static int git_parse_source(config_fn_t fn, void *data,
                 */
                strbuf_setlen(var, baselen);
                strbuf_addch(var, tolower(c));
-               if (get_value(fn, data, var) < 0)
+               if (get_value(cs, kvi, fn, data, var) < 0)
                        break;
        }
 
-       if (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)
+       if (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)
                return -1;
 
-       switch (cf->origin_type) {
+       switch (cs->origin_type) {
        case CONFIG_ORIGIN_BLOB:
                error_msg = xstrfmt(_("bad config line %d in blob %s"),
-                                     cf->linenr, cf->name);
+                                     cs->linenr, cs->name);
                break;
        case CONFIG_ORIGIN_FILE:
                error_msg = xstrfmt(_("bad config line %d in file %s"),
-                                     cf->linenr, cf->name);
+                                     cs->linenr, cs->name);
                break;
        case CONFIG_ORIGIN_STDIN:
                error_msg = xstrfmt(_("bad config line %d in standard input"),
-                                     cf->linenr);
+                                     cs->linenr);
                break;
        case CONFIG_ORIGIN_SUBMODULE_BLOB:
                error_msg = xstrfmt(_("bad config line %d in submodule-blob %s"),
-                                      cf->linenr, cf->name);
+                                      cs->linenr, cs->name);
                break;
        case CONFIG_ORIGIN_CMDLINE:
                error_msg = xstrfmt(_("bad config line %d in command line %s"),
-                                      cf->linenr, cf->name);
+                                      cs->linenr, cs->name);
                break;
        default:
                error_msg = xstrfmt(_("bad config line %d in %s"),
-                                     cf->linenr, cf->name);
+                                     cs->linenr, cs->name);
        }
 
        switch (opts && opts->error_action ?
                opts->error_action :
-               cf->default_error_action) {
+               cs->default_error_action) {
        case CONFIG_ERROR_DIE:
                die("%s", error_msg);
                break;
@@ -1142,213 +1168,81 @@ static int git_parse_source(config_fn_t fn, void *data,
        return error_return;
 }
 
-static uintmax_t get_unit_factor(const char *end)
-{
-       if (!*end)
-               return 1;
-       else if (!strcasecmp(end, "k"))
-               return 1024;
-       else if (!strcasecmp(end, "m"))
-               return 1024 * 1024;
-       else if (!strcasecmp(end, "g"))
-               return 1024 * 1024 * 1024;
-       return 0;
-}
-
-static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)
-{
-       if (value && *value) {
-               char *end;
-               intmax_t val;
-               intmax_t factor;
-
-               if (max < 0)
-                       BUG("max must be a positive integer");
-
-               errno = 0;
-               val = strtoimax(value, &end, 0);
-               if (errno == ERANGE)
-                       return 0;
-               if (end == value) {
-                       errno = EINVAL;
-                       return 0;
-               }
-               factor = get_unit_factor(end);
-               if (!factor) {
-                       errno = EINVAL;
-                       return 0;
-               }
-               if ((val < 0 && -max / factor > val) ||
-                   (val > 0 && max / factor < val)) {
-                       errno = ERANGE;
-                       return 0;
-               }
-               val *= factor;
-               *ret = val;
-               return 1;
-       }
-       errno = EINVAL;
-       return 0;
-}
-
-static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)
-{
-       if (value && *value) {
-               char *end;
-               uintmax_t val;
-               uintmax_t factor;
-
-               /* negative values would be accepted by strtoumax */
-               if (strchr(value, '-')) {
-                       errno = EINVAL;
-                       return 0;
-               }
-               errno = 0;
-               val = strtoumax(value, &end, 0);
-               if (errno == ERANGE)
-                       return 0;
-               if (end == value) {
-                       errno = EINVAL;
-                       return 0;
-               }
-               factor = get_unit_factor(end);
-               if (!factor) {
-                       errno = EINVAL;
-                       return 0;
-               }
-               if (unsigned_mult_overflows(factor, val) ||
-                   factor * val > max) {
-                       errno = ERANGE;
-                       return 0;
-               }
-               val *= factor;
-               *ret = val;
-               return 1;
-       }
-       errno = EINVAL;
-       return 0;
-}
-
-int git_parse_int(const char *value, int *ret)
-{
-       intmax_t tmp;
-       if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int)))
-               return 0;
-       *ret = tmp;
-       return 1;
-}
-
-static int git_parse_int64(const char *value, int64_t *ret)
-{
-       intmax_t tmp;
-       if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int64_t)))
-               return 0;
-       *ret = tmp;
-       return 1;
-}
-
-int git_parse_ulong(const char *value, unsigned long *ret)
-{
-       uintmax_t tmp;
-       if (!git_parse_unsigned(value, &tmp, maximum_unsigned_value_of_type(long)))
-               return 0;
-       *ret = tmp;
-       return 1;
-}
-
-int git_parse_ssize_t(const char *value, ssize_t *ret)
-{
-       intmax_t tmp;
-       if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(ssize_t)))
-               return 0;
-       *ret = tmp;
-       return 1;
-}
-
 NORETURN
-static void die_bad_number(const char *name, const char *value)
+static void die_bad_number(const char *name, const char *value,
+                          const struct key_value_info *kvi)
 {
        const char *error_type = (errno == ERANGE) ?
                N_("out of range") : N_("invalid unit");
        const char *bad_numeric = N_("bad numeric config value '%s' for '%s': %s");
 
+       if (!kvi)
+               BUG("kvi should not be NULL");
+
        if (!value)
                value = "";
 
-       if (!(cf && cf->name))
+       if (!kvi->filename)
                die(_(bad_numeric), value, name, _(error_type));
 
-       switch (cf->origin_type) {
+       switch (kvi->origin_type) {
        case CONFIG_ORIGIN_BLOB:
                die(_("bad numeric config value '%s' for '%s' in blob %s: %s"),
-                   value, name, cf->name, _(error_type));
+                   value, name, kvi->filename, _(error_type));
        case CONFIG_ORIGIN_FILE:
                die(_("bad numeric config value '%s' for '%s' in file %s: %s"),
-                   value, name, cf->name, _(error_type));
+                   value, name, kvi->filename, _(error_type));
        case CONFIG_ORIGIN_STDIN:
                die(_("bad numeric config value '%s' for '%s' in standard input: %s"),
                    value, name, _(error_type));
        case CONFIG_ORIGIN_SUBMODULE_BLOB:
                die(_("bad numeric config value '%s' for '%s' in submodule-blob %s: %s"),
-                   value, name, cf->name, _(error_type));
+                   value, name, kvi->filename, _(error_type));
        case CONFIG_ORIGIN_CMDLINE:
                die(_("bad numeric config value '%s' for '%s' in command line %s: %s"),
-                   value, name, cf->name, _(error_type));
+                   value, name, kvi->filename, _(error_type));
        default:
                die(_("bad numeric config value '%s' for '%s' in %s: %s"),
-                   value, name, cf->name, _(error_type));
+                   value, name, kvi->filename, _(error_type));
        }
 }
 
-int git_config_int(const char *name, const char *value)
+int git_config_int(const char *name, const char *value,
+                  const struct key_value_info *kvi)
 {
        int ret;
        if (!git_parse_int(value, &ret))
-               die_bad_number(name, value);
+               die_bad_number(name, value, kvi);
        return ret;
 }
 
-int64_t git_config_int64(const char *name, const char *value)
+int64_t git_config_int64(const char *name, const char *value,
+                        const struct key_value_info *kvi)
 {
        int64_t ret;
        if (!git_parse_int64(value, &ret))
-               die_bad_number(name, value);
+               die_bad_number(name, value, kvi);
        return ret;
 }
 
-unsigned long git_config_ulong(const char *name, const char *value)
+unsigned long git_config_ulong(const char *name, const char *value,
+                              const struct key_value_info *kvi)
 {
        unsigned long ret;
        if (!git_parse_ulong(value, &ret))
-               die_bad_number(name, value);
+               die_bad_number(name, value, kvi);
        return ret;
 }
 
-ssize_t git_config_ssize_t(const char *name, const char *value)
+ssize_t git_config_ssize_t(const char *name, const char *value,
+                          const struct key_value_info *kvi)
 {
        ssize_t ret;
        if (!git_parse_ssize_t(value, &ret))
-               die_bad_number(name, value);
+               die_bad_number(name, value, kvi);
        return ret;
 }
 
-static int git_parse_maybe_bool_text(const char *value)
-{
-       if (!value)
-               return 1;
-       if (!*value)
-               return 0;
-       if (!strcasecmp(value, "true")
-           || !strcasecmp(value, "yes")
-           || !strcasecmp(value, "on"))
-               return 1;
-       if (!strcasecmp(value, "false")
-           || !strcasecmp(value, "no")
-           || !strcasecmp(value, "off"))
-               return 0;
-       return -1;
-}
-
 static const struct fsync_component_name {
        const char *name;
        enum fsync_component component_bits;
@@ -1423,17 +1317,8 @@ next_name:
        return (current & ~negative) | positive;
 }
 
-int git_parse_maybe_bool(const char *value)
-{
-       int v = git_parse_maybe_bool_text(value);
-       if (0 <= v)
-               return v;
-       if (git_parse_int(value, &v))
-               return !!v;
-       return -1;
-}
-
-int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
+int git_config_bool_or_int(const char *name, const char *value,
+                          const struct key_value_info *kvi, int *is_bool)
 {
        int v = git_parse_maybe_bool_text(value);
        if (0 <= v) {
@@ -1441,7 +1326,7 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
                return v;
        }
        *is_bool = 0;
-       return git_config_int(name, value);
+       return git_config_int(name, value, kvi);
 }
 
 int git_config_bool(const char *name, const char *value)
@@ -1489,7 +1374,8 @@ int git_config_color(char *dest, const char *var, const char *value)
        return 0;
 }
 
-static int git_default_core_config(const char *var, const char *value, void *cb)
+static int git_default_core_config(const char *var, const char *value,
+                                  const struct config_context *ctx, void *cb)
 {
        /* This needs a better name */
        if (!strcmp(var, "core.filemode")) {
@@ -1501,10 +1387,15 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
                return 0;
        }
        if (!strcmp(var, "core.checkstat")) {
+               if (!value)
+                       return config_error_nonbool(var);
                if (!strcasecmp(value, "default"))
                        check_stat = 1;
                else if (!strcasecmp(value, "minimal"))
                        check_stat = 0;
+               else
+                       return error(_("invalid value for '%s': '%s'"),
+                                    var, value);
        }
 
        if (!strcmp(var, "core.quotepath")) {
@@ -1566,7 +1457,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
                else if (!git_parse_maybe_bool_text(value))
                        default_abbrev = the_hash_algo->hexsz;
                else {
-                       int abbrev = git_config_int(var, value);
+                       int abbrev = git_config_int(var, value, ctx->kvi);
                        if (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)
                                return error(_("abbrev length out of range: %d"), abbrev);
                        default_abbrev = abbrev;
@@ -1578,7 +1469,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
                return set_disambiguate_hint_config(var, value);
 
        if (!strcmp(var, "core.loosecompression")) {
-               int level = git_config_int(var, value);
+               int level = git_config_int(var, value, ctx->kvi);
                if (level == -1)
                        level = Z_DEFAULT_COMPRESSION;
                else if (level < 0 || level > Z_BEST_COMPRESSION)
@@ -1589,7 +1480,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp(var, "core.compression")) {
-               int level = git_config_int(var, value);
+               int level = git_config_int(var, value, ctx->kvi);
                if (level == -1)
                        level = Z_DEFAULT_COMPRESSION;
                else if (level < 0 || level > Z_BEST_COMPRESSION)
@@ -1603,7 +1494,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
 
        if (!strcmp(var, "core.packedgitwindowsize")) {
                int pgsz_x2 = getpagesize() * 2;
-               packed_git_window_size = git_config_ulong(var, value);
+               packed_git_window_size = git_config_ulong(var, value, ctx->kvi);
 
                /* This value must be multiple of (pagesize * 2) */
                packed_git_window_size /= pgsz_x2;
@@ -1614,17 +1505,17 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp(var, "core.bigfilethreshold")) {
-               big_file_threshold = git_config_ulong(var, value);
+               big_file_threshold = git_config_ulong(var, value, ctx->kvi);
                return 0;
        }
 
        if (!strcmp(var, "core.packedgitlimit")) {
-               packed_git_limit = git_config_ulong(var, value);
+               packed_git_limit = git_config_ulong(var, value, ctx->kvi);
                return 0;
        }
 
        if (!strcmp(var, "core.deltabasecachelimit")) {
-               delta_base_cache_limit = git_config_ulong(var, value);
+               delta_base_cache_limit = git_config_ulong(var, value, ctx->kvi);
                return 0;
        }
 
@@ -1661,12 +1552,12 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       if (!strcmp(var, "core.checkroundtripencoding")) {
-               check_roundtrip_encoding = xstrdup(value);
-               return 0;
-       }
+       if (!strcmp(var, "core.checkroundtripencoding"))
+               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;
        }
@@ -1674,16 +1565,19 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
        if (!strcmp(var, "core.editor"))
                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 = xstrdup(value);
                        auto_comment_line_char = 0;
                } else
-                       return error(_("core.commentChar should only be one character"));
+                       return error(_("%s must have at least one character"), var);
                return 0;
        }
 
@@ -1736,6 +1630,8 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp(var, "core.createobject")) {
+               if (!value)
+                       return config_error_nonbool(var);
                if (!strcmp(value, "rename"))
                        object_creation_mode = OBJECT_CREATION_USES_RENAMES;
                else if (!strcmp(value, "link"))
@@ -1770,13 +1666,13 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       if (!strcmp(var, "core.usereplacerefs")) {
-               read_replace_refs = git_config_bool(var, value);
+       if (!strcmp(var, "core.maxtreedepth")) {
+               max_allowed_tree_depth = git_config_int(var, value, ctx->kvi);
                return 0;
        }
 
        /* Add other config variables here and to Documentation/config.txt. */
-       return platform_core_config(var, value, cb);
+       return platform_core_config(var, value, ctx, cb);
 }
 
 static int git_default_sparse_config(const char *var, const char *value)
@@ -1878,15 +1774,28 @@ static int git_default_mailmap_config(const char *var, const char *value)
        return 0;
 }
 
-int git_default_config(const char *var, const char *value, void *cb)
+static int git_default_attr_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "attr.tree"))
+               return git_config_string(&git_attr_tree, var, value);
+
+       /*
+        * Add other attribute related config variables here and to
+        * Documentation/config/attr.txt.
+        */
+       return 0;
+}
+
+int git_default_config(const char *var, const char *value,
+                      const struct config_context *ctx, void *cb)
 {
        if (starts_with(var, "core."))
-               return git_default_core_config(var, value, cb);
+               return git_default_core_config(var, value, ctx, cb);
 
        if (starts_with(var, "user.") ||
            starts_with(var, "author.") ||
            starts_with(var, "committer."))
-               return git_ident_config(var, value, cb);
+               return git_ident_config(var, value, ctx, cb);
 
        if (starts_with(var, "i18n."))
                return git_default_i18n_config(var, value);
@@ -1900,6 +1809,9 @@ int git_default_config(const char *var, const char *value, void *cb)
        if (starts_with(var, "mailmap."))
                return git_default_mailmap_config(var, value);
 
+       if (starts_with(var, "attr."))
+               return git_default_attr_config(var, value);
+
        if (starts_with(var, "advice.") || starts_with(var, "color.advice"))
                return git_default_advice_config(var, value);
 
@@ -1909,12 +1821,12 @@ int git_default_config(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp(var, "pack.packsizelimit")) {
-               pack_size_limit_cfg = git_config_ulong(var, value);
+               pack_size_limit_cfg = git_config_ulong(var, value, ctx->kvi);
                return 0;
        }
 
        if (!strcmp(var, "pack.compression")) {
-               int level = git_config_int(var, value);
+               int level = git_config_int(var, value, ctx->kvi);
                if (level == -1)
                        level = Z_DEFAULT_COMPRESSION;
                else if (level < 0 || level > Z_BEST_COMPRESSION)
@@ -1936,36 +1848,36 @@ int git_default_config(const char *var, const char *value, void *cb)
  * fgetc, ungetc, ftell of top need to be initialized before calling
  * this function.
  */
-static int do_config_from(struct config_source *top, config_fn_t fn, void *data,
+static int do_config_from(struct config_source *top, config_fn_t fn,
+                         void *data, enum config_scope scope,
                          const struct config_options *opts)
 {
+       struct key_value_info kvi = KVI_INIT;
        int ret;
 
        /* push config-file parsing state stack */
-       top->prev = cf;
        top->linenr = 1;
        top->eof = 0;
        top->total_len = 0;
        strbuf_init(&top->value, 1024);
        strbuf_init(&top->var, 1024);
-       cf = top;
+       kvi_from_source(top, scope, &kvi);
 
-       ret = git_parse_source(fn, data, opts);
+       ret = git_parse_source(top, fn, &kvi, data, opts);
 
-       /* pop config-file parsing state stack */
        strbuf_release(&top->value);
        strbuf_release(&top->var);
-       cf = top->prev;
 
        return ret;
 }
 
 static int do_config_from_file(config_fn_t fn,
-               const enum config_origin_type origin_type,
-               const char *name, const char *path, FILE *f,
-               void *data, const struct config_options *opts)
+                              const enum config_origin_type origin_type,
+                              const char *name, const char *path, FILE *f,
+                              void *data, enum config_scope scope,
+                              const struct config_options *opts)
 {
-       struct config_source top;
+       struct config_source top = CONFIG_SOURCE_INIT;
        int ret;
 
        top.u.file = f;
@@ -1978,19 +1890,20 @@ static int do_config_from_file(config_fn_t fn,
        top.do_ftell = config_file_ftell;
 
        flockfile(f);
-       ret = do_config_from(&top, fn, data, opts);
+       ret = do_config_from(&top, fn, data, scope, opts);
        funlockfile(f);
        return ret;
 }
 
-static int git_config_from_stdin(config_fn_t fn, void *data)
+static int git_config_from_stdin(config_fn_t fn, void *data,
+                                enum config_scope scope)
 {
        return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
-                                  data, NULL);
+                                  data, scope, NULL);
 }
 
 int git_config_from_file_with_options(config_fn_t fn, const char *filename,
-                                     void *data,
+                                     void *data, enum config_scope scope,
                                      const struct config_options *opts)
 {
        int ret = -1;
@@ -2001,7 +1914,7 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,
        f = fopen_or_warn(filename, "r");
        if (f) {
                ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
-                                         filename, f, data, opts);
+                                         filename, f, data, scope, opts);
                fclose(f);
        }
        return ret;
@@ -2009,15 +1922,17 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,
 
 int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 {
-       return git_config_from_file_with_options(fn, filename, data, NULL);
+       return git_config_from_file_with_options(fn, filename, data,
+                                                CONFIG_SCOPE_UNKNOWN, NULL);
 }
 
 int git_config_from_mem(config_fn_t fn,
                        const enum config_origin_type origin_type,
                        const char *name, const char *buf, size_t len,
-                       void *data, const struct config_options *opts)
+                       void *data, enum config_scope scope,
+                       const struct config_options *opts)
 {
-       struct config_source top;
+       struct config_source top = CONFIG_SOURCE_INIT;
 
        top.u.buf.buf = buf;
        top.u.buf.len = len;
@@ -2030,14 +1945,15 @@ int git_config_from_mem(config_fn_t fn,
        top.do_ungetc = config_buf_ungetc;
        top.do_ftell = config_buf_ftell;
 
-       return do_config_from(&top, fn, data, opts);
+       return do_config_from(&top, fn, data, scope, opts);
 }
 
 int git_config_from_blob_oid(config_fn_t fn,
                              const char *name,
                              struct repository *repo,
                              const struct object_id *oid,
-                             void *data)
+                             void *data,
+                             enum config_scope scope)
 {
        enum object_type type;
        char *buf;
@@ -2053,7 +1969,7 @@ int git_config_from_blob_oid(config_fn_t fn,
        }
 
        ret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,
-                                 data, NULL);
+                                 data, scope, NULL);
        free(buf);
 
        return ret;
@@ -2062,13 +1978,14 @@ int git_config_from_blob_oid(config_fn_t fn,
 static int git_config_from_blob_ref(config_fn_t fn,
                                    struct repository *repo,
                                    const char *name,
-                                   void *data)
+                                   void *data,
+                                   enum config_scope scope)
 {
        struct object_id oid;
 
        if (repo_get_oid(repo, name, &oid) < 0)
                return error(_("unable to resolve config blob '%s'"), name);
-       return git_config_from_blob_oid(fn, name, repo, &oid, data);
+       return git_config_from_blob_oid(fn, name, repo, &oid, data, scope);
 }
 
 char *git_system_config(void)
@@ -2080,7 +1997,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;
@@ -2094,34 +2031,13 @@ void git_global_config(char **user_out, char **xdg_out)
        *xdg_out = xdg_config;
 }
 
-/*
- * Parse environment variable 'k' as a boolean (in various
- * possible spellings); if missing, use the default value 'def'.
- */
-int git_env_bool(const char *k, int def)
-{
-       const char *v = getenv(k);
-       return v ? git_config_bool(k, v) : def;
-}
-
-/*
- * Parse environment variable 'k' as ulong with possibly a unit
- * suffix; if missing, use the default value 'val'.
- */
-unsigned long git_env_ulong(const char *k, unsigned long val)
-{
-       const char *v = getenv(k);
-       if (v && !git_parse_ulong(v, &val))
-               die(_("failed to parse %s"), k);
-       return val;
-}
-
 int git_config_system(void)
 {
        return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
 static int do_git_config_sequence(const struct config_options *opts,
+                                 const struct repository *repo,
                                  config_fn_t fn, void *data)
 {
        int ret = 0;
@@ -2129,57 +2045,68 @@ static int do_git_config_sequence(const struct config_options *opts,
        char *xdg_config = NULL;
        char *user_config = NULL;
        char *repo_config;
-       enum config_scope prev_parsing_scope = current_parsing_scope;
+       char *worktree_config;
+
+       /*
+        * Ensure that either:
+        * - the git_dir and commondir are both set, or
+        * - the git_dir and commondir are both NULL
+        */
+       if (!opts->git_dir != !opts->commondir)
+               BUG("only one of commondir and git_dir is non-NULL");
 
-       if (opts->commondir)
+       if (opts->commondir) {
                repo_config = mkpathdup("%s/config", opts->commondir);
-       else if (opts->git_dir)
-               BUG("git_dir without commondir");
-       else
+               worktree_config = mkpathdup("%s/config.worktree", opts->git_dir);
+       } else {
                repo_config = NULL;
+               worktree_config = NULL;
+       }
 
-       current_parsing_scope = CONFIG_SCOPE_SYSTEM;
        if (git_config_system() && system_config &&
            !access_or_die(system_config, R_OK,
                           opts->system_gently ? ACCESS_EACCES_OK : 0))
-               ret += git_config_from_file(fn, system_config, data);
+               ret += git_config_from_file_with_options(fn, system_config,
+                                                        data, CONFIG_SCOPE_SYSTEM,
+                                                        NULL);
 
-       current_parsing_scope = CONFIG_SCOPE_GLOBAL;
-       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(fn, xdg_config, data);
+               ret += git_config_from_file_with_options(fn, xdg_config, data,
+                                                        CONFIG_SCOPE_GLOBAL, NULL);
 
        if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
-               ret += git_config_from_file(fn, user_config, data);
+               ret += git_config_from_file_with_options(fn, user_config, data,
+                                                        CONFIG_SCOPE_GLOBAL, NULL);
 
-       current_parsing_scope = CONFIG_SCOPE_LOCAL;
        if (!opts->ignore_repo && repo_config &&
            !access_or_die(repo_config, R_OK, 0))
-               ret += git_config_from_file(fn, repo_config, data);
+               ret += git_config_from_file_with_options(fn, repo_config, data,
+                                                        CONFIG_SCOPE_LOCAL, NULL);
 
-       current_parsing_scope = CONFIG_SCOPE_WORKTREE;
-       if (!opts->ignore_worktree && repository_format_worktree_config) {
-               char *path = git_pathdup("config.worktree");
-               if (!access_or_die(path, R_OK, 0))
-                       ret += git_config_from_file(fn, path, data);
-               free(path);
+       if (!opts->ignore_worktree && worktree_config &&
+           repo && repo->repository_format_worktree_config &&
+           !access_or_die(worktree_config, R_OK, 0)) {
+                       ret += git_config_from_file_with_options(fn, worktree_config, data,
+                                                                CONFIG_SCOPE_WORKTREE,
+                                                                NULL);
        }
 
-       current_parsing_scope = CONFIG_SCOPE_COMMAND;
        if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)
                die(_("unable to parse command-line config"));
 
-       current_parsing_scope = prev_parsing_scope;
        free(system_config);
        free(xdg_config);
        free(user_config);
        free(repo_config);
+       free(worktree_config);
        return ret;
 }
 
 int config_with_options(config_fn_t fn, void *data,
                        struct git_config_source *config_source,
+                       struct repository *repo,
                        const struct config_options *opts)
 {
        struct config_include_data inc = CONFIG_INCLUDE_INIT;
@@ -2189,29 +2116,27 @@ int config_with_options(config_fn_t fn, void *data,
                inc.fn = fn;
                inc.data = data;
                inc.opts = opts;
+               inc.repo = repo;
                inc.config_source = config_source;
                fn = git_config_include;
                data = &inc;
        }
 
-       if (config_source)
-               current_parsing_scope = config_source->scope;
-
        /*
         * If we have a specific filename, use it. Otherwise, follow the
         * regular lookup sequence.
         */
        if (config_source && config_source->use_stdin) {
-               ret = git_config_from_stdin(fn, data);
+               ret = git_config_from_stdin(fn, data, config_source->scope);
        } else if (config_source && config_source->file) {
-               ret = git_config_from_file(fn, config_source->file, data);
+               ret = git_config_from_file_with_options(fn, config_source->file,
+                                                       data, config_source->scope,
+                                                       NULL);
        } else if (config_source && config_source->blob) {
-               struct repository *repo = config_source->repo ?
-                       config_source->repo : the_repository;
                ret = git_config_from_blob_ref(fn, repo, config_source->blob,
-                                               data);
+                                              data, config_source->scope);
        } else {
-               ret = do_git_config_sequence(opts, fn, data);
+               ret = do_git_config_sequence(opts, repo, fn, data);
        }
 
        if (inc.remote_urls) {
@@ -2221,26 +2146,24 @@ int config_with_options(config_fn_t fn, void *data,
        return ret;
 }
 
-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
+static void configset_iter(struct config_set *set, config_fn_t fn, void *data)
 {
        int i, value_index;
        struct string_list *values;
        struct config_set_element *entry;
-       struct configset_list *list = &cs->list;
+       struct configset_list *list = &set->list;
+       struct config_context ctx = CONFIG_CONTEXT_INIT;
 
        for (i = 0; i < list->nr; i++) {
                entry = list->items[i].e;
                value_index = list->items[i].value_index;
                values = &entry->value_list;
 
-               current_config_kvi = values->items[value_index].util;
-
-               if (fn(entry->key, values->items[value_index].string, data) < 0)
+               ctx.kvi = values->items[value_index].util;
+               if (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)
                        git_die_config_linenr(entry->key,
-                                             current_config_kvi->filename,
-                                             current_config_kvi->linenr);
-
-               current_config_kvi = NULL;
+                                             ctx.kvi->filename,
+                                             ctx.kvi->linenr);
        }
 }
 
@@ -2268,7 +2191,7 @@ void read_early_config(config_fn_t cb, void *data)
                opts.git_dir = gitdir.buf;
        }
 
-       config_with_options(cb, data, NULL, &opts);
+       config_with_options(cb, data, NULL, NULL, &opts);
 
        strbuf_release(&commondir);
        strbuf_release(&gitdir);
@@ -2288,36 +2211,47 @@ void read_very_early_config(config_fn_t cb, void *data)
        opts.ignore_cmdline = 1;
        opts.system_gently = 1;
 
-       config_with_options(cb, data, NULL, &opts);
+       config_with_options(cb, data, NULL, NULL, &opts);
 }
 
-static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
+RESULT_MUST_BE_USED
+static int configset_find_element(struct config_set *set, const char *key,
+                                 struct config_set_element **dest)
 {
        struct config_set_element k;
        struct config_set_element *found_entry;
        char *normalized_key;
+       int ret;
+
        /*
         * `key` may come from the user, so normalize it before using it
         * for querying entries from the hashmap.
         */
-       if (git_config_parse_key(key, &normalized_key, NULL))
-               return NULL;
+       ret = git_config_parse_key(key, &normalized_key, NULL);
+       if (ret)
+               return ret;
 
        hashmap_entry_init(&k.ent, strhash(normalized_key));
        k.key = normalized_key;
-       found_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);
+       found_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);
        free(normalized_key);
-       return found_entry;
+       *dest = found_entry;
+       return 0;
 }
 
-static int configset_add_value(struct config_set *cs, const char *key, const char *value)
+static int configset_add_value(const struct key_value_info *kvi_p,
+                              struct config_set *set, const char *key,
+                              const char *value)
 {
        struct config_set_element *e;
        struct string_list_item *si;
        struct configset_list_item *l_item;
        struct key_value_info *kv_info = xmalloc(sizeof(*kv_info));
+       int ret;
 
-       e = configset_find_element(cs, key);
+       ret = configset_find_element(set, key, &e);
+       if (ret)
+               return ret;
        /*
         * Since the keys are being fed by git_config*() callback mechanism, they
         * are already normalized. So simply add them without any further munging.
@@ -2327,28 +2261,16 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
                hashmap_entry_init(&e->ent, strhash(key));
                e->key = xstrdup(key);
                string_list_init_dup(&e->value_list);
-               hashmap_add(&cs->config_hash, &e->ent);
+               hashmap_add(&set->config_hash, &e->ent);
        }
        si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
 
-       ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);
-       l_item = &cs->list.items[cs->list.nr++];
+       ALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);
+       l_item = &set->list.items[set->list.nr++];
        l_item->e = e;
        l_item->value_index = e->value_list.nr - 1;
 
-       if (!cf)
-               BUG("configset_add_value has no source");
-       if (cf->name) {
-               kv_info->filename = strintern(cf->name);
-               kv_info->linenr = cf->linenr;
-               kv_info->origin_type = cf->origin_type;
-       } else {
-               /* for values read from `git_config_from_parameters()` */
-               kv_info->filename = NULL;
-               kv_info->linenr = -1;
-               kv_info->origin_type = CONFIG_ORIGIN_CMDLINE;
-       }
-       kv_info->scope = current_parsing_scope;
+       *kv_info = *kvi_p;
        si->util = kv_info;
 
        return 0;
@@ -2367,84 +2289,131 @@ static int config_set_element_cmp(const void *cmp_data UNUSED,
        return strcmp(e1->key, e2->key);
 }
 
-void git_configset_init(struct config_set *cs)
+void git_configset_init(struct config_set *set)
 {
-       hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);
-       cs->hash_initialized = 1;
-       cs->list.nr = 0;
-       cs->list.alloc = 0;
-       cs->list.items = NULL;
+       hashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);
+       set->hash_initialized = 1;
+       set->list.nr = 0;
+       set->list.alloc = 0;
+       set->list.items = NULL;
 }
 
-void git_configset_clear(struct config_set *cs)
+void git_configset_clear(struct config_set *set)
 {
        struct config_set_element *entry;
        struct hashmap_iter iter;
-       if (!cs->hash_initialized)
+       if (!set->hash_initialized)
                return;
 
-       hashmap_for_each_entry(&cs->config_hash, &iter, entry,
+       hashmap_for_each_entry(&set->config_hash, &iter, entry,
                                ent /* member name */) {
                free(entry->key);
                string_list_clear(&entry->value_list, 1);
        }
-       hashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);
-       cs->hash_initialized = 0;
-       free(cs->list.items);
-       cs->list.nr = 0;
-       cs->list.alloc = 0;
-       cs->list.items = NULL;
+       hashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);
+       set->hash_initialized = 0;
+       free(set->list.items);
+       set->list.nr = 0;
+       set->list.alloc = 0;
+       set->list.items = NULL;
 }
 
-static int config_set_callback(const char *key, const char *value, void *cb)
+static int config_set_callback(const char *key, const char *value,
+                              const struct config_context *ctx,
+                              void *cb)
 {
-       struct config_set *cs = cb;
-       configset_add_value(cs, key, value);
+       struct config_set *set = cb;
+       configset_add_value(ctx->kvi, set, key, value);
        return 0;
 }
 
-int git_configset_add_file(struct config_set *cs, const char *filename)
+int git_configset_add_file(struct config_set *set, const char *filename)
 {
-       return git_config_from_file(config_set_callback, filename, cs);
+       return git_config_from_file(config_set_callback, filename, set);
 }
 
-int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
+int git_configset_get_value(struct config_set *set, const char *key,
+                           const char **value, struct key_value_info *kvi)
 {
        const struct string_list *values = NULL;
+       int ret;
+       struct string_list_item item;
        /*
         * Follows "last one wins" semantic, i.e., if there are multiple matches for the
         * queried key in the files of the configset, the value returned will be the last
         * value in the value list for that key.
         */
-       values = git_configset_get_value_multi(cs, key);
+       if ((ret = git_configset_get_value_multi(set, key, &values)))
+               return ret;
 
-       if (!values)
-               return 1;
        assert(values->nr > 0);
-       *value = values->items[values->nr - 1].string;
+       item = values->items[values->nr - 1];
+       *value = item.string;
+       if (kvi)
+               *kvi = *((struct key_value_info *)item.util);
+       return 0;
+}
+
+int git_configset_get_value_multi(struct config_set *set, const char *key,
+                                 const struct string_list **dest)
+{
+       struct config_set_element *e;
+       int ret;
+
+       if ((ret = configset_find_element(set, key, &e)))
+               return ret;
+       else if (!e)
+               return 1;
+       *dest = &e->value_list;
+
        return 0;
 }
 
-const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)
+static int check_multi_string(struct string_list_item *item, void *util)
 {
-       struct config_set_element *e = configset_find_element(cs, key);
-       return e ? &e->value_list : NULL;
+       return item->string ? 0 : config_error_nonbool(util);
 }
 
-int git_configset_get_string(struct config_set *cs, const char *key, char **dest)
+int git_configset_get_string_multi(struct config_set *cs, const char *key,
+                                  const struct string_list **dest)
+{
+       int ret;
+
+       if ((ret = git_configset_get_value_multi(cs, key, dest)))
+               return ret;
+       if ((ret = for_each_string_list((struct string_list *)*dest,
+                                       check_multi_string, (void *)key)))
+               return ret;
+
+       return 0;
+}
+
+int git_configset_get(struct config_set *set, const char *key)
+{
+       struct config_set_element *e;
+       int ret;
+
+       if ((ret = configset_find_element(set, key, &e)))
+               return ret;
+       else if (!e)
+               return 1;
+       return 0;
+}
+
+int git_configset_get_string(struct config_set *set, const char *key, char **dest)
 {
        const char *value;
-       if (!git_configset_get_value(cs, key, &value))
+       if (!git_configset_get_value(set, key, &value, NULL))
                return git_config_string((const char **)dest, key, value);
        else
                return 1;
 }
 
-static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
+static int git_configset_get_string_tmp(struct config_set *set, const char *key,
                                        const char **dest)
 {
        const char *value;
-       if (!git_configset_get_value(cs, key, &value)) {
+       if (!git_configset_get_value(set, key, &value, NULL)) {
                if (!value)
                        return config_error_nonbool(key);
                *dest = value;
@@ -2454,51 +2423,57 @@ static int git_configset_get_string_tmp(struct config_set *cs, const char *key,
        }
 }
 
-int git_configset_get_int(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_int(struct config_set *set, const char *key, int *dest)
 {
        const char *value;
-       if (!git_configset_get_value(cs, key, &value)) {
-               *dest = git_config_int(key, value);
+       struct key_value_info kvi;
+
+       if (!git_configset_get_value(set, key, &value, &kvi)) {
+               *dest = git_config_int(key, value, &kvi);
                return 0;
        } else
                return 1;
 }
 
-int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest)
+int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)
 {
        const char *value;
-       if (!git_configset_get_value(cs, key, &value)) {
-               *dest = git_config_ulong(key, value);
+       struct key_value_info kvi;
+
+       if (!git_configset_get_value(set, key, &value, &kvi)) {
+               *dest = git_config_ulong(key, value, &kvi);
                return 0;
        } else
                return 1;
 }
 
-int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_bool(struct config_set *set, const char *key, int *dest)
 {
        const char *value;
-       if (!git_configset_get_value(cs, key, &value)) {
+       if (!git_configset_get_value(set, key, &value, NULL)) {
                *dest = git_config_bool(key, value);
                return 0;
        } else
                return 1;
 }
 
-int git_configset_get_bool_or_int(struct config_set *cs, const char *key,
+int git_configset_get_bool_or_int(struct config_set *set, const char *key,
                                int *is_bool, int *dest)
 {
        const char *value;
-       if (!git_configset_get_value(cs, key, &value)) {
-               *dest = git_config_bool_or_int(key, value, is_bool);
+       struct key_value_info kvi;
+
+       if (!git_configset_get_value(set, key, &value, &kvi)) {
+               *dest = git_config_bool_or_int(key, value, &kvi, is_bool);
                return 0;
        } else
                return 1;
 }
 
-int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)
+int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)
 {
        const char *value;
-       if (!git_configset_get_value(cs, key, &value)) {
+       if (!git_configset_get_value(set, key, &value, NULL)) {
                *dest = git_parse_maybe_bool(value);
                if (*dest == -1)
                        return -1;
@@ -2507,10 +2482,10 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de
                return 1;
 }
 
-int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)
+int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)
 {
        const char *value;
-       if (!git_configset_get_value(cs, key, &value))
+       if (!git_configset_get_value(set, key, &value, NULL))
                return git_config_pathname(dest, key, value);
        else
                return 1;
@@ -2531,8 +2506,8 @@ static void repo_read_config(struct repository *repo)
                git_configset_clear(repo->config);
 
        git_configset_init(repo->config);
-
-       if (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0)
+       if (config_with_options(config_set_callback, repo->config, NULL,
+                               repo, &opts) < 0)
                /*
                 * config_with_options() normally returns only
                 * zero, as most errors are fatal, and
@@ -2567,18 +2542,31 @@ void repo_config(struct repository *repo, config_fn_t fn, void *data)
        configset_iter(repo->config, fn, data);
 }
 
+int repo_config_get(struct repository *repo, const char *key)
+{
+       git_config_check_init(repo);
+       return git_configset_get(repo->config, key);
+}
+
 int repo_config_get_value(struct repository *repo,
                          const char *key, const char **value)
 {
        git_config_check_init(repo);
-       return git_configset_get_value(repo->config, key, value);
+       return git_configset_get_value(repo->config, key, value, NULL);
+}
+
+int repo_config_get_value_multi(struct repository *repo, const char *key,
+                               const struct string_list **dest)
+{
+       git_config_check_init(repo);
+       return git_configset_get_value_multi(repo->config, key, dest);
 }
 
-const struct string_list *repo_config_get_value_multi(struct repository *repo,
-                                                     const char *key)
+int repo_config_get_string_multi(struct repository *repo, const char *key,
+                                const struct string_list **dest)
 {
        git_config_check_init(repo);
-       return git_configset_get_value_multi(repo->config, key);
+       return git_configset_get_string_multi(repo->config, key, dest);
 }
 
 int repo_config_get_string(struct repository *repo,
@@ -2658,8 +2646,9 @@ static void read_protected_config(void)
                .ignore_worktree = 1,
                .system_gently = 1,
        };
+
        git_configset_init(&protected_config);
-       config_with_options(config_set_callback, &protected_config,
+       config_with_options(config_set_callback, &protected_config, NULL,
                            NULL, &opts);
 }
 
@@ -2681,14 +2670,25 @@ 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);
 }
 
-const struct string_list *git_config_get_value_multi(const char *key)
+int git_config_get_value_multi(const char *key, const struct string_list **dest)
 {
-       return repo_config_get_value_multi(the_repository, key);
+       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)
@@ -2835,7 +2835,8 @@ void git_die_config(const char *key, const char *err, ...)
                error_fn(err, params);
                va_end(params);
        }
-       values = git_config_get_value_multi(key);
+       if (git_config_get_value_multi(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);
 }
@@ -2859,6 +2860,7 @@ struct config_store_data {
        unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;
        unsigned int key_seen:1, section_seen:1, is_keys_section:1;
 };
+#define CONFIG_STORE_INIT { 0 }
 
 static void config_store_data_clear(struct config_store_data *store)
 {
@@ -2889,8 +2891,8 @@ static int matches(const char *key, const char *value,
                (value && !regexec(store->value_pattern, value, 0, NULL, 0));
 }
 
-static int store_aux_event(enum config_event_t type,
-                          size_t begin, size_t end, void *data)
+static int store_aux_event(enum config_event_t type, size_t begin, size_t end,
+                          struct config_source *cs, void *data)
 {
        struct config_store_data *store = data;
 
@@ -2902,10 +2904,10 @@ static int store_aux_event(enum config_event_t type,
        if (type == CONFIG_EVENT_SECTION) {
                int (*cmpfn)(const char *, const char *, size_t);
 
-               if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
-                       return error(_("invalid section name '%s'"), cf->var.buf);
+               if (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')
+                       return error(_("invalid section name '%s'"), cs->var.buf);
 
-               if (cf->subsection_case_sensitive)
+               if (cs->subsection_case_sensitive)
                        cmpfn = strncasecmp;
                else
                        cmpfn = strncmp;
@@ -2913,8 +2915,8 @@ static int store_aux_event(enum config_event_t type,
                /* Is this the section we were looking for? */
                store->is_keys_section =
                        store->parsed[store->parsed_nr].is_keys_section =
-                       cf->var.len - 1 == store->baselen &&
-                       !cmpfn(cf->var.buf, store->key, store->baselen);
+                       cs->var.len - 1 == store->baselen &&
+                       !cmpfn(cs->var.buf, store->key, store->baselen);
                if (store->is_keys_section) {
                        store->section_seen = 1;
                        ALLOC_GROW(store->seen, store->seen_nr + 1,
@@ -2928,7 +2930,8 @@ static int store_aux_event(enum config_event_t type,
        return 0;
 }
 
-static int store_aux(const char *key, const char *value, void *cb)
+static int store_aux(const char *key, const char *value,
+                    const struct config_context *ctx UNUSED, void *cb)
 {
        struct config_store_data *store = cb;
 
@@ -3008,6 +3011,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;
@@ -3048,7 +3052,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);
@@ -3137,9 +3145,9 @@ static void maybe_remove_section(struct config_store_data *store,
 }
 
 int git_config_set_in_file_gently(const char *config_filename,
-                                 const char *key, const char *value)
+                                 const char *key, const char *comment, const char *value)
 {
-       return git_config_set_multivar_in_file_gently(config_filename, key, value, NULL, 0);
+       return git_config_set_multivar_in_file_gently(config_filename, key, value, NULL, comment, 0);
 }
 
 void git_config_set_in_file(const char *config_filename,
@@ -3157,10 +3165,10 @@ int repo_config_set_worktree_gently(struct repository *r,
                                    const char *key, const char *value)
 {
        /* Only use worktree-specific config if it is already enabled. */
-       if (repository_format_worktree_config) {
+       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);
+                                       file, key, value, NULL, NULL, 0);
                free(file);
                return ret;
        }
@@ -3174,6 +3182,62 @@ void git_config_set(const char *key, const char *value)
        trace2_cmd_set_config(key, value);
 }
 
+/*
+ * The ownership rule is that the caller will own the string
+ * if it receives a piece of memory different from what it passed
+ * as the parameter.
+ */
+const char *git_config_prepare_comment_string(const char *comment)
+{
+       size_t leading_blanks;
+
+       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] == '#')
+               ; /* use it as-is */
+       else if (comment[0] == '#')
+               comment = xstrfmt(" %s", comment);
+       else
+               comment = xstrfmt(" # %s", comment);
+
+       return comment;
+}
+
+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.
@@ -3202,6 +3266,7 @@ void git_config_set(const char *key, const char *value)
 int git_config_set_multivar_in_file_gently(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;
@@ -3210,9 +3275,9 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
        char *filename_buf = NULL;
        char *contents = NULL;
        size_t contents_sz;
-       struct config_store_data store;
+       struct config_store_data store = CONFIG_STORE_INIT;
 
-       memset(&store, 0, sizeof(store));
+       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);
@@ -3254,7 +3319,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;
@@ -3302,7 +3367,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                 */
                if (git_config_from_file_with_options(store_aux,
                                                      config_filename,
-                                                     &store, &opts)) {
+                                                     &store, CONFIG_SCOPE_UNKNOWN,
+                                                     &opts)) {
                        error(_("invalid config file %s"), config_filename);
                        ret = CONFIG_INVALID_FILE;
                        goto out_free;
@@ -3407,7 +3473,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;
                }
 
@@ -3445,7 +3511,6 @@ 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,
@@ -3453,7 +3518,7 @@ void git_config_set_multivar_in_file(const char *config_filename,
                                     const char *value_pattern, unsigned flags)
 {
        if (!git_config_set_multivar_in_file_gently(config_filename, key, value,
-                                                   value_pattern, flags))
+                                                   value_pattern, NULL, flags))
                return;
        if (value)
                die(_("could not set '%s' to '%s'"), key, value);
@@ -3476,7 +3541,7 @@ int repo_config_set_multivar_gently(struct repository *r, const char *key,
        int res = git_config_set_multivar_in_file_gently(file,
                                                         key, value,
                                                         value_pattern,
-                                                        flags);
+                                                        NULL, flags);
        free(file);
        return res;
 }
@@ -3663,6 +3728,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename
                                                output[0] = '\t';
                                        }
                                } else {
+                                       strbuf_release(&copystr);
                                        copystr = store_create_section(new_name, &store);
                                }
                        }
@@ -3709,6 +3775,7 @@ out_no_rollback:
        free(filename_buf);
        config_store_data_clear(&store);
        strbuf_release(&buf);
+       strbuf_release(&copystr);
        return ret;
 }
 
@@ -3782,16 +3849,8 @@ int parse_config_key(const char *var,
        return 0;
 }
 
-const char *current_config_origin_type(void)
+const char *config_origin_type_name(enum config_origin_type type)
 {
-       int type;
-       if (current_config_kvi)
-               type = current_config_kvi->origin_type;
-       else if(cf)
-               type = cf->origin_type;
-       else
-               BUG("current_config_origin_type called outside config callback");
-
        switch (type) {
        case CONFIG_ORIGIN_BLOB:
                return "blob";
@@ -3828,34 +3887,6 @@ const char *config_scope_name(enum config_scope scope)
        }
 }
 
-const char *current_config_name(void)
-{
-       const char *name;
-       if (current_config_kvi)
-               name = current_config_kvi->filename;
-       else if (cf)
-               name = cf->name;
-       else
-               BUG("current_config_name called outside config callback");
-       return name ? name : "";
-}
-
-enum config_scope current_config_scope(void)
-{
-       if (current_config_kvi)
-               return current_config_kvi->scope;
-       else
-               return current_parsing_scope;
-}
-
-int current_config_line(void)
-{
-       if (current_config_kvi)
-               return current_config_kvi->linenr;
-       else
-               return cf->linenr;
-}
-
 int lookup_config(const char **mapping, int nr_mapping, const char *var)
 {
        int i;