connect: split git:// setup into a separate function
[gitweb.git] / config.c
index 0dfed682b86829fc06667d303c676e8abe6b3150..a77cae56ed26212c6bfbbd831c85c8cf64deb8b9 100644 (file)
--- a/config.c
+++ b/config.c
@@ -6,6 +6,8 @@
  *
  */
 #include "cache.h"
+#include "config.h"
+#include "repository.h"
 #include "lockfile.h"
 #include "exec_cmd.h"
 #include "strbuf.h"
@@ -13,6 +15,8 @@
 #include "hashmap.h"
 #include "string-list.h"
 #include "utf8.h"
+#include "dir.h"
+#include "color.h"
 
 struct config_source {
        struct config_source *prev;
@@ -66,15 +70,10 @@ static struct key_value_info *current_config_kvi;
  */
 static enum config_scope current_parsing_scope;
 
+static int core_compression_seen;
+static int pack_compression_seen;
 static int zlib_compression_seen;
 
-/*
- * Default config_set that contains key-value pairs from the usual set of config
- * config files (i.e repo specific .git/config, user wide ~/.gitconfig, XDG
- * config file and the global /etc/gitconfig)
- */
-static struct config_set the_config_set;
-
 static int config_file_fgetc(struct config_source *conf)
 {
        return getc_unlocked(conf->u.file);
@@ -132,7 +131,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc
        if (!path)
                return config_error_nonbool("include.path");
 
-       expanded = expand_user_path(path);
+       expanded = expand_user_path(path, 0);
        if (!expanded)
                return error("could not expand include path '%s'", path);
        path = expanded;
@@ -168,9 +167,118 @@ static int handle_path_include(const char *path, struct config_include_data *inc
        return ret;
 }
 
+static int prepare_include_condition_pattern(struct strbuf *pat)
+{
+       struct strbuf path = STRBUF_INIT;
+       char *expanded;
+       int prefix = 0;
+
+       expanded = expand_user_path(pat->buf, 1);
+       if (expanded) {
+               strbuf_reset(pat);
+               strbuf_addstr(pat, expanded);
+               free(expanded);
+       }
+
+       if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
+               const char *slash;
+
+               if (!cf || !cf->path)
+                       return error(_("relative config include "
+                                      "conditionals must come from files"));
+
+               strbuf_realpath(&path, cf->path, 1);
+               slash = find_last_dir_sep(path.buf);
+               if (!slash)
+                       die("BUG: how is this possible?");
+               strbuf_splice(pat, 0, 1, path.buf, slash - path.buf);
+               prefix = slash - path.buf + 1 /* slash */;
+       } else if (!is_absolute_path(pat->buf))
+               strbuf_insert(pat, 0, "**/", 3);
+
+       if (pat->len && is_dir_sep(pat->buf[pat->len - 1]))
+               strbuf_addstr(pat, "**");
+
+       strbuf_release(&path);
+       return prefix;
+}
+
+static int include_by_gitdir(const struct config_options *opts,
+                            const char *cond, size_t cond_len, int icase)
+{
+       struct strbuf text = STRBUF_INIT;
+       struct strbuf pattern = STRBUF_INIT;
+       int ret = 0, prefix;
+       const char *git_dir;
+       int already_tried_absolute = 0;
+
+       if (opts->git_dir)
+               git_dir = opts->git_dir;
+       else
+               goto done;
+
+       strbuf_realpath(&text, git_dir, 1);
+       strbuf_add(&pattern, cond, cond_len);
+       prefix = prepare_include_condition_pattern(&pattern);
+
+again:
+       if (prefix < 0)
+               goto done;
+
+       if (prefix > 0) {
+               /*
+                * perform literal matching on the prefix part so that
+                * any wildcard character in it can't create side effects.
+                */
+               if (text.len < prefix)
+                       goto done;
+               if (!icase && strncmp(pattern.buf, text.buf, prefix))
+                       goto done;
+               if (icase && strncasecmp(pattern.buf, text.buf, prefix))
+                       goto done;
+       }
+
+       ret = !wildmatch(pattern.buf + prefix, text.buf + prefix,
+                        icase ? WM_CASEFOLD : 0);
+
+       if (!ret && !already_tried_absolute) {
+               /*
+                * We've tried e.g. matching gitdir:~/work, but if
+                * ~/work is a symlink to /mnt/storage/work
+                * strbuf_realpath() will expand it, so the rule won't
+                * match. Let's match against a
+                * strbuf_add_absolute_path() version of the path,
+                * which'll do the right thing
+                */
+               strbuf_reset(&text);
+               strbuf_add_absolute_path(&text, git_dir);
+               already_tried_absolute = 1;
+               goto again;
+       }
+done:
+       strbuf_release(&pattern);
+       strbuf_release(&text);
+       return ret;
+}
+
+static int include_condition_is_true(const struct config_options *opts,
+                                    const char *cond, size_t cond_len)
+{
+
+       if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
+               return include_by_gitdir(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);
+
+       /* unknown conditionals are always false */
+       return 0;
+}
+
 int git_config_include(const char *var, const char *value, void *data)
 {
        struct config_include_data *inc = data;
+       const char *cond, *key;
+       int cond_len;
        int ret;
 
        /*
@@ -183,6 +291,12 @@ int git_config_include(const char *var, const char *value, void *data)
 
        if (!strcmp(var, "include.path"))
                ret = handle_path_include(value, inc);
+
+       if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
+           (cond && include_condition_is_true(inc->opts, cond, cond_len)) &&
+           !strcmp(key, "path"))
+               ret = handle_path_include(value, inc);
+
        return ret;
 }
 
@@ -199,11 +313,104 @@ void git_config_push_parameter(const char *text)
        strbuf_release(&env);
 }
 
+static inline int iskeychar(int c)
+{
+       return isalnum(c) || c == '-';
+}
+
+/*
+ * Auxiliary function to sanity-check and split the key into the section
+ * identifier and variable name.
+ *
+ * Returns 0 on success, -1 when there is an invalid character in the key and
+ * -2 if there is no section name in the key.
+ *
+ * store_key - pointer to char* which will hold a copy of the key with
+ *             lowercase section and variable name
+ * baselen - pointer to int which will hold the length of the
+ *           section + subsection part, can be NULL
+ */
+static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
+{
+       int i, dot, baselen;
+       const char *last_dot = strrchr(key, '.');
+
+       /*
+        * Since "key" actually contains the section name and the real
+        * key name separated by a dot, we have to know where the dot is.
+        */
+
+       if (last_dot == NULL || last_dot == key) {
+               if (!quiet)
+                       error("key does not contain a section: %s", key);
+               return -CONFIG_NO_SECTION_OR_NAME;
+       }
+
+       if (!last_dot[1]) {
+               if (!quiet)
+                       error("key does not contain variable name: %s", key);
+               return -CONFIG_NO_SECTION_OR_NAME;
+       }
+
+       baselen = last_dot - key;
+       if (baselen_)
+               *baselen_ = baselen;
+
+       /*
+        * Validate the key and while at it, lower case it for matching.
+        */
+       if (store_key)
+               *store_key = xmallocz(strlen(key));
+
+       dot = 0;
+       for (i = 0; key[i]; i++) {
+               unsigned char c = key[i];
+               if (c == '.')
+                       dot = 1;
+               /* Leave the extended basename untouched.. */
+               if (!dot || i > baselen) {
+                       if (!iskeychar(c) ||
+                           (i == baselen + 1 && !isalpha(c))) {
+                               if (!quiet)
+                                       error("invalid key: %s", key);
+                               goto out_free_ret_1;
+                       }
+                       c = tolower(c);
+               } else if (c == '\n') {
+                       if (!quiet)
+                               error("invalid key (newline): %s", key);
+                       goto out_free_ret_1;
+               }
+               if (store_key)
+                       (*store_key)[i] = c;
+       }
+
+       return 0;
+
+out_free_ret_1:
+       if (store_key) {
+               FREE_AND_NULL(*store_key);
+       }
+       return -CONFIG_INVALID_KEY;
+}
+
+int git_config_parse_key(const char *key, char **store_key, int *baselen)
+{
+       return git_config_parse_key_1(key, store_key, baselen, 0);
+}
+
+int git_config_key_is_valid(const char *key)
+{
+       return !git_config_parse_key_1(key, NULL, NULL, 1);
+}
+
 int git_config_parse_parameter(const char *text,
                               config_fn_t fn, void *data)
 {
        const char *value;
+       char *canonical_name;
        struct strbuf **pair;
+       int ret;
 
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
@@ -221,13 +428,15 @@ int git_config_parse_parameter(const char *text,
                strbuf_list_free(pair);
                return error("bogus config parameter: %s", text);
        }
-       strbuf_tolower(pair[0]);
-       if (fn(pair[0]->buf, value, data) < 0) {
-               strbuf_list_free(pair);
-               return -1;
+
+       if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {
+               ret = -1;
+       } else {
+               ret = (fn(canonical_name, value, data) < 0) ? -1 : 0;
+               free(canonical_name);
        }
        strbuf_list_free(pair);
-       return 0;
+       return ret;
 }
 
 int git_config_from_parameters(config_fn_t fn, void *data)
@@ -354,11 +563,6 @@ static char *parse_value(void)
        }
 }
 
-static inline int iskeychar(int c)
-{
-       return isalnum(c) || c == '-';
-}
-
 static int get_value(config_fn_t fn, void *data, struct strbuf *name)
 {
        int c;
@@ -393,7 +597,8 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)
         */
        cf->linenr--;
        ret = fn(name->buf, value, data);
-       cf->linenr++;
+       if (ret >= 0)
+               cf->linenr++;
        return ret;
 }
 
