+void repo_config(struct repository *repo, config_fn_t fn, void *data)
+{
+ git_config_check_init(repo);
+ configset_iter(repo->config, fn, data);
+}
+
+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);
+}
+
+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(struct repository *repo,
+ const char *key, char **dest)
+{
+ int ret;
+ git_config_check_init(repo);
+ ret = git_configset_get_string(repo->config, key, dest);
+ if (ret < 0)
+ git_die_config(key, NULL);
+ return ret;
+}
+
+int repo_config_get_string_tmp(struct repository *repo,
+ const char *key, const char **dest)
+{
+ int ret;
+ git_config_check_init(repo);
+ ret = git_configset_get_string_tmp(repo->config, key, dest);
+ if (ret < 0)
+ git_die_config(key, NULL);
+ return ret;
+}
+
+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(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 char *key, char **dest)
+{
+ return repo_config_get_string(the_repository, key, dest);
+}
+
+int git_config_get_string_tmp(const char *key, const char **dest)
+{
+ return repo_config_get_string_tmp(the_repository, key, dest);
+}
+
+int git_config_get_int(const char *key, int *dest)
+{
+ return repo_config_get_int(the_repository, key, dest);
+}
+
+int git_config_get_ulong(const char *key, unsigned long *dest)
+{
+ return repo_config_get_ulong(the_repository, key, dest);
+}
+
+int git_config_get_bool(const char *key, int *dest)
+{
+ return repo_config_get_bool(the_repository, key, dest);
+}
+
+int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)
+{
+ return repo_config_get_bool_or_int(the_repository, key, is_bool, dest);
+}
+
+int git_config_get_maybe_bool(const char *key, int *dest)
+{
+ return repo_config_get_maybe_bool(the_repository, key, dest);
+}
+
+int git_config_get_pathname(const char *key, const char **dest)
+{
+ return repo_config_get_pathname(the_repository, key, dest);
+}
+
+int git_config_get_expiry(const char *key, const char **output)
+{
+ int ret = git_config_get_string(key, (char **)output);
+ if (ret)
+ return ret;
+ if (strcmp(*output, "now")) {
+ timestamp_t now = approxidate("now");
+ if (approxidate(*output) >= now)
+ git_die_config(key, _("Invalid %s: '%s'"), key, *output);
+ }
+ return ret;
+}
+
+int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestamp_t now)
+{
+ const char *expiry_string;
+ intmax_t days;
+ timestamp_t when;
+
+ if (git_config_get_string_tmp(key, &expiry_string))
+ 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_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 */
+}
+
+int git_config_get_fsmonitor(void)
+{
+ if (git_config_get_pathname("core.fsmonitor", &core_fsmonitor))
+ core_fsmonitor = getenv("GIT_TEST_FSMONITOR");
+
+ if (core_fsmonitor && !*core_fsmonitor)
+ core_fsmonitor = NULL;
+
+ if (core_fsmonitor)
+ return 1;
+
+ return 0;
+}
+
+int git_config_get_index_threads(int *dest)
+{
+ int is_bool, val;
+
+ val = git_env_ulong("GIT_TEST_INDEX_THREADS", 0);
+ if (val) {
+ *dest = val;
+ return 0;
+ }
+
+ if (!git_config_get_bool_or_int("index.threads", &is_bool, &val)) {
+ if (is_bool)
+ *dest = val ? 0 : 1;
+ else
+ *dest = val;
+ return 0;
+ }
+
+ return 1;
+}
+
+NORETURN
+void git_die_config_linenr(const char *key, const char *filename, int linenr)
+{
+ if (!filename)
+ die(_("unable to parse '%s' from command-line config"), key);
+ else
+ die(_("bad config variable '%s' in file '%s' at line %d"),
+ key, filename, linenr);
+}
+
+NORETURN __attribute__((format(printf, 2, 3)))
+void git_die_config(const char *key, const char *err, ...)
+{
+ const struct string_list *values;
+ struct key_value_info *kv_info;
+
+ if (err) {
+ va_list params;
+ va_start(params, err);
+ vreportf("error: ", err, params);
+ va_end(params);
+ }
+ values = git_config_get_value_multi(key);
+ kv_info = values->items[values->nr - 1].util;
+ git_die_config_linenr(key, kv_info->filename, kv_info->linenr);
+}
+
+/*
+ * Find all the stuff for git_config_set() below.
+ */
+
+struct config_store_data {
+ size_t baselen;
+ char *key;
+ int do_not_match;
+ const char *fixed_value;
+ regex_t *value_pattern;
+ int multi_replace;
+ struct {
+ size_t begin, end;
+ enum config_event_t type;
+ int is_keys_section;
+ } *parsed;
+ unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;
+ unsigned int key_seen:1, section_seen:1, is_keys_section:1;
+};
+
+static void config_store_data_clear(struct config_store_data *store)
+{
+ free(store->key);
+ if (store->value_pattern != NULL &&
+ store->value_pattern != CONFIG_REGEX_NONE) {
+ regfree(store->value_pattern);
+ free(store->value_pattern);
+ }
+ free(store->parsed);
+ free(store->seen);
+ memset(store, 0, sizeof(*store));
+}
+
+static int matches(const char *key, const char *value,
+ const struct config_store_data *store)
+{
+ if (strcmp(key, store->key))
+ return 0; /* not ours */
+ if (store->fixed_value)
+ return !strcmp(store->fixed_value, value);
+ if (!store->value_pattern)
+ return 1; /* always matches */
+ if (store->value_pattern == CONFIG_REGEX_NONE)
+ return 0; /* never matches */
+
+ return store->do_not_match ^
+ (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)
+{
+ struct config_store_data *store = data;
+
+ ALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);
+ store->parsed[store->parsed_nr].begin = begin;
+ store->parsed[store->parsed_nr].end = end;
+ store->parsed[store->parsed_nr].type = 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 (cf->subsection_case_sensitive)
+ cmpfn = strncasecmp;
+ else
+ cmpfn = strncmp;
+
+ /* 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);
+ if (store->is_keys_section) {
+ store->section_seen = 1;
+ ALLOC_GROW(store->seen, store->seen_nr + 1,
+ store->seen_alloc);
+ store->seen[store->seen_nr] = store->parsed_nr;
+ }
+ }
+
+ store->parsed_nr++;
+
+ return 0;
+}
+
+static int store_aux(const char *key, const char *value, void *cb)
+{
+ struct config_store_data *store = cb;
+
+ if (store->key_seen) {
+ if (matches(key, value, store)) {
+ if (store->seen_nr == 1 && store->multi_replace == 0) {
+ warning(_("%s has multiple values"), key);
+ }
+
+ ALLOC_GROW(store->seen, store->seen_nr + 1,
+ store->seen_alloc);
+
+ store->seen[store->seen_nr] = store->parsed_nr;
+ store->seen_nr++;
+ }
+ } else if (store->is_keys_section) {
+ /*
+ * Do not increment matches yet: this may not be a match, but we
+ * are in the desired section.
+ */
+ ALLOC_GROW(store->seen, store->seen_nr + 1, store->seen_alloc);
+ store->seen[store->seen_nr] = store->parsed_nr;
+ store->section_seen = 1;
+
+ if (matches(key, value, store)) {
+ store->seen_nr++;
+ store->key_seen = 1;
+ }
+ }
+
+ return 0;
+}
+
+static int write_error(const char *filename)
+{
+ error(_("failed to write new configuration file %s"), filename);
+
+ /* Same error code as "failed to rename". */
+ return 4;
+}
+
+static struct strbuf store_create_section(const char *key,
+ const struct config_store_data *store)
+{
+ const char *dot;
+ size_t i;
+ struct strbuf sb = STRBUF_INIT;
+
+ dot = memchr(key, '.', store->baselen);
+ if (dot) {
+ strbuf_addf(&sb, "[%.*s \"", (int)(dot - key), key);
+ for (i = dot - key + 1; i < store->baselen; i++) {
+ if (key[i] == '"' || key[i] == '\\')
+ strbuf_addch(&sb, '\\');
+ strbuf_addch(&sb, key[i]);
+ }
+ strbuf_addstr(&sb, "\"]\n");
+ } else {
+ strbuf_addch(&sb, '[');
+ strbuf_add(&sb, key, store->baselen);
+ strbuf_addstr(&sb, "]\n");
+ }
+
+ return sb;
+}
+
+static ssize_t write_section(int fd, const char *key,
+ const struct config_store_data *store)
+{
+ struct strbuf sb = store_create_section(key, store);
+ ssize_t ret;
+
+ ret = write_in_full(fd, sb.buf, sb.len);
+ strbuf_release(&sb);
+
+ return ret;
+}
+
+static ssize_t write_pair(int fd, const char *key, const char *value,
+ const struct config_store_data *store)
+{
+ int i;
+ ssize_t ret;
+ const char *quote = "";
+ struct strbuf sb = STRBUF_INIT;
+
+ /*
+ * Check to see if the value needs to be surrounded with a dq pair.
+ * Note that problematic characters are always backslash-quoted; this
+ * check is about not losing leading or trailing SP and strings that
+ * follow beginning-of-comment characters (i.e. ';' and '#') by the
+ * configuration parser.
+ */
+ if (value[0] == ' ')
+ quote = "\"";
+ for (i = 0; value[i]; i++)
+ if (value[i] == ';' || value[i] == '#')
+ quote = "\"";
+ if (i && value[i - 1] == ' ')
+ quote = "\"";
+
+ strbuf_addf(&sb, "\t%s = %s", key + store->baselen + 1, quote);
+
+ for (i = 0; value[i]; i++)
+ switch (value[i]) {
+ case '\n':
+ strbuf_addstr(&sb, "\\n");
+ break;
+ case '\t':
+ strbuf_addstr(&sb, "\\t");
+ break;
+ case '"':
+ case '\\':
+ strbuf_addch(&sb, '\\');
+ /* fallthrough */
+ default:
+ strbuf_addch(&sb, value[i]);
+ break;
+ }
+ strbuf_addf(&sb, "%s\n", quote);
+
+ ret = write_in_full(fd, sb.buf, sb.len);
+ strbuf_release(&sb);
+
+ return ret;
+}
+
+/*
+ * If we are about to unset the last key(s) in a section, and if there are
+ * no comments surrounding (or included in) the section, we will want to
+ * extend begin/end to remove the entire section.
+ *
+ * Note: the parameter `seen_ptr` points to the index into the store.seen
+ * array. * This index may be incremented if a section has more than one
+ * entry (which all are to be removed).
+ */
+static void maybe_remove_section(struct config_store_data *store,
+ size_t *begin_offset, size_t *end_offset,
+ int *seen_ptr)
+{
+ size_t begin;
+ int i, seen, section_seen = 0;
+
+ /*
+ * First, ensure that this is the first key, and that there are no
+ * comments before the entry nor before the section header.
+ */
+ seen = *seen_ptr;
+ for (i = store->seen[seen]; i > 0; i--) {
+ enum config_event_t type = store->parsed[i - 1].type;
+
+ if (type == CONFIG_EVENT_COMMENT)
+ /* There is a comment before this entry or section */
+ return;
+ if (type == CONFIG_EVENT_ENTRY) {
+ if (!section_seen)
+ /* This is not the section's first entry. */
+ return;
+ /* We encountered no comment before the section. */
+ break;
+ }
+ if (type == CONFIG_EVENT_SECTION) {
+ if (!store->parsed[i - 1].is_keys_section)
+ break;
+ section_seen = 1;
+ }
+ }
+ begin = store->parsed[i].begin;
+
+ /*
+ * Next, make sure that we are removing he last key(s) in the section,
+ * and that there are no comments that are possibly about the current
+ * section.
+ */
+ for (i = store->seen[seen] + 1; i < store->parsed_nr; i++) {
+ enum config_event_t type = store->parsed[i].type;
+
+ if (type == CONFIG_EVENT_COMMENT)
+ return;
+ if (type == CONFIG_EVENT_SECTION) {
+ if (store->parsed[i].is_keys_section)
+ continue;
+ break;
+ }
+ if (type == CONFIG_EVENT_ENTRY) {
+ if (++seen < store->seen_nr &&
+ i == store->seen[seen])
+ /* We want to remove this entry, too */
+ continue;
+ /* There is another entry in this section. */
+ return;
+ }
+ }
+
+ /*
+ * We are really removing the last entry/entries from this section, and
+ * there are no enclosed or surrounding comments. Remove the entire,
+ * now-empty section.
+ */
+ *seen_ptr = seen;
+ *begin_offset = begin;
+ if (i < store->parsed_nr)
+ *end_offset = store->parsed[i].begin;
+ else
+ *end_offset = store->parsed[store->parsed_nr - 1].end;
+}
+
+int git_config_set_in_file_gently(const char *config_filename,
+ const char *key, const char *value)
+{
+ return git_config_set_multivar_in_file_gently(config_filename, key, value, NULL, 0);
+}
+
+void git_config_set_in_file(const char *config_filename,
+ const char *key, const char *value)
+{
+ git_config_set_multivar_in_file(config_filename, key, value, NULL, 0);
+}
+
+int git_config_set_gently(const char *key, const char *value)
+{
+ return git_config_set_multivar_gently(key, value, NULL, 0);
+}
+
+void git_config_set(const char *key, const char *value)
+{
+ git_config_set_multivar(key, value, NULL, 0);
+
+ trace2_cmd_set_config(key, value);