@@ -649,6 +854,15 @@ int git_parse_ulong(const char *value, unsigned long *ret)
        return 1;
 }
 
+static 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)
 {
@@ -707,7 +921,15 @@ unsigned long git_config_ulong(const char *name, const char *value)
        return ret;
 }
 
-int git_parse_maybe_bool(const char *value)
+ssize_t git_config_ssize_t(const char *name, const char *value)
+{
+       ssize_t ret;
+       if (!git_parse_ssize_t(value, &ret))
+               die_bad_number(name, value);
+       return ret;
+}
+
+static int git_parse_maybe_bool_text(const char *value)
 {
        if (!value)
                return 1;
@@ -724,9 +946,9 @@ int git_parse_maybe_bool(const char *value)
        return -1;
 }
 
-int git_config_maybe_bool(const char *name, const char *value)
+int git_parse_maybe_bool(const char *value)
 {
-       int v = git_parse_maybe_bool(value);
+       int v = git_parse_maybe_bool_text(value);
        if (0 <= v)
                return v;
        if (git_parse_int(value, &v))
@@ -736,7 +958,7 @@ int git_config_maybe_bool(const char *name, const char *value)
 
 int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
 {
-       int v = git_parse_maybe_bool(value);
+       int v = git_parse_maybe_bool_text(value);
        if (0 <= v) {
                *is_bool = 1;
                return v;
@@ -763,7 +985,7 @@ int git_config_pathname(const char **dest, const char *var, const char *value)
 {
        if (!value)
                return config_error_nonbool(var);
-       *dest = expand_user_path(value);
+       *dest = expand_user_path(value, 0);
        if (!*dest)
                die(_("failed to expand user dir in: '%s'"), value);
        return 0;
@@ -824,7 +1046,12 @@ static int git_default_core_config(const char *var, const char *value)
        }
 
        if (!strcmp(var, "core.logallrefupdates")) {
-               log_all_ref_updates = git_config_bool(var, value);
+               if (value && !strcasecmp(value, "always"))
+                       log_all_ref_updates = LOG_REFS_ALWAYS;
+               else if (git_config_bool(var, value))
+                       log_all_ref_updates = LOG_REFS_NORMAL;
+               else
+                       log_all_ref_updates = LOG_REFS_NONE;
                return 0;
        }
 
@@ -834,13 +1061,22 @@ static int git_default_core_config(const char *var, const char *value)
        }
 
        if (!strcmp(var, "core.abbrev")) {
-               int abbrev = git_config_int(var, value);
-               if (abbrev < minimum_abbrev || abbrev > 40)
-                       return -1;
-               default_abbrev = abbrev;
+               if (!value)
+                       return config_error_nonbool(var);
+               if (!strcasecmp(value, "auto"))
+                       default_abbrev = -1;
+               else {
+                       int abbrev = git_config_int(var, value);
+                       if (abbrev < minimum_abbrev || abbrev > 40)
+                               return error("abbrev length out of range: %d", abbrev);
+                       default_abbrev = abbrev;
+               }
                return 0;
        }
 
+       if (!strcmp(var, "core.disambiguate"))
+               return set_disambiguate_hint_config(var, value);
+
        if (!strcmp(var, "core.loosecompression")) {
                int level = git_config_int(var, value);
                if (level == -1)
@@ -862,6 +1098,8 @@ static int git_default_core_config(const char *var, const char *value)
                core_compression_seen = 1;
                if (!zlib_compression_seen)
                        zlib_compression_level = level;
+               if (!pack_compression_seen)
+                       pack_compression_level = level;
                return 0;
        }
 
@@ -927,9 +1165,6 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
-       if (!strcmp(var, "core.pager"))
-               return git_config_string(&pager_program, var, value);
-
        if (!strcmp(var, "core.editor"))
                return git_config_string(&editor_program, var, value);
 
@@ -1116,6 +1351,9 @@ int git_default_config(const char *var, const char *value, void *dummy)
        if (starts_with(var, "advice."))
                return git_default_advice_config(var, value);
 
+       if (git_color_config(var, value, dummy) < 0)
+               return -1;
+
        if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
                pager_use_color = git_config_bool(var,value);
                return 0;
@@ -1125,6 +1363,18 @@ int git_default_config(const char *var, const char *value, void *dummy)
                pack_size_limit_cfg = git_config_ulong(var, value);
                return 0;
        }
+
+       if (!strcmp(var, "pack.compression")) {
+               int level = git_config_int(var, value);
+               if (level == -1)
+                       level = Z_DEFAULT_COMPRESSION;
+               else if (level < 0 || level > Z_BEST_COMPRESSION)
+                       die(_("bad pack compression level %d"), level);
+               pack_compression_level = level;
+               pack_compression_seen = 1;
+               return 0;
+       }
+
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
@@ -1185,7 +1435,7 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
        int ret = -1;
        FILE *f;
 
-       f = fopen(filename, "r");
+       f = fopen_or_warn(filename, "r");
        if (f) {
                flockfile(f);
                ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data);
@@ -1214,17 +1464,17 @@ int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_typ
        return do_config_from(&top, fn, data);
 }
 
-static int git_config_from_blob_sha1(config_fn_t fn,
-                                    const char *name,
-                                    const unsigned char *sha1,
-                                    void *data)
+int git_config_from_blob_oid(config_fn_t fn,
+                             const char *name,
+                             const struct object_id *oid,
+                             void *data)
 {
        enum object_type type;
        char *buf;
        unsigned long size;
        int ret;
 
-       buf = read_sha1_file(sha1, &type, &size);
+       buf = read_sha1_file(oid->hash, &type, &size);
        if (!buf)
                return error("unable to load config blob object '%s'", name);
        if (type != OBJ_BLOB) {
@@ -1242,11 +1492,11 @@ static int git_config_from_blob_ref(config_fn_t fn,
                                    const char *name,
                                    void *data)
 {
-       unsigned char sha1[20];
+       struct object_id oid;
 
-       if (get_sha1(name, sha1) < 0)
+       if (get_oid(name, &oid) < 0)
                return error("unable to resolve config blob '%s'", name);
-       return git_config_from_blob_sha1(fn, name, sha1, data);
+       return git_config_from_blob_oid(fn, name, &oid, data);
 }
 
 const char *git_etc_gitconfig(void)
@@ -1284,12 +1534,18 @@ int git_config_system(void)
        return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
-static int do_git_config_sequence(config_fn_t fn, void *data)
+static int do_git_config_sequence(const struct config_options *opts,
+                                 config_fn_t fn, void *data)
 {
        int ret = 0;
        char *xdg_config = xdg_config_home("config");
-       char *user_config = expand_user_path("~/.gitconfig");
-       char *repo_config = git_pathdup("config");
+       char *user_config = expand_user_path("~/.gitconfig", 0);
+       char *repo_config;
+
+       if (opts->commondir)
+               repo_config = mkpathdup("%s/config", opts->commondir);
+       else
+               repo_config = NULL;
 
        current_parsing_scope = CONFIG_SCOPE_SYSTEM;
        if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0))
@@ -1318,15 +1574,16 @@ static int do_git_config_sequence(config_fn_t fn, void *data)
        return ret;
 }
 
-int git_config_with_options(config_fn_t fn, void *data,
-                           struct git_config_source *config_source,
-                           int respect_includes)
+int config_with_options(config_fn_t fn, void *data,
+                       struct git_config_source *config_source,
+                       const struct config_options *opts)
 {
        struct config_include_data inc = CONFIG_INCLUDE_INIT;
 
-       if (respect_includes) {
+       if (opts->respect_includes) {
                inc.fn = fn;
                inc.data = data;
+               inc.opts = opts;
                fn = git_config_include;
                data = &inc;
        }
@@ -1342,24 +1599,7 @@ int git_config_with_options(config_fn_t fn, void *data,
        else if (config_source && config_source->blob)
                return git_config_from_blob_ref(fn, config_source->blob, data);
 
-       return do_git_config_sequence(fn, data);
-}
-
-static void git_config_raw(config_fn_t fn, void *data)
-{
-       if (git_config_with_options(fn, data, NULL, 1) < 0)
-               /*
-                * git_config_with_options() normally returns only
-                * zero, as most errors are fatal, and
-                * non-fatal potential errors are guarded by "if"
-                * statements that are entered only when no error is
-                * possible.
-                *
-                * If we ever encounter a non-fatal error, it means
-                * something went really wrong and we should stop
-                * immediately.
-                */
-               die(_("unknown error occurred while reading the configuration files"));
+       return do_git_config_sequence(opts, fn, data);
 }
 
 static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
@@ -1385,12 +1625,34 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
        }
 }
 
-static void git_config_check_init(void);
-
-void git_config(config_fn_t fn, void *data)
+void read_early_config(config_fn_t cb, void *data)
 {
-       git_config_check_init();
-       configset_iter(&the_config_set, fn, data);
+       struct config_options opts = {0};
+       struct strbuf commondir = STRBUF_INIT;
+       struct strbuf gitdir = STRBUF_INIT;
+
+       opts.respect_includes = 1;
+
+       if (have_git_dir()) {
+               opts.commondir = get_git_common_dir();
+               opts.git_dir = get_git_dir();
+       /*
+        * When setup_git_directory() was not yet asked to discover the
+        * GIT_DIR, we ask discover_git_directory() to figure out whether there
+        * is any repository config we should use (but unlike
+        * setup_git_directory_gently(), no global state is changed, most
+        * notably, the current working directory is still the same after the
+        * call).
+        */
+       } else if (!discover_git_directory(&commondir, &gitdir)) {
+               opts.commondir = commondir.buf;
+               opts.git_dir = gitdir.buf;
+       }
+
+       config_with_options(cb, data, NULL, &opts);
+
+       strbuf_release(&commondir);
+       strbuf_release(&gitdir);
 }
 
 static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
@@ -1456,15 +1718,20 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
        return 0;
 }
 
-static int config_set_element_cmp(const struct config_set_element *e1,
-                                const struct config_set_element *e2, const void *unused)
+static int config_set_element_cmp(const void *unused_cmp_data,
+                                 const void *entry,
+                                 const void *entry_or_key,
+                                 const void *unused_keydata)
 {
+       const struct config_set_element *e1 = entry;
+       const struct config_set_element *e2 = entry_or_key;
+
        return strcmp(e1->key, e2->key);
 }
 
 void git_configset_init(struct config_set *cs)
 {
-       hashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_set_element_cmp, 0);
+       hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);
        cs->hash_initialized = 1;
        cs->list.nr = 0;
        cs->list.alloc = 0;
@@ -1585,7 +1852,7 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de
 {
        const char *value;
        if (!git_configset_get_value(cs, key, &value)) {
-               *dest = git_config_maybe_bool(key, value);
+               *dest = git_parse_maybe_bool(value);
                if (*dest == -1)
                        return -1;
                return 0;
@@ -1602,89 +1869,248 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha
                return 1;
 }
 
-static void git_config_check_init(void)
+/* Functions use to read configuration from a repository */
+static void repo_read_config(struct repository *repo)
+{
+       struct config_options opts;
+
+       opts.respect_includes = 1;
+       opts.commondir = repo->commondir;
+       opts.git_dir = repo->gitdir;
+
+       if (!repo->config)
+               repo->config = xcalloc(1, sizeof(struct config_set));
+       else
+               git_configset_clear(repo->config);
+
+       git_configset_init(repo->config);
+
+       if (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0)
+               /*
+                * config_with_options() normally returns only
+                * zero, as most errors are fatal, and
+                * non-fatal potential errors are guarded by "if"
+                * statements that are entered only when no error is
+                * possible.
+                *
+                * If we ever encounter a non-fatal error, it means
+                * something went really wrong and we should stop
+                * immediately.
+                */
+               die(_("unknown error occurred while reading the configuration files"));
+}
+
+static void git_config_check_init(struct repository *repo)
 {
-       if (the_config_set.hash_initialized)
+       if (repo->config && repo->config->hash_initialized)
                return;
-       git_configset_init(&the_config_set);
-       git_config_raw(config_set_callback, &the_config_set);
+       repo_read_config(repo);
 }
 
-void git_config_clear(void)
+static void repo_config_clear(struct repository *repo)
 {
-       if (!the_config_set.hash_initialized)
+       if (!repo->config || !repo->config->hash_initialized)
                return;
-       git_configset_clear(&the_config_set);
+       git_configset_clear(repo->config);
 }
 
-int git_config_get_value(const char *key, const char **value)
+void repo_config(struct repository *repo, config_fn_t fn, void *data)
 {
-       git_config_check_init();
-       return git_configset_get_value(&the_config_set, key, value);
+       git_config_check_init(repo);
+       configset_iter(repo->config, fn, data);
 }
 
-const struct string_list *git_config_get_value_multi(const char *key)
+int repo_config_get_value(struct repository *repo,
+                         const char *key, const char **value)
 {
-       git_config_check_init();
-       return git_configset_get_value_multi(&the_config_set, key);
+       git_config_check_init(repo);
+       return git_configset_get_value(repo->config, key, value);
 }
 
-int git_config_get_string_const(const char *key, const char **dest)
+const struct string_list *repo_config_get_value_multi(struct repository *repo,
+                                                     const char *key)
+{
+       git_config_check_init(repo);
+       return git_configset_get_value_multi(repo->config, key);
+}
+
+int repo_config_get_string_const(struct repository *repo,
+                                const char *key, const char **dest)
+{
+       int ret;
+       git_config_check_init(repo);
+       ret = git_configset_get_string_const(repo->config, key, dest);
+       if (ret < 0)
+               git_die_config(key, NULL);
+       return ret;
+}
+
+int repo_config_get_string(struct repository *repo,
+                          const char *key, char **dest)
+{
+       git_config_check_init(repo);
+       return repo_config_get_string_const(repo, key, (const char **)dest);
+}
+
+int repo_config_get_int(struct repository *repo,
+                       const char *key, int *dest)
+{
+       git_config_check_init(repo);
+       return git_configset_get_int(repo->config, key, dest);
+}
+
+int repo_config_get_ulong(struct repository *repo,
+                         const char *key, unsigned long *dest)
+{
+       git_config_check_init(repo);
+       return git_configset_get_ulong(repo->config, key, dest);
+}
+
+int repo_config_get_bool(struct repository *repo,
+                        const char *key, int *dest)
+{
+       git_config_check_init(repo);
+       return git_configset_get_bool(repo->config, key, dest);
+}
+
+int repo_config_get_bool_or_int(struct repository *repo,
+                               const char *key, int *is_bool, int *dest)
+{
+       git_config_check_init(repo);
+       return git_configset_get_bool_or_int(repo->config, key, is_bool, dest);
+}
+
+int repo_config_get_maybe_bool(struct repository *repo,
+                              const char *key, int *dest)
+{
+       git_config_check_init(repo);
+       return git_configset_get_maybe_bool(repo->config, key, dest);
+}
+
+int repo_config_get_pathname(struct repository *repo,
+                            const char *key, const char **dest)
 {
        int ret;
-       git_config_check_init();
-       ret = git_configset_get_string_const(&the_config_set, key, dest);
+       git_config_check_init(repo);
+       ret = git_configset_get_pathname(repo->config, key, dest);
        if (ret < 0)
                git_die_config(key, NULL);
        return ret;
 }
 
+/* Functions used historically to read configuration from 'the_repository' */
+void git_config(config_fn_t fn, void *data)
+{
+       repo_config(the_repository, fn, data);
+}
+
+void git_config_clear(void)
+{
+       repo_config_clear(the_repository);
+}
+
+int git_config_get_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)
+{
+       return repo_config_get_value_multi(the_repository, key);
+}
+
+int git_config_get_string_const(const char *key, const char **dest)
+{
+       return repo_config_get_string_const(the_repository, key, dest);
+}
+
 int git_config_get_string(const char *key, char **dest)
 {
-       git_config_check_init();
-       return git_config_get_string_const(key, (const char **)dest);
+       return repo_config_get_string(the_repository, key, dest);
 }
 
 int git_config_get_int(const char *key, int *dest)
 {
-       git_config_check_init();
-       return git_configset_get_int(&the_config_set, key, dest);
+       return repo_config_get_int(the_repository, key, dest);
 }
 
 int git_config_get_ulong(const char *key, unsigned long *dest)
 {
-       git_config_check_init();
-       return git_configset_get_ulong(&the_config_set, key, dest);
+       return repo_config_get_ulong(the_repository, key, dest);
 }
 
 int git_config_get_bool(const char *key, int *dest)
 {
-       git_config_check_init();
-       return git_configset_get_bool(&the_config_set, key, dest);
+       return repo_config_get_bool(the_repository, key, dest);
 }
 
 int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)
 {
-       git_config_check_init();
-       return git_configset_get_bool_or_int(&the_config_set, key, is_bool, dest);
+       return repo_config_get_bool_or_int(the_repository, key, is_bool, dest);
 }
 
 int git_config_get_maybe_bool(const char *key, int *dest)
 {
-       git_config_check_init();
-       return git_configset_get_maybe_bool(&the_config_set, key, dest);
+       return repo_config_get_maybe_bool(the_repository, key, dest);
 }
 
 int git_config_get_pathname(const char *key, const char **dest)
 {
-       int ret;
-       git_config_check_init();
-       ret = git_configset_get_pathname(&the_config_set, key, dest);
-       if (ret < 0)
-               git_die_config(key, NULL);
+       return repo_config_get_pathname(the_repository, key, dest);
+}
+
+/*
+ * Note: This function exists solely to maintain backward compatibility with
+ * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should
+ * NOT be used anywhere else.
+ *
+ * Runs the provided config function on the '.gitmodules' file found in the
+ * working directory.
+ */
+void config_from_gitmodules(config_fn_t fn, void *data)
+{
+       if (the_repository->worktree) {
+               char *file = repo_worktree_path(the_repository, GITMODULES_FILE);
+               git_config_from_file(fn, file, data);
+               free(file);
+       }
+}
+
+int git_config_get_expiry(const char *key, const char **output)
+{
+       int ret = git_config_get_string_const(key, output);
+       if (ret)
+               return ret;
+       if (strcmp(*output, "now")) {
+               timestamp_t now = approxidate("now");
+               if (approxidate(*output) >= now)
+                       git_die_config(key, _("Invalid %s: '%s'"), key, *output);
+       }
        return ret;
 }
 
+int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestamp_t now)
+{
+       char *expiry_string;
+       intmax_t days;
+       timestamp_t when;
+
+       if (git_config_get_string(key, &expiry_string))
+               return 1; /* no such thing */
+
+       if (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {
+               const int scale = 86400;
+               *expiry = now - days * scale;
+               return 0;
+       }
+
+       if (!parse_expiry_date(expiry_string, &when)) {
+               *expiry = when;
+               return 0;
+       }
+       return -1; /* thing exists but cannot be parsed */
+}
+
 int git_config_get_untracked_cache(void)
 {
        int val = -1;
@@ -1701,14 +2127,39 @@ int git_config_get_untracked_cache(void)
                if (!strcasecmp(v, "keep"))
                        return -1;
 
-               error("unknown core.untrackedCache value '%s'; "
-                     "using 'keep' default value", v);
+               error(_("unknown core.untrackedCache value '%s'; "
+                       "using 'keep' default value"), v);
                return -1;
        }
 
        return -1; /* default value */
 }
 
+int git_config_get_split_index(void)
+{
+       int val;
+
+       if (!git_config_get_maybe_bool("core.splitindex", &val))
+               return val;
+
+       return -1; /* default value */
+}
+
+int git_config_get_max_percent_split_change(void)
+{
+       int val = -1;
+
+       if (!git_config_get_int("splitindex.maxpercentchange", &val)) {
+               if (0 <= val && val <= 100)
+                       return val;
+
+               return error(_("splitIndex.maxPercentChange value '%d' "
+                              "should be between 0 and 100"), val);
+       }
+
+       return -1; /* default value */
+}
+
 NORETURN
 void git_die_config_linenr(const char *key, const char *filename, int linenr)
 {
@@ -1841,10 +2292,11 @@ static int write_error(const char *filename)
        return 4;
 }
 
-static int store_write_section(int fd, const char *key)
+static ssize_t write_section(int fd, const char *key)
 {
        const char *dot;
-       int i, success;
+       int i;
+       ssize_t ret;
        struct strbuf sb = STRBUF_INIT;
 
        dot = memchr(key, '.', store.baselen);
@@ -1860,15 +2312,16 @@ static int store_write_section(int fd, const char *key)
                strbuf_addf(&sb, "[%.*s]\n", store.baselen, key);
        }
 
-       success = write_in_full(fd, sb.buf, sb.len) == sb.len;
+       ret = write_in_full(fd, sb.buf, sb.len);
        strbuf_release(&sb);
 
-       return success;
+       return ret;
 }
 
-static int store_write_pair(int fd, const char *key, const char *value)
+static ssize_t write_pair(int fd, const char *key, const char *value)
 {
-       int i, success;
+       int i;
+       ssize_t ret;
        int length = strlen(key + store.baselen + 1);
        const char *quote = "";
        struct strbuf sb = STRBUF_INIT;
@@ -1908,10 +2361,10 @@ static int store_write_pair(int fd, const char *key, const char *value)
                }
        strbuf_addf(&sb, "%s\n", quote);
 
-       success = write_in_full(fd, sb.buf, sb.len) == sb.len;
+       ret = write_in_full(fd, sb.buf, sb.len);
        strbuf_release(&sb);
 
-       return success;
+       return ret;
 }
 
 static ssize_t find_beginning_of_line(const char *contents, size_t size,
@@ -1962,93 +2415,6 @@ void git_config_set(const char *key, const char *value)
        git_config_set_multivar(key, value, NULL, 0);
 }
 
-/*
- * Auxiliary function to sanity-check and split the key into the section
- * identifier and variable name.
- *
- * Returns 0 on success, -1 when there is an invalid character in the key and
- * -2 if there is no section name in the key.
- *
- * store_key - pointer to char* which will hold a copy of the key with
- *             lowercase section and variable name
- * baselen - pointer to int which will hold the length of the
- *           section + subsection part, can be NULL
- */
-static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
-{
-       int i, dot, baselen;
-       const char *last_dot = strrchr(key, '.');
-
-       /*
-        * Since "key" actually contains the section name and the real
-        * key name separated by a dot, we have to know where the dot is.
-        */
-
-       if (last_dot == NULL || last_dot == key) {
-               if (!quiet)
-                       error("key does not contain a section: %s", key);
-               return -CONFIG_NO_SECTION_OR_NAME;
-       }
-
-       if (!last_dot[1]) {
-               if (!quiet)
-                       error("key does not contain variable name: %s", key);
-               return -CONFIG_NO_SECTION_OR_NAME;
-       }
-
-       baselen = last_dot - key;
-       if (baselen_)
-               *baselen_ = baselen;
-
-       /*
-        * Validate the key and while at it, lower case it for matching.
-        */
-       if (store_key)
-               *store_key = xmallocz(strlen(key));
-
-       dot = 0;
-       for (i = 0; key[i]; i++) {
-               unsigned char c = key[i];
-               if (c == '.')
-                       dot = 1;
-               /* Leave the extended basename untouched.. */
-               if (!dot || i > baselen) {
-                       if (!iskeychar(c) ||
-                           (i == baselen + 1 && !isalpha(c))) {
-                               if (!quiet)
-                                       error("invalid key: %s", key);
-                               goto out_free_ret_1;
-                       }
-                       c = tolower(c);
-               } else if (c == '\n') {
-                       if (!quiet)
-                               error("invalid key (newline): %s", key);
-                       goto out_free_ret_1;
-               }
-               if (store_key)
-                       (*store_key)[i] = c;
-       }
-
-       return 0;
-
-out_free_ret_1:
-       if (store_key) {
-               free(*store_key);
-               *store_key = NULL;
-       }
-       return -CONFIG_INVALID_KEY;
-}
-
-int git_config_parse_key(const char *key, char **store_key, int *baselen)
-{
-       return git_config_parse_key_1(key, store_key, baselen, 0);
-}
-
-int git_config_key_is_valid(const char *key)
-{
-       return !git_config_parse_key_1(key, NULL, NULL, 1);
-}
-
 /*
  * If value==NULL, unset in (remove from) config,
  * if value_regex!=NULL, disregard key/value pairs where value does not match.
@@ -2081,7 +2447,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
 {
        int fd = -1, in_fd = -1;
        int ret;
-       struct lock_file *lock = NULL;
+       struct lock_file lock = LOCK_INIT;
        char *filename_buf = NULL;
        char *contents = NULL;
        size_t contents_sz;
@@ -2100,8 +2466,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
         * The lock serves a purpose in addition to locking: the new
         * contents of .git/config will be written into it.
         */
-       lock = xcalloc(1, sizeof(struct lock_file));
-       fd = hold_lock_file_for_update(lock, config_filename, 0);
+       fd = hold_lock_file_for_update(&lock, config_filename, 0);
        if (fd < 0) {
                error_errno("could not lock config file %s", config_filename);
                free(store.key);
@@ -2128,8 +2493,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                }
 
                store.key = (char *)key;
-               if (!store_write_section(fd, key) ||
-                   !store_write_pair(fd, key, value))
+               if (write_section(fd, key) < 0 ||
+                   write_pair(fd, key, value) < 0)
                        goto write_err_out;
        } else {
                struct stat st;
@@ -2194,7 +2559,12 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                        goto out_free;
                }
 
-               fstat(in_fd, &st);
+               if (fstat(in_fd, &st) == -1) {
+                       error_errno(_("fstat on %s failed"), config_filename);
+                       ret = CONFIG_INVALID_FILE;
+                       goto out_free;
+               }
+
                contents_sz = xsize_t(st.st_size);
                contents = xmmap_gently(NULL, contents_sz, PROT_READ,
                                        MAP_PRIVATE, in_fd, 0);
@@ -2209,8 +2579,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                close(in_fd);
                in_fd = -1;
 
-               if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
-                       error_errno("chmod on %s failed", get_lock_file_path(lock));
+               if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) {
+                       error_errno("chmod on %s failed", get_lock_file_path(&lock));
                        ret = CONFIG_NO_WRITE;
                        goto out_free;
                }
@@ -2234,11 +2604,10 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                        /* write the first part of the config */
                        if (copy_end > copy_begin) {
                                if (write_in_full(fd, contents + copy_begin,
-                                                 copy_end - copy_begin) <
-                                   copy_end - copy_begin)
+                                                 copy_end - copy_begin) < 0)
                                        goto write_err_out;
                                if (new_line &&
-                                   write_str_in_full(fd, "\n") != 1)
+                                   write_str_in_full(fd, "\n") < 0)
                                        goto write_err_out;
                        }
                        copy_begin = store.offset[i];
@@ -2247,46 +2616,36 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                /* write the pair (value == NULL means unset) */
                if (value != NULL) {
                        if (store.state == START) {
-                               if (!store_write_section(fd, key))
+                               if (write_section(fd, key) < 0)
                                        goto write_err_out;
                        }
-                       if (!store_write_pair(fd, key, value))
+                       if (write_pair(fd, key, value) < 0)
                                goto write_err_out;
                }
 
                /* write the rest of the config */
                if (copy_begin < contents_sz)
                        if (write_in_full(fd, contents + copy_begin,
-                                         contents_sz - copy_begin) <
-                           contents_sz - copy_begin)
+                                         contents_sz - copy_begin) < 0)
                                goto write_err_out;
 
                munmap(contents, contents_sz);
                contents = NULL;
        }
 
-       if (commit_lock_file(lock) < 0) {
+       if (commit_lock_file(&lock) < 0) {
                error_errno("could not write config file %s", config_filename);
                ret = CONFIG_NO_WRITE;
-               lock = NULL;
                goto out_free;
        }
 
-       /*
-        * lock is committed, so don't try to roll it back below.
-        * NOTE: Since lockfile.c keeps a linked list of all created
-        * lock_file structures, it isn't safe to free(lock).  It's
-        * better to just leave it hanging around.
-        */
-       lock = NULL;
        ret = 0;
 
        /* Invalidate the config cache */
        git_config_clear();
 
 out_free:
-       if (lock)
-               rollback_lock_file(lock);
+       rollback_lock_file(&lock);
        free(filename_buf);
        if (contents)
                munmap(contents, contents_sz);
@@ -2295,7 +2654,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
        return ret;
 
 write_err_out:
-       ret = write_error(get_lock_file_path(lock));
+       ret = write_error(get_lock_file_path(&lock));
        goto out_free;
 
 }
@@ -2391,12 +2750,12 @@ int git_config_rename_section_in_file(const char *config_filename,
        struct lock_file *lock;
        int out_fd;
        char buf[1024];
-       FILE *config_file;
+       FILE *config_file = NULL;
        struct stat st;
 
        if (new_name && !section_name_is_ok(new_name)) {
                ret = error("invalid section name: %s", new_name);
-               goto out;
+               goto out_no_rollback;
        }
 
        if (!config_filename)
@@ -2410,11 +2769,17 @@ int git_config_rename_section_in_file(const char *config_filename,
        }
 
        if (!(config_file = fopen(config_filename, "rb"))) {
+               ret = warn_on_fopen_errors(config_filename);
+               if (ret)
+                       goto out;
                /* no config file means nothing to rename, no error */
-               goto unlock_and_out;
+               goto commit_and_out;
        }
 
-       fstat(fileno(config_file), &st);
+       if (fstat(fileno(config_file), &st) == -1) {
+               ret = error_errno(_("fstat on %s failed"), config_filename);
+               goto out;
+       }
 
        if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
                ret = error_errno("chmod on %s failed",
@@ -2438,7 +2803,7 @@ int git_config_rename_section_in_file(const char *config_filename,
                                        continue;
                                }
                                store.baselen = strlen(new_name);
-                               if (!store_write_section(out_fd, new_name)) {
+                               if (write_section(out_fd, new_name) < 0) {
                                        ret = write_error(get_lock_file_path(lock));
                                        goto out;
                                }
@@ -2464,17 +2829,22 @@ int git_config_rename_section_in_file(const char *config_filename,
                if (remove)
                        continue;
                length = strlen(output);
-               if (write_in_full(out_fd, output, length) != length) {
+               if (write_in_full(out_fd, output, length) < 0) {
                        ret = write_error(get_lock_file_path(lock));
                        goto out;
                }
        }
        fclose(config_file);
-unlock_and_out:
+       config_file = NULL;
+commit_and_out:
        if (commit_lock_file(lock) < 0)
                ret = error_errno("could not write config file %s",
                                  config_filename);
 out:
+       if (config_file)
+               fclose(config_file);
+       rollback_lock_file(lock);
+out_no_rollback:
        free(filename_buf);
        return ret;
 }
@@ -2499,11 +2869,10 @@ int parse_config_key(const char *var,
                     const char **subsection, int *subsection_len,
                     const char **key)
 {
-       int section_len = strlen(section);
        const char *dot;
 
        /* Does it start with "section." ? */
-       if (!starts_with(var, section) || var[section_len] != '.')
+       if (!skip_prefix(var, section, &var) || *var != '.')
                return -1;
 
        /*
@@ -2515,12 +2884,16 @@ int parse_config_key(const char *var,
        *key = dot + 1;
 
        /* Did we have a subsection at all? */
-       if (dot == var + section_len) {
-               *subsection = NULL;
-               *subsection_len = 0;
+       if (dot == var) {
+               if (subsection) {
+                       *subsection = NULL;
+                       *subsection_len = 0;
+               }
        }
        else {
-               *subsection = var + section_len + 1;
+               if (!subsection)
+                       return -1;
+               *subsection = var + 1;
                *subsection_len = dot - *subsection;
        